using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using StellaOps.Scanner.Worker.Diagnostics; namespace StellaOps.Scanner.Worker.Processing; public sealed partial class ScanProgressReporter { private readonly ScannerWorkerMetrics _metrics; private readonly ILogger _logger; public ScanProgressReporter(ScannerWorkerMetrics metrics, ILogger logger) { _metrics = metrics ?? throw new ArgumentNullException(nameof(metrics)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async ValueTask ExecuteStageAsync( ScanJobContext context, string stageName, Func stageWork, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(context); ArgumentException.ThrowIfNullOrWhiteSpace(stageName); ArgumentNullException.ThrowIfNull(stageWork); StageStarting(_logger, context.JobId, context.ScanId, stageName, context.Lease.Attempt); var start = context.TimeProvider.GetUtcNow(); using var activity = ScannerWorkerInstrumentation.ActivitySource.StartActivity( $"scanner.worker.{stageName}", ActivityKind.Internal); activity?.SetTag("scanner.worker.job_id", context.JobId); activity?.SetTag("scanner.worker.scan_id", context.ScanId); activity?.SetTag("scanner.worker.stage", stageName); try { await stageWork(context, cancellationToken).ConfigureAwait(false); var duration = context.TimeProvider.GetUtcNow() - start; _metrics.RecordStageDuration(context, stageName, duration); StageCompleted(_logger, context.JobId, context.ScanId, stageName, duration.TotalMilliseconds); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { StageCancelled(_logger, context.JobId, context.ScanId, stageName); throw; } catch (Exception ex) { var duration = context.TimeProvider.GetUtcNow() - start; _metrics.RecordStageDuration(context, stageName, duration); StageFailed(_logger, context.JobId, context.ScanId, stageName, ex); throw; } } [LoggerMessage( EventId = 1000, Level = LogLevel.Information, Message = "Job {JobId} (scan {ScanId}) entering stage {Stage} (attempt {Attempt}).")] private static partial void StageStarting(ILogger logger, string jobId, string scanId, string stage, int attempt); [LoggerMessage( EventId = 1001, Level = LogLevel.Information, Message = "Job {JobId} (scan {ScanId}) finished stage {Stage} in {ElapsedMs:F0} ms.")] private static partial void StageCompleted(ILogger logger, string jobId, string scanId, string stage, double elapsedMs); [LoggerMessage( EventId = 1002, Level = LogLevel.Warning, Message = "Job {JobId} (scan {ScanId}) stage {Stage} cancelled by request.")] private static partial void StageCancelled(ILogger logger, string jobId, string scanId, string stage); [LoggerMessage( EventId = 1003, Level = LogLevel.Error, Message = "Job {JobId} (scan {ScanId}) stage {Stage} failed.")] private static partial void StageFailed(ILogger logger, string jobId, string scanId, string stage, Exception exception); }