Skip to content

Aspire Dashboard

The Aspire dashboard is a standalone tool for viewing the logs, traces, and metrics that your apps emit over the OpenTelemetry Protocol (OTLP). It provides a web interface to explore telemetry during local development.

Add the following dependency to your project file:

NuGet
1
dotnet add package Testcontainers.AspireDashboard

You can start an Aspire Dashboard container instance from any .NET application. This example uses xUnit.net's IAsyncLifetime interface to manage the lifecycle of the container. The container is started in the InitializeAsync method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the DisposeAsync method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
private readonly AspireDashboardContainer _aspireDashboardContainer = new AspireDashboardBuilder(TestSession.GetImageFromDockerfile()).Build();

private readonly string _serviceName = Guid.NewGuid().ToString("N");

private readonly string _spanName = Guid.NewGuid().ToString("N");

public async ValueTask InitializeAsync()
{
    await _aspireDashboardContainer.StartAsync()
        .ConfigureAwait(false);
}

public ValueTask DisposeAsync()
{
    return _aspireDashboardContainer.DisposeAsync();
}

[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public async Task GetDashboardReturnsHttpStatusCodeOk()
{
    // Given
    using var httpClient = new HttpClient();
    httpClient.BaseAddress = new Uri(_aspireDashboardContainer.GetDashboardAddress());

    // When
    using var httpResponse = await httpClient.GetAsync("/", TestContext.Current.CancellationToken)
        .ConfigureAwait(true);

    // Then
    Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
    Assert.Equal(_aspireDashboardContainer.GetDashboardAddress(), _aspireDashboardContainer.GetConnectionString());
}

[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public Task OtlpExportOverGrpcSpanIsIngested()
{
    var endpoint = new Uri(_aspireDashboardContainer.GetOtlpGrpcAddress());
    return ExportAndAssertSpanIngestedAsync(endpoint, OtlpExportProtocol.Grpc);
}

[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public Task OtlpExportOverHttpSpanIsIngested()
{
    var endpoint = new Uri(new Uri(_aspireDashboardContainer.GetOtlpHttpAddress()), "/v1/traces");
    return ExportAndAssertSpanIngestedAsync(endpoint, OtlpExportProtocol.HttpProtobuf);
}

private async Task ExportAndAssertSpanIngestedAsync(Uri endpoint, OtlpExportProtocol protocol)
{
    // Given
    using var httpClient = new HttpClient();
    httpClient.BaseAddress = new Uri(_aspireDashboardContainer.GetDashboardAddress());

    var resourceBuilder = ResourceBuilder
        .CreateDefault()
        .AddService(_serviceName);

    var tracerProviderBuilder = Sdk
        .CreateTracerProviderBuilder()
        .SetResourceBuilder(resourceBuilder)
        .AddSource(_serviceName)
        .AddOtlpExporter(options =>
        {
            options.Endpoint = endpoint;
            options.Protocol = protocol;
        });

    // When
    using (var _ = tracerProviderBuilder.Build())
    {
        using (var activitySource = new ActivitySource(_serviceName))
        {
            using (var activity = activitySource.StartActivity(_spanName))
            {
                activity!.SetTag("test.key", "test-value");
            }
        }
    }

    // Then
    var spansJson = await httpClient.GetStringAsync("/api/telemetry/spans")
        .ConfigureAwait(true);

    using var jsonDocument = JsonDocument.Parse(spansJson);
    var totalCount = jsonDocument.RootElement.GetProperty("totalCount").GetInt32();

    Assert.Equal(1, totalCount);
    Assert.Contains(_serviceName, spansJson);
    Assert.Contains(_spanName, spansJson);
}

The test example exports a trace to the dashboard over OTLP and queries the dashboard's telemetry API to confirm that the span was ingested. Use GetOtlpGrpcAddress() or GetOtlpHttpAddress() to configure the OpenTelemetry exporter in your .NET service, and GetDashboardAddress() to open the dashboard's web interface.

The test example uses the following NuGet dependencies:

1
2
3
4
5
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
<PackageReference Include="coverlet.collector"/>
<PackageReference Include="xunit.runner.visualstudio"/>
<PackageReference Include="xunit.v3"/>
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol"/>

To execute the tests, use the command dotnet test from a terminal.

Tip

For the complete source code of this example and additional information, please refer to our test projects.