@saidsef/tracing-node 3.9.0 → 3.11.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 +172 -7
  2. package/package.json +1 -1
package/libs/index.mjs CHANGED
@@ -44,6 +44,8 @@ diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);
44
44
  * This function configures a NodeTracerProvider with various instrumentations
45
45
  * and span processors to enable tracing for the application. It supports
46
46
  * tracing for HTTP, Express, AWS, Pino, DNS, Elasticsearch, and IORedis.
47
+ * The IORedis instrumentation includes peer.service attributes for proper
48
+ * service map visualization in distributed tracing tools like Tempo.
47
49
  *
48
50
  * @param {Object} options - Configuration options for tracing.
49
51
  * @param {string} [options.hostname=process.env.HOSTNAME] - The hostname of the service.
@@ -118,20 +120,133 @@ export function setupTracing(options = {}) {
118
120
  span.setAttribute('peer.service', 'elasticsearch');
119
121
  span.setAttribute('db.system', 'elasticsearch');
120
122
  }
123
+
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');
129
+ }
121
130
  };
122
131
 
123
132
  // Register instrumentations
124
133
  const instrumentations = [
125
134
  new HttpInstrumentation({
126
- serverName: serviceName,
135
+ serverName: serviceName,
127
136
  ignoreIncomingRequestHook,
128
137
  applyCustomAttributesOnSpan,
138
+ requestHook: (span, request) => {
139
+ // Enrich spans with additional HTTP request attributes
140
+ if (request.headers) {
141
+ const userAgent = request.headers['user-agent'];
142
+ const contentType = request.headers['content-type'];
143
+ const contentLength = request.headers['content-length'];
144
+
145
+ if (userAgent) span.setAttribute('http.user_agent', userAgent);
146
+ if (contentType) span.setAttribute('http.request.content_type', contentType);
147
+ if (contentLength) span.setAttribute('http.request.content_length', parseInt(contentLength, 10));
148
+ }
149
+ },
150
+ responseHook: (span, response) => {
151
+ // Add response attributes for better observability
152
+ if (response.headers) {
153
+ const contentType = response.headers['content-type'];
154
+ const contentLength = response.headers['content-length'];
155
+
156
+ if (contentType) span.setAttribute('http.response.content_type', contentType);
157
+ if (contentLength) span.setAttribute('http.response.content_length', parseInt(contentLength, 10));
158
+ }
159
+ },
160
+ headersToSpanAttributes: {
161
+ server: {
162
+ requestHeaders: ['x-request-id', 'x-correlation-id', 'x-trace-id'],
163
+ responseHeaders: ['x-request-id', 'x-correlation-id'],
164
+ },
165
+ client: {
166
+ requestHeaders: ['x-request-id', 'x-correlation-id', 'x-trace-id'],
167
+ responseHeaders: ['x-request-id', 'x-correlation-id'],
168
+ },
169
+ },
170
+ }),
171
+ new ExpressInstrumentation({
172
+ ignoreIncomingRequestHook,
173
+ requestHook: (span, request) => {
174
+ // Add Express-specific attributes
175
+ if (request.route?.path) {
176
+ span.setAttribute('express.route', request.route.path);
177
+ span.updateName(`${request.method} ${request.route.path}`);
178
+ }
179
+ if (request.params && Object.keys(request.params).length > 0) {
180
+ span.setAttribute('express.params', JSON.stringify(request.params));
181
+ }
182
+ if (request.query && Object.keys(request.query).length > 0) {
183
+ span.setAttribute('express.query', JSON.stringify(request.query));
184
+ }
185
+ // Add user context if available
186
+ if (request.user?.id) {
187
+ span.setAttribute('user.id', request.user.id);
188
+ }
189
+ },
190
+ }),
191
+ new PinoInstrumentation({
192
+ logHook: (span, record) => {
193
+ // Inject trace context into log records
194
+ const spanContext = span.spanContext();
195
+ record['trace_id'] = spanContext.traceId;
196
+ record['span_id'] = spanContext.spanId;
197
+ record['trace_flags'] = `0${spanContext.traceFlags.toString(16)}`;
198
+
199
+ // Add service name for better log correlation
200
+ if (serviceName) {
201
+ record['service.name'] = serviceName;
202
+ }
203
+ },
204
+ logSeverity: {
205
+ error: 'ERROR',
206
+ warn: 'WARN',
207
+ info: 'INFO',
208
+ debug: 'DEBUG',
209
+ trace: 'TRACE',
210
+ },
211
+ }),
212
+ new ConnectInstrumentation({
213
+ ignoreIncomingRequestHook,
214
+ requestHook: (span, request) => {
215
+ // Add Connect middleware attributes
216
+ if (request.url) {
217
+ span.setAttribute('connect.url', request.url);
218
+ }
219
+ if (request.method) {
220
+ span.setAttribute('connect.method', request.method);
221
+ }
222
+ },
223
+ }),
224
+ new AwsInstrumentation({
225
+ suppressInternalInstrumentation: false,
226
+ sqsExtractContextPropagationFromPayload: true,
227
+ preRequestHook: (span, request) => {
228
+ // Add peer.service attribute for better service map visualization
229
+ const serviceName = request.serviceName || request.service?.serviceIdentifier;
230
+ if (serviceName) {
231
+ span.setAttribute('peer.service', serviceName.toLowerCase());
232
+ span.setAttribute('aws.service', serviceName.toLowerCase());
233
+ }
234
+ },
235
+ responseHook: (span, response) => {
236
+ // Add additional attributes from response if available
237
+ if (response?.requestId) {
238
+ span.setAttribute('aws.request_id', response.requestId);
239
+ }
240
+ },
241
+ }),
242
+ new IORedisInstrumentation({
243
+ responseHook: (span) => {
244
+ span.setAttribute('peer.service', 'redis');
245
+ },
246
+ requestHook: (span) => {
247
+ span.setAttribute('peer.service', 'redis');
248
+ },
129
249
  }),
130
- new ExpressInstrumentation({ ignoreIncomingRequestHook, }),
131
- new PinoInstrumentation({logHook: (span, record) => {record['trace_id'] = span.spanContext().traceId;record['span_id'] = span.spanContext().spanId;},}),
132
- new ConnectInstrumentation(),
133
- new AwsInstrumentation({ sqsExtractContextPropagationFromPayload: true, }),
134
- new IORedisInstrumentation(),
135
250
  new ElasticsearchInstrumentation(),
136
251
  ];
137
252
 
@@ -144,7 +259,57 @@ export function setupTracing(options = {}) {
144
259
  if (enableDnsInstrumentation) {
145
260
  // Enable DNS instrumentation if specified
146
261
  // This instrumentation is useful for tracing DNS operations.
147
- instrumentations.push(new DnsInstrumentation());
262
+ instrumentations.push(new DnsInstrumentation({
263
+ ignoreHostnames: ['localhost', '127.0.0.1', '::1'],
264
+ requestHook: (span, request) => {
265
+ // Add DNS query details for better observability
266
+ if (request.hostname) {
267
+ span.setAttribute('dns.hostname', request.hostname);
268
+ span.updateName(`DNS ${request.hostname}`);
269
+ }
270
+ if (request.rrtype) {
271
+ span.setAttribute('dns.record_type', request.rrtype);
272
+ }
273
+ // Add additional context
274
+ span.setAttribute('peer.service', 'dns');
275
+ span.setAttribute('dns.query_count', 1);
276
+ },
277
+ responseHook: (span, response) => {
278
+ // Add DNS response details
279
+ if (Array.isArray(response)) {
280
+ span.setAttribute('dns.result_count', response.length);
281
+ // Log first few results for debugging (limit to avoid overwhelming spans)
282
+ const resultSample = response.slice(0, 3).map(r =>
283
+ typeof r === 'string' ? r : JSON.stringify(r)
284
+ );
285
+ if (resultSample.length > 0) {
286
+ span.setAttribute('dns.results', JSON.stringify(resultSample));
287
+ }
288
+ } else if (response) {
289
+ span.setAttribute('dns.result_count', 1);
290
+ span.setAttribute('dns.result', typeof response === 'string' ? response : JSON.stringify(response));
291
+ }
292
+ },
293
+ errorHook: (span, error) => {
294
+ // Enhanced error tracking for DNS failures
295
+ if (error) {
296
+ span.setAttribute('dns.error', true);
297
+ span.setAttribute('dns.error.code', error.code || 'UNKNOWN');
298
+ span.setAttribute('dns.error.message', error.message || 'DNS lookup failed');
299
+
300
+ // Categorize common DNS errors
301
+ if (error.code === 'ENOTFOUND') {
302
+ span.setAttribute('dns.error.type', 'NOT_FOUND');
303
+ } else if (error.code === 'ETIMEOUT') {
304
+ span.setAttribute('dns.error.type', 'TIMEOUT');
305
+ } else if (error.code === 'ECONNREFUSED') {
306
+ span.setAttribute('dns.error.type', 'CONNECTION_REFUSED');
307
+ } else {
308
+ span.setAttribute('dns.error.type', 'OTHER');
309
+ }
310
+ }
311
+ },
312
+ }));
148
313
  }
149
314
 
150
315
  // Register instrumentations
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saidsef/tracing-node",
3
- "version": "3.9.0",
3
+ "version": "3.11.0",
4
4
  "description": "tracing NodeJS - Wrapper for OpenTelemetry instrumentation packages",
5
5
  "main": "libs/index.mjs",
6
6
  "scripts": {