| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114 |
- using System;
- using System.IO;
- using System.Text;
- namespace Aivfo.OperationLog
- {
- /// <summary>
- /// 本地文件写入:调试级日志(14§4)+ 运行兜底(Kafka 发不出、队列满)+ 组件自身错误。
- /// 按天滚动;内部加锁串行写;全 try 兜底,绝不抛给业务。
- /// </summary>
- 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 */ }
- }
- /// <summary>调试级一行(结构化文本,不进 Kafka)。</summary>
- 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);
- }
- /// <summary>兜底:完整 JSON 落本地(Kafka 不可用 / 队列满丢弃前保留)。</summary>
- public void WriteFallback(string json)
- {
- Write("fallback", json);
- }
- /// <summary>组件自身错误(连不上 Kafka、序列化失败等)。</summary>
- 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 { /* 本地写失败也不能拖垮业务 */ }
- }
- // ===== G3-3 / 14§11 兜底补送原语 =====
- /// <summary>列出待补送的兜底文件(不含正在补送的 .resending)。按文件名升序≈时间升序。</summary>
- public string[] ListFallbackFiles()
- {
- try
- {
- lock (_lock)
- {
- if (!Directory.Exists(_dir)) return Array.Empty<string>();
- var files = Directory.GetFiles(_dir, "oplog-fallback-*.log");
- Array.Sort(files, StringComparer.OrdinalIgnoreCase);
- return files;
- }
- }
- catch { return Array.Empty<string>(); }
- }
- /// <summary>
- /// 认领一个兜底文件:原子重命名为 .resending(避免与 append 竞争、避免并发重复补送)。
- /// 成功返回新路径,失败(已被认领/被占用)返回 null。
- /// </summary>
- public string ClaimFallback(string path)
- {
- try
- {
- lock (_lock)
- {
- if (!File.Exists(path)) return null;
- var dst = path + "." + DateTime.Now.ToString("HHmmssfff") + ".resending";
- File.Move(path, dst);
- return dst;
- }
- }
- catch { return null; }
- }
- /// <summary>读取认领文件的所有非空行。</summary>
- public string[] ReadLines(string path)
- {
- try { return File.ReadAllLines(path, Encoding.UTF8); }
- catch { return Array.Empty<string>(); }
- }
- /// <summary>删除文件(补送成功后清理认领文件)。</summary>
- public void DeleteQuietly(string path)
- {
- try { if (File.Exists(path)) File.Delete(path); }
- catch { /* ignore */ }
- }
- 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 + ")";
- }
- }
- }
|