using System; using System.IO; using System.Text; namespace Aivfo.OperationLog { /// /// 本地文件写入:调试级日志(14§4)+ 运行兜底(Kafka 发不出、队列满)+ 组件自身错误。 /// 按天滚动;内部加锁串行写;全 try 兜底,绝不抛给业务。 /// internal sealed class LocalFileWriter { private readonly string _dir; private readonly object _lock = new object(); public LocalFileWriter(string dir) { _dir = string.IsNullOrEmpty(dir) ? "oplog_local" : dir; try { Directory.CreateDirectory(_dir); } catch { /* ignore */ } } /// 调试级一行(结构化文本,不进 Kafka)。 public void WriteDebug(OperationLogMessage m) { var line = $"{DateTimeOffset.FromUnixTimeMilliseconds(m.Time):yyyy-MM-dd HH:mm:ss.fff}\t" + $"[DEBUG]\ttrace={m.TraceId}\tmodule={m.Module}\top={m.Operation}\t" + $"house={m.HouseSn}\twell={m.WellSn}\tresult={m.Result}\t" + $"in={Truncate(m.Input)}\tout={Truncate(m.Output)}\terr={m.Error}"; Write("debug", line); } /// 兜底:完整 JSON 落本地(Kafka 不可用 / 队列满丢弃前保留)。 public void WriteFallback(string json) { Write("fallback", json); } /// 组件自身错误(连不上 Kafka、序列化失败等)。 public void WriteSelfError(string msg) { Write("error", $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}\t{msg}"); } private void Write(string kind, string line) { try { var path = Path.Combine(_dir, $"oplog-{kind}-{DateTime.Now:yyyyMMdd}.log"); lock (_lock) { File.AppendAllText(path, line + Environment.NewLine, Encoding.UTF8); } } catch { /* 本地写失败也不能拖垮业务 */ } } private static string Truncate(string s, int max = 500) { if (string.IsNullOrEmpty(s)) return s; return s.Length <= max ? s : s.Substring(0, max) + "...(" + s.Length + ")"; } } }