using System; using System.IO; using System.Text; using NLog; using NLog.Config; using NLog.Targets; namespace TeamAAS.Logging { /// /// NLog 统一接入点:全应用只此一份 NLog 配置,挂两个文件目标—— /// 公共日志(Common):路径由 CommonPattern 解析(年/日期滚动),AppLogger 与插件 1/2 级日志共用; /// 任务日志(Task):路径由 TaskPattern 解析,流程名/任务名由每条日志的事件属性带入。 /// 单文件大小归档(ArchiveAboveSize)与保留天数自动删除(MaxArchiveDays)均由 NLog 原生完成。 /// 行格式由调用侧拼好后以 ${message} 原样写入。 /// internal static class NLogHub { /// 公共日志器名(AppLogger + 插件 1/2 级共用)。 public const string CommonLoggerName = "TeamAAS.Common"; /// 任务日志器名(插件 3/4 级用)。 public const string TaskLoggerName = "TeamAAS.Task"; /// 流程名事件属性名(TaskPattern 中的 {flow})。 public const string FlowPropertyName = "flow"; /// 任务名事件属性名(TaskPattern 中的 {task})。 public const string TaskPropertyName = "task"; private static readonly object Gate = new object(); private static PluginLogConfig _config; private static string _appliedSignature; /// 下发日志配置(启动加载/保存系统配置时调用),配置变化时重建 NLog 目标。 public static void Configure(PluginLogConfig config) { lock (Gate) { _config = config ?? new PluginLogConfig(); _appliedSignature = null; // 强制下次 Apply 重建 } Apply(); } /// 确保 NLog 配置已就绪(未下发时用默认配置);配置未变则幂等返回。 public static void Apply() { lock (Gate) { var cfg = _config ?? (_config = new PluginLogConfig()); var signature = BuildSignature(cfg); if (signature == _appliedSignature && LogManager.Configuration != null) return; var rootDir = ResolveRoot(cfg.RootPath); var commonFileName = Path.Combine(rootDir, ToLayout(cfg.CommonPattern, @"{year}\{time}.log")); var taskFileName = Path.Combine(rootDir, ToLayout(cfg.TaskPattern, @"{flow}\{task}\{year}\{time}.log")); long maxBytes = cfg.MaxFileMB > 0 ? (long)cfg.MaxFileMB * 1024 * 1024 : 0; int maxDays = cfg.RetentionDays > 0 ? cfg.RetentionDays : 0; var nlogCfg = new LoggingConfiguration(); var commonTarget = MakeTarget("common", commonFileName, maxBytes, maxDays); var taskTarget = MakeTarget("task", taskFileName, maxBytes, maxDays); nlogCfg.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Fatal, commonTarget, CommonLoggerName); nlogCfg.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Fatal, taskTarget, TaskLoggerName); LogManager.Configuration = nlogCfg; _appliedSignature = signature; } } /// 构造一个文件目标:日期滚动 + 超大小归档 + 超保留天数自动删除。 private static FileTarget MakeTarget(string name, string fileName, long maxBytes, int maxDays) => new FileTarget(name) { FileName = fileName, Layout = "${message}", Encoding = Encoding.UTF8, KeepFileOpen = false, ConcurrentWrites = true, ArchiveAboveSize = maxBytes, // 0=不按大小归档 ArchiveNumbering = ArchiveNumberingMode.Rolling, MaxArchiveFiles = 0, // 不按份数限制 MaxArchiveDays = maxDays // 0=不按天数删除 }; /// 公共日志器(AppLogger + 插件 1/2 级)。 public static Logger Common => LogManager.GetLogger(CommonLoggerName); /// 任务日志器(插件 3/4 级)。 public static Logger Task => LogManager.GetLogger(TaskLoggerName); /// 解析日志根目录:绝对路径直接用,相对路径拼到程序目录下。 private static string ResolveRoot(string root) => Path.IsPathRooted(root ?? "") ? root : Path.Combine(TeamAAS.PathHelper.Root, string.IsNullOrWhiteSpace(root) ? "Logs" : root); /// 把路径公式里的占位符转为 NLog 布局渲染器(日期/事件属性),交由 NLog 自行滚动与归档。 private static string ToLayout(string pattern, string fallback) { if (string.IsNullOrWhiteSpace(pattern)) pattern = fallback; return pattern .Replace("{year}", "${date:format=yyyy}") .Replace("{month}", "${date:format=MM}") .Replace("{day}", "${date:format=dd}") .Replace("{time}", "${date:format=yyyy-MM-dd}") .Replace("{timestamp}", "${date:format=yyyyMMdd_HHmmss}") .Replace("{flow}", "${event-properties:item=" + FlowPropertyName + ":whenEmpty=_}") .Replace("{task}", "${event-properties:item=" + TaskPropertyName + ":whenEmpty=_}"); } /// 配置指纹:任一关键字段变化则重建 NLog 配置。 private static string BuildSignature(PluginLogConfig c) => string.Join("|", c.RootPath, c.CommonPattern, c.TaskPattern, c.MaxFileMB, c.RetentionDays); } }