diff --git a/package.json b/package.json index 984f282c8a..2dc623ca09 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@connectrpc/connect-fastify": "^1.6.1", "@connectrpc/connect-node": "^1.6.1", "@dailydotdev/graphql-redis-subscriptions": "^2.4.3", + "@dailydotdev/node-common": "^0.0.8", "@dailydotdev/schema": "0.2.74", "@dailydotdev/ts-ioredis-pool": "^1.0.2", "@fastify/cookie": "^11.0.2", @@ -47,7 +48,7 @@ "@fastify/swagger": "^9.6.1", "@fastify/swagger-ui": "^5.2.5", "@google-cloud/bigquery": "^8.1.1", - "@google-cloud/pino-logging-gcp-config": "^1.3.1", + "@google-cloud/pino-logging-gcp-config": "^1.3.3", "@google-cloud/pubsub": "^5.2.0", "@google-cloud/storage": "^7.18.0", "@graphql-tools/schema": "^10.0.30", @@ -142,7 +143,7 @@ "parsecurrency": "^1.1.1", "pg": "^8.16.3", "pg-query-stream": "^4.10.3", - "pino": "^10.1.0", + "pino": "^10.3.1", "rate-limiter-flexible": "^9.0.1", "reflect-metadata": "^0.2.2", "retry": "^0.13.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7cbdab5359..87bcf5468a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,6 +34,9 @@ importers: '@dailydotdev/graphql-redis-subscriptions': specifier: ^2.4.3 version: 2.4.3(graphql-subscriptions@3.0.0(graphql@16.12.0)) + '@dailydotdev/node-common': + specifier: ^0.0.8 + version: 0.0.8 '@dailydotdev/schema': specifier: 0.2.74 version: 0.2.74(@bufbuild/protobuf@1.10.0) @@ -65,8 +68,8 @@ importers: specifier: ^8.1.1 version: 8.1.1 '@google-cloud/pino-logging-gcp-config': - specifier: ^1.3.1 - version: 1.3.1 + specifier: ^1.3.3 + version: 1.3.3 '@google-cloud/pubsub': specifier: ^5.2.0 version: 5.2.0 @@ -350,8 +353,8 @@ importers: specifier: ^4.10.3 version: 4.10.3(pg@8.16.3) pino: - specifier: ^10.1.0 - version: 10.1.0 + specifier: ^10.3.1 + version: 10.3.1 rate-limiter-flexible: specifier: ^9.0.1 version: 9.0.1 @@ -792,6 +795,9 @@ packages: peerDependencies: graphql-subscriptions: ^1.0.0 || ^2.0.0 + '@dailydotdev/node-common@0.0.8': + resolution: {integrity: sha512-9p8DGZniL1kfLkY4ICf0LlkH5wHwgInAqkgBhbIuP/L0TmwSCTFQoMzw4zoJgUH7LWb0kmDQ+JkNXaBNzUnj5g==} + '@dailydotdev/schema@0.2.74': resolution: {integrity: sha512-RKWNzhR+KFgCcc1Kl82UiqoXx6nUtCQ1xV2yH2YwRGeAU+fhx1PCEzEYl1Vc98gz65oxaQhOZRF+7G1+04mIMA==} peerDependencies: @@ -837,6 +843,9 @@ packages: '@fastify/ajv-compiler@4.0.1': resolution: {integrity: sha512-DxrBdgsjNLP0YM6W5Hd6/Fmj43S8zMKiFJYgi+Ri3htTGAowPVG/tG1wpnWLMjufEnehRivUCKZ1pLDIoZdTuw==} + '@fastify/ajv-compiler@4.0.5': + resolution: {integrity: sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==} + '@fastify/busboy@2.1.0': resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==} engines: {node: '>=14'} @@ -919,9 +928,9 @@ packages: resolution: {integrity: sha512-g5nmMnzC+94kBxOKkLGpK1ikvolTFCC3s2qtE4F+1EuArcJ7HHC23RDQVt3Ra3CqpUYZ+oXNKZ8n5Cn5yug8DA==} engines: {node: '>=18'} - '@google-cloud/pino-logging-gcp-config@1.3.1': - resolution: {integrity: sha512-hsxdbrtx4pmvzSr9VSZTD8n8JxoCfDbr/RTtfhWcW9H+XtAwJF3ojH92oavjQxAxh+LasbVUum/aFVQnU0rZYA==} - engines: {node: '>=20'} + '@google-cloud/pino-logging-gcp-config@1.3.3': + resolution: {integrity: sha512-vqUYcPL6ZhOntt/P8ny7kI++jtam5EZuY1fjGLMwIwj66ZpFZgXzLqTl2nQRm/URzBsktvYjo9c7Lp6BEciXZA==} + engines: {node: '>=20.20.0'} '@google-cloud/precise-date@5.0.0': resolution: {integrity: sha512-9h0Gvw92EvPdE8AK8AgZPbMnH5ftDyPtKm7/KUfcJVaPEPjwGDsJd1QV0H8esBDV4II41R/2lDWH1epBqIoKUw==} @@ -1348,6 +1357,10 @@ packages: resolution: {integrity: sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==} engines: {node: '>=14'} + '@opentelemetry/semantic-conventions@1.40.0': + resolution: {integrity: sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==} + engines: {node: '>=14'} + '@opentelemetry/sql-common@0.41.2': resolution: {integrity: sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==} engines: {node: ^18.19.0 || >=20.6.0} @@ -2799,6 +2812,9 @@ packages: fastify@5.6.2: resolution: {integrity: sha512-dPugdGnsvYkBlENLhCgX8yhyGCsCPrpA8lFWbTNU428l+YOnLgYHR69hzV8HWPC79n536EqzqQtvhtdaCE0dKg==} + fastify@5.7.4: + resolution: {integrity: sha512-e6l5NsRdaEP8rdD8VR0ErJASeyaRbzXYpmkrpr2SuvuMq6Si3lvsaVy5C+7gLanEkvjpMDzBXWE5HPeb/hgTxA==} + fastparallel@2.4.1: resolution: {integrity: sha512-qUmhxPgNHmvRjZKBFUNI0oZuuH9OlSIOXmJ98lhKPxMZZ7zS/Fi0wRHOihDSz0R1YiIOjxzOY4bq65YTcdBi2Q==} @@ -4147,9 +4163,6 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - pino-abstract-transport@2.0.0: - resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} - pino-abstract-transport@3.0.0: resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} @@ -4160,8 +4173,8 @@ packages: pino-std-serializers@7.0.0: resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} - pino@10.1.0: - resolution: {integrity: sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==} + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} hasBin: true pirates@4.0.6: @@ -4710,8 +4723,9 @@ packages: peerDependencies: tslib: ^2 - thread-stream@3.1.0: - resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + thread-stream@4.0.0: + resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} + engines: {node: '>=20'} tiny-lru@11.4.5: resolution: {integrity: sha512-hkcz3FjNJfKXjV4mjQ1OrXSLAehg8Hw+cEZclOVT+5c/cWQWImQ9wolzTjth+dmmDe++p3bme3fTxz6Q4Etsqw==} @@ -5513,6 +5527,19 @@ snapshots: transitivePeerDependencies: - supports-color + '@dailydotdev/node-common@0.0.8': + dependencies: + '@google-cloud/pino-logging-gcp-config': 1.3.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.40.0 + fastify: 5.7.4 + fastify-plugin: 5.1.0 + pino: 10.3.1 + transitivePeerDependencies: + - encoding + - supports-color + '@dailydotdev/schema@0.2.74(@bufbuild/protobuf@1.10.0)': dependencies: '@bufbuild/protobuf': 1.10.0 @@ -5560,6 +5587,12 @@ snapshots: ajv-formats: 3.0.1(ajv@8.17.1) fast-uri: 3.0.5 + '@fastify/ajv-compiler@4.0.5': + dependencies: + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + fast-uri: 3.0.5 + '@fastify/busboy@2.1.0': {} '@fastify/cookie@11.0.2': @@ -5752,11 +5785,11 @@ snapshots: dependencies: extend: 3.0.2 - '@google-cloud/pino-logging-gcp-config@1.3.1': + '@google-cloud/pino-logging-gcp-config@1.3.3': dependencies: '@google-cloud/logging': 11.2.1 eventid: 2.0.1 - pino: 10.1.0 + pino: 10.3.1 transitivePeerDependencies: - encoding - supports-color @@ -6367,6 +6400,8 @@ snapshots: '@opentelemetry/semantic-conventions@1.39.0': {} + '@opentelemetry/semantic-conventions@1.40.0': {} + '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -7950,7 +7985,25 @@ snapshots: fast-json-stringify: 6.1.1 find-my-way: 9.1.0 light-my-request: 6.1.0 - pino: 10.1.0 + pino: 10.3.1 + process-warning: 5.0.0 + rfdc: 1.4.1 + secure-json-parse: 4.1.0 + semver: 7.7.3 + toad-cache: 3.7.0 + + fastify@5.7.4: + dependencies: + '@fastify/ajv-compiler': 4.0.5 + '@fastify/error': 4.0.0 + '@fastify/fast-json-stringify-compiler': 5.0.1 + '@fastify/proxy-addr': 5.0.0 + abstract-logging: 2.0.1 + avvio: 9.0.0 + fast-json-stringify: 6.1.1 + find-my-way: 9.1.0 + light-my-request: 6.1.0 + pino: 10.3.1 process-warning: 5.0.0 rfdc: 1.4.1 secure-json-parse: 4.1.0 @@ -9553,10 +9606,6 @@ snapshots: picomatch@2.3.1: {} - pino-abstract-transport@2.0.0: - dependencies: - split2: 4.2.0 - pino-abstract-transport@3.0.0: dependencies: split2: 4.2.0 @@ -9579,19 +9628,19 @@ snapshots: pino-std-serializers@7.0.0: {} - pino@10.1.0: + pino@10.3.1: dependencies: '@pinojs/redact': 0.4.0 atomic-sleep: 1.0.0 on-exit-leak-free: 2.1.2 - pino-abstract-transport: 2.0.0 + pino-abstract-transport: 3.0.0 pino-std-serializers: 7.0.0 process-warning: 5.0.0 quick-format-unescaped: 4.0.4 real-require: 0.2.0 safe-stable-stringify: 2.4.3 sonic-boom: 4.0.1 - thread-stream: 3.1.0 + thread-stream: 4.0.0 pirates@4.0.6: {} @@ -10126,7 +10175,7 @@ snapshots: dependencies: tslib: 2.8.1 - thread-stream@3.1.0: + thread-stream@4.0.0: dependencies: real-require: 0.2.0 diff --git a/src/auth.ts b/src/auth.ts index 71bc001a24..727a1210d4 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -5,7 +5,7 @@ import jwt from 'jsonwebtoken'; import { Roles } from './roles'; import { cookies } from './cookies'; import * as fs from 'fs'; -import { runInSpan } from './telemetry'; +import { runInSpan } from '@dailydotdev/node-common/telemetry'; const INTERNAL_AUTH_MAX_AGE_MS = 5000; // 5 seconds diff --git a/src/background.ts b/src/background.ts index 4ce2a5ff64..5ce82fe376 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,4 +1,4 @@ -import { runInRootSpan } from './telemetry'; +import { runInRootSpan } from '@dailydotdev/node-common/telemetry'; import 'reflect-metadata'; import { PubSub } from '@google-cloud/pubsub'; diff --git a/src/commands/personalizedDigest.ts b/src/commands/personalizedDigest.ts index e9a1a5b074..25c017ff72 100644 --- a/src/commands/personalizedDigest.ts +++ b/src/commands/personalizedDigest.ts @@ -1,4 +1,4 @@ -import { runInRootSpan } from '../telemetry'; +import { runInRootSpan } from '@dailydotdev/node-common/telemetry'; import 'reflect-metadata'; import { PubSub } from '@google-cloud/pubsub'; diff --git a/src/commands/workerJob.ts b/src/commands/workerJob.ts index 88258072df..83446babae 100644 --- a/src/commands/workerJob.ts +++ b/src/commands/workerJob.ts @@ -1,4 +1,4 @@ -import { runInRootSpan } from '../telemetry'; +import { runInRootSpan } from '@dailydotdev/node-common/telemetry'; import 'reflect-metadata'; import { PubSub } from '@google-cloud/pubsub'; diff --git a/src/common/feedGenerator.ts b/src/common/feedGenerator.ts index 247f11ecc9..ca23075329 100644 --- a/src/common/feedGenerator.ts +++ b/src/common/feedGenerator.ts @@ -28,7 +28,7 @@ import { } from '../schema/common'; import graphorm from '../graphorm'; import { mapArrayToOjbect } from './object'; -import { runInSpan } from '../telemetry'; +import { runInSpan } from '@dailydotdev/node-common/telemetry'; import { whereVordrFilter } from './vordr'; import { baseFeedConfig, type FeedFlagsFilters } from '../integrations/feed'; import type { FeedResponse } from '../integrations/feed/types'; diff --git a/src/common/pubsub.ts b/src/common/pubsub.ts index 817cab7d24..2271347c87 100644 --- a/src/common/pubsub.ts +++ b/src/common/pubsub.ts @@ -23,7 +23,8 @@ import { import { ChangeMessage, ChangeObject } from '../types'; import { SourceMemberRoles } from '../roles'; import { SpanKind } from '@opentelemetry/api'; -import { addPubsubSpanLabels, runInRootSpan, runInSpan } from '../telemetry'; +import { addPubsubSpanLabels } from '../telemetry'; +import { runInRootSpan, runInSpan } from '@dailydotdev/node-common/telemetry'; import { Message } from '@google-cloud/pubsub'; // import { performance } from 'perf_hooks'; import { DataSource } from 'typeorm'; diff --git a/src/cron.ts b/src/cron.ts index 55a0f71319..c283999a78 100644 --- a/src/cron.ts +++ b/src/cron.ts @@ -6,7 +6,7 @@ import './config'; import { crons } from './cron/index'; import createOrGetConnection from './db'; import { logger } from './logger'; -import { runInRootSpan } from './telemetry'; +import { runInRootSpan } from '@dailydotdev/node-common/telemetry'; export default async function app(cronName: string): Promise { const connection = await createOrGetConnection(); diff --git a/src/index.ts b/src/index.ts index a662aa3eea..f7facb770b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import fastify, { FastifyError, FastifyReply, type FastifyRegisterOptions, + type FastifyPluginCallback, } from 'fastify'; import fastifyRawBody from 'fastify-raw-body'; import helmet from '@fastify/helmet'; @@ -30,15 +31,17 @@ import cookie, { FastifyCookieOptions } from '@fastify/cookie'; import { getSubscriptionSettings } from './subscription'; import { ioRedisPool } from './redis'; import { loadFeatures } from './growthbook'; -import { runInRootSpan } from './telemetry'; -import { loggerConfig } from './logger'; +import { runInRootSpan } from '@dailydotdev/node-common/telemetry'; import { getTemporalClient } from './temporal/client'; import { BrokenCircuitError } from 'cockatiel'; import { remoteConfig } from './remoteConfig'; import { ZodError } from 'zod'; import { closeClickHouseClient } from './common/clickhouse'; import { GQL_MAX_FILE_SIZE } from './config'; -import otelPlugin from './telemetry/plugin'; +import { pinoLoggerConfig } from '@dailydotdev/node-common/logger'; +import otelPlugin, { + type FastifyOtelPluginOptions, +} from '@dailydotdev/node-common/telemetry/plugin'; type Mutable = { -readonly [Key in keyof Type]: Type[Key]; @@ -71,7 +74,7 @@ export default async function app( ); const app = fastify({ - logger: loggerConfig, + logger: pinoLoggerConfig, disableRequestLogging: true, trustProxy: true, routerOptions: { @@ -80,7 +83,9 @@ export default async function app( }); app.log.info('loading features'); - app.register(otelPlugin); + app.register( + otelPlugin as unknown as FastifyPluginCallback, // TODO: Fix this + ); await loadFeatures(app.log); const gracefulShutdown = () => { diff --git a/src/integrations/feed/configs.ts b/src/integrations/feed/configs.ts index 4142f030b4..1b1c415edf 100644 --- a/src/integrations/feed/configs.ts +++ b/src/integrations/feed/configs.ts @@ -8,7 +8,7 @@ import { } from './types'; import { AnonymousFeedFilters, feedToFilters } from '../../common'; import { postTypes } from '../../entity'; -import { runInSpan } from '../../telemetry'; +import { runInSpan } from '@dailydotdev/node-common/telemetry'; import { ILofnClient } from '../lofn'; import { Context } from '../../Context'; diff --git a/src/integrations/retry.ts b/src/integrations/retry.ts index 4d2aef4620..35845a53f0 100644 --- a/src/integrations/retry.ts +++ b/src/integrations/retry.ts @@ -1,7 +1,7 @@ import retry, { OperationOptions } from 'retry'; import isNetworkError from './networkError'; import fetch, { RequestInfo, RequestInit, Response } from 'node-fetch'; -import { runInSpan } from '../telemetry'; +import { runInSpan } from '@dailydotdev/node-common/telemetry'; import { trace } from '@opentelemetry/api'; import { ATTR_HTTP_REQUEST_METHOD, diff --git a/src/logger.ts b/src/logger.ts index ba657c3f8f..07d7dba9c2 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,59 +1,5 @@ -import { env } from 'node:process'; +import pino from 'pino'; -import { createGcpLoggingPinoConfig } from '@google-cloud/pino-logging-gcp-config'; -import pino, { type LoggerOptions } from 'pino'; +import { pinoLoggerConfig } from '@dailydotdev/node-common/logger'; -const isProd = env.NODE_ENV === 'production'; - -const OTEL_SEV_MAPPING = { - 10: 1, // TRACE - 20: 5, // DEBUG - 30: 9, // INFO - 40: 13, // WARN - 50: 17, // ERROR - 60: 21, // FATAL -}; - -const devTransport: LoggerOptions['transport'] = { - target: 'pino-pretty', -}; - -const buildLoggerConfig = (): LoggerOptions => { - const baseOptions: LoggerOptions = { - level: env.LOG_LEVEL || 'info', - }; - - if (!isProd) { - return { ...baseOptions, transport: devTransport }; - } - - if (isProd && env.OTEL_LOGGER_FORMAT === 'gcp') { - return createGcpLoggingPinoConfig( - { - serviceContext: { - service: env.OTEL_SERVICE_NAME, - version: env.OTEL_SERVICE_VERSION, - }, - }, - baseOptions, - ); - } - - return { - ...baseOptions, - timestamp: () => `,"timeUnixNano":"${Date.now()}000000"`, - messageKey: 'body', - formatters: { - level: (severity, level) => ({ - severityText: severity.toUpperCase(), - severityNumber: - OTEL_SEV_MAPPING[level as keyof typeof OTEL_SEV_MAPPING] || - OTEL_SEV_MAPPING[30], // default to INFO - }), - }, - }; -}; - -export const loggerConfig: LoggerOptions = buildLoggerConfig(); - -export const logger = pino(loggerConfig); +export const logger = pino(pinoLoggerConfig); diff --git a/src/routes/boot.ts b/src/routes/boot.ts index 0fa78d315c..5c89871f1b 100644 --- a/src/routes/boot.ts +++ b/src/routes/boot.ts @@ -70,8 +70,8 @@ import { getUserGrowthBookInstance, } from '../growthbook'; import { differenceInMinutes, isSameDay, subDays } from 'date-fns'; +import { runInSpan } from '@dailydotdev/node-common/telemetry'; import { - runInSpan, SEMATTRS_DAILY_APPS_USER_ID, SEMATTRS_DAILY_STAFF, } from '../telemetry'; diff --git a/src/telemetry/plugin.ts b/src/telemetry/plugin.ts deleted file mode 100644 index f6cf0bc65b..0000000000 --- a/src/telemetry/plugin.ts +++ /dev/null @@ -1,145 +0,0 @@ -import type { FastifyPluginCallback, FastifyRequest } from 'fastify'; -import fp from 'fastify-plugin'; - -import { - trace, - context, - type Span, - SpanStatusCode, - type Tracer, - type Context, - type TextMapSetter, - type TextMapGetter, - propagation, -} from '@opentelemetry/api'; - -import { getRPCMetadata, RPCType } from '@opentelemetry/core'; -import { - ATTR_HTTP_RESPONSE_STATUS_CODE, - ATTR_HTTP_ROUTE, -} from '@opentelemetry/semantic-conventions'; - -const kRequestSpan = Symbol('fastify otel request span'); -const kRequestContext = Symbol('fastify otel request context'); -const kRequestContextValue = Symbol('fastify otel request context value'); - -export interface OpenTelemetry { - span: Span | null; - tracer: Tracer; - context: Context; - inject: (carrier: C, setter?: TextMapSetter) => void; - extract: (carrier: C, getter?: TextMapGetter) => Context; -} - -export interface FastifyOtelPluginOptions { - tracer?: Tracer; -} - -declare module 'fastify' { - interface FastifyRequest { - opentelemetry(): OpenTelemetry; - [kRequestSpan]: Span | null; - [kRequestContext]: Context; - [kRequestContextValue]: Context | null; - } -} - -const plugin: FastifyPluginCallback = async ( - instance, - opts, -) => { - const tracer = opts.tracer ?? trace.getTracer('fastify-otel'); - - instance.decorateRequest( - 'opentelemetry', - function opentelemetry(this: FastifyRequest): OpenTelemetry { - const ctx = this[kRequestContext]; - const span = this[kRequestSpan]; - - return { - span, - tracer, - context: ctx, - inject: (carrier, setter) => propagation.inject(ctx, carrier, setter), - extract: (carrier, getter) => propagation.extract(ctx, carrier, getter), - }; - }, - ); - instance.decorateRequest(kRequestSpan, null); - instance.decorateRequest(kRequestContextValue, null); - instance.decorateRequest(kRequestContext, { - getter(this: FastifyRequest) { - return this[kRequestContextValue] ?? context.active(); - }, - setter(this: FastifyRequest, value: Context) { - this[kRequestContextValue] = value; - }, - }); - - instance.addHook('onRequest', async (request) => { - const requestPath = request.routeOptions.url; - let ctx = context.active(); - - if (trace.getSpan(ctx) == null) { - ctx = propagation.extract(ctx, request.headers); - } - - const span = trace.getSpan(ctx); - - if (span != null && requestPath != null) { - span.setAttributes({ - [ATTR_HTTP_ROUTE]: requestPath, - }); - - span.updateName(`${request.method} ${requestPath}`); - - const rpcMetadata = getRPCMetadata(ctx); - if (rpcMetadata?.type === RPCType.HTTP) { - rpcMetadata.route = requestPath; - } - } - - request[kRequestContext] = ctx; - request[kRequestSpan] = span ?? null; - }); - - instance.addHook('onSend', (request, reply, payload, done) => { - const span = request[kRequestSpan]; - - if (span != null) { - if (reply.statusCode >= 500) { - span.setStatus({ - code: SpanStatusCode.ERROR, - message: `HTTP ${reply.statusCode}`, - }); - } else { - span.setStatus({ - code: SpanStatusCode.OK, - message: 'OK', - }); - } - span.setAttributes({ - [ATTR_HTTP_RESPONSE_STATUS_CODE]: reply.statusCode, - }); - } - - request[kRequestSpan] = null; - done(null, payload); - }); - - instance.addHook('onError', async (request, _, error) => { - const span = request[kRequestSpan]; - - if (span != null) { - span.setStatus({ - code: SpanStatusCode.ERROR, - message: error.message, - }); - span.recordException(error); - } - }); -}; - -export default fp(plugin, { - name: 'opentelemetry', -}); diff --git a/src/telemetry/tracing.ts b/src/telemetry/tracing.ts index 732c1a036a..7b21aa38a4 100644 --- a/src/telemetry/tracing.ts +++ b/src/telemetry/tracing.ts @@ -1,11 +1,6 @@ import type { Message } from '@google-cloud/pubsub'; -import { - trace, - type Span, - type SpanOptions, - SpanStatusCode, -} from '@opentelemetry/api'; +import { type Span } from '@opentelemetry/api'; import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc'; @@ -42,61 +37,3 @@ export const createSpanProcessor = (): BatchSpanProcessor => { exportTimeoutMillis: 30000, }); }; - -export const runInSpan = async ( - name: string, - func: (span: Span) => Promise, - options?: SpanOptions, -): Promise => - trace - .getTracer('runInSpan') - .startActiveSpan(name, options ?? {}, async (span) => { - try { - return await func(span); - } catch (originalError) { - const err = originalError as Error; - - span.recordException(err); - span.setStatus({ - code: SpanStatusCode.ERROR, - message: err?.message, - }); - throw err; - } finally { - span.end(); - } - }) as T; - -export const runInSpanSync = ( - name: string, - func: (span: Span) => T, - options?: SpanOptions, -): T => - trace.getTracer('runInSpan').startActiveSpan(name, options ?? {}, (span) => { - try { - return func(span); - } catch (originalError) { - const err = originalError as Error; - - span.recordException(err); - span.setStatus({ - code: SpanStatusCode.ERROR, - message: err?.message, - }); - throw err; - } finally { - span.end(); - } - }) as T; - -export const runInRootSpan = async ( - name: string, - func: (span: Span) => Promise, - options?: SpanOptions, -): Promise => runInSpan(name, func, { ...options, root: true }); - -export const runInRootSpanSync = ( - name: string, - func: (span: Span) => T, - options?: SpanOptions, -): T => runInSpanSync(name, func, { ...options, root: true });