diff --git a/src/frontend/config/sidebar/dashboard.topics.ts b/src/frontend/config/sidebar/dashboard.topics.ts index c7c68c37e..90f98e5bb 100644 --- a/src/frontend/config/sidebar/dashboard.topics.ts +++ b/src/frontend/config/sidebar/dashboard.topics.ts @@ -219,7 +219,20 @@ export const dashboardTopics: StarlightSidebarTopicsUserConfig = { uk: 'Увімкнути телеметрію браузера', 'zh-CN': '启用浏览器遥测', }, - slug: 'dashboard/enable-browser-telemetry', + items: [ + { + label: 'Overview', + slug: 'dashboard/enable-browser-telemetry', + }, + { + label: 'Browser app configuration', + slug: 'dashboard/enable-browser-telemetry/browser-app-configuration', + }, + { + label: 'Blazor WebAssembly integration', + slug: 'dashboard/enable-browser-telemetry/blazor-webassembly', + }, + ], }, { label: 'Microsoft telemetry', @@ -244,5 +257,28 @@ export const dashboardTopics: StarlightSidebarTopicsUserConfig = { }, slug: 'dashboard/microsoft-collected-dashboard-telemetry', }, + { + label: 'Telemetry after deployment', + translations: { + da: 'Telemetri efter implementering', + de: 'Telemetrie nach der Bereitstellung', + en: 'Telemetry after deployment', + es: 'Telemetría después de la implementación', + fr: 'Télémétrie après le déploiement', + hi: 'परिनियोजन के बाद टेलीमेट्री', + id: 'Telemetri setelah penerapan', + it: 'Telemetria dopo la distribuzione', + ja: 'デプロイ後のテレメトリ', + ko: '배포 후 텔레메트리', + pt: 'Telemetria após a implantação', + 'pt-BR': 'Telemetria após a implantação', + 'pt-PT': 'Telemetria após a implementação', + ru: 'Телеметрия после развертывания', + tr: 'Dağıtımdan sonra telemetri', + uk: 'Телеметрія після розгортання', + 'zh-CN': '部署后的遥测', + }, + slug: 'dashboard/telemetry-after-deployment', + }, ], }; diff --git a/src/frontend/src/content/docs/dashboard/enable-browser-telemetry/blazor-webassembly.mdx b/src/frontend/src/content/docs/dashboard/enable-browser-telemetry/blazor-webassembly.mdx new file mode 100644 index 000000000..cb1641eb3 --- /dev/null +++ b/src/frontend/src/content/docs/dashboard/enable-browser-telemetry/blazor-webassembly.mdx @@ -0,0 +1,186 @@ +--- +title: Blazor WebAssembly integration +description: Learn how to integrate browser telemetry in a Blazor WebAssembly app using JavaScript interop. +--- + +import LearnMore from '@components/LearnMore.astro'; +import { Aside } from '@astrojs/starlight/components'; + +Blazor WebAssembly (WASM) apps run entirely in the browser using a .NET runtime compiled to WebAssembly. Like other browser apps, Blazor WASM apps use the [JavaScript OTEL SDK](https://opentelemetry.io/docs/languages/js/getting-started/browser/) to send telemetry to the Aspire dashboard via JavaScript interop. + +## Provide OTEL configuration to the browser using a CORS proxy + +Blazor WASM apps can't read server-side environment variables directly. When the app is hosted by an ASP.NET Core server (for example, a Blazor Web App or hosted Blazor WASM project), expose the OTEL configuration through a server-side CORS proxy. Because the browser sends telemetry to the same-origin proxy rather than directly to the dashboard, no CORS configuration is required on the dashboard and no sensitive values reach the browser. + +Add a lightweight configuration endpoint that returns only the proxy path: + +```csharp title="C# — Program.cs (Server)" +// Register an HttpClient for forwarding OTEL traffic +builder.Services.AddHttpClient("otel-proxy"); + +// Configuration endpoint — returns the proxy URL only (no API key) +app.MapGet("/api/telemetry-config", () => new +{ + Endpoint = "/api/otel-proxy" +}); + +// CORS proxy endpoint — forwards OTEL traffic with the API key added server-side +app.MapPost("/api/otel-proxy/{**path}", async ( + string path, + HttpContext context, + IHttpClientFactory httpClientFactory, + IConfiguration config) => +{ + var dashboardEndpoint = config.GetValue("OTEL_EXPORTER_OTLP_ENDPOINT"); + if (string.IsNullOrEmpty(dashboardEndpoint)) + return Results.NotFound(); + + var client = httpClientFactory.CreateClient("otel-proxy"); + + using var requestBody = new StreamContent(context.Request.Body); + requestBody.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue + .Parse(context.Request.ContentType ?? "application/x-protobuf"); + + using var request = new HttpRequestMessage( + HttpMethod.Post, + $"{dashboardEndpoint.TrimEnd('/')}/{path}") + { + Content = requestBody + }; + + // Attach OTLP API key server-side — never sent to the browser + var headersEnv = config.GetValue("OTEL_EXPORTER_OTLP_HEADERS") ?? string.Empty; + foreach (var header in headersEnv.Split(',', StringSplitOptions.RemoveEmptyEntries)) + { + var parts = header.Split('=', 2); + if (parts.Length == 2) + request.Headers.TryAddWithoutValidation(parts[0].Trim(), parts[1].Trim()); + } + + var response = await client.SendAsync(request); + context.Response.StatusCode = (int)response.StatusCode; + await response.Content.CopyToAsync(context.Response.Body); + return Results.Empty; +}); +``` + +The browser fetches this endpoint and receives only the proxy URL. In the Blazor WASM app, call `initializeTelemetry` with the proxy URL so the JavaScript OTEL SDK sends data to the same-origin proxy rather than the dashboard directly. For more details, see [Initialize OTEL from a JavaScript initializer](#initialize-otel-from-a-javascript-initializer) below. + + + +## Initialize OTEL from a JavaScript initializer + +Blazor WASM supports [JavaScript initializers](https://learn.microsoft.com/aspnet/core/blazor/javascript-interoperability/?view=aspnetcore-9.0#javascript-initializers) that run automatically during the Blazor startup lifecycle. You can use these initializers to initialize OTEL telemetry. Create a file named `{AssemblyName}.lib.module.js` in the `wwwroot` folder of your Blazor WASM project, replacing `{AssemblyName}` with your project's assembly name, then export an `afterWebAssemblyStarted` function: + +```javascript title="JavaScript — {YourApp}.lib.module.js (Client wwwroot)" +export async function afterWebAssemblyStarted(blazor) { + const response = await fetch('/api/telemetry-config'); + if (!response.ok) return; + + const config = await response.json(); + if (config.endpoint) { + initializeTelemetry(config.endpoint, config.headers, config.resourceAttributes); + } +} +``` + + + +Blazor automatically discovers and executes the initializer during startup — no Razor component or `IJSRuntime` injection is needed. Because `afterWebAssemblyStarted` fires after the Blazor runtime has loaded but before components render, telemetry is active from the very first component lifecycle. + +## Backend-to-frontend trace correlation + +To correlate browser spans with server-side traces, include the current trace context in the server-rendered HTML. When the Blazor WASM app is hosted within a server-rendered page, such as a Blazor Web App with prerendering, the server can write the `traceparent` meta tag during prerender: + +```razor title="Razor — App.razor (Server prerender)" +@using System.Diagnostics + + + + @if (Activity.Current is { } currentActivity) + { + + } + + +``` + +The JavaScript OTEL SDK reads this `traceparent` value automatically when `DocumentLoadInstrumentation` is registered, linking the browser spans to the originating server trace. + +## Authenticated OTEL proxy + +When the Aspire dashboard's OTLP API key must not be exposed to the browser, route telemetry through a server-side proxy endpoint. The browser sends telemetry to the proxy, and the proxy forwards it to the dashboard with the API key included as a server-side secret: + +```csharp title="C# — Program.cs (Server proxy endpoint)" +// Register an HttpClient for the OTEL proxy +builder.Services.AddHttpClient("otel-proxy"); + +// ... + +app.MapPost("/api/telemetry/{**path}", async ( + string path, + HttpContext context, + IHttpClientFactory httpClientFactory) => +{ + var dashboardEndpoint = Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT"); + if (string.IsNullOrEmpty(dashboardEndpoint)) + { + return Results.NotFound(); + } + + var client = httpClientFactory.CreateClient("otel-proxy"); + + using var requestBody = new StreamContent(context.Request.Body); + requestBody.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue + .Parse(context.Request.ContentType ?? "application/x-protobuf"); + + using var request = new HttpRequestMessage( + HttpMethod.Post, + $"{dashboardEndpoint.TrimEnd('/')}/{path}") + { + Content = requestBody + }; + + // Copy OTLP API key from server environment to the forwarded request + var headersEnv = Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_HEADERS") ?? string.Empty; + foreach (var header in headersEnv.Split(',', StringSplitOptions.RemoveEmptyEntries)) + { + var parts = header.Split('=', 2); + if (parts.Length == 2) + { + request.Headers.TryAddWithoutValidation(parts[0].Trim(), parts[1].Trim()); + } + } + + var response = await client.SendAsync(request); + + context.Response.StatusCode = (int)response.StatusCode; + await response.Content.CopyToAsync(context.Response.Body); + return Results.Empty; +}); +``` + +Configure the JavaScript OTEL SDK in the Blazor WASM app to point to the proxy endpoint instead of the dashboard directly: + +```javascript title="JavaScript — telemetry.js (Client)" +export function initializeTelemetry(resourceAttributes) { + const otlpOptions = { + url: '/api/telemetry/v1/traces' // Proxy endpoint, same origin - no CORS needed + }; + // ... rest of SDK initialization +} +``` + +This pattern eliminates the need for CORS configuration on the dashboard because the browser communicates only with the same-origin server. The API key stays on the server and is never visible to the browser. + +## See also + +- [Enable browser telemetry overview](/dashboard/enable-browser-telemetry/) +- [Browser app configuration](/dashboard/enable-browser-telemetry/browser-app-configuration/) +- [Aspire dashboard configuration](/dashboard/configuration/) +- [Standalone Aspire dashboard](/dashboard/standalone/) +- [Telemetry after deployment](/dashboard/telemetry-after-deployment/) diff --git a/src/frontend/src/content/docs/dashboard/enable-browser-telemetry/browser-app-configuration.mdx b/src/frontend/src/content/docs/dashboard/enable-browser-telemetry/browser-app-configuration.mdx new file mode 100644 index 000000000..7f27f045e --- /dev/null +++ b/src/frontend/src/content/docs/dashboard/enable-browser-telemetry/browser-app-configuration.mdx @@ -0,0 +1,152 @@ +--- +title: Browser app configuration +description: Learn how to configure the JavaScript OTEL SDK in your browser app to send telemetry to the Aspire dashboard. +--- + +import LearnMore from '@components/LearnMore.astro'; +import { Aside } from '@astrojs/starlight/components'; + +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. + +Before configuring the browser app, ensure the dashboard is configured with an [OTLP HTTP endpoint and CORS](/dashboard/enable-browser-telemetry/). For Blazor WebAssembly apps, see [Blazor WebAssembly integration](/dashboard/enable-browser-telemetry/blazor-webassembly/). + +## OTLP exporter + +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. + +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 + +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 + + @if (Activity.Current is { } currentActivity) + { + + } + + +``` + +The preceding code sets the `traceparent` meta element to the current activity ID. + +## Example browser telemetry code + +The following JavaScript code demonstrates the initialization of the OpenTelemetry JavaScript SDK and the sending of telemetry data to the dashboard: + +```javascript +import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'; +import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load'; +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'; +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { resourceFromAttributes } from '@opentelemetry/resources'; +import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; +import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'; +import { ZoneContextManager } from '@opentelemetry/context-zone'; + +export function initializeTelemetry(otlpUrl, headers, resourceAttributes) { + const otlpOptions = { + url: `${otlpUrl}/v1/traces`, + headers: parseDelimitedValues(headers) + }; + + const attributes = parseDelimitedValues(resourceAttributes); + attributes[SemanticResourceAttributes.SERVICE_NAME] = 'browser'; + + const provider = new WebTracerProvider({ + resource: resourceFromAttributes(attributes), + }); + provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); + provider.addSpanProcessor(new SimpleSpanProcessor(new OTLPTraceExporter(otlpOptions))); + + provider.register({ + // Prefer ZoneContextManager: supports asynchronous operations + contextManager: new ZoneContextManager(), + }); + + // Registering instrumentations + registerInstrumentations({ + instrumentations: [new DocumentLoadInstrumentation()], + }); +} + +function parseDelimitedValues(s) { + const headers = s.split(','); // Split by comma + const result = {}; + + headers.forEach(header => { + const [key, value] = header.split('='); // Split by equal sign + result[key.trim()] = value.trim(); // Add to the object, trimming spaces + }); + + return result; +} +``` + +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} +@using System.Diagnostics + + + + + + @ViewData["Title"] - BrowserTelemetry + + + + @if (Activity.Current is { } currentActivity) + { + + } + + +
+ +
+
+
+ @RenderBody() +
+
+ @await RenderSectionAsync("Scripts", required: false) + + @if (Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT") is { Length: > 0 } endpointUrl) + { + var headers = Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_HEADERS"); + var attributes = Environment.GetEnvironmentVariable("OTEL_RESOURCE_ATTRIBUTES"); + + } + + +``` + + + +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/microsoft/aspire/tree/main/playground/BrowserTelemetry). + +## See also + +- [Enable browser telemetry overview](/dashboard/enable-browser-telemetry/) +- [Blazor WebAssembly integration](/dashboard/enable-browser-telemetry/blazor-webassembly/) +- [Aspire dashboard configuration](/dashboard/configuration/) +- [Standalone Aspire dashboard](/dashboard/standalone/) +- [Telemetry after deployment](/dashboard/telemetry-after-deployment/) diff --git a/src/frontend/src/content/docs/dashboard/enable-browser-telemetry.mdx b/src/frontend/src/content/docs/dashboard/enable-browser-telemetry/index.mdx similarity index 51% rename from src/frontend/src/content/docs/dashboard/enable-browser-telemetry.mdx rename to src/frontend/src/content/docs/dashboard/enable-browser-telemetry/index.mdx index 4deb371b6..215e44535 100644 --- a/src/frontend/src/content/docs/dashboard/enable-browser-telemetry.mdx +++ b/src/frontend/src/content/docs/dashboard/enable-browser-telemetry/index.mdx @@ -1,15 +1,15 @@ --- title: Enable browser telemetry -description: Learn how to enable browser telemetry in the Aspire dashboard. +description: Learn how to enable browser telemetry in the Aspire dashboard, including OTLP HTTP and CORS configuration. --- import LearnMore from '@components/LearnMore.astro'; -import { Aside } from '@astrojs/starlight/components'; +import { Aside, CardGrid, LinkCard } from '@astrojs/starlight/components'; import OsAwareTabs from '@components/OsAwareTabs.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. -This article discusses how to enable browser telemetry in the Aspire dashboard. +This article discusses how to configure the Aspire dashboard to receive browser telemetry. For information on configuring the browser app itself, see [Browser app configuration](/dashboard/enable-browser-telemetry/browser-app-configuration/). For Blazor WebAssembly apps, see [Blazor WebAssembly integration](/dashboard/enable-browser-telemetry/blazor-webassembly/). ## Dashboard configuration @@ -169,144 +169,23 @@ API key authentication is automatically enabled when the dashboard is run from t 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 - -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. - -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 - -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 - - @if (Activity.Current is { } currentActivity) - { - - } - - -``` - -The preceding code sets the `traceparent` meta element to the current activity ID. - -## Example browser telemetry code - -The following JavaScript code demonstrates the initialization of the OpenTelemetry JavaScript SDK and the sending of telemetry data to the dashboard: - -```javascript -import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'; -import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load'; -import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { resourceFromAttributes } from '@opentelemetry/resources'; -import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'; -import { ZoneContextManager } from '@opentelemetry/context-zone'; - -export function initializeTelemetry(otlpUrl, headers, resourceAttributes) { - const otlpOptions = { - url: `${otlpUrl}/v1/traces`, - headers: parseDelimitedValues(headers) - }; - - const attributes = parseDelimitedValues(resourceAttributes); - attributes[SemanticResourceAttributes.SERVICE_NAME] = 'browser'; - - const provider = new WebTracerProvider({ - resource: resourceFromAttributes(attributes), - }); - provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); - provider.addSpanProcessor(new SimpleSpanProcessor(new OTLPTraceExporter(otlpOptions))); - - provider.register({ - // Prefer ZoneContextManager: supports asynchronous operations - contextManager: new ZoneContextManager(), - }); - - // Registering instrumentations - registerInstrumentations({ - instrumentations: [new DocumentLoadInstrumentation()], - }); -} - -function parseDelimitedValues(s) { - const headers = s.split(','); // Split by comma - const result = {}; - - headers.forEach(header => { - const [key, value] = header.split('='); // Split by equal sign - result[key.trim()] = value.trim(); // Add to the object, trimming spaces - }); - - return result; -} -``` - -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} -@using System.Diagnostics - - - - - - @ViewData["Title"] - BrowserTelemetry - - - - @if (Activity.Current is { } currentActivity) - { - - } - - -
- -
-
-
- @RenderBody() -
-
- @await RenderSectionAsync("Scripts", required: false) - - @if (Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT") is { Length: > 0 } endpointUrl) - { - var headers = Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_HEADERS"); - var attributes = Environment.GetEnvironmentVariable("OTEL_RESOURCE_ATTRIBUTES"); - - } - - -``` - - - -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/microsoft/aspire/tree/main/playground/BrowserTelemetry). +## Next steps + + + + + ## See also - [Aspire dashboard configuration](/dashboard/configuration/) - [Standalone Aspire dashboard](/dashboard/standalone/) +- [Telemetry after deployment](/dashboard/telemetry-after-deployment/) diff --git a/src/frontend/src/content/docs/dashboard/telemetry-after-deployment.mdx b/src/frontend/src/content/docs/dashboard/telemetry-after-deployment.mdx new file mode 100644 index 000000000..64fe3d1dd --- /dev/null +++ b/src/frontend/src/content/docs/dashboard/telemetry-after-deployment.mdx @@ -0,0 +1,189 @@ +--- +title: Telemetry after deployment +description: Understand how telemetry and the Aspire dashboard work after you deploy your app, and how to configure production-grade observability. +--- + +import { Aside, Steps } from '@astrojs/starlight/components'; +import LearnMore from '@components/LearnMore.astro'; + +The Aspire dashboard is designed for local development and short-term diagnostics. It stores telemetry in memory, which means telemetry is lost when the dashboard restarts and there are built-in limits on how much data it retains. After deploying your app to a production environment, you need to configure a persistent telemetry backend. + +This article explains what changes when you deploy your app, how to configure production telemetry with Azure Monitor, and how to access the Aspire dashboard if it's included in your deployment. + +## Development versus production telemetry + +During development, Aspire automatically starts the dashboard and configures your app's OTEL environment variables to send telemetry to it. This works well for local diagnostics but is not suitable for production for the following reasons: + +| | Aspire dashboard | Production telemetry backend | +|---|---|---| +| **Storage** | In-memory only | Persistent (database, cloud service) | +| **Retention** | Lost on restart | Configurable (days, months, indefinitely) | +| **Telemetry limits** | Default 10,000 log entries, 10,000 traces | Configurable or unlimited | +| **Access** | Local or private | Secured, multi-user | +| **Alerting** | None | Configurable alerts and dashboards | + +After deploying, configure your app to send telemetry to a persistent backend. Azure Monitor with Application Insights is the recommended production telemetry solution for Azure-hosted apps. + +## Configure Azure Monitor for production telemetry + +[Azure Monitor](https://learn.microsoft.com/azure/azure-monitor/overview) collects, analyzes, and responds to telemetry data from your cloud applications. Aspire has built-in support for Azure Application Insights, which is the Azure Monitor feature for application telemetry. + +### Add Application Insights to your AppHost + +Add the Application Insights resource to your AppHost project: + +```csharp title="C# — AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +var insights = builder.AddAzureApplicationInsights("app-insights"); + +builder.AddProject("apiservice") + .WithReference(insights); + +builder.AddProject("webfrontend") + .WithReference(insights) + .WithExternalHttpEndpoints(); + +builder.Build().Run(); +``` + +When you reference Application Insights, Aspire automatically configures the `APPLICATIONINSIGHTS_CONNECTION_STRING` environment variable for each service. The `Azure.Monitor.OpenTelemetry.AspNetCore` package uses the `UseAzureMonitor()` method to read this variable and send telemetry to Application Insights. + + + +### Use OpenTelemetry with Azure Monitor + +Add the [📦 Azure.Monitor.OpenTelemetry.AspNetCore](https://www.nuget.org/packages/Azure.Monitor.OpenTelemetry.AspNetCore) package to each service project to enable OTEL-based export to Azure Monitor: + +```csharp title="C# — Program.cs (service)" +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +// Add Azure Monitor OTEL export when connection string is available +builder.Services.AddOpenTelemetry() + .UseAzureMonitor(); +``` + +Aspire's service defaults already configure OpenTelemetry. The `UseAzureMonitor()` call adds Azure Monitor as an additional exporter. When the `APPLICATIONINSIGHTS_CONNECTION_STRING` environment variable is set, which Aspire does automatically when you reference the resource, telemetry flows to Azure Monitor. + + +For more information, see [Azure Monitor OpenTelemetry documentation](https://learn.microsoft.com/azure/azure-monitor/app/opentelemetry-enable). + + +## Troubleshoot missing logs after deployment + +If some or all logs don't appear in your telemetry backend after deployment, check the following common causes. + +### OTEL environment variables not set + +Verify that the OTEL environment variables are correctly set in your deployed containers. When deploying to Azure Container Apps, Aspire sets these variables automatically when you use `WithReference` for Application Insights or when you configure the OTEL endpoint. + +Check for these environment variables in your deployed container: + +- `OTEL_EXPORTER_OTLP_ENDPOINT`: The OTLP endpoint receiving telemetry. +- `OTEL_EXPORTER_OTLP_HEADERS`: Headers including the API key, if required. +- `APPLICATIONINSIGHTS_CONNECTION_STRING`: Application Insights connection string, when using Azure Monitor. + +For Azure Container Apps deployments, verify variables in the [Azure Portal](https://portal.azure.com) by navigating to your Container app → **Containers** → **Environment variables**. + +### Telemetry not exported from the app + +Verify that your app is configured to export telemetry. All service projects should call `AddServiceDefaults()` in their `Program.cs`, which sets up OpenTelemetry: + +```csharp title="C# — Program.cs" +var builder = WebApplication.CreateBuilder(args); + +// This configures OpenTelemetry with logging, metrics, and tracing +builder.AddServiceDefaults(); +``` + +If you're not using Aspire service defaults, ensure your app is configured with the OpenTelemetry SDK and an appropriate exporter. + +### Telemetry sampling missed an event + +To ensure that the OpenTelemetry SDK doesn't flood logs with too much data, you can configure it to sample a proportion of traces, so that not every request produces a trace. By default, Aspire's service defaults configure 100% sampling for development. In production, check your sampling configuration. + +### Transport protocol mismatch + +The Aspire dashboard supports both gRPC OTLP (port 18889) and HTTP OTLP (port 18890). Most cloud-hosted OTLP endpoints require HTTP. Verify that the protocol in `OTEL_EXPORTER_OTLP_PROTOCOL` matches the endpoint: + +- Use `grpc` for the Aspire dashboard's gRPC OTLP endpoint. +- Use `http/protobuf` for HTTP OTLP endpoints (required for browser apps and many cloud services). + +## Dashboard access after deployment + +When you deploy an Aspire app to Azure Container Apps using `aspire deploy`, the Aspire dashboard is included as a container app in your deployment. This gives you a familiar UI for viewing telemetry from your deployed app. + + + +### Find the dashboard URL + +After a successful `aspire deploy`, the deployment output includes URLs for your deployed resources. Look for a Container App named `aspire-dashboard` or similar in the output. The exact format varies depending on the environment name you configured. + +You can also find the URL in the [Azure Portal](https://portal.azure.com) by navigating to your resource group, locating the Container App for the Aspire dashboard, and finding its **Application URL** on the **Overview** page. + +Alternatively, use the Azure CLI: + +```bash title="Azure CLI — Get dashboard URL" +az containerapp show \ + --name aspire-dashboard \ + --resource-group my-resource-group \ + --query properties.configuration.ingress.fqdn \ + --output tsv +``` + +### Authenticate with the login token + +The deployed dashboard requires a login token, just like the standalone dashboard. The token is displayed in the dashboard container's logs. + + + +1. In the Azure Portal, navigate to your `aspire-dashboard` Container App. + +2. Select **Monitoring** → **Log stream** from the left navigation. + +3. Look for a log line containing `login?t=`, for example: + + ```plaintext data-disable-copy + Login to the dashboard at https://aspire-dashboard.example.com/login?t=abc123... + ``` + +4. Copy the token value (the part after `t=`) and use it to log in at the dashboard URL. + + + +### Configure a fixed login token + +By default, the dashboard generates a new token each time it starts. To set a fixed token, configure the `Dashboard:Frontend:BrowserToken` setting on the dashboard container app after deployment using the Azure CLI: + +```azurecli title="Azure CLI — Set dashboard token" +az containerapp update \ + --name aspire-dashboard \ + --resource-group my-resource-group \ + --set-env-vars "DASHBOARD__FRONTEND__BROWSERTOKEN=my-secret-token" +``` + + + +### Use OpenID Connect authentication + +For team access to the deployed dashboard, configure OpenID Connect (OIDC) authentication instead of browser token authentication. This allows multiple users to log in with their organizational identity provider. + + +For more information on configuring OIDC, see [Aspire dashboard configuration: Frontend](/dashboard/configuration/#frontend). + + +## See also + +- [Aspire dashboard configuration](/dashboard/configuration/) +- [Aspire dashboard security considerations](/dashboard/security-considerations/) +- [Enable browser telemetry](/dashboard/enable-browser-telemetry/) +- [Deploy using the Aspire CLI](/deployment/azure/aca-deployment-aspire-cli/) +- [Azure Monitor OpenTelemetry documentation](https://learn.microsoft.com/azure/azure-monitor/app/opentelemetry-enable)