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;
}
}
}