using Microsoft.Extensions.Logging;
using Opc.Ua;
namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient;
///
/// The the driver hands the SDK's non-obsolete (telemetry-taking)
/// overloads. has no logging provider (static helpers and tests);
/// routes the SDK's own Warning-and-above output (keep-alive, publish and
/// channel errors) to a driver instance's logger — without it every SDK-side session error was
/// invisible (Gitea #670).
///
internal static class SdkTelemetry
{
private static readonly Lazy Instance = new(() => DefaultTelemetry.Create(_ => { }));
/// Gets the shared, provider-less telemetry context.
public static ITelemetryContext Context => Instance.Value;
///
/// Creates a telemetry context whose SDK log output at Warning and above goes to ,
/// rate-limited (a dead upstream makes the SDK warn on every keep-alive and publish for as long as the
/// outage lasts). The context owns a : release it with
/// when the driver is disposed — is not itself disposable.
///
/// The driver instance's logger.
/// The telemetry context.
public static ITelemetryContext ForLogger(ILogger logger) =>
DefaultTelemetry.Create(builder => builder
.SetMinimumLevel(LogLevel.Warning)
.AddProvider(new ForwardingLoggerProvider(new RateLimitedLogger(logger, MaxEntriesPerWindow, Window))));
/// Disposes the logger factory a context owns. Never the shared one.
/// The context to release.
public static void Release(ITelemetryContext context)
{
if (ReferenceEquals(context, Context)) return;
try { (context as TelemetryContextBase)?.LoggerFactory?.Dispose(); }
catch { /* best-effort */ }
}
/// SDK log entries forwarded per before the rest are dropped and counted.
internal const int MaxEntriesPerWindow = 20;
/// The rate-limit window.
internal static readonly TimeSpan Window = TimeSpan.FromMinutes(1);
/// Hands every SDK logger category the one (rate-limited) driver logger.
private sealed class ForwardingLoggerProvider(ILogger target) : ILoggerProvider
{
public ILogger CreateLogger(string categoryName) => target;
public void Dispose() { }
}
///
/// Forwards at most max entries per window; the first entry of the next window carries a note of how
/// many were suppressed.
///
internal sealed class RateLimitedLogger(ILogger target, int max, TimeSpan window) : ILogger
{
private readonly object _lock = new();
private DateTime _windowStart = DateTime.MinValue;
private int _count;
private int _suppressed;
public IDisposable? BeginScope(TState state) where TState : notnull => target.BeginScope(state);
public bool IsEnabled(LogLevel logLevel) => target.IsEnabled(logLevel);
public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
Func formatter)
{
int suppressedNote;
lock (_lock)
{
var now = DateTime.UtcNow;
if (now - _windowStart >= window)
{
suppressedNote = _suppressed;
_windowStart = now;
_count = 0;
_suppressed = 0;
}
else suppressedNote = 0;
if (_count >= max)
{
_suppressed++;
return;
}
_count++;
}
if (suppressedNote > 0)
target.LogWarning("OPC UA SDK: {Suppressed} further log entries were suppressed in the last {Window}.",
suppressedNote, window);
target.Log(logLevel, eventId, state, exception, formatter);
}
}
}