@saidsef/tracing-node 3.11.1 → 3.12.1

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 +117 -17
  2. package/package.json +1 -1
package/libs/index.mjs CHANGED
@@ -109,24 +109,61 @@ export function setupTracing(options = {}) {
109
109
  return req.url.startsWith('/metrics') || req.url.startsWith('/healthz');
110
110
  };
111
111
 
112
- // Hook to set peer service name for outgoing requests
113
- const applyCustomAttributesOnSpan = (span, request) => {
114
- const url = request?.url || request?.uri || '';
115
- const hostname = request?.hostname || request?.host || '';
112
+ // Helper function to extract hostname from various request formats
113
+ const extractHostname = (request) => {
114
+ // Try to get hostname from various possible locations
115
+ let hostname = request?.hostname || request?.host || '';
116
116
 
117
- // Detect Elasticsearch endpoints
118
- if (hostname.includes('elasticsearch') || url.includes('elasticsearch') ||
119
- hostname.includes(':9200') || url.includes(':9200')) {
120
- span.setAttribute('peer.service', 'elasticsearch');
121
- span.setAttribute('db.system', 'elasticsearch');
117
+ // If not found, try to parse from URL
118
+ if (!hostname && request?.url) {
119
+ try {
120
+ const urlObj = new URL(request.url);
121
+ hostname = urlObj.hostname;
122
+ } catch {
123
+ // If URL parsing fails, try to extract from request options
124
+ if (request?.options?.hostname) {
125
+ hostname = request.options.hostname;
126
+ } else if (request?.options?.host) {
127
+ hostname = request.options.host;
128
+ }
129
+ }
130
+ }
131
+
132
+ return hostname;
133
+ };
134
+
135
+ // Helper function to determine peer service name from hostname/URL
136
+ const getPeerServiceName = (hostname, url = '') => {
137
+ const lowerHostname = hostname.toLowerCase();
138
+ const lowerUrl = url.toLowerCase();
139
+
140
+ // Check for known service patterns
141
+ if (lowerHostname.includes('elasticsearch') || lowerUrl.includes('elasticsearch') ||
142
+ lowerHostname.includes(':9200') || lowerUrl.includes(':9200')) {
143
+ return 'elasticsearch';
144
+ }
145
+
146
+ if (lowerHostname.includes('redis') || lowerUrl.includes('redis') ||
147
+ lowerHostname.includes(':6379') || lowerUrl.includes(':6379')) {
148
+ return 'redis';
149
+ }
150
+
151
+ // Extract service name from hostname patterns like:
152
+ // - service-name.namespace.svc.cluster.local
153
+ // - service-name.namespace.svc
154
+ // - api.service-name.com
155
+ if (lowerHostname.includes('.svc')) {
156
+ const parts = lowerHostname.split('.');
157
+ return parts[0]; // Return the service name part
122
158
  }
123
159
 
124
- // Detect Redis endpoints
125
- if (hostname.includes('redis') || url.includes('redis') ||
126
- hostname.includes(':6379') || url.includes(':6379')) {
127
- span.setAttribute('peer.service', 'redis');
128
- span.setAttribute('db.system', 'redis');
160
+ // For external domains, use the hostname without 'www'
161
+ if (lowerHostname.startsWith('www.')) {
162
+ return lowerHostname.substring(4);
129
163
  }
164
+
165
+ // Return the full hostname as the service name
166
+ return hostname || 'unknown';
130
167
  };
131
168
 
132
169
  // Register instrumentations
@@ -134,8 +171,39 @@ export function setupTracing(options = {}) {
134
171
  new HttpInstrumentation({
135
172
  serverName: serviceName,
136
173
  ignoreIncomingRequestHook,
137
- applyCustomAttributesOnSpan,
174
+ requireParentforOutgoingSpans: false,
175
+ requireParentforIncomingSpans: false,
138
176
  requestHook: (span, request) => {
177
+ const spanKind = span.kind;
178
+ const isClientSpan = spanKind === 3; // SpanKind.CLIENT = 3
179
+
180
+ // For CLIENT spans (outgoing requests), add peer service attributes
181
+ if (isClientSpan) {
182
+ const hostname = extractHostname(request);
183
+ const url = request?.url || request?.uri || '';
184
+
185
+ if (hostname) {
186
+ const peerService = getPeerServiceName(hostname, url);
187
+
188
+ // Set attributes for service graph
189
+ span.setAttribute('peer.service', peerService);
190
+ span.setAttribute('net.peer.name', hostname);
191
+
192
+ // Add port if available
193
+ const port = request?.port || request?.options?.port;
194
+ if (port) {
195
+ span.setAttribute('net.peer.port', parseInt(port, 10));
196
+ }
197
+
198
+ // Add method for better span naming
199
+ const method = request?.method || 'GET';
200
+ span.setAttribute('http.method', method.toUpperCase());
201
+
202
+ // Update span name for clarity
203
+ span.updateName(`${method.toUpperCase()} ${peerService}`);
204
+ }
205
+ }
206
+
139
207
  // Enrich spans with additional HTTP request attributes
140
208
  if (request.headers) {
141
209
  const userAgent = request.headers['user-agent'];
@@ -240,14 +308,46 @@ export function setupTracing(options = {}) {
240
308
  },
241
309
  }),
242
310
  new IORedisInstrumentation({
311
+ requireParentSpan: false,
312
+ requestHook: (span) => {
313
+ // Set peer.service for service graph visualization - CRITICAL for Tempo
314
+ // This must be set in requestHook to ensure it's available for service graph
315
+ span.setAttribute('peer.service', 'redis');
316
+ span.setAttribute('db.system', 'redis');
317
+
318
+ // Ensure span kind is CLIENT for proper service graph visualization
319
+ span.setAttribute('span.kind', 'CLIENT');
320
+
321
+ // Add additional attributes for better service graph visualization
322
+ span.setAttribute('db.connection_string', 'redis');
323
+ span.setAttribute('net.peer.name', 'redis');
324
+ },
243
325
  responseHook: (span) => {
326
+ // Ensure peer.service persists through response
244
327
  span.setAttribute('peer.service', 'redis');
245
328
  },
329
+ dbStatementSerializer: (cmdName, cmdArgs) => {
330
+ // Serialize command for better observability (limit arg length to avoid huge spans)
331
+ const args = cmdArgs.map(arg => {
332
+ const str = String(arg);
333
+ return str.length > 100 ? `${str.substring(0, 100)}...` : str;
334
+ });
335
+ return `${cmdName} ${args.join(' ')}`;
336
+ },
337
+ }),
338
+ new ElasticsearchInstrumentation({
339
+ suppressInternalInstrumentation: true,
246
340
  requestHook: (span) => {
247
- span.setAttribute('peer.service', 'redis');
341
+ // Set peer.service for service graph visualization - CRITICAL for Tempo
342
+ span.setAttribute('peer.service', 'elasticsearch');
343
+ span.setAttribute('db.system', 'elasticsearch');
344
+ },
345
+ responseHook: (span) => {
346
+ // Ensure peer.service persists through response
347
+ span.setAttribute('peer.service', 'elasticsearch');
348
+ span.setAttribute('db.system', 'elasticsearch');
248
349
  },
249
350
  }),
250
- new ElasticsearchInstrumentation(),
251
351
  ];
252
352
 
253
353
  if (enableFsInstrumentation) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saidsef/tracing-node",
3
- "version": "3.11.1",
3
+ "version": "3.12.1",
4
4
  "description": "tracing NodeJS - Wrapper for OpenTelemetry instrumentation packages",
5
5
  "main": "libs/index.mjs",
6
6
  "scripts": {