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 { /* 本地写失败也不能拖垮业务 */ }
}
// ===== G3-3 / 14§11 兜底补送原语 =====
/// 列出待补送的兜底文件(不含正在补送的 .resending)。按文件名升序≈时间升序。
public string[] ListFallbackFiles()
{
try
{
lock (_lock)
{
if (!Directory.Exists(_dir)) return Array.Empty();
var files = Directory.GetFiles(_dir, "oplog-fallback-*.log");
Array.Sort(files, StringComparer.OrdinalIgnoreCase);
return files;
}
}
catch { return Array.Empty(); }
}
///
/// 认领一个兜底文件:原子重命名为 .resending(避免与 append 竞争、避免并发重复补送)。
/// 成功返回新路径,失败(已被认领/被占用)返回 null。
///
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; }
}
/// 读取认领文件的所有非空行。
public string[] ReadLines(string path)
{
try { return File.ReadAllLines(path, Encoding.UTF8); }
catch { return Array.Empty(); }
}
/// 删除文件(补送成功后清理认领文件)。
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 + ")";
}
}
}