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