## 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>
19 KiB
Telemetry and Logging
When to read this: Read this when migrating logger use (
Utils.LogX,Utils.Trace, static log helpers) to the newITelemetryContextmodel that resolvesILoggerviatelemetry.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
- Non-exhaustive summary of breaking changes
- ETW
EventSourceprovider removal - Migration utilities
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).ApplicationConfigurationgainsApplicationConfiguration(ITelemetryContext).ContentFilter.Evaluate(...)moved to a newFilterEvaluatorclass that requires telemetry context.
Interface additions
IServiceMessageContextgainsITelemetryContext Telemetry { get; }.ITransportBindingFactory<T>.Create(...)now takesITelemetryContext.ITelemetryContextgainsCreateMeter(Assembly)andGetActivitySource(Assembly)for component-specific telemetry.
System context
SystemContext()→SystemContext(ITelemetryContext)andSystemContext(IOperationContext, ITelemetryContext)(parameter-less constructor removed).
Certificate and security
CertificateValidatorgainsCertificateValidator(ITelemetryContext).CertificateStoreIdentifier.CreateStore(string, ITelemetryContext)andOpenStore(ITelemetryContext)— custom stores must implement the new signature.CertificateIdentifier.OpenStore(ITelemetryContext)— same custom-store impact.CertificateIdentifierCollectionno longer implementsICertificateStore; useCertificateIdentifierCollectionStorefor the read-only store behaviour.DirectoryCertificateStoreandX509CertificateStorelose their parameter-less constructors; pass anITelemetryContext.
Transport layer
TcpTransportChannel,TcpTransportListener,HttpsTransportListener: parameter-less constructors removed; factoryCreate(...)methods now take anITelemetryContext.TransportBindings.GetChannel(uriScheme, telemetry)/GetListener(uriScheme, telemetry): telemetry parameter added.
Client library
SessionClient: new constructor(ITransportChannel, ITelemetryContext).Subscription(ITelemetryContext)(otherwise attached onAddSubscription).Browser(ISession, ITelemetryContext)(otherwise set via the newTelemetryproperty).ReverseConnectManager(ITelemetryContext); default constructor[Obsolete].DefaultSessionFactory(ITelemetryContext)and the newManagedSessionFactory(ITelemetryContext)(which wraps raw sessions in reconnect-awareManagedSessions).TraceableSessionFactory(ITelemetryContext)andTraceableSession(ISession, ITelemetryContext).ComplexTypeSystem(..., ITelemetryContext).SessionReconnectHandler(ITelemetryContext, bool, int).
Configuration library
ApplicationInstance(ITelemetryContext)andApplicationInstance(ITelemetryContext, ApplicationConfiguration); non-telemetry constructors[Obsolete].ConfiguredEndpointCollection.Load(..., ITelemetryContext).ConfiguredEndpoint.UpdateFromServerAsync(ITelemetryContext, ...).
Server library
DataChangeMonitoredItemQueue,EventMonitoredItemQueue,MonitoredItemQueueFactory: constructors now takeITelemetryContext.
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 theCertificategetter; 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/LogCriticaloverloads (including theEventId-bearing variants). - All
Utils.Log(LogLevel, ...)andUtils.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'sSystem.Diagnostics.Tracing.EventSourceproviders, or if it setsClientTraceFlags.EventLogon 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/HasEventSourcereference type (Opc.Ua.Server.Fluent, used for Alarms & Conditions modelling) and the BCL's ownSystem.Buffers.ArrayPoolEventSourceare 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 anITelemetryContextbacked 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 anm_loggerfield to avoid null-reference exceptions while a class is being migrated. Distinct fromNullLogger.InstanceinMicrosoft.Extensions.Logging.Abstractions.Utils.Fallback.Logger— anILoggerthat mimics the legacy staticILogger. Use as a strictly temporary placeholder in places where no telemetry context can yet be plumbed through. TheFallbackclass is markedExperimentaland 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
- Related: packages.md, configuration.md.
docs/Diagnostics.md— full end-state usage and extensibility guidance forITelemetryContext(custom contexts, OpenTelemetry wiring, metrics inventory).docs/Sessions.md— diagnostics surface contract — fullOpc.Ua.ChannelManagercompatibility event table and safe-field policy.docs/DeveloperGuide.md— narrow exception: retained EventSource-compatibility ids — authoring rules for the retained compatibility ids.- 2.0 migration index — analyzer quick-start + symptom → sub-doc table.
- Migration Guide — landing page across versions.