可观测性后端【免费下载链接】highlighthighlight.io: The open source, full-stack monitoring platform. Error monitoring, session replay, logging, distributed tracing, and more.项目地址https://gitcode.com/gh_mirrors/hi/highlight点击查看免费下载本文基于 highlight 开源仓库中的 React Native 快速上手文档及其配套示例应用完整讲解如何在 React NativeExpo应用中使用 OpenTelemetry 标准 API 接入 highlight.io 的 Errors、Logs 与 Traces 三大产品。读完本文你可以独立完成OpenTelemetry 依赖安装、针对 Metro 打包器兼容问题的自定义 OTLP Exporter 实现、Tracer/Resource 配置、日志与错误上报封装、console 全局钩子monkey patch并使用仓库内的官方示例应用验证上报链路。文档定位一个通过 OpenTelemetry 覆盖三种遥测的 QuickStart官方文档入口是 React Native 快速上手页该页面本身是一个 MDX 壳仅渲染quickStartContent[client][js][react-native]指向的快捷步骤。其真实内容定义在 react-native.tsx元信息声明如下标题React Native (beta)——当前方案处于 beta 阶段产品覆盖[Errors, Logs, Traces]副标题核心主张使用 OpenTelemetry 在 React Native 应用中配置 highlight.io 的 errors、logs 和 traces。也就是说highlight 目前还没有独立的 React Native SDK官方推荐路线是直接采用 OpenTelemetry 的 trace API 产生 Span通过一个自定义 Exporter 以 OTLP/HTTP JSON 格式 POST 到 highlight 的 OTLP 端点https://otel.highlight.io:4318/v1/traces由 highlight 后端把 Span 解析为日志、错误和追踪。日志和错误也复用 trace 通道发送靠约定的 Span 名称与属性区分类型。仓库中 e2e/react-native 目录下的 Expo 示例应用是该方案的完整可运行实现本文所有代码均以 e2e/react-native/app/highlight.ts 的真实实现为准。第一步安装 OpenTelemetry npm 包在终端中安装以下四个opentelemetry包文档提供 npm/yarn/pnpm 三种方式# with npm npm install opentelemetry/api opentelemetry/core opentelemetry/resources opentelemetry/sdk-trace-base# with yarn yarn add opentelemetry/api opentelemetry/core opentelemetry/resources opentelemetry/sdk-trace-base# with pnpm pnpm add opentelemetry/api opentelemetry/core opentelemetry/resources opentelemetry/sdk-trace-base官方示例应用 e2e/react-native/package.json 中的版本基线可供参考opentelemetry/api:^1.9.0opentelemetry/core:^1.30.0opentelemetry/resources:^1.30.0opentelemetry/sdk-trace-base:^1.30.0应用侧react-native 0.76.6、expo ~52.0.23、react 18.3.1注意方案刻意没有安装opentelemetry/exporter-trace-otlp-http这类标准导出器原因是 React Native 的 Metro 打包器与部分 OpenTelemetry 包存在浏览器兼容性问题官方选择用自定义 Exporter 绕过详见下文。第二步自定义 OTLP Exporter解决 Metro 打包器兼容问题这是整个接入方案中最关键的一步。文档原文说明部分 OpenTelemetry 包无法与 React Native 的 Metro bundler 一起使用存在浏览器兼容性问题。作为变通方案团队编写了一个自定义 Exporter 负责序列化数据。基于 bundler 的解决方案即使用官方 OTLPTraceExporter也在推进中。自定义 Exporter 需要实现SpanExporter接口来自opentelemetry/sdk-trace-base完整实现见 react-native.tsx 中的 exporter 代码块示例应用中的实际版本为 e2e/react-native/app/highlight.ts#L17-L164import { BatchSpanProcessor, BasicTracerProvider, SpanExporter, ReadableSpan, TimedEvent } from opentelemetry/sdk-trace-base; import type { Link, Attributes } from opentelemetry/api; import { ExportResultCode } from opentelemetry/core; import { Resource } from opentelemetry/resources; type KeyValue { key: string; value: KeyValue }; class ReactNativeOTLPTraceExporter implements SpanExporter { url: string; constructor(options: { url: string; }) { this.url options.url; this._buildResourceSpans this._buildResourceSpans.bind(this); this._convertEvent this._convertEvent.bind(this); this._convertToOTLPFormat this._convertToOTLPFormat.bind(this); this._convertLink this._convertLink.bind(this); this._convertAttributes this._convertAttributes.bind(this); this._convertKeyValue this._convertKeyValue.bind(this); this._toAnyValue this._toAnyValue.bind(this); } export(spans: ReadableSpan[], resultCallback: any) { fetch(this.url, { method: POST, headers: { Content-Type: application/json }, body: this._buildResourceSpans(spans), }) .then((resp) { resultCallback({ code: ExportResultCode.SUCCESS }); }) .catch((err) { resultCallback({ code: ExportResultCode.FAILED, error: err }); }); } shutdown() { return Promise.resolve(); } _buildResourceSpans(spans: ReadableSpan[] []) { const resource spans[0]?.resource; const scope spans[0]?.instrumentationLibrary; return JSON.stringify({ resourceSpans: [ { resource: { attributes: resource.attributes ? this._convertAttributes(resource.attributes) : [], }, scopeSpans: [ { scope: { name: scope?.name, version: scope?.version }, spans: spans.map(this._convertToOTLPFormat), }, ], }, ], }); } _convertToOTLPFormat(span: ReadableSpan) { const spanContext span.spanContext(); const status span.status; return { traceId: spanContext.traceId, spanId: spanContext.spanId, parentSpanId: span.parentSpanId, traceState: spanContext.traceState?.serialize(), name: span.name, // Span kind is offset by 1 because the API does not define a value for unset kind: span.kind null ? 0 : span.kind 1, startTimeUnixNano: span.startTime[0] * 1e9 span.startTime[1], endTimeUnixNano: span.endTime[0] * 1e9 span.endTime[1], attributes: span.attributes ? this._convertAttributes(span.attributes) : [], droppedAttributesCount: span.droppedAttributesCount || 0, events: span.events?.map(this._convertEvent) || [], droppedEventsCount: span.droppedEventsCount || 0, status: { code: status.code, message: status.message, }, links: span.links?.map(this._convertLink) || [], droppedLinksCount: span.droppedLinksCount, }; } _convertEvent(timedEvent: TimedEvent) { return { attributes: timedEvent.attributes ? this._convertAttributes(timedEvent.attributes) : [], name: timedEvent.name, timeUnixNano: timedEvent.time[0] * 1e9 timedEvent.time[1], droppedAttributesCount: timedEvent.droppedAttributesCount || 0, }; } _convertLink(link: Link) { return { attributes: link.attributes ? this._convertAttributes(link.attributes) : [], spanId: link.context.spanId, traceId: link.context.traceId, traceState: link.context.traceState?.serialize(), droppedAttributesCount: link.droppedAttributesCount || 0, }; } _convertAttributes(attributes: Attributes) { return Object.keys(attributes).map(key this._convertKeyValue(key, attributes[key])); } _convertKeyValue(key: string, value: any): KeyValue { return { key: key, value: this._toAnyValue(value), }; } _toAnyValue(value: any): any { const t typeof value; if (t string) return { stringValue: value as string }; if (t number) { if (!Number.isInteger(value)) return { doubleValue: value as number }; return { intValue: value as number }; } if (t boolean) return { boolValue: value as boolean }; if (value instanceof Uint8Array) return { bytesValue: value }; if (Array.isArray(value)) return { arrayValue: { values: value.map(this._toAnyValue) } }; if (t object value ! null) return { kvlistValue: { values: Object.entries(value as object).map(([k, v]) this._convertKeyValue(k, v) ), }, }; return {}; } }从源码结构看该实现手动完成了标准 OTLP 导出器的工作几个细节值得注意export()方法用fetch将整批 Span 序列化为 JSON 后 POST 到目标 URL成功回调ExportResultCode.SUCCESS、失败回调FAILED并附带错误对象。它完全依赖 RN 运行时的fetch这正是选择自定义实现以绕开 Metro 兼容性问题的原因。_buildResourceSpans()按 OTLP/JSON 结构组装{ resourceSpans: [{ resource, scopeSpans }] }载荷Resource 属性取自批次内第一个 Spaninstrumentation scope 同样取自第一个 Span 的instrumentationLibrary。_convertToOTLPFormat()完成 Span 到 OTLP 字段的映射其中两处协议细节容易被忽略——kind因为 API 未定义“未设置”的枚举值需要偏移 1span.kind null ? 0 : span.kind 1startTime/endTime在 SDK 中是[秒, 纳秒余数]二元组需要转换为startTime[0] * 1e9 startTime[1]的纳秒时间戳。_toAnyValue()把任意 JS 值映射为 OTLP 的 AnyValuestringValue/intValue/doubleValue/boolValue/bytesValue/arrayValue/kvlistValue这是属性能无损到达后端的保证。第三步创建 Tracer 并配置 Resource创建 Exporter 实例后组装Resource、BasicTracerProvider与BatchSpanProcessor并注册全局 Tracer。文档给出的代码块为// create tracer with resource const resource new Resource({ highlight.project_id: YOUR_PROJECT_ID, // add more resource attributes here for every trace/log/error service.name: reactnativeapp // see more in opentelemetry/semantic-conventions }); const tracerProvider new BasicTracerProvider({resource}) const otlpExporter new ReactNativeOTLPTraceExporter({ url: https://otel.highlight.io:4318/v1/traces }); tracerProvider.addSpanProcessor(new BatchSpanProcessor(otlpExporter)); tracerProvider.register(); export const tracer tracerProvider.getTracer(react-native-tracer);示例应用中的真实写法见 highlight.ts#L166-L177与上面一致仅project_id换成了演示值const resource new Resource({ highlight.project_id: 1261, service.name: reactnativeapp, }) const tracerProvider new BasicTracerProvider({ resource }) const otlpExporter new ReactNativeOTLPTraceExporter({ url: https://otel.highlight.io:4318/v1/traces, }) tracerProvider.addSpanProcessor(new BatchSpanProcessor(otlpExporter)) tracerProvider.register() export const tracer tracerProvider.getTracer(react-native-tracer)各要素的作用要素作用说明Resource挂载到每一个trace/log/error 上的公共属性必须包含highlight.project_id替换为你在 highlight 中的项目 ID演示值1261不可直接使用service.name用于在服务维度识别应用还可按 semantic conventions 追加environment等属性BasicTracerProvider({resource})OpenTelemetry 的 Provider 入口持有全部遥测配置负责创建与管理 SpanReactNativeOTLPTraceExporter自定义导出器指向https://otel.highlight.io:4318/v1/traces即 OTLP/HTTP 的 traces 端点4318 端口BatchSpanProcessor批处理处理器将 Span 攒批后统一交给 Exporter比逐条发送更高效tracerProvider.register()将 Provider 注册为全局默认之后通过getTracer()取出的 tracer 即可直接用于业务代码第四步封装日志上报函数Log as a Tracehighlight 的日志上报复用 trace 通道文档说明通过创建 log trace 发送日志参数可以根据使用场景简化或修改。有两个关键约定——Span 名称必须为highlight.log后端据此判定这是一个日志并在事件上携带log.severity与log.message属性const ConsoleLevels { debug: debug, info: info, log: info, count: info, dir: info, warn: warn, assert: warn, error: error, trace: trace, } as const // send logs via trace export const log (level: keyof typeof ConsoleLevels, message: string, attributes {}) { const span tracer.startSpan(highlight.log) span.addEvent(log, { ...attributes, [log.severity]: level, [log.message]: message, }, new Date()) span.end() };ConsoleLevels把 console 的九种方法映射到 highlight 认可的日志级别log/count/dir归入infoassert归入warn。level与message之外传入的attributes会展开合并进事件属性从而成为 highlight 中可检索的日志字段。示例应用中的对应实现见 highlight.ts#L192-L208。一个注意点单独使用这个log()函数时消息只会上报到 highlight不会出现在开发工具的控制台里——保留双通道输出的需求由下一步的 console 钩子解决。第五步封装错误上报函数错误同样经由 trace 发送Span 名沿用highlight.log约定然后用recordException记录异常、setAttributes附加自定义属性// send errors via trace export const error (message: string, attributes {}) { const span tracer.startSpan(highlight.log) span.recordException( new Error(message), new Date(), ) span.setAttributes(attributes) span.end() };对应示例应用实现为 highlight.ts#L211-L216。其好处是错误名可自定义不必依赖异常对象且属性可携带任意上下文recordException会保留标准exception.*语义字段后端据此将 Span 处理为一条错误记录。第六步Monkey Patch console实现自动上报手动调用log()/error()灵活但侵入性强。如果只想“控制台里发生什么就上报什么”文档提供了hookConsole()它覆写 console 各方法使其在打印到 devtools 的同时默认发送到 highlight.io。完整代码// monkey patch console type ConsoleFn (...data: any) void let consoleHooked false export function hookConsole() { if (consoleHooked) return consoleHooked true for (const [level, highlightLevel] of Object.entries(ConsoleLevels)) { const origWrite console[level as keyof Console] as ConsoleFn ;(console[level as keyof Console] as ConsoleFn) function ( ...data: any[] ) { const date new Date() try { return origWrite(...data) } finally { const o: { stack: any } { stack: {} } Error.captureStackTrace(o) const message data.map((o) typeof o object ? safeStringify(o) : o, ) const attributes data.filter((d) typeof d object).reduce((a, b) ({ ...a, ...b }), {}) if (level error) { attributes[exception.type] Error attributes[exception.message] message.join() attributes[exception.stacktrace] JSON.stringify(o.stack) } log( highlightLevel, message.join( ), attributes ) } } } } // https://stackoverflow.com/a/2805230 const MAX_RECURSION 128 export function safeStringify(obj: any): string { function replacer(input: any, depth?: number): any { if ((depth ?? 0) MAX_RECURSION) { throw new Error(max recursion exceeded) } if (input typeof input object) { for (let k in input) { if (typeof input[k] object) { replacer(input[k], (depth ?? 0) 1) } else if (!canStringify(input[k])) { input[k] input[k].toString() } } } return input } function canStringify(value: any): boolean { try { JSON.stringify(value) return true } catch (e) { return false } } try { return JSON.stringify(replacer(obj)) } catch (e) { return obj.toString() } }从源码逻辑看钩子的设计要点有四处防重复注入consoleHooked标志保证只覆写一次重复调用直接返回先打印后上报try/finally结构确保原始origWrite(...data)一定执行devtools 输出不受影响上报失败也不会打断业务逻辑错误增强level error时额外注入exception.type、exception.message、exception.stacktrace通过Error.captureStackTrace抓取现场调用栈使console.error在 highlight 中呈现为带堆栈的错误安全序列化safeStringify用MAX_RECURSION 128限制递归深度对无法直接JSON.stringify的叶子值降级为toString()最终失败时整体回退为obj.toString()避免深层/循环对象拖垮上报链路。在应用中使用 tracer、log、error 与 hookConsole所有函数集中在highlight.ts中导出业务代码按需引入即可。文档给出的调用示例import * as H from ./highlight.ts // path to highlight functions自定义 Span追踪 异常记录const span H.tracer.startSpan(Custom span name) ... span.recordException( new Error(this is a otel tracer error), ) span.end()上报一条 warn 日志附带可检索属性H.log(warn, Default sending information loaded, { sender: spencer })上报一条自定义错误H.error(Divide by 0 error, { numerator: 623 })启用 console 自动上报H.hookConsole() console.log(Hello World)官方示例应用展示了这些函数在真实工程中的用法。根布局 e2e/react-native/app/_layout.tsx#L25-L31 在字体资源加载完成后、应用真正开始渲染前调用钩子保证首屏日志也能被捕获useEffect(() { if (loaded) { H.hookConsole() SplashScreen.hideAsync() } else { } }, [loaded])首页 e2e/react-native/app/(tabs)/index.tsx#L10-L19/index.tsx#L10-L19) 则一次性演示了全部三类上报useEffect(() { const span H.tracer.startSpan(HomeScreen) console.log(A hooked console message) span.recordException(new Error(this is a otel tracer error)) span.end() }, []) H.log(warn, Default home screen loaded, { sender: spencer }) H.error(type error, { code: 623 })其中console.log会在hookConsole()生效后同时出现在 devtools 与 highlight 中HomeScreenSpan 上记录了一条异常用于验证 trace 通道。运行官方示例应用验证链路仓库内置的示例应用 e2e/react-native 是一个基于 Expoexpo-router 文件路由的标准项目README 给出了启动方式# 1. 安装依赖 npm install # 2. 启动应用使用隧道便于真机/模拟器访问 npx expo start --tunnel启动后输出中会提供在 development build、Android 模拟器、iOS 模拟器或 Expo Go 中打开应用的选项。将 app/highlight.ts 中的highlight.project_id换成你自己的项目 ID 后打开应用即可在 highlight 的 Logs、Errors、Traces 面板中看到上述三类遥测数据。注意事项与演进方向beta 状态文档标题与 QuickStart 元信息均标注(beta)方案细节如highlight.logSpan 名约定、OTLP 端点以当前仓库版本为准。自定义 Exporter 是过渡方案文档与配套工程博客 how-to-instrument-your-react-native-app-with-opentelemetry.md 都明确指出基于 bundler 的官方 OTLP 导出器方案在推进中届时可用标准的OTLPTraceExporter替换ReactNativeOTLPTraceExporter。双通道输出直接用log()只发 highlight 不进 devtoolshookConsole()之后console.*才能两者兼得建议在应用生命周期早期如根布局useEffect调用一次。Resource 是全局生效的highlight.project_id、service.name等 Resource 属性会附加到每条 trace/log/error 上接入多环境时可用environment等语义属性区分方便按维度过滤与告警。整体调用链可以概括为业务代码 → tracerstartSpan / log / error / hookConsole→ BasicTracerProvider BatchSpanProcessor 攒批 → ReactNativeOTLPTraceExporter 序列化并 fetch POST →https://otel.highlight.io:4318/v1/traces→ highlight 后端按 Span 名称与属性解析为 Logs、Errors、Traces。所有实现细节都能在 e2e/react-native/app/highlight.ts 与 highlight.io/components/QuickstartContent/frontend/react-native.tsx 中逐行核对。赞分享可观测性后端【免费下载链接】highlighthighlight.io: The open source, full-stack monitoring platform. Error monitoring, session replay, logging, distributed tracing, and more.项目地址https://gitcode.com/gh_mirrors/hi/highlight点击查看免费下载相关推荐Windmill 可观测性实战基于 Tempo、Grafana、Prometheus 与 Loki 的 OpenTelemetry 链路追踪与日志监控Windmill 可观测性实战基于 Tempo、Grafana、Prometheus 与 Loki 的 OpenTelemetry 链路追踪与日志监控 导读后端工作流自动化任务调度低代码前端Bindu 可观测性实战指南基于 OpenTelemetry 与 Sentry 的 AI Agent 全链路追踪与错误监控Bindu 可观测性实战指南基于 OpenTelemetry 与 Sentry 的 AI Agent 全链路追踪与错误监控 导读 本文是一份面向 Bindu微服务链路追踪实战基于nerdctl部署Jaeger与OpenTelemetry全链路监控微服务链路追踪实战基于nerdctl部署Jaeger与OpenTelemetry全链路监控 在微服务架构中全链路监控是排查分布式系统问题的关键。但传统部署方CLI云原生上一篇Formtastic表单数据预处理终极指南如何实现智能默认值与动态选项下一篇VR控制器按键自定义教程用OpenVR Advanced Settings打造专属操控体验创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考