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