@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.
- package/libs/index.mjs +172 -7
- 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
|