using System.Text.Json; namespace Journal.Core.Services.Logging; public static class LogRedactor { private static readonly HashSet SensitiveKeys = new(StringComparer.OrdinalIgnoreCase) { "password", "passphrase", "secret", "token", "apiKey", "api_key", "cloudAiApiKey", "content", "rawContent", "prompt", "audioBase64", "audio_base64", "text" }; public static object? RedactPayload(JsonElement? payload) { if (payload is null) return null; return RedactElement(payload.Value, parentKey: null); } private static object? RedactElement(JsonElement element, string? parentKey) { if (parentKey is not null && SensitiveKeys.Contains(parentKey)) return "[REDACTED]"; return element.ValueKind switch { JsonValueKind.Object => RedactObject(element), JsonValueKind.Array => RedactArray(element), JsonValueKind.String => RedactString(element.GetString() ?? "", parentKey), JsonValueKind.Number => element.GetRawText(), JsonValueKind.True => true, JsonValueKind.False => false, JsonValueKind.Null => null, _ => element.GetRawText() }; } private static Dictionary RedactObject(JsonElement element) { var output = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var property in element.EnumerateObject()) output[property.Name] = RedactElement(property.Value, property.Name); return output; } private static List RedactArray(JsonElement element) { var output = new List(); foreach (var item in element.EnumerateArray()) output.Add(RedactElement(item, parentKey: null)); return output; } private static object RedactString(string value, string? key) { if (key is not null && SensitiveKeys.Contains(key)) return "[REDACTED]"; if (value.Length <= 128) return value; return $"{value[..128]}...(truncated)"; } }