OneUptime 前端浏览器 RUM 接入指南使用 OpenTelemetry Browser SDK 采集真实用户监控数据【免费下载链接】oneuptimeComplete open-source monitoring and observability platform.项目地址: https://gitcode.com/GitHub_Trending/on/oneuptime导读本文是 OneUptime 浏览器端真实用户监控Real User MonitoringRUM的完整接入手册。你将学会如何创建 Telemetry Ingestion Token 并把 OpenTelemetry 浏览器 SDK 接入你的 Web 应用让页面加载、路由切换、fetch/XHR 请求与未捕获异常以 trace 形式上报到 OneUptime如何正确设置browser.*资源属性以保证遥测被归类为浏览器 RUM 而非后端 Service以及如何打通 Session Replay、后端链路追踪、CSP 与自托管环境。读完本文你可以直接复刻出一套生产可用的src/telemetry.ts接入代码。本文主体依据仓库文档 browser-setup.md 编写并辅以 OneUptime 遥测摄入侧源码OTelIngest.ts、TelemetryIngest.ts、OtelRequestMiddleware.ts进行原理性佐证。前置条件创建 Telemetry Ingestion Token浏览器接入的第一步是获取一个Telemetry Ingestion Token遥测摄入令牌登录 OneUptime 控制台进入Project Settings → Telemetry APM → Ingestion Keys点击Create Ingestion Key创建新的摄入密钥在密钥列表中点击View查看令牌内容。令牌的公开性与安全边界该令牌会被直接嵌入页面 JavaScript 中因此必须把它当作公开信息对待。它只有写入ingestion能力无法从你的项目中读取任何数据——这是它能够安全放在公网页面的前提。从源码看这套约束在服务端被严格落地。OneUptime 将摄入密钥分为两类见 TelemetryIngestionKeyType.tsServer 密钥部署在后端进程、OTel Collector 配置或 CI 密钥中不会被项目外部看到拥有完整摄入面、无来源origin绑定是历史默认行为Browser 密钥被粘贴进公开页面任何查看网页源码的人都能拿到因此被当作公开的只写凭证服务端会强制约束其来源白名单origin allowlist、固定的service.name、按密钥的速率限制、过期时间、关闭开关kill switch以及收窄后的摄入面BROWSER_ALLOWED_INGEST_SURFACES。在摄入鉴权中间件 TelemetryIngest.ts 中可以看到浏览器密钥请求会经过一连串校验缺失/无效令牌返回 401OTLP 规范将其视为不可重试避免客户端重试风暴、被禁用返回 403、过期返回 401、不在允许的摄入面或来源白名单内返回 403、超限返回 429 并附带Retry-After头。其中一个值得注意的设计浏览器密钥的空来源白名单会被直接拒绝fail closed因为空白名单若被解读为任意来源就等于发放了无限制的公开写密钥。如果你同时启用了 Session Replay务必在其来源白名单中配置允许的来源防止被复制的令牌被用来向你的项目写入录制内容。安装 OpenTelemetry 浏览器 SDK 依赖在 Web 应用项目中安装以下 npm 包npm install opentelemetry/api \ opentelemetry/sdk-trace-web \ opentelemetry/resources \ opentelemetry/semantic-conventions \ opentelemetry/opentelemetry-browser-detector \ opentelemetry/exporter-trace-otlp-http \ opentelemetry/context-zone \ opentelemetry/instrumentation \ opentelemetry/instrumentation-document-load \ opentelemetry/instrumentation-fetch \ opentelemetry/instrumentation-xml-http-request各包职责一览包用途opentelemetry/apiOpenTelemetry 公共 APItrace、metrics、SpanStatusCode等opentelemetry/sdk-trace-webWeb 端 TracerProvider 与BatchSpanProcessoropentelemetry/resources资源resource构建与合并defaultResource、detectResources等opentelemetry/semantic-conventions语义约定常量如ATTR_SERVICE_NAMEopentelemetry/opentelemetry-browser-detector浏览器资源探测器产出browser.*属性opentelemetry/exporter-trace-otlp-httpOTLP/HTTP trace 导出器opentelemetry/context-zoneZone.js 上下文管理器用于异步传播 trace 上下文opentelemetry/instrumentation插桩注册工具registerInstrumentationsopentelemetry/instrumentation-document-load文档加载插桩追踪首次页面加载opentelemetry/instrumentation-fetch追踪fetch请求opentelemetry/instrumentation-xml-http-request追踪XMLHttpRequest请求核心配置创建src/telemetry.ts创建src/telemetry.ts并在任何其他代码之前于入口文件导入它——插桩会 patchfetch与XMLHttpRequest任何在 patch 之前运行的代码都不会被追踪到。// src/telemetry.ts import { WebTracerProvider, BatchSpanProcessor, } from opentelemetry/sdk-trace-web; import { OTLPTraceExporter } from opentelemetry/exporter-trace-otlp-http; import { ZoneContextManager } from opentelemetry/context-zone; import { registerInstrumentations } from opentelemetry/instrumentation; import { DocumentLoadInstrumentation } from opentelemetry/instrumentation-document-load; import { FetchInstrumentation } from opentelemetry/instrumentation-fetch; import { XMLHttpRequestInstrumentation } from opentelemetry/instrumentation-xml-http-request; import { defaultResource, detectResources, resourceFromAttributes, } from opentelemetry/resources; import { browserDetector } from opentelemetry/opentelemetry-browser-detector; import { ATTR_SERVICE_NAME } from opentelemetry/semantic-conventions; const ONEUPTIME_URL https://oneuptime.com; const ONEUPTIME_TOKEN YOUR_TELEMETRY_INGESTION_TOKEN; /* * browserDetector 提供 browser.* 属性。缺少它们这批遥测会被归类为 * 后端 Service 而不是 RUM 应用。属性后写覆盖因此这里的 service.name * 会覆盖 defaultResource() 提供的 unknown_service。 */ const resource defaultResource() .merge(detectResources({ detectors: [browserDetector] })) .merge( resourceFromAttributes({ [ATTR_SERVICE_NAME]: storefront-web, }), ); const provider new WebTracerProvider({ resource: resource, spanProcessors: [ new BatchSpanProcessor( new OTLPTraceExporter({ url: ${ONEUPTIME_URL}/otlp/v1/traces, headers: { x-oneuptime-token: ONEUPTIME_TOKEN }, }), ), ], }); provider.register({ contextManager: new ZoneContextManager(), }); registerInstrumentations({ instrumentations: [ new DocumentLoadInstrumentation(), new FetchInstrumentation({ // 见下文 关联后端链路追踪 一节再决定是否放宽此配置 propagateTraceHeaderCorsUrls: [/^https:\/\/api\.example\.com/], }), new XMLHttpRequestInstrumentation({ propagateTraceHeaderCorsUrls: [/^https:\/\/api\.example\.com/], }), ], });然后在应用入口的第一个 import处引入// src/index.tsx (React)、src/main.ts (Vue / Angular) 等 import ./telemetry; import React from react; // ... 其余应用代码为什么browser.*资源属性决定成败browserDetector补充的browser.*属性并非锦上添花而是归类依据。从 RUM 文档 index.md 可确认OneUptime 在摄入时按每个遥测批次batch的资源属性进行分类资源携带browser.platform、browser.language或非空browser.brands中的任意一个 → 归类为浏览器 RUM否则若携带device.id、device.model.identifier或device.manufacturer→ 归类为移动端 RUM否则不算 RUM被当作后端 Service处理。因此只设置service.name而不带任何browser.*属性会产生一个完全正常的后端 Service而不是 RUM 应用——这是官方文档中明确标注的最常见接入错误。应用在Resources → Real User Monitoring下按service.name命名大小写不敏感收到首个批次遥测后自动创建无需手工建应用。加载页面后通常一分钟内即可在Resources → Real User Monitoring下看到以service.name命名的应用出现。文档 index.md 还列出telemetry.sdk.language、telemetry.sdk.version等可选属性以及可提升为项目标签的oneuptime.label.name属性。不使用探测器时手动设置browser.*browserDetector读取 UA Client Hints API该 API 仅 Chromium 内核支持因此 Safari 和 Firefox 上探测器只能设置browser.language和user_agent.original无法设置browser.platform、browser.brands和browser.mobile。这仍然足以完成归类——仅browser.language就能把批次标记为浏览器 RUM——但Clients 标签页是由browser.platform填充的Safari 与 Firefox 的流量若不自行补充该属性将不会产生客户端记录行。如果不想引入探测器依赖或希望所有浏览器都有平台值可直接设置属性const resource defaultResource().merge( resourceFromAttributes({ [ATTR_SERVICE_NAME]: storefront-web, // 以下三者中的任意一个即可把遥测标记为浏览器 RUM browser.language: navigator.language, browser.platform: (navigator as any).userAgentData?.platform ?? unknown, browser.mobile: (navigator as any).userAgentData?.mobile ?? false, user_agent.original: navigator.userAgent, }), );务必保持browser.platform为粗粒度值。它是资源属性每个不同的取值都会在 Clients 标签页上生成一行——把完整 UA 字符串或按用户粒度的值放进去会产生无界列表同时也是隐私问题。单页应用SPA的路由追踪DocumentLoadInstrumentation只追踪首次加载。SPA 中后续的路由切换是不可见的除非自行发起 span通常只需在路由器中加几行代码import { trace } from opentelemetry/api; const tracer trace.getTracer(app-router); function onRouteChange(to: string): void { const span tracer.startSpan(route-change, { attributes: { app.route: to }, }); // 在路由数据加载完成、视图绘制完成后结束 span requestAnimationFrame(() { return span.end(); }); }如果还希望追踪点击等 DOM 事件可额外添加opentelemetry/instrumentation-user-interaction插桩。关联后端链路追踪propagateTraceHeaderCorsUrlspropagateTraceHeaderCorsUrls决定哪些跨域请求会附加 W3Ctraceparent头从而把浏览器 span 与它所触发的后端链路trace关联起来。不要把该项设为/.*/。添加请求头会把简单跨域请求升级为需要预检preflight的请求任何没有在Access-Control-Allow-Headers中列出traceparent的第三方 API 都会开始失败——仅仅因为你安装了插桩。只列出你拥有并已配置好的来源new FetchInstrumentation({ propagateTraceHeaderCorsUrls: [ /^https:\/\/api\.example\.com/, /^https:\/\/auth\.example\.com/, ], });同源请求无需任何配置即可自动传播。与 Session Replay 关联如果你同时运行 Session Replay录制与浏览器遥测通过资源上的session.id属性关联。录制器通过onSessionChange告知会话 id——若会话已存在会立即触发之后在每次 id 轮换时再次触发空闲 30 分钟后、4 小时上限到达时或同一访客的另一个标签页率先轮换时——所以让属性跟随它declare global { interface Window { OneUptimeReplay?: { onSessionChange: ( listener: (sessionId: string, tabId: string) void, ) () void; }; OneUptimeReplayQueue?: ArrayArrayunknown; } } const onSessionChange (sessionId: string, tabId: string): void { resource.attributes[session.id] sessionId; resource.attributes[session.tab.id] tabId; }; // 录制器脚本是异步加载的若尚未就绪则把监听器入队 // 录制器启动的那一刻即被应用。 if (window.OneUptimeReplay) { window.OneUptimeReplay.onSessionChange(onSessionChange); } else { (window.OneUptimeReplayQueue window.OneUptimeReplayQueue || []).push([ onSessionChange, onSessionChange, ]); }此后导出的 span 与日志都会携带该 id回放播放器的Logs与Traces标签页会按录制时钟列出它们仪表盘中的每条日志与 span 也都能链接回回放中的对应时刻。把 id 转发给你的后端以 baggage 或自定义请求头形式让 API 把它读到请求 span 上每个请求的后端一侧也能关联起来。录制器会读取FetchInstrumentation/XMLHttpRequestInstrumentation设置的traceparent头包括Request对象上的因此回放中的请求行无需额外配置即可链接到后端链路。注意应用回放策略中的Trace propagation origins仅针对未接入本 SDK的页面。Content Security Policy 配置如果你的站点启用了 CSP导出器的请求会被拦截直到你放行 OneUptime。这种失败在你自己机器上看不到任何报错——页面只是什么都不上报。connect-src self https://oneuptime.com;自托管部署时将https://oneuptime.com替换为你自己的主机名。错误与异常上报OpenTelemetry 浏览器 SDK不会自动捕获未捕获异常。需要显式上报使其并入Exceptions视图、与后端错误并列import { trace, SpanStatusCode } from opentelemetry/api; const tracer trace.getTracer(app-errors); window.addEventListener(error, (event: ErrorEvent) { const span tracer.startSpan(window.onerror); span.recordException(event.error ?? new Error(event.message)); span.setStatus({ code: SpanStatusCode.ERROR }); span.end(); }); window.addEventListener(unhandledrejection, (event: PromiseRejectionEvent) { const span tracer.startSpan(unhandledrejection); span.recordException( event.reason instanceof Error ? event.reason : new Error(String(event.reason)), ); span.setStatus({ code: SpanStatusCode.ERROR }); span.end(); });如果使用了 Session Replay其录制器会自行捕获未捕获错误并在回放时间线上标记异常页面随后会提供Watch what the user saw卡片点击可打开错误发生前 10 秒的回放。在默认策略下会话无论是否发生异常都会被录制异常只决定卡片指向的位置。可选浏览器日志与指标含 Core Web Vitalstrace 已足以填充概览。若希望浏览器日志可在 OneUptime 中检索或希望接入 Core Web Vitals可继续添加日志与指标管线npm install opentelemetry/api-logs opentelemetry/sdk-logs \ opentelemetry/exporter-logs-otlp-http \ opentelemetry/sdk-metrics opentelemetry/exporter-metrics-otlp-httpimport { LoggerProvider, BatchLogRecordProcessor } from opentelemetry/sdk-logs; import { OTLPLogExporter } from opentelemetry/exporter-logs-otlp-http; import { MeterProvider, PeriodicExportingMetricReader } from opentelemetry/sdk-metrics; import { OTLPMetricExporter } from opentelemetry/exporter-metrics-otlp-http; import { logs } from opentelemetry/api-logs; import { metrics } from opentelemetry/api; const headers { x-oneuptime-token: ONEUPTIME_TOKEN }; logs.setGlobalLoggerProvider( new LoggerProvider({ resource: resource, processors: [ new BatchLogRecordProcessor({ exporter: new OTLPLogExporter({ url: ${ONEUPTIME_URL}/otlp/v1/logs, headers, }), }), ], }), ); metrics.setGlobalMeterProvider( new MeterProvider({ resource: resource, readers: [ new PeriodicExportingMetricReader({ exporter: new OTLPMetricExporter({ url: ${ONEUPTIME_URL}/otlp/v1/metrics, headers, }), exportIntervalMillis: 30000, }), ], }), );务必复用同一个resource对象。用缺少browser.*的资源构建日志或指标管线会被归类为独立的后端 Service。自托管 OneUptime自托管部署时只需把上文所有https://oneuptime.com替换为你自己的主机名——包括 OTLP URL 与 CSP 条目。其他一切不变。端点参考SignalEndpointHeaderTracesPOST {host}/otlp/v1/tracesx-oneuptime-token: tokenMetricsPOST {host}/otlp/v1/metricsx-oneuptime-token: tokenLogsPOST {host}/otlp/v1/logsx-oneuptime-token: token这些端点同时接受OTLP/JSON 与 OTLP/protobuf两种格式并允许来自任意来源的跨域请求且放行x-oneuptime-token请求头因此浏览器可以直接向其导出无需自建 Collector 或代理。服务端摄入链路印证上述端点与鉴权行为都能在仓库源码中找到对应实现。在 OTelIngest.ts 中/otlp/v1/traces、/otlp/v1/metrics、/otlp/v1/logs、/otlp/v1/profiles四条路由依次经过TelemetryIngestionDisabled全局开关→parseBody原始字节读取与 50 MiB 上限检查见 OtelRequestMiddleware.ts→ 信号标记的摄入指标中间件 →getProductType按 URL 识别信号类型与 protobuf/JSON 编码→TelemetryIngest.forSurface(...)浏览器/服务端密钥鉴权、来源白名单、速率限制→ 对应的Otel*IngestService完成入库。其中parseBody刻意不做 gzip 解压与 protobuf 解码——解压与解码被移到 BullMQ worker 中执行避免阻塞 Express 事件循环。另外OTelIngest.ts 还提供了GET /otlp/v1/validate校验端点携带x-oneuptime-token或x-oneuptime-service-token、x-oneuptime-ingestion-key访问即可确认令牌是否被接受响应 200{ valid: true, projectId, keyType, isEnabled, isExpired }或 401{ valid: false, ... }。它不执行任何摄入、不写入数据且令牌只从头读取绝不放入查询串可用来快速排查为什么我的数据没到。小结归类靠资源属性browser.*或移动端device.*是 RUM 归类的硬性依据只设service.name会被当作后端 Service令牌公开但受限Browser 密钥是公开只写凭证服务端以来源白名单、固定service.name、速率限制、过期与 kill switch 兜底导入顺序第一telemetry.ts必须是入口文件的第一个 import插桩才覆盖到所有fetch/XHR链路关联三件套propagateTraceHeaderCorsUrls接后端 trace、资源session.id接回放、traceparent让回放请求行反链后端链路安全注意CSP 需放行connect-srcpropagateTraceHeaderCorsUrls切勿写成/.*/。更多细节可继续阅读同目录下的 RUM 应用管理、Core Web Vitals 与 RUM 故障排查 文档。【免费下载链接】oneuptimeComplete open-source monitoring and observability platform.项目地址: https://gitcode.com/GitHub_Trending/on/oneuptime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
