@saidsef/tracing-node 3.13.6 → 3.14.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/libs/index.mjs CHANGED
@@ -85,14 +85,21 @@ export function setupTracing(options = {}) {
85
85
 
86
86
  // Configure exporter with the Collector endpoint - uses gRPC
87
87
  const exportOptions = {
88
- concurrencyLimit: parseInt(concurrencyLimit, 10),
89
- url: url,
90
- timeoutMillis: 1000,
88
+ concurrencyLimit,
89
+ url,
90
+ timeoutMillis: 10000,
91
91
  };
92
92
 
93
93
  // Register the span processor with the tracer provider
94
94
  const exporter = new OTLPTraceExporter(exportOptions);
95
- const spanProcessor = new BatchSpanProcessor(exporter);
95
+
96
+ // Configure BatchSpanProcessor for production workloads
97
+ const spanProcessor = new BatchSpanProcessor(exporter, {
98
+ maxQueueSize: 4096,
99
+ maxExportBatchSize: 1024,
100
+ scheduledDelayMillis: 2000,
101
+ exportTimeoutMillis: 10000,
102
+ });
96
103
 
97
104
  tracerProvider = new NodeTracerProvider({
98
105
  spanProcessors: [spanProcessor],
@@ -151,35 +158,52 @@ export function setupTracing(options = {}) {
151
158
  applyCustomAttributesOnSpan,
152
159
  requestHook: (span, request) => {
153
160
  // Enrich spans with additional HTTP request attributes
154
- if (request.headers) {
155
- const userAgent = request.headers['user-agent'];
156
- const contentType = request.headers['content-type'];
157
- const contentLength = request.headers['content-length'];
158
-
159
- if (userAgent) span.setAttribute('http.user_agent', userAgent);
160
- if (contentType) span.setAttribute('http.request.content_type', contentType);
161
- if (contentLength) span.setAttribute('http.request.content_length', parseInt(contentLength, 10));
161
+ if (!request.headers) return;
162
+
163
+ const headers = request.headers;
164
+
165
+ // Safe header extraction with case-insensitive fallback
166
+ const userAgent = headers['user-agent'] || headers['User-Agent'];
167
+ const contentType = headers['content-type'] || headers['Content-Type'];
168
+ const contentLength = headers['content-length'] || headers['Content-Length'];
169
+ const requestId = headers['x-request-id'] || headers['X-Request-ID'];
170
+ const correlationId = headers['x-correlation-id'] || headers['X-Correlation-ID'];
171
+
172
+ // Only set attributes if values exist
173
+ if (userAgent) span.setAttribute('http.user_agent', userAgent);
174
+ if (contentType) span.setAttribute('http.request.content_type', contentType);
175
+
176
+ // Safe integer parsing with validation
177
+ if (contentLength) {
178
+ const length = parseInt(contentLength, 10);
179
+ if (!Number.isNaN(length) && length >= 0) {
180
+ span.setAttribute('http.request.content_length', length);
181
+ }
162
182
  }
183
+
184
+ // Correlation headers for distributed tracing
185
+ if (requestId) span.setAttribute('http.request_id', requestId);
186
+ if (correlationId) span.setAttribute('http.correlation_id', correlationId);
163
187
  },
164
188
  responseHook: (span, response) => {
165
189
  // Add response attributes for better observability
166
- if (response.headers) {
167
- const contentType = response.headers['content-type'];
168
- const contentLength = response.headers['content-length'];
190
+ if (!response.headers) return;
191
+
192
+ const headers = response.headers;
193
+ const contentType = headers['content-type'] || headers['Content-Type'];
194
+ const contentLength = headers['content-length'] || headers['Content-Length'];
195
+ const requestId = headers['x-request-id'] || headers['X-Request-ID'];
169
196
 
170
- if (contentType) span.setAttribute('http.response.content_type', contentType);
171
- if (contentLength) span.setAttribute('http.response.content_length', parseInt(contentLength, 10));
197
+ if (contentType) span.setAttribute('http.response.content_type', contentType);
198
+
199
+ if (contentLength) {
200
+ const length = parseInt(contentLength, 10);
201
+ if (!Number.isNaN(length) && length >= 0) {
202
+ span.setAttribute('http.response.content_length', length);
203
+ }
172
204
  }
173
- },
174
- headersToSpanAttributes: {
175
- server: {
176
- requestHeaders: ['x-request-id', 'x-correlation-id', 'x-trace-id'],
177
- responseHeaders: ['x-request-id', 'x-correlation-id'],
178
- },
179
- client: {
180
- requestHeaders: ['x-request-id', 'x-correlation-id', 'x-trace-id'],
181
- responseHeaders: ['x-request-id', 'x-correlation-id'],
182
- },
205
+
206
+ if (requestId) span.setAttribute('http.request_id', requestId);
183
207
  },
184
208
  }),
185
209
  new ExpressInstrumentation({
@@ -409,3 +433,12 @@ export async function stopTracing() {
409
433
  console.warn('Tracer provider is not initialized.');
410
434
  }
411
435
  }
436
+
437
+ /**
438
+ * @internal
439
+ * Resets the tracer provider for testing purposes.
440
+ * DO NOT use in production code.
441
+ */
442
+ export function __resetTracingForTesting() {
443
+ tracerProvider = null;
444
+ }
@@ -1,14 +1,30 @@
1
1
  // index.test.mjs
2
- import { describe, it, beforeEach } from 'node:test';
2
+ import { describe, it, beforeEach, afterEach } from 'node:test';
3
3
  import assert from 'node:assert';
4
4
 
5
5
  describe('setupTracing', () => {
6
- // Clear environment before each test
7
- beforeEach(() => {
6
+ let stopTracing;
7
+
8
+ // Clear environment and reset tracing state before each test
9
+ beforeEach(async () => {
8
10
  delete process.env.SERVICE_NAME;
9
11
  delete process.env.ENDPOINT;
10
12
  delete process.env.HOSTNAME;
11
13
  delete process.env.CONTAINER_NAME;
14
+
15
+ // Import and store stopTracing for cleanup
16
+ const tracing = await import('./index.mjs');
17
+ stopTracing = tracing.stopTracing;
18
+
19
+ // Reset singleton for test isolation
20
+ tracing.__resetTracingForTesting();
21
+ });
22
+
23
+ // Clean up tracing after each test
24
+ afterEach(async () => {
25
+ if (stopTracing) {
26
+ await stopTracing();
27
+ }
12
28
  });
13
29
 
14
30
  it('should throw error when serviceName is not provided', async () => {
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@saidsef/tracing-node",
3
- "version": "3.13.6",
3
+ "version": "3.14.0",
4
4
  "description": "tracing NodeJS - Wrapper for OpenTelemetry instrumentation packages",
5
5
  "main": "libs/index.mjs",
6
6
  "scripts": {
7
- "test": "node --trace-warnings --test --report-uncaught-exception --heap-prof --cpu-prof --track-heap-objects --report-dir=test/ --diagnostic-dir=test/ --heap-prof-dir=test/ libs/index.test.mjs",
7
+ "test": "node --trace-warnings --test --report-uncaught-exception libs/index.test.mjs",
8
8
  "lint": "eslint .",
9
9
  "rebuild": "rm -rfv node_modules/ package-lock.json && npm install --prod --omit=dev"
10
10
  },
@@ -34,14 +34,14 @@
34
34
  "@opentelemetry/context-async-hooks": "^2.0.1",
35
35
  "@opentelemetry/exporter-trace-otlp-grpc": "^0.213.0",
36
36
  "@opentelemetry/instrumentation": "^0.213.0",
37
- "@opentelemetry/instrumentation-aws-sdk": "^0.67.0",
38
- "@opentelemetry/instrumentation-connect": "^0.55.0",
39
- "@opentelemetry/instrumentation-dns": "^0.55.0",
40
- "@opentelemetry/instrumentation-express": "^0.60.0",
41
- "@opentelemetry/instrumentation-fs": "^0.31.0",
37
+ "@opentelemetry/instrumentation-aws-sdk": "^0.68.0",
38
+ "@opentelemetry/instrumentation-connect": "^0.56.0",
39
+ "@opentelemetry/instrumentation-dns": "^0.56.0",
40
+ "@opentelemetry/instrumentation-express": "^0.61.0",
41
+ "@opentelemetry/instrumentation-fs": "^0.32.0",
42
42
  "@opentelemetry/instrumentation-http": "^0.213.0",
43
- "@opentelemetry/instrumentation-ioredis": "^0.60.0",
44
- "@opentelemetry/instrumentation-pino": "^0.58.0",
43
+ "@opentelemetry/instrumentation-ioredis": "^0.61.0",
44
+ "@opentelemetry/instrumentation-pino": "^0.59.0",
45
45
  "@opentelemetry/resources": "^2.0.1",
46
46
  "@opentelemetry/sdk-trace-base": "^2.0.0",
47
47
  "@opentelemetry/sdk-trace-node": "^2.0.0",
@@ -51,5 +51,8 @@
51
51
  "devDependencies": {
52
52
  "eslint": "^9.30.0",
53
53
  "jest": "^30.0.0"
54
+ },
55
+ "overrides": {
56
+ "protobufjs": "^7.5.3"
54
57
  }
55
58
  }