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)); }