using System; using System.Collections.Concurrent; namespace Aivfo.OperationLog { /// /// 组件配置(14§10 可配置)。集中持有,支持运行时改(热生效简化为:直接改属性即下次记日志生效)。 /// 模块级开关:默认全开,可按模块名设 开/关 + 最低级别。 /// public sealed class OperationLogOptions { /// 端/服务名(project 字段),如 "operate" / "front"。 public string Project { get; set; } = "operate"; /// Kafka 地址,如 "localhost:9092"。 public string KafkaBootstrapServers { get; set; } = "localhost:9092"; /// Kafka topic。 public string Topic { get; set; } = "tl-oplog"; /// 全局总开关。关掉则完全不记(连入队都跳过)。 public bool Enabled { get; set; } = true; /// /// 全局最低级别。低于此级别的日志不发 Kafka。 /// 默认 Info:只发操作级;调试级(Debug)默认走本地文件、不入 Kafka。 /// public OpLogLevel GlobalLevel { get; set; } = OpLogLevel.Info; /// 内存队列上限。满了走降级(丢弃 Debug / 落本地兜底)。 public int QueueCapacity { get; set; } = 10000; /// 后台发送批量大小。 public int BatchSize { get; set; } = 100; /// 本地文件目录(调试级 + 兜底)。 public string LocalLogDir { get; set; } = "oplog_local"; /// 设备上下文:培养箱 SN(可选,作为全局默认)。 public string TlSn { get; set; } /// 本机 host(默认取机器名)。 public string Host { get; set; } = SafeMachineName(); // ===== G3-3 / 14§10 配置热生效(集中下发的本地落地) ===== /// /// 配置文件路径(JSON)。非空则组件后台轮询该文件,改文件即热生效(不重编)。 /// "集中下发"落地方式:日志微服务/运维把该文件下发/覆盖到各端此路径即可。 /// 为空则关闭配置热加载(仅用代码 Init 时的设置)。 /// public string ConfigFilePath { get; set; } /// 配置文件轮询间隔(秒)。默认 15。 public int ConfigReloadSeconds { get; set; } = 15; // ===== G3-3 / 14§11 本地兜底补送 ===== /// 是否启用 Kafka 恢复后补送本地兜底文件。默认开。 public bool EnableFallbackResend { get; set; } = true; /// 兜底补送轮询间隔(秒)。默认 60。 public int FallbackResendSeconds { get; set; } = 60; /// 每轮补送最多处理的兜底文件数(限流,避免一次扫太多)。默认 5。 public int ResendFilesPerCycle { get; set; } = 5; // 模块级开关:moduleName -> (enabled, minLevel)。缺省即全开、按 GlobalLevel。 private readonly ConcurrentDictionary _modules = new ConcurrentDictionary(); /// 设置某模块的开关与最低级别(运行时可调,热生效)。 public void SetModule(string module, bool enabled, OpLogLevel minLevel = OpLogLevel.Info) { if (string.IsNullOrEmpty(module)) return; _modules[module] = new ModuleSetting { Enabled = enabled, MinLevel = minLevel }; } /// 判断某模块是否启用(总开关 + 模块开关)。关闭则连本地调试文件也不写。 public bool IsModuleEnabled(string module) { if (!Enabled) return false; if (!string.IsNullOrEmpty(module) && _modules.TryGetValue(module, out var s)) return s.Enabled; return true; } /// /// 判断某模块某级别是否应**发 Kafka 入库**(操作级门槛)。 /// 调试级(Debug)天然低于 Info,故默认不发 Kafka、走本地文件——这是 14§4 两级设计。 /// 要让某模块的调试级也入库(很少用),把该模块 MinLevel 设为 Debug。 /// public bool ShouldSendKafka(string module, OpLogLevel level) { if (!IsModuleEnabled(module)) return false; if (!string.IsNullOrEmpty(module) && _modules.TryGetValue(module, out var s)) return level >= s.MinLevel; return level >= GlobalLevel; } private static string SafeMachineName() { try { return Environment.MachineName; } catch { return "unknown"; } } /// /// 应用一份 JSON 配置(热生效)。容错:任意字段缺失/格式错都忽略该字段,不抛。 /// 形如:{ "enabled":true, "globalLevel":"Info", /// "modules":{ "串口":{"enabled":true,"minLevel":"Debug"}, "对焦调试":{"enabled":true} } } /// public void ApplyConfigJson(string json) { if (string.IsNullOrWhiteSpace(json)) return; try { var root = Newtonsoft.Json.Linq.JObject.Parse(json); var en = root["enabled"]; if (en != null && en.Type == Newtonsoft.Json.Linq.JTokenType.Boolean) Enabled = (bool)en; var gl = root["globalLevel"]; if (gl != null && TryParseLevel(gl.ToString(), out var glLevel)) GlobalLevel = glLevel; var modules = root["modules"] as Newtonsoft.Json.Linq.JObject; if (modules != null) { foreach (var prop in modules.Properties()) { var name = prop.Name; if (string.IsNullOrEmpty(name)) continue; var m = prop.Value as Newtonsoft.Json.Linq.JObject; if (m == null) continue; // 模块缺省:enabled=true、minLevel 跟随全局。 bool mEnabled = true; var me = m["enabled"]; if (me != null && me.Type == Newtonsoft.Json.Linq.JTokenType.Boolean) mEnabled = (bool)me; var minLevel = GlobalLevel; var ml = m["minLevel"]; if (ml != null) TryParseLevel(ml.ToString(), out minLevel); SetModule(name, mEnabled, minLevel); } } } catch { /* 配置解析失败不影响业务,沿用旧配置 */ } } /// 解析级别字符串(Debug/Info,大小写不敏感)。 private static bool TryParseLevel(string s, out OpLogLevel level) { level = OpLogLevel.Info; if (string.IsNullOrWhiteSpace(s)) return false; switch (s.Trim().ToLowerInvariant()) { case "debug": case "0": level = OpLogLevel.Debug; return true; case "info": case "1": level = OpLogLevel.Info; return true; default: return false; } } private sealed class ModuleSetting { public bool Enabled; public OpLogLevel MinLevel; } } }