@comprehend/telemetry-node 0.1.3 → 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 -1
- package/.idea/telemetry-node.iml +0 -1
- package/DEVELOPMENT.md +69 -0
- package/README.md +173 -0
- package/dist/ComprehendDevSpanProcessor.d.ts +9 -6
- package/dist/ComprehendDevSpanProcessor.js +146 -87
- package/dist/ComprehendDevSpanProcessor.test.d.ts +1 -0
- package/dist/ComprehendDevSpanProcessor.test.js +495 -0
- 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.d.ts +1 -0
- package/dist/WebSocketConnection.test.js +473 -0
- 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/jest.config.js +1 -0
- package/package.json +4 -2
- package/src/ComprehendDevSpanProcessor.test.ts +626 -0
- package/src/ComprehendDevSpanProcessor.ts +170 -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 +616 -0
- 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,17 +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
|
+
}
|
|
84
|
+
return newService;
|
|
75
85
|
}
|
|
76
86
|
processHTTPRoute(service, route, method, span) {
|
|
77
|
-
// Check if this route+method already exists under the service
|
|
78
87
|
let observedRoute = service.httpRoutes.find(r => r.route === route && r.method === method);
|
|
79
88
|
if (!observedRoute) {
|
|
80
|
-
// It's new; hash it and add it to our collection.
|
|
81
89
|
const idString = `http-route:${service.hash}:${method}:${route}`;
|
|
82
90
|
const hash = hashIdString(idString);
|
|
83
91
|
observedRoute = { method, route, hash };
|
|
84
92
|
service.httpRoutes.push(observedRoute);
|
|
85
|
-
// Emit observation message
|
|
86
93
|
const message = {
|
|
87
94
|
event: "new-entity",
|
|
88
95
|
type: "http-route",
|
|
@@ -92,14 +99,13 @@ class ComprehendDevSpanProcessor {
|
|
|
92
99
|
};
|
|
93
100
|
this.ingestMessage(message);
|
|
94
101
|
}
|
|
95
|
-
// Extract the request path, making sure we
|
|
102
|
+
// Extract the request path, making sure we strip any query string.
|
|
96
103
|
const attrs = span.attributes;
|
|
97
104
|
let path;
|
|
98
105
|
if (attrs['http.target']) {
|
|
99
106
|
try {
|
|
100
|
-
// This might be just a path like "/search?q=foo"
|
|
101
107
|
const rawTarget = attrs['http.target'];
|
|
102
|
-
const fakeUrl = new URL(rawTarget, 'http://placeholder');
|
|
108
|
+
const fakeUrl = new URL(rawTarget, 'http://placeholder');
|
|
103
109
|
path = fakeUrl.pathname;
|
|
104
110
|
}
|
|
105
111
|
catch {
|
|
@@ -119,7 +125,6 @@ class ComprehendDevSpanProcessor {
|
|
|
119
125
|
else {
|
|
120
126
|
path = '/';
|
|
121
127
|
}
|
|
122
|
-
// Build and ingest observation.
|
|
123
128
|
const status = attrs['http.status_code'] ?? 0;
|
|
124
129
|
const duration = span.duration;
|
|
125
130
|
const httpVersion = attrs['http.flavor'];
|
|
@@ -127,9 +132,12 @@ class ComprehendDevSpanProcessor {
|
|
|
127
132
|
const requestBytes = attrs['http.request_content_length'];
|
|
128
133
|
const responseBytes = attrs['http.response_content_length'];
|
|
129
134
|
const { message: errorMessage, type: errorType, stack: stack } = extractErrorInfo(span);
|
|
135
|
+
const { traceId, spanId } = getSpanContext(span);
|
|
130
136
|
const observation = {
|
|
131
137
|
type: 'http-server',
|
|
132
138
|
subject: observedRoute.hash,
|
|
139
|
+
spanId,
|
|
140
|
+
traceId,
|
|
133
141
|
timestamp: span.startTime,
|
|
134
142
|
path,
|
|
135
143
|
status,
|
|
@@ -144,12 +152,11 @@ class ComprehendDevSpanProcessor {
|
|
|
144
152
|
};
|
|
145
153
|
this.ingestMessage({
|
|
146
154
|
event: "observations",
|
|
147
|
-
seq: this.
|
|
155
|
+
seq: this.connection.nextSeq(),
|
|
148
156
|
observations: [observation]
|
|
149
157
|
});
|
|
150
158
|
}
|
|
151
159
|
processDatabaseOperation(currentService, span) {
|
|
152
|
-
// Parse the connection string.
|
|
153
160
|
const attrs = span.attributes;
|
|
154
161
|
const system = attrs['db.system'];
|
|
155
162
|
const rawConnection = attrs['db.connection_string'];
|
|
@@ -162,10 +169,8 @@ class ComprehendDevSpanProcessor {
|
|
|
162
169
|
port: attrs['net.peer.port']?.toString(),
|
|
163
170
|
name: attrs['db.name'],
|
|
164
171
|
};
|
|
165
|
-
// See if we already have an entry for this database.
|
|
166
172
|
const hash = hashIdString(`database:${system}:${parsed.host ?? ''}:${parsed.port ?? ''}:${parsed.name ?? ''}`);
|
|
167
173
|
let observedDatabase = this.observedDatabases.find(db => db.hash === hash);
|
|
168
|
-
// If we see it for the first time, add and ingest it.
|
|
169
174
|
if (!observedDatabase) {
|
|
170
175
|
observedDatabase = {
|
|
171
176
|
system,
|
|
@@ -175,7 +180,6 @@ class ComprehendDevSpanProcessor {
|
|
|
175
180
|
hash
|
|
176
181
|
};
|
|
177
182
|
this.observedDatabases.push(observedDatabase);
|
|
178
|
-
// The existence of the database.
|
|
179
183
|
const message = {
|
|
180
184
|
event: "new-entity",
|
|
181
185
|
type: "database",
|
|
@@ -189,7 +193,6 @@ class ComprehendDevSpanProcessor {
|
|
|
189
193
|
};
|
|
190
194
|
this.ingestMessage(message);
|
|
191
195
|
}
|
|
192
|
-
// The connection to the database should have an interaction.
|
|
193
196
|
const interactions = this.getInteractions(currentService.hash, observedDatabase.hash);
|
|
194
197
|
let connectionInteraction = interactions.dbConnections.find(c => c.connection === parsed.scrubbed && c.user === parsed.user);
|
|
195
198
|
if (!connectionInteraction) {
|
|
@@ -210,71 +213,42 @@ class ComprehendDevSpanProcessor {
|
|
|
210
213
|
};
|
|
211
214
|
this.ingestMessage(message);
|
|
212
215
|
}
|
|
213
|
-
//
|
|
216
|
+
// For SQL databases, send raw query to server for analysis
|
|
214
217
|
if (sqlDbSystems.has(system) && attrs['db.statement']) {
|
|
215
|
-
// The interaction, based upon the normalized query.
|
|
216
|
-
let queryInfo = (0, sql_analyzer_1.analyzeSQL)(attrs['db.statement']);
|
|
217
|
-
let queryInteraction = interactions.dbQueries.find(q => q.query === queryInfo.normalizedQuery);
|
|
218
|
-
if (!queryInteraction) {
|
|
219
|
-
queryInteraction = {
|
|
220
|
-
hash: hashIdString(`db-query:${currentService.hash}:${observedDatabase.hash}:${queryInfo.normalizedQuery}`),
|
|
221
|
-
query: queryInfo.normalizedQuery
|
|
222
|
-
};
|
|
223
|
-
interactions.dbQueries.push(queryInteraction);
|
|
224
|
-
const selects = getTablesWithOperation(queryInfo.tableOperations, 'SELECT');
|
|
225
|
-
const inserts = getTablesWithOperation(queryInfo.tableOperations, 'INSERT');
|
|
226
|
-
const updates = getTablesWithOperation(queryInfo.tableOperations, 'UPDATE');
|
|
227
|
-
const deletes = getTablesWithOperation(queryInfo.tableOperations, 'DELETE');
|
|
228
|
-
const message = {
|
|
229
|
-
event: "new-interaction",
|
|
230
|
-
type: "db-query",
|
|
231
|
-
hash: queryInteraction.hash,
|
|
232
|
-
from: currentService.hash,
|
|
233
|
-
to: observedDatabase.hash,
|
|
234
|
-
query: queryInfo.presentableQuery,
|
|
235
|
-
...(selects ? { selects } : {}),
|
|
236
|
-
...(inserts ? { inserts } : {}),
|
|
237
|
-
...(updates ? { updates } : {}),
|
|
238
|
-
...(deletes ? { deletes } : {}),
|
|
239
|
-
};
|
|
240
|
-
this.ingestMessage(message);
|
|
241
|
-
}
|
|
242
|
-
// Build and ingest observation.
|
|
243
218
|
const duration = span.duration;
|
|
244
219
|
const returnedRows = attrs['db.response.returned_rows']
|
|
245
220
|
?? attrs['db.sql.rows'];
|
|
246
221
|
const { message: errorMessage, type: errorType, stack } = extractErrorInfo(span);
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
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,
|
|
250
229
|
timestamp: span.startTime,
|
|
251
230
|
duration,
|
|
231
|
+
traceId,
|
|
232
|
+
spanId,
|
|
252
233
|
...(errorMessage ? { errorMessage } : {}),
|
|
253
234
|
...(errorType ? { errorType } : {}),
|
|
254
235
|
...(stack ? { stack } : {}),
|
|
255
236
|
...(returnedRows !== undefined ? { returnedRows } : {})
|
|
256
237
|
};
|
|
257
|
-
this.ingestMessage(
|
|
258
|
-
event: "observations",
|
|
259
|
-
seq: this.observationsSeq++,
|
|
260
|
-
observations: [observation]
|
|
261
|
-
});
|
|
238
|
+
this.ingestMessage(dbQueryMessage);
|
|
262
239
|
}
|
|
263
240
|
}
|
|
264
241
|
processHttpRequest(currentService, url, span) {
|
|
265
|
-
// Build identity based upon protocol, host, and port.
|
|
266
242
|
const parsed = new URL(url);
|
|
267
|
-
const protocol = parsed.protocol.replace(':', '');
|
|
243
|
+
const protocol = parsed.protocol.replace(':', '');
|
|
268
244
|
const host = parsed.hostname;
|
|
269
245
|
const port = parsed.port ? parseInt(parsed.port) : (protocol === 'https' ? 443 : 80);
|
|
270
246
|
const idString = `http-service:${protocol}:${host}:${port}`;
|
|
271
247
|
const hash = hashIdString(idString);
|
|
272
|
-
// Ingest it if it's not already observed.
|
|
273
248
|
let observedHttpService = this.observedHttpServices.find(s => s.protocol === protocol && s.host === host && s.port === port);
|
|
274
249
|
if (!observedHttpService) {
|
|
275
250
|
observedHttpService = { protocol, host, port, hash };
|
|
276
251
|
this.observedHttpServices.push(observedHttpService);
|
|
277
|
-
// The existence of the service.
|
|
278
252
|
const message = {
|
|
279
253
|
event: "new-entity",
|
|
280
254
|
type: "http-service",
|
|
@@ -285,7 +259,6 @@ class ComprehendDevSpanProcessor {
|
|
|
285
259
|
};
|
|
286
260
|
this.ingestMessage(message);
|
|
287
261
|
}
|
|
288
|
-
// Ingest the interaction if first observed.
|
|
289
262
|
const interactions = this.getInteractions(currentService.hash, observedHttpService.hash);
|
|
290
263
|
if (!interactions.httpRequest) {
|
|
291
264
|
const idString = `http-request:${currentService.hash}:${observedHttpService.hash}`;
|
|
@@ -300,11 +273,10 @@ class ComprehendDevSpanProcessor {
|
|
|
300
273
|
};
|
|
301
274
|
this.ingestMessage(message);
|
|
302
275
|
}
|
|
303
|
-
// Build and ingest observation.
|
|
304
276
|
const attrs = span.attributes;
|
|
305
277
|
const path = parsed.pathname || '/';
|
|
306
278
|
const method = span.attributes['http.method'];
|
|
307
|
-
if (!method)
|
|
279
|
+
if (!method)
|
|
308
280
|
return;
|
|
309
281
|
const status = attrs['http.status_code'];
|
|
310
282
|
const duration = span.duration;
|
|
@@ -312,9 +284,12 @@ class ComprehendDevSpanProcessor {
|
|
|
312
284
|
const requestBytes = attrs['http.request_content_length'];
|
|
313
285
|
const responseBytes = attrs['http.response_content_length'];
|
|
314
286
|
const { message: errorMessage, type: errorType, stack } = extractErrorInfo(span);
|
|
287
|
+
const { traceId, spanId } = getSpanContext(span);
|
|
315
288
|
const observation = {
|
|
316
289
|
type: "http-client",
|
|
317
290
|
subject: interactions.httpRequest.hash,
|
|
291
|
+
spanId,
|
|
292
|
+
traceId,
|
|
318
293
|
timestamp: span.startTime,
|
|
319
294
|
path,
|
|
320
295
|
method,
|
|
@@ -329,10 +304,57 @@ class ComprehendDevSpanProcessor {
|
|
|
329
304
|
};
|
|
330
305
|
this.ingestMessage({
|
|
331
306
|
event: "observations",
|
|
332
|
-
seq: this.
|
|
307
|
+
seq: this.connection.nextSeq(),
|
|
333
308
|
observations: [observation]
|
|
334
309
|
});
|
|
335
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
|
+
}
|
|
336
358
|
getInteractions(from, to) {
|
|
337
359
|
let fromMap = this.observedInteractions.get(from);
|
|
338
360
|
if (!fromMap) {
|
|
@@ -341,7 +363,7 @@ class ComprehendDevSpanProcessor {
|
|
|
341
363
|
}
|
|
342
364
|
let interactions = fromMap.get(to);
|
|
343
365
|
if (!interactions) {
|
|
344
|
-
interactions = { httpRequest: undefined, dbConnections: []
|
|
366
|
+
interactions = { httpRequest: undefined, dbConnections: [] };
|
|
345
367
|
fromMap.set(to, interactions);
|
|
346
368
|
}
|
|
347
369
|
return interactions;
|
|
@@ -352,7 +374,6 @@ class ComprehendDevSpanProcessor {
|
|
|
352
374
|
async forceFlush() {
|
|
353
375
|
}
|
|
354
376
|
async shutdown() {
|
|
355
|
-
this.connection.close();
|
|
356
377
|
}
|
|
357
378
|
}
|
|
358
379
|
exports.ComprehendDevSpanProcessor = ComprehendDevSpanProcessor;
|
|
@@ -361,11 +382,36 @@ function hashIdString(idString) {
|
|
|
361
382
|
.map(b => b.toString(16).padStart(2, '0'))
|
|
362
383
|
.join('');
|
|
363
384
|
}
|
|
364
|
-
|
|
365
|
-
|
|
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). */
|
|
366
413
|
function parseDatabaseConnectionStringRaw(conn) {
|
|
367
414
|
try {
|
|
368
|
-
// Try URL-style parsing first; scrub the user details if present.
|
|
369
415
|
const url = new URL(conn);
|
|
370
416
|
const user = url.username || undefined;
|
|
371
417
|
const host = url.hostname || undefined;
|
|
@@ -382,7 +428,6 @@ function parseDatabaseConnectionStringRaw(conn) {
|
|
|
382
428
|
};
|
|
383
429
|
}
|
|
384
430
|
catch {
|
|
385
|
-
// Not a URL-style string; try semi-structured parsing
|
|
386
431
|
const parts = conn.split(';');
|
|
387
432
|
const kv = {};
|
|
388
433
|
for (const part of parts) {
|
|
@@ -394,7 +439,6 @@ function parseDatabaseConnectionStringRaw(conn) {
|
|
|
394
439
|
const host = kv['server'] || kv['data source'] || kv['address'];
|
|
395
440
|
const port = kv['port'];
|
|
396
441
|
const name = kv['database'] || kv['initial catalog'];
|
|
397
|
-
// Reconstruct a scrubbed connection string without credentials
|
|
398
442
|
const scrubbed = Object.entries(kv)
|
|
399
443
|
.filter(([key]) => !['user id', 'uid', 'password', 'pwd'].includes(key))
|
|
400
444
|
.map(([k, v]) => `${k}=${v}`)
|
|
@@ -408,11 +452,10 @@ function parseDatabaseConnectionStringRaw(conn) {
|
|
|
408
452
|
};
|
|
409
453
|
}
|
|
410
454
|
}
|
|
411
|
-
/** Try pretty hard to get error info out of a span, if it has any. Handles it being there as an
|
|
412
|
-
* 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. */
|
|
413
457
|
function extractErrorInfo(span) {
|
|
414
458
|
const attrs = span.attributes;
|
|
415
|
-
// Try to extract from a structured 'exception' event, as it should have more detail
|
|
416
459
|
const exceptionEvent = span.events.find(e => e.name === 'exception');
|
|
417
460
|
if (exceptionEvent?.attributes) {
|
|
418
461
|
const message = exceptionEvent.attributes['exception.message'];
|
|
@@ -422,7 +465,6 @@ function extractErrorInfo(span) {
|
|
|
422
465
|
return { message, type, stack };
|
|
423
466
|
}
|
|
424
467
|
}
|
|
425
|
-
// Fallback to attributes directly on the span.
|
|
426
468
|
const isError = span.status.code === 2;
|
|
427
469
|
const message = attrs['exception.message'] ??
|
|
428
470
|
attrs['http.error_message'] ??
|
|
@@ -439,10 +481,27 @@ function extractErrorInfo(span) {
|
|
|
439
481
|
stack
|
|
440
482
|
};
|
|
441
483
|
}
|
|
442
|
-
function
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
+
}
|
|
448
507
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|