mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-26 13:39:41 +00:00
feat(vuln): emit VulnerabilityCacheWarmupCompleted customEvent per cold start
Trace-level warmup logs are dropped by default App Insights sampling, so ops cannot reliably tell whether the MSRC / KEV cache hydration ran cleanly on a given cold start. Add a sampling-bypass customEvent (host.json already excludes "Event" from sampling) emitted once per warmup run. Captured properties let ops verify runtime health at a glance: - msrcEntries / kevEntries — how many entries each cache holds after hydrate - msrcBlobHit / kevBlobHit — derived from entries > 0; false on first deploy before any sync has written the blob, true on every subsequent cold start - msrcLastRefresh / kevLastRefresh — ISO timestamp from the persisted snapshot - durationMs — total warmup wall time (parallel Task.WhenAll, typically <100ms) - errorType / errorMessage — populated only if Task.WhenAll threw Query: customEvents | where name == 'VulnerabilityCacheWarmupCompleted' | project timestamp, customDimensions | order by timestamp desc Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+39
@@ -1,6 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.ApplicationInsights;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -12,20 +15,31 @@ namespace AutopilotMonitor.Functions.Services.Vulnerability
|
||||
/// host comes up immediately — the actual blob reads run in the background and the
|
||||
/// correlation hot path defensively calls <c>EnsureHydratedAsync</c> in case telemetry
|
||||
/// arrives before warmup completes.
|
||||
///
|
||||
/// Emits a <c>VulnerabilityCacheWarmupCompleted</c> custom event per cold start so
|
||||
/// runtime health is observable without relying on aggressively sampled trace logs:
|
||||
/// <code>
|
||||
/// customEvents
|
||||
/// | where name == 'VulnerabilityCacheWarmupCompleted'
|
||||
/// | project timestamp, customDimensions
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public class VulnerabilityCacheWarmer : IHostedService
|
||||
{
|
||||
private readonly MsrcCorrelationService _msrc;
|
||||
private readonly KevDataService _kev;
|
||||
private readonly TelemetryClient? _telemetryClient;
|
||||
private readonly ILogger<VulnerabilityCacheWarmer> _logger;
|
||||
|
||||
public VulnerabilityCacheWarmer(
|
||||
MsrcCorrelationService msrc,
|
||||
KevDataService kev,
|
||||
TelemetryClient? telemetryClient,
|
||||
ILogger<VulnerabilityCacheWarmer> logger)
|
||||
{
|
||||
_msrc = msrc;
|
||||
_kev = kev;
|
||||
_telemetryClient = telemetryClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -37,6 +51,9 @@ namespace AutopilotMonitor.Functions.Services.Vulnerability
|
||||
// in-flight load instead of duplicating work.
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
string? errorType = null;
|
||||
string? errorMessage = null;
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(
|
||||
@@ -48,8 +65,30 @@ namespace AutopilotMonitor.Functions.Services.Vulnerability
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorType = ex.GetType().Name;
|
||||
errorMessage = ex.Message;
|
||||
_logger.LogWarning(ex, "Vulnerability cache warmup failed — defensive hydration will still cover correctness");
|
||||
}
|
||||
finally
|
||||
{
|
||||
sw.Stop();
|
||||
// Sampling-bypass observability — host.json's excludedTypes adds "Event"
|
||||
// so this survives even when default trace sampling is aggressive. Lets
|
||||
// ops verify per-cold-start that hydration ran and which source(s) hit
|
||||
// the blob (msrcBlobHit/kevBlobHit = entries > 0 after EnsureHydrated).
|
||||
_telemetryClient?.TrackEvent("VulnerabilityCacheWarmupCompleted", new Dictionary<string, string>
|
||||
{
|
||||
{ "msrcEntries", _msrc.IndexSize.ToString() },
|
||||
{ "kevEntries", _kev.CatalogSize.ToString() },
|
||||
{ "msrcBlobHit", (_msrc.IndexSize > 0).ToString() },
|
||||
{ "kevBlobHit", (_kev.CatalogSize > 0).ToString() },
|
||||
{ "msrcLastRefresh", _msrc.LastRefresh == DateTime.MinValue ? "" : _msrc.LastRefresh.ToString("o") },
|
||||
{ "kevLastRefresh", _kev.LastRefresh == DateTime.MinValue ? "" : _kev.LastRefresh.ToString("o") },
|
||||
{ "durationMs", sw.ElapsedMilliseconds.ToString() },
|
||||
{ "errorType", errorType ?? "" },
|
||||
{ "errorMessage", errorMessage ?? "" }
|
||||
});
|
||||
}
|
||||
}, cancellationToken);
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
||||
Reference in New Issue
Block a user