using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Core;
using Serilog.Events;
using ZB.MOM.WW.ScadaBridge.Commons.Observability;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.Logging;
///
/// A Microsoft that writes each call straight to a Serilog logger enriched
/// from LogContext - the shape the host gets from UseSerilog - and keeps the events,
/// so a test can read the ExecutionId a line carried at the moment it was logged.
///
internal sealed class LogContextCapture : ILogger, ILogEventSink
{
private readonly Serilog.ILogger _serilog;
/// Creates the capture with its own Serilog pipeline.
public LogContextCapture()
=> _serilog = new LoggerConfiguration().MinimumLevel.Verbose()
.Enrich.FromLogContext().WriteTo.Sink(this).CreateLogger();
/// Every event logged so far.
public ConcurrentQueue Events { get; } = new();
/// The ExecutionId the first line containing carried (null when none).
/// Text the rendered message contains.
/// The id, or null when the line carried none.
public Guid? ExecutionIdOf(string fragment)
{
var evt = Events.First(e => e.RenderMessage().Contains(fragment, StringComparison.Ordinal));
return evt.Properties.TryGetValue(ExecutionLogContext.ExecutionIdProperty, out var value)
? Guid.Parse(value.ToString().Trim('"'))
: null;
}
/// Whether a line containing was logged.
/// Text the rendered message contains.
/// True once such a line exists.
public bool Has(string fragment) => Events.Any(e => e.RenderMessage().Contains(fragment, StringComparison.Ordinal));
///
public void Emit(LogEvent logEvent) => Events.Enqueue(logEvent);
///
public IDisposable? BeginScope(TState state) where TState : notnull => null;
///
public bool IsEnabled(LogLevel logLevel) => true;
///
public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
Func formatter)
=> _serilog.Write(LogEventLevel.Information, exception, "{Line}", formatter(state, exception));
}