212 lines
7.2 KiB
C#
212 lines
7.2 KiB
C#
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);
|
||
}
|
||
} |