using log4net;
|
using log4net.Appender;
|
using log4net.Core;
|
|
namespace DeviceService.Logging;
|
|
/// <summary>内存缓冲中的一条日志。</summary>
|
public sealed class LogEntry
|
{
|
public DateTime Time { get; set; }
|
|
public string Level { get; set; } = string.Empty;
|
|
public string Logger { get; set; } = string.Empty;
|
|
public string Message { get; set; } = string.Empty;
|
|
public string Display => $"{Time:yyyy-MM-dd HH:mm:ss.fff} [{Level}] {Logger} - {Message}";
|
|
public override string ToString() => Display;
|
}
|
|
/// <summary>
|
/// 自定义 Appender:只在内存环形缓冲中保留最近 <see cref="Capacity"/> 条日志,
|
/// 通过 <see cref="EntryAppended"/> 与 <see cref="Snapshot"/> 供日志窗口查看。
|
/// 文件输出由 log4net.config 中的 RollingFileAppender 负责,本 Appender 不写任何文件。
|
/// 实例由 log4net 依据 Config/log4net.config 创建(appender name="Memory")。
|
/// </summary>
|
public sealed class MemoryAppender : AppenderSkeleton
|
{
|
/// <summary>内存中最多保留的日志条数,可由 log4net.config 的 capacity 参数覆盖。</summary>
|
public int Capacity { get; set; } = 1000;
|
|
/// <summary>log4net.config 中配置的 MemoryAppender 实例。</summary>
|
public static MemoryAppender Current =>
|
LogManager.GetRepository(typeof(MemoryAppender).Assembly)
|
.GetAppenders()
|
.OfType<MemoryAppender>()
|
.FirstOrDefault()
|
?? throw new InvalidOperationException(
|
"未找到 MemoryAppender,请检查 Config/log4net.config 中是否配置了 name=\"Memory\" 的 appender,并在 root 中引用。");
|
|
private readonly object _sync = new object();
|
private readonly Queue<LogEntry> _entries = new Queue<LogEntry>();
|
|
/// <summary>新日志到达(可能在后台线程触发,UI 订阅方需切换回 UI 线程)。</summary>
|
public event Action<LogEntry>? EntryAppended;
|
|
/// <summary>当前内存中日志的快照。</summary>
|
public IReadOnlyList<LogEntry> Snapshot
|
{
|
get
|
{
|
lock (_sync)
|
{
|
return _entries.ToList();
|
}
|
}
|
}
|
|
public MemoryAppender()
|
{
|
Name = "Memory";
|
}
|
|
/// <summary>清空内存缓冲(不影响文件)。</summary>
|
public void Clear()
|
{
|
lock (_sync)
|
{
|
_entries.Clear();
|
}
|
}
|
|
protected override void Append(LoggingEvent loggingEvent)
|
{
|
if (loggingEvent == null)
|
{
|
return;
|
}
|
|
string message = loggingEvent.RenderedMessage ?? string.Empty;
|
if (loggingEvent.ExceptionObject != null)
|
{
|
message += Environment.NewLine + loggingEvent.ExceptionObject;
|
}
|
|
var entry = new LogEntry
|
{
|
Time = loggingEvent.TimeStamp,
|
Level = loggingEvent.Level?.Name ?? string.Empty,
|
Logger = loggingEvent.LoggerName,
|
Message = message
|
};
|
|
lock (_sync)
|
{
|
_entries.Enqueue(entry);
|
while (_entries.Count > Capacity)
|
{
|
_entries.Dequeue();
|
}
|
|
// Monitor 锁可重入,回调内部若调用 Snapshot/Clear 不会死锁
|
EntryAppended?.Invoke(entry);
|
}
|
}
|
}
|