@lokalise/opentelemetry-fastify-bootstrap 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2024 Lokalise, Inc.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # OpenTelemetry Fastify Bootstrap
2
+
3
+ This package provides a pre-configured OpenTelemetry setup for Fastify applications with automatic instrumentation.
4
+
5
+ **Important:** OpenTelemetry must be initialized before importing any modules you want to instrument (fastify, http, etc.), because it works by patching module exports at import time. This requires using dynamic imports to control the loading order.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @lokalise/opentelemetry-fastify-bootstrap
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Your application entry point must use dynamic `await import()` to ensure OpenTelemetry initializes before other modules are loaded:
16
+
17
+ ```ts
18
+ // index.ts (entry point)
19
+
20
+ // This MUST be first - initializes OpenTelemetry before anything else
21
+ const { initOpenTelemetry } = await import('@lokalise/opentelemetry-fastify-bootstrap')
22
+ initOpenTelemetry({
23
+ skippedPaths: ['/health', '/ready', '/live', '/metrics', '/'],
24
+ })
25
+
26
+ // Now dynamically import your actual server code
27
+ const server = await import('./server.ts')
28
+ await server.start()
29
+ ```
30
+
31
+ **Why dynamic imports?** Static imports in ESM are hoisted and resolved together before any code executes. Dynamic `await import()` ensures sequential loading - the package fully initializes before server.ts is even parsed.
32
+
33
+ ### Using defaults
34
+
35
+ If you don't need custom skipped paths:
36
+
37
+ ```ts
38
+ const { initOpenTelemetry } = await import('@lokalise/opentelemetry-fastify-bootstrap')
39
+ initOpenTelemetry()
40
+ ```
41
+
42
+ ## Configuration
43
+
44
+ ### Environment Variables
45
+
46
+ | Variable | Description | Default |
47
+ |----------|-------------|---------|
48
+ | `NODE_ENV` | When set to `test`, OpenTelemetry is disabled | - |
49
+ | `OPEN_TELEMETRY_ENABLED` | Set to `true` to enable OpenTelemetry | `false` |
50
+ | `OPEN_TELEMETRY_EXPORTER_URL` | OTLP gRPC exporter URL | `grpc://localhost:4317` |
51
+
52
+ ### Options
53
+
54
+ | Option | Type | Default | Description |
55
+ |--------|------|---------|-------------|
56
+ | `skippedPaths` | `string[]` | `['/health', '/metrics', '/']` | Paths to exclude from tracing |
57
+
58
+ ## Features
59
+
60
+ - Automatic instrumentation for Node.js applications via `@opentelemetry/auto-instrumentations-node`
61
+ - Fastify-specific instrumentation via `@fastify/otel`
62
+ - OTLP gRPC trace exporter
63
+ - Configurable path filtering
64
+ - Graceful shutdown support
65
+
66
+ ## Graceful Shutdown
67
+
68
+ To properly shutdown the OpenTelemetry SDK when your application exits:
69
+
70
+ ```ts
71
+ import { gracefulOtelShutdown } from '@lokalise/opentelemetry-fastify-bootstrap'
72
+
73
+ process.on('SIGTERM', async () => {
74
+ await gracefulOtelShutdown()
75
+ process.exit(0)
76
+ })
77
+ ```
@@ -0,0 +1,25 @@
1
+ export interface OpenTelemetryOptions {
2
+ /**
3
+ * Paths to exclude from tracing.
4
+ * @default ['/health', '/metrics', '/']
5
+ */
6
+ skippedPaths?: string[];
7
+ }
8
+ /**
9
+ * Initialize OpenTelemetry instrumentation.
10
+ *
11
+ * IMPORTANT: This function must be called BEFORE importing any other modules
12
+ * that you want to instrument (fastify, http, etc.).
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * // At the very top of your entry point
17
+ * import { initOpenTelemetry } from '@lokalise/opentelemetry-fastify-bootstrap'
18
+ * initOpenTelemetry({ skippedPaths: ['/health', '/ready', '/live'] })
19
+ *
20
+ * // Only import other modules AFTER initialization
21
+ * import fastify from 'fastify'
22
+ * ```
23
+ */
24
+ export declare function initOpenTelemetry(options?: OpenTelemetryOptions): void;
25
+ export declare function gracefulOtelShutdown(): Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,124 @@
1
+ import { FastifyOtelInstrumentation } from '@fastify/otel';
2
+ import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
3
+ import { OTLPTraceExporter as OTLPTraceExporterGrpc } from '@opentelemetry/exporter-trace-otlp-grpc';
4
+ import { NodeSDK } from '@opentelemetry/sdk-node';
5
+ function createLogEntry(level, msg, data) {
6
+ return {
7
+ level,
8
+ time: Date.now(),
9
+ ...data,
10
+ msg,
11
+ };
12
+ }
13
+ function log(level, msgOrData, msg) {
14
+ let logEntry;
15
+ if (typeof msgOrData === 'string') {
16
+ logEntry = createLogEntry(level, msgOrData);
17
+ }
18
+ else {
19
+ logEntry = createLogEntry(level, msg ?? '', msgOrData);
20
+ }
21
+ const output = JSON.stringify(logEntry);
22
+ if (level === 'error') {
23
+ // biome-ignore lint/suspicious/noConsole: this is the logger implementation
24
+ console.error(output);
25
+ }
26
+ else if (level === 'warn') {
27
+ // biome-ignore lint/suspicious/noConsole: this is the logger implementation
28
+ console.warn(output);
29
+ }
30
+ else {
31
+ // biome-ignore lint/suspicious/noConsole: this is the logger implementation
32
+ console.log(output);
33
+ }
34
+ }
35
+ const logger = {
36
+ info: (msgOrData, msg) => log('info', msgOrData, msg),
37
+ error: (msgOrData, msg) => log('error', msgOrData, msg),
38
+ warn: (msgOrData, msg) => log('warn', msgOrData, msg),
39
+ debug: (msgOrData, msg) => log('debug', msgOrData, msg),
40
+ };
41
+ const DEFAULT_SKIPPED_PATHS = ['/health', '/metrics', '/'];
42
+ let isInstrumentationRegistered = false;
43
+ let sdk;
44
+ /**
45
+ * Initialize OpenTelemetry instrumentation.
46
+ *
47
+ * IMPORTANT: This function must be called BEFORE importing any other modules
48
+ * that you want to instrument (fastify, http, etc.).
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * // At the very top of your entry point
53
+ * import { initOpenTelemetry } from '@lokalise/opentelemetry-fastify-bootstrap'
54
+ * initOpenTelemetry({ skippedPaths: ['/health', '/ready', '/live'] })
55
+ *
56
+ * // Only import other modules AFTER initialization
57
+ * import fastify from 'fastify'
58
+ * ```
59
+ */
60
+ export function initOpenTelemetry(options = {}) {
61
+ const { skippedPaths = DEFAULT_SKIPPED_PATHS } = options;
62
+ logger.info('[OTEL] initOpenTelemetry called');
63
+ const isOpenTelemetryEnabled = process.env.NODE_ENV !== 'test' && process.env.OPEN_TELEMETRY_ENABLED?.toLowerCase() === 'true';
64
+ logger.info({
65
+ nodeEnv: process.env.NODE_ENV,
66
+ openTelemetryEnabled: process.env.OPEN_TELEMETRY_ENABLED,
67
+ isOpenTelemetryEnabled,
68
+ skippedPaths,
69
+ }, '[OTEL] Configuration');
70
+ if (isOpenTelemetryEnabled && !isInstrumentationRegistered) {
71
+ logger.info('[OTEL] Initializing OpenTelemetry SDK...');
72
+ // Configure the OTLP trace exporter
73
+ const exporterUrl = process.env.OPEN_TELEMETRY_EXPORTER_URL || 'grpc://localhost:4317';
74
+ logger.info({ exporterUrl }, '[OTEL] Configuring trace exporter');
75
+ const traceExporter = new OTLPTraceExporterGrpc({
76
+ // optional - url default value is http://localhost:4318/v1/traces (http)
77
+ // or grpc://localhost:4317/opentelemetry.proto.collector.trace.v1.TraceService/Export (grpc)
78
+ url: exporterUrl,
79
+ });
80
+ // Setup SDK
81
+ sdk = new NodeSDK({
82
+ traceExporter,
83
+ instrumentations: [
84
+ getNodeAutoInstrumentations({
85
+ '@opentelemetry/instrumentation-fastify': {
86
+ enabled: false,
87
+ },
88
+ }),
89
+ new FastifyOtelInstrumentation({
90
+ registerOnInitialization: true,
91
+ ignorePaths: (req) => {
92
+ if (!req.url)
93
+ return false;
94
+ // Extract path without query string, normalize empty to '/'
95
+ const path = req.url.split('?')[0] || '/';
96
+ return skippedPaths.includes(path);
97
+ },
98
+ }),
99
+ ],
100
+ });
101
+ sdk.start();
102
+ isInstrumentationRegistered = true;
103
+ logger.info('[OTEL] SDK started successfully - ready to send traces');
104
+ }
105
+ else {
106
+ logger.info('[OTEL] OpenTelemetry is disabled or already registered');
107
+ }
108
+ }
109
+ export async function gracefulOtelShutdown() {
110
+ logger.info('[OTEL] Shutdown requested');
111
+ if (!sdk) {
112
+ logger.info('[OTEL] No SDK instance to shutdown');
113
+ return;
114
+ }
115
+ try {
116
+ await sdk.shutdown();
117
+ isInstrumentationRegistered = false;
118
+ logger.info('[OTEL] SDK shutdown completed successfully');
119
+ }
120
+ catch (error) {
121
+ logger.error({ error }, '[OTEL] Error during SDK shutdown');
122
+ }
123
+ }
124
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@lokalise/opentelemetry-fastify-bootstrap",
3
+ "version": "1.1.0",
4
+ "type": "module",
5
+ "files": [
6
+ "dist",
7
+ "README.md",
8
+ "LICENSE.md"
9
+ ],
10
+ "license": "Apache-2.0",
11
+ "main": "./dist/index.js",
12
+ "module": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": "./dist/index.js",
16
+ "./package.json": "./package.json"
17
+ },
18
+ "keywords": [
19
+ "opentelemetry",
20
+ "otel",
21
+ "fastify",
22
+ "tracing",
23
+ "instrumentation",
24
+ "bootstrap"
25
+ ],
26
+ "homepage": "https://github.com/lokalise/shared-ts-libs",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git://github.com/lokalise/shared-ts-libs.git"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "scripts": {
35
+ "build": "rimraf dist && tsc --project tsconfig.build.json",
36
+ "lint": "biome check . && tsc",
37
+ "lint:fix": "biome check --write",
38
+ "prepublishOnly": "npm run build",
39
+ "package-version": "echo $npm_package_version",
40
+ "postversion": "biome check --write package.json"
41
+ },
42
+ "dependencies": {},
43
+ "peerDependencies": {
44
+ "fastify": ">=5.0.0",
45
+ "@fastify/otel": ">=0.16.0",
46
+ "@opentelemetry/auto-instrumentations-node": ">=0.67.3",
47
+ "@opentelemetry/exporter-trace-otlp-grpc": ">=0.208.0",
48
+ "@opentelemetry/sdk-node": ">=0.208.0"
49
+ },
50
+ "devDependencies": {
51
+ "@biomejs/biome": "^2.3.7",
52
+ "@lokalise/biome-config": "^3.1.0",
53
+ "@lokalise/tsconfig": "~1.2.0",
54
+ "rimraf": "^6.1.2",
55
+ "typescript": "5.9.3"
56
+ }
57
+ }