diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index 70b57e115..df35e59f5 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -120,7 +120,6 @@ async function createApp() { if (!o) return null - o.start() dependsOn.lifecycle.appHooks.onStop(() => o.shutdown()) return o }, diff --git a/apps/server/src/libs/otel.ts b/apps/server/src/libs/otel.ts index f9f42e4aa..db4a59f01 100644 --- a/apps/server/src/libs/otel.ts +++ b/apps/server/src/libs/otel.ts @@ -80,10 +80,11 @@ export function initOtel(env: Env) { resource, sampler, spanProcessors: [new BatchSpanProcessor(traceExporter)], - metricReader: new PeriodicExportingMetricReader({ + metricReaders: [new PeriodicExportingMetricReader({ exporter: metricExporter, - exportIntervalMillis: 15000, - }), + exportIntervalMillis: 15_000, + exportTimeoutMillis: 10_000, + })], logRecordProcessors: [new BatchLogRecordProcessor(logExporter)], instrumentations: [ new HttpInstrumentation({ @@ -100,10 +101,11 @@ export function initOtel(env: Env) { ], }) - const start = () => { - sdk.start() - logger.log(`OpenTelemetry initialized, exporting to ${otlpEndpoint}, sampling ratio: ${samplingRatio}`) - } + // SDK must start BEFORE metrics.getMeter() — the metrics API does NOT + // have a proxy mechanism like traces. getMeter() called before start() + // returns a permanent NoopMeter that never upgrades. + sdk.start() + logger.log(`OpenTelemetry initialized, exporting to ${otlpEndpoint}, sampling ratio: ${samplingRatio}`) const meter = metrics.getMeter(serviceName) @@ -161,7 +163,6 @@ export function initOtel(env: Env) { authFailures, stripeEvents, - start, shutdown, } } diff --git a/apps/server/src/middlewares/otel.ts b/apps/server/src/middlewares/otel.ts index d7f94a596..f12bfc1c6 100644 --- a/apps/server/src/middlewares/otel.ts +++ b/apps/server/src/middlewares/otel.ts @@ -17,31 +17,32 @@ export function otelMiddleware(otelMetrics: { return async (c, next) => { const startTime = performance.now() const method = c.req.method - const path = c.req.routePath || c.req.path - const attributes = { - 'http.method': method, - 'http.route': path, - 'http.url': c.req.url, - } + const path = c.req.path - otelMetrics.httpActiveRequests.add(1, { 'http.method': method, 'http.route': path }) + otelMetrics.httpActiveRequests.add(1, { 'http.request.method': method, 'http.route': path }) - const span = tracer.startSpan(`${method} ${path}`, { attributes }) + const span = tracer.startSpan(`${method} ${path}`, { + attributes: { + 'http.request.method': method, + 'http.route': path, + 'url.full': c.req.url, + }, + }) try { await context.with(trace.setSpan(context.active(), span), () => next()) const status = c.res.status - span.setAttribute('http.status_code', status) + span.setAttribute('http.response.status_code', status) if (status >= 500) { span.setStatus({ code: SpanStatusCode.ERROR, message: `HTTP ${status}` }) } otelMetrics.httpRequestDuration.record(performance.now() - startTime, { - 'http.method': method, + 'http.request.method': method, 'http.route': path, - 'http.status_code': status, + 'http.response.status_code': status, }) } catch (err) { @@ -50,7 +51,7 @@ export function otelMiddleware(otelMetrics: { throw err } finally { - otelMetrics.httpActiveRequests.add(-1, { 'http.method': method, 'http.route': path }) + otelMetrics.httpActiveRequests.add(-1, { 'http.request.method': method, 'http.route': path }) span.end() } }