using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime; /// /// PR 6.1 — Decorator that emits one span /// per gw write batch. Tags secured-write counts so ops can see the routing-by- /// classification split (FreeAccess/Operate vs Tune/Configure) without re-reading /// the discovery dictionary. /// internal sealed class TracedGalaxyDataWriter(IGalaxyDataWriter inner, string clientName) : IGalaxyDataWriter { public async Task> WriteAsync( IReadOnlyList writes, Func securityResolver, CancellationToken cancellationToken) { using var activity = GalaxyTelemetry.ActivitySource.StartActivity("galaxy.write"); activity?.SetTag("galaxy.client", clientName); activity?.SetTag("galaxy.tag_count", writes.Count); if (activity is { IsAllDataRequested: true }) { // Counting the secured-write split is cheap (one resolver call per request) // and only happens when a tracing listener is actively recording — keeps the // hot path free when no one's listening. var securedCount = 0; foreach (var w in writes) { var sc = securityResolver(w.FullReference); if (sc is SecurityClassification.Tune or SecurityClassification.Configure or SecurityClassification.VerifiedWrite) { securedCount++; } } activity.SetTag("galaxy.secured_write_count", securedCount); } try { var results = await inner.WriteAsync(writes, securityResolver, cancellationToken) .ConfigureAwait(false); activity?.SetTag("galaxy.success_count", results.Count(r => r.StatusCode < 0x80000000u)); return results; } catch (Exception ex) { activity.RecordError(ex); throw; } } }