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.
///
///
/// Per docs/v2/implementation/phase-6-1-resilience-and-observability.md §Stream C.2.
/// 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.
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.
///
public static string NewCorrelationId() => Guid.NewGuid().ToString("N")[..12];
private sealed class CompositeScope : IDisposable
{
private readonly IDisposable[] _inner;
public CompositeScope(params IDisposable[] inner) => _inner = inner;
public void Dispose()
{
// Reverse-order disposal matches Serilog's stack semantics.
for (var i = _inner.Length - 1; i >= 0; i--)
_inner[i].Dispose();
}
}
}