OpenTelemetry instrumentation for xUnit v3 tests. Automatically wraps tests in distributed tracing spans with HTTP, SQL, and gRPC instrumentation.
- Requirements
- Installation
- Quick Start
- How It Works
- Configuration
- Failed Test Logging
- Viewing Telemetry
- API Reference
- Troubleshooting
- License
- .NET Standard 2.0 compatible runtime (.NET Framework 4.6.1+, .NET Core 2.0+, .NET 5+)
- xUnit v3 (not compatible with xUnit v2)
dotnet add package xUnitOTelusing Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
using xUnitOTel.Diagnostics;
public class TestSetup : IAsyncLifetime
{
private IHost _host = null!;
public IHost Host => _host;
public T GetRequiredService<T>() where T : notnull
=> _host.Services.GetRequiredService<T>();
public async ValueTask InitializeAsync()
{
var builder = Microsoft.Extensions.Hosting.Host.CreateApplicationBuilder();
builder.Services.AddOTelDiagnostics(
configureMeterProviderBuilder: m => m.AddOtlpExporter(),
configureTracerProviderBuilder: t => t.AddOtlpExporter(),
configureLoggingBuilder: options => options.AddOpenTelemetry(o => o.AddOtlpExporter())
);
builder.Services.AddHttpClient();
_host = builder.Build();
await _host.StartAsync();
}
public async ValueTask DisposeAsync()
{
await _host.StopAsync();
_host.Dispose();
}
}Add to any test file to trace all tests in the assembly:
[assembly: Trace]
[assembly: AssemblyFixture(typeof(TestSetup))]Or apply [Trace] to individual test methods.
public class ApiTests(TestSetup fixture)
{
[Fact]
public async Task GetUsers_ReturnsSuccess()
{
var client = fixture.GetRequiredService<IHttpClientFactory>().CreateClient();
var response = await client.GetAsync("https://api.example.com/users");
Assert.True(response.IsSuccessStatusCode);
}
}All HTTP requests, timing, and trace IDs appear in test output automatically.
flowchart LR
A[Test Method] --> B["[Trace] Attribute"]
B --> C[Activity/Span]
C --> D[OpenTelemetry]
D --> E[OTLP Exporter]
E --> F[Aspire Dashboard]
TestSetupcreates Host withAddOTelDiagnostics()configuring OpenTelemetry[Trace]attribute wraps tests in Activity spans- Spans tagged with
test.class.method,test.name,test.framework - HTTP/SQL/gRPC calls auto-instrumented as child spans
- Logs attached as Activity events
| Parameter | Type | Description |
|---|---|---|
serviceName |
string? |
Service name for telemetry (default: entry assembly name) |
configureMeterProviderBuilder |
Action<MeterProviderBuilder>? |
Configure metrics exporters |
configureTracerProviderBuilder |
Action<TracerProviderBuilder>? |
Configure trace exporters |
configureLoggingBuilder |
Action<ILoggingBuilder>? |
Configure log exporters |
| Property | Type | Default | Description |
|---|---|---|---|
CaptureError |
bool |
true |
Capture stderr to test output |
CaptureOut |
bool |
true |
Capture stdout to test output |
| Tag | Description |
|---|---|
test.class.method |
Full test class and method name |
test.name |
Test method name |
test.framework |
Always "xunit" |
testrun.id |
Unique ID for test run session |
Automatically exports failed test logs to JSON files for LLM analysis, CI/CD debugging, or archiving.
builder.Services.AddOTelDiagnostics(
configureFailedTestLogging: opts => {
opts.OutputDirectory = "test-failures";
}
);| Option | Default | Description |
|---|---|---|
Enabled |
true |
Enable/disable feature |
OutputDirectory |
"logs" |
Where to save JSON files |
File naming: {TestClass}.{TestName}_{yyyyMMdd_HHmmss}.json
{
"traceId": "abc123...",
"testClass": "ApiTests",
"testName": "GetUsers_ReturnsSuccess",
"timestamp": "2025-01-15T10:30:00Z",
"failure": {
"messages": ["Expected: true, Actual: false"],
"stackTraces": ["at ApiTests.GetUsers_ReturnsSuccess()..."]
},
"logs": [
{
"timestamp": "2025-01-15T10:29:59Z",
"level": "Information",
"category": "System.Net.Http.HttpClient",
"message": "Sending HTTP request GET https://api.example.com/users",
"exception": null
}
]
}- Only failed tests create files (passing tests are ignored)
- Works with parallel test execution (AsyncLocal isolation)
- HTTP/SQL/gRPC instrumentation logs included automatically
Start the dashboard:
docker run -d \
-p 18888:18888 \
-p 4317:18889 \
-e DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true \
--name aspire-dashboard \
mcr.microsoft.com/dotnet/aspire-dashboard:9.0Open http://localhost:18888 to view traces.
For simple debugging without external tools:
builder.Services.AddOTelDiagnostics(
configureTracerProviderBuilder: t => t.AddConsoleExporter()
);xUnit BeforeAfterTestAttribute that wraps tests in OpenTelemetry spans.
// Assembly-level (all tests)
[assembly: Trace]
// Method-level
[Fact]
[Trace]
public void MyTest() { }Entry point for configuring OpenTelemetry.
services.AddOTelDiagnostics(
serviceName: "MyTests",
configureTracerProviderBuilder: t => t.AddOtlpExporter()
);Static ActivitySource for creating custom spans:
using var activity = ApplicationDiagnostics.ActivitySource.StartActivity("CustomOperation");
// ... operation codeThe attribute requires OpenTelemetry to be configured via AddOTelDiagnostics(). Ensure your TestSetup fixture is properly registered with [assembly: AssemblyFixture(typeof(TestSetup))].
- Verify OTLP endpoint is reachable (default:
localhost:4317) - Check that
AddOtlpExporter()is configured - Ensure Aspire Dashboard is running
This package only supports xUnit v3. For xUnit v2, consider upgrading or using a different instrumentation approach.
AddOTelDiagnostics() automatically adds instrumentation for:
System.Net.Http(HttpClient)System.Data.SqlClient/Microsoft.Data.SqlClient- gRPC clients
Ensure these libraries are used via dependency injection from the Host.
MIT License - see LICENSE for details.
