TimeWarp.State.Telemetry
12.0.0-beta.15
Prefix Reserved
dotnet add package TimeWarp.State.Telemetry --version 12.0.0-beta.15
NuGet\Install-Package TimeWarp.State.Telemetry -Version 12.0.0-beta.15
<PackageReference Include="TimeWarp.State.Telemetry" Version="12.0.0-beta.15" />
<PackageVersion Include="TimeWarp.State.Telemetry" Version="12.0.0-beta.15" />
<PackageReference Include="TimeWarp.State.Telemetry" />
paket add TimeWarp.State.Telemetry --version 12.0.0-beta.15
#r "nuget: TimeWarp.State.Telemetry, 12.0.0-beta.15"
#:package TimeWarp.State.Telemetry@12.0.0-beta.15
#addin nuget:?package=TimeWarp.State.Telemetry&version=12.0.0-beta.15&prerelease
#tool nuget:?package=TimeWarp.State.Telemetry&version=12.0.0-beta.15&prerelease
TimeWarp.State.Telemetry
OpenTelemetry instrumentation for the TimeWarp.State mediator pipeline. Each dispatched action becomes a span with type names, duration, and success/failure on any OTel backend (Aspire dashboard, Jaeger, Seq, Application Insights).
The companion DevTools app reads these spans for the action log, state tree, and diff. OTLP is one-directional.
Install
dotnet add package TimeWarp.State.Telemetry
The package weaves TelemetryBehavior<TAction, TResponse> into ClientPipeline the same way StateTransactionBehavior is woven (generated mediator, closed types visible to the trimmer). Order 350 sits inside StateTransactionBehavior (300) and outside RenderSubscriptionsPostProcessor (400): handler failures are recorded as Error before the transaction restores state and swallows the exception. Span duration is handler + render, not the clone.
Register
builder.Services.AddGeneratedMediator<ClientPipeline>();
builder.Services.AddTimeWarpState();
builder.Services.AddTimeWarpStateTelemetry();
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddSource(TimeWarpStateTelemetry.ActivitySourceName);
tracing.AddAspNetCoreInstrumentation();
tracing.AddOtlpExporter(); // reads OTEL_EXPORTER_OTLP_* (Aspire sets these)
});
AddTimeWarpStateTelemetry uses TryAdd* so a host can replace options or the snapshot cache.
What a span contains
Default (no payload):
| Item | Source |
|---|---|
| Name | Nested declaring-type chain, e.g. CounterState.IncrementCountActionSet.Action |
timewarp.state.action |
Nested name relative to the enclosing state, e.g. IncrementCountActionSet.Action |
timewarp.state.state_type |
Enclosing state type name |
timewarp.state.store |
Store.Guid for the scope that handled the action, formatted D |
timewarp.state.render |
Optional. prerender or interactive when the host registered IStateRenderKind and a component called Observe. Absent otherwise |
| Duration | Activity start/stop (handler + render) |
| Status | Ok or Error (exception recorded) |
An action that triggers HTTP is the parent of that HTTP span when the handler runs under Activity.Current.
Opt-in snapshots
Snapshots are off unless all of these are true:
- An Activity listener is attached (
ActivitySource.HasListeners()). - The span is sampled (
IsAllDataRequested). TimeWarpStateTelemetryOptions.IncludeSnapshotsis true.- Caller-supplied
JsonSerializerOptions.TypeInfoResolverreturnsJsonTypeInfofor the state type.
builder.Services.AddTimeWarpStateTelemetry(options =>
{
options.IncludeSnapshots = true;
options.JsonSerializerOptions = timeWarpStateJsonOptions; // TypeInfoResolver required
options.MaxSnapshotChars = 16_384;
});
Or set TypeInfoResolver on TimeWarpStateOptions.JsonSerializerOptions (the same options Store and persistence use). This package never calls new JsonSerializerOptions() and never JsonSerializer.Serialize(object) on an open TState.
Events (not span attributes):
- When a state finishes
Initialize, and snapshots are on: activity{StateTypeName}.Initializedtaggedtimewarp.state.action=init,timewarp.state.state_type,timewarp.state.store,timewarp.state.kind=init, andtimewarp.state.renderwhen the host supplied a render kind, plus astate.snapshotevent. The snapshot cache records that JSON, so the first later change isstate.diff. NoJsonTypeInfoskips it with the same warning as an action snapshot. The handler does not await, so the event is recorded beforeStore.GetStatereturns. A Blazor prerender and the interactive circuit are different stores, so one page load emits two init spans with different store ids. - First JSON for a state type that was not initialized under snapshots:
state.snapshotwith tagsnapshot.json - Later unequal JSON:
state.diffwith the new JSON (string compare of the full JSON, no reflection property walk) - Equal JSON: no event
MaxSnapshotCharstruncates only the event payload and sets boolean tagsnapshot.truncated
Performance
The dispatch hot path with no listener is ActivitySource.HasListeners() (false) and next(). No GetState, no JSON, no snapshot cache.
With a listener, StartActivity returns null when the sampler drops the span; that path also skips snapshots.
Security
State JSON may contain user data. Defaults emit no payload. Enable snapshots only in local/dev, pair them with sampling, and redact via JsonConverter / source-generated context (omit secrets, tokens, PII). Truncation (MaxSnapshotChars) is a size cap, not redaction.
Do not enable IncludeSnapshots in production unless the OTel backend is access-controlled and payloads are redacted.
AOT / trimming
IsAotCompatible=trueon this package.- Default spans use
typeofnames only. - Snapshots require caller
JsonTypeInfo(typically aJsonSerializerContext). - No
Type.GetType, noAssemblyQualifiedName, noGetPropertieswalk. - No reflection hydration. State JSON comes from the caller
JsonTypeInfo.
Blazor Server
Works out of the box: the circuit runs .NET, so ActivitySource spans export over OTLP to the Aspire dashboard (or any backend).
Blazor WebAssembly / browser telemetry
WASM process telemetry is the JS OpenTelemetry SDK, not this ActivitySource. That is maintained SDK code.
Aspire dashboard browser telemetry (OTLP/HTTP + CORS):
- When the AppHost launches both the app and the dashboard, CORS for the OTLP HTTP endpoint is configured automatically. See Enable browser telemetry.
- A standalone dashboard needs
DASHBOARD__OTLP__CORS__ALLOWEDORIGINSset to the WASM origin (for examplehttps://localhost:7001).
This JS is not the C# trimmer’s problem.
DevTools
Use this package with TimeWarp.State DevTools for the action log, timing, and snapshots. UseReduxDevTools() was removed in 12.0.0-beta.11.
Sample
samples/04-telemetry/ is a Blazor Server counter that exports action spans. Run it against the Aspire dashboard (see that sample’s overview).
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net11.0 is compatible. |
-
net11.0
- TimeWarp.Mediator.Contracts (>= 14.0.0-beta.5)
- TimeWarp.State (>= 12.0.0-beta.15)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 12.0.0-beta.15 | 55 | 10/16/2026 |
| 12.0.0-beta.14 | 52 | 10/16/2026 |
| 12.0.0-beta.13 | 42 | 10/13/2026 |
| 12.0.0-beta.12 | 44 | 10/12/2026 |
| 12.0.0-beta.11 | 48 | 10/12/2026 |
| 12.0.0-beta.10 | 49 | 10/9/2026 |
| 12.0.0-beta.9 | 49 | 10/9/2026 |
| 12.0.0-beta.8 | 57 | 10/5/2026 |
| 12.0.0-beta.7 | 55 | 10/1/2026 |
| 12.0.0-beta.6 | 65 | 9/30/2026 |
| 12.0.0-beta.5 | 59 | 9/24/2026 |