@saidsef/tracing-node 4.3.0 → 5.0.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/README.md +5 -0
- package/libs/index.mjs +85 -27
- package/libs/index.test.mjs +140 -1
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ Effortlessly supercharge your applications with world-class distributed tracing!
|
|
|
22
22
|
| AWS SDK | Cloud service instrumentation |
|
|
23
23
|
| Pino logger | Integration with trace/span IDs |
|
|
24
24
|
| Node runtime metrics | Event loop, garbage collection, heap |
|
|
25
|
+
| Log export | Pino records over OTLP, correlated by trace |
|
|
25
26
|
| RED metrics | Request duration histograms over OTLP |
|
|
26
27
|
| DNS/FS instrumentation | Optional monitoring |
|
|
27
28
|
| Resource detection | Host, OS, process, container |
|
|
@@ -47,6 +48,8 @@ The W3C Trace Context propagation this library registers is what lets Tempo pair
|
|
|
47
48
|
|
|
48
49
|
Metrics go to the same endpoint by default and land in Mimir. They are recorded before the sampler runs, so they stay complete however far trace volume is turned down.
|
|
49
50
|
|
|
51
|
+
Pino log records go to the same endpoint and land in Loki, each carrying the trace and span id of the request that wrote it. No log agent or file scraping sits in between.
|
|
52
|
+
|
|
50
53
|
## Instalation
|
|
51
54
|
|
|
52
55
|
```
|
|
@@ -86,6 +89,8 @@ setupTracing({hostname: 'hostname', serviceName: 'service_name', url: 'endpoint'
|
|
|
86
89
|
| enableMetrics | boolean | export metrics as well as traces | No | `true` |
|
|
87
90
|
| metricsUrl | string | metrics endpoint, when it differs from `url` | No | `url` |
|
|
88
91
|
| metricExportIntervalMillis | number | how often metrics are exported | No | `60000` |
|
|
92
|
+
| enableLogs | boolean | send Pino log records over OTLP | No | `true` |
|
|
93
|
+
| logsUrl | string | logs endpoint, when it differs from `url` | No | `url` |
|
|
89
94
|
|
|
90
95
|
## Documentation
|
|
91
96
|
|
package/libs/index.mjs
CHANGED
|
@@ -21,8 +21,10 @@ import {diag, DiagConsoleLogger, DiagLogLevel, metrics} from '@opentelemetry/api
|
|
|
21
21
|
import {HttpInstrumentation} from '@opentelemetry/instrumentation-http';
|
|
22
22
|
import {DnsInstrumentation} from '@opentelemetry/instrumentation-dns';
|
|
23
23
|
import {ElasticsearchInstrumentation} from 'opentelemetry-instrumentation-elasticsearch';
|
|
24
|
-
import {ExpressInstrumentation} from '@opentelemetry/instrumentation-express';
|
|
24
|
+
import {ExpressInstrumentation, ExpressLayerType} from '@opentelemetry/instrumentation-express';
|
|
25
|
+
import {logs} from '@opentelemetry/api-logs';
|
|
25
26
|
import {NodeTracerProvider} from '@opentelemetry/sdk-trace-node';
|
|
27
|
+
import {OTLPLogExporter} from '@opentelemetry/exporter-logs-otlp-grpc';
|
|
26
28
|
import {OTLPMetricExporter} from '@opentelemetry/exporter-metrics-otlp-grpc';
|
|
27
29
|
import {OTLPTraceExporter} from '@opentelemetry/exporter-trace-otlp-grpc';
|
|
28
30
|
import {PinoInstrumentation} from '@opentelemetry/instrumentation-pino';
|
|
@@ -31,6 +33,7 @@ import {IORedisInstrumentation} from '@opentelemetry/instrumentation-ioredis';
|
|
|
31
33
|
import {registerInstrumentations} from '@opentelemetry/instrumentation';
|
|
32
34
|
import {RuntimeNodeInstrumentation} from '@opentelemetry/instrumentation-runtime-node';
|
|
33
35
|
import {MeterProvider, PeriodicExportingMetricReader} from '@opentelemetry/sdk-metrics';
|
|
36
|
+
import {BatchLogRecordProcessor, LoggerProvider} from '@opentelemetry/sdk-logs';
|
|
34
37
|
import {FsInstrumentation} from '@opentelemetry/instrumentation-fs';
|
|
35
38
|
import {resourceFromAttributes, detectResources, envDetector, hostDetector, osDetector, processDetector, serviceInstanceIdDetector} from '@opentelemetry/resources';
|
|
36
39
|
import {ATTR_SERVICE_NAME} from '@opentelemetry/semantic-conventions';
|
|
@@ -69,8 +72,35 @@ const setPeerService = (span, host) => {
|
|
|
69
72
|
}
|
|
70
73
|
};
|
|
71
74
|
|
|
75
|
+
// The express hook runs once per layer span - every middleware, every router,
|
|
76
|
+
// and the request handler. Only the request handler carries the matched route,
|
|
77
|
+
// so the rest return before serialising anything.
|
|
78
|
+
const expressRequestHook = (span, info) => {
|
|
79
|
+
// info is ExpressRequestInfo: { request, route, layerType }
|
|
80
|
+
if (info?.layerType !== ExpressLayerType.REQUEST_HANDLER) return;
|
|
81
|
+
|
|
82
|
+
const request = info.request;
|
|
83
|
+
if (info.route) {
|
|
84
|
+
span.setAttribute('express.route', info.route);
|
|
85
|
+
}
|
|
86
|
+
if (request?.params && Object.keys(request.params).length > 0) {
|
|
87
|
+
span.setAttribute('express.params', JSON.stringify(request.params));
|
|
88
|
+
}
|
|
89
|
+
// Names only. Query values carry tokens and personal data, and the span
|
|
90
|
+
// attribute value length limit defaults to unbounded.
|
|
91
|
+
const queryKeys = request?.query ? Object.keys(request.query) : [];
|
|
92
|
+
if (queryKeys.length > 0) {
|
|
93
|
+
span.setAttribute('express.query_keys', queryKeys.sort());
|
|
94
|
+
}
|
|
95
|
+
// Add user context if available
|
|
96
|
+
if (request?.user?.id) {
|
|
97
|
+
span.setAttribute('user.id', request.user.id);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
72
101
|
let tracerProvider = null; // Declare provider in module scope for access in stopTracing
|
|
73
102
|
let meterProvider = null;
|
|
103
|
+
let loggerProvider = null;
|
|
74
104
|
|
|
75
105
|
/**
|
|
76
106
|
* Sets up tracing for the application using OpenTelemetry.
|
|
@@ -95,6 +125,8 @@ let meterProvider = null;
|
|
|
95
125
|
* @param {boolean} [options.enableMetrics=true] - Export metrics as well as traces.
|
|
96
126
|
* @param {string} [options.metricsUrl=options.url] - Endpoint for metrics, when it differs from the trace endpoint.
|
|
97
127
|
* @param {number} [options.metricExportIntervalMillis=60000] - How often metrics are exported.
|
|
128
|
+
* @param {boolean} [options.enableLogs=true] - Send Pino log records over OTLP.
|
|
129
|
+
* @param {string} [options.logsUrl=options.url] - Endpoint for logs, when it differs from the trace endpoint.
|
|
98
130
|
*
|
|
99
131
|
* @returns {Tracer} - The tracer for the service.
|
|
100
132
|
*/
|
|
@@ -115,6 +147,8 @@ export function setupTracing(options = {}) {
|
|
|
115
147
|
enableMetrics = true,
|
|
116
148
|
metricsUrl = url,
|
|
117
149
|
metricExportIntervalMillis = 60000,
|
|
150
|
+
enableLogs = true,
|
|
151
|
+
logsUrl = url,
|
|
118
152
|
} = options;
|
|
119
153
|
|
|
120
154
|
// Validate required parameters
|
|
@@ -149,8 +183,8 @@ export function setupTracing(options = {}) {
|
|
|
149
183
|
explicitAttributes[ATTR_CONTAINER_NAME] = hostname;
|
|
150
184
|
}
|
|
151
185
|
|
|
152
|
-
// One resource for
|
|
153
|
-
// service.name, so the
|
|
186
|
+
// One resource for every signal. Grafana pairs a metric and a log line with
|
|
187
|
+
// a trace on service.name, so the providers carry an identical resource.
|
|
154
188
|
const resource = detectResources({
|
|
155
189
|
detectors: [envDetector, hostDetector, osDetector, processDetector, serviceInstanceIdDetector],
|
|
156
190
|
}).merge(resourceFromAttributes(explicitAttributes));
|
|
@@ -173,6 +207,22 @@ export function setupTracing(options = {}) {
|
|
|
173
207
|
metrics.setGlobalMeterProvider(meterProvider);
|
|
174
208
|
}
|
|
175
209
|
|
|
210
|
+
if (enableLogs) {
|
|
211
|
+
loggerProvider = new LoggerProvider({
|
|
212
|
+
resource,
|
|
213
|
+
processors: [
|
|
214
|
+
new BatchLogRecordProcessor({
|
|
215
|
+
exporter: new OTLPLogExporter({...exportOptions, url: logsUrl}),
|
|
216
|
+
maxQueueSize: 4096,
|
|
217
|
+
maxExportBatchSize: 1024,
|
|
218
|
+
scheduledDelayMillis: 2000,
|
|
219
|
+
exportTimeoutMillis: 10000,
|
|
220
|
+
}),
|
|
221
|
+
],
|
|
222
|
+
});
|
|
223
|
+
logs.setGlobalLoggerProvider(loggerProvider);
|
|
224
|
+
}
|
|
225
|
+
|
|
176
226
|
// Register globally. With no overrides, register() installs the modern
|
|
177
227
|
// AsyncLocalStorageContextManager and a CompositePropagator of
|
|
178
228
|
// W3CTraceContext + W3CBaggage - identical propagation to the previous
|
|
@@ -223,28 +273,13 @@ export function setupTracing(options = {}) {
|
|
|
223
273
|
requestHook: (span, request) => setPeerService(span, request?.origin),
|
|
224
274
|
}),
|
|
225
275
|
new ExpressInstrumentation({
|
|
226
|
-
requestHook:
|
|
227
|
-
// info is ExpressRequestInfo: { request, route, layerType }
|
|
228
|
-
const request = info.request;
|
|
229
|
-
if (info.route) {
|
|
230
|
-
span.setAttribute('express.route', info.route);
|
|
231
|
-
if (request?.method) {
|
|
232
|
-
span.updateName(`${request.method} ${info.route}`);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
if (request?.params && Object.keys(request.params).length > 0) {
|
|
236
|
-
span.setAttribute('express.params', JSON.stringify(request.params));
|
|
237
|
-
}
|
|
238
|
-
if (request?.query && Object.keys(request.query).length > 0) {
|
|
239
|
-
span.setAttribute('express.query', JSON.stringify(request.query));
|
|
240
|
-
}
|
|
241
|
-
// Add user context if available
|
|
242
|
-
if (request?.user?.id) {
|
|
243
|
-
span.setAttribute('user.id', request.user.id);
|
|
244
|
-
}
|
|
245
|
-
},
|
|
276
|
+
requestHook: expressRequestHook,
|
|
246
277
|
}),
|
|
247
278
|
new PinoInstrumentation({
|
|
279
|
+
// Log sending is on by default, and every record is parsed and rebuilt as
|
|
280
|
+
// a LogRecord before it reaches a logger. With no logger provider that
|
|
281
|
+
// work is done for a no-op, so turn it off rather than pay for nothing.
|
|
282
|
+
disableLogSending: !enableLogs,
|
|
248
283
|
logHook: (span, record) => {
|
|
249
284
|
// trace_id/span_id/trace_flags are injected by the instrumentation by
|
|
250
285
|
// default; only add service name for better log correlation.
|
|
@@ -329,6 +364,7 @@ export function setupTracing(options = {}) {
|
|
|
329
364
|
registerInstrumentations({
|
|
330
365
|
tracerProvider,
|
|
331
366
|
meterProvider,
|
|
367
|
+
loggerProvider,
|
|
332
368
|
instrumentations,
|
|
333
369
|
});
|
|
334
370
|
|
|
@@ -337,11 +373,11 @@ export function setupTracing(options = {}) {
|
|
|
337
373
|
}
|
|
338
374
|
|
|
339
375
|
/**
|
|
340
|
-
* Gracefully stops the tracing by shutting down
|
|
376
|
+
* Gracefully stops the tracing by shutting down every provider it registered.
|
|
341
377
|
*
|
|
342
|
-
* This function ensures that all pending spans and
|
|
343
|
-
* resources are cleaned up properly. It is recommended to call
|
|
344
|
-
* during the application's shutdown process.
|
|
378
|
+
* This function ensures that all pending spans, metrics and log records are
|
|
379
|
+
* exported and resources are cleaned up properly. It is recommended to call
|
|
380
|
+
* this function during the application's shutdown process.
|
|
345
381
|
*
|
|
346
382
|
* @returns {Promise<void>} - A promise that resolves when shutdown is complete.
|
|
347
383
|
*/
|
|
@@ -372,8 +408,29 @@ export async function stopTracing() {
|
|
|
372
408
|
diag.error('Error during metrics shutdown:', error);
|
|
373
409
|
}
|
|
374
410
|
}
|
|
411
|
+
|
|
412
|
+
if (loggerProvider) {
|
|
413
|
+
try {
|
|
414
|
+
await loggerProvider.shutdown();
|
|
415
|
+
diag.info('Logs have been successfully shut down.');
|
|
416
|
+
} catch (error) {
|
|
417
|
+
diag.error('Error during logs shutdown:', error);
|
|
418
|
+
} finally {
|
|
419
|
+
// A second setGlobalLoggerProvider is ignored, so unregister whatever the
|
|
420
|
+
// flush did, or a later setupTracing keeps writing to a dead provider.
|
|
421
|
+
loggerProvider = null;
|
|
422
|
+
logs.disable();
|
|
423
|
+
}
|
|
424
|
+
}
|
|
375
425
|
}
|
|
376
426
|
|
|
427
|
+
/**
|
|
428
|
+
* @internal
|
|
429
|
+
* The express request hook, exposed so it can be driven without Express.
|
|
430
|
+
* DO NOT use in production code.
|
|
431
|
+
*/
|
|
432
|
+
export const __expressRequestHookForTesting = expressRequestHook;
|
|
433
|
+
|
|
377
434
|
/**
|
|
378
435
|
* @internal
|
|
379
436
|
* Resets the tracer provider for testing purposes.
|
|
@@ -382,4 +439,5 @@ export async function stopTracing() {
|
|
|
382
439
|
export function __resetTracingForTesting() {
|
|
383
440
|
tracerProvider = null;
|
|
384
441
|
meterProvider = null;
|
|
442
|
+
loggerProvider = null;
|
|
385
443
|
}
|
package/libs/index.test.mjs
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
3
3
|
import assert from 'node:assert';
|
|
4
4
|
import { metrics } from '@opentelemetry/api';
|
|
5
|
+
import { logs } from '@opentelemetry/api-logs';
|
|
5
6
|
import { MeterProvider } from '@opentelemetry/sdk-metrics';
|
|
6
|
-
import {
|
|
7
|
+
import { LoggerProvider } from '@opentelemetry/sdk-logs';
|
|
8
|
+
import { setupTracing, stopTracing, __resetTracingForTesting, __expressRequestHookForTesting } from './index.mjs';
|
|
7
9
|
|
|
8
10
|
describe('setupTracing', () => {
|
|
9
11
|
// Clear environment and reset tracing state before each test
|
|
@@ -105,6 +107,53 @@ describe('setupTracing', () => {
|
|
|
105
107
|
assert.ok(metrics.getMeterProvider() instanceof MeterProvider, 'global meter provider should be the SDK one');
|
|
106
108
|
});
|
|
107
109
|
|
|
110
|
+
// The Pino instrumentation sends every log record to the Logs API whether or
|
|
111
|
+
// not a provider is registered. Without one the record is built and dropped.
|
|
112
|
+
it('should register a global logger provider by default', () => {
|
|
113
|
+
setupTracing({
|
|
114
|
+
serviceName: 'test-service',
|
|
115
|
+
url: 'http://localhost:4317',
|
|
116
|
+
});
|
|
117
|
+
assert.ok(logs.getLoggerProvider() instanceof LoggerProvider, 'global logger provider should be the SDK one');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should leave the no-op logger provider in place when logs are disabled', () => {
|
|
121
|
+
setupTracing({
|
|
122
|
+
serviceName: 'test-service',
|
|
123
|
+
url: 'http://localhost:4317',
|
|
124
|
+
enableLogs: false,
|
|
125
|
+
});
|
|
126
|
+
assert.ok(!(logs.getLoggerProvider() instanceof LoggerProvider), 'no logger provider should be registered');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should accept a separate logs endpoint', () => {
|
|
130
|
+
const tracer = setupTracing({
|
|
131
|
+
serviceName: 'test-service',
|
|
132
|
+
url: 'http://localhost:4317',
|
|
133
|
+
logsUrl: 'http://localhost:4318',
|
|
134
|
+
});
|
|
135
|
+
assert.ok(tracer, 'tracer should be defined');
|
|
136
|
+
assert.ok(logs.getLoggerProvider() instanceof LoggerProvider, 'global logger provider should be the SDK one');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Without the unregister in stopTracing the API keeps the first provider and
|
|
140
|
+
// silently ignores the second registration.
|
|
141
|
+
it('should unregister the logger provider on shutdown', async () => {
|
|
142
|
+
setupTracing({
|
|
143
|
+
serviceName: 'test-service',
|
|
144
|
+
url: 'http://localhost:4317',
|
|
145
|
+
});
|
|
146
|
+
await stopTracing();
|
|
147
|
+
assert.ok(!(logs.getLoggerProvider() instanceof LoggerProvider), 'logger provider should be unregistered');
|
|
148
|
+
|
|
149
|
+
__resetTracingForTesting();
|
|
150
|
+
setupTracing({
|
|
151
|
+
serviceName: 'test-service',
|
|
152
|
+
url: 'http://localhost:4317',
|
|
153
|
+
});
|
|
154
|
+
assert.ok(logs.getLoggerProvider() instanceof LoggerProvider, 'a later setup should register again');
|
|
155
|
+
});
|
|
156
|
+
|
|
108
157
|
// Without the unregister in stopTracing the API refuses the second
|
|
109
158
|
// registration and the global keeps pointing at the shut-down provider.
|
|
110
159
|
it('should unregister the meter provider on shutdown', async () => {
|
|
@@ -123,3 +172,93 @@ describe('setupTracing', () => {
|
|
|
123
172
|
assert.ok(metrics.getMeterProvider() instanceof MeterProvider, 'a later setup should register again');
|
|
124
173
|
});
|
|
125
174
|
});
|
|
175
|
+
|
|
176
|
+
// The instrumentation calls the hook once per layer span, so the cheap path
|
|
177
|
+
// through it matters as much as what it records.
|
|
178
|
+
describe('express request hook', () => {
|
|
179
|
+
const fakeSpan = () => {
|
|
180
|
+
const attributes = {};
|
|
181
|
+
return {
|
|
182
|
+
attributes,
|
|
183
|
+
names: [],
|
|
184
|
+
setAttribute(key, value) {
|
|
185
|
+
attributes[key] = value;
|
|
186
|
+
},
|
|
187
|
+
updateName(name) {
|
|
188
|
+
this.names.push(name);
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const requestHandler = (request, route = '/work/:id') => ({
|
|
194
|
+
request,
|
|
195
|
+
route,
|
|
196
|
+
layerType: 'request_handler',
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should record route and params on a request handler layer', () => {
|
|
200
|
+
const span = fakeSpan();
|
|
201
|
+
__expressRequestHookForTesting(span, requestHandler({
|
|
202
|
+
method: 'GET',
|
|
203
|
+
params: {id: '42'},
|
|
204
|
+
query: {},
|
|
205
|
+
}));
|
|
206
|
+
assert.strictEqual(span.attributes['express.route'], '/work/:id');
|
|
207
|
+
assert.strictEqual(span.attributes['express.params'], '{"id":"42"}');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should ignore middleware and router layers', () => {
|
|
211
|
+
for (const layerType of ['middleware', 'router']) {
|
|
212
|
+
const span = fakeSpan();
|
|
213
|
+
__expressRequestHookForTesting(span, {
|
|
214
|
+
request: {method: 'GET', params: {id: '42'}, query: {page: '1'}},
|
|
215
|
+
route: '/work/:id',
|
|
216
|
+
layerType,
|
|
217
|
+
});
|
|
218
|
+
assert.deepStrictEqual(span.attributes, {}, `${layerType} layer should record nothing`);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// The HTTP instrumentation renames the server span from http.route already.
|
|
223
|
+
// Renaming here would relabel every middleware span with the same string.
|
|
224
|
+
it('should not rename the span', () => {
|
|
225
|
+
const span = fakeSpan();
|
|
226
|
+
__expressRequestHookForTesting(span, requestHandler({
|
|
227
|
+
method: 'GET',
|
|
228
|
+
params: {id: '42'},
|
|
229
|
+
query: {},
|
|
230
|
+
}));
|
|
231
|
+
assert.deepStrictEqual(span.names, [], 'the hook should not rename a span');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// A query string carries tokens and personal data, and the span attribute
|
|
235
|
+
// value length limit is unbounded by default.
|
|
236
|
+
it('should record query key names without their values', () => {
|
|
237
|
+
const span = fakeSpan();
|
|
238
|
+
__expressRequestHookForTesting(span, requestHandler({
|
|
239
|
+
method: 'GET',
|
|
240
|
+
params: {},
|
|
241
|
+
query: {token: 'sensitive-value', page: '2'},
|
|
242
|
+
}));
|
|
243
|
+
assert.deepStrictEqual(span.attributes['express.query_keys'], ['page', 'token']);
|
|
244
|
+
assert.strictEqual(span.attributes['express.query'], undefined, 'query values should not be recorded');
|
|
245
|
+
assert.ok(!JSON.stringify(span.attributes).includes('sensitive-value'), 'no query value should reach the span');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should record the user id when the application sets one', () => {
|
|
249
|
+
const span = fakeSpan();
|
|
250
|
+
__expressRequestHookForTesting(span, requestHandler({
|
|
251
|
+
method: 'GET',
|
|
252
|
+
params: {},
|
|
253
|
+
query: {},
|
|
254
|
+
user: {id: 'user-7'},
|
|
255
|
+
}));
|
|
256
|
+
assert.strictEqual(span.attributes['user.id'], 'user-7');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should tolerate a layer with no request', () => {
|
|
260
|
+
const span = fakeSpan();
|
|
261
|
+
assert.doesNotThrow(() => __expressRequestHookForTesting(span, {layerType: 'request_handler'}));
|
|
262
|
+
assert.deepStrictEqual(span.attributes, {});
|
|
263
|
+
});
|
|
264
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saidsef/tracing-node",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "tracing NodeJS - Wrapper for OpenTelemetry instrumentation packages",
|
|
5
5
|
"main": "libs/index.mjs",
|
|
6
6
|
"scripts": {
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
"homepage": "https://github.com/saidsef/tracing-node#readme",
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@opentelemetry/api": "^1.9.1",
|
|
36
|
+
"@opentelemetry/api-logs": "^0.222.0",
|
|
37
|
+
"@opentelemetry/exporter-logs-otlp-grpc": "^0.222.0",
|
|
36
38
|
"@opentelemetry/exporter-metrics-otlp-grpc": "^0.222.0",
|
|
37
39
|
"@opentelemetry/exporter-trace-otlp-grpc": "^0.222.0",
|
|
38
40
|
"@opentelemetry/instrumentation": "^0.222.0",
|
|
@@ -47,6 +49,7 @@
|
|
|
47
49
|
"@opentelemetry/instrumentation-runtime-node": "^0.35.0",
|
|
48
50
|
"@opentelemetry/instrumentation-undici": "^0.32.0",
|
|
49
51
|
"@opentelemetry/resources": "^2.11.0",
|
|
52
|
+
"@opentelemetry/sdk-logs": "^0.222.0",
|
|
50
53
|
"@opentelemetry/sdk-metrics": "^2.11.0",
|
|
51
54
|
"@opentelemetry/sdk-trace-base": "^2.11.0",
|
|
52
55
|
"@opentelemetry/sdk-trace-node": "^2.11.0",
|