using System; using System.IO; using System.Text.Json; using JXHelper.Core; namespace JXHelper.Common; public record TelemetryEvent( string Timestamp, string EventName, string State, int TargetPid, string Details ); /// /// Ghi nhận nhật ký có cấu trúc và viễn trắc (Telemetry) ra đĩa. /// Tuân thủ bất biến: INV-STRUCTURED-LOG-STREAM. /// Sử dụng FileShare.ReadWrite để dev/agent có thể đọc stream live liên tục không bị lock file. /// public static class TelemetryLogger { private static readonly object LockObj = new(); private static readonly string LogDir; private static readonly string JsonlPath; private static readonly string ActionsLogPath; static TelemetryLogger() { // Thư mục logs nằm tại root workspace hoặc cạnh binary string baseDir = AppDomain.CurrentDomain.BaseDirectory; // Thử tìm root workspace nếu đang chạy trong src/JXHelper/bin... string candidate = Path.GetFullPath(Path.Combine(baseDir, "..", "..", "..", "..", "..", "logs")); if (Directory.Exists(Path.GetDirectoryName(candidate))) { LogDir = candidate; } else { LogDir = Path.Combine(baseDir, "logs"); } Directory.CreateDirectory(LogDir); JsonlPath = Path.Combine(LogDir, "telemetry.jsonl"); ActionsLogPath = Path.Combine(LogDir, "actions.log"); } public static void LogEvent(string eventName, BotState state, int targetPid, string details) { var evt = new TelemetryEvent( DateTime.UtcNow.ToString("o"), eventName, state.ToString(), targetPid, details ); string jsonLine = JsonSerializer.Serialize(evt) + Environment.NewLine; string textLine = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] [{state}] (PID:{targetPid}) {eventName}: {details}{Environment.NewLine}"; lock (LockObj) { try { using var jsonStream = new FileStream(JsonlPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); using var writer = new StreamWriter(jsonStream); writer.Write(jsonLine); } catch { /* non-blocking */ } try { using var textStream = new FileStream(ActionsLogPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); using var writer = new StreamWriter(textStream); writer.Write(textLine); } catch { /* non-blocking */ } } } public static void LogAction(string action) { lock (LockObj) { try { using var fs = new FileStream(ActionsLogPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); using var sw = new StreamWriter(fs); sw.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {action}"); } catch { /* non-blocking */ } } } }