From ab0145c2bc2ec018b30626fa56acd7470c3636d7 Mon Sep 17 00:00:00 2001 From: Rahul Mishra Date: Sun, 28 Jul 2024 20:41:49 +0530 Subject: [PATCH] chore: load opentelemetry modules on demand (#619) --- apps/mail-bridge/tracing.ts | 7 ++-- apps/mail-bridge/tsup.config.ts | 2 +- apps/platform/tracing.ts | 7 ++-- apps/platform/tsup.config.ts | 2 +- apps/storage/tracing.ts | 7 ++-- apps/storage/tsup.config.ts | 2 +- apps/web/src/instrumentation.ts | 10 ++++-- apps/worker/tracing.ts | 7 ++-- apps/worker/tsup.config.ts | 2 +- packages/database/index.ts | 55 ++++++++++++++++-------------- packages/otel/exports.ts | 1 - packages/otel/helpers.ts | 13 ++++--- packages/otel/hono.ts | 5 ++- packages/otel/index.ts | 60 +-------------------------------- packages/otel/package.json | 1 + packages/otel/setup.ts | 58 +++++++++++++++++++++++++++++++ 16 files changed, 130 insertions(+), 109 deletions(-) create mode 100644 packages/otel/setup.ts diff --git a/apps/mail-bridge/tracing.ts b/apps/mail-bridge/tracing.ts index a3667a3b..ed857f7b 100644 --- a/apps/mail-bridge/tracing.ts +++ b/apps/mail-bridge/tracing.ts @@ -1,4 +1,7 @@ -import { setupOpentelemetry } from '@u22n/otel'; +import { opentelemetryEnabled } from '@u22n/otel'; import { name, version } from './package.json'; -setupOpentelemetry({ name, version }); +if (opentelemetryEnabled) { + const { setupOpentelemetry } = await import('@u22n/otel/setup'); + setupOpentelemetry({ name, version }); +} diff --git a/apps/mail-bridge/tsup.config.ts b/apps/mail-bridge/tsup.config.ts index 19de0192..a0d9cb1a 100644 --- a/apps/mail-bridge/tsup.config.ts +++ b/apps/mail-bridge/tsup.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from 'tsup'; export default defineConfig({ - entry: ['app.ts', './tracing.ts'], + entry: ['app.ts', 'tracing.ts'], outDir: '.output', format: 'esm', target: 'esnext', diff --git a/apps/platform/tracing.ts b/apps/platform/tracing.ts index a3667a3b..ed857f7b 100644 --- a/apps/platform/tracing.ts +++ b/apps/platform/tracing.ts @@ -1,4 +1,7 @@ -import { setupOpentelemetry } from '@u22n/otel'; +import { opentelemetryEnabled } from '@u22n/otel'; import { name, version } from './package.json'; -setupOpentelemetry({ name, version }); +if (opentelemetryEnabled) { + const { setupOpentelemetry } = await import('@u22n/otel/setup'); + setupOpentelemetry({ name, version }); +} diff --git a/apps/platform/tsup.config.ts b/apps/platform/tsup.config.ts index 19de0192..a0d9cb1a 100644 --- a/apps/platform/tsup.config.ts +++ b/apps/platform/tsup.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from 'tsup'; export default defineConfig({ - entry: ['app.ts', './tracing.ts'], + entry: ['app.ts', 'tracing.ts'], outDir: '.output', format: 'esm', target: 'esnext', diff --git a/apps/storage/tracing.ts b/apps/storage/tracing.ts index a3667a3b..ed857f7b 100644 --- a/apps/storage/tracing.ts +++ b/apps/storage/tracing.ts @@ -1,4 +1,7 @@ -import { setupOpentelemetry } from '@u22n/otel'; +import { opentelemetryEnabled } from '@u22n/otel'; import { name, version } from './package.json'; -setupOpentelemetry({ name, version }); +if (opentelemetryEnabled) { + const { setupOpentelemetry } = await import('@u22n/otel/setup'); + setupOpentelemetry({ name, version }); +} diff --git a/apps/storage/tsup.config.ts b/apps/storage/tsup.config.ts index 19de0192..a0d9cb1a 100644 --- a/apps/storage/tsup.config.ts +++ b/apps/storage/tsup.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from 'tsup'; export default defineConfig({ - entry: ['app.ts', './tracing.ts'], + entry: ['app.ts', 'tracing.ts'], outDir: '.output', format: 'esm', target: 'esnext', diff --git a/apps/web/src/instrumentation.ts b/apps/web/src/instrumentation.ts index 9d427e7c..ce2657be 100644 --- a/apps/web/src/instrumentation.ts +++ b/apps/web/src/instrumentation.ts @@ -1,7 +1,11 @@ export async function register() { if (process.env.NEXT_RUNTIME === 'nodejs') { - const { name, version } = await import('../package.json'); - const { setupOpentelemetry } = await import('@u22n/otel'); - setupOpentelemetry({ name, version }); + const { opentelemetryEnabled } = await import('@u22n/otel'); + + if (opentelemetryEnabled) { + const { name, version } = await import('../package.json'); + const { setupOpentelemetry } = await import('@u22n/otel/setup'); + setupOpentelemetry({ name, version }); + } } } diff --git a/apps/worker/tracing.ts b/apps/worker/tracing.ts index a3667a3b..ed857f7b 100644 --- a/apps/worker/tracing.ts +++ b/apps/worker/tracing.ts @@ -1,4 +1,7 @@ -import { setupOpentelemetry } from '@u22n/otel'; +import { opentelemetryEnabled } from '@u22n/otel'; import { name, version } from './package.json'; -setupOpentelemetry({ name, version }); +if (opentelemetryEnabled) { + const { setupOpentelemetry } = await import('@u22n/otel/setup'); + setupOpentelemetry({ name, version }); +} diff --git a/apps/worker/tsup.config.ts b/apps/worker/tsup.config.ts index 19de0192..a0d9cb1a 100644 --- a/apps/worker/tsup.config.ts +++ b/apps/worker/tsup.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from 'tsup'; export default defineConfig({ - entry: ['app.ts', './tracing.ts'], + entry: ['app.ts', 'tracing.ts'], outDir: '.output', format: 'esm', target: 'esnext', diff --git a/packages/database/index.ts b/packages/database/index.ts index e91aa985..28a67d6d 100644 --- a/packages/database/index.ts +++ b/packages/database/index.ts @@ -1,37 +1,40 @@ import { drizzle } from 'drizzle-orm/planetscale-serverless'; import { Client, Connection } from '@planetscale/database'; -import { getTracer } from '@u22n/otel/helpers'; +import { opentelemetryEnabled } from '@u22n/otel'; import * as schema from './schema'; import { env } from './env'; -const databaseTracer = getTracer('database'); +if (opentelemetryEnabled) { + const { getTracer } = await import('@u22n/otel/helpers'); + const databaseTracer = getTracer('database'); -// eslint-disable-next-line @typescript-eslint/unbound-method -const originalExecute = Connection.prototype.execute; + // eslint-disable-next-line @typescript-eslint/unbound-method + const originalExecute = Connection.prototype.execute; -Connection.prototype.execute = async function (query, args, options) { - return databaseTracer.startActiveSpan(`Database Query`, async (span) => { - if (span) { - span.addEvent('database.query.start'); - span.setAttribute('database.statement', query); - if (Array.isArray(args)) { - span.setAttribute( - 'database.values', - args.map((v: string) => v.toString()) - ); + Connection.prototype.execute = async function (query, args, options) { + return databaseTracer.startActiveSpan(`Database Query`, async (span) => { + if (span) { + span.addEvent('database.query.start'); + span.setAttribute('database.statement', query); + if (Array.isArray(args)) { + span.setAttribute( + 'database.values', + args.map((v: string) => v.toString()) + ); + } } - } - const result = await originalExecute - // @ts-expect-error, don't care about types here - .call(this, query, args, options) - .catch((err: Error) => { - span?.recordException(err); - throw err; - }); - span?.addEvent('database.query.end'); - return result; - }); -}; + const result = await originalExecute + // @ts-expect-error, don't care about types here + .call(this, query, args, options) + .catch((err: Error) => { + span?.recordException(err); + throw err; + }); + span?.addEvent('database.query.end'); + return result; + }); + }; +} const client = new Client({ host: env.DB_PLANETSCALE_HOST, diff --git a/packages/otel/exports.ts b/packages/otel/exports.ts index a84e90be..3a7ae3ae 100644 --- a/packages/otel/exports.ts +++ b/packages/otel/exports.ts @@ -1,2 +1 @@ -export { trace } from '@opentelemetry/api'; export { flatten, unflatten } from 'flat'; diff --git a/packages/otel/helpers.ts b/packages/otel/helpers.ts index 2994bcfa..6c5dd92e 100644 --- a/packages/otel/helpers.ts +++ b/packages/otel/helpers.ts @@ -1,9 +1,13 @@ import type { Span } from '@opentelemetry/api'; -import { trace } from '@opentelemetry/api'; -import { env } from './env'; +import { opentelemetryEnabled } from '.'; + +// Import OpenTelemetry API only if it's enabled +const { trace } = opentelemetryEnabled + ? await import('@opentelemetry/api') + : { trace: undefined }; export function getTracer(name: string) { - if (!env.OTEL_ENABLED) + if (!trace) return { startActiveSpan: (name: string, fn: (span?: Span) => Fn) => fn() }; @@ -11,7 +15,6 @@ export function getTracer(name: string) { const tracer = trace.getTracer(name); return { startActiveSpan(name: string, fn: (span?: Span) => Fn) { - if (!env.OTEL_ENABLED) return fn(); return tracer.startActiveSpan(name, (span) => { const result = fn(span); if (result instanceof Promise) { @@ -26,7 +29,7 @@ export function getTracer(name: string) { } export function inActiveSpan(fn: (span?: Span) => Fn) { - if (!env.OTEL_ENABLED) return fn(); + if (!trace) return fn(); const span = trace.getActiveSpan(); return fn(span); } diff --git a/packages/otel/hono.ts b/packages/otel/hono.ts index 7d5b367a..c11dfb95 100644 --- a/packages/otel/hono.ts +++ b/packages/otel/hono.ts @@ -6,14 +6,13 @@ function formatHeaders(headers: Record | Headers) { } export const opentelemetry = (name?: string) => { - // eslint-disable-next-line @typescript-eslint/unbound-method - const { startActiveSpan } = getTracer(name ?? 'hono'); + const tracer = getTracer(name ?? 'hono'); return createMiddleware<{ Variables: { requestId: string } }>((c, next) => // eslint-disable-next-line @typescript-eslint/no-unsafe-return inActiveSpan(async (parent) => { parent?.updateName(`HTTP ${c.req.method} ${c.req.path}`); if (c.req.method === 'OPTIONS') return next(); - return startActiveSpan(`Hono Handler`, async (span) => { + return tracer.startActiveSpan(`Hono Handler`, async (span) => { span?.addEvent('hono.start'); span?.setAttributes({ 'hono.req.headers': formatHeaders(c.req.header()) diff --git a/packages/otel/index.ts b/packages/otel/index.ts index 39e85de3..c35c6824 100644 --- a/packages/otel/index.ts +++ b/packages/otel/index.ts @@ -1,60 +1,2 @@ -import { - NodeTracerProvider, - BatchSpanProcessor -} from '@opentelemetry/sdk-trace-node'; -import { - BatchLogRecordProcessor, - LoggerProvider -} from '@opentelemetry/sdk-logs'; -import { WinstonInstrumentation } from '@opentelemetry/instrumentation-winston'; -import { UndiciInstrumentation } from '@opentelemetry/instrumentation-undici'; -import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; -import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; -import { Resource } from '@opentelemetry/resources'; -import { logs } from '@opentelemetry/api-logs'; import { env } from './env'; - -export const setupOpentelemetry = ({ - name, - version -}: { - name: string; - version: string; -}) => { - if (!env.OTEL_ENABLED) return; - - const resource = new Resource({ - 'service.name': name, - 'service.version': version - }); - - const traceProvider = new NodeTracerProvider({ resource }); - traceProvider.addSpanProcessor( - new BatchSpanProcessor( - new OTLPTraceExporter({ - url: env.OTEL_EXPORTER_TRACES_ENDPOINT - }) - ) - ); - traceProvider.register(); - - const loggerProvider = new LoggerProvider({ resource }); - loggerProvider.addLogRecordProcessor( - new BatchLogRecordProcessor( - new OTLPLogExporter({ - url: env.OTEL_EXPORTER_LOGS_ENDPOINT - }) - ) - ); - logs.setGlobalLoggerProvider(loggerProvider); - - registerInstrumentations({ - instrumentations: [ - new UndiciInstrumentation(), - new HttpInstrumentation(), - new WinstonInstrumentation() - ] - }); -}; +export const opentelemetryEnabled = env.OTEL_ENABLED; diff --git a/packages/otel/package.json b/packages/otel/package.json index f6b1cc4d..9e55ae84 100644 --- a/packages/otel/package.json +++ b/packages/otel/package.json @@ -4,6 +4,7 @@ "type": "module", "exports": { ".": "./index.ts", + "./setup": "./setup.ts", "./hono": "./hono.ts", "./exports": "./exports.ts", "./logger": "./logger.ts", diff --git a/packages/otel/setup.ts b/packages/otel/setup.ts new file mode 100644 index 00000000..4b8de6a5 --- /dev/null +++ b/packages/otel/setup.ts @@ -0,0 +1,58 @@ +import { + BatchSpanProcessor, + NodeTracerProvider +} from '@opentelemetry/sdk-trace-node'; +import { + BatchLogRecordProcessor, + LoggerProvider +} from '@opentelemetry/sdk-logs'; +import { WinstonInstrumentation } from '@opentelemetry/instrumentation-winston'; +import { UndiciInstrumentation } from '@opentelemetry/instrumentation-undici'; +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; +import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; +import { Resource } from '@opentelemetry/resources'; +import { logs } from '@opentelemetry/api-logs'; +import { env } from './env'; + +export const setupOpentelemetry = ({ + name, + version +}: { + name: string; + version: string; +}) => { + const resource = new Resource({ + 'service.name': name, + 'service.version': version + }); + + const traceProvider = new NodeTracerProvider({ resource }); + traceProvider.addSpanProcessor( + new BatchSpanProcessor( + new OTLPTraceExporter({ + url: env.OTEL_EXPORTER_TRACES_ENDPOINT + }) + ) + ); + traceProvider.register(); + + const loggerProvider = new LoggerProvider({ resource }); + loggerProvider.addLogRecordProcessor( + new BatchLogRecordProcessor( + new OTLPLogExporter({ + url: env.OTEL_EXPORTER_LOGS_ENDPOINT + }) + ) + ); + logs.setGlobalLoggerProvider(loggerProvider); + + registerInstrumentations({ + instrumentations: [ + new UndiciInstrumentation(), + new HttpInstrumentation(), + new WinstonInstrumentation() + ] + }); +};