From 0dc784c2be3432134e0e37a838339aa2f1b25cdc Mon Sep 17 00:00:00 2001 From: TomyJan Date: Wed, 14 Aug 2024 14:32:14 +0800 Subject: [PATCH] feat(telemetry): add `Sentry` as frontend telemetry (#1458) Co-authored-by: sealday Reviewed-on: https://git.daoyoucloud.com/daoyoucloud/tachybase/pulls/1458 Reviewed-by: sealday Co-authored-by: TomyJan Co-committed-by: TomyJan --- .env.example | 12 ++- .gitea/workflows/release.yaml | 7 ++ packages/core/app/client/.umirc.ts | 7 ++ packages/core/app/src/index.ts | 2 +- packages/core/client/package.json | 1 + .../client/src/application/Application.tsx | 54 +++++++++++ packages/core/telemetry/src/config.ts | 2 +- pnpm-lock.yaml | 97 +++++++++++++++++++ 8 files changed, 178 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index 601c8ec33..e2d72ee83 100644 --- a/.env.example +++ b/.env.example @@ -58,9 +58,17 @@ INIT_ROOT_USERNAME=tachybase # TELEMETRY_ENABLED=on # 是否启用遥测 # TELEMETRY_SERVICE_NAME=tachybase-default-demo # 服务名称,默认为 tachybase-main -# OTEL_LOG_LEVEL=debug # OpenTelemetry 日志级别,仅输出到控制台,见 [官方文档](https://open-telemetry.github.io/opentelemetry-js/enums/_opentelemetry_api.DiagLogLevel.html) ,不配置则默认不输出日志 +# OTEL_LOG_LEVEL=debug # OpenTelemetry 日志级别,仅输出到控制台,见 https://open-telemetry.github.io/opentelemetry-js/enums/_opentelemetry_api.DiagLogLevel.html ,不配置则默认不输出日志 # OTEL_METRICS_READER=console,prometheus # 指标读取器,两个均为内置,console 为控制台,prometheus 为 prometheus 服务器 # OTEL_PROMETHEUS_SERVER=on # 是否启用 Prometheus 服务,用于创建服务器以供导出指标数据 # OTEL_PROMETHEUS_PORT=9464 # Prometheus 服务器端口,默认为 9464,注意此服务器没有鉴权 # OTEL_TRACES_PROCESSOR=console,otlp # 链路追踪处理器,两个均为内置,console 为控制台,otlp 为 OTLP 规范的追踪服务器 -# OTEL_EXPORTER_OTLP_TRACES_ENDPOINT_GRPC=http://localhost:4317 # [OTLP](https://opentelemetry.io/docs/specs/otlp/) 规范的追踪服务器的 gRPC 端点地址 +# OTEL_EXPORTER_OTLP_TRACES_ENDPOINT_GRPC=http://localhost:4317 # OTLP https://opentelemetry.io/docs/specs/otlp/ 规范的追踪服务器的 gRPC 端点地址 +# 下面为前端 Sentry 跟踪配置,注意修改后需要重新 build 前端代码 +# SENTRY_DSN= # Sentry DSN,用于前端遥测 +# SENTRY_TRACE_ENABLE=on # 是否启用 Sentry 跟踪,文档 https://docs.sentry.io/platforms/javascript/guides/react/tracing/ +# SENTRY_TRACE_SAMPLE_RATE=1.0 # Sentry 跟踪采样率,1.0 为 100%,0.0 为 0%,默认为 1.0 +# SENTRY_TRACE_PROPAGATION_TARGETS=localhost,/^\// # Sentry 跟踪传播目标过滤器,多个用逗号分隔,支持正则,默认为 localhost,/^\// ,文档 https://docs.sentry.io/platforms/javascript/tracing/instrumentation/automatic-instrumentation/#tracepropagationtargets +# SENTRY_SESSION_REPLAY_ENABLE=on # 是否启用 Sentry 会话重放,文档 https://docs.sentry.io/platforms/javascript/guides/react/session-replay/ +# SENTRY_SESSION_REPLAY_SAMPLE_RATE=0.1 # Sentry 会话重放采样率,1.0 为 100%,0.0 为 0%,默认为 0.1 +# SENTRY_SESSION_REPLAY_ONERROR_SAMPLE_RATE=1.0 # Sentry 会话重放错误采样率,1.0 为 100%,0.0 为 0%,默认为 1.0 diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index ded1917c1..0fb16907b 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -31,4 +31,11 @@ jobs: - name: Publish env: NODE_AUTH_TOKEN: ${{ secrets.NPMTOKEN }} + SENTRY_DSN: ${{ vars.SENTRY_DSN }} + SENTRY_TRACE_ENABLE: ${{ vars.SENTRY_TRACE_ENABLE }} + SENTRY_TRACE_SAMPLE_RATE: ${{ vars.SENTRY_TRACE_SAMPLE_RATE }} + SENTRY_TRACE_PROPAGATION_TARGETS: ${{ vars.SENTRY_TRACE_PROPAGATION_TARGETS }} + SENTRY_SESSION_REPLAY_ENABLE: ${{ vars.SENTRY_SESSION_REPLAY_ENABLE }} + SENTRY_SESSION_REPLAY_SAMPLE_RATE: ${{ vars.SENTRY_SESSION_REPLAY_SAMPLE_RATE }} + SENTRY_SESSION_REPLAY_ONERROR_SAMPLE_RATE: ${{ vars.SENTRY_SESSION_REPLAY_ONERROR_SAMPLE_RATE }} run: pnpm -r publish --access=public --no-git-checks --publish-branch ${{ gitea.ref }} diff --git a/packages/core/app/client/.umirc.ts b/packages/core/app/client/.umirc.ts index dde89a80d..5ae322982 100644 --- a/packages/core/app/client/.umirc.ts +++ b/packages/core/app/client/.umirc.ts @@ -57,6 +57,13 @@ export default defineConfig({ define: { ...umiConfig.define, 'process.env.USE_REMOTE_PLUGIN': process.env.USE_REMOTE_PLUGIN, + 'process.env.SENTRY_DSN': process.env.SENTRY_DSN, + 'process.env.SENTRY_TRACE_ENABLE': process.env.SENTRY_TRACE_ENABLE, + 'process.env.SENTRY_TRACE_SAMPLE_RATE': process.env.SENTRY_TRACE_SAMPLE_RATE, + 'process.env.SENTRY_TRACE_PROPAGATION_TARGETS': process.env.SENTRY_TRACE_PROPAGATION_TARGETS, + 'process.env.SENTRY_SESSION_REPLAY_ENABLE': process.env.SENTRY_SESSION_REPLAY_ENABLE, + 'process.env.SENTRY_SESSION_REPLAY_SAMPLE_RATE': process.env.SENTRY_SESSION_REPLAY_SAMPLE_RATE, + 'process.env.SENTRY_SESSION_REPLAY_ONERROR_SAMPLE_RATE': process.env.SENTRY_SESSION_REPLAY_ONERROR_SAMPLE_RATE, }, proxy: { ...umiConfig.proxy, diff --git a/packages/core/app/src/index.ts b/packages/core/app/src/index.ts index 0c9081fb8..b9932c7d7 100644 --- a/packages/core/app/src/index.ts +++ b/packages/core/app/src/index.ts @@ -1,4 +1,4 @@ -import '@tachybase/telemetry'; // 尽早实例化,以尽力保证插桩成功,TODO: Koa 依然无法成功 hook +import '@tachybase/telemetry'; // 尽早实例化,以尽力保证插桩成功 import { Gateway } from '@tachybase/server'; diff --git a/packages/core/client/package.json b/packages/core/client/package.json index 2f8c5e067..6a8e39345 100644 --- a/packages/core/client/package.json +++ b/packages/core/client/package.json @@ -18,6 +18,7 @@ "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", "@emotion/css": "^11.7.1", + "@sentry/react": "^8.25.0", "@tachybase/components": "workspace:*", "@tachybase/evaluators": "workspace:*", "@tachybase/schema": "workspace:*", diff --git a/packages/core/client/src/application/Application.tsx b/packages/core/client/src/application/Application.tsx index f3dd52e00..0c5581f77 100644 --- a/packages/core/client/src/application/Application.tsx +++ b/packages/core/client/src/application/Application.tsx @@ -2,6 +2,7 @@ import React, { ComponentType, ReactElement } from 'react'; import { define, observable } from '@tachybase/schema'; import { APIClientOptions, getSubAppName } from '@tachybase/sdk'; +import * as Sentry from '@sentry/react'; import { i18n as i18next } from 'i18next'; import { get, merge, set } from 'lodash'; import { createRoot } from 'react-dom/client'; @@ -273,6 +274,59 @@ export class Application { }; console.error(error, this.error); } + if (process.env.APP_ENV === 'production') { + const dsn = process.env.SENTRY_DSN; + if (dsn) { + const integrations = []; + let tracesSampleRate = 1.0; + let tracePropagationTargets = ['localhost', /^\//]; + if (process.env.SENTRY_TRACE_ENABLE === 'on') { + integrations.push(Sentry.browserTracingIntegration()); + if (Number(process.env.SENTRY_TRACE_SAMPLE_RATE) >= 0 && Number(process.env.SENTRY_TRACE_SAMPLE_RATE) <= 1) { + tracesSampleRate = parseFloat(process.env.SENTRY_TRACE_SAMPLE_RATE); + } + if (process.env.SENTRY_TRACE_PROPAGATION_TARGETS) { + tracePropagationTargets = process.env.SENTRY_TRACE_PROPAGATION_TARGETS.split(',').map((target) => { + if (target.startsWith('/')) { + return new RegExp(target); + } + return target; + }); + } + } + let replaysSessionSampleRate = 0.1; + let replaysOnErrorSampleRate = 1.0; + if (process.env.SENTRY_SESSION_REPLAY_ENABLE === 'on') { + integrations.push(Sentry.replayIntegration()); + if ( + Number(process.env.SENTRY_SESSION_REPLAY_SAMPLE_RATE) >= 0 && + Number(process.env.SENTRY_SESSION_REPLAY_SAMPLE_RATE) <= 1 + ) { + replaysSessionSampleRate = parseFloat(process.env.SENTRY_SESSION_REPLAY_SAMPLE_RATE); + } + if ( + Number(process.env.SENTRY_SESSION_REPLAY_ONERROR_SAMPLE_RATE) >= 0 && + Number(process.env.SENTRY_SESSION_REPLAY_ONERROR_SAMPLE_RATE) <= 1 + ) { + replaysOnErrorSampleRate = parseFloat(process.env.SENTRY_SESSION_REPLAY_ON_ERROR_SAMPLE_RATE); + } + } + Sentry.init({ + dsn, + integrations, + // Performance Monitoring + tracesSampleRate, // Capture 100% of the transactions + // Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled + tracePropagationTargets, + // Session Replay + replaysSessionSampleRate, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. + replaysOnErrorSampleRate, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. + }); + } else { + console.log('SENTRY_DSN is not set, Sentry will not be initialized'); + } + } + this.loading = false; } diff --git a/packages/core/telemetry/src/config.ts b/packages/core/telemetry/src/config.ts index 1c93b0794..f212bf303 100644 --- a/packages/core/telemetry/src/config.ts +++ b/packages/core/telemetry/src/config.ts @@ -1,5 +1,5 @@ export const telemetryOptions = { - enabled: process.env.TELEMETRY_ENABLED === 'on', + enabled: process.env.TELEMETRY_ENABLED === 'on' && process.env.APP_ENV === 'production', metric: { readerName: process.env.OTEL_METRICS_READER, }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5832e3dd..16d5b60c8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -473,6 +473,9 @@ importers: '@emotion/css': specifier: ^11.7.1 version: 11.11.2 + '@sentry/react': + specifier: ^8.25.0 + version: 8.25.0(react@18.3.1) '@tachybase/components': specifier: workspace:* version: link:../components @@ -8672,10 +8675,34 @@ packages: '@scure/bip39@1.3.0': resolution: {integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==} + '@sentry-internal/browser-utils@8.25.0': + resolution: {integrity: sha512-nlWgp1lVhNQOTUplW85G3qm0fOIgAhJ/sl/31OIuScVrITYhYDF2bO+Zv/jQ8YsdUBAUXqY1tPT9wwPJklnPhw==} + engines: {node: '>=14.18'} + + '@sentry-internal/feedback@8.25.0': + resolution: {integrity: sha512-327I5XJAFrsgjc5qUKxZ9rff3WNCfGvf1fIii70LQ2YQhQgG4XHZILmkD06ETEyXb+H1tkrNQQEJ1/d4ai+q5g==} + engines: {node: '>=14.18'} + + '@sentry-internal/replay-canvas@8.25.0': + resolution: {integrity: sha512-dPXlkAbkFL1DBum8rGTaHS+apJKaXEZJF9gLcBBKTruhTCizrugFLxajzIfVSiFVuwNKuJWa2fzhzbeQM0ee7w==} + engines: {node: '>=14.18'} + + '@sentry-internal/replay@8.25.0': + resolution: {integrity: sha512-3f7x8EYthyj157uV9V8vBjun+1gJnHhh2+i0qxYLhMGx7N2Fq0J3Bvvo1rosSg+fYh5HzPNZDufwIRdg5C/MQw==} + engines: {node: '>=14.18'} + + '@sentry/browser@8.25.0': + resolution: {integrity: sha512-51bdVGXjyooqVGzaSGsnExqRTt9NvZ1zGFsxbbCSXi5UoEFN6zdMUz6jKYsL2K80eeELP2VKOVlobHlEzeJQfw==} + engines: {node: '>=14.18'} + '@sentry/core@5.30.0': resolution: {integrity: sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==} engines: {node: '>=6'} + '@sentry/core@8.25.0': + resolution: {integrity: sha512-7KtglbrW1eX4DOHkf6i4rRIExEf2CgtQ99qZ8gn5FUaAmNMg0rK7bb1yZMx0RZtp5G1TSz/S0jQQgxHWebaEig==} + engines: {node: '>=14.18'} + '@sentry/hub@5.30.0': resolution: {integrity: sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==} engines: {node: '>=6'} @@ -8688,6 +8715,12 @@ packages: resolution: {integrity: sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==} engines: {node: '>=6'} + '@sentry/react@8.25.0': + resolution: {integrity: sha512-A3QeSCJEa+lpo5nh0kxKeqsmnuW2nycKNN/0bpXPv5T5jiEfExSmEBVM0zutrQpf+J0WRIl1AGunUYGPO+GPQg==} + engines: {node: '>=14.18'} + peerDependencies: + react: ^16.14.0 || 17.x || 18.x || 19.x + '@sentry/tracing@5.30.0': resolution: {integrity: sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==} engines: {node: '>=6'} @@ -8696,10 +8729,18 @@ packages: resolution: {integrity: sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==} engines: {node: '>=6'} + '@sentry/types@8.25.0': + resolution: {integrity: sha512-ojim0gDcRhGJPguYrtms4FsprX4xZz3LGNk9Z0hwTbSVEdlhQIInsQ7CYcdM3sjUs+qT7kfpxTRZGUeZNRRJcA==} + engines: {node: '>=14.18'} + '@sentry/utils@5.30.0': resolution: {integrity: sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==} engines: {node: '>=6'} + '@sentry/utils@8.25.0': + resolution: {integrity: sha512-mVlkV7S62ZZ2jM38/kOwWx2xoW8fUv2cjw2IwFKoAIPyLBh3mo1WJtvfdtN/rXGjQWZJBKW53EWaWnD00rkjyA==} + engines: {node: '>=14.18'} + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -24734,6 +24775,42 @@ snapshots: '@noble/hashes': 1.4.0 '@scure/base': 1.1.7 + '@sentry-internal/browser-utils@8.25.0': + dependencies: + '@sentry/core': 8.25.0 + '@sentry/types': 8.25.0 + '@sentry/utils': 8.25.0 + + '@sentry-internal/feedback@8.25.0': + dependencies: + '@sentry/core': 8.25.0 + '@sentry/types': 8.25.0 + '@sentry/utils': 8.25.0 + + '@sentry-internal/replay-canvas@8.25.0': + dependencies: + '@sentry-internal/replay': 8.25.0 + '@sentry/core': 8.25.0 + '@sentry/types': 8.25.0 + '@sentry/utils': 8.25.0 + + '@sentry-internal/replay@8.25.0': + dependencies: + '@sentry-internal/browser-utils': 8.25.0 + '@sentry/core': 8.25.0 + '@sentry/types': 8.25.0 + '@sentry/utils': 8.25.0 + + '@sentry/browser@8.25.0': + dependencies: + '@sentry-internal/browser-utils': 8.25.0 + '@sentry-internal/feedback': 8.25.0 + '@sentry-internal/replay': 8.25.0 + '@sentry-internal/replay-canvas': 8.25.0 + '@sentry/core': 8.25.0 + '@sentry/types': 8.25.0 + '@sentry/utils': 8.25.0 + '@sentry/core@5.30.0': dependencies: '@sentry/hub': 5.30.0 @@ -24742,6 +24819,11 @@ snapshots: '@sentry/utils': 5.30.0 tslib: 1.14.1 + '@sentry/core@8.25.0': + dependencies: + '@sentry/types': 8.25.0 + '@sentry/utils': 8.25.0 + '@sentry/hub@5.30.0': dependencies: '@sentry/types': 5.30.0 @@ -24768,6 +24850,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@sentry/react@8.25.0(react@18.3.1)': + dependencies: + '@sentry/browser': 8.25.0 + '@sentry/core': 8.25.0 + '@sentry/types': 8.25.0 + '@sentry/utils': 8.25.0 + hoist-non-react-statics: 3.3.2 + react: 18.3.1 + '@sentry/tracing@5.30.0': dependencies: '@sentry/hub': 5.30.0 @@ -24778,11 +24869,17 @@ snapshots: '@sentry/types@5.30.0': {} + '@sentry/types@8.25.0': {} + '@sentry/utils@5.30.0': dependencies: '@sentry/types': 5.30.0 tslib: 1.14.1 + '@sentry/utils@8.25.0': + dependencies: + '@sentry/types': 8.25.0 + '@sinclair/typebox@0.27.8': {} '@sindresorhus/is@0.14.0': {}