using System; using System.Collections.Concurrent; using System.Threading; using Microsoft.Extensions.Logging; using NLog; using ILogger = Microsoft.Extensions.Logging.ILogger; using LogLevel = Microsoft.Extensions.Logging.LogLevel; using NLogLevel = NLog.LogLevel; namespace DMS.WPF.Logging; /// /// NLog日志服务实现,直接使用NLog记录日志,并实现Microsoft.Extensions.Logging.ILogger接口 /// public class NLogLogger : ILogger { /// /// 获取指定名称的 NLog 日志实例。 /// private readonly NLog.Logger _logger; /// /// 日志记录器名称 /// private readonly string _name; public NLogLogger() { _name = nameof(NLogLogger); _logger = NLog.LogManager.GetCurrentClassLogger(); } public NLogLogger(string name) { _name = name; _logger = NLog.LogManager.GetLogger(name); } /// /// 内部类,用于存储节流日志的状态信息。 /// private class ThrottledLogInfo { /// /// 日志在节流时间窗口内的调用次数。 /// 使用 int 类型,并通过 Interlocked.Increment 进行原子性递增,确保线程安全。 /// public int Count; /// /// 用于在节流时间结束后执行操作的计时器。 /// public Timer Timer; } /// /// 线程安全的字典,用于存储正在被节流的日志。 /// 键 (string) 是根据日志消息生成的唯一标识。 /// 值 (ThrottledLogInfo) 是该日志的节流状态信息。 /// private static readonly ConcurrentDictionary ThrottledLogs = new ConcurrentDictionary(); /// /// 定义节流的时间窗口(单位:秒)。 /// private const int ThrottleTimeSeconds = 30; /// /// 内部核心日志记录方法,包含了节流逻辑。 /// /// 日志消息内容。 /// NLog 的日志级别。 /// 可选的异常对象。 /// 是否启用节流。 private void LogInternal(string msg, NLogLevel level, Exception exception, bool throttle) { // 如果不启用节流,则直接记录日志并返回。 if (!throttle) { _logger.Log(level, exception, msg); return; } // 使用消息内容生成唯一键,以区分不同的日志来源。 var key = msg; // 使用 AddOrUpdate 实现原子操作,确保线程安全。 // 它会尝试添加一个新的节流日志条目,如果键已存在,则更新现有条目。 ThrottledLogs.AddOrUpdate( key, // --- 添加逻辑 (addValueFactory):当日志第一次被节流时执行 --- _ => { // 1. 首次出现,立即记录一次原始日志。 _logger.Log(level, exception, msg); // 2. 创建一个新的节流信息对象。 var newThrottledLog = new ThrottledLogInfo { Count = 1 }; // 3. 创建并启动一个一次性计时器。 newThrottledLog.Timer = new Timer(s => { // --- 计时器回调:在指定时间(例如30秒)后触发 --- // 尝试从字典中移除当前日志条目。 if (ThrottledLogs.TryRemove(key, out var finishedLog)) { // 释放计时器资源。 finishedLog.Timer.Dispose(); // 如果在节流期间有后续调用(Count > 1),则记录一条摘要日志。 if (finishedLog.Count > 1) { var summaryMsg = $"日志 '{msg}' 在过去 {ThrottleTimeSeconds} 秒内被调用 {finishedLog.Count} 次。"; _logger.Log(level, summaryMsg); } } }, null, ThrottleTimeSeconds * 1000, Timeout.Infinite); // 设置30秒后触发,且不重复。 return newThrottledLog; }, // --- 更新逻辑 (updateValueFactory):当日志在节流窗口内再次被调用时执行 --- (_, existingLog) => { // 只需将调用次数加一。使用 Interlocked.Increment 保证原子操作,避免多线程下的竞态条件。 Interlocked.Increment(ref existingLog.Count); return existingLog; }); } public IDisposable BeginScope(TState state) { return null; // NLog不直接支持作用域 } public bool IsEnabled(LogLevel logLevel) { return _logger.IsEnabled(ToNLogLevel(logLevel)); } public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { if (!IsEnabled(logLevel)) return; var message = formatter(state, exception); var nlogLevel = ToNLogLevel(logLevel); switch (logLevel) { case LogLevel.Trace: LogInternal(message, nlogLevel, exception, false); break; case LogLevel.Debug: LogInternal(message, nlogLevel, exception, false); break; case LogLevel.Information: LogInternal(message, nlogLevel, exception, false); break; case LogLevel.Warning: LogInternal(message, nlogLevel, exception, false); break; case LogLevel.Error: LogInternal(message, nlogLevel, exception, false); break; case LogLevel.Critical: LogInternal($"[Critical] {message}", nlogLevel, exception, false); break; } } private NLogLevel ToNLogLevel(LogLevel logLevel) { return logLevel switch { LogLevel.Trace => NLogLevel.Trace, LogLevel.Debug => NLogLevel.Debug, LogLevel.Information => NLogLevel.Info, LogLevel.Warning => NLogLevel.Warn, LogLevel.Error => NLogLevel.Error, LogLevel.Critical => NLogLevel.Fatal, _ => NLogLevel.Debug }; } // 保持原有的自定义方法以提供向后兼容性 public void LogError(string message, Exception exception = null) { LogInternal(message, NLogLevel.Error, exception, true); } public void LogInfo(string message) { LogInternal(message, NLogLevel.Info, null, true); } public void LogWarning(string message) { LogInternal(message, NLogLevel.Warn, null, true); } public void LogDebug(string message) { LogInternal(message, NLogLevel.Debug, null, true); } public void LogTrace(string message) { LogInternal(message, NLogLevel.Trace, null, true); } }