-
Notifications
You must be signed in to change notification settings - Fork 1
Integration OpenTelemetry Extension
The Kronikol core library automatically captures System.Diagnostics.Activity spans during test execution via a non-invasive ActivityListener. These spans are used by Internal Flow Tracking to show what happened inside the SUT between HTTP boundaries.
No setup required. The TestTrackingMessageHandler constructor auto-starts an InternalFlowActivityListener that subscribes to all well-known auto-instrumentation sources (ASP.NET Core, HttpClient, EF Core, Redis, Cosmos, etc.). InternalFlowTracking defaults to true on ReportConfigurationOptions.
To also capture custom ActivitySources, add their names to the handler options you're already configuring:
new XUnitTestTrackingMessageHandlerOptions
{
CallerName = "Tests",
PortsToServiceNames = { [5001] = "MyApi" },
InternalFlowActivitySources = ["MyApi.Services", "MyApi.Workers"] // optional
}If you need the listener started before any handler is constructed, or want to register via DI for container-managed disposal:
builder.ConfigureTestServices(services =>
{
// ... existing tracking setup ...
services.AddActivityListenerForInternalFlowTracking("MyApi.Services");
});This is:
-
Non-invasive — uses a raw
System.Diagnostics.ActivityListener(BCL), not the OTel SDK - Zero interference — does not touch the SUT's existing OpenTelemetry sampling, exporters, or telemetry assertions
- Automatic — subscribes to all well-known auto-instrumentation sources
-
No extra packages — lives in the core
Kronikolpackage
Namespace:
Kronikol.InternalFlow— you may need to add ausingdirective.
Registers an InternalFlowActivityListener — a System.Diagnostics.ActivityListener that:
- Subscribes to all well-known auto-instrumentation sources plus any additional sources you specify
-
Samples with
AllData(notAllDataAndRecorded) — theActivity.Recordedflag staysfalse, so existing OTel exporters that checkRecordedwill skip these activities -
Captures completed spans via
ActivityStoppedintoInternalFlowSpanStore(aConcurrentQueue<Activity>in the core package)
Because it uses the BCL ActivityListener rather than the OTel SDK, it works regardless of whether the SUT configures OpenTelemetry, and never interferes with existing exporters or samplers.
For users who want to add the exporter to their own TracerProviderBuilder:
services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddSource("MyApi.Services")
.AddSource("Microsoft.AspNetCore")
.AddSource("System.Net.Http")
.AddTestTrackingExporter());This requires the Kronikol.Extensions.OpenTelemetry package and gives you full control over the OTel pipeline. The exporter writes to the same InternalFlowSpanStore as the listener approach.
⚠️ Warning: Adding sources and the exporter to an existingTracerProviderBuildercauses all exporters on that builder to receive the additional activity data — which can break telemetry assertion tests. See Projects with Existing OpenTelemetry Configuration below.
| Scenario | Recommendation |
|---|---|
| Standard case | Do nothing — auto-start covers well-known sources |
| Custom ActivitySources | Add InternalFlowActivitySources to handler options |
| Need DI-registered listener | Use AddActivityListenerForInternalFlowTracking()
|
| Full OTel SDK control | Use AddTestTrackingExporter() on a separate AddOpenTelemetry() call |
No additional package is needed for the default auto-start behaviour — span capture is built into the core Kronikol package.
For the manual AddTestTrackingExporter() approach:
dotnet add package Kronikol.Extensions.OpenTelemetryDependencies:
-
OpenTelemetry(1.*) -
OpenTelemetry.Exporter.InMemory(>= 1.15.1) -
Kronikol(core library)
Version alignment: The OpenTelemetry extension package requires a matching (or newer) version of the core
Kronikolpackage. If you're using other Kronikol packages (CosmosDB, Redis, EF Core, etc.) or a framework package (xUnit3, NUnit, etc.), ensure they are all at the same version. Mixing versions (e.g. OpenTelemetry extension at 2.0.2 with xUnit3 at 2.0.1) will cause NuGet restore failures referencing the coreKronikolpackage.If you use central package management (
Directory.Packages.props), you may also need to bump yourOpenTelemetry.Exporter.InMemoryversion to >= 1.15.1 to satisfy the transitive dependency. WithTreatWarningsAsErrorsenabled, a version mismatch will produce a build-breaking NU1605 error.
No setup required. The TestTrackingMessageHandler constructor auto-starts an InternalFlowActivityListener for well-known sources. To capture custom ActivitySources, add them to your handler options:
new XUnitTestTrackingMessageHandlerOptions
{
CallerName = "Tests",
PortsToServiceNames = { [5001] = "MyApi" },
InternalFlowActivitySources = ["MyApi.Services"] // optional
}If you prefer DI-based lifecycle management or need the listener before any handler is constructed:
using Kronikol.InternalFlow;
builder.ConfigureTestServices(services =>
{
services.AddActivityListenerForInternalFlowTracking("MyApi.Services");
});For full pipeline control (requires Kronikol.Extensions.OpenTelemetry):
using Kronikol.Extensions.OpenTelemetry;
builder.ConfigureTestServices(services =>
{
services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddSource("MyApi.Services")
.AddSource("Microsoft.AspNetCore")
.AddSource("System.Net.Http")
.AddSource("Microsoft.EntityFrameworkCore")
.AddTestTrackingExporter()); // ← Captures spans for diagrams
});The auto-started listener and the OTel exporter can be active simultaneously —
InternalFlowSpanStorededuplicates by reference identity.
⚠️ This section only applies to the manualAddTestTrackingExporter()approach. The auto-started listener andAddActivityListenerForInternalFlowTracking()are always non-invasive and never interfere with existing OTel configuration.
If your project already configures OpenTelemetry (e.g. via a shared telemetry library or an existing TracerProviderBuilder), do not add .AddTestTrackingExporter() and the additional activity sources to the existing builder. Adding sources like Microsoft.AspNetCore, System.Net.Http, etc. to an existing builder causes all exporters on that builder to receive the additional activity data — which can break telemetry assertion tests that rely on a specific set of captured activities.
Recommended approach: Register a separate AddOpenTelemetry().WithTracing(...) exclusively for the Kronikol exporter, keeping the existing builder untouched:
builder.ConfigureTestServices(services =>
{
// ✅ Existing OTel setup — unchanged, feeds your ExportedActivities for telemetry assertions
services.AddMyTelemetry(options => { ... }, builder => builder
.AddInMemoryExporter(exportedActivities));
// ✅ Separate registration for Kronikol Internal Flow Tracking
services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddSource("MyApi.Services")
.AddSource("Microsoft.AspNetCore")
.AddSource("System.Net.Http")
.AddSource("Microsoft.Azure.Cosmos")
.AddTestTrackingExporter());
});Or even simpler — just use the recommended approach instead:
builder.ConfigureTestServices(services =>
{
// ✅ Existing OTel setup — completely untouched
services.AddMyTelemetry(options => { ... }, builder => builder
.AddInMemoryExporter(exportedActivities));
// ✅ Non-invasive — doesn't touch the TracerProvider at all
services.AddActivityListenerForInternalFlowTracking("MyApi.Services");
});InternalFlowTracking defaults to true on ReportConfigurationOptions. No explicit setting is needed. To opt out:
new ReportConfigurationOptions
{
InternalFlowTracking = false, // disable internal flow popups
}The captured spans automatically appear in sequence diagram popups at report viewing time.
You must explicitly register each ActivitySource you want to capture. Common sources:
Important: Your SUT's own
ActivitySourceis the most valuable one to add. Without it, the internal flow diagrams will only show framework-level spans (HTTP pipeline, ASP.NET Core middleware, database calls) and not your actual application logic. The auto-instrumentation sources below provide useful context, but it's the custom sources that show your business logic flow in the internal flow popups.
| Source | Package | What it captures |
|---|---|---|
Microsoft.AspNetCore |
Built-in (ASP.NET Core) | Incoming HTTP request processing |
System.Net.Http |
Built-in (.NET) | Outgoing HTTP client calls |
Microsoft.EntityFrameworkCore |
Built-in (EF Core) | Database queries and commands |
Npgsql |
Npgsql | PostgreSQL-specific operations |
StackExchange.Redis |
StackExchange.Redis | Redis operations |
Azure.Cosmos / Microsoft.Azure.Cosmos
|
Azure Cosmos SDK | Cosmos DB operations |
| Your custom sources | Your code | Any custom ActivitySource in your SUT |
If your SUT uses custom ActivitySource instances, add those source names too:
// In your SUT code
public static class Telemetry
{
public static readonly ActivitySource Source = new("MyApi.Services");
}
// In a service method
using var activity = Telemetry.Source.StartActivity("ProcessOrder");
// ... processing logic// In your test setup
tracing.AddSource("MyApi.Services")
.AddTestTrackingExporter();The span store is static and accumulates spans across all tests in a run. In most cases this is fine — InternalFlowSegmentBuilder correlates spans with specific tests using TraceId.
If you need to clear spans between tests (e.g. for isolation), call:
InternalFlowSpanStore.Clear(); // Core package
// or
TestTrackingSpanStore.Clear(); // Backward-compatible facade (Extensions.OpenTelemetry)| Method | Description |
|---|---|
AddActivityListenerForInternalFlowTracking(this IServiceCollection, params string[]) |
Registers a non-invasive InternalFlowActivityListener via DI for container-managed disposal. Optional — the TestTrackingMessageHandler auto-starts a listener automatically. Use this when you need custom sources via DI or want the listener started before any handler is constructed. |
IDisposable class wrapping System.Diagnostics.ActivityListener. Auto-started by TestTrackingMessageHandler constructor (process-wide singleton). Can also be created manually or via AddActivityListenerForInternalFlowTracking().
| Member | Description |
|---|---|
Constructor (params string[] additionalActivitySources)
|
Creates and registers an ActivityListener for well-known + custom sources. |
EnsureStarted(string[]?) |
Starts the process-wide singleton (first caller wins). Called automatically by TestTrackingMessageHandler. |
Dispose() |
Unregisters the listener. Stops capturing new spans. |
Static thread-safe store (ConcurrentQueue<Activity>) in the core package. Deduplicates by reference identity.
| Member | Description |
|---|---|
Add(Activity) |
Enqueues a span if not already present (reference-equality dedup). Called by InternalFlowActivityListener and TestTrackingSpanExporter. |
GetSpans() |
Returns a snapshot of all captured Activity[] spans. |
Clear() |
Clears all captured spans. |
| Method | Description |
|---|---|
AddTestTrackingExporter(this TracerProviderBuilder) |
Registers a SimpleActivityExportProcessor with TestTrackingSpanExporter to capture all completed spans into InternalFlowSpanStore. |
Backward-compatible static class that delegates to InternalFlowSpanStore in the core package.
| Member | Description |
|---|---|
Add(Activity) |
Delegates to InternalFlowSpanStore.Add(). |
GetSpans() |
Delegates to InternalFlowSpanStore.GetSpans(). |
Clear() |
Delegates to InternalFlowSpanStore.Clear(). |
Internal class. Implements BaseExporter<Activity> and forwards each completed span to InternalFlowSpanStore.Add().
Option A: ActivityListener (recommended) Option B: OTel SDK (manual)
┌────────────────────────────────┐ ┌──────────────────────────────┐
│ InternalFlowActivityListener │ │ OpenTelemetry TracerProvider │
│ └─ ShouldListenTo(wellKnown+) │ │ └─ AddSource("...") │
│ └─ Sample = AllData │ │ └─ AddTestTrackingExporter │
│ └─ ActivityStopped callback │ └──────────┬───────────────────┘
└──────────┬─────────────────────┘ │
│ ▼
│ ┌──────────────────────────────────┐
│ │ SimpleActivityExportProcessor │
│ │ └─ TestTrackingSpanExporter │
│ └──────────┬───────────────────────┘
│ │
▼ ▼
┌──────────────────────────────────────────────┐
│ InternalFlowSpanStore (core package) │
│ └─ ConcurrentQueue<Activity> │
│ └─ GetSpans() → Activity[] │
└──────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────┐
│ InternalFlowSpanCollector │
│ └─ CollectSpans() │
│ └─ FilterSpans(granularity) │
└──────────────────────────────┘
Both paths feed the same InternalFlowSpanStore in the core package. The store deduplicates by reference identity, so both paths can be active simultaneously without duplicates. InternalFlowSpanCollector reads directly from the store (no reflection).
No hard dependency: The auto-started listener and
AddActivityListenerForInternalFlowTracking()live in the core package and require zero additional NuGet packages. TheAddTestTrackingExporter()method requires theKronikol.Extensions.OpenTelemetrypackage.
TestTrackingMessageHandler |
InternalFlowActivityListener / TestTrackingSpanExporter
|
|
|---|---|---|
| What it captures | HTTP request/response pairs between services | All Activity spans from registered sources |
| Where it appears | Sequence diagram arrows | Internal flow popups (when clicking an arrow) |
| Granularity | One entry per HTTP call | One entry per activity span (can be many per HTTP call) |
| Configuration | TestTrackingMessageHandlerOptions |
Auto-started (default) or InternalFlowActivitySources option |
| Required for diagrams | Yes — core functionality | No — optional enhancement for internal flow |
Getting Started
Common Tasks
Integration Guides
- Integration xUnit3
- Integration xUnit2
- Integration NUnit
- Integration MSTest
- Integration TUnit
- Integration BDDfy xUnit3
- Integration LightBDD xUnit2
- Integration LightBDD xUnit3
- Integration LightBDD TUnit
- Integration ReqNRoll xUnit2
- Integration ReqNRoll xUnit3
- Integration ReqNRoll TUnit
Extensions
- Integration AtlasDataApi Extension
- Integration BigQuery Extension
- Integration Bigtable Extension
- Integration BlobStorage Extension
- Integration ClickHouse Extension
- Integration CloudStorage Extension
- Integration CosmosDB Extension
- Integration Dapper Extension
- Integration DynamoDB Extension
- Integration EF Core Relational Extension
- Integration Elasticsearch Extension
- Integration EventBridge Extension
- Integration EventHubs Extension
- Integration Grpc Extension
- Integration Kafka Extension
- Integration MassTransit Extension
- Integration MongoDB Extension
- Integration MySqlConnector Extension
- Integration Npgsql Extension
- Integration Oracle Extension
- Integration PubSub Extension
- Integration Redis Extension
- Integration S3 Extension
- Integration ServiceBus Extension
- Integration SNS Extension
- Integration Spanner Extension
- Integration SqlClient Extension
- Integration Sqlite Extension
- Integration SQS Extension
- Integration StorageQueues Extension
- Integration OpenTelemetry Extension
- Integration DispatchProxy Extension
- Integration MediatR Extension
- Integration PlantUML IKVM
Configuration
- Tracking Dependencies
- Tracking Custom Dependencies
- HTTP Tracking Setup
- Report Configuration
- Diagram Customisation
- Phase-Aware Tracking
- Content Formatting
- PlantUML Server Configuration
Features
- Generated Reports
- Search Syntax
- Component Diagrams
- PlantUML Browser Rendering
- Inline SVG Rendering
- Internal Flow Tracking
- Tags and Attributes
- Excluding Requests
- Excluded Headers
- Multi-Host Test Architectures
- Event-Driven Architecture Testing
- Service Bus Tracking Patterns
- Background Thread Correlation
- Parallel-Safe Background Correlation
- Event & Message Tracking
- Assertion Tracking
- Step Tracking
- Tabular Attributes
- Large Response and Diagram Handling
- Diagnostics and Debugging
- CI Summary Integration
- CI Artifact Upload
- Merging Parallel Reports
Reference