启用浏览器遥测

可以将 .NET.NET Aspire 仪表板配置为接收从浏览器应用发送的遥测数据。 此功能可用于监视客户端性能和用户交互。 浏览器遥测需要额外的仪表板配置,并且需要将 JavaScript OTEL SDK 添加到浏览器应用中。

本文讨论如何在 .NET.NET Aspire 仪表板中启用浏览器遥测。

仪表板配置

浏览器遥测需要仪表板才能启用这些功能:

  • OTLP HTTP 终结点。 仪表板使用此终结点从浏览器应用接收遥测数据。
  • 跨域资源共享 (CORS)。 CORS 允许浏览器应用向仪表板发出请求。

OTLP 配置

.NET .NET Aspire 仪表板通过 OTLP 端点接收遥测数据。 仪表板支持 HTTP OTLP 终结点 和 gRPC OTLP 终结点。 浏览器应用必须使用 HTTP OLTP 将遥测数据发送到仪表板,因为浏览器应用不支持 gRPC。

若要配置 gPRC 或 HTTP 终结点,请指定以下环境变量:

  • DOTNET_DASHBOARD_OTLP_ENDPOINT_URL:仪表板连接到其数据的 gRPC 终结点。
  • DOTNET_DASHBOARD_OTLP_HTTP_ENDPOINT_URL:仪表板用于连接其数据的 HTTP 终结点。

HTTP OTLP 终结点的配置取决于仪表板是由应用主机启动还是独立运行。

使用应用主机配置 OTLP HTTP

如果仪表板和应用由应用主机启动,则仪表板 OTLP 终结点在应用主机的 launchSettings.json 文件中配置。

请考虑以下示例 JSON 文件:

{
  "$schema": "http://json.schemastore.org/launchsettings.json",
  "profiles": {
    "https": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "https://localhost:15887;http://localhost:15888",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development",
        "DOTNET_ENVIRONMENT": "Development",
        "DOTNET_DASHBOARD_OTLP_HTTP_ENDPOINT_URL": "https://localhost:16175",
        "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:17037",
        "DOTNET_ASPIRE_SHOW_DASHBOARD_RESOURCES": "true"
      }
    },
    "http": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "http://localhost:15888",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development",
        "DOTNET_ENVIRONMENT": "Development",
        "DOTNET_DASHBOARD_OTLP_HTTP_ENDPOINT_URL": "http://localhost:16175",
        "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:17037",
        "DOTNET_ASPIRE_SHOW_DASHBOARD_RESOURCES": "true",
        "ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true"
      }
    },
    "generate-manifest": {
      "commandName": "Project",
      "launchBrowser": true,
      "dotnetRunMessages": true,
      "commandLineArgs": "--publisher manifest --output-path aspire-manifest.json",
      "applicationUrl": "http://localhost:15888",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development",
        "DOTNET_ENVIRONMENT": "Development"
      }
    }
  }
}

上述启动设置 JSON 文件将所有配置文件配置为包括 DOTNET_DASHBOARD_OTLP_HTTP_ENDPOINT_URL 环境变量。

使用独立仪表板配置 OTLP HTTP

如果仪表板是独立使用的,而不使用其余 .NET.NET Aspire,则默认情况下在端口 18890上启用 OTLP HTTP 终结点。 但是,在启动仪表板容器时,必须映射端口:

docker run --rm -it -d \
    -p 18888:18888 \
    -p 4317:18889 \
    -p 4318:18890 \
    --name aspire-dashboard \
    mcr.microsoft.com/dotnet/aspire-dashboard:9.0

上述命令运行仪表板容器并将 gRPC OTLP 映射到端口 4317,将 HTTP OTLP 映射到端口 4318

CORS 配置

默认情况下,浏览器应用被限制为进行跨域 API 调用。 这会影响将遥测数据发送到仪表板,因为仪表板和浏览器应用始终位于不同的域中。 在 .NET.NET Aspire 仪表板中配置 CORS 会消除限制。

如果仪表板和应用由应用主机启动,则无需 CORS 配置。 .NET .NET Aspire 自动配置仪表板以允许所有资源的来源。

如果独立使用仪表板,则必须手动配置 CORS。 用于查看浏览器应用的域必须配置为允许的源,方法是在启动仪表板容器时指定 DASHBOARD__OTLP__CORS__ALLOWEDORIGINS 环境变量:

docker run --rm -it -d \
    -p 18888:18888 \
    -p 4317:18889 \
    -p 4318:18890 \
    -e DASHBOARD__OTLP__CORS__ALLOWEDORIGINS=https://localhost:8080 \
    --name aspire-dashboard \
    mcr.microsoft.com/dotnet/aspire-dashboard:9.0

上述命令运行仪表板容器,并将 https://localhost:8080 配置为允许的源。 这意味着使用 https://localhost:8080 访问的浏览器应用有权发送仪表板遥测数据。

可以使用逗号分隔值允许多个源。 或者,可以使用 * 通配符允许所有源。 例如,DASHBOARD__OTLP__CORS__ALLOWEDORIGINS=*

有关详细信息,请参阅 .NET.NET Aspire 仪表板配置:OTLP CORS

OTLP 终结点安全性

仪表板 OTLP 终结点可以使用 API 密钥身份验证进行保护。 启用后,向仪表板发出的 HTTP OTLP 请求必须包含 API 密钥作为 x-otlp-api-key 标头。 默认情况下,每次运行仪表板时都会生成新的 API 密钥。

从应用主机运行仪表板时,会自动启用 API 密钥身份验证。 可以通过在应用主机的 launchSettings.json 文件中将 DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS 设置为 true 来禁用仪表板身份验证。

OTLP 端点在独立仪表板中默认是不安全的。

浏览器应用配置

浏览器应用使用 JavaScript OTEL SDK 将遥测数据发送到仪表板。 成功将遥测数据发送到仪表板需要正确配置 SDK。

OTLP 导出器

OTLP 导出程序必须包含在浏览器应用中,并使用 SDK 进行配置。 例如,使用 OTLP 导出分布式跟踪时,可以使用 @opentelemetry/exporter-trace-otlp-proto 包。

将 OTLP 添加到 SDK 时,必须指定 OTLP 选项。 OTLP 选项包括:

  • url:HTTP OTLP 请求的地址。 该地址应是仪表板 HTTP OTLP 终结点和 OTLP HTTP API 的路径。 例如,https://localhost:4318/v1/traces 是用于跟踪的 OTLP 导出程序。 如果浏览器应用由应用主机启动,则可从 OTEL_EXPORTER_OTLP_ENDPOINT 环境变量获取 HTTP OTLP 终结点。

  • headers:随请求一起发送的标头。 如果启用了 OTLP 终结点 API 密钥身份验证,则必须在 OTLP 请求中发送 x-otlp-api-key 标头。 如果浏览器应用由应用主机启动,则 API 密钥可从 OTEL_EXPORTER_OTLP_HEADERS 环境变量获取。

浏览器元数据

当浏览器应用配置为收集分布式追踪时,浏览器应用可以通过 HTML 中的 meta 元素设置浏览器跨度的追踪父节点。 name="traceparent" 元数据元素的值应对应于当前跟踪。

例如,在 .NET 应用中,跟踪父值可能从 Activity.Current 分配,并将其 Activity.Id 值作为 content传递。 例如,请考虑以下 Razor 代码:

<head>
    @if (Activity.Current is { } currentActivity)
    {
        <meta name="traceparent" content="@currentActivity.Id" />
    }
    <!-- Other elements omitted for brevity... -->
</head>

前面的代码将 traceparent 元元素设置为当前活动 ID。

浏览器遥测代码示例

以下 JavaScript 代码演示了 OpenTelemetry JavaScript SDK 的初始化,并将遥测数据发送到仪表板:

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 { Resource } 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: new Resource(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;
}

前面的 JavaScript 代码定义一个 initializeTelemetry 函数,该函数需要 OTLP 终结点 URL、标头和资源属性。 这些参数由使用的浏览器应用提供,该应用从应用主机设定的环境变量中提取这些参数。 请考虑以下 Razor 代码:

@using System.Diagnostics
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - BrowserTelemetry</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />

    @if (Activity.Current is { } currentActivity)
    {
        <meta name="traceparent" content="@currentActivity.Id" />
    }
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">BrowserTelemetry</a>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>
    @await RenderSectionAsync("Scripts", required: false)
    <script src="scripts/bundle.js"></script>
    @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");
        <script>
            BrowserTelemetry.initializeTelemetry('@endpointUrl', '@headers', '@attributes');
        </script>
    }
</body>
</html>

提示

JavaScript 代码的捆绑和缩小超出了本文的范围。

有关如何将 JavaScript OTEL SDK 配置为将遥测数据发送到仪表板的完整工作示例,请参阅 浏览器遥测示例

另请参阅