React 前端应用中快速实践 OpenTelemetry 云原生可观测性(SigNoz/K8S)

OpenTelemetry 可用于跟踪 React 应用程序的性能问题和错误。您可以跟踪从前端 web 应用程序到下游服务的用户请求。OpenTelemetry 是云原生计算基金会(CNCF)下的一个开源项目,旨在标准化遥测数据的生成和收集。已成为下一代可观测平台的事实标准。

React(也称为 React.jsReactJS )是一个免费的开源前端 JavaScript 库,用于基于 UI 组件构建用户界面。它是由 Meta (以前的 Facebook)和一个由个人开发者和公司组成的社区维护的。React 可以作为使用 Next.js 等框架开发单页、移动或服务器渲染应用程序的基础。

然而,React 只关心状态管理和将状态呈现给 DOM,因此创建 React 应用程序通常需要使用额外的库进行路由,以及某些客户端功能。

使用 opentelemetry-js 库,你可以让你的 React 应用生成跟踪数据。您可以跟踪从前端 web 应用程序到下游服务的用户请求。

在演示如何实现 OpenTelemetry 库之前,让我们简要概述一下 OpenTelmetry

什么是 OpenTelemetry?

OpenTelemetry 是一套与第三方厂商无关的开源工具、API 和 SDK,用于检测应用程序,以创建和管理遥测数据(日志、指标和跟踪)。

OpenTelemetry 库的 instrument(采集程序) 应用程序代码生成遥测数据,然后发送到可观察性工具进行存储和可视化

OpenTelemetry 是建立可观测性框架的基础。它还为您提供了选择后端分析工具的自由。

OpenTelemetry 与 SigNoz

在本文中,我们将使用 SigNoz 作为后端分析工具。SigNoz 是一个全栈开源 APM 工具,可用于存储和可视化 OpenTelemetry 收集的遥测数据。它是在 OpenTelemetry 上原生构建的,并适用于 OTLP 数据格式。

SigNoz 为最终用户提供了查询和可视化功能,并附带了用于应用程序度量和跟踪的开箱即用图表。

现在,让我们开始了解如何使用 opentelemetry-js 库,然后在 SigNoz 中可视化收集的数据。

快速实践

实验环境

DigitalOcean 托管集群(k8s v1.24.13)。

Helm 一键安装 SigNoz

sh 复制代码
helm repo add signoz https://charts.signoz.io

helm install signoz signoz/signoz -n apm --create-namespace \
--set otelCollector.config.receivers.otlp.protocols.http.include_metadata=true \
--set otelCollector.config.receivers.otlp.protocols.http.cors.allowed_origins='https://apm-demo.react-admin.com'

注意:cors 跨域设置,我这里 React 应用域名是 apm-demo.react-admin.com

查看 Pod

sh 复制代码
kubectl get po -n apm

NAME                                               READY   STATUS    RESTARTS       AGE
chi-signoz-clickhouse-cluster-0-0-0                1/1     Running   0              3m51s
signoz-alertmanager-0                              1/1     Running   0              4m5s
signoz-clickhouse-operator-54b6d79f58-b47ff        2/2     Running   2 (4m2s ago)   4m5s
signoz-frontend-564b8c4868-88grm                   1/1     Running   0              4m5s
signoz-k8s-infra-otel-agent-dqh5c                  1/1     Running   0              4m6s
signoz-k8s-infra-otel-agent-jdvnh                  1/1     Running   0              4m6s
signoz-k8s-infra-otel-agent-tb8sp                  1/1     Running   0              4m6s
signoz-k8s-infra-otel-deployment-dc85b496f-n6dhm   1/1     Running   0              4m5s
signoz-otel-collector-655cff46d8-7z5wn             1/1     Running   0              4m5s
signoz-otel-collector-metrics-7775fc9857-mb8wv     1/1     Running   0              4m5s
signoz-query-service-0                             1/1     Running   0              4m5s
signoz-zookeeper-0                                 1/1     Running   0              4m5s

暴露采集器 Server

此集群 Ingress Controller 是 Traefik,配置如下:

yaml 复制代码
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: ingest
  namespace: apm
spec:
  entryPoints:
    - web
  routes:
    - match: PathPrefix(`/`) && Host(`ingest.doge-data.com`)
      kind: Rule
      services:
        - name: signoz-otel-collector
          port: 4318

示例应用

测试地址:

仓库:

OpenTelemetry-JS

仓库:

官方文档:

Tracing 示例核心源码

位于示例仓库:src/helpers/tracing/index.ts

ts 复制代码
import { context, trace, Span, SpanStatusCode } from "@opentelemetry/api";
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
import { Resource } from "@opentelemetry/resources";
import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { ZoneContextManager } from "@opentelemetry/context-zone";
import { FetchInstrumentation } from "@opentelemetry/instrumentation-fetch";
import { FetchError } from "@opentelemetry/instrumentation-fetch/build/src/types";
import { registerInstrumentations } from "@opentelemetry/instrumentation";

const serviceName = "link-frontend";

const resource = new Resource({ "service.name": serviceName });
const provider = new WebTracerProvider({ resource });

const collector = new OTLPTraceExporter({
  url: "https://ingest.doge-data.com/v1/traces",
  // headers: {
  //   "signoz-access-token": "SigNoz-Cloud-Ingestion-Token-HERE"
  // }
});

provider.addSpanProcessor(new SimpleSpanProcessor(collector));
provider.register({ contextManager: new ZoneContextManager() });

const webTracerWithZone = provider.getTracer(serviceName);

declare const window: any;
var bindingSpan: Span | undefined;

window.startBindingSpan = (
  traceId: string,
  spanId: string,
  traceFlags: number
) => {
  bindingSpan = webTracerWithZone.startSpan("");
  bindingSpan.spanContext().traceId = traceId;
  bindingSpan.spanContext().spanId = spanId;
  bindingSpan.spanContext().traceFlags = traceFlags;
};

registerInstrumentations({
  instrumentations: [
    new FetchInstrumentation({
      propagateTraceHeaderCorsUrls: ["/.*/g"],
      clearTimingResources: true,
      applyCustomAttributesOnSpan: (
        span: Span,
        request: Request | RequestInit,
        result: Response | FetchError
      ) => {
        const attributes = (span as any).attributes;
        if (attributes.component === "fetch") {
          span.updateName(
            `${attributes["http.method"]} ${attributes["http.url"]}`
          );
        }
        if (result instanceof Error) {
          span.setStatus({
            code: SpanStatusCode.ERROR,
            message: result.message,
          });
          span.recordException(result.stack || result.name);
        }
      },
    }),
  ],
});

export function traceSpan<F extends (...args: any) => ReturnType<F>>(
  name: string,
  func: F
): ReturnType<F> {
  var singleSpan: Span;
  if (bindingSpan) {
    const ctx = trace.setSpan(context.active(), bindingSpan);
    singleSpan = webTracerWithZone.startSpan(name, undefined, ctx);
    bindingSpan = undefined;
  } else {
    singleSpan = webTracerWithZone.startSpan(name);
  }
  return context.with(trace.setSpan(context.active(), singleSpan), () => {
    try {
      const result = func();
      singleSpan.end();
      return result;
    } catch (error) {
      singleSpan.setStatus({ code: SpanStatusCode.ERROR });
      singleSpan.end();
      throw error;
    }
  });
}

在 React 组件中使用

位于示例仓库:src/components/TracingButton/index.tsx

tsx 复制代码
import { Button } from '@mui/material'

import { traceSpan } from 'helpers/tracing'

interface Props {
  label: string;
  id?: string;
  secondary?: boolean;
  onClick: () => void;
}

export default (props: Props) => {
  const onClick = (): void =>
    traceSpan(`'${props.label}' button clicked`, props.onClick);

  return (
    <div>
      <Button
        id={props.id}
        variant={"contained"}
        color={props.secondary ? "secondary" : "primary"}
        onClick={onClick}
      >
        {props.label}
      </Button>
    </div>
  );
};

测试 React 应用上报

转到 apm-demo.react-admin.com , 单击 FETCH LINKS

SigNoz 后台面板查看,聚合指标等

总结

本篇文章侧重于快速实践,OpenTelemetry 本身很复杂,涉及很多基础概念,大家自行翻阅文档。

SigNoz 作为后端分析与可视化工具。虽相对于 ELK Stack 还有很多不足,但它号称是基于 OpenTelemetry 生态原生构建的下一代开源可观测平台,期待它后续发展。

有兴趣的朋友,也可以二次开发 SigNoz,增加自身项目需求。目前也还比较容易的。

相关推荐
a1117763 分钟前
原生 Markdown 阅读与编辑器 开源项目
前端·开源·软件
laity173 分钟前
python发光表白爱心(从零到一实现)
前端·后端
程序员爱钓鱼12 分钟前
Rust impl详解:为Struct定义方法与关联函数
前端·后端·rust
用户0595401744622 分钟前
Qdrant 召回不一致踩坑实录:跑了 300 次测试才发现是索引没刷新
前端·css
Highcharts.js33 分钟前
Highcharts 主流前端框架无缝集成指南
前端·vue.js·前端框架·highcharts·可视化图表
JAVA面经实录91735 分钟前
网络编程基础(Java Web/分布式前置·完整版)(十一)
java·前端·网络
IT_陈寒37 分钟前
搞不定JavaScript的数组去重?你可能漏了这两个坑
前端·人工智能·后端
三8441 小时前
CSRF跨站请求伪造基础
前端·csrf
岁岁种桃花儿1 小时前
Vue核心语法第十一篇:绑定样式
前端·javascript·vue.js