Files
DMS/DMS.WPF/Logging/NLogLogger.cs
2025-09-05 16:18:01 +08:00

212 lines
7.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// NLog日志服务实现直接使用NLog记录日志并实现Microsoft.Extensions.Logging.ILogger接口
/// </summary>
public class NLogLogger : ILogger
{
/// <summary>
/// 获取指定名称的 NLog 日志实例。
/// </summary>
private readonly NLog.Logger _logger;
/// <summary>
/// 日志记录器名称
/// </summary>
private readonly string _name;
public NLogLogger()
{
_name = nameof(NLogLogger);
_logger = NLog.LogManager.GetCurrentClassLogger();
}
public NLogLogger(string name)
{
_name = name;
_logger = NLog.LogManager.GetLogger(name);
}
/// <summary>
/// 内部类,用于存储节流日志的状态信息。
/// </summary>
private class ThrottledLogInfo
{
/// <summary>
/// 日志在节流时间窗口内的调用次数。
/// 使用 int 类型,并通过 Interlocked.Increment 进行原子性递增,确保线程安全。
/// </summary>
public int Count;
/// <summary>
/// 用于在节流时间结束后执行操作的计时器。
/// </summary>
public Timer Timer;
}
/// <summary>
/// 线程安全的字典,用于存储正在被节流的日志。
/// 键 (string) 是根据日志消息生成的唯一标识。
/// 值 (ThrottledLogInfo) 是该日志的节流状态信息。
/// </summary>
private static readonly ConcurrentDictionary<string, ThrottledLogInfo> ThrottledLogs = new ConcurrentDictionary<string, ThrottledLogInfo>();
/// <summary>
/// 定义节流的时间窗口(单位:秒)。
/// </summary>
private const int ThrottleTimeSeconds = 30;
/// <summary>
/// 内部核心日志记录方法,包含了节流逻辑。
/// </summary>
/// <param name="msg">日志消息内容。</param>
/// <param name="level">NLog 的日志级别。</param>
/// <param name="exception">可选的异常对象。</param>
/// <param name="throttle">是否启用节流。</param>
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>(TState state)
{
return null; // NLog不直接支持作用域
}
public bool IsEnabled(LogLevel logLevel)
{
return _logger.IsEnabled(ToNLogLevel(logLevel));
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> 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);
}
}