From 0ff03adab34730a7a1a336093b5d3c3bdbb06b74 Mon Sep 17 00:00:00 2001 From: ukorvl Date: Wed, 12 Jun 2024 22:24:13 +0400 Subject: [PATCH] add opentelemetry #178 --- .github/workflows/build.yaml | 5 +- package-lock.json | 208 +++++++++++++++--- package.json | 3 +- public/runtime-config.toml | 5 +- src/App.tsx | 8 +- src/api/common/apiClient.ts | 8 + src/datadog.ts | 40 ---- .../routing/constants/routesConfig.tsx | 1 - src/index.tsx | 4 +- src/opentelemetry.ts | 58 +++++ src/types/runtime-config.d.ts | 5 +- src/utils/runtimeConfig/checkRuntimeConfig.ts | 4 - 12 files changed, 254 insertions(+), 95 deletions(-) delete mode 100644 src/datadog.ts create mode 100644 src/opentelemetry.ts diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 3b326a11..2557e4e5 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -51,10 +51,7 @@ jobs: PROOFMARKET_TOOLCHAIN_REPO = "${{ secrets.PROOFMARKET_TOOLCHAIN_REPO }}" CIRCUIT_DEVELOPER_GUIDE_URL = "${{ secrets.CIRCUIT_DEVELOPER_GUIDE_URL }}" API_RESPONSE_WAIT_TIMEOUT = "${{ secrets.API_RESPONSE_WAIT_TIMEOUT }}" - DATADOG_APPLICATION_ID = "${{ secrets.DATADOG_APPLICATION_ID }}" - DATADOG_CLIENT_TOKEN = "${{ secrets.DATADOG_CLIENT_TOKEN }}" - DATADOG_SITE = "${{ secrets.DATADOG_SITE }}" - DATADOG_SERVICE_NAME = "${{ secrets.DATADOG_SERVICE_NAME }}" + OTEL_TRACE_EXPORTER_URL = "${{ secrets.OTEL_TRACE_EXPORTER_URL }}" ' echo "$config" > runtime-config.toml echo runtime-config.toml diff --git a/package-lock.json b/package-lock.json index 071e3eba..31fdd6ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,11 @@ "name": "proof-market.frontend", "version": "0.1.9", "dependencies": { - "@datadog/browser-rum": "^5.17.1", "@loadable/component": "^5.15.3", "@nilfoundation/react-components": "^0.8.3", "@nilfoundation/ui-kit": "^2.1.3", + "@opentelemetry/exporter-trace-otlp-http": "^0.51.1", + "@opentelemetry/sdk-trace-web": "^1.24.1", "@reduxjs/toolkit": "^1.8.5", "baseui": "^13.0.0", "bootstrap-sass": "^3.4.3", @@ -1932,36 +1933,6 @@ "w3c-keyname": "^2.2.4" } }, - "node_modules/@datadog/browser-core": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/@datadog/browser-core/-/browser-core-5.17.1.tgz", - "integrity": "sha512-OsdjeRCCT1U9+ddygqg3PkqHOkkk7Vr2tI3U8xgZQyiqCzCuooDHebq/40UMP6mf9OVETDkqlSWQ/ouqvFKjUQ==" - }, - "node_modules/@datadog/browser-rum": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/@datadog/browser-rum/-/browser-rum-5.17.1.tgz", - "integrity": "sha512-BiXA47pBTCjifQxseEVDsrKzLByMdhcp223z8LpyJfjAjMsJad0qxJV5EC+sXANN6yXvpCiC0IjMWNpNHRqpIA==", - "dependencies": { - "@datadog/browser-core": "5.17.1", - "@datadog/browser-rum-core": "5.17.1" - }, - "peerDependencies": { - "@datadog/browser-logs": "5.17.1" - }, - "peerDependenciesMeta": { - "@datadog/browser-logs": { - "optional": true - } - } - }, - "node_modules/@datadog/browser-rum-core": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/@datadog/browser-rum-core/-/browser-rum-core-5.17.1.tgz", - "integrity": "sha512-tb+kzIR5C5EHeCXLiTW5bQTm4T1KsuBUE+Xdf1MJJlpcPKtiqnNKS2gJ1OJQpfuOHWwWrGsH2tIO7fZUtALqAg==", - "dependencies": { - "@datadog/browser-core": "5.17.1" - } - }, "node_modules/@date-io/core": { "version": "2.17.0", "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.17.0.tgz", @@ -2658,6 +2629,177 @@ "node": ">= 8" } }, + "node_modules/@opentelemetry/api": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.8.0.tgz", + "integrity": "sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.51.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.51.1.tgz", + "integrity": "sha512-E3skn949Pk1z2XtXu/lxf6QAZpawuTM/IUEXcAzpiUkTd73Hmvw26FiN3cJuTmkpM5hZzHwkomVdtrh/n/zzwA==", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/core": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.24.1.tgz", + "integrity": "sha512-wMSGfsdmibI88K9wB498zXY04yThPexo8jvwNNlm542HZB7XrrMRBbAyKJqG8qDRJwIBdBrPMi4V9ZPW/sqrcg==", + "dependencies": { + "@opentelemetry/semantic-conventions": "1.24.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.9.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.51.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.51.1.tgz", + "integrity": "sha512-n+LhLPsX07URh+HhV2SHVSvz1t4G/l/CE5BjpmhAPqeTceFac1VpyQkavWEJbvnK5bUEXijWt4LxAxFpt2fXyw==", + "dependencies": { + "@opentelemetry/core": "1.24.1", + "@opentelemetry/otlp-exporter-base": "0.51.1", + "@opentelemetry/otlp-transformer": "0.51.1", + "@opentelemetry/resources": "1.24.1", + "@opentelemetry/sdk-trace-base": "1.24.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.51.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.51.1.tgz", + "integrity": "sha512-UYlnOYyDdzo1Gw559EHCzru0RwhvuXCwoH8jGo9J4gO1TE58GjnEmIjomMsKBCym3qWNJfIQXw+9SZCV0DdQNg==", + "dependencies": { + "@opentelemetry/core": "1.24.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.51.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.51.1.tgz", + "integrity": "sha512-OppYOXwV9LQqqtYUCywqoOqX/JT9LQ5/FMuPZ//eTkvuHdUC4ZMwz2c6uSoT2R90GWvvGnF1iEqTGyTT3xAt2Q==", + "dependencies": { + "@opentelemetry/api-logs": "0.51.1", + "@opentelemetry/core": "1.24.1", + "@opentelemetry/resources": "1.24.1", + "@opentelemetry/sdk-logs": "0.51.1", + "@opentelemetry/sdk-metrics": "1.24.1", + "@opentelemetry/sdk-trace-base": "1.24.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.9.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.24.1.tgz", + "integrity": "sha512-cyv0MwAaPF7O86x5hk3NNgenMObeejZFLJJDVuSeSMIsknlsj3oOZzRv3qSzlwYomXsICfBeFFlxwHQte5mGXQ==", + "dependencies": { + "@opentelemetry/core": "1.24.1", + "@opentelemetry/semantic-conventions": "1.24.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.9.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.51.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.51.1.tgz", + "integrity": "sha512-ULQQtl82b673PpZc5/0EtH4V+BrwVOgKJZEB7tYZnGTG3I98tQVk89S9/JSixomDr++F4ih+LSJTCqIKBz+MQQ==", + "dependencies": { + "@opentelemetry/core": "1.24.1", + "@opentelemetry/resources": "1.24.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.9.0", + "@opentelemetry/api-logs": ">=0.39.1" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.24.1.tgz", + "integrity": "sha512-FrAqCbbGao9iKI+Mgh+OsC9+U2YMoXnlDHe06yH7dvavCKzE3S892dGtX54+WhSFVxHR/TMRVJiK/CV93GR0TQ==", + "dependencies": { + "@opentelemetry/core": "1.24.1", + "@opentelemetry/resources": "1.24.1", + "lodash.merge": "^4.6.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.9.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.24.1.tgz", + "integrity": "sha512-zz+N423IcySgjihl2NfjBf0qw1RWe11XIAWVrTNOSSI6dtSPJiVom2zipFB2AEEtJWpv0Iz6DY6+TjnyTV5pWg==", + "dependencies": { + "@opentelemetry/core": "1.24.1", + "@opentelemetry/resources": "1.24.1", + "@opentelemetry/semantic-conventions": "1.24.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.9.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-web": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-web/-/sdk-trace-web-1.24.1.tgz", + "integrity": "sha512-0w+aKRai9VREeo3VrtW+hcbrE2Fl/uKL7G+oXgRNf6pI9QLaEGuEzUTX+oxXVPBadzjOd+5dqCHYdX7UeVjzwA==", + "dependencies": { + "@opentelemetry/core": "1.24.1", + "@opentelemetry/sdk-trace-base": "1.24.1", + "@opentelemetry/semantic-conventions": "1.24.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.9.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz", + "integrity": "sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw==", + "engines": { + "node": ">=14" + } + }, "node_modules/@redux-saga/core": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.2.3.tgz", @@ -7268,9 +7410,7 @@ "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "peer": true + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/log-update": { "version": "5.0.1", diff --git a/package.json b/package.json index 34dc3a8d..0cfb45a7 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,11 @@ "version": "0.1.9", "private": true, "dependencies": { - "@datadog/browser-rum": "^5.17.1", "@loadable/component": "^5.15.3", "@nilfoundation/react-components": "^0.8.3", "@nilfoundation/ui-kit": "^2.1.3", + "@opentelemetry/exporter-trace-otlp-http": "^0.51.1", + "@opentelemetry/sdk-trace-web": "^1.24.1", "@reduxjs/toolkit": "^1.8.5", "baseui": "^13.0.0", "bootstrap-sass": "^3.4.3", diff --git a/public/runtime-config.toml b/public/runtime-config.toml index 595eaea5..7bf146a6 100644 --- a/public/runtime-config.toml +++ b/public/runtime-config.toml @@ -8,7 +8,4 @@ GA_TRACKING_ID = "" PROOFMARKET_TOOLCHAIN_REPO = "" CIRCUIT_DEVELOPER_GUIDE_URL = "" API_RESPONSE_WAIT_TIMEOUT = "" -DATADOG_APPLICATION_ID = "" -DATADOG_CLIENT_TOKEN = "" -DATADOG_SITE = "" -DATADOG_SERVICE_NAME = "" +OTEL_TRACE_EXPORTER_URL = "" diff --git a/src/App.tsx b/src/App.tsx index 0104e07b..798c06ad 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -15,6 +15,7 @@ import { useBreakpoint } from './features/shared'; import { MobileRouter } from './features/mobile'; import { NotificationContainer } from './features/notifications'; import { BREAKPOINT } from './styles/Breakpoint'; +import { exportErrorToOtelCollector } from './opentelemetry'; const baseDocumentTitle = getRuntimeConfigOrThrow().SITE_DEFAULT_TITLE; @@ -24,8 +25,13 @@ const baseDocumentTitle = getRuntimeConfigOrThrow().SITE_DEFAULT_TITLE; function App(): ReactElement { const bp = useBreakpoint(); + throw new Error('Test error'); + return ( - }> + } + onError={e => exportErrorToOtelCollector(e)} + > { request.headers.set('Authorization', `Bearer ${LocalStorageAPI.getItem('userToken')}`); }, ], + beforeError: [ + error => { + exportErrorToOtelCollector(error); + + return error; + }, + ], }, timeout: Number(API_RESPONSE_WAIT_TIMEOUT) ?? defaultTimeout, ...options, diff --git a/src/datadog.ts b/src/datadog.ts deleted file mode 100644 index e301c1bd..00000000 --- a/src/datadog.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @file Datadog rum configuration. - * @copyright Yury Korotovskikh - */ - -import { datadogRum } from '@datadog/browser-rum'; -import { getRuntimeConfigOrThrow } from './utils'; - -/** - * Configures Datadog Real User Monitoring (RUM) for the application. - * - * @see {@link https://docs.datadoghq.com/real_user_monitoring/browser/} - */ -export const configureDatadogRUM = (): void => { - // if (!import.meta.env.PROD) { - // return; - // } - - const { DATADOG_APPLICATION_ID, DATADOG_CLIENT_TOKEN, DATADOG_SERVICE_NAME, DATADOG_SITE } = - getRuntimeConfigOrThrow(); - - if (!DATADOG_APPLICATION_ID || !DATADOG_CLIENT_TOKEN || !DATADOG_SERVICE_NAME || !DATADOG_SITE) { - return; - } - - datadogRum.init({ - applicationId: DATADOG_APPLICATION_ID, - clientToken: DATADOG_CLIENT_TOKEN, - site: DATADOG_SITE, - service: DATADOG_SERVICE_NAME, - env: 'production', - sessionSampleRate: 100, - sessionReplaySampleRate: 100, - trackResources: false, - trackLongTasks: false, - trackUserInteractions: false, - silentMultipleInit: true, - allowFallbackToLocalStorage: true, - }); -}; diff --git a/src/features/routing/constants/routesConfig.tsx b/src/features/routing/constants/routesConfig.tsx index f23e7e86..1b39a35c 100644 --- a/src/features/routing/constants/routesConfig.tsx +++ b/src/features/routing/constants/routesConfig.tsx @@ -3,7 +3,6 @@ * @copyright Yury Korotovskikh */ -import { lazy } from 'react'; import { Navigate } from 'react-router-dom'; import type { RouteObject } from 'react-router-dom'; import { RouterParam } from '@/enums'; diff --git a/src/index.tsx b/src/index.tsx index 077e1744..d161e789 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -15,15 +15,15 @@ import { Provider as StyletronProvider } from 'styletron-react'; import { BaseProvider } from 'baseui'; import App from './App'; import { store } from './redux'; -import { configureDatadogRUM } from './datadog'; +import { initOpentelemetry } from './opentelemetry'; import { reportWebVitals } from './reportWebVitals'; import configureGA from './ga'; //import * as serviceWorkerRegistration from './serviceWorkerRegistration'; import { checkRuntimeConfig } from './utils'; checkRuntimeConfig(); -configureDatadogRUM(); configureGA(); +initOpentelemetry(); const engine = new Styletron(); const { theme } = createTheme(engine, { enableDefaultFonts: false }); diff --git a/src/opentelemetry.ts b/src/opentelemetry.ts new file mode 100644 index 00000000..467b0fb4 --- /dev/null +++ b/src/opentelemetry.ts @@ -0,0 +1,58 @@ +/** + * @file Opentelemtry configuration. + * @copyright Yury Korotovskikh + */ + +import { BatchSpanProcessor, WebTracerProvider } from '@opentelemetry/sdk-trace-web'; +import { OTLPTraceExporter as HttpTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; +import type { OTLPExporterNodeConfigBase } from '@opentelemetry/otlp-exporter-base'; +import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; +import { Resource } from '@opentelemetry/resources'; +import { getRuntimeConfigOrThrow } from './utils'; +import packageJson from '../package.json'; + +const { OTEL_TRACE_EXPORTER_URL } = getRuntimeConfigOrThrow(); + +const exporterOptions = { + url: OTEL_TRACE_EXPORTER_URL, +} satisfies OTLPExporterNodeConfigBase; + +const provider = new WebTracerProvider({ + resource: new Resource({ + [SEMRESATTRS_SERVICE_NAME]: packageJson.name, + }), +}); + +const exporter = new HttpTraceExporter(exporterOptions); + +// eslint-disable-next-line jsdoc/require-jsdoc +export const initOpentelemetry = () => { + provider.addSpanProcessor(new BatchSpanProcessor(exporter)); + provider.register(); +}; + +/** + * Register error in opentelemetry. + * + * @param error - Error to be reported. + */ +export const exportErrorToOtelCollector = (error: Error) => { + const span = provider.getTracer('default').startSpan('Operational error'); + + span.setAttribute('message', error.message); + span.recordException(error); + span.setStatus({ code: 2, message: error.message }); + + span.end(); +}; + +// Register global error handler to report uncaught errors. +window.onerror = function (message, _source, _lineno, _colno, error) { + const span = provider.getTracer('default').startSpan('Uncaught error'); + + error && span.recordException(error); + typeof message === 'string' && span.setAttribute('message', message); + span.setStatus({ code: 2, message: typeof message === 'string' ? message : '' }); + + span.end(); +}; diff --git a/src/types/runtime-config.d.ts b/src/types/runtime-config.d.ts index 743e26d6..f13d60ed 100644 --- a/src/types/runtime-config.d.ts +++ b/src/types/runtime-config.d.ts @@ -16,10 +16,7 @@ const keys = [ 'GA_TRACKING_ID', 'CIRCUIT_DEVELOPER_GUIDE_URL', 'API_RESPONSE_WAIT_TIMEOUT', - 'DATADOG_APPLICATION_ID', - 'DATADOG_CLIENT_TOKEN', - 'DATADOG_SITE', - 'DATADOG_SERVICE_NAME', + 'OTEL_TRACE_EXPORTER_URL', ] as const; type RuntimConfigKeys = (typeof keys)[number]; diff --git a/src/utils/runtimeConfig/checkRuntimeConfig.ts b/src/utils/runtimeConfig/checkRuntimeConfig.ts index c6684d10..0633fe13 100644 --- a/src/utils/runtimeConfig/checkRuntimeConfig.ts +++ b/src/utils/runtimeConfig/checkRuntimeConfig.ts @@ -15,10 +15,6 @@ const requiredEnvs: Array = [ 'PROOFMARKET_TOOLCHAIN_REPO', 'SITE_DEFAULT_TITLE', 'CIRCUIT_DEVELOPER_GUIDE_URL', - 'DATADOG_APPLICATION_ID', - 'DATADOG_CLIENT_TOKEN', - 'DATADOG_SITE', - 'DATADOG_SERVICE_NAME', ]; /**