diff --git a/src/frontend/src/content/docs/dashboard/enable-browser-telemetry.mdx b/src/frontend/src/content/docs/dashboard/enable-browser-telemetry.mdx
index 799bedf..5b1b4cb 100644
--- a/src/frontend/src/content/docs/dashboard/enable-browser-telemetry.mdx
+++ b/src/frontend/src/content/docs/dashboard/enable-browser-telemetry.mdx
@@ -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.
+
+
## Dashboard configuration
@@ -170,25 +181,141 @@ OTLP endpoints are unsecured by default in the standalone dashboard.
## Browser app configuration
+
+
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
+
+
+
+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.
+
+
+
+### Install required packages
+
+
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.
+
+
+
+Add the following NuGet packages to your Blazor WebAssembly project:
+
+```xml title="XML — C# project file"
+
+
+
+
+
+```
+
+
+
+### Configure OpenTelemetry
+
+
+
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
+
+
+
+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
+
+
+
+@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.
+
+
+
+
+
+### Set trace context
+
+
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"
@if (Activity.Current is { } currentActivity)
{
@@ -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
+
+
+
+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.
+
+
+
+## Example implementation
+
+
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';
@@ -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
@@ -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).
+
+
+
+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
+
+
Browser Telemetry Example
+
+
+@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();
+ }
+}
+```
+
+
+
## See also
- [Aspire dashboard configuration](/dashboard/configuration/)