OpenTelemetry Instrumentation#
Misskey's backend uses OpenTelemetry (OTel) for distributed tracing. All instrumentation code lives under packages/backend/src/core/telemetry/. The entry point for initialization is telemetry-registry.ts, which bootstraps adapters before NestJS's DI container is built . TelemetryService is a thin NestJS-injectable wrapper over this module-level state .
Build Externalization of @opentelemetry/*#
All @opentelemetry/* packages are excluded from the rolldown bundle via a regex rule in the external list :
/^@opentelemetry\/.*/
Similarly, pg is also externalized so that require-hook-based instrumentation (Sentry or OTel) can patch it at runtime . Without externalizing pg, the bundler would inline it and the hooks applied before require('pg') would find no module to intercept.
In watch (dev) mode all non-local modules are externalized wholesale, bypassing the explicit list .
Packages in package.json:
| Package | Version |
|---|---|
@opentelemetry/api | 1.9.1 |
@opentelemetry/core | 2.9.0 |
@opentelemetry/exporter-trace-otlp-proto | 0.220.0 |
@opentelemetry/instrumentation-pg | 0.72.0 |
@opentelemetry/resources | 2.9.0 |
@opentelemetry/sdk-trace-base / sdk-trace-node | 2.9.0 |
@opentelemetry/semantic-conventions | 1.43.0 |
diagnostics_channel Instead of Require-Hook Instrumentation#
For HTTP client and Redis, Misskey deliberately avoids the classic require-hook approach (patching require('http') / require('ioredis') on load). Instead, both instrumentations subscribe to native Node.js diagnostics_channel events published by the libraries themselves:
-
HTTP client β
http-client-instrumentation.tssubscribes tohttp.client.request.created,http.client.response.finish, andhttp.client.request.errorchannels usingnode:diagnostics_channel. The code comment explicitly states this approach captures modules loaded before telemetry initialization β which require-hooks cannot do . -
Redis (ioredis) β
redis-instrumentation.tsusesnode:diagnostics_channel'stracingChannelAPI to subscribe toioredis:commandandioredis:connectchannels published by ioredis β₯5.11 . This also works after bundling because ioredis itself emits the events β no module patching required.
Both instrumentations are fully testable in isolation by injecting mock subscribe / tracingChannel dependencies .
PostgreSQL: Dynamic Load & Opt-In#
PostgreSQL instrumentation uses @opentelemetry/instrumentation-pg, which relies on the traditional require-hook pattern. The hook must be installed before the pg module is first imported .
installDatabaseInstrumentation in database-instrumentation.ts handles this with two safeguards:
- Opt-in guard β if
options.capturePgSpansisfalse, the function returns immediately without loading the package at all . - Dynamic import β
@opentelemetry/instrumentation-pgis only loaded viaawait import(...)when pg spans are enabled, so the module and its hooks are never materialized in the common path .
Privacy defaults for pg spans :
enhancedDatabaseReportingis hardcodedfalseβ SQL parameters (which may contain post content or credentials) are never captured.db.statementanddb.query.textspan attributes are overwritten with[REDACTED]unlesscapturePgStatementis explicitly enabled via the opt-in config.
Adapter Architecture & Sentry Co-existence#
telemetry-registry.ts supports three configurations :
| Config | Adapter used |
|---|---|
| Sentry only | SentryTelemetryAdapter |
| OTel only | OpenTelemetryAdapter (standalone NodeTracerProvider) |
| Both | SentryTelemetryAdapter with an OTLP processor added β shares one provider |
TelemetryService exposes startSpan, startSpanWithTraceContext (for BullMQ worker context propagation), and captureMessage to the rest of the application .
Queue trace-context propagation is handled by queue-instrumentation.ts, which wraps Queue.add / Queue.addBulk to inject the active OTel context into BullMQ job data before serialization to Redis .