概述

OpenTelemetry,以下简称 OTEL,是由 CNCF 托管的“一站式可观测性标准”,把指标、链路、日志三大信号统一为单一 SDK/API,零侵入地采集从浏览器、移动端到后端、容器、云服务的全栈遥测数据,并支持 40+ 后端一键导出,让分布式系统的黑盒瞬间变透明。

OpenTelemetry-JS 是 OpenTelemetry 开源的 JavaScript/TypeScript 观测框架,可在浏览器与 Node.js 中无侵入地采集 Traces、Metrics、Logs,自动埋点 HTTP、Fetch、WebSocket、gRPC、数据库等调用链,一键导出至 Jaeger、Prometheus、Zipkin 等后端,实现前端到后端的统一可观测性。

本文章主要通过参考 opentelemetry-js 相关开源方案,经过代码编写以及前端业务自埋点改造,演示 OTEL 前端 Span 如何上报到观测云,以及基于 OTEL 的前端 Span 上报,如何实现在 WebSocket 应用场景的最后一公里探测的最佳实践。

众所周知,OTEL 的前端和后端都是通用的 Span 数据上报方式,而观测云又兼容 OTEL 协议并且 DataKit 开箱即用支持 OTEL Span 数据的上报,因此对于 WebSocket 应用,基于 OTEL 的后端与前端 Span 埋点监控可以在链路层面实现完整的端到端的监控。

前端 Span 上报观测云实践

功能特性

  • OpenTelemetry SDK 初始化
  • 基于 trace parent 创建 span
  • OTLP 协议数据导出
  • 批量 span 处理
  • 分布式追踪上下文传播
  • TypeScript 支持

代码说明

otel-span/
├── index.ts              # 主入口文件
├── create-span.ts        # OpenTelemetry span 创建逻辑
├── package.json          # 项目依赖配置
├── tsconfig.json         # TypeScript 配置
index.ts

index.ts 作为主入口文件

import { setupOTelSDK, createSpanWithTraceParent } from './create-span'// 初始化 OpenTelemetry SDK
setupOTelSDK()// 使用traceparent创建span, 可以在请求 request header中获取
const traceParent = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
const spanName = 'test-span'console.log('开始创建 span...')
const span = createSpanWithTraceParent(traceParent, spanName)
console.log('span 创建完成!')// 等待一段时间确保 span 被导出
setTimeout(() => {console.log('程序执行完成')process.exit(0)
}, 2000)

create-span.ts

create-span.ts 用于创建 span 逻辑

import { Resource } from '@opentelemetry/resources'
import { BatchSpanProcessor, WebTracerProvider } from '@opentelemetry/sdk-trace-web'
import { ZoneContextManager } from '@opentelemetry/context-zone'
import { trace, SpanContext, TraceFlags, context } from '@opentelemetry/api'
import { W3CTraceContextPropagator } from '@opentelemetry/core'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'const setupOTelSDK = () => {const resource = Resource.default().merge(new Resource({'service.name': 'test',}))const tracerProvider = new WebTracerProvider({resource: resource,})const traceExporter = new OTLPTraceExporter({url: 'http://127.0.0.1:9529/otel/v1/traces',headers: {},})const spanProcessor = new BatchSpanProcessor(traceExporter, {// 可选配置参数maxExportBatchSize: 100, // 每批最多处理的span数量scheduledDelayMillis: 1000, // 定期导出的间隔时间(毫秒)})// propagation.setGlobalPropagator(new W3CTraceContextPropagator());// 设置上下文传播器const contextManager = new ZoneContextManager()tracerProvider.addSpanProcessor(spanProcessor)tracerProvider.register({contextManager,propagator: new W3CTraceContextPropagator(),})trace.setGlobalTracerProvider(tracerProvider)}const parseTraceParent = (traceParent: string) => {const parts = traceParent.split('-')if (parts.length !== 4) throw new Error('Invalid trace_parent format')if (parts[0] !== '00') throw new Error('Unsupported trace_parent version')const traceId = parts[1]const parentSpanId = parts[2]if (traceId.length !== 32) throw new Error('traceId must be 32 characters')if (parentSpanId.length !== 16) throw new Error('parentSpanId must be 16 characters')if (!isHex(traceId)) throw new Error('traceId contains invalid hex characters')if (!isHex(parentSpanId)) throw new Error('parentSpanId contains invalid hex characters')return [traceId, parentSpanId]
}const isHex = (s: string) => {return /^[0-9a-fA-F]+$/.test(s)
}const createSpanWithTraceParent = (traceParent: string, spanName: string) => {if (!traceParent) returnconst [traceId, parentSpanId] = parseTraceParent(traceParent)const tracer = trace.getTracer('Browser')// 创建SpanContextconst spanContext: SpanContext = {traceId: traceId,spanId: parentSpanId,traceFlags: TraceFlags.SAMPLED,// traceState: new TraceState(),isRemote: true,}// 包装SpanContext为Spanconst parentSpan = trace.wrapSpanContext(spanContext)// 创建父级上下文 - 修正这一行const parentContext = trace.setSpan(context.active(), parentSpan)// 创建并启动子spanconst childSpan = tracer.startSpan(spanName,{// attributes: {//   "parsing time": `${10000/1000}μs`// }},parentContext)try {// console.info(`Child span started with trace_id: ${traceId}`);// 业务逻辑...} finally {childSpan.end()}
}export { setupOTelSDK, createSpanWithTraceParent }

扩展说明

添加自定义属性

const childSpan = tracer.startSpan(spanName,{attributes: {'custom.attribute': 'value','user.id': '12345','operation.type': 'read'}},parentContext
)

添加事件

childSpan.addEvent('operation.started', {'input.size': inputSize
})

设置状态

childSpan.setStatus({code: SpanStatusCode.OK,message: 'Operation completed successfully'
})

上报测试

1、克隆或解压项目

https://github.com/lrwh/observable-demo/tree/main/otel-span

数据上报地址使用观测云的本地的 DataKit 为例。

2、安装依赖:npm install

3、开发模式试运行:npm run dev

数据上报服务名为“test”,span 名称为“test-span” 。

4、观测云 DataKit 数据接收与展示

WebSocket 应用场景实战

场景描述

某平台已实现基于 OTEL 的后端 Span 的上报,前端三端的监控是基于观测云的 SDK 进行了集成,也实现了一定意义上的前端RUM数据和和后端 OTEL 的链路数据关联,但是 WebSocket 长连接打破了传统的请求-响应模式,传统 HTTP 的 Trace 是请求粒度的,而 WebSocket 连接可能持续数小时,而且重点是 WebSocket 的 Server 端也会发起一些业务数据推送请求到客户端时,此时仅通过后端的 OTEL 链路无法确定数据什么时候推送到的客户端,以及客户端的渲染情况表现如何。

方案与原理

首先,观测云在三端客户端(web,安卓,IOS)通过自身的 SDK 集成,会生成基于 w3c_traceparent 的 Span,相关的 traceparent 上下文会传递到 WebSocket Server 后端链路 Span,当后端的 WebSocket Server 推业务请求数据到客户端时,会继续传播 traceparent 上下文给 OTEL 前端 Span,进而通过补充 OTEL 前端 Span 的最后一公里的数据上报,实现整个 websocket 通信的全链路监控以及链路不同阶段调用的耗时情况。

前端自埋点

  • 通过前端埋点来探测 WebSocket Server 端什么时间刚好把业务数据请求发送到客户端

如下图所示,在前端业务代码中定义时,后端传过来 data.traceparent,随后即执行核心业务代码创建 span 的操作,即上述“前端 Span 上报观测云“章节中类似主入口中的index.ts 的 createSpanWithTraceParent 方法。

const span = createSpanWithTraceParent(traceParent, spanName)

也即是最终会调用 create-span.ts 程序文件中的 createSpanWithTraceParent 方法。

  • 通过前端埋点来探测 WebSocket Server 端推送业务请求数据到客户端之后,客户端什么时候渲染完成

如下图,同理,也是 WebSocket Server 端 traceparent 数据传播到客户端,即调用 createSpanWithTraceParent(traceParent, spanName) 方法来实现。

更多自埋点类似原理,需要自行选择合适位置进行埋点。

效果展示

  • 拓扑展示

  • 链路展示

总结

基于 OTEL 前端 Span 的数据上报与自定义埋点改造,解决了 WebSocket 应用场景下 WebSocket Server 到客户端最后一公里的探测问题,从而使 WebSocket 应用的请求通信有了端到端的可观测,整个通信过程的性能耗细节一览无余。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/93933.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/93933.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/93933.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VS Code 出现的 Web 视图加载错误和服务工作者注册失败问题解决方案

针对 VS Code 或 Cursor (vscode系)中出现的 Web 视图加载错误和服务工作者注册失败问题,以下是永久性解决方案的完整操作指南:解决方案步骤打开命令面板 使用快捷键 CtrlShiftP(Windows/Linux)或 CmdShift…

【qml-4】qml与c++交互(类型多例)

背景: 【qml-1】qml与c交互第一次尝试(实例注入) 【qml-2】尝试一个有模式的qml弹窗 【qml-3】qml与c交互第二次尝试(类型注册) 【qml-4】qml与c交互(类型多例) 【qml-5】qml与c交互&#…

图数据库如何构筑 Web3 风控防线 聚焦批量注册与链上盗转 悦数图数据库

随着 Web3 生态的不断演进,链上风险呈现出团伙化、隐蔽化和动态化的趋势,传统的单点风控手段已难以应对复杂多变的攻击模式。尤其在批量注册薅羊毛与链上交易盗转洗钱等高频风险场景中,攻击者往往通过伪造身份、跨链操作、多层嵌套转账等方式…

恒流源电路学习

恒流源的设计原理: 如图所示你可以看到右边的的推到公式得到红点处的电压是一个和左边相关的定值,所以呢右边的电流就是电压除以那个4Ω,所以得到右边的电路的电流大体是一个定值,不管你再加什么东西都可以保持这个电流&#xff…

基于生成对抗网络的模糊图像恢复原理与技术实现

1. 引言图像模糊是数字图像处理中的常见问题,其成因包括相机抖动、物体运动、聚焦不良等。传统方法如维纳滤波、Lucy-Richardson 算法等依赖于模糊核估计和逆滤波,在复杂场景下性能有限。生成对抗网络(Generative Adversarial Networks, GAN&…

【Doris 系列】Doris IP 变更修复

FE 恢复 异常日志 查看 fe.out 会有以下报错,此时 fe 进程是无法启动的,操作前注意备份所有 fe 的元数据并停止上游读写动作! java.io.IOException: the self host 192.168.31.78 does not equal to the host in ROLE file 192.168.31.81. Yo…

安卓14系统应用收不到开机广播

安卓14系统应用收不到开机广播 - Wesley’s Blog 前段时间有测试反馈在安卓14 上面某系统应用恢复出厂设置后没有自启动,究竟是什么原因呢? 回顾 Android 从3.1开始,会将新安装并且从未被启动的应用置为“STOPPED”状态,或者被…

C# Attribute 方法扩展

场景 刚写完一个干净利落的方法,比如保存数据到数据库,逻辑清晰、结构优雅, 第二天,“嘿,保存完数据,记得给客户发个邮件哦~” 第三天,“能不能再发个消息通知其他系统?” 第四天&am…

【URP】[法线贴图]为什么主要是蓝色的?

【从UnityURP开始探索游戏渲染】专栏-直达 法线贴图呈现蓝紫色调(尤其以蓝色为主)是由其‌存储原理、切线空间坐标系设计及颜色编码规则共同决定的‌。 核心原因:法线向量的存储规则‌ ‌法线向量的物理范围‌ 法线是单位向量,…

驱动开发系列63 - NVIDIA 开源GPU驱动open-gpu-kernel-modules编译调试

目录 一:通过apt方式安装nvidia 驱动 二:通过 .run 方式安装nvidia驱动 三:编译安装nvidia开源内核驱动 四:验证和调试 五:卸载驱动 1. 以apt方式安装nvidia 驱动的卸载方法 2. 以.run方式安装nvidia驱动的卸载方法 六:安装CUDA环境 一:通过apt方式安装nvidia 驱动…

对KingbaseES架构的解析:从读写分离到异地灾备的技术实现与保障机制

声明:文章为本人真实测评博客,非广告,并没有推广该平台 ,为用户体验文章 本人旨在分享最真实的用户体验,为关注此类产品的朋友们提供一个客观的参考。 文章目录一、架构全景:四级高可用构建数字基础1.1 物…

Visual Studio中的常用调试功能(上)

1、利用断点进行调试添加断点的方式有以下几种1.键盘快捷键F92.通过菜单【Debug(调试)】-》【Toggle BreakPoint(切换断点)】3.点击代码行左边的空白处(推荐)设置断点后,按F5运行程序&#xff0…

Linux -- 线程同步

1.1条件变量 (1)当⼀个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。 (2)例如⼀个线程访问队列时,发现队列为空,它只能等待,只到其它线程将⼀个节点添加到队列 中。这种情况就需要⽤到…

前端进阶指南:JavaScript性能优化实战全解析

深入剖析 JavaScript 性能瓶颈,分享优化技巧与最佳实践,让你的前端应用更快、更稳、更流畅。 📑 目录 一、前言 二、性能瓶颈的常见来源 三、JavaScript代码优化技巧 1. 避免重复计算 2. 合理使用防抖与节流 3. 使用事件委托 四、渲染…

RabbitMQ:SpringAMQP Direct Exchange(直连型交换机)

目录一、案例需求二、基础配置三、代码实现直连型交换机也叫做定向交换机,通过RoutingKey绑定交换机与队列直接的关系。 生产者源码 消费者源码 一、案例需求 在RabbitMQ控制台中,声明队列direct.queue1和direct.queue2。在RabbitMQ控制台中&#xff…

implement libtime on Windows

因为Windows的time命令和Linux的time命令不一样&#xff0c;尝试实现libtime libtime.h /** libtime.h - 跨平台时间测量库* 功能&#xff1a;执行外部命令并测量其运行时间和资源使用*/#ifndef LIBTIME_H #define LIBTIME_H#include <stdio.h> #include <stdlib.h>…

Unity进阶--C#补充知识点--【C#各版本的新功能新语法】C#1~4与C#5

来源于唐老狮的视频教学&#xff0c;仅作记录和感悟记录&#xff0c;方便日后复习或者查找 一.C#版本与Unity的关系 1.各Unity版本支持的C#版本 更多信息可以在Untiy官网说明查看 https://docs.unity3d.com/2020.3/Documentation/Manual/CSharpCompiler.html&#xff08;这个好…

水闸安全综合监测系统解决方案

一、方案概述 水闸作为重要的水利工程设施&#xff0c;承担着防洪、排涝、供水和灌溉等关键功能。其安全性直接关系到下游人民群众的生命财产安全以及区域经济的稳定发展。近年来&#xff0c;随着极端天气频发和工程老化问题日益突出&#xff0c;水闸安全监测工作显得尤为重要。…

基于单片机智能点滴输液系统

传送门 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目速选一览表 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目功能速览 概述 该系统基于单片机控制技术&#xff0c;结合传感器和无线通信模块&#xff0c;实现对输液过程的实…

AI数据仓库管理提升效率

内容概要在数字化转型浪潮中&#xff0c;AI数据仓库管理正重塑企业数据处理格局。本部分简要介绍其核心机制&#xff0c;即通过智能API接入外部数据源实现多平台数据无缝整合&#xff0c;随后应用数据清洗技术去除冗余信息&#xff0c;确保数据质量。同时&#xff0c;加密存储机…