alielie
7 天以前 e57938fbda9bff702004038f76d009422968bff5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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);
        }
    }
}