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,增加自身项目需求。目前也还比较容易的。

相关推荐
人工智能训练师3 小时前
Ubuntu22.04如何安装新版本的Node.js和npm
linux·运维·前端·人工智能·ubuntu·npm·node.js
Seveny073 小时前
pnpm相对于npm,yarn的优势
前端·npm·node.js
yddddddy4 小时前
css的基本知识
前端·css
昔人'4 小时前
css `lh`单位
前端·css
Nan_Shu_6146 小时前
Web前端面试题(2)
前端
知识分享小能手6 小时前
React学习教程,从入门到精通,React 组件核心语法知识点详解(类组件体系)(19)
前端·javascript·vue.js·学习·react.js·react·anti-design-vue
AKAMAI6 小时前
Sport Network 凭借 Akamai 实现卓越成就
人工智能·云原生·云计算
ajax_beijing6 小时前
zookeeper是啥
分布式·zookeeper·云原生
蚂蚁RichLab前端团队7 小时前
🚀🚀🚀 RichLab - 花呗前端团队招贤纳士 - 【转岗/内推/社招】
前端·javascript·人工智能
孩子 你要相信光7 小时前
css之一个元素可以同时应用多个动画效果
前端·css