using System.Diagnostics.Metrics; namespace StellaOps.Concelier.Connector.Kisa.Internal; public sealed class KisaDiagnostics : IDisposable { public const string MeterName = "StellaOps.Concelier.Connector.Kisa"; private const string MeterVersion = "1.0.0"; private readonly Meter _meter; private readonly Counter _feedAttempts; private readonly Counter _feedSuccess; private readonly Counter _feedFailures; private readonly Counter _feedItems; private readonly Counter _detailAttempts; private readonly Counter _detailSuccess; private readonly Counter _detailUnchanged; private readonly Counter _detailFailures; private readonly Counter _parseAttempts; private readonly Counter _parseSuccess; private readonly Counter _parseFailures; private readonly Counter _mapSuccess; private readonly Counter _mapFailures; private readonly Counter _cursorUpdates; public KisaDiagnostics() { _meter = new Meter(MeterName, MeterVersion); _feedAttempts = _meter.CreateCounter( name: "kisa.feed.attempts", unit: "operations", description: "Number of RSS fetch attempts performed for the KISA connector."); _feedSuccess = _meter.CreateCounter( name: "kisa.feed.success", unit: "operations", description: "Number of RSS fetch attempts that completed successfully."); _feedFailures = _meter.CreateCounter( name: "kisa.feed.failures", unit: "operations", description: "Number of RSS fetch attempts that failed."); _feedItems = _meter.CreateCounter( name: "kisa.feed.items", unit: "items", description: "Number of feed items returned by successful RSS fetches."); _detailAttempts = _meter.CreateCounter( name: "kisa.detail.attempts", unit: "documents", description: "Number of advisory detail fetch attempts."); _detailSuccess = _meter.CreateCounter( name: "kisa.detail.success", unit: "documents", description: "Number of advisory detail documents fetched successfully."); _detailUnchanged = _meter.CreateCounter( name: "kisa.detail.unchanged", unit: "documents", description: "Number of advisory detail fetches that returned HTTP 304 (no change)."); _detailFailures = _meter.CreateCounter( name: "kisa.detail.failures", unit: "documents", description: "Number of advisory detail fetch attempts that failed."); _parseAttempts = _meter.CreateCounter( name: "kisa.parse.attempts", unit: "documents", description: "Number of advisory documents queued for parsing."); _parseSuccess = _meter.CreateCounter( name: "kisa.parse.success", unit: "documents", description: "Number of advisory documents parsed successfully into DTOs."); _parseFailures = _meter.CreateCounter( name: "kisa.parse.failures", unit: "documents", description: "Number of advisory documents that failed parsing."); _mapSuccess = _meter.CreateCounter( name: "kisa.map.success", unit: "advisories", description: "Number of canonical advisories produced by the mapper."); _mapFailures = _meter.CreateCounter( name: "kisa.map.failures", unit: "advisories", description: "Number of advisories that failed to map."); _cursorUpdates = _meter.CreateCounter( name: "kisa.cursor.updates", unit: "updates", description: "Number of times the published cursor advanced."); } public void FeedAttempt() => _feedAttempts.Add(1); public void FeedSuccess(int itemCount) { _feedSuccess.Add(1); if (itemCount > 0) { _feedItems.Add(itemCount); } } public void FeedFailure(string reason) => _feedFailures.Add(1, GetReasonTags(reason)); public void DetailAttempt(string? category) => _detailAttempts.Add(1, GetCategoryTags(category)); public void DetailSuccess(string? category) => _detailSuccess.Add(1, GetCategoryTags(category)); public void DetailUnchanged(string? category) => _detailUnchanged.Add(1, GetCategoryTags(category)); public void DetailFailure(string? category, string reason) => _detailFailures.Add(1, GetCategoryReasonTags(category, reason)); public void ParseAttempt(string? category) => _parseAttempts.Add(1, GetCategoryTags(category)); public void ParseSuccess(string? category) => _parseSuccess.Add(1, GetCategoryTags(category)); public void ParseFailure(string? category, string reason) => _parseFailures.Add(1, GetCategoryReasonTags(category, reason)); public void MapSuccess(string? severity) => _mapSuccess.Add(1, GetSeverityTags(severity)); public void MapFailure(string? severity, string reason) => _mapFailures.Add(1, GetSeverityReasonTags(severity, reason)); public void CursorAdvanced() => _cursorUpdates.Add(1); public Meter Meter => _meter; public void Dispose() => _meter.Dispose(); private static KeyValuePair[] GetCategoryTags(string? category) => new[] { new KeyValuePair("category", Normalize(category)) }; private static KeyValuePair[] GetCategoryReasonTags(string? category, string reason) => new[] { new KeyValuePair("category", Normalize(category)), new KeyValuePair("reason", Normalize(reason)), }; private static KeyValuePair[] GetSeverityTags(string? severity) => new[] { new KeyValuePair("severity", Normalize(severity)), }; private static KeyValuePair[] GetSeverityReasonTags(string? severity, string reason) => new[] { new KeyValuePair("severity", Normalize(severity)), new KeyValuePair("reason", Normalize(reason)), }; private static KeyValuePair[] GetReasonTags(string reason) => new[] { new KeyValuePair("reason", Normalize(reason)), }; private static string Normalize(string? value) => string.IsNullOrWhiteSpace(value) ? "unknown" : value!; }