mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2026-03-13 10:00:26 +08:00
chore: add open telemetry context propagation to http client
This commit is contained in:
@@ -73,6 +73,9 @@
|
||||
"@mui/icons-material": "^5.11.11",
|
||||
"@mui/material": "^6.0.0",
|
||||
"@mui/x-date-pickers-pro": "^6.18.2",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/core": "^2.5.1",
|
||||
"@opentelemetry/sdk-trace-web": "^2.5.1",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||
"@radix-ui/react-avatar": "^1.1.3",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
|
||||
69
pnpm-lock.yaml
generated
69
pnpm-lock.yaml
generated
@@ -83,6 +83,15 @@ importers:
|
||||
'@mui/x-date-pickers-pro':
|
||||
specifier: ^6.18.2
|
||||
version: 6.20.2(@emotion/react@11.14.0(@types/react@18.3.21)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.21)(react@18.3.1))(@types/react@18.3.21)(react@18.3.1))(@mui/material@6.4.11(@emotion/react@11.14.0(@types/react@18.3.21)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.21)(react@18.3.1))(@types/react@18.3.21)(react@18.3.1))(@types/react@18.3.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@6.4.11(@emotion/react@11.14.0(@types/react@18.3.21)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.21)(react@18.3.1))(@types/react@18.3.21)(react@18.3.1))(@types/react@18.3.21)(react@18.3.1))(@types/react@18.3.21)(date-fns@4.1.0)(dayjs@1.11.13)(luxon@3.6.1)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@opentelemetry/api':
|
||||
specifier: ^1.9.0
|
||||
version: 1.9.0
|
||||
'@opentelemetry/core':
|
||||
specifier: ^2.5.1
|
||||
version: 2.5.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-trace-web':
|
||||
specifier: ^2.5.1
|
||||
version: 2.5.1(@opentelemetry/api@1.9.0)
|
||||
'@radix-ui/react-alert-dialog':
|
||||
specifier: ^1.1.15
|
||||
version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.21))(@types/react@18.3.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@@ -2486,6 +2495,38 @@ packages:
|
||||
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
|
||||
engines: {node: '>=12.4.0'}
|
||||
|
||||
'@opentelemetry/api@1.9.0':
|
||||
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
'@opentelemetry/core@2.5.1':
|
||||
resolution: {integrity: sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA==}
|
||||
engines: {node: ^18.19.0 || >=20.6.0}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': '>=1.0.0 <1.10.0'
|
||||
|
||||
'@opentelemetry/resources@2.5.1':
|
||||
resolution: {integrity: sha512-BViBCdE/GuXRlp9k7nS1w6wJvY5fnFX5XvuEtWsTAOQFIO89Eru7lGW3WbfbxtCuZ/GbrJfAziXG0w0dpxL7eQ==}
|
||||
engines: {node: ^18.19.0 || >=20.6.0}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': '>=1.3.0 <1.10.0'
|
||||
|
||||
'@opentelemetry/sdk-trace-base@2.5.1':
|
||||
resolution: {integrity: sha512-iZH3Gw8cxQn0gjpOjJMmKLd9GIaNh/E3v3ST67vyzLSxHBs14HsG4dy7jMYyC5WXGdBVEcM7U/XTF5hCQxjDMw==}
|
||||
engines: {node: ^18.19.0 || >=20.6.0}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': '>=1.3.0 <1.10.0'
|
||||
|
||||
'@opentelemetry/sdk-trace-web@2.5.1':
|
||||
resolution: {integrity: sha512-4PWFtMJ5nqWMP2YqgKjcMlQlUeN1imUYSXdhy6Xl/3bnO0/Ryo5Y3/kWG8T66uMHo2RpTQLloZjoQACKdbHbxg==}
|
||||
engines: {node: ^18.19.0 || >=20.6.0}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': '>=1.0.0 <1.10.0'
|
||||
|
||||
'@opentelemetry/semantic-conventions@1.39.0':
|
||||
resolution: {integrity: sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@parcel/watcher-android-arm64@2.5.1':
|
||||
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
@@ -11944,6 +11985,34 @@ snapshots:
|
||||
|
||||
'@nolyfill/is-core-module@1.0.39': {}
|
||||
|
||||
'@opentelemetry/api@1.9.0': {}
|
||||
|
||||
'@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/semantic-conventions': 1.39.0
|
||||
|
||||
'@opentelemetry/resources@2.5.1(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/semantic-conventions': 1.39.0
|
||||
|
||||
'@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/semantic-conventions': 1.39.0
|
||||
|
||||
'@opentelemetry/sdk-trace-web@2.5.1(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-trace-base': 2.5.1(@opentelemetry/api@1.9.0)
|
||||
|
||||
'@opentelemetry/semantic-conventions@1.39.0': {}
|
||||
|
||||
'@parcel/watcher-android-arm64@2.5.1':
|
||||
optional: true
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import axios, { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
|
||||
import { Span } from '@opentelemetry/api';
|
||||
import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||
import dayjs from 'dayjs';
|
||||
import { omit } from 'lodash-es';
|
||||
import { nanoid } from 'nanoid';
|
||||
@@ -76,6 +77,7 @@ import { database_blob } from '@/proto/database_blob';
|
||||
import { getAppFlowyFileUploadUrl, getAppFlowyFileUrl } from '@/utils/file-storage-url';
|
||||
import { Log } from '@/utils/log';
|
||||
import { hasProAccessFromPlans } from '@/utils/subscription';
|
||||
import { endHttpSpan, startHttpSpan } from '@/utils/telemetry';
|
||||
|
||||
export * from './gotrue';
|
||||
|
||||
@@ -266,6 +268,39 @@ export function initAPIService(config: AFCloudConfig) {
|
||||
|
||||
initGrantService(config.gotrueURL);
|
||||
|
||||
// OpenTelemetry: inject W3C Trace Context headers into outgoing requests
|
||||
axiosInstance.interceptors.request.use((config) => {
|
||||
const method = config.method?.toUpperCase() || 'UNKNOWN';
|
||||
const url = config.url || '';
|
||||
const headers = config.headers as unknown as Record<string, string>;
|
||||
const span = startHttpSpan(method, url, headers);
|
||||
|
||||
(config as InternalAxiosRequestConfig & { _otelSpan?: Span })._otelSpan = span;
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
axiosInstance.interceptors.response.use(
|
||||
(response) => {
|
||||
const span = (response.config as InternalAxiosRequestConfig & { _otelSpan?: Span })._otelSpan;
|
||||
|
||||
if (span) {
|
||||
endHttpSpan(span);
|
||||
}
|
||||
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
const span = (error?.config as InternalAxiosRequestConfig & { _otelSpan?: Span })?._otelSpan;
|
||||
|
||||
if (span) {
|
||||
endHttpSpan(span, true);
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
axiosInstance.interceptors.request.use(
|
||||
async (config) => {
|
||||
const token = getTokenParsed();
|
||||
|
||||
52
src/utils/telemetry.ts
Normal file
52
src/utils/telemetry.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { context, propagation, Span, SpanStatusCode, trace } from '@opentelemetry/api';
|
||||
import { W3CTraceContextPropagator } from '@opentelemetry/core';
|
||||
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
|
||||
|
||||
const TRACER_NAME = 'appflowy-web';
|
||||
|
||||
let initialized = false;
|
||||
|
||||
function ensureInitialized() {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
const provider = new WebTracerProvider();
|
||||
|
||||
provider.register({
|
||||
propagator: new W3CTraceContextPropagator(),
|
||||
});
|
||||
}
|
||||
|
||||
function getTracer() {
|
||||
ensureInitialized();
|
||||
return trace.getTracer(TRACER_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a span for an HTTP request and injects W3C Trace Context headers
|
||||
* (`traceparent`) into the provided headers record.
|
||||
*
|
||||
* Returns the span so the caller can end it after the response arrives.
|
||||
*/
|
||||
export function startHttpSpan(method: string, url: string, headers: Record<string, string>): Span {
|
||||
const tracer = getTracer();
|
||||
const span = tracer.startSpan(`HTTP ${method.toUpperCase()} ${url}`);
|
||||
|
||||
// Inject traceparent header into the carrier (headers object)
|
||||
const ctx = trace.setSpan(context.active(), span);
|
||||
|
||||
propagation.inject(ctx, headers);
|
||||
|
||||
return span;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends an HTTP span, optionally marking it as an error.
|
||||
*/
|
||||
export function endHttpSpan(span: Span, error?: boolean) {
|
||||
if (error) {
|
||||
span.setStatus({ code: SpanStatusCode.ERROR });
|
||||
}
|
||||
|
||||
span.end();
|
||||
}
|
||||
Reference in New Issue
Block a user