// OpenTelemetry Node SDK bootstrap for FarmaClic backend. // Started as a side-effect import from server.js (ESM). // // Env vars (set by docker-compose): // OTEL_SERVICE_NAME — default: farmaclic-backend // OTEL_EXPORTER_OTLP_ENDPOINT — OTLP gRPC endpoint (e.g. http://alloy:4317) // // Exports traces to the shared Grafana Alloy collector, where they are // routed to Tempo. import { NodeSDK } from '@opentelemetry/sdk-node'; import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'; import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc'; import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc'; import * as resources from '@opentelemetry/resources'; import { PinoInstrumentation } from '@opentelemetry/instrumentation-pino'; const serviceName = process.env.OTEL_SERVICE_NAME || 'farmaclic-backend'; const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4317'; // Use plain string attribute keys (not the semantic-conventions named exports) // so we don't depend on a specific @opentelemetry/semantic-conventions version. const resource = typeof resources.resourceFromAttributes === 'function' ? resources.resourceFromAttributes({ 'service.name': serviceName, 'service.namespace': 'farmaclic', }) : new resources.Resource({ 'service.name': serviceName, 'service.namespace': 'farmaclic', }); // Metrics → OTLP → Alloy → Prometheus remote_write. The PeriodicExportingMetricReader // makes the auto-instrumentations (http/express/redis) emit request duration/error // metrics with no extra code, on top of the custom business metrics in src/metrics.js. const metricReader = new PeriodicExportingMetricReader({ exportIntervalMillis: 15_000, exporter: new OTLPMetricExporter({ url: otlpEndpoint }), }); const sdk = new NodeSDK({ resource, traceExporter: new OTLPTraceExporter({ url: otlpEndpoint }), metricReader, instrumentations: [ getNodeAutoInstrumentations({ // Disable fs by default — it is noisy and rarely useful. '@opentelemetry/instrumentation-fs': { enabled: false }, '@opentelemetry/instrumentation-dns': { enabled: false }, }), new PinoInstrumentation(), ], }); if (process.env.NODE_ENV !== 'test') { sdk.start(); } const shutdown = async () => { try { if (process.env.NODE_ENV !== 'test') { await sdk.shutdown(); } } catch (err) { // eslint-disable-next-line no-console console.error('OpenTelemetry shutdown failed', err); } }; process.on('SIGTERM', shutdown); process.on('SIGINT', shutdown);