using Serilog.Context; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Core.Observability; /// /// Convenience wrapper around Serilog — attaches the set of /// structured properties a capability call should carry (DriverInstanceId, DriverType, /// CapabilityName, CorrelationId). Callers wrap their call-site body in a using /// block; inner Log.Information / Log.Warning calls emit the context /// automatically via the Serilog enricher chain. /// /// /// The correlation ID should be the OPC UA RequestHeader.RequestHandle when in-flight; /// otherwise a short random GUID. Callers supply whichever is available. /// public static class LogContextEnricher { /// Attach the capability-call property set. Dispose the returned scope to pop. /// The unique identifier for the driver instance. /// The driver type name. /// The driver capability being invoked. /// The correlation ID for tracing the call. /// A disposable scope that pops the pushed properties when disposed. public static IDisposable Push(string driverInstanceId, string driverType, DriverCapability capability, string correlationId) { ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId); ArgumentException.ThrowIfNullOrWhiteSpace(driverType); ArgumentException.ThrowIfNullOrWhiteSpace(correlationId); var a = LogContext.PushProperty("DriverInstanceId", driverInstanceId); var b = LogContext.PushProperty("DriverType", driverType); var c = LogContext.PushProperty("CapabilityName", capability.ToString()); var d = LogContext.PushProperty("CorrelationId", correlationId); return new CompositeScope(a, b, c, d); } /// /// Generate a short correlation ID when no OPC UA RequestHandle is available. /// 12-hex-char slice of a GUID — long enough for log correlation, short enough to /// scan visually. /// /// A 12-hex-character correlation ID. public static string NewCorrelationId() => Guid.NewGuid().ToString("N")[..12]; private sealed class CompositeScope : IDisposable { private readonly IDisposable[] _inner; /// Initializes a new instance of the CompositeScope with the given inner scopes. /// The inner scopes to composite and dispose together. public CompositeScope(params IDisposable[] inner) => _inner = inner; /// Disposes all inner scopes in reverse order. public void Dispose() { // Reverse-order disposal matches Serilog's stack semantics. for (var i = _inner.Length - 1; i >= 0; i--) _inner[i].Dispose(); } } }