feat(otel): server instrumentation script

This commit is contained in:
RainbowBird
2026-04-02 18:21:30 +08:00
parent 457ad19f09
commit 9451cd7cc0
5 changed files with 48 additions and 20 deletions
+31
View File
@@ -0,0 +1,31 @@
/**
* OTEL instrumentation preload — loaded via `--import` BEFORE tsx processes
* any application module. This ensures @opentelemetry/instrumentation-pg can
* monkey-patch the CJS `pg` module before it is imported anywhere.
*
* Only instrumentations that patch third-party modules need to live here.
* The full SDK (exporters, metrics, log processors) is still configured in
* src/libs/otel.ts — the NodeSDK there will reuse the already-registered
* instrumentations.
*
* NOTICE: `pg` and `ioredis` are CJS packages. When ESM code does
* `import pg from 'pg'`, Node.js internally calls `require()` to load
* the CJS module, so `require-in-the-middle` hooks still intercept it.
*/
import { registerInstrumentations } from '@opentelemetry/instrumentation'
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'
import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis'
import { PgInstrumentation } from '@opentelemetry/instrumentation-pg'
registerInstrumentations({
instrumentations: [
new HttpInstrumentation({
ignoreIncomingRequestHook: req => req.url === '/health',
}),
new PgInstrumentation({
enhancedDatabaseReporting: true,
}),
new IORedisInstrumentation(),
],
})
+4 -3
View File
@@ -6,9 +6,9 @@
"scripts": {
"apply:env": "dotenvx run -f .env.local --overload --ignore=MISSING_ENV_FILE",
"auth:generate": "pnpm run apply:env -- better-auth generate --config src/scripts/auth.ts --output src/schemas/accounts.ts -y",
"dev": "pnpm run apply:env -- tsx --watch src/bin/run.ts api",
"start": "pnpm run apply:env -- tsx src/bin/run.ts api",
"server": "pnpm run apply:env -- tsx src/bin/run.ts",
"dev": "pnpm run apply:env -- tsx --import ./instrumentation.mjs --watch src/bin/run.ts api",
"start": "pnpm run apply:env -- tsx --import ./instrumentation.mjs src/bin/run.ts api",
"server": "pnpm run apply:env -- tsx --import ./instrumentation.mjs src/bin/run.ts",
"build": "tsc -b",
"typecheck": "tsc --noEmit",
"db:generate": "drizzle-kit generate",
@@ -29,6 +29,7 @@
"@opentelemetry/exporter-logs-otlp-proto": "^0.214.0",
"@opentelemetry/exporter-metrics-otlp-proto": "^0.214.0",
"@opentelemetry/exporter-trace-otlp-proto": "^0.214.0",
"@opentelemetry/instrumentation": "^0.214.0",
"@opentelemetry/instrumentation-http": "^0.214.0",
"@opentelemetry/instrumentation-ioredis": "^0.62.0",
"@opentelemetry/instrumentation-pg": "^0.66.0",
+6 -2
View File
@@ -1,10 +1,11 @@
import type { Env } from './env'
import pg from 'pg'
import { useLogger } from '@guiiai/logg'
import { migrate } from '@proj-airi/drizzle-orm-browser-migrator/pg'
import { migrations } from '@proj-airi/server-schema'
import { drizzle } from 'drizzle-orm/node-postgres'
import { Pool } from 'pg'
import * as fullSchema from '../schemas'
@@ -14,8 +15,11 @@ export type Database = ReturnType<typeof createDrizzle>['db']
type DrizzleEnv = Pick<Env, 'DATABASE_URL' | 'DB_POOL_MAX' | 'DB_POOL_IDLE_TIMEOUT_MS' | 'DB_POOL_CONNECTION_TIMEOUT_MS' | 'DB_POOL_KEEPALIVE_INITIAL_DELAY_MS'>
// NOTICE: pg is imported statically here. The OTEL instrumentation hooks are
// registered via --import ./instrumentation.mjs (preload) which runs before
// tsx loads application modules, allowing require-in-the-middle to patch pg.
export function createDrizzle(env: DrizzleEnv) {
const pool = new Pool({
const pool = new pg.Pool({
connectionString: env.DATABASE_URL,
max: env.DB_POOL_MAX,
idleTimeoutMillis: env.DB_POOL_IDLE_TIMEOUT_MS,
+4 -15
View File
@@ -1,5 +1,3 @@
import type { IncomingMessage } from 'node:http'
import type { Counter, Histogram, UpDownCounter } from '@opentelemetry/api'
import type { Env } from './env'
@@ -12,9 +10,6 @@ import { logs, SeverityNumber } from '@opentelemetry/api-logs'
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-proto'
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-proto'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'
import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis'
import { PgInstrumentation } from '@opentelemetry/instrumentation-pg'
import { RuntimeNodeInstrumentation } from '@opentelemetry/instrumentation-runtime-node'
import { resourceFromAttributes } from '@opentelemetry/resources'
import { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs'
@@ -172,17 +167,11 @@ export function initOtel(env: Env): OtelInstance | undefined {
exportTimeoutMillis: 10_000,
})],
logRecordProcessors: [new BatchLogRecordProcessor(logExporter)],
// NOTICE: HttpInstrumentation, PgInstrumentation, and IORedisInstrumentation
// are registered in instrumentation.cjs (loaded via --require) so that
// require-in-the-middle can patch the CJS modules before tsx's ESM loader
// imports them. Only non-patching instrumentations belong here.
instrumentations: [
new HttpInstrumentation({
ignoreIncomingRequestHook: (req: IncomingMessage) => {
// Ignore health check requests to reduce noise
return req.url === '/health'
},
}),
new PgInstrumentation({
enhancedDatabaseReporting: true,
}),
new IORedisInstrumentation(),
new RuntimeNodeInstrumentation(),
],
})
+3
View File
@@ -563,6 +563,9 @@ importers:
'@opentelemetry/exporter-trace-otlp-proto':
specifier: ^0.214.0
version: 0.214.0(@opentelemetry/api@1.9.1)
'@opentelemetry/instrumentation':
specifier: ^0.214.0
version: 0.214.0(@opentelemetry/api@1.9.1)
'@opentelemetry/instrumentation-http':
specifier: ^0.214.0
version: 0.214.0(@opentelemetry/api@1.9.1)