@adonisjs/otel 1.0.0-next.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 +9 -0
- package/README.md +38 -0
- package/build/configure.d.ts +2 -0
- package/build/configure.js +69 -0
- package/build/index.d.ts +11 -0
- package/build/index.js +19 -0
- package/build/providers/otel_provider.d.ts +11 -0
- package/build/providers/otel_provider.js +50 -0
- package/build/src/decorators.d.ts +68 -0
- package/build/src/decorators.js +102 -0
- package/build/src/define_config.d.ts +2 -0
- package/build/src/define_config.js +3 -0
- package/build/src/helpers.d.ts +174 -0
- package/build/src/helpers.js +236 -0
- package/build/src/middleware/otel_middleware.d.ts +20 -0
- package/build/src/middleware/otel_middleware.js +64 -0
- package/build/src/otel.d.ts +65 -0
- package/build/src/otel.js +329 -0
- package/build/src/start.d.ts +18 -0
- package/build/src/start.js +37 -0
- package/build/src/types/decorators.d.ts +13 -0
- package/build/src/types/decorators.js +1 -0
- package/build/src/types/index.d.ts +155 -0
- package/build/src/types/index.js +1 -0
- package/build/src/types/instrumentations.d.ts +79 -0
- package/build/src/types/instrumentations.js +1 -0
- package/build/src/types/logging.d.ts +12 -0
- package/build/src/types/logging.js +13 -0
- package/build/stubs/config.stub +12 -0
- package/build/stubs/main.d.ts +5 -0
- package/build/stubs/main.js +7 -0
- package/build/stubs/otel.stub +12 -0
- package/package.json +134 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { getNodeAutoInstrumentations, } from '@opentelemetry/auto-instrumentations-node';
|
|
2
|
+
import { resourceFromAttributes } from '@opentelemetry/resources';
|
|
3
|
+
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
4
|
+
import { ConsoleSpanExporter, ParentBasedSampler, SimpleSpanProcessor, TraceIdRatioBasedSampler, } from '@opentelemetry/sdk-trace-base';
|
|
5
|
+
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
|
|
6
|
+
import { ATTR_DEPLOYMENT_ENVIRONMENT_NAME, ATTR_SERVICE_INSTANCE_ID, } from '@opentelemetry/semantic-conventions/incubating';
|
|
7
|
+
import { HttpContext } from '@adonisjs/core/http';
|
|
8
|
+
/**
|
|
9
|
+
* OpenTelemetry SDK manager for AdonisJS.
|
|
10
|
+
*
|
|
11
|
+
* Provides sensible defaults and easy configuration for OpenTelemetry
|
|
12
|
+
* while allowing full customization when needed.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { OtelManager } from '@adonisjs/otel'
|
|
17
|
+
* import config from '#config/otel'
|
|
18
|
+
*
|
|
19
|
+
* const manager = OtelManager.create(config)
|
|
20
|
+
* manager?.start()
|
|
21
|
+
*
|
|
22
|
+
* // Later, on shutdown
|
|
23
|
+
* await manager?.shutdown()
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export class OtelManager {
|
|
27
|
+
static DEFAULT_IGNORED_URLS = [
|
|
28
|
+
'/health',
|
|
29
|
+
'/healthz',
|
|
30
|
+
'/internal/healthz',
|
|
31
|
+
'/ready',
|
|
32
|
+
'/readiness',
|
|
33
|
+
'/metrics',
|
|
34
|
+
'/internal/metrics',
|
|
35
|
+
'/favicon.ico',
|
|
36
|
+
];
|
|
37
|
+
static #instance = null;
|
|
38
|
+
sdk;
|
|
39
|
+
serviceName;
|
|
40
|
+
serviceVersion;
|
|
41
|
+
environment;
|
|
42
|
+
#config;
|
|
43
|
+
constructor(config) {
|
|
44
|
+
this.#config = config;
|
|
45
|
+
this.serviceName = this.#resolveServiceName();
|
|
46
|
+
this.serviceVersion = this.#resolveServiceVersion();
|
|
47
|
+
this.environment = this.#resolveEnvironment();
|
|
48
|
+
this.sdk = this.#createSdk();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Resolve the service name from config or environment
|
|
52
|
+
*/
|
|
53
|
+
#resolveServiceName() {
|
|
54
|
+
return (this.#config.serviceName ||
|
|
55
|
+
process.env.OTEL_SERVICE_NAME ||
|
|
56
|
+
process.env.APP_NAME ||
|
|
57
|
+
'unknown_service');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Resolve the service version from config or environment
|
|
61
|
+
*/
|
|
62
|
+
#resolveServiceVersion() {
|
|
63
|
+
return this.#config.serviceVersion || process.env.APP_VERSION || '0.0.0';
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve the environment from config or environment
|
|
67
|
+
*/
|
|
68
|
+
#resolveEnvironment() {
|
|
69
|
+
return this.#config.environment || process.env.APP_ENV || 'development';
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Build the OpenTelemetry Resource with service metadata
|
|
73
|
+
*/
|
|
74
|
+
#buildResource() {
|
|
75
|
+
return resourceFromAttributes({
|
|
76
|
+
[ATTR_SERVICE_NAME]: this.serviceName,
|
|
77
|
+
[ATTR_SERVICE_VERSION]: this.serviceVersion,
|
|
78
|
+
[ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: this.environment,
|
|
79
|
+
[ATTR_SERVICE_INSTANCE_ID]: process.env.HOSTNAME || crypto.randomUUID(),
|
|
80
|
+
...this.#config.resourceAttributes,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get the resolved list of ignored URLs from HTTP instrumentation config
|
|
85
|
+
*/
|
|
86
|
+
#getIgnoredUrls(httpConfig) {
|
|
87
|
+
const userUrls = httpConfig?.ignoredUrls || [];
|
|
88
|
+
const mergeWithDefaults = httpConfig?.mergeIgnoredUrls !== false;
|
|
89
|
+
if (!mergeWithDefaults) {
|
|
90
|
+
return userUrls;
|
|
91
|
+
}
|
|
92
|
+
return [...OtelManager.DEFAULT_IGNORED_URLS, ...userUrls];
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Check if a URL should be ignored based on the ignored URLs list.
|
|
96
|
+
* Supports exact matches and wildcard patterns (e.g., '/internal/*')
|
|
97
|
+
*/
|
|
98
|
+
#shouldIgnoreUrl(url, ignoredUrls) {
|
|
99
|
+
if (!url)
|
|
100
|
+
return false;
|
|
101
|
+
return ignoredUrls.some((pattern) => {
|
|
102
|
+
if (pattern.endsWith('/*')) {
|
|
103
|
+
const prefix = pattern.slice(0, -1);
|
|
104
|
+
return url.startsWith(prefix);
|
|
105
|
+
}
|
|
106
|
+
return url.startsWith(pattern);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Check if a value is an Instrumentation instance
|
|
111
|
+
*/
|
|
112
|
+
#isInstrumentationInstance(value) {
|
|
113
|
+
return (typeof value === 'object' &&
|
|
114
|
+
value !== null &&
|
|
115
|
+
'instrumentationName' in value &&
|
|
116
|
+
typeof value.instrumentationName === 'string');
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Check if a value is a "disabled" config
|
|
120
|
+
*/
|
|
121
|
+
#isDisabledConfig(value) {
|
|
122
|
+
return (typeof value === 'object' &&
|
|
123
|
+
value !== null &&
|
|
124
|
+
'enabled' in value &&
|
|
125
|
+
value.enabled === false);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Process user instrumentation configuration
|
|
129
|
+
*/
|
|
130
|
+
#processUserInstrumentations(userConfig) {
|
|
131
|
+
const customInstances = [];
|
|
132
|
+
const disabledSet = new Set();
|
|
133
|
+
const configOverrides = {};
|
|
134
|
+
let httpConfig;
|
|
135
|
+
let pinoConfig;
|
|
136
|
+
if (!userConfig)
|
|
137
|
+
return { customInstances, disabledSet, configOverrides, httpConfig, pinoConfig };
|
|
138
|
+
for (const [name, value] of Object.entries(userConfig)) {
|
|
139
|
+
if (this.#isDisabledConfig(value)) {
|
|
140
|
+
disabledSet.add(name);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (this.#isInstrumentationInstance(value)) {
|
|
144
|
+
customInstances.push(value);
|
|
145
|
+
disabledSet.add(name);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (name === '@opentelemetry/instrumentation-http') {
|
|
149
|
+
httpConfig = value;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (name === '@opentelemetry/instrumentation-pino') {
|
|
153
|
+
pinoConfig = value;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
configOverrides[name] = value;
|
|
157
|
+
}
|
|
158
|
+
return { customInstances, disabledSet, configOverrides, httpConfig, pinoConfig };
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Build the base instrumentation configuration
|
|
162
|
+
*/
|
|
163
|
+
#buildBaseInstrumentationConfig() {
|
|
164
|
+
return {
|
|
165
|
+
'@opentelemetry/instrumentation-net': { enabled: false },
|
|
166
|
+
'@opentelemetry/instrumentation-dns': { enabled: false },
|
|
167
|
+
'@opentelemetry/instrumentation-socket.io': { enabled: false },
|
|
168
|
+
'@opentelemetry/instrumentation-pg': { enabled: false },
|
|
169
|
+
'@opentelemetry/instrumentation-mysql': { enabled: false },
|
|
170
|
+
'@opentelemetry/instrumentation-mysql2': { enabled: false },
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Build instrumentations from user config merged with defaults
|
|
175
|
+
*/
|
|
176
|
+
#buildInstrumentations() {
|
|
177
|
+
const { customInstances, disabledSet, configOverrides, httpConfig, pinoConfig } = this.#processUserInstrumentations(this.#config.instrumentations);
|
|
178
|
+
const mergedConfig = this.#buildBaseInstrumentationConfig();
|
|
179
|
+
for (const [name, config] of Object.entries(configOverrides)) {
|
|
180
|
+
mergedConfig[name] = {
|
|
181
|
+
...mergedConfig[name],
|
|
182
|
+
...config,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
for (const name of disabledSet) {
|
|
186
|
+
mergedConfig[name] = { enabled: false };
|
|
187
|
+
}
|
|
188
|
+
this.#applyHttpInstrumentationConfig(mergedConfig, httpConfig);
|
|
189
|
+
this.#applyPinoInstrumentationConfig(mergedConfig, pinoConfig);
|
|
190
|
+
const autoInstrumentations = getNodeAutoInstrumentations(mergedConfig);
|
|
191
|
+
return [...autoInstrumentations, ...customInstances];
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Apply HTTP instrumentation configuration with smart merging of ignoreIncomingRequestHook
|
|
195
|
+
*/
|
|
196
|
+
#applyHttpInstrumentationConfig(mergedConfig, userHttpConfig) {
|
|
197
|
+
const httpKey = '@opentelemetry/instrumentation-http';
|
|
198
|
+
const currentConfig = mergedConfig[httpKey];
|
|
199
|
+
if (currentConfig && 'enabled' in currentConfig && currentConfig.enabled === false) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const ignoredUrls = this.#getIgnoredUrls(userHttpConfig);
|
|
203
|
+
const userIgnoreHook = userHttpConfig?.ignoreIncomingRequestHook;
|
|
204
|
+
mergedConfig[httpKey] = {
|
|
205
|
+
...currentConfig,
|
|
206
|
+
...userHttpConfig,
|
|
207
|
+
ignoreIncomingRequestHook: (req) => {
|
|
208
|
+
if (this.#shouldIgnoreUrl(req.url, ignoredUrls))
|
|
209
|
+
return true;
|
|
210
|
+
if (userIgnoreHook)
|
|
211
|
+
return userIgnoreHook(req);
|
|
212
|
+
return false;
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Apply Pino instrumentation configuration with smart merging of logHook
|
|
218
|
+
*/
|
|
219
|
+
#applyPinoInstrumentationConfig(mergedConfig, userPinoConfig) {
|
|
220
|
+
const pinoKey = '@opentelemetry/instrumentation-pino';
|
|
221
|
+
const currentConfig = mergedConfig[pinoKey];
|
|
222
|
+
if (currentConfig && 'enabled' in currentConfig && currentConfig.enabled === false) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const userLogHook = userPinoConfig?.logHook;
|
|
226
|
+
const internalLogHook = (span) => {
|
|
227
|
+
const httpContext = HttpContext.get();
|
|
228
|
+
span.setAttribute('http.route', httpContext?.route?.pattern || '');
|
|
229
|
+
};
|
|
230
|
+
mergedConfig[pinoKey] = {
|
|
231
|
+
...currentConfig,
|
|
232
|
+
...userPinoConfig,
|
|
233
|
+
logHook: (span, record) => {
|
|
234
|
+
internalLogHook(span);
|
|
235
|
+
if (userLogHook)
|
|
236
|
+
userLogHook(span, record);
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Build sampler from samplingRatio if no explicit sampler is provided
|
|
242
|
+
*/
|
|
243
|
+
#buildSampler() {
|
|
244
|
+
if (this.#config.sampler)
|
|
245
|
+
return this.#config.sampler;
|
|
246
|
+
if (this.#config.samplingRatio !== undefined) {
|
|
247
|
+
const ratio = Math.max(0, Math.min(1, this.#config.samplingRatio));
|
|
248
|
+
return new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(ratio) });
|
|
249
|
+
}
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Build span processors, including debug console exporter if enabled
|
|
254
|
+
*/
|
|
255
|
+
#buildSpanProcessors() {
|
|
256
|
+
const processors = this.#config.spanProcessors ? [...this.#config.spanProcessors] : [];
|
|
257
|
+
if (this.#config.debug)
|
|
258
|
+
processors.push(new SimpleSpanProcessor(new ConsoleSpanExporter()));
|
|
259
|
+
return processors.length > 0 ? processors : undefined;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Create the NodeSDK instance with all configuration
|
|
263
|
+
*/
|
|
264
|
+
#createSdk() {
|
|
265
|
+
const resource = this.#buildResource();
|
|
266
|
+
const instrumentations = this.#buildInstrumentations();
|
|
267
|
+
const sampler = this.#buildSampler();
|
|
268
|
+
const spanProcessors = this.#buildSpanProcessors();
|
|
269
|
+
return new NodeSDK({
|
|
270
|
+
...this.#config,
|
|
271
|
+
resource,
|
|
272
|
+
serviceName: this.serviceName,
|
|
273
|
+
instrumentations,
|
|
274
|
+
...(sampler && { sampler }),
|
|
275
|
+
...(spanProcessors && { spanProcessors }),
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Start the OpenTelemetry SDK
|
|
280
|
+
*/
|
|
281
|
+
start() {
|
|
282
|
+
this.sdk.start();
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Gracefully shutdown the OpenTelemetry SDK
|
|
286
|
+
*/
|
|
287
|
+
async shutdown() {
|
|
288
|
+
await this.sdk.shutdown();
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Check if OpenTelemetry should be enabled based on config.
|
|
292
|
+
* Defaults to false when NODE_ENV === 'test'.
|
|
293
|
+
*/
|
|
294
|
+
static isEnabled(config) {
|
|
295
|
+
if (config.enabled !== undefined)
|
|
296
|
+
return config.enabled;
|
|
297
|
+
return process.env.NODE_ENV !== 'test';
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Create and configure the OpenTelemetry SDK manager.
|
|
301
|
+
*
|
|
302
|
+
* Returns null if OpenTelemetry is disabled.
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* ```ts
|
|
306
|
+
* import { OtelManager } from '@adonisjs/otel'
|
|
307
|
+
* import config from '#config/otel'
|
|
308
|
+
*
|
|
309
|
+
* const manager = OtelManager.create(config)
|
|
310
|
+
* manager?.start()
|
|
311
|
+
*
|
|
312
|
+
* // Later, on shutdown
|
|
313
|
+
* await manager?.shutdown()
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
static create(config) {
|
|
317
|
+
if (!OtelManager.isEnabled(config))
|
|
318
|
+
return null;
|
|
319
|
+
OtelManager.#instance = new OtelManager(config);
|
|
320
|
+
return OtelManager.#instance;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Get the global OtelManager instance.
|
|
324
|
+
* Returns null if OpenTelemetry is disabled or not yet initialized.
|
|
325
|
+
*/
|
|
326
|
+
static getInstance() {
|
|
327
|
+
return OtelManager.#instance;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry initialization file.
|
|
3
|
+
*
|
|
4
|
+
* This file should be loaded BEFORE your application starts
|
|
5
|
+
* to enable auto-instrumentation:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* // bin/otel.ts
|
|
9
|
+
* import { init } from '@adonisjs/otel/init'
|
|
10
|
+
* await init(import.meta.dirname)
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* Then import it first in bin/server.ts:
|
|
14
|
+
* ```ts
|
|
15
|
+
* import './otel.js'
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function init(dirname: string): Promise<void>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry initialization file.
|
|
3
|
+
*
|
|
4
|
+
* This file should be loaded BEFORE your application starts
|
|
5
|
+
* to enable auto-instrumentation:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* // bin/otel.ts
|
|
9
|
+
* import { init } from '@adonisjs/otel/init'
|
|
10
|
+
* await init(import.meta.dirname)
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* Then import it first in bin/server.ts:
|
|
14
|
+
* ```ts
|
|
15
|
+
* import './otel.js'
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import { createAddHookMessageChannel } from 'import-in-the-middle';
|
|
19
|
+
import { register } from 'node:module';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
export async function init(dirname) {
|
|
22
|
+
// Setup import-in-the-middle hooks for auto-instrumentation
|
|
23
|
+
const { registerOptions, waitForAllMessagesAcknowledged } = createAddHookMessageChannel();
|
|
24
|
+
register('import-in-the-middle/hook.mjs', import.meta.url, registerOptions);
|
|
25
|
+
// Import SDK functions after hooks are registered
|
|
26
|
+
const { OtelManager } = await import('./otel.js');
|
|
27
|
+
const configPath = join(dirname, '../config/otel.ts');
|
|
28
|
+
const config = await import(configPath).then((mod) => mod.default || mod);
|
|
29
|
+
if (!config)
|
|
30
|
+
throw new Error(`Otel configuration not found at ${configPath}`);
|
|
31
|
+
// Check if OTEL is enabled
|
|
32
|
+
if (!OtelManager.isEnabled(config))
|
|
33
|
+
return;
|
|
34
|
+
const manager = OtelManager.create(config);
|
|
35
|
+
manager?.start();
|
|
36
|
+
await waitForAllMessagesAcknowledged();
|
|
37
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for span decorators
|
|
3
|
+
*/
|
|
4
|
+
export interface SpanOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Custom span name. Defaults to `ClassName.methodName`
|
|
7
|
+
*/
|
|
8
|
+
name?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Additional attributes to add to the span
|
|
11
|
+
*/
|
|
12
|
+
attributes?: Record<string, string | number | boolean>;
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { NodeSDKConfiguration } from '@opentelemetry/sdk-node';
|
|
2
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
3
|
+
export type { InstrumentationValue, CustomInstrumentationValue, HttpInstrumentationConfig, PinoInstrumentationConfig, InstrumentationsConfig, } from './instrumentations.js';
|
|
4
|
+
export type { SpanOptions } from './decorators.js';
|
|
5
|
+
export { hiddenFields, type HiddenField, type OtelLoggingPresetOptions } from './logging.js';
|
|
6
|
+
import type { InstrumentationsConfig } from './instrumentations.js';
|
|
7
|
+
/**
|
|
8
|
+
* Configuration for @adonisjs/otel
|
|
9
|
+
*
|
|
10
|
+
* Extends NodeSDKConfiguration with simplified options and good defaults.
|
|
11
|
+
* All options are optional - the package works out of the box.
|
|
12
|
+
*/
|
|
13
|
+
export interface OtelConfig extends Partial<Omit<NodeSDKConfiguration, 'resource' | 'instrumentations'>> {
|
|
14
|
+
/**
|
|
15
|
+
* Enable or disable OpenTelemetry entirely.
|
|
16
|
+
*
|
|
17
|
+
* When disabled, no SDK is initialized and no traces are collected.
|
|
18
|
+
* Useful for tests or local development without a collector.
|
|
19
|
+
*
|
|
20
|
+
* @default true (false in 'test' environment)
|
|
21
|
+
*/
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Sampling ratio for traces (0.0 to 1.0).
|
|
25
|
+
*
|
|
26
|
+
* - `1.0` = 100% of traces are sampled (default)
|
|
27
|
+
* - `0.1` = 10% of traces are sampled
|
|
28
|
+
* - `0.0` = No traces are sampled
|
|
29
|
+
*
|
|
30
|
+
* This option is ignored if `sampler` is explicitly provided.
|
|
31
|
+
*
|
|
32
|
+
* @default 1.0
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* defineConfig({
|
|
37
|
+
* samplingRatio: 0.1, // Sample 10% of traces in production
|
|
38
|
+
* })
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
samplingRatio?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Enable debug mode to print spans to the console.
|
|
44
|
+
*
|
|
45
|
+
* When enabled, a `ConsoleSpanExporter` is automatically added
|
|
46
|
+
* to help with local development and debugging.
|
|
47
|
+
*
|
|
48
|
+
* @default false
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* defineConfig({
|
|
53
|
+
* debug: true, // Print spans to console
|
|
54
|
+
* })
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
debug?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Service name for telemetry identification
|
|
60
|
+
* @default process.env.OTEL_SERVICE_NAME || process.env.APP_NAME || 'unknown_service'
|
|
61
|
+
*/
|
|
62
|
+
serviceName?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Service version
|
|
65
|
+
* @default process.env.APP_VERSION || '0.0.0'
|
|
66
|
+
*/
|
|
67
|
+
serviceVersion?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Deployment environment (production, staging, development, etc.)
|
|
70
|
+
* @default process.env.APP_ENV || 'development'
|
|
71
|
+
*/
|
|
72
|
+
environment?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Additional resource attributes to include in telemetry
|
|
75
|
+
*/
|
|
76
|
+
resourceAttributes?: Record<string, string>;
|
|
77
|
+
/**
|
|
78
|
+
* Instrumentations configuration.
|
|
79
|
+
*
|
|
80
|
+
* - Pass a config object to merge with defaults
|
|
81
|
+
* - Pass an Instrumentation instance for custom instrumentations
|
|
82
|
+
* - Pass `{ enabled: false }` to disable
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* instrumentations: {
|
|
87
|
+
* '@opentelemetry/instrumentation-http': {
|
|
88
|
+
* ignoredUrls: ['/internal/*', '/custom-health'],
|
|
89
|
+
* },
|
|
90
|
+
* '@opentelemetry/instrumentation-pino': {
|
|
91
|
+
* logHook: (span, record) => {
|
|
92
|
+
* record.tenant_id = getTenantId()
|
|
93
|
+
* },
|
|
94
|
+
* },
|
|
95
|
+
* '@opentelemetry/instrumentation-pg': { enabled: false },
|
|
96
|
+
* }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
instrumentations?: InstrumentationsConfig;
|
|
100
|
+
/**
|
|
101
|
+
* Configure automatic user context extraction in the middleware.
|
|
102
|
+
*
|
|
103
|
+
* By default, extracts `id`, `email`, `role` from `ctx.auth.user`.
|
|
104
|
+
* Set to `false` to disable entirely.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* userContext: {
|
|
109
|
+
* resolver: async (ctx) => ({
|
|
110
|
+
* id: ctx.auth.user.id,
|
|
111
|
+
* tenantId: ctx.auth.user.tenantId,
|
|
112
|
+
* }),
|
|
113
|
+
* }
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
userContext?: false | UserContextConfig;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Result returned by the user context resolver
|
|
120
|
+
*/
|
|
121
|
+
export interface UserContextResult {
|
|
122
|
+
id: string | number;
|
|
123
|
+
email?: string;
|
|
124
|
+
role?: string;
|
|
125
|
+
[key: string]: unknown;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Configuration for automatic user context extraction
|
|
129
|
+
*/
|
|
130
|
+
export interface UserContextConfig {
|
|
131
|
+
/**
|
|
132
|
+
* Enable/disable automatic user extraction from @adonisjs/auth
|
|
133
|
+
* @default true
|
|
134
|
+
*/
|
|
135
|
+
enabled?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Custom function to extract user context from the HttpContext.
|
|
138
|
+
* When provided, replaces the default extraction logic.
|
|
139
|
+
* Return `null` to skip setting user context for this request.
|
|
140
|
+
*/
|
|
141
|
+
resolver?: (ctx: HttpContext) => UserContextResult | null | Promise<UserContextResult | null>;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* User information for tracing
|
|
145
|
+
*/
|
|
146
|
+
export interface UserContext {
|
|
147
|
+
id: string | number;
|
|
148
|
+
email?: string;
|
|
149
|
+
role?: string;
|
|
150
|
+
[key: string]: unknown;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Headers carrier type for context propagation
|
|
154
|
+
*/
|
|
155
|
+
export type HeadersCarrier = Record<string, string | string[] | undefined>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { hiddenFields } from './logging.js';
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Span } from '@opentelemetry/api';
|
|
2
|
+
import type { InstrumentationConfigMap } from '@opentelemetry/auto-instrumentations-node';
|
|
3
|
+
import type { Instrumentation } from '@opentelemetry/instrumentation';
|
|
4
|
+
/**
|
|
5
|
+
* Value for a known instrumentation: config object, instance, or disabled
|
|
6
|
+
*/
|
|
7
|
+
export type InstrumentationValue<K extends keyof InstrumentationConfigMap> = InstrumentationConfigMap[K] | Instrumentation | {
|
|
8
|
+
enabled: false;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Value for a custom instrumentation: instance or disabled
|
|
12
|
+
*/
|
|
13
|
+
export type CustomInstrumentationValue = Instrumentation | {
|
|
14
|
+
enabled: false;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Extended config for @opentelemetry/instrumentation-http.
|
|
18
|
+
* Adds AdonisJS-specific helpers for URL filtering.
|
|
19
|
+
*/
|
|
20
|
+
export interface HttpInstrumentationConfig extends Omit<NonNullable<InstrumentationConfigMap['@opentelemetry/instrumentation-http']>, 'ignoreIncomingRequestHook'> {
|
|
21
|
+
/**
|
|
22
|
+
* URLs to ignore in HTTP instrumentation.
|
|
23
|
+
* Merged with defaults unless `mergeIgnoredUrls` is false.
|
|
24
|
+
* Supports wildcards: '/internal/*'
|
|
25
|
+
*
|
|
26
|
+
* @default ['/health', '/healthz', '/ready', '/metrics', '/favicon.ico', ...]
|
|
27
|
+
*/
|
|
28
|
+
ignoredUrls?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* Whether to merge ignoredUrls with default ignored URLs or replace them entirely.
|
|
31
|
+
* @default true
|
|
32
|
+
*/
|
|
33
|
+
mergeIgnoredUrls?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Custom hook to ignore specific incoming requests.
|
|
36
|
+
* Called AFTER the ignoredUrls check.
|
|
37
|
+
*/
|
|
38
|
+
ignoreIncomingRequestHook?: (request: {
|
|
39
|
+
url?: string;
|
|
40
|
+
}) => boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Extended config for @opentelemetry/instrumentation-pino.
|
|
44
|
+
* Allows custom logHook while preserving the internal one.
|
|
45
|
+
*/
|
|
46
|
+
export interface PinoInstrumentationConfig extends Omit<NonNullable<InstrumentationConfigMap['@opentelemetry/instrumentation-pino']>, 'logHook'> {
|
|
47
|
+
/**
|
|
48
|
+
* Custom log hook executed AFTER the internal hook that adds route info.
|
|
49
|
+
* Use this to add your own properties to log records.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* logHook: (span, record) => {
|
|
54
|
+
* record.tenant_id = getCurrentTenantId()
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
logHook?: (span: Span, record: Record<string, unknown>) => void;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Keys of instrumentations with custom extended configs
|
|
62
|
+
*/
|
|
63
|
+
type ExtendedInstrumentationKeys = '@opentelemetry/instrumentation-http' | '@opentelemetry/instrumentation-pino';
|
|
64
|
+
/**
|
|
65
|
+
* Instrumentations configuration map.
|
|
66
|
+
*
|
|
67
|
+
* - HTTP and Pino instrumentations have extended configs
|
|
68
|
+
*/
|
|
69
|
+
export type InstrumentationsConfig = {
|
|
70
|
+
'@opentelemetry/instrumentation-http'?: HttpInstrumentationConfig | Instrumentation | {
|
|
71
|
+
enabled: false;
|
|
72
|
+
};
|
|
73
|
+
'@opentelemetry/instrumentation-pino'?: PinoInstrumentationConfig | Instrumentation | {
|
|
74
|
+
enabled: false;
|
|
75
|
+
};
|
|
76
|
+
} & {
|
|
77
|
+
[K in Exclude<keyof InstrumentationConfigMap, ExtendedInstrumentationKeys>]?: InstrumentationValue<K>;
|
|
78
|
+
};
|
|
79
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fields hidden by default in pino-pretty output
|
|
3
|
+
*/
|
|
4
|
+
export declare const hiddenFields: readonly ["pid", "hostname", "trace_id", "span_id", "trace_flags", "route", "request_id", "x-request-id"];
|
|
5
|
+
export type HiddenField = (typeof hiddenFields)[number];
|
|
6
|
+
export interface OtelLoggingPresetOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Fields to keep visible in logs (not hidden).
|
|
9
|
+
* Useful if you want to see trace context in development.
|
|
10
|
+
*/
|
|
11
|
+
keep?: HiddenField[];
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{{{
|
|
2
|
+
exports({ to: app.configPath('otel.ts') })
|
|
3
|
+
}}}
|
|
4
|
+
import { defineConfig } from '@adonisjs/otel'
|
|
5
|
+
import env from '#start/env'
|
|
6
|
+
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
serviceName: env.get('APP_NAME'),
|
|
9
|
+
serviceVersion: env.get('APP_VERSION'),
|
|
10
|
+
environment: env.get('APP_ENV'),
|
|
11
|
+
})
|
|
12
|
+
|