server/core/lib/opentelemetry/tracing.ts
import type { Span, Tracer } from '@opentelemetry/api'
import { logger } from '@server/helpers/logger.js'
import { CONFIG } from '@server/initializers/config.js'
let tracer: Tracer | TrackerMock
async function registerOpentelemetryTracing () {
if (CONFIG.OPEN_TELEMETRY.TRACING.ENABLED !== true) {
tracer = new TrackerMock()
return
}
const { diag, DiagLogLevel, trace } = await import('@opentelemetry/api')
tracer = trace.getTracer('peertube')
const [
{ JaegerExporter },
{ registerInstrumentations },
DnsInstrumentation,
ExpressInstrumentation,
{ FsInstrumentation },
{ HttpInstrumentation },
IORedisInstrumentation,
PgInstrumentation,
{ SequelizeInstrumentation },
Resource,
BatchSpanProcessor,
NodeTracerProvider,
SemanticResourceAttributes
] = await Promise.all([
import('@opentelemetry/exporter-jaeger'),
import('@opentelemetry/instrumentation'),
import('@opentelemetry/instrumentation-dns'),
import('@opentelemetry/instrumentation-express'),
import('@opentelemetry/instrumentation-fs'),
import('@opentelemetry/instrumentation-http'),
import('@opentelemetry/instrumentation-ioredis'),
import('@opentelemetry/instrumentation-pg'),
import('opentelemetry-instrumentation-sequelize'),
import('@opentelemetry/resources'),
import('@opentelemetry/sdk-trace-base'),
import('@opentelemetry/sdk-trace-node'),
import('@opentelemetry/semantic-conventions')
])
logger.info('Registering Open Telemetry tracing')
const customLogger = (level: string) => {
return (message: string, ...args: unknown[]) => {
let fullMessage = message
for (const arg of args) {
if (typeof arg === 'string') fullMessage += arg
else break
}
logger[level](fullMessage)
}
}
diag.setLogger({
error: customLogger('error'),
warn: customLogger('warn'),
info: customLogger('info'),
debug: customLogger('debug'),
verbose: customLogger('verbose')
}, DiagLogLevel.INFO)
const tracerProvider = new NodeTracerProvider.default.NodeTracerProvider({
resource: new Resource.default.Resource({
[SemanticResourceAttributes.default.SemanticResourceAttributes.SERVICE_NAME]: 'peertube'
})
})
registerInstrumentations({
tracerProvider,
instrumentations: [
new PgInstrumentation.default.PgInstrumentation({
enhancedDatabaseReporting: true
}),
new DnsInstrumentation.default.DnsInstrumentation(),
new HttpInstrumentation(),
new ExpressInstrumentation.default.ExpressInstrumentation(),
new IORedisInstrumentation.default.IORedisInstrumentation({
dbStatementSerializer: function (cmdName, cmdArgs) {
return [ cmdName, ...cmdArgs ].join(' ')
}
}),
new FsInstrumentation(),
new SequelizeInstrumentation()
]
})
tracerProvider.addSpanProcessor(
new BatchSpanProcessor.default.BatchSpanProcessor(
new JaegerExporter({ endpoint: CONFIG.OPEN_TELEMETRY.TRACING.JAEGER_EXPORTER.ENDPOINT })
)
)
tracerProvider.register()
}
async function wrapWithSpanAndContext <T> (spanName: string, cb: () => Promise<T>) {
const { context, trace } = await import('@opentelemetry/api')
if (CONFIG.OPEN_TELEMETRY.TRACING.ENABLED !== true) {
return cb()
}
const span = tracer.startSpan(spanName)
const activeContext = trace.setSpan(context.active(), span as Span)
const result = await context.with(activeContext, () => cb())
span.end()
return result
}
export {
registerOpentelemetryTracing,
tracer,
wrapWithSpanAndContext
}
// ---------------------------------------------------------------------------
// Private
// ---------------------------------------------------------------------------
class TrackerMock {
startSpan () {
return new SpanMock()
}
}
class SpanMock {
end () {
}
}