@saidsef/tracing-node 3.21.0 → 3.22.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.
Files changed (2) hide show
  1. package/libs/index.mjs +51 -123
  2. package/package.json +8 -6
package/libs/index.mjs CHANGED
@@ -17,9 +17,7 @@
17
17
  */
18
18
 
19
19
  import {AwsInstrumentation} from '@opentelemetry/instrumentation-aws-sdk';
20
- import {AsyncHooksContextManager} from '@opentelemetry/context-async-hooks';
21
20
  import {BatchSpanProcessor} from '@opentelemetry/sdk-trace-base';
22
- import {CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator} from '@opentelemetry/core';
23
21
  import {ConnectInstrumentation} from '@opentelemetry/instrumentation-connect';
24
22
  import {diag, DiagConsoleLogger, DiagLogLevel} from '@opentelemetry/api';
25
23
  import {HttpInstrumentation} from '@opentelemetry/instrumentation-http';
@@ -101,29 +99,26 @@ export function setupTracing(options = {}) {
101
99
  exportTimeoutMillis: 10000,
102
100
  });
103
101
 
102
+ // Explicit attributes (service/container) must win over env detection, so
103
+ // detect first and merge the explicit resource on top. Only include defined
104
+ // keys so an undefined hostname does not write container.name: undefined.
105
+ const explicitAttributes = {[ATTR_SERVICE_NAME]: serviceName};
106
+ if (hostname) {
107
+ explicitAttributes[ATTR_CONTAINER_NAME] = hostname;
108
+ }
109
+
104
110
  tracerProvider = new NodeTracerProvider({
105
111
  spanProcessors: [spanProcessor],
106
- resource: new resourceFromAttributes({
107
- [ATTR_SERVICE_NAME]: serviceName,
108
- [ATTR_CONTAINER_NAME]: hostname,
109
- }).merge(
110
- detectResources({
111
- detectors: [envDetector, hostDetector, osDetector, processDetector, serviceInstanceIdDetector],
112
- })
113
- ),
114
- autoDetectResources: true,
112
+ resource: detectResources({
113
+ detectors: [envDetector, hostDetector, osDetector, processDetector, serviceInstanceIdDetector],
114
+ }).merge(resourceFromAttributes(explicitAttributes)),
115
115
  });
116
116
 
117
- // Initialize the tracer provider with propagators
118
- tracerProvider.register({
119
- contextManager: new AsyncHooksContextManager().enable(),
120
- propagator: new CompositePropagator({
121
- propagators: [
122
- new W3CTraceContextPropagator(),
123
- new W3CBaggagePropagator(),
124
- ],
125
- }),
126
- });
117
+ // Register globally. With no overrides, register() installs the modern
118
+ // AsyncLocalStorageContextManager and a CompositePropagator of
119
+ // W3CTraceContext + W3CBaggage - identical propagation to the previous
120
+ // explicit config, with the recommended context manager.
121
+ tracerProvider.register();
127
122
 
128
123
  // Ignore spans from static assets.
129
124
  const ignoreIncomingRequestHook = (req) => {
@@ -207,89 +202,68 @@ export function setupTracing(options = {}) {
207
202
  },
208
203
  }),
209
204
  new ExpressInstrumentation({
210
- ignoreIncomingRequestHook,
211
- requestHook: (span, request) => {
212
- // Add Express-specific attributes
213
- if (request.route?.path) {
214
- span.setAttribute('express.route', request.route.path);
215
- span.updateName(`${request.method} ${request.route.path}`);
205
+ requestHook: (span, info) => {
206
+ // info is ExpressRequestInfo: { request, route, layerType }
207
+ const request = info.request;
208
+ if (info.route) {
209
+ span.setAttribute('express.route', info.route);
210
+ if (request?.method) {
211
+ span.updateName(`${request.method} ${info.route}`);
212
+ }
216
213
  }
217
- if (request.params && Object.keys(request.params).length > 0) {
214
+ if (request?.params && Object.keys(request.params).length > 0) {
218
215
  span.setAttribute('express.params', JSON.stringify(request.params));
219
216
  }
220
- if (request.query && Object.keys(request.query).length > 0) {
217
+ if (request?.query && Object.keys(request.query).length > 0) {
221
218
  span.setAttribute('express.query', JSON.stringify(request.query));
222
219
  }
223
220
  // Add user context if available
224
- if (request.user?.id) {
221
+ if (request?.user?.id) {
225
222
  span.setAttribute('user.id', request.user.id);
226
223
  }
227
224
  },
228
225
  }),
229
226
  new PinoInstrumentation({
230
227
  logHook: (span, record) => {
231
- // Inject trace context into log records
232
- const spanContext = span.spanContext();
233
- record['trace_id'] = spanContext.traceId;
234
- record['span_id'] = spanContext.spanId;
235
- record['trace_flags'] = `0${spanContext.traceFlags.toString(16)}`;
236
-
237
- // Add service name for better log correlation
228
+ // trace_id/span_id/trace_flags are injected by the instrumentation by
229
+ // default; only add service name for better log correlation.
238
230
  if (serviceName) {
239
231
  record['service.name'] = serviceName;
240
232
  }
241
233
  },
242
- logSeverity: {
243
- error: 'ERROR',
244
- warn: 'WARN',
245
- info: 'INFO',
246
- debug: 'DEBUG',
247
- trace: 'TRACE',
248
- },
249
- }),
250
- new ConnectInstrumentation({
251
- ignoreIncomingRequestHook,
252
- requestHook: (span, request) => {
253
- // Add Connect middleware attributes
254
- if (request.url) {
255
- span.setAttribute('connect.url', request.url);
256
- }
257
- if (request.method) {
258
- span.setAttribute('connect.method', request.method);
259
- }
260
- },
261
234
  }),
235
+ // ConnectInstrumentation accepts only the base InstrumentationConfig; it has
236
+ // no request/ignore hooks, so configure it with defaults.
237
+ new ConnectInstrumentation(),
262
238
  new AwsInstrumentation({
263
239
  suppressInternalInstrumentation: false,
264
240
  sqsExtractContextPropagationFromPayload: true,
265
- preRequestHook: (span, request) => {
266
- // Add peer.service attribute for better service map visualization
267
- const serviceName = request.serviceName || request.service?.serviceIdentifier;
268
- if (serviceName) {
269
- span.setAttribute('peer.service', serviceName.toLowerCase());
270
- span.setAttribute('aws.service', serviceName.toLowerCase());
241
+ preRequestHook: (span, requestInfo) => {
242
+ // requestInfo is AwsSdkRequestHookInformation: { request: NormalizedRequest }
243
+ const awsServiceName = requestInfo.request?.serviceName;
244
+ if (awsServiceName) {
245
+ span.setAttribute('peer.service', awsServiceName.toLowerCase());
246
+ span.setAttribute('aws.service', awsServiceName.toLowerCase());
271
247
  }
272
248
  },
273
- responseHook: (span, response) => {
274
- // Add additional attributes from response if available
275
- if (response?.requestId) {
276
- span.setAttribute('aws.request_id', response.requestId);
249
+ responseHook: (span, responseInfo) => {
250
+ // responseInfo is AwsSdkResponseHookInformation: { response: NormalizedResponse }
251
+ const requestId = responseInfo.response?.requestId;
252
+ if (requestId) {
253
+ span.setAttribute('aws.request_id', requestId);
277
254
  }
278
255
  },
279
256
  }),
280
257
  new IORedisInstrumentation({
281
258
  requireParentSpan: false,
282
- requestHook: (span, cmdName, cmdArgs) => {
283
- // Set peer.service for service graph visualization - CRITICAL for Tempo
259
+ requestHook: (span, {cmdName, cmdArgs}) => {
260
+ // requestInfo is IORedisRequestHookInformation: { cmdName, cmdArgs }.
261
+ // Set peer.service for service graph visualization - CRITICAL for Tempo.
262
+ // The span is already created with SpanKind.CLIENT and net.peer.name is
263
+ // already set to the real host by the instrumentation, so we do not
264
+ // override those here.
284
265
  span.setAttribute('peer.service', 'redis');
285
266
  span.setAttribute('db.system', 'redis');
286
-
287
- // CRITICAL: Ensure span kind is CLIENT for service graph
288
- span.setAttribute('span.kind', 'CLIENT');
289
-
290
- // Add network peer attributes (helps Tempo identify the service)
291
- span.setAttribute('net.peer.name', 'redis');
292
- span.setAttribute('db.connection_string', 'redis');
293
267
 
294
268
  // Add command details for better observability
295
269
  if (cmdName) {
@@ -348,56 +322,10 @@ export function setupTracing(options = {}) {
348
322
  if (enableDnsInstrumentation) {
349
323
  // Enable DNS instrumentation if specified
350
324
  // This instrumentation is useful for tracing DNS operations.
325
+ // DnsInstrumentationConfig only supports ignoreHostnames; it has no
326
+ // request/response/error hooks.
351
327
  instrumentations.push(new DnsInstrumentation({
352
328
  ignoreHostnames: ['localhost', '127.0.0.1', '::1'],
353
- requestHook: (span, request) => {
354
- // Add DNS query details for better observability
355
- if (request.hostname) {
356
- span.setAttribute('dns.hostname', request.hostname);
357
- span.updateName(`DNS ${request.hostname}`);
358
- }
359
- if (request.rrtype) {
360
- span.setAttribute('dns.record_type', request.rrtype);
361
- }
362
- // Add additional context
363
- span.setAttribute('peer.service', 'dns');
364
- span.setAttribute('dns.query_count', 1);
365
- },
366
- responseHook: (span, response) => {
367
- // Add DNS response details
368
- if (Array.isArray(response)) {
369
- span.setAttribute('dns.result_count', response.length);
370
- // Log first few results for debugging (limit to avoid overwhelming spans)
371
- const resultSample = response.slice(0, 3).map(r =>
372
- typeof r === 'string' ? r : JSON.stringify(r)
373
- );
374
- if (resultSample.length > 0) {
375
- span.setAttribute('dns.results', JSON.stringify(resultSample));
376
- }
377
- } else if (response) {
378
- span.setAttribute('dns.result_count', 1);
379
- span.setAttribute('dns.result', typeof response === 'string' ? response : JSON.stringify(response));
380
- }
381
- },
382
- errorHook: (span, error) => {
383
- // Enhanced error tracking for DNS failures
384
- if (error) {
385
- span.setAttribute('dns.error', true);
386
- span.setAttribute('dns.error.code', error.code || 'UNKNOWN');
387
- span.setAttribute('dns.error.message', error.message || 'DNS lookup failed');
388
-
389
- // Categorize common DNS errors
390
- if (error.code === 'ENOTFOUND') {
391
- span.setAttribute('dns.error.type', 'NOT_FOUND');
392
- } else if (error.code === 'ETIMEOUT') {
393
- span.setAttribute('dns.error.type', 'TIMEOUT');
394
- } else if (error.code === 'ECONNREFUSED') {
395
- span.setAttribute('dns.error.type', 'CONNECTION_REFUSED');
396
- } else {
397
- span.setAttribute('dns.error.type', 'OTHER');
398
- }
399
- }
400
- },
401
329
  }));
402
330
  }
403
331
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saidsef/tracing-node",
3
- "version": "3.21.0",
3
+ "version": "3.22.0",
4
4
  "description": "tracing NodeJS - Wrapper for OpenTelemetry instrumentation packages",
5
5
  "main": "libs/index.mjs",
6
6
  "scripts": {
@@ -31,7 +31,6 @@
31
31
  "homepage": "https://github.com/saidsef/tracing-node#readme",
32
32
  "dependencies": {
33
33
  "@opentelemetry/api": "^1.9.0",
34
- "@opentelemetry/context-async-hooks": "^2.8.0",
35
34
  "@opentelemetry/exporter-trace-otlp-grpc": "^0.219.0",
36
35
  "@opentelemetry/instrumentation": "^0.219.0",
37
36
  "@opentelemetry/instrumentation-aws-sdk": "^0.74.0",
@@ -46,13 +45,16 @@
46
45
  "@opentelemetry/sdk-trace-base": "^2.8.0",
47
46
  "@opentelemetry/sdk-trace-node": "^2.8.0",
48
47
  "@opentelemetry/semantic-conventions": "^1.41.1",
49
- "opentelemetry-instrumentation-elasticsearch": "0.41.0"
48
+ "opentelemetry-instrumentation-elasticsearch": "^0.41.0"
50
49
  },
51
50
  "devDependencies": {
52
- "eslint": "^10.5.0",
53
- "jest": "^30.0.0"
51
+ "eslint": "^10.5.0"
54
52
  },
55
53
  "overrides": {
56
- "protobufjs": "^7.5.3"
54
+ "protobufjs": "^7.5.3",
55
+ "@opentelemetry/core": "^2.8.0"
56
+ },
57
+ "allowScripts": {
58
+ "protobufjs@7.6.4": true
57
59
  }
58
60
  }