@saidsef/tracing-node 4.0.0 → 4.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/README.md CHANGED
@@ -15,6 +15,7 @@ Effortlessly supercharge your applications with world-class distributed tracing!
15
15
  | Feature | Description |
16
16
  |---------|-------------|
17
17
  | HTTP/HTTPS instrumentation | Automatic service detection |
18
+ | fetch/undici instrumentation | Outgoing `globalThis.fetch` calls |
18
19
  | Express.js support | Framework instrumentation |
19
20
  | Elasticsearch client | Database instrumentation |
20
21
  | IORedis client | Cache instrumentation |
package/libs/index.mjs CHANGED
@@ -25,6 +25,7 @@ import {ExpressInstrumentation} from '@opentelemetry/instrumentation-express';
25
25
  import {NodeTracerProvider} from '@opentelemetry/sdk-trace-node';
26
26
  import {OTLPTraceExporter} from '@opentelemetry/exporter-trace-otlp-grpc';
27
27
  import {PinoInstrumentation} from '@opentelemetry/instrumentation-pino';
28
+ import {UndiciInstrumentation} from '@opentelemetry/instrumentation-undici';
28
29
  import {IORedisInstrumentation} from '@opentelemetry/instrumentation-ioredis';
29
30
  import {registerInstrumentations} from '@opentelemetry/instrumentation';
30
31
  import {FsInstrumentation} from '@opentelemetry/instrumentation-fs';
@@ -50,6 +51,21 @@ const truncateArg = (value, limit) => {
50
51
  return str.length > limit ? `${str.substring(0, limit)}...` : str;
51
52
  };
52
53
 
54
+ // Tempo names a service-graph node from peer.service, which no instrumentation
55
+ // emits, so both the http and undici hooks below derive it from the remote host.
56
+ const PEER_SERVICES = ['elasticsearch', 'redis'];
57
+
58
+ const setPeerService = (span, host) => {
59
+ if (!host) return;
60
+ for (const service of PEER_SERVICES) {
61
+ if (host.includes(service)) {
62
+ span.setAttribute('peer.service', service);
63
+ span.setAttribute('db.system.name', service);
64
+ return;
65
+ }
66
+ }
67
+ };
68
+
53
69
  let tracerProvider = null; // Declare provider in module scope for access in stopTracing
54
70
 
55
71
  /**
@@ -134,18 +150,7 @@ export function setupTracing(options = {}) {
134
150
 
135
151
  // Only an outgoing ClientRequest carries .host, so bailing without it keeps
136
152
  // server spans out: peer.service must name the remote service being called.
137
- const applyCustomAttributesOnSpan = (span, request) => {
138
- const host = request?.host;
139
- if (!host) return;
140
-
141
- for (const service of ['elasticsearch', 'redis']) {
142
- if (host.includes(service)) {
143
- span.setAttribute('peer.service', service);
144
- span.setAttribute('db.system.name', service);
145
- return;
146
- }
147
- }
148
- };
153
+ const applyCustomAttributesOnSpan = (span, request) => setPeerService(span, request?.host);
149
154
 
150
155
  const instrumentations = [
151
156
  new HttpInstrumentation({
@@ -178,6 +183,14 @@ export function setupTracing(options = {}) {
178
183
  if (requestId) span.setAttribute('http.request_id', requestId);
179
184
  },
180
185
  }),
186
+ // globalThis.fetch runs on undici, which never touches the http/https
187
+ // modules HttpInstrumentation patches. Without this, fetch calls produce no
188
+ // client span and inject no traceparent, so the callee starts a new trace
189
+ // and the two services can never be paired into a service-graph edge.
190
+ new UndiciInstrumentation({
191
+ // UndiciRequest exposes origin, not the .host the http hook reads.
192
+ requestHook: (span, request) => setPeerService(span, request?.origin),
193
+ }),
181
194
  new ExpressInstrumentation({
182
195
  requestHook: (span, info) => {
183
196
  // info is ExpressRequestInfo: { request, route, layerType }
@@ -49,6 +49,20 @@ describe('setupTracing', () => {
49
49
  assert.ok(tracer, 'tracer should be defined');
50
50
  });
51
51
 
52
+ // globalThis.fetch runs on undici, so it is invisible to HttpInstrumentation.
53
+ // Registering it must not disturb setup, and the patch has to land on the
54
+ // global fetch itself - otherwise outgoing calls carry no traceparent.
55
+ it('should instrument global fetch', () => {
56
+ const before = globalThis.fetch;
57
+ const tracer = setupTracing({
58
+ serviceName: 'test-service',
59
+ url: 'http://localhost:4317',
60
+ });
61
+ assert.ok(tracer, 'tracer should be defined');
62
+ assert.strictEqual(typeof globalThis.fetch, 'function', 'global fetch should still be callable');
63
+ assert.ok(before, 'global fetch should exist on a supported runtime');
64
+ });
65
+
52
66
  it('should accept optional instrumentations', () => {
53
67
  const tracer = setupTracing({
54
68
  serviceName: 'test-service',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saidsef/tracing-node",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "tracing NodeJS - Wrapper for OpenTelemetry instrumentation packages",
5
5
  "main": "libs/index.mjs",
6
6
  "scripts": {
@@ -23,7 +23,7 @@
23
23
  "author": "Said Sef <saidsef@gmail.com>",
24
24
  "license": "Apache-2.0",
25
25
  "engines": {
26
- "node": ">= 20"
26
+ "node": ">= 20.6.0"
27
27
  },
28
28
  "bugs": {
29
29
  "url": "https://github.com/saidsef/tracing-node/issues"
@@ -41,6 +41,7 @@
41
41
  "@opentelemetry/instrumentation-http": "^0.221.0",
42
42
  "@opentelemetry/instrumentation-ioredis": "^0.69.0",
43
43
  "@opentelemetry/instrumentation-pino": "^0.67.0",
44
+ "@opentelemetry/instrumentation-undici": "^0.31.0",
44
45
  "@opentelemetry/resources": "^2.10.0",
45
46
  "@opentelemetry/sdk-trace-base": "^2.10.0",
46
47
  "@opentelemetry/sdk-trace-node": "^2.10.0",