using System.Globalization; using System.Text.Json; namespace Journal.Core.Services.Logging; public sealed class CommandLogger { public static void LogStart(string action, string correlationId, JsonElement? payload) { var redactedPayload = LogRedactor.RedactPayload(payload); EmitLog("information", action, correlationId, "start", redactedPayload); } public static void LogSuccess(string action, string correlationId) => EmitLog("information", action, correlationId, "success"); public static void LogFailure(string action, string correlationId, string errorType, string? message = null) { var details = string.IsNullOrWhiteSpace(message) ? "" : (message!.Length <= 160 ? message : $"{message[..160]}...(truncated)"); EmitLog("warning", action, correlationId, "failure", errorType: errorType, details: details); } private static void EmitLog( string level, string action, string correlationId, string outcome, object? payload = null, string? errorType = null, string? details = null) { if (!ShouldLog(level)) return; var envelope = new { timestamp = DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture), level, component = "Entry", action, correlation_id = correlationId, outcome, error_type = errorType, details, payload }; Console.Error.WriteLine(JsonSerializer.Serialize(envelope)); } private static bool ShouldLog(string level) { var configured = (Environment.GetEnvironmentVariable("JOURNAL_LOG_LEVEL") ?? "warning") .Trim() .ToLowerInvariant(); var configuredRank = LogLevelRank(configured); var incomingRank = LogLevelRank(level); return incomingRank >= configuredRank; } private static int LogLevelRank(string level) => level switch { "trace" => 0, "debug" => 1, "information" => 2, "info" => 2, "warning" => 3, "warn" => 3, "error" => 4, "critical" => 5, _ => 3 }; }