@comprehend/telemetry-node 0.1.4 → 0.2.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.
- package/README.md +112 -27
- package/dist/ComprehendDevSpanProcessor.d.ts +10 -6
- package/dist/ComprehendDevSpanProcessor.js +154 -87
- package/dist/ComprehendDevSpanProcessor.test.js +270 -449
- package/dist/ComprehendMetricsExporter.d.ts +18 -0
- package/dist/ComprehendMetricsExporter.js +178 -0
- package/dist/ComprehendMetricsExporter.test.d.ts +1 -0
- package/dist/ComprehendMetricsExporter.test.js +266 -0
- package/dist/ComprehendSDK.d.ts +18 -0
- package/dist/ComprehendSDK.js +56 -0
- package/dist/ComprehendSDK.test.d.ts +1 -0
- package/dist/ComprehendSDK.test.js +126 -0
- package/dist/WebSocketConnection.d.ts +23 -3
- package/dist/WebSocketConnection.js +106 -12
- package/dist/WebSocketConnection.test.js +236 -169
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -1
- package/dist/util.d.ts +2 -0
- package/dist/util.js +7 -0
- package/dist/wire-protocol.d.ts +168 -28
- package/package.json +3 -1
- package/src/ComprehendDevSpanProcessor.test.ts +311 -507
- package/src/ComprehendDevSpanProcessor.ts +178 -105
- package/src/ComprehendMetricsExporter.test.ts +334 -0
- package/src/ComprehendMetricsExporter.ts +225 -0
- package/src/ComprehendSDK.test.ts +160 -0
- package/src/ComprehendSDK.ts +63 -0
- package/src/WebSocketConnection.test.ts +286 -205
- package/src/WebSocketConnection.ts +135 -13
- package/src/index.ts +3 -2
- package/src/util.ts +6 -0
- package/src/wire-protocol.ts +204 -29
- package/src/sql-analyzer.test.ts +0 -599
- package/src/sql-analyzer.ts +0 -439
|
@@ -1,38 +1,49 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ComprehendDevSpanProcessor = void 0;
|
|
4
|
+
exports.matchSpanRule = matchSpanRule;
|
|
4
5
|
const sha2_1 = require("@noble/hashes/sha2");
|
|
5
6
|
const utils_1 = require("@noble/hashes/utils");
|
|
6
|
-
const
|
|
7
|
-
const WebSocketConnection_1 = require("./WebSocketConnection");
|
|
7
|
+
const api_1 = require("@opentelemetry/api");
|
|
8
8
|
const sqlDbSystems = new Set([
|
|
9
9
|
'mysql', 'postgresql', 'mssql', 'oracle', 'db2', 'sqlite', 'hsqldb', 'h2',
|
|
10
10
|
'informix', 'cockroachdb', 'redshift', 'tidb', 'trino', 'greenplum'
|
|
11
11
|
]);
|
|
12
12
|
class ComprehendDevSpanProcessor {
|
|
13
|
-
constructor(
|
|
13
|
+
constructor(connection) {
|
|
14
14
|
this.observedServices = [];
|
|
15
15
|
this.observedDatabases = [];
|
|
16
16
|
this.observedHttpServices = [];
|
|
17
17
|
this.observedInteractions = new Map();
|
|
18
|
-
this.
|
|
19
|
-
this.
|
|
18
|
+
this.processContextSet = false;
|
|
19
|
+
this.spanObservationSpecs = [];
|
|
20
|
+
this.connection = connection;
|
|
21
|
+
}
|
|
22
|
+
updateCustomMetrics(specs) {
|
|
23
|
+
this.spanObservationSpecs = specs.filter((s) => s.type === 'span');
|
|
20
24
|
}
|
|
21
25
|
onStart(span, parentContext) {
|
|
22
26
|
}
|
|
23
27
|
onEnd(span) {
|
|
28
|
+
const process = () => this.processSpan(span);
|
|
29
|
+
if (span.resource.asyncAttributesPending) {
|
|
30
|
+
span.resource.waitForAsyncAttributes?.().then(process);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
process();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
processSpan(span) {
|
|
24
37
|
const currentService = this.discoverService(span);
|
|
25
38
|
if (!currentService)
|
|
26
39
|
return;
|
|
27
40
|
const attrs = span.attributes;
|
|
28
|
-
if (span.kind === 1) {
|
|
29
|
-
// Server span, see if it's something to ingest.
|
|
41
|
+
if (span.kind === 1) { // Server span
|
|
30
42
|
if (attrs['http.route'] && attrs['http.method']) {
|
|
31
43
|
this.processHTTPRoute(currentService, attrs['http.route'], attrs['http.method'], span);
|
|
32
44
|
}
|
|
33
45
|
}
|
|
34
|
-
else if (span.kind === 2) {
|
|
35
|
-
// Client span, see if it's something to ingest.
|
|
46
|
+
else if (span.kind === 2) { // Client span
|
|
36
47
|
if (attrs['http.url']) {
|
|
37
48
|
this.processHttpRequest(currentService, attrs['http.url'], span);
|
|
38
49
|
}
|
|
@@ -40,9 +51,12 @@ class ComprehendDevSpanProcessor {
|
|
|
40
51
|
if (attrs['db.system']) {
|
|
41
52
|
this.processDatabaseOperation(currentService, span);
|
|
42
53
|
}
|
|
54
|
+
// Report trace span for every span
|
|
55
|
+
this.reportTraceSpan(span);
|
|
56
|
+
// Check custom span observation matching
|
|
57
|
+
this.checkCustomSpanMatching(span);
|
|
43
58
|
}
|
|
44
59
|
discoverService(span) {
|
|
45
|
-
// Look for an existing matching entry.
|
|
46
60
|
const resAttrs = span.resource.attributes;
|
|
47
61
|
const name = resAttrs['service.name'];
|
|
48
62
|
if (!name)
|
|
@@ -54,7 +68,6 @@ class ComprehendDevSpanProcessor {
|
|
|
54
68
|
s.environment === environment);
|
|
55
69
|
if (existing)
|
|
56
70
|
return existing;
|
|
57
|
-
// New; hash it and add it to the observed services.
|
|
58
71
|
const idString = `service:${name}:${namespace ?? ''}:${environment ?? ''}`;
|
|
59
72
|
const hash = hashIdString(idString);
|
|
60
73
|
const newService = {
|
|
@@ -62,7 +75,6 @@ class ComprehendDevSpanProcessor {
|
|
|
62
75
|
httpRoutes: []
|
|
63
76
|
};
|
|
64
77
|
this.observedServices.push(newService);
|
|
65
|
-
// Ingest its existence.
|
|
66
78
|
const message = {
|
|
67
79
|
event: "new-entity",
|
|
68
80
|
type: "service",
|
|
@@ -72,18 +84,21 @@ class ComprehendDevSpanProcessor {
|
|
|
72
84
|
...(environment ? { environment } : {})
|
|
73
85
|
};
|
|
74
86
|
this.ingestMessage(message);
|
|
87
|
+
// Set process context on first service discovery
|
|
88
|
+
if (!this.processContextSet) {
|
|
89
|
+
this.processContextSet = true;
|
|
90
|
+
const resources = flattenResourceAttributes(resAttrs);
|
|
91
|
+
this.connection.setProcessContext(hash, resources);
|
|
92
|
+
}
|
|
75
93
|
return newService;
|
|
76
94
|
}
|
|
77
95
|
processHTTPRoute(service, route, method, span) {
|
|
78
|
-
// Check if this route+method already exists under the service
|
|
79
96
|
let observedRoute = service.httpRoutes.find(r => r.route === route && r.method === method);
|
|
80
97
|
if (!observedRoute) {
|
|
81
|
-
// It's new; hash it and add it to our collection.
|
|
82
98
|
const idString = `http-route:${service.hash}:${method}:${route}`;
|
|
83
99
|
const hash = hashIdString(idString);
|
|
84
100
|
observedRoute = { method, route, hash };
|
|
85
101
|
service.httpRoutes.push(observedRoute);
|
|
86
|
-
// Emit observation message
|
|
87
102
|
const message = {
|
|
88
103
|
event: "new-entity",
|
|
89
104
|
type: "http-route",
|
|
@@ -93,14 +108,13 @@ class ComprehendDevSpanProcessor {
|
|
|
93
108
|
};
|
|
94
109
|
this.ingestMessage(message);
|
|
95
110
|
}
|
|
96
|
-
// Extract the request path, making sure we
|
|
111
|
+
// Extract the request path, making sure we strip any query string.
|
|
97
112
|
const attrs = span.attributes;
|
|
98
113
|
let path;
|
|
99
114
|
if (attrs['http.target']) {
|
|
100
115
|
try {
|
|
101
|
-
// This might be just a path like "/search?q=foo"
|
|
102
116
|
const rawTarget = attrs['http.target'];
|
|
103
|
-
const fakeUrl = new URL(rawTarget, 'http://placeholder');
|
|
117
|
+
const fakeUrl = new URL(rawTarget, 'http://placeholder');
|
|
104
118
|
path = fakeUrl.pathname;
|
|
105
119
|
}
|
|
106
120
|
catch {
|
|
@@ -120,7 +134,6 @@ class ComprehendDevSpanProcessor {
|
|
|
120
134
|
else {
|
|
121
135
|
path = '/';
|
|
122
136
|
}
|
|
123
|
-
// Build and ingest observation.
|
|
124
137
|
const status = attrs['http.status_code'] ?? 0;
|
|
125
138
|
const duration = span.duration;
|
|
126
139
|
const httpVersion = attrs['http.flavor'];
|
|
@@ -128,9 +141,12 @@ class ComprehendDevSpanProcessor {
|
|
|
128
141
|
const requestBytes = attrs['http.request_content_length'];
|
|
129
142
|
const responseBytes = attrs['http.response_content_length'];
|
|
130
143
|
const { message: errorMessage, type: errorType, stack: stack } = extractErrorInfo(span);
|
|
144
|
+
const { traceId, spanId } = getSpanContext(span);
|
|
131
145
|
const observation = {
|
|
132
146
|
type: 'http-server',
|
|
133
147
|
subject: observedRoute.hash,
|
|
148
|
+
spanId,
|
|
149
|
+
traceId,
|
|
134
150
|
timestamp: span.startTime,
|
|
135
151
|
path,
|
|
136
152
|
status,
|
|
@@ -145,12 +161,11 @@ class ComprehendDevSpanProcessor {
|
|
|
145
161
|
};
|
|
146
162
|
this.ingestMessage({
|
|
147
163
|
event: "observations",
|
|
148
|
-
seq: this.
|
|
164
|
+
seq: this.connection.nextSeq(),
|
|
149
165
|
observations: [observation]
|
|
150
166
|
});
|
|
151
167
|
}
|
|
152
168
|
processDatabaseOperation(currentService, span) {
|
|
153
|
-
// Parse the connection string.
|
|
154
169
|
const attrs = span.attributes;
|
|
155
170
|
const system = attrs['db.system'];
|
|
156
171
|
const rawConnection = attrs['db.connection_string'];
|
|
@@ -163,10 +178,8 @@ class ComprehendDevSpanProcessor {
|
|
|
163
178
|
port: attrs['net.peer.port']?.toString(),
|
|
164
179
|
name: attrs['db.name'],
|
|
165
180
|
};
|
|
166
|
-
// See if we already have an entry for this database.
|
|
167
181
|
const hash = hashIdString(`database:${system}:${parsed.host ?? ''}:${parsed.port ?? ''}:${parsed.name ?? ''}`);
|
|
168
182
|
let observedDatabase = this.observedDatabases.find(db => db.hash === hash);
|
|
169
|
-
// If we see it for the first time, add and ingest it.
|
|
170
183
|
if (!observedDatabase) {
|
|
171
184
|
observedDatabase = {
|
|
172
185
|
system,
|
|
@@ -176,7 +189,6 @@ class ComprehendDevSpanProcessor {
|
|
|
176
189
|
hash
|
|
177
190
|
};
|
|
178
191
|
this.observedDatabases.push(observedDatabase);
|
|
179
|
-
// The existence of the database.
|
|
180
192
|
const message = {
|
|
181
193
|
event: "new-entity",
|
|
182
194
|
type: "database",
|
|
@@ -190,7 +202,6 @@ class ComprehendDevSpanProcessor {
|
|
|
190
202
|
};
|
|
191
203
|
this.ingestMessage(message);
|
|
192
204
|
}
|
|
193
|
-
// The connection to the database should have an interaction.
|
|
194
205
|
const interactions = this.getInteractions(currentService.hash, observedDatabase.hash);
|
|
195
206
|
let connectionInteraction = interactions.dbConnections.find(c => c.connection === parsed.scrubbed && c.user === parsed.user);
|
|
196
207
|
if (!connectionInteraction) {
|
|
@@ -211,71 +222,42 @@ class ComprehendDevSpanProcessor {
|
|
|
211
222
|
};
|
|
212
223
|
this.ingestMessage(message);
|
|
213
224
|
}
|
|
214
|
-
//
|
|
225
|
+
// For SQL databases, send raw query to server for analysis
|
|
215
226
|
if (sqlDbSystems.has(system) && attrs['db.statement']) {
|
|
216
|
-
// The interaction, based upon the normalized query.
|
|
217
|
-
let queryInfo = (0, sql_analyzer_1.analyzeSQL)(attrs['db.statement']);
|
|
218
|
-
let queryInteraction = interactions.dbQueries.find(q => q.query === queryInfo.normalizedQuery);
|
|
219
|
-
if (!queryInteraction) {
|
|
220
|
-
queryInteraction = {
|
|
221
|
-
hash: hashIdString(`db-query:${currentService.hash}:${observedDatabase.hash}:${queryInfo.normalizedQuery}`),
|
|
222
|
-
query: queryInfo.normalizedQuery
|
|
223
|
-
};
|
|
224
|
-
interactions.dbQueries.push(queryInteraction);
|
|
225
|
-
const selects = getTablesWithOperation(queryInfo.tableOperations, 'SELECT');
|
|
226
|
-
const inserts = getTablesWithOperation(queryInfo.tableOperations, 'INSERT');
|
|
227
|
-
const updates = getTablesWithOperation(queryInfo.tableOperations, 'UPDATE');
|
|
228
|
-
const deletes = getTablesWithOperation(queryInfo.tableOperations, 'DELETE');
|
|
229
|
-
const message = {
|
|
230
|
-
event: "new-interaction",
|
|
231
|
-
type: "db-query",
|
|
232
|
-
hash: queryInteraction.hash,
|
|
233
|
-
from: currentService.hash,
|
|
234
|
-
to: observedDatabase.hash,
|
|
235
|
-
query: queryInfo.presentableQuery,
|
|
236
|
-
...(selects ? { selects } : {}),
|
|
237
|
-
...(inserts ? { inserts } : {}),
|
|
238
|
-
...(updates ? { updates } : {}),
|
|
239
|
-
...(deletes ? { deletes } : {}),
|
|
240
|
-
};
|
|
241
|
-
this.ingestMessage(message);
|
|
242
|
-
}
|
|
243
|
-
// Build and ingest observation.
|
|
244
227
|
const duration = span.duration;
|
|
245
228
|
const returnedRows = attrs['db.response.returned_rows']
|
|
246
229
|
?? attrs['db.sql.rows'];
|
|
247
230
|
const { message: errorMessage, type: errorType, stack } = extractErrorInfo(span);
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
231
|
+
const { traceId, spanId } = span.spanContext();
|
|
232
|
+
const dbQueryMessage = {
|
|
233
|
+
event: "db-query",
|
|
234
|
+
seq: this.connection.nextSeq(),
|
|
235
|
+
query: attrs['db.statement'],
|
|
236
|
+
from: currentService.hash,
|
|
237
|
+
to: observedDatabase.hash,
|
|
251
238
|
timestamp: span.startTime,
|
|
252
239
|
duration,
|
|
240
|
+
traceId,
|
|
241
|
+
spanId,
|
|
253
242
|
...(errorMessage ? { errorMessage } : {}),
|
|
254
243
|
...(errorType ? { errorType } : {}),
|
|
255
244
|
...(stack ? { stack } : {}),
|
|
256
245
|
...(returnedRows !== undefined ? { returnedRows } : {})
|
|
257
246
|
};
|
|
258
|
-
this.ingestMessage(
|
|
259
|
-
event: "observations",
|
|
260
|
-
seq: this.observationsSeq++,
|
|
261
|
-
observations: [observation]
|
|
262
|
-
});
|
|
247
|
+
this.ingestMessage(dbQueryMessage);
|
|
263
248
|
}
|
|
264
249
|
}
|
|
265
250
|
processHttpRequest(currentService, url, span) {
|
|
266
|
-
// Build identity based upon protocol, host, and port.
|
|
267
251
|
const parsed = new URL(url);
|
|
268
|
-
const protocol = parsed.protocol.replace(':', '');
|
|
252
|
+
const protocol = parsed.protocol.replace(':', '');
|
|
269
253
|
const host = parsed.hostname;
|
|
270
254
|
const port = parsed.port ? parseInt(parsed.port) : (protocol === 'https' ? 443 : 80);
|
|
271
255
|
const idString = `http-service:${protocol}:${host}:${port}`;
|
|
272
256
|
const hash = hashIdString(idString);
|
|
273
|
-
// Ingest it if it's not already observed.
|
|
274
257
|
let observedHttpService = this.observedHttpServices.find(s => s.protocol === protocol && s.host === host && s.port === port);
|
|
275
258
|
if (!observedHttpService) {
|
|
276
259
|
observedHttpService = { protocol, host, port, hash };
|
|
277
260
|
this.observedHttpServices.push(observedHttpService);
|
|
278
|
-
// The existence of the service.
|
|
279
261
|
const message = {
|
|
280
262
|
event: "new-entity",
|
|
281
263
|
type: "http-service",
|
|
@@ -286,7 +268,6 @@ class ComprehendDevSpanProcessor {
|
|
|
286
268
|
};
|
|
287
269
|
this.ingestMessage(message);
|
|
288
270
|
}
|
|
289
|
-
// Ingest the interaction if first observed.
|
|
290
271
|
const interactions = this.getInteractions(currentService.hash, observedHttpService.hash);
|
|
291
272
|
if (!interactions.httpRequest) {
|
|
292
273
|
const idString = `http-request:${currentService.hash}:${observedHttpService.hash}`;
|
|
@@ -301,11 +282,10 @@ class ComprehendDevSpanProcessor {
|
|
|
301
282
|
};
|
|
302
283
|
this.ingestMessage(message);
|
|
303
284
|
}
|
|
304
|
-
// Build and ingest observation.
|
|
305
285
|
const attrs = span.attributes;
|
|
306
286
|
const path = parsed.pathname || '/';
|
|
307
287
|
const method = span.attributes['http.method'];
|
|
308
|
-
if (!method)
|
|
288
|
+
if (!method)
|
|
309
289
|
return;
|
|
310
290
|
const status = attrs['http.status_code'];
|
|
311
291
|
const duration = span.duration;
|
|
@@ -313,9 +293,12 @@ class ComprehendDevSpanProcessor {
|
|
|
313
293
|
const requestBytes = attrs['http.request_content_length'];
|
|
314
294
|
const responseBytes = attrs['http.response_content_length'];
|
|
315
295
|
const { message: errorMessage, type: errorType, stack } = extractErrorInfo(span);
|
|
296
|
+
const { traceId, spanId } = getSpanContext(span);
|
|
316
297
|
const observation = {
|
|
317
298
|
type: "http-client",
|
|
318
299
|
subject: interactions.httpRequest.hash,
|
|
300
|
+
spanId,
|
|
301
|
+
traceId,
|
|
319
302
|
timestamp: span.startTime,
|
|
320
303
|
path,
|
|
321
304
|
method,
|
|
@@ -330,10 +313,57 @@ class ComprehendDevSpanProcessor {
|
|
|
330
313
|
};
|
|
331
314
|
this.ingestMessage({
|
|
332
315
|
event: "observations",
|
|
333
|
-
seq: this.
|
|
316
|
+
seq: this.connection.nextSeq(),
|
|
334
317
|
observations: [observation]
|
|
335
318
|
});
|
|
336
319
|
}
|
|
320
|
+
reportTraceSpan(span) {
|
|
321
|
+
const { traceId, spanId } = getSpanContext(span);
|
|
322
|
+
const parent = getParentSpanId(span);
|
|
323
|
+
const traceSpanMessage = {
|
|
324
|
+
event: "tracespans",
|
|
325
|
+
seq: this.connection.nextSeq(),
|
|
326
|
+
data: [{
|
|
327
|
+
trace: traceId,
|
|
328
|
+
span: spanId,
|
|
329
|
+
parent,
|
|
330
|
+
name: span.name,
|
|
331
|
+
timestamp: span.startTime,
|
|
332
|
+
}]
|
|
333
|
+
};
|
|
334
|
+
this.ingestMessage(traceSpanMessage);
|
|
335
|
+
}
|
|
336
|
+
checkCustomSpanMatching(span) {
|
|
337
|
+
for (const spec of this.spanObservationSpecs) {
|
|
338
|
+
if (matchSpanRule(span, spec.rule)) {
|
|
339
|
+
const { traceId, spanId } = getSpanContext(span);
|
|
340
|
+
const { message: errorMessage, type: errorType, stack } = extractErrorInfo(span);
|
|
341
|
+
const collectedAttrs = {};
|
|
342
|
+
for (const [key, value] of Object.entries(span.attributes)) {
|
|
343
|
+
if (value !== undefined) {
|
|
344
|
+
collectedAttrs[key] = value;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const observation = {
|
|
348
|
+
type: "custom",
|
|
349
|
+
subject: spec.subject,
|
|
350
|
+
id: spec.subject,
|
|
351
|
+
spanId,
|
|
352
|
+
traceId,
|
|
353
|
+
timestamp: span.startTime,
|
|
354
|
+
attributes: collectedAttrs,
|
|
355
|
+
...(errorMessage ? { errorMessage } : {}),
|
|
356
|
+
...(errorType ? { errorType } : {}),
|
|
357
|
+
...(stack ? { stack } : {}),
|
|
358
|
+
};
|
|
359
|
+
this.ingestMessage({
|
|
360
|
+
event: "observations",
|
|
361
|
+
seq: this.connection.nextSeq(),
|
|
362
|
+
observations: [observation]
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
337
367
|
getInteractions(from, to) {
|
|
338
368
|
let fromMap = this.observedInteractions.get(from);
|
|
339
369
|
if (!fromMap) {
|
|
@@ -342,7 +372,7 @@ class ComprehendDevSpanProcessor {
|
|
|
342
372
|
}
|
|
343
373
|
let interactions = fromMap.get(to);
|
|
344
374
|
if (!interactions) {
|
|
345
|
-
interactions = { httpRequest: undefined, dbConnections: []
|
|
375
|
+
interactions = { httpRequest: undefined, dbConnections: [] };
|
|
346
376
|
fromMap.set(to, interactions);
|
|
347
377
|
}
|
|
348
378
|
return interactions;
|
|
@@ -353,7 +383,6 @@ class ComprehendDevSpanProcessor {
|
|
|
353
383
|
async forceFlush() {
|
|
354
384
|
}
|
|
355
385
|
async shutdown() {
|
|
356
|
-
this.connection.close();
|
|
357
386
|
}
|
|
358
387
|
}
|
|
359
388
|
exports.ComprehendDevSpanProcessor = ComprehendDevSpanProcessor;
|
|
@@ -362,11 +391,36 @@ function hashIdString(idString) {
|
|
|
362
391
|
.map(b => b.toString(16).padStart(2, '0'))
|
|
363
392
|
.join('');
|
|
364
393
|
}
|
|
365
|
-
|
|
366
|
-
|
|
394
|
+
function getSpanContext(span) {
|
|
395
|
+
const ctx = span.spanContext?.();
|
|
396
|
+
return {
|
|
397
|
+
traceId: ctx?.traceId ?? '',
|
|
398
|
+
spanId: ctx?.spanId ?? '',
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function getParentSpanId(span) {
|
|
402
|
+
return span.parentSpanContext?.spanId ?? '';
|
|
403
|
+
}
|
|
404
|
+
function flattenResourceAttributes(attrs) {
|
|
405
|
+
const result = {};
|
|
406
|
+
function flatten(obj, prefix) {
|
|
407
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
408
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
409
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
410
|
+
flatten(value, fullKey);
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
result[fullKey] = String(value);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
flatten(attrs, '');
|
|
418
|
+
return result;
|
|
419
|
+
}
|
|
420
|
+
/** Try to extract data from the database connection string. Many are URL-based; falls back to
|
|
421
|
+
* semicolon-separated key=value parsing for the rest (e.g. MSSQL style). */
|
|
367
422
|
function parseDatabaseConnectionStringRaw(conn) {
|
|
368
423
|
try {
|
|
369
|
-
// Try URL-style parsing first; scrub the user details if present.
|
|
370
424
|
const url = new URL(conn);
|
|
371
425
|
const user = url.username || undefined;
|
|
372
426
|
const host = url.hostname || undefined;
|
|
@@ -383,7 +437,6 @@ function parseDatabaseConnectionStringRaw(conn) {
|
|
|
383
437
|
};
|
|
384
438
|
}
|
|
385
439
|
catch {
|
|
386
|
-
// Not a URL-style string; try semi-structured parsing
|
|
387
440
|
const parts = conn.split(';');
|
|
388
441
|
const kv = {};
|
|
389
442
|
for (const part of parts) {
|
|
@@ -395,7 +448,6 @@ function parseDatabaseConnectionStringRaw(conn) {
|
|
|
395
448
|
const host = kv['server'] || kv['data source'] || kv['address'];
|
|
396
449
|
const port = kv['port'];
|
|
397
450
|
const name = kv['database'] || kv['initial catalog'];
|
|
398
|
-
// Reconstruct a scrubbed connection string without credentials
|
|
399
451
|
const scrubbed = Object.entries(kv)
|
|
400
452
|
.filter(([key]) => !['user id', 'uid', 'password', 'pwd'].includes(key))
|
|
401
453
|
.map(([k, v]) => `${k}=${v}`)
|
|
@@ -409,11 +461,10 @@ function parseDatabaseConnectionStringRaw(conn) {
|
|
|
409
461
|
};
|
|
410
462
|
}
|
|
411
463
|
}
|
|
412
|
-
/** Try pretty hard to get error info out of a span, if it has any. Handles it being there as an
|
|
413
|
-
* directly on the span with error semantics, and some other more ad-hoc cases. */
|
|
464
|
+
/** Try pretty hard to get error info out of a span, if it has any. Handles it being there as an
|
|
465
|
+
* event, directly on the span with error semantics, and some other more ad-hoc cases. */
|
|
414
466
|
function extractErrorInfo(span) {
|
|
415
467
|
const attrs = span.attributes;
|
|
416
|
-
// Try to extract from a structured 'exception' event, as it should have more detail
|
|
417
468
|
const exceptionEvent = span.events.find(e => e.name === 'exception');
|
|
418
469
|
if (exceptionEvent?.attributes) {
|
|
419
470
|
const message = exceptionEvent.attributes['exception.message'];
|
|
@@ -423,7 +474,6 @@ function extractErrorInfo(span) {
|
|
|
423
474
|
return { message, type, stack };
|
|
424
475
|
}
|
|
425
476
|
}
|
|
426
|
-
// Fallback to attributes directly on the span.
|
|
427
477
|
const isError = span.status.code === 2;
|
|
428
478
|
const message = attrs['exception.message'] ??
|
|
429
479
|
attrs['http.error_message'] ??
|
|
@@ -440,10 +490,27 @@ function extractErrorInfo(span) {
|
|
|
440
490
|
stack
|
|
441
491
|
};
|
|
442
492
|
}
|
|
443
|
-
function
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
493
|
+
function matchSpanRule(span, rule) {
|
|
494
|
+
switch (rule.kind) {
|
|
495
|
+
case 'type': {
|
|
496
|
+
const kindMap = {
|
|
497
|
+
'client': api_1.SpanKind.CLIENT,
|
|
498
|
+
'server': api_1.SpanKind.SERVER,
|
|
499
|
+
'internal': api_1.SpanKind.INTERNAL,
|
|
500
|
+
};
|
|
501
|
+
return span.kind === kindMap[rule.value];
|
|
502
|
+
}
|
|
503
|
+
case 'attribute-present':
|
|
504
|
+
return span.attributes[rule.key] !== undefined;
|
|
505
|
+
case 'attribute-absent':
|
|
506
|
+
return span.attributes[rule.key] === undefined;
|
|
507
|
+
case 'attribute-equals':
|
|
508
|
+
return String(span.attributes[rule.key]) === rule.value;
|
|
509
|
+
case 'attribute-not-equals':
|
|
510
|
+
return String(span.attributes[rule.key]) !== rule.value;
|
|
511
|
+
case 'all':
|
|
512
|
+
return rule.rules.every(r => matchSpanRule(span, r));
|
|
513
|
+
case 'any':
|
|
514
|
+
return rule.rules.some(r => matchSpanRule(span, r));
|
|
515
|
+
}
|
|
449
516
|
}
|