forked from mkaring/ConfuserEx
-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathDiagnosticCollector.cs
More file actions
163 lines (137 loc) · 5.69 KB
/
Copy pathDiagnosticCollector.cs
File metadata and controls
163 lines (137 loc) · 5.69 KB
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
using System;
using System.Collections.Generic;
using Confuser.Core.Project;
using Microsoft.Extensions.Logging;
namespace Confuser.Core.Diagnostics {
/// <summary>
/// A single captured log entry, rendered to text at capture time.
/// </summary>
public readonly struct DiagnosticLogEntry {
public DiagnosticLogEntry(LogLevel level, string message, string exception) {
Level = level;
Message = message;
Exception = exception;
}
/// <summary>The severity of the entry.</summary>
public LogLevel Level { get; }
/// <summary>The rendered message text.</summary>
public string Message { get; }
/// <summary>The rendered exception (including stack trace), or <c>null</c> if none.</summary>
public string Exception { get; }
}
/// <summary>
/// Wraps the real <see cref="ILogger" /> and <see cref="IProgressReporter" /> used during an
/// obfuscation run, passing every call through while capturing a full-verbosity transcript,
/// timing and outcome. On completion — success or failure — it can produce a self-contained
/// markdown diagnostic report suitable for a bug report.
/// </summary>
/// <remarks>
/// <para>
/// Capture is intentionally independent of the inner logger's level: the collector reports
/// <see cref="IsEnabled" /> as <c>true</c> and keeps every entry, so a report from a default
/// (Information) run still contains the Debug detail needed to diagnose a failure. Display
/// filtering is preserved because entries are only forwarded to the inner logger when it is
/// enabled for that level.
/// </para>
/// <para>
/// The entry buffer is bounded (see <see cref="DefaultCapacity" />); once full, the oldest
/// entries are dropped and counted in <see cref="DroppedCount" /> so the report can note the
/// loss rather than silently mislead.
/// </para>
/// <para>
/// <see cref="Finish" /> is last-wins: a packer runs a nested engine pass with the same
/// collector, so the top-level run — which finishes last — determines the reported outcome
/// and elapsed time.
/// </para>
/// </remarks>
public sealed class DiagnosticCollector : ILogger, IProgressReporter {
/// <summary>The default maximum number of log entries retained.</summary>
public const int DefaultCapacity = 2000;
readonly ILogger inner;
readonly IProgressReporter innerReporter;
readonly int capacity;
readonly object sync = new object();
readonly Queue<DiagnosticLogEntry> entries;
readonly DateTime begin = DateTime.UtcNow;
int dropped;
bool? successful;
TimeSpan elapsed;
/// <summary>
/// Initializes a new collector.
/// </summary>
/// <param name="inner">The logger to forward display output to. Required.</param>
/// <param name="innerReporter">The progress reporter to forward to, or <c>null</c>.</param>
/// <param name="capacity">The maximum number of log entries to retain.</param>
public DiagnosticCollector(ILogger inner, IProgressReporter innerReporter = null, int capacity = DefaultCapacity) {
this.inner = inner ?? throw new ArgumentNullException(nameof(inner));
this.innerReporter = innerReporter;
this.capacity = capacity < 1 ? 1 : capacity;
entries = new Queue<DiagnosticLogEntry>(Math.Min(this.capacity, 64));
}
/// <summary>
/// The project being processed, used to populate the report's configuration section.
/// </summary>
public ConfuserProject Project { get; set; }
/// <summary>
/// The run outcome: <c>true</c> on success, <c>false</c> on failure, <c>null</c> if the run
/// never reported completion.
/// </summary>
public bool? Successful {
get { lock (sync) return successful; }
}
/// <summary>The elapsed time recorded at the last <see cref="Finish" /> call.</summary>
public TimeSpan Elapsed {
get { lock (sync) return elapsed; }
}
/// <summary>The number of log entries dropped because the buffer was full.</summary>
public int DroppedCount {
get { lock (sync) return dropped; }
}
/// <summary>
/// Returns an immutable copy of the currently retained log entries, oldest first.
/// </summary>
public IReadOnlyList<DiagnosticLogEntry> Snapshot() {
lock (sync) return new List<DiagnosticLogEntry>(entries);
}
/// <summary>
/// Produces the markdown diagnostic report. Never throws.
/// </summary>
public string GenerateReport() => DiagnosticReport.Generate(this);
#region ILogger
public IDisposable BeginScope<TState>(TState state) => inner.BeginScope(state);
public bool IsEnabled(LogLevel logLevel) => true;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception,
Func<TState, Exception, string> formatter) {
string message;
try {
message = formatter != null ? formatter(state, exception) : state?.ToString() ?? string.Empty;
}
catch {
message = state?.ToString() ?? string.Empty;
}
var entry = new DiagnosticLogEntry(logLevel, message, exception?.ToString());
lock (sync) {
entries.Enqueue(entry);
while (entries.Count > capacity) {
entries.Dequeue();
dropped++;
}
}
// Forward to the inner logger for display; it applies its own level filter.
if (inner.IsEnabled(logLevel))
inner.Log(logLevel, eventId, state, exception, formatter);
}
#endregion
#region IProgressReporter
public void Progress(int progress, int overall) => innerReporter?.Progress(progress, overall);
public void EndProgress() => innerReporter?.EndProgress();
public void Finish(bool successful) {
lock (sync) {
this.successful = successful;
elapsed = DateTime.UtcNow - begin;
}
innerReporter?.Finish(successful);
}
#endregion
}
}