Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 222 additions & 8 deletions src/frontend/src/content/docs/dashboard/enable-browser-telemetry.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,21 @@ description: Learn how to enable browser telemetry in the Aspire dashboard.

import LearnMore from '@components/LearnMore.astro';
import { Aside, Tabs, TabItem } from '@astrojs/starlight/components';
import PivotSelector from '@components/PivotSelector.astro';
import Pivot from '@components/Pivot.astro';

The Aspire dashboard can be configured to receive telemetry sent from browser apps. This feature is useful for monitoring client-side performance and user interactions. Browser telemetry requires additional dashboard configuration and that the [JavaScript OTEL SDK](https://opentelemetry.io/docs/languages/js/getting-started/browser/) is added to the browser apps.
The Aspire dashboard can be configured to receive telemetry sent from browser apps. This feature is useful for monitoring client-side performance and user interactions. Browser telemetry requires additional dashboard configuration and that OpenTelemetry is configured in the browser app.

This article discusses how to enable browser telemetry in the Aspire dashboard.
This article discusses how to enable browser telemetry in the Aspire dashboard and provides examples for different approaches.

<PivotSelector
title="Select your browser telemetry approach"
key="browser-telemetry-approach"
options={[
{ id: "razor-javascript", title: "Razor Pages with JavaScript" },
{ id: "blazor-wasm", title: "Blazor WebAssembly" },
]}
/>

## Dashboard configuration

Expand Down Expand Up @@ -170,25 +181,141 @@ OTLP endpoints are unsecured by default in the standalone dashboard.

## Browser app configuration

<Pivot id="razor-javascript">

A browser app uses the [JavaScript OTEL SDK](https://opentelemetry.io/docs/languages/js/getting-started/browser/) to send telemetry to the dashboard. Successfully sending telemetry to the dashboard requires the SDK to be correctly configured.

### OTLP exporter
</Pivot>
<Pivot id="blazor-wasm">

Blazor WebAssembly apps can send telemetry to the dashboard using the .NET OpenTelemetry packages. This approach allows you to use familiar .NET APIs instead of JavaScript for telemetry configuration.

</Pivot>

### Install required packages

<Pivot id="razor-javascript">

OTLP exporters must be included in the browser app and configured with the SDK. For example, exporting distributed tracing with OTLP uses the [@opentelemetry/exporter-trace-otlp-proto](https://www.npmjs.com/package/@opentelemetry/exporter-trace-otlp-proto) package.

</Pivot>
<Pivot id="blazor-wasm">

Add the following NuGet packages to your Blazor WebAssembly project:

```xml title="XML — C# project file"
<ItemGroup>
<PackageReference Include="OpenTelemetry" Version="1.10.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.10.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.10.0" />
</ItemGroup>
```

</Pivot>

### Configure OpenTelemetry

<Pivot id="razor-javascript">

When OTLP is added to the SDK, OTLP options must be specified. OTLP options includes:

- `url`: The address that HTTP OTLP requests are made to. The address should be the dashboard HTTP OTLP endpoint and the path to the OTLP HTTP API. For example, `https://localhost:4318/v1/traces` for the trace OTLP exporter. If the browser app is launched by the AppHost then the HTTP OTLP endpoint is available from the `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable.

- `headers`: The headers sent with requests. If OTLP endpoint API key authentication is enabled the `x-otlp-api-key` header must be sent with OTLP requests. If the browser app is launched by the AppHost then the API key is available from the `OTEL_EXPORTER_OTLP_HEADERS` environment variable.

### Browser metadata
</Pivot>
<Pivot id="blazor-wasm">

Create a component or add the following code to an existing component to configure OpenTelemetry:

```razor title="Razor — Components/Trace.razor"
@using OpenTelemetry
@using OpenTelemetry.Exporter
@using OpenTelemetry.Resources
@using OpenTelemetry.Trace
@using System.Diagnostics
@implements IDisposable

<button @onclick="Trace">Trace</button>

@code {
private TracerProvider? _tracerProvider;
private ActivitySource? _activitySource;

protected override void OnInitialized()
{
var resourceBuilder = ResourceBuilder.CreateDefault()
.AddService("blazor-otel");

_tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetResourceBuilder(resourceBuilder)
.AddSource("ui")
.AddOtlpExporter(options =>
{
var endpoint = Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT")
?? "https://localhost:4318";
options.Endpoint = new Uri(endpoint);
options.Protocol = OtlpExportProtocol.HttpProtobuf;
options.ExportProcessorType = ExportProcessorType.Simple;

// Add API key header if available
var headers = Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_HEADERS");
if (!string.IsNullOrEmpty(headers))
{
foreach (var header in headers.Split(','))
{
var parts = header.Split('=', 2);
if (parts.Length == 2)
{
options.Headers = $"{parts[0].Trim()}={parts[1].Trim()}";
}
}
}
})
.Build();

_activitySource = new ActivitySource("ui");
}

private void Trace()
{
using var activity = _activitySource?.StartActivity("trace-click", ActivityKind.Client);
activity?.AddEvent(new ActivityEvent("Trace button click"));
}

public void Dispose()
{
_activitySource?.Dispose();
_tracerProvider?.Dispose();
}
}
```

The preceding code:

- Initializes the `TracerProvider` and `ActivitySource` once during component initialization in `OnInitialized()`.
- Creates a resource builder with a service name of `blazor-otel`.
- Configures the tracer provider to use an OTLP exporter with `HttpProtobuf` protocol (required for browser compatibility).
- Reads the endpoint from `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable if available, otherwise defaults to `https://localhost:4318`.
- Parses and applies authentication headers from `OTEL_EXPORTER_OTLP_HEADERS` if present (e.g., for API key authentication).
- Creates an activity when the button is clicked and adds an event to it.
- Properly disposes of the tracer provider and activity source when the component is disposed.

<Aside type="caution">
**Important**: Blazor WebAssembly apps must use `OtlpExportProtocol.HttpProtobuf` because browsers do not support gRPC. The environment variables (`OTEL_EXPORTER_OTLP_ENDPOINT` and `OTEL_EXPORTER_OTLP_HEADERS`) are typically set by the AppHost for interactive auto render modes where the component initializes on the server before running in the browser. For pure client-side Blazor WebAssembly apps, consider storing these values in `appsettings.json` or using `IConfiguration` instead.
</Aside>

</Pivot>

### Set trace context

<Pivot id="razor-javascript">

When a browser app is configured to collect distributed traces, the browser app can set the trace parent a browser's spans using the `meta` element in the HTML. The value of the `name="traceparent"` meta element should correspond to the current trace.

In a .NET app, for example, the trace parent value would likely be assigned from the `Activity.Current` and passing its `Activity.Id` value as the `content`. For example, consider the following Razor code:

```razor
```razor title="Razor — Layout.razor"
<head>
@if (Activity.Current is { } currentActivity)
{
Expand All @@ -200,11 +327,25 @@ In a .NET app, for example, the trace parent value would likely be assigned from

The preceding code sets the `traceparent` meta element to the current activity ID.

## Example browser telemetry code
</Pivot>
<Pivot id="blazor-wasm">

For production applications, consider the following best practices:

- Initialize the tracer provider once during application startup rather than on each trace operation.
- Use dependency injection to provide the tracer provider to components.
- Configure the OTLP endpoint and other settings from configuration or environment variables.
- Handle authentication headers if OTLP endpoint API key authentication is enabled.

</Pivot>

## Example implementation

<Pivot id="razor-javascript">

The following JavaScript code demonstrates the initialization of the OpenTelemetry JavaScript SDK and the sending of telemetry data to the dashboard:

```javascript
```javascript title="JavaScript — index.js"
import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
Expand Down Expand Up @@ -255,7 +396,7 @@ function parseDelimitedValues(s) {

The preceding JavaScript code defines an `initializeTelemetry` function that expects the OTLP endpoint URL, the headers, and the resource attributes. These parameters are provided by the consuming browser app that pulls them from the environment variables set by the app host. Consider the following Razor code:

```razor {32-39}
```razor title="Razor — Layout.razor" {32-39}
@using System.Diagnostics
<!DOCTYPE html>
<html lang="en">
Expand Down Expand Up @@ -305,6 +446,79 @@ The bundling and minification of the JavaScript code is beyond the scope of this

For the complete working example of how to configure the JavaScript OTEL SDK to send telemetry to the dashboard, see the [browser telemetry sample](https://github.com/dotnet/aspire/tree/main/playground/BrowserTelemetry).

</Pivot>
<Pivot id="blazor-wasm">

The following example demonstrates a complete implementation using Blazor WebAssembly. For a full working sample, refer to the Aspire samples repository.

```razor title="Razor — Pages/Telemetry.razor"
@page "/telemetry"
@using OpenTelemetry
@using OpenTelemetry.Exporter
@using OpenTelemetry.Resources
@using OpenTelemetry.Trace
@using System.Diagnostics
@implements IDisposable

<h3>Browser Telemetry Example</h3>
<button @onclick="SendTrace">Send Trace</button>

@code {
private TracerProvider? _tracerProvider;
private ActivitySource? _activitySource;

protected override void OnInitialized()
{
var resourceBuilder = ResourceBuilder.CreateDefault()
.AddService("blazor-browser-telemetry");

_tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetResourceBuilder(resourceBuilder)
.AddSource("browser-app")
.AddOtlpExporter(options =>
{
var endpoint = Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT")
?? "https://localhost:4318";
options.Endpoint = new Uri(endpoint);
options.Protocol = OtlpExportProtocol.HttpProtobuf;
options.ExportProcessorType = ExportProcessorType.Simple;

// Add API key header if available
var headers = Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_HEADERS");
if (!string.IsNullOrEmpty(headers))
{
foreach (var header in headers.Split(','))
{
var parts = header.Split('=', 2);
if (parts.Length == 2)
{
options.Headers = $"{parts[0].Trim()}={parts[1].Trim()}";
}
}
}
})
.Build();

_activitySource = new ActivitySource("browser-app");
}

private void SendTrace()
{
using var activity = _activitySource?.StartActivity("user-action", ActivityKind.Client);
activity?.AddEvent(new ActivityEvent("Button clicked"));
activity?.SetTag("user.action", "send-trace");
}

public void Dispose()
{
_activitySource?.Dispose();
_tracerProvider?.Dispose();
}
}
```

</Pivot>

## See also

- [Aspire dashboard configuration](/dashboard/configuration/)
Expand Down
Loading