in src/Elastic.Documentation/Diagnostics/DiagnosticsCollector.cs [11:140]
public class DiagnosticsCollector(IReadOnlyCollection<IDiagnosticsOutput> outputs)
: IDiagnosticsCollector, IHostedService
{
private DiagnosticsChannel Channel { get; } = new();
private int _errors;
private int _warnings;
private int _hints;
public int Warnings => _warnings;
public int Errors => _errors;
public int Hints => _hints;
private Task? _started;
public HashSet<string> OffendingFiles { get; } = [];
public ConcurrentDictionary<string, bool> InUseSubstitutionKeys { get; } = [];
public ConcurrentBag<string> CrossLinks { get; } = [];
public bool NoHints { get; init; }
public DiagnosticsCollector StartAsync(Cancel ctx)
{
_ = ((IHostedService)this).StartAsync(ctx);
return this;
}
Task IHostedService.StartAsync(Cancel cancellationToken)
{
if (_started is not null)
return _started;
_started = Task.Run(async () =>
{
_ = await Channel.WaitToWrite(cancellationToken);
while (!Channel.CancellationToken.IsCancellationRequested)
{
try
{
while (await Channel.Reader.WaitToReadAsync(Channel.CancellationToken))
Drain();
}
catch
{
//ignore
}
}
Drain();
}, cancellationToken);
return _started;
void Drain()
{
while (Channel.Reader.TryRead(out var item))
{
if (item.Severity == Severity.Hint && NoHints)
continue;
HandleItem(item);
_ = OffendingFiles.Add(item.File);
foreach (var output in outputs)
output.Write(item);
}
}
}
private void IncrementSeverityCount(Diagnostic item)
{
if (item.Severity == Severity.Error)
_ = Interlocked.Increment(ref _errors);
else if (item.Severity == Severity.Warning)
_ = Interlocked.Increment(ref _warnings);
else if (item.Severity == Severity.Hint && !NoHints)
_ = Interlocked.Increment(ref _hints);
}
protected virtual void HandleItem(Diagnostic diagnostic) { }
public virtual async Task StopAsync(Cancel cancellationToken)
{
Channel.TryComplete();
if (_started is not null)
await _started;
await Channel.Reader.Completion;
}
public void EmitCrossLink(string link) => CrossLinks.Add(link);
public void Write(Diagnostic diagnostic)
{
IncrementSeverityCount(diagnostic);
Channel.Write(diagnostic);
}
private void Emit(Severity severity, string file, string message) =>
Write(new Diagnostic
{
Severity = severity,
File = file,
Message = message
});
public void EmitError(string file, string message, Exception? e = null)
{
message = message
+ (e != null ? Environment.NewLine + e : string.Empty)
+ (e?.InnerException != null ? Environment.NewLine + e.InnerException : string.Empty);
Emit(Severity.Error, file, message);
}
public void EmitWarning(string file, string message) => Emit(Severity.Warning, file, message);
public void EmitHint(string file, string message) => Emit(Severity.Hint, file, message);
public void EmitError(IFileInfo file, string message, Exception? e = null) => EmitError(file.FullName, message, e);
public void EmitWarning(IFileInfo file, string message) => Emit(Severity.Warning, file.FullName, message);
public void EmitHint(IFileInfo file, string message) => Emit(Severity.Hint, file.FullName, message);
public async ValueTask DisposeAsync()
{
Channel.TryComplete();
await StopAsync(CancellationToken.None);
GC.SuppressFinalize(this);
}
public void CollectUsedSubstitutionKey(ReadOnlySpan<char> key) =>
_ = InUseSubstitutionKeys.TryAdd(key.ToString(), true);
}