Files
Marc Schier c4e0721a29 Add a documentation learning path and fix issues found while verifying it (#4555)
## Summary

### Documentation
- Stage the documentation index as a learning path: a first application,
independent client, server, and PubSub paths, then data and services,
companion models, production, advanced integration, and draft or
experimental capabilities.
- Add Getting started (repository samples or NuGet packages), OPC UA
concepts, Your first information model, and a production readiness
checklist.
- Add Native AOT application publishing, a first PubSub publisher and
subscriber exchange, and the UDP transport options.
- Add or expand contents navigation throughout `docs/`, and keep
contributor documentation separate.
- Correct outdated SDK examples and capability guidance, consolidate
repeated explanations into canonical guides, and repair local links and
heading structure.
- Rewrite What's New in 2.0 as a concise feature overview with short
linked details; keep migration instructions in the Migration Guide.
- Expand the feature catalogue with specification IDs, GDS, LDS, the OPC
10000-21 onboarding registrar, ISA-95, xRegistry, OpenUSD, and
sample-local models; distinguish draft status, explain crypto
pluggability, and bring performance guidance forward.
- Address review feedback on wording, active monitored items during
shadow reload, and the `Samples.md` filename and references.
- Regenerate the `opcua-v20-migration` plugin's bundled copy of the
migration guides so it matches the edited `docs/migrate/2.0.x` guides.
- Merge the latest `master`, preserving upstream resource-isolation
guidance within the task-based documentation index.
- Retain deferred Lingity findings where appropriate; this PR does not
claim that every document meets the Lingity score thresholds.

### Fixes found while verifying the guides
Each fix has regression tests that fail without it.
- **Client:** V2 subscriptions compared their lifetime with the
operation timeout instead of the session timeout, and logged a spurious
warning. `ISubscriptionEngineContext`, which is new in 2.0, gains a
`SessionTimeout` property; implementations must add it.
- **PubSub:** a subscriber now registers the `DataSetMetaData`
configured on its readers (OPC 10000-14 6.2.9.4), and the UADP decoder
decodes DataSetMessages that carry no ConfigurationVersion. Before, a
secured subscriber of the reference sample received no DataSets.
- **Source generator:** a ModelDesign method that declares its arguments
inline, without a method type, produced code that did not compile; it
now uses `MethodState`. The generated code of existing models is
unchanged.
- **MinimalClient sample:** waits for its first data-change notification
before it disconnects.

### Known issues, not addressed here
- UDP multicast: the publisher does not send on the interface that the
subscriber joins, and `MulticastLoopback` defaults to false. The PubSub
guide uses unicast for the exchange on one computer.
- The UDP metadata announcement is sent unsecured on secured writer
groups; a secured subscriber drops it with a warning.
- A server does not start in globalization-invariant mode, because
`ResourceManager` creates `CultureInfo("en-US")`. The Native AOT guide
cautions against the mode.

## Validation
- `git diff --check` and
`./.azurepipelines/validate-migration-plugin.ps1` (15 bundled migration
docs validated, plugin manifests synchronized).
- Local Markdown links, anchors, file-link casing, and contents lists
checked across all 87 documents under `docs/`. The index links every
top-level guide and every target of the previous index.
- Both Getting started routes, Your first information model, the PubSub
exchange, and the Native AOT publish of the Getting started client and
server were run as documented. The NuGet route and the tutorial use the
public `2.0.0-preview.5` packages.
- The changed libraries build for all target frameworks in Release
without warnings.
- Tests on .NET 10: Opc.Ua.SourceGeneration.Tests 199 passed;
Opc.Ua.SourceGeneration.Core.Tests 3,977 passed, 8 skipped;
Opc.Ua.Server.Tests `Fluent` category 621 passed; Opc.Ua.Client.Tests
subscription-engine tests 70 passed; Opc.Ua.PubSub.Tests 1,374 passed, 2
skipped; PubSub fuzz replay 145 passed. The new and affected tests also
pass on .NET Framework 4.8.
- Earlier merge validation: 174 resource-isolation tests passed on .NET
10.
- The full solution suite was not run locally.

Review and validation artifacts are maintained outside the repository.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-09-28 19:19:39 +00:00

19 KiB

Telemetry and Logging

When to read this: Read this when migrating logger use (Utils.LogX, Utils.Trace, static log helpers) to the new ITelemetryContext model that resolves ILogger via telemetry.CreateLogger<T>().

Observability in 2.0 is plumbed through ITelemetryContext. Loggers are resolved from the telemetry context via telemetry.CreateLogger<T>() rather than from Utils.Trace / Utils.LogX. The static logging helpers remain compilable but are [Obsolete]; consumers should resolve ILogger from ITelemetryContext instead.

Constructor injection across the public API is not uniform - the parameter is required positionally on most types, optional on ApplicationInstance, and absent on Session / CustomNodeManager2. The table below summarises the precise shape per type:

Type Telemetry parameter Notes
ApplicationInstance(ITelemetryContext? telemetry) Nullable Also ApplicationInstance(ApplicationConfiguration, ITelemetryContext?). Passing null falls back to a default telemetry context.
ServerBase(ITelemetryContext telemetry) Required positional The only public ctor.
CertificateManagerFactory.Create(SecurityConfiguration, ITelemetryContext, Action<CertificateManagerOptions>?) Required positional (2nd parameter) Factory entry point for CertificateManager.
DefaultSessionFactory() / DefaultSessionFactory(ITelemetryContext telemetry) Both ctors exist The parameterless ctor is [Obsolete]; use the telemetry-aware overload.
ManagedSessionFactory(ITelemetryContext telemetry) Required positional The only public ctor.
Session ctors None Telemetry flows in via ApplicationConfiguration or ISubscriptionEngineFactory. Do not look for a Session(... ITelemetryContext) overload - none exists.
CustomNodeManager2(IServerInternal, ApplicationConfiguration?, bool, ILogger, params string[]) None directly Obtain a logger via server.Telemetry.CreateLogger<T>() and pass it to the ctor.
// Server side - log via the server's telemetry context
public sealed class MyNodeManager : CustomNodeManager2
{
    public MyNodeManager(IServerInternal server, ApplicationConfiguration configuration)
        : base(server, configuration, useSamplingGroups: false,
               server.Telemetry.CreateLogger<MyNodeManager>(),
               "urn:example:my-namespace")
    {
    }
}

// Client side - construct the factory with telemetry
var factory = new ManagedSessionFactory(telemetry);
ISession session = await factory.CreateAsync(/* ... */);

Contents

Replacing the static logger surface

The legacy static logger management (Utils.SetLogger / Utils.SetLogLevel) is removed; ad-hoc static Utils.LogX / Utils.Trace helpers are [Obsolete]. The replacements:

From static logger management

// OLD - No longer works (removed in 2.0)
Utils.SetLogger(myLogger);
Utils.SetLogLevel(LogLevel.Information);

// NEW - Use ITelemetryContext via DefaultTelemetry / ApplicationInstance
var telemetryContext = DefaultTelemetry.Create(builder => builder.AddConsole());
var applicationInstance = new ApplicationInstance(telemetryContext);

Even better, register the OPC UA fluent surface via Microsoft.Extensions.DependencyInjection:

HostApplicationBuilder builder = Host.CreateApplicationBuilder();
builder.Services
    .AddOpcUa()                              // registers ITelemetryContext
    .AddLogging(b => b.AddConsole())
    .AddMetrics();

AddOpcUa() registers a ServiceProviderTelemetryContext that resolves the host's ILoggerFactory from DI on first use. See docs/Diagnostics.md for the full DI guidance.

From static Utils.Log* methods

// OLD - Obsolete
Utils.LogInformation("Connection established to {0}", endpoint);
Utils.LogError(exception, "Failed to connect to {0}", endpoint);

// NEW - Context-based logging
var logger = telemetryContext.CreateLogger<MyClass>();
logger.LogInformation("Connection established to {Endpoint}", endpoint);
logger.LogError(exception, "Failed to connect to {Endpoint}", endpoint);

From Utils.Trace* methods

// OLD - Obsolete
Utils.Trace("Processing request {0}", id);
Utils.Trace(TraceMasks.Information, "Request processed successfully");

// NEW - Structured logging with context
var logger = telemetryContext.CreateLogger<MyClass>();
logger.LogInformation("Processing request {RequestId}", id);
logger.LogInformation("Request processed successfully");

Non-exhaustive summary of breaking changes

The telemetry-context rollout touched many core types. The list below covers replaced constructors, modified factory methods, and [Obsolete] shims grouped by layer. Most affected APIs are internal to the stack; the most common external impact is that applications must create an ApplicationInstance with an explicit telemetry context.

Core infrastructure constructors

  • ServiceMessageContext() → ServiceMessageContext(ITelemetryContext) (parameter-less constructor removed).
  • EncodeableFactory() → EncodeableFactory(ITelemetryContext) (parameter-less constructor removed).
  • ApplicationConfiguration gains ApplicationConfiguration(ITelemetryContext).
  • ContentFilter.Evaluate(...) moved to a new FilterEvaluator class that requires telemetry context.

Interface additions

  • IServiceMessageContext gains ITelemetryContext Telemetry { get; }.
  • ITransportBindingFactory<T>.Create(...) now takes ITelemetryContext.
  • ITelemetryContext gains CreateMeter(Assembly) and GetActivitySource(Assembly) for component-specific telemetry.

System context

  • SystemContext() → SystemContext(ITelemetryContext) and SystemContext(IOperationContext, ITelemetryContext) (parameter-less constructor removed).

Certificate and security

  • CertificateValidator gains CertificateValidator(ITelemetryContext).
  • CertificateStoreIdentifier.CreateStore(string, ITelemetryContext) and OpenStore(ITelemetryContext) — custom stores must implement the new signature.
  • CertificateIdentifier.OpenStore(ITelemetryContext) — same custom-store impact.
  • CertificateIdentifierCollection no longer implements ICertificateStore; use CertificateIdentifierCollectionStore for the read-only store behaviour.
  • DirectoryCertificateStore and X509CertificateStore lose their parameter-less constructors; pass an ITelemetryContext.

Transport layer

  • TcpTransportChannel, TcpTransportListener, HttpsTransportListener: parameter-less constructors removed; factory Create(...) methods now take an ITelemetryContext.
  • TransportBindings.GetChannel(uriScheme, telemetry) / GetListener(uriScheme, telemetry): telemetry parameter added.

Client library

  • SessionClient: new constructor (ITransportChannel, ITelemetryContext).
  • Subscription(ITelemetryContext) (otherwise attached on AddSubscription).
  • Browser(ISession, ITelemetryContext) (otherwise set via the new Telemetry property).
  • ReverseConnectManager(ITelemetryContext); default constructor [Obsolete].
  • DefaultSessionFactory(ITelemetryContext) and the new ManagedSessionFactory(ITelemetryContext) (which wraps raw sessions in reconnect-aware ManagedSessions).
  • TraceableSessionFactory(ITelemetryContext) and TraceableSession(ISession, ITelemetryContext).
  • ComplexTypeSystem(..., ITelemetryContext).
  • SessionReconnectHandler(ITelemetryContext, bool, int).

Configuration library

  • ApplicationInstance(ITelemetryContext) and ApplicationInstance(ITelemetryContext, ApplicationConfiguration); non-telemetry constructors [Obsolete].
  • ConfiguredEndpointCollection.Load(..., ITelemetryContext).
  • ConfiguredEndpoint.UpdateFromServerAsync(ITelemetryContext, ...).

Server library

  • DataChangeMonitoredItemQueue, EventMonitoredItemQueue, MonitoredItemQueueFactory: constructors now take ITelemetryContext.

PubSub library

  • UaPubSubApplication.Create(IUaPubSubDataStore, ITelemetryContext).
  • UaPubSubConfigurator(ITelemetryContext).
  • UdpClientBroadcast(..., ITelemetryContext) (internal).
  • UdpClientUnicast(..., ITelemetryContext) (internal).

Method signatures

  • UserIdentityToken.GetOrCreateCertificate(ITelemetryContext) — certificate creation is no longer implicit on the Certificate getter; call this method explicitly when needed.
  • ServerSecurityPolicy.CalculateSecurityLevel(..., ILogger) — the non-logger overload is [Obsolete].

Removed and [Obsolete] Utils static APIs

Removed entirely:

  • Utils.SetLogger(ILogger)
  • Utils.SetLogLevel(LogLevel)

Marked [Obsolete] and slated for removal:

  • Utils.SetTraceOutput, Utils.SetTraceMask, Utils.SetTraceLog, Utils.TraceMask, Utils.Tracing, Utils.UseTraceEvent.
  • All Utils.Trace* overloads (basic, exception, TraceMask-based).
  • All Utils.LogDebug / LogTrace / LogInformation / LogWarning / LogError / LogCritical overloads (including the EventId-bearing variants).
  • All Utils.Log(LogLevel, ...) and Utils.Log(int traceMask, ...) generic overloads.

Replace each with the equivalent ILogger.LogXxx call on a logger obtained from ITelemetryContext.CreateLogger<T>().

While migrating explicit-mask calls, their original masks remain available to legacy Tracing.TraceEventHandler subscribers, including combined categories. Do not copy a numeric trace mask into an ordinary source-generated EventId: event identifiers are classified by log level unless an explicitly named legacy category is selected. See Diagnostics.


ETW EventSource provider removal

When to read this: Read this if your application or tooling attaches an EventListener, dotnet-trace, PerfView, or any other ETW consumer to one of the stack's System.Diagnostics.Tracing.EventSource providers, or if it sets ClientTraceFlags.EventLog on a client.

The stack shipped four internal EventSource providers for high-performance tracing. All four are removed in 2.0; there is no compile-time or runtime fallback. Their events are replaced one-for-one by [LoggerMessage]-generated ILogger calls that preserve the original numeric EventId, EventId.Name, mapped level, message template, and structured fields, so the event identity survives even though the transport changes from ETW to Microsoft.Extensions.Logging / OpenTelemetry logging.

Removed provider (ETW name) Assembly Replacement ILogger category
OPC-UA-Core Opc.Ua.Core OPC-UA-Core
OPC-UA-Client Opc.Ua.Client OPC-UA-Client
OPC-UA-Server Opc.Ua.Server OPC-UA-Server
Opc.Ua.ChannelManager Opc.Ua.Core (client channel manager) Opc.Ua.ChannelManager

The replacement logger category is always the exact old ETW provider name, not the assembly's usual typed category. Migrated filters can therefore keep the same identifying string (for example, AddFilter("OPC-UA-Client", LogLevel.Trace)) after they move from an ETW provider subscription to ILogger configuration. EventLevel mapped to LogLevel on a like-for-like basis (Verbose → Trace, Informational → Information, Warning → Warning, Error/Critical → Error/Critical). See DeveloperGuide.md — narrow exception: retained EventSource-compatibility ids for the authoring-side rules and Sessions.md — diagnostics surface contract for the full Opc.Ua.ChannelManager event table.

The OPC-UA-Server compatibility category remains opt-in like the retired EventSource provider. Enable that category at Trace to receive its records, including the ServerCall and SessionState records whose retained legacy level is Information. A global Information minimum therefore does not emit a record for every server request.

Not retained: the ETW provider GUID, EventTask/EventKeywords definitions, and the ETW manifest. There is no ILogger equivalent for these, and providers/consumers that depended on them (raw ETW session subscribers keyed by provider GUID, manifest-based decoders) must move to Microsoft.Extensions.Logging category/event-name filtering instead.

ClientTraceFlags.EventLog removed (source breaking)

ClientTraceFlags.EventLog — the flag that routed ClientBase request/response tracing to the (now-removed) OPC-UA-Core EventSource — is removed. Code that references it fails to compile:

// OLD - no longer compiles (ClientTraceFlags.EventLog removed)
client.ActivityTraceFlags = ClientTraceFlags.Log | ClientTraceFlags.EventLog;

// NEW - ClientTraceFlags.Log alone now emits the canonical OPC-UA-Core
// compatibility events (same EventId / EventName / message as the old
// EventLog flag); the separate per-call ClientBase structured logs that
// ClientTraceFlags.Log used to emit are replaced by these events
client.ActivityTraceFlags = ClientTraceFlags.Log;

ClientTraceFlags.Log now emits the canonical OPC-UA-Core request events (ServiceCallStart / ServiceCallStop / ServiceCallBadStop) through the OPC-UA-Core logger category, so existing filters or dashboards keyed on those event names keep matching without further changes. The separate SendResponse compatibility event is emitted by TcpServerChannel under the same category and is not controlled by ClientTraceFlags. There is no separate flag for the compatibility events; remove any reference to ClientTraceFlags.EventLog from your code.

This consolidation also adopts the legacy event levels and fields: request start/stop move from Information to Trace, failure moves from Error to Warning, and the former per-call elapsed-time field is no longer part of these log records (request duration remains available through opc.ua.client.request.duration). Configure the OPC-UA-Core category at Trace if you need successful request start/stop records.

Migrating EventListener / dotnet-trace consumers

If your code or tooling previously attached to one of the four providers, replace it with the Microsoft.Extensions.Logging / OpenTelemetry logging equivalent:

// OLD - ETW EventListener attached to a stack provider (removed in 2.0)
public sealed class ClientEventListener : EventListener
{
    protected override void OnEventSourceCreated(EventSource source)
    {
        if (source.Name == "OPC-UA-Client")
        {
            EnableEvents(source, EventLevel.Verbose);
        }
    }

    protected override void OnEventWritten(EventWrittenEventArgs e)
    {
        Console.WriteLine($"{e.EventName}: {string.Join(", ", e.Payload ?? [])}");
    }
}

// NEW - ILoggerProvider filtered to the same category name
builder.Services.AddLogging(b => b
    .AddFilter("OPC-UA-Client", LogLevel.Trace)
    .AddConsole());

// NEW - OpenTelemetry Logs SDK, same category
builder.Services.AddOpenTelemetry().WithLogging(l => l
    .AddProcessor(/* your exporter of choice */));

dotnet-trace / PerfView users who captured OPC-UA-Core, OPC-UA-Client, OPC-UA-Server, or Opc.Ua.ChannelManager as ETW providers should instead configure an ILoggerProvider (Console, OpenTelemetry OTLP, Application Insights, etc.) filtered to the matching category name, or use the Microsoft-Extensions-Logging ETW provider that dotnet-trace already understands for standard ILogger output, if ETW-shaped capture is still required.

Out of scope. The OPC UA information-model EventSourceRegistry / HasEventSource reference type (Opc.Ua.Server.Fluent, used for Alarms & Conditions modelling) and the BCL's own System.Buffers.ArrayPoolEventSource are unrelated to this removal — neither is a stack-owned ETW provider, and neither is affected by this migration.


Migration utilities

To aid migration the stack provides:

  • DefaultTelemetry.Create(...) — convenient factory that returns an ITelemetryContext backed by the trace logger when no configuration is supplied.
  • Telemetry.NullLogger / Telemetry.NullLogger<T> — no-op-in-release / debug-check-in-debug logger you can assign to an m_logger field to avoid null-reference exceptions while a class is being migrated. Distinct from NullLogger.Instance in Microsoft.Extensions.Logging.Abstractions.
  • Utils.Fallback.Logger — an ILogger that mimics the legacy static ILogger. Use as a strictly temporary placeholder in places where no telemetry context can yet be plumbed through. The Fallback class is marked Experimental and may be removed in a future release; treat any usage as a TODO.

In debug builds, any code path that resolves a null telemetry context triggers a Debug.Fail, surfacing the missing wiring at test time.


See also