using System;
using System.Globalization;
using System.IO;
using System.Text;
namespace TeamAAS.Logging
{
///
/// 插件日志路径/行为配置(挂在 SystemConfiguration.PluginLog 下统一配置管理)。
/// 路径公式支持的占位符:{root} {flow} {task} {year} {month} {day} {time} {timestamp}
/// {time}=yyyy-MM-dd {timestamp}=yyyyMMdd_HHmmss {flow}=流程名 {task}=任务名
///
[Serializable]
public class PluginLogConfig
{
/// 日志根目录:程序目录下的相对子目录(默认 Logs),也可填绝对路径。
public string RootPath { get; set; } = "Logs";
/// 公共日志相对路径公式(1/2 级写入;默认 年/时间.log)。
public string CommonPattern { get; set; } = @"{year}\{time}.log";
/// 任务日志相对路径公式(3/4 级写入;默认 流程名\任务名\年\时间.log)。
public string TaskPattern { get; set; } = @"{year}\{time}\{flow}\{task}\{time}.log";
/// 新建插件未显式设置时的默认「存储详细度」:粗略=只写 L1/L2 公共日志;详细=额外写 L3/L4 到独立路径。
public LogVerbosity DefaultStoreVerbosity { get; set; } = LogVerbosity.粗略;
/// 新建插件未显式设置时的默认「显示详细度」:粗略=实时日志只显 L1/L2;详细=额外显 L3/L4。
public LogVerbosity DefaultDisplayVerbosity { get; set; } = LogVerbosity.粗略;
/// 单个日志文件最大 MB,超过后由 NLog 自动归档(0 或负数=不按大小切分)。
public int MaxFileMB { get; set; } = 10;
/// 日志保留天数,超期的归档文件由 NLog 自动删除(MaxArchiveDays;0 或负数=不删除)。
public int RetentionDays { get; set; } = 3650;
public PluginLogConfig Clone()
=> (PluginLogConfig)MemberwiseClone();
}
///
/// 插件日志核心:全局配置 + 阈值过滤 + 等级路由,文件写入统一交给 NLog(NLogHub)。
///
/// 路由规则(等级即详细度档位,与 Debug/Info 严重级别无关):
/// - 1 低级 / 2 中级:写公共日志(CommonPattern,与 AppLogger 合并到同一个文件);
/// - 3 高级 / 4 终极:写任务日志(TaskPattern,按 流程名/任务名 分目录)。
///
/// 两道独立闸门:存储(level ≤ 节点存储详细度才写盘)与显示(level ≤ 节点显示详细度才进实时控件),互不影响。
/// 单文件大小归档与保留天数清理均由 NLog 原生实现(见 NLogHub)。
/// 写失败静默吞掉(回退 Debug 输出),绝不影响业务流程。
///
public static class PluginLogger
{
private static PluginLogConfig _config = new PluginLogConfig();
/// 应用全局配置(启动加载/保存系统配置时调用)。
public static void Configure(PluginLogConfig config)
{
_config = config ?? new PluginLogConfig();
NLogHub.Configure(_config);
}
/// 当前生效配置(只读快照语义,UI 展示用)。
public static PluginLogConfig Current => _config;
///
/// 写一条插件日志(唯一入口,静态直调)。两道相互独立的闸门:
/// flow/task:所属流程名/任务名(3/4 级任务日志分目录用,1/2 级忽略);
/// storeVerbosity:该节点「存储详细度」——level ≤ 它才写盘(L1/L2→公共日志,L3/L4→流程\任务 独立路径);
/// displayVerbosity:该节点「显示详细度」——level ≤ 它才进实时日志控件(与是否落盘无关,可显而不存);
/// level:本条日志的详细度标签(1低级/2中级/3高级/4终极);
/// category:日志类别,默认 Info,只作为条目标记不影响存/显。
///
public static void Log(string flow, string task, LogVerbosity storeVerbosity, LogVerbosity displayVerbosity,
int level, string message, LogLevel category = LogLevel.Info)
{
if (level <= 0) return;
// 闸门A·存储:level ≤ 存储详细度 才写文件(1/2 公共日志,3/4 流程/任务 独立路径)
if (level <= (int)storeVerbosity)
{
var line = FormatLine(flow, task, level, category, message);
try
{
NLogHub.Apply();
if (level <= (int)PluginLogLevel.中级)
{
// 1/2 级:公共日志(与 AppLogger 写入同一个 CommonPattern 文件)
NLogHub.Common.Log(MapNLogLevel(category), line);
}
else
{
// 3/4 级:任务日志,按 流程名/任务名 分目录(路径由 NLog 依据 TaskPattern 解析)
var ev = NLog.LogEventInfo.Create(MapNLogLevel(category), NLogHub.TaskLoggerName, line);
ev.Properties[NLogHub.FlowPropertyName] = Sanitize(flow, "未命名流程");
ev.Properties[NLogHub.TaskPropertyName] = Sanitize(task, "未命名任务");
NLogHub.Task.Log(ev);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("[PluginLogger] write failed: " + ex.Message);
}
}
// 闸门B·显示:level ≤ 显示详细度 才联动实时日志控件(独立于存储,可显而不存)
if (level <= (int)displayVerbosity)
{
LogStream.Publish(new LogEntry(
DateTime.Now, LogSource.Plugin, category,
level: level, flow: flow, task: task, message: message));
}
}
private static NLog.LogLevel MapNLogLevel(LogLevel category)
{
switch (category)
{
case LogLevel.Warning: return NLog.LogLevel.Warn;
case LogLevel.Error: return NLog.LogLevel.Error;
default: return NLog.LogLevel.Info;
}
}
private static string FormatLine(string flow, string task, int level, LogLevel category, string message)
{
var sb = new StringBuilder();
sb.Append(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture));
sb.Append(" [L").Append(level).Append(']');
sb.Append(" [").Append(category.ToString().ToUpperInvariant()).Append(']');
sb.Append(" [T").Append(Environment.CurrentManagedThreadId).Append(']');
if (!string.IsNullOrEmpty(flow)) sb.Append('[').Append(flow).Append(']');
if (!string.IsNullOrEmpty(task)) sb.Append('[').Append(task).Append(']');
sb.Append(' ').Append((message ?? "").Replace(Environment.NewLine, " | "));
return sb.ToString();
}
/// 清洗流程/任务名中的文件路径非法字符(作为 NLog 事件属性写入路径前调用)。
private static string Sanitize(string name, string fallback)
{
if (string.IsNullOrWhiteSpace(name)) return fallback;
var sb = new StringBuilder(name.Length);
foreach (var c in name)
{
if (Array.IndexOf(Path.GetInvalidFileNameChars(), c) >= 0 || c == '\\' || c == '/')
sb.Append('_');
else
sb.Append(c);
}
return sb.ToString();
}
}
}