@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
|
@@ -5,43 +5,31 @@ const ComprehendDevSpanProcessor_1 = require("./ComprehendDevSpanProcessor");
|
|
|
5
5
|
const WebSocketConnection_1 = require("./WebSocketConnection");
|
|
6
6
|
// Mock the WebSocketConnection
|
|
7
7
|
jest.mock('./WebSocketConnection');
|
|
8
|
-
// Mock the SQL analyzer
|
|
9
|
-
jest.mock('./sql-analyzer', () => ({
|
|
10
|
-
analyzeSQL: jest.fn()
|
|
11
|
-
}));
|
|
12
|
-
const sql_analyzer_1 = require("./sql-analyzer");
|
|
13
|
-
const mockedAnalyzeSQL = sql_analyzer_1.analyzeSQL;
|
|
14
8
|
const MockedWebSocketConnection = WebSocketConnection_1.WebSocketConnection;
|
|
15
9
|
describe('ComprehendDevSpanProcessor', () => {
|
|
16
10
|
let processor;
|
|
17
11
|
let mockConnection;
|
|
18
12
|
let sentMessages;
|
|
13
|
+
let seqCounter;
|
|
19
14
|
beforeEach(() => {
|
|
20
|
-
// Reset mocks
|
|
21
15
|
MockedWebSocketConnection.mockClear();
|
|
22
|
-
mockedAnalyzeSQL.mockClear();
|
|
23
16
|
sentMessages = [];
|
|
24
|
-
|
|
17
|
+
seqCounter = 1;
|
|
25
18
|
mockConnection = {
|
|
26
19
|
sendMessage: jest.fn((message) => {
|
|
27
20
|
sentMessages.push(message);
|
|
28
21
|
}),
|
|
22
|
+
setProcessContext: jest.fn(),
|
|
23
|
+
nextSeq: jest.fn(() => seqCounter++),
|
|
29
24
|
close: jest.fn()
|
|
30
25
|
};
|
|
31
|
-
|
|
32
|
-
// Create processor instance
|
|
33
|
-
processor = new ComprehendDevSpanProcessor_1.ComprehendDevSpanProcessor({
|
|
34
|
-
organization: 'test-org',
|
|
35
|
-
token: 'test-token'
|
|
36
|
-
});
|
|
26
|
+
processor = new ComprehendDevSpanProcessor_1.ComprehendDevSpanProcessor(mockConnection);
|
|
37
27
|
});
|
|
38
28
|
afterEach(() => {
|
|
39
29
|
processor.shutdown();
|
|
40
30
|
});
|
|
41
|
-
// Helper function to create a mock span
|
|
42
31
|
function createMockSpan(options = {}) {
|
|
43
|
-
const { name = 'test-span', kind = api_1.SpanKind.INTERNAL, attributes = {}, resourceAttributes = { 'service.name': 'test-service' }, status = { code: api_1.SpanStatusCode.OK }, events = [], startTime = [1700000000, 123456789], duration = [0, 100000000]
|
|
44
|
-
} = options;
|
|
32
|
+
const { name = 'test-span', kind = api_1.SpanKind.INTERNAL, attributes = {}, resourceAttributes = { 'service.name': 'test-service' }, status = { code: api_1.SpanStatusCode.OK }, events = [], startTime = [1700000000, 123456789], duration = [0, 100000000], traceId = 'abc123trace', spanId = 'def456span', parentSpanId, } = options;
|
|
45
33
|
return {
|
|
46
34
|
name,
|
|
47
35
|
kind,
|
|
@@ -56,7 +44,9 @@ describe('ComprehendDevSpanProcessor', () => {
|
|
|
56
44
|
time: startTime
|
|
57
45
|
})),
|
|
58
46
|
startTime,
|
|
59
|
-
duration
|
|
47
|
+
duration,
|
|
48
|
+
spanContext: () => ({ traceId, spanId, traceFlags: 1 }),
|
|
49
|
+
parentSpanContext: parentSpanId ? { spanId: parentSpanId, traceId: '', traceFlags: 0 } : undefined,
|
|
60
50
|
};
|
|
61
51
|
}
|
|
62
52
|
describe('Service Discovery', () => {
|
|
@@ -69,52 +59,61 @@ describe('ComprehendDevSpanProcessor', () => {
|
|
|
69
59
|
}
|
|
70
60
|
});
|
|
71
61
|
processor.onEnd(span);
|
|
72
|
-
|
|
73
|
-
expect(
|
|
62
|
+
const serviceMsg = sentMessages.find(m => m.event === 'new-entity' && m.type === 'service');
|
|
63
|
+
expect(serviceMsg).toMatchObject({
|
|
74
64
|
event: 'new-entity',
|
|
75
65
|
type: 'service',
|
|
76
66
|
name: 'my-api',
|
|
77
67
|
namespace: 'production',
|
|
78
68
|
environment: 'prod'
|
|
79
69
|
});
|
|
80
|
-
expect(
|
|
70
|
+
expect(serviceMsg.hash).toBeDefined();
|
|
81
71
|
});
|
|
82
72
|
it('should not duplicate service registrations', () => {
|
|
83
|
-
const span1 = createMockSpan({
|
|
84
|
-
|
|
85
|
-
});
|
|
86
|
-
const span2 = createMockSpan({
|
|
87
|
-
resourceAttributes: { 'service.name': 'my-api' }
|
|
88
|
-
});
|
|
73
|
+
const span1 = createMockSpan({ resourceAttributes: { 'service.name': 'my-api' } });
|
|
74
|
+
const span2 = createMockSpan({ resourceAttributes: { 'service.name': 'my-api' } });
|
|
89
75
|
processor.onEnd(span1);
|
|
90
76
|
processor.onEnd(span2);
|
|
91
|
-
// Should only send one service registration
|
|
92
77
|
const serviceMessages = sentMessages.filter(m => m.event === 'new-entity' && m.type === 'service');
|
|
93
78
|
expect(serviceMessages).toHaveLength(1);
|
|
94
79
|
});
|
|
95
80
|
it('should handle service without namespace and environment', () => {
|
|
96
|
-
const span = createMockSpan({
|
|
97
|
-
resourceAttributes: { 'service.name': 'simple-service' }
|
|
98
|
-
});
|
|
81
|
+
const span = createMockSpan({ resourceAttributes: { 'service.name': 'simple-service' } });
|
|
99
82
|
processor.onEnd(span);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
});
|
|
105
|
-
expect(sentMessages[0]).not.toHaveProperty('namespace');
|
|
106
|
-
expect(sentMessages[0]).not.toHaveProperty('environment');
|
|
83
|
+
const serviceMsg = sentMessages.find(m => m.type === 'service');
|
|
84
|
+
expect(serviceMsg).toMatchObject({ name: 'simple-service' });
|
|
85
|
+
expect(serviceMsg).not.toHaveProperty('namespace');
|
|
86
|
+
expect(serviceMsg).not.toHaveProperty('environment');
|
|
107
87
|
});
|
|
108
88
|
it('should ignore spans without service.name', () => {
|
|
89
|
+
const span = createMockSpan({ resourceAttributes: {} });
|
|
90
|
+
processor.onEnd(span);
|
|
91
|
+
expect(sentMessages).toHaveLength(0);
|
|
92
|
+
});
|
|
93
|
+
it('should set process context on first service discovery', () => {
|
|
109
94
|
const span = createMockSpan({
|
|
110
|
-
resourceAttributes: {
|
|
95
|
+
resourceAttributes: {
|
|
96
|
+
'service.name': 'my-api',
|
|
97
|
+
'deployment.environment': 'prod',
|
|
98
|
+
}
|
|
111
99
|
});
|
|
112
100
|
processor.onEnd(span);
|
|
113
|
-
expect(
|
|
101
|
+
expect(mockConnection.setProcessContext).toHaveBeenCalledTimes(1);
|
|
102
|
+
const [hash, resources] = mockConnection.setProcessContext.mock.calls[0];
|
|
103
|
+
expect(hash).toBeDefined();
|
|
104
|
+
expect(resources['service.name']).toBe('my-api');
|
|
105
|
+
expect(resources['deployment.environment']).toBe('prod');
|
|
106
|
+
});
|
|
107
|
+
it('should only set process context once', () => {
|
|
108
|
+
const span1 = createMockSpan({ resourceAttributes: { 'service.name': 'svc1' } });
|
|
109
|
+
const span2 = createMockSpan({ resourceAttributes: { 'service.name': 'svc2' } });
|
|
110
|
+
processor.onEnd(span1);
|
|
111
|
+
processor.onEnd(span2);
|
|
112
|
+
expect(mockConnection.setProcessContext).toHaveBeenCalledTimes(1);
|
|
114
113
|
});
|
|
115
114
|
});
|
|
116
115
|
describe('HTTP Route Processing', () => {
|
|
117
|
-
it('should process HTTP server spans
|
|
116
|
+
it('should process HTTP server spans with V2 observations (spanId, traceId)', () => {
|
|
118
117
|
const span = createMockSpan({
|
|
119
118
|
kind: api_1.SpanKind.SERVER,
|
|
120
119
|
attributes: {
|
|
@@ -122,98 +121,29 @@ describe('ComprehendDevSpanProcessor', () => {
|
|
|
122
121
|
'http.method': 'GET',
|
|
123
122
|
'http.target': '/api/users/123',
|
|
124
123
|
'http.status_code': 200
|
|
125
|
-
}
|
|
124
|
+
},
|
|
125
|
+
traceId: 'trace-abc',
|
|
126
|
+
spanId: 'span-123',
|
|
126
127
|
});
|
|
127
128
|
processor.onEnd(span);
|
|
128
|
-
// Should have service + route messages
|
|
129
|
-
expect(sentMessages).toHaveLength(3); // service, route, observation
|
|
130
|
-
const routeMessage = sentMessages.find(m => m.type === 'http-route');
|
|
131
|
-
expect(routeMessage).toMatchObject({
|
|
132
|
-
event: 'new-entity',
|
|
133
|
-
type: 'http-route',
|
|
134
|
-
method: 'GET',
|
|
135
|
-
route: '/api/users/{id}'
|
|
136
|
-
});
|
|
137
129
|
const observationMessage = sentMessages.find(m => m.event === 'observations');
|
|
138
|
-
expect(observationMessage.observations).toHaveLength(1);
|
|
139
130
|
const observation = observationMessage.observations[0];
|
|
140
131
|
expect(observation).toMatchObject({
|
|
141
132
|
type: 'http-server',
|
|
142
133
|
path: '/api/users/123',
|
|
143
|
-
status: 200
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
it('should extract path from http.url when http.target is not available', () => {
|
|
147
|
-
const span = createMockSpan({
|
|
148
|
-
kind: api_1.SpanKind.SERVER,
|
|
149
|
-
attributes: {
|
|
150
|
-
'http.route': '/api/test',
|
|
151
|
-
'http.method': 'POST',
|
|
152
|
-
'http.url': 'https://example.com/api/test?param=value',
|
|
153
|
-
'http.status_code': 201
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
processor.onEnd(span);
|
|
157
|
-
const observationMessage = sentMessages.find(m => m.event === 'observations');
|
|
158
|
-
const observation = observationMessage.observations[0];
|
|
159
|
-
expect(observation.path).toBe('/api/test');
|
|
160
|
-
});
|
|
161
|
-
it('should handle relative URLs in http.target', () => {
|
|
162
|
-
const span = createMockSpan({
|
|
163
|
-
kind: api_1.SpanKind.SERVER,
|
|
164
|
-
attributes: {
|
|
165
|
-
'http.route': '/api/test',
|
|
166
|
-
'http.method': 'GET',
|
|
167
|
-
'http.target': 'invalid-url',
|
|
168
|
-
'http.status_code': 400
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
processor.onEnd(span);
|
|
172
|
-
const observationMessage = sentMessages.find(m => m.event === 'observations');
|
|
173
|
-
const observation = observationMessage.observations[0];
|
|
174
|
-
// When used with placeholder base, 'invalid-url' becomes '/invalid-url'
|
|
175
|
-
expect(observation.path).toBe('/invalid-url');
|
|
176
|
-
});
|
|
177
|
-
it('should include optional HTTP attributes when present', () => {
|
|
178
|
-
const span = createMockSpan({
|
|
179
|
-
kind: api_1.SpanKind.SERVER,
|
|
180
|
-
attributes: {
|
|
181
|
-
'http.route': '/api/upload',
|
|
182
|
-
'http.method': 'POST',
|
|
183
|
-
'http.target': '/api/upload',
|
|
184
|
-
'http.status_code': 200,
|
|
185
|
-
'http.flavor': '1.1',
|
|
186
|
-
'http.user_agent': 'Mozilla/5.0',
|
|
187
|
-
'http.request_content_length': 1024,
|
|
188
|
-
'http.response_content_length': 256
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
processor.onEnd(span);
|
|
192
|
-
const observationMessage = sentMessages.find(m => m.event === 'observations');
|
|
193
|
-
const observation = observationMessage.observations[0];
|
|
194
|
-
expect(observation).toMatchObject({
|
|
195
|
-
httpVersion: '1.1',
|
|
196
|
-
userAgent: 'Mozilla/5.0',
|
|
197
|
-
requestBytes: 1024,
|
|
198
|
-
responseBytes: 256
|
|
134
|
+
status: 200,
|
|
135
|
+
spanId: 'span-123',
|
|
136
|
+
traceId: 'trace-abc',
|
|
199
137
|
});
|
|
200
138
|
});
|
|
201
139
|
it('should not duplicate route registrations', () => {
|
|
202
140
|
const span1 = createMockSpan({
|
|
203
141
|
kind: api_1.SpanKind.SERVER,
|
|
204
|
-
attributes: {
|
|
205
|
-
'http.route': '/api/users',
|
|
206
|
-
'http.method': 'GET',
|
|
207
|
-
'http.status_code': 200
|
|
208
|
-
}
|
|
142
|
+
attributes: { 'http.route': '/api/users', 'http.method': 'GET', 'http.status_code': 200 }
|
|
209
143
|
});
|
|
210
144
|
const span2 = createMockSpan({
|
|
211
145
|
kind: api_1.SpanKind.SERVER,
|
|
212
|
-
attributes: {
|
|
213
|
-
'http.route': '/api/users',
|
|
214
|
-
'http.method': 'GET',
|
|
215
|
-
'http.status_code': 200
|
|
216
|
-
}
|
|
146
|
+
attributes: { 'http.route': '/api/users', 'http.method': 'GET', 'http.status_code': 200 }
|
|
217
147
|
});
|
|
218
148
|
processor.onEnd(span1);
|
|
219
149
|
processor.onEnd(span2);
|
|
@@ -222,96 +152,53 @@ describe('ComprehendDevSpanProcessor', () => {
|
|
|
222
152
|
});
|
|
223
153
|
});
|
|
224
154
|
describe('HTTP Client Processing', () => {
|
|
225
|
-
it('should process HTTP client spans
|
|
155
|
+
it('should process HTTP client spans with V2 observations', () => {
|
|
226
156
|
const span = createMockSpan({
|
|
227
157
|
kind: api_1.SpanKind.CLIENT,
|
|
228
158
|
attributes: {
|
|
229
159
|
'http.url': 'https://api.external.com/v1/data',
|
|
230
160
|
'http.method': 'GET',
|
|
231
161
|
'http.status_code': 200
|
|
232
|
-
}
|
|
162
|
+
},
|
|
163
|
+
traceId: 'trace-xyz',
|
|
164
|
+
spanId: 'span-456',
|
|
233
165
|
});
|
|
234
166
|
processor.onEnd(span);
|
|
235
|
-
// Should have service + http-service + http-request + observation
|
|
236
|
-
expect(sentMessages).toHaveLength(4);
|
|
237
|
-
const httpServiceMessage = sentMessages.find(m => m.type === 'http-service');
|
|
238
|
-
expect(httpServiceMessage).toMatchObject({
|
|
239
|
-
event: 'new-entity',
|
|
240
|
-
type: 'http-service',
|
|
241
|
-
protocol: 'https',
|
|
242
|
-
host: 'api.external.com',
|
|
243
|
-
port: 443
|
|
244
|
-
});
|
|
245
|
-
const httpRequestMessage = sentMessages.find(m => m.type === 'http-request');
|
|
246
|
-
expect(httpRequestMessage).toMatchObject({
|
|
247
|
-
event: 'new-interaction',
|
|
248
|
-
type: 'http-request'
|
|
249
|
-
});
|
|
250
167
|
const observationMessage = sentMessages.find(m => m.event === 'observations');
|
|
251
168
|
const observation = observationMessage.observations[0];
|
|
252
169
|
expect(observation).toMatchObject({
|
|
253
170
|
type: 'http-client',
|
|
254
171
|
path: '/v1/data',
|
|
255
172
|
method: 'GET',
|
|
256
|
-
status: 200
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
it('should handle HTTP on port 80 and HTTPS on port 443 correctly', () => {
|
|
260
|
-
const httpSpan = createMockSpan({
|
|
261
|
-
kind: api_1.SpanKind.CLIENT,
|
|
262
|
-
attributes: {
|
|
263
|
-
'http.url': 'http://api.example.com/test',
|
|
264
|
-
'http.method': 'GET'
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
processor.onEnd(httpSpan);
|
|
268
|
-
const httpServiceMessage = sentMessages.find(m => m.type === 'http-service');
|
|
269
|
-
expect(httpServiceMessage).toMatchObject({
|
|
270
|
-
protocol: 'http',
|
|
271
|
-
host: 'api.example.com',
|
|
272
|
-
port: 80
|
|
173
|
+
status: 200,
|
|
174
|
+
spanId: 'span-456',
|
|
175
|
+
traceId: 'trace-xyz',
|
|
273
176
|
});
|
|
274
177
|
});
|
|
275
|
-
|
|
178
|
+
});
|
|
179
|
+
describe('Database Processing', () => {
|
|
180
|
+
it('should send DatabaseQueryMessage for SQL queries (server-side analysis)', () => {
|
|
276
181
|
const span = createMockSpan({
|
|
277
|
-
kind: api_1.SpanKind.CLIENT,
|
|
278
182
|
attributes: {
|
|
279
|
-
'
|
|
280
|
-
'
|
|
183
|
+
'db.system': 'postgresql',
|
|
184
|
+
'db.statement': 'SELECT * FROM users WHERE id = $1',
|
|
185
|
+
'db.name': 'myapp',
|
|
186
|
+
'net.peer.name': 'localhost',
|
|
187
|
+
'db.response.returned_rows': 1
|
|
281
188
|
}
|
|
282
189
|
});
|
|
283
190
|
processor.onEnd(span);
|
|
284
|
-
const
|
|
285
|
-
expect(
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
191
|
+
const dbQueryMsg = sentMessages.find(m => m.event === 'db-query');
|
|
192
|
+
expect(dbQueryMsg).toMatchObject({
|
|
193
|
+
event: 'db-query',
|
|
194
|
+
query: 'SELECT * FROM users WHERE id = $1',
|
|
195
|
+
returnedRows: 1,
|
|
289
196
|
});
|
|
197
|
+
expect(dbQueryMsg.from).toBeDefined();
|
|
198
|
+
expect(dbQueryMsg.to).toBeDefined();
|
|
199
|
+
expect(dbQueryMsg.seq).toBeDefined();
|
|
290
200
|
});
|
|
291
|
-
it('should
|
|
292
|
-
const span = createMockSpan({
|
|
293
|
-
kind: api_1.SpanKind.CLIENT,
|
|
294
|
-
attributes: {
|
|
295
|
-
'http.url': 'https://api.example.com/test'
|
|
296
|
-
// Missing http.method
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
processor.onEnd(span);
|
|
300
|
-
// Should still create service and interaction, but no observation
|
|
301
|
-
const observationMessages = sentMessages.filter(m => m.event === 'observations');
|
|
302
|
-
expect(observationMessages).toHaveLength(0);
|
|
303
|
-
});
|
|
304
|
-
});
|
|
305
|
-
describe('Database Processing', () => {
|
|
306
|
-
beforeEach(() => {
|
|
307
|
-
// Setup SQL analyzer mock
|
|
308
|
-
mockedAnalyzeSQL.mockReturnValue({
|
|
309
|
-
tableOperations: { users: ['SELECT'] },
|
|
310
|
-
normalizedQuery: 'SELECT * FROM users',
|
|
311
|
-
presentableQuery: 'SELECT * FROM users'
|
|
312
|
-
});
|
|
313
|
-
});
|
|
314
|
-
it('should process database spans and create database entities', () => {
|
|
201
|
+
it('should create database entity and connection interaction', () => {
|
|
315
202
|
const span = createMockSpan({
|
|
316
203
|
attributes: {
|
|
317
204
|
'db.system': 'postgresql',
|
|
@@ -330,112 +217,153 @@ describe('ComprehendDevSpanProcessor', () => {
|
|
|
330
217
|
host: 'db.example.com',
|
|
331
218
|
port: 5432
|
|
332
219
|
});
|
|
333
|
-
});
|
|
334
|
-
it('should process database connection strings', () => {
|
|
335
|
-
const span = createMockSpan({
|
|
336
|
-
attributes: {
|
|
337
|
-
'db.system': 'postgresql',
|
|
338
|
-
'db.connection_string': 'postgresql://user:password@localhost:5432/testdb'
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
processor.onEnd(span);
|
|
342
|
-
const databaseMessage = sentMessages.find(m => m.type === 'database');
|
|
343
|
-
expect(databaseMessage).toMatchObject({
|
|
344
|
-
system: 'postgresql',
|
|
345
|
-
host: 'localhost',
|
|
346
|
-
port: 5432,
|
|
347
|
-
name: 'testdb'
|
|
348
|
-
});
|
|
349
220
|
const connectionMessage = sentMessages.find(m => m.type === 'db-connection');
|
|
350
221
|
expect(connectionMessage).toMatchObject({
|
|
351
222
|
event: 'new-interaction',
|
|
352
223
|
type: 'db-connection',
|
|
353
|
-
user: 'user'
|
|
354
224
|
});
|
|
355
225
|
});
|
|
356
|
-
it('should
|
|
226
|
+
it('should not send db-query for non-SQL database systems', () => {
|
|
357
227
|
const span = createMockSpan({
|
|
358
228
|
attributes: {
|
|
359
|
-
'db.system': '
|
|
360
|
-
'db.statement': '
|
|
361
|
-
'db.name': 'myapp'
|
|
362
|
-
'net.peer.name': 'localhost',
|
|
363
|
-
'db.response.returned_rows': 1
|
|
229
|
+
'db.system': 'mongodb',
|
|
230
|
+
'db.statement': 'db.users.find({})',
|
|
231
|
+
'db.name': 'myapp'
|
|
364
232
|
}
|
|
365
233
|
});
|
|
366
234
|
processor.onEnd(span);
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
expect(queryMessage).toMatchObject({
|
|
370
|
-
event: 'new-interaction',
|
|
371
|
-
type: 'db-query',
|
|
372
|
-
query: 'SELECT * FROM users',
|
|
373
|
-
selects: ['users']
|
|
374
|
-
});
|
|
375
|
-
const observationMessage = sentMessages.find(m => m.event === 'observations');
|
|
376
|
-
const observation = observationMessage.observations[0];
|
|
377
|
-
expect(observation).toMatchObject({
|
|
378
|
-
type: 'db-query',
|
|
379
|
-
returnedRows: 1
|
|
380
|
-
});
|
|
235
|
+
const dbQueryMessages = sentMessages.filter(m => m.event === 'db-query');
|
|
236
|
+
expect(dbQueryMessages).toHaveLength(0);
|
|
381
237
|
});
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
users: ['SELECT'],
|
|
386
|
-
orders: ['INSERT'],
|
|
387
|
-
products: ['UPDATE']
|
|
388
|
-
},
|
|
389
|
-
normalizedQuery: 'INSERT INTO orders SELECT * FROM users UPDATE products',
|
|
390
|
-
presentableQuery: 'INSERT INTO orders SELECT * FROM users UPDATE products'
|
|
391
|
-
});
|
|
238
|
+
});
|
|
239
|
+
describe('Trace Span Reporting', () => {
|
|
240
|
+
it('should send TraceSpansMessage for every span', () => {
|
|
392
241
|
const span = createMockSpan({
|
|
242
|
+
name: 'GET /api/users',
|
|
243
|
+
kind: api_1.SpanKind.SERVER,
|
|
393
244
|
attributes: {
|
|
394
|
-
'
|
|
395
|
-
'
|
|
396
|
-
'
|
|
397
|
-
}
|
|
245
|
+
'http.route': '/api/users',
|
|
246
|
+
'http.method': 'GET',
|
|
247
|
+
'http.status_code': 200,
|
|
248
|
+
},
|
|
249
|
+
traceId: 'trace-001',
|
|
250
|
+
spanId: 'span-001',
|
|
251
|
+
parentSpanId: 'span-000',
|
|
398
252
|
});
|
|
399
253
|
processor.onEnd(span);
|
|
400
|
-
const
|
|
401
|
-
expect(
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
254
|
+
const traceSpanMsg = sentMessages.find(m => m.event === 'tracespans');
|
|
255
|
+
expect(traceSpanMsg).toBeDefined();
|
|
256
|
+
expect(traceSpanMsg.data).toHaveLength(1);
|
|
257
|
+
expect(traceSpanMsg.data[0]).toMatchObject({
|
|
258
|
+
trace: 'trace-001',
|
|
259
|
+
span: 'span-001',
|
|
260
|
+
parent: 'span-000',
|
|
261
|
+
name: 'GET /api/users',
|
|
405
262
|
});
|
|
406
263
|
});
|
|
407
|
-
it('should
|
|
264
|
+
it('should use empty string for parent when no parentSpanId', () => {
|
|
408
265
|
const span = createMockSpan({
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
'db.name': 'myapp'
|
|
413
|
-
}
|
|
266
|
+
traceId: 'trace-root',
|
|
267
|
+
spanId: 'span-root',
|
|
268
|
+
// no parentSpanId
|
|
414
269
|
});
|
|
415
270
|
processor.onEnd(span);
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const queryMessages = sentMessages.filter(m => m.type === 'db-query');
|
|
419
|
-
expect(queryMessages).toHaveLength(0);
|
|
271
|
+
const traceSpanMsg = sentMessages.find(m => m.event === 'tracespans');
|
|
272
|
+
expect(traceSpanMsg.data[0].parent).toBe('');
|
|
420
273
|
});
|
|
421
|
-
it('should
|
|
274
|
+
it('should send trace span even for internal spans with no http/db attributes', () => {
|
|
422
275
|
const span = createMockSpan({
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
'db.connection_string': 'Server=localhost;Database=TestDB;User Id=sa;Password=secret;'
|
|
426
|
-
}
|
|
276
|
+
name: 'internal-operation',
|
|
277
|
+
kind: api_1.SpanKind.INTERNAL,
|
|
427
278
|
});
|
|
428
279
|
processor.onEnd(span);
|
|
429
|
-
const
|
|
430
|
-
expect(
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
280
|
+
const traceSpanMsg = sentMessages.find(m => m.event === 'tracespans');
|
|
281
|
+
expect(traceSpanMsg).toBeDefined();
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
describe('Custom Span Observation Matching', () => {
|
|
285
|
+
it('should match spans by type rule', () => {
|
|
286
|
+
processor.updateCustomMetrics([{
|
|
287
|
+
type: 'span',
|
|
288
|
+
rule: { kind: 'type', value: 'server' },
|
|
289
|
+
subject: 'custom-server-obs',
|
|
290
|
+
}]);
|
|
291
|
+
const span = createMockSpan({
|
|
292
|
+
kind: api_1.SpanKind.SERVER,
|
|
293
|
+
attributes: {
|
|
294
|
+
'http.route': '/test',
|
|
295
|
+
'http.method': 'GET',
|
|
296
|
+
'http.status_code': 200,
|
|
297
|
+
},
|
|
434
298
|
});
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
299
|
+
processor.onEnd(span);
|
|
300
|
+
const customObs = sentMessages
|
|
301
|
+
.filter(m => m.event === 'observations')
|
|
302
|
+
.flatMap((m) => m.observations)
|
|
303
|
+
.find((o) => o.type === 'custom');
|
|
304
|
+
expect(customObs).toBeDefined();
|
|
305
|
+
expect(customObs.subject).toBe('custom-server-obs');
|
|
306
|
+
expect(customObs.id).toBe('custom-server-obs');
|
|
307
|
+
expect(customObs.attributes).toBeDefined();
|
|
308
|
+
});
|
|
309
|
+
it('should match spans by attribute-present rule', () => {
|
|
310
|
+
processor.updateCustomMetrics([{
|
|
311
|
+
type: 'span',
|
|
312
|
+
rule: { kind: 'attribute-present', key: 'custom.attr' },
|
|
313
|
+
subject: 'has-custom-attr',
|
|
314
|
+
}]);
|
|
315
|
+
const spanWith = createMockSpan({ attributes: { 'custom.attr': 'value' } });
|
|
316
|
+
const spanWithout = createMockSpan({ attributes: {} });
|
|
317
|
+
processor.onEnd(spanWith);
|
|
318
|
+
processor.onEnd(spanWithout);
|
|
319
|
+
const customObs = sentMessages
|
|
320
|
+
.filter(m => m.event === 'observations')
|
|
321
|
+
.flatMap((m) => m.observations)
|
|
322
|
+
.filter((o) => o.type === 'custom');
|
|
323
|
+
expect(customObs).toHaveLength(1);
|
|
324
|
+
});
|
|
325
|
+
it('should match spans by compound all rule', () => {
|
|
326
|
+
processor.updateCustomMetrics([{
|
|
327
|
+
type: 'span',
|
|
328
|
+
rule: {
|
|
329
|
+
kind: 'all',
|
|
330
|
+
rules: [
|
|
331
|
+
{ kind: 'type', value: 'client' },
|
|
332
|
+
{ kind: 'attribute-equals', key: 'rpc.system', value: 'grpc' },
|
|
333
|
+
]
|
|
334
|
+
},
|
|
335
|
+
subject: 'grpc-client',
|
|
336
|
+
}]);
|
|
337
|
+
const matchingSpan = createMockSpan({
|
|
338
|
+
kind: api_1.SpanKind.CLIENT,
|
|
339
|
+
attributes: { 'rpc.system': 'grpc', 'http.url': 'http://x' },
|
|
438
340
|
});
|
|
341
|
+
const nonMatchingSpan = createMockSpan({
|
|
342
|
+
kind: api_1.SpanKind.CLIENT,
|
|
343
|
+
attributes: { 'rpc.system': 'thrift' },
|
|
344
|
+
});
|
|
345
|
+
processor.onEnd(matchingSpan);
|
|
346
|
+
processor.onEnd(nonMatchingSpan);
|
|
347
|
+
const customObs = sentMessages
|
|
348
|
+
.filter(m => m.event === 'observations')
|
|
349
|
+
.flatMap((m) => m.observations)
|
|
350
|
+
.filter((o) => o.type === 'custom');
|
|
351
|
+
expect(customObs).toHaveLength(1);
|
|
352
|
+
expect(customObs[0].subject).toBe('grpc-client');
|
|
353
|
+
});
|
|
354
|
+
it('should not create custom observations when no specs match', () => {
|
|
355
|
+
processor.updateCustomMetrics([{
|
|
356
|
+
type: 'span',
|
|
357
|
+
rule: { kind: 'type', value: 'internal' },
|
|
358
|
+
subject: 'internal-only',
|
|
359
|
+
}]);
|
|
360
|
+
const span = createMockSpan({ kind: api_1.SpanKind.SERVER });
|
|
361
|
+
processor.onEnd(span);
|
|
362
|
+
const customObs = sentMessages
|
|
363
|
+
.filter(m => m.event === 'observations')
|
|
364
|
+
.flatMap((m) => m.observations)
|
|
365
|
+
.filter((o) => o.type === 'custom');
|
|
366
|
+
expect(customObs).toHaveLength(0);
|
|
439
367
|
});
|
|
440
368
|
});
|
|
441
369
|
describe('Error Handling', () => {
|
|
@@ -466,101 +394,28 @@ describe('ComprehendDevSpanProcessor', () => {
|
|
|
466
394
|
stack: 'Error at line 42\n at method()'
|
|
467
395
|
});
|
|
468
396
|
});
|
|
469
|
-
it('should fallback to span attributes for error information', () => {
|
|
470
|
-
const span = createMockSpan({
|
|
471
|
-
kind: api_1.SpanKind.CLIENT,
|
|
472
|
-
attributes: {
|
|
473
|
-
'http.url': 'https://api.example.com/fail',
|
|
474
|
-
'http.method': 'POST',
|
|
475
|
-
'http.status_code': 404,
|
|
476
|
-
'exception.message': 'Not found',
|
|
477
|
-
'exception.type': 'HttpError'
|
|
478
|
-
},
|
|
479
|
-
status: { code: api_1.SpanStatusCode.ERROR }
|
|
480
|
-
});
|
|
481
|
-
processor.onEnd(span);
|
|
482
|
-
const observationMessage = sentMessages.find(m => m.event === 'observations');
|
|
483
|
-
const observation = observationMessage.observations[0];
|
|
484
|
-
expect(observation).toMatchObject({
|
|
485
|
-
errorMessage: 'Not found',
|
|
486
|
-
errorType: 'HttpError'
|
|
487
|
-
});
|
|
488
|
-
});
|
|
489
|
-
it('should handle spans with error status but no explicit error attributes', () => {
|
|
490
|
-
const span = createMockSpan({
|
|
491
|
-
kind: api_1.SpanKind.SERVER,
|
|
492
|
-
attributes: {
|
|
493
|
-
'http.route': '/api/timeout',
|
|
494
|
-
'http.method': 'GET',
|
|
495
|
-
'http.status_code': 500
|
|
496
|
-
},
|
|
497
|
-
status: { code: api_1.SpanStatusCode.ERROR, message: 'Request timeout' }
|
|
498
|
-
});
|
|
499
|
-
processor.onEnd(span);
|
|
500
|
-
const observationMessage = sentMessages.find(m => m.event === 'observations');
|
|
501
|
-
const observation = observationMessage.observations[0];
|
|
502
|
-
expect(observation.errorMessage).toBe('Request timeout');
|
|
503
|
-
});
|
|
504
397
|
});
|
|
505
|
-
describe('Sequence Numbers
|
|
506
|
-
it('should
|
|
398
|
+
describe('Sequence Numbers', () => {
|
|
399
|
+
it('should use connection.nextSeq() for observation messages', () => {
|
|
507
400
|
const span1 = createMockSpan({
|
|
508
401
|
kind: api_1.SpanKind.SERVER,
|
|
509
|
-
attributes: {
|
|
510
|
-
'http.route': '/api/test1',
|
|
511
|
-
'http.method': 'GET',
|
|
512
|
-
'http.status_code': 200
|
|
513
|
-
}
|
|
402
|
+
attributes: { 'http.route': '/api/test1', 'http.method': 'GET', 'http.status_code': 200 }
|
|
514
403
|
});
|
|
515
404
|
const span2 = createMockSpan({
|
|
516
405
|
kind: api_1.SpanKind.SERVER,
|
|
517
|
-
attributes: {
|
|
518
|
-
'http.route': '/api/test2',
|
|
519
|
-
'http.method': 'GET',
|
|
520
|
-
'http.status_code': 200
|
|
521
|
-
}
|
|
406
|
+
attributes: { 'http.route': '/api/test2', 'http.method': 'GET', 'http.status_code': 200 }
|
|
522
407
|
});
|
|
523
408
|
processor.onEnd(span1);
|
|
524
409
|
processor.onEnd(span2);
|
|
410
|
+
// nextSeq is called for observations AND tracespans
|
|
411
|
+
expect(mockConnection.nextSeq).toHaveBeenCalled();
|
|
525
412
|
const observationMessages = sentMessages.filter(m => m.event === 'observations');
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
expect(
|
|
413
|
+
// Each should have a unique seq
|
|
414
|
+
const seqs = observationMessages.map((m) => m.seq);
|
|
415
|
+
expect(new Set(seqs).size).toBe(seqs.length);
|
|
529
416
|
});
|
|
530
417
|
});
|
|
531
418
|
describe('Processor Lifecycle', () => {
|
|
532
|
-
it('should construct with debug logging', () => {
|
|
533
|
-
const mockLogger = jest.fn();
|
|
534
|
-
const debugProcessor = new ComprehendDevSpanProcessor_1.ComprehendDevSpanProcessor({
|
|
535
|
-
organization: 'test-org',
|
|
536
|
-
token: 'test-token',
|
|
537
|
-
debug: mockLogger
|
|
538
|
-
});
|
|
539
|
-
expect(MockedWebSocketConnection).toHaveBeenCalledWith('test-org', 'test-token', mockLogger);
|
|
540
|
-
debugProcessor.shutdown();
|
|
541
|
-
});
|
|
542
|
-
it('should construct with debug enabled as boolean', () => {
|
|
543
|
-
const debugProcessor = new ComprehendDevSpanProcessor_1.ComprehendDevSpanProcessor({
|
|
544
|
-
organization: 'test-org',
|
|
545
|
-
token: 'test-token',
|
|
546
|
-
debug: true
|
|
547
|
-
});
|
|
548
|
-
expect(MockedWebSocketConnection).toHaveBeenCalledWith('test-org', 'test-token', console.log);
|
|
549
|
-
debugProcessor.shutdown();
|
|
550
|
-
});
|
|
551
|
-
it('should construct with debug disabled', () => {
|
|
552
|
-
const noDebugProcessor = new ComprehendDevSpanProcessor_1.ComprehendDevSpanProcessor({
|
|
553
|
-
organization: 'test-org',
|
|
554
|
-
token: 'test-token',
|
|
555
|
-
debug: false
|
|
556
|
-
});
|
|
557
|
-
expect(MockedWebSocketConnection).toHaveBeenCalledWith('test-org', 'test-token', undefined);
|
|
558
|
-
noDebugProcessor.shutdown();
|
|
559
|
-
});
|
|
560
|
-
it('should call connection.close() on shutdown', async () => {
|
|
561
|
-
await processor.shutdown();
|
|
562
|
-
expect(mockConnection.close).toHaveBeenCalled();
|
|
563
|
-
});
|
|
564
419
|
it('should handle forceFlush without error', async () => {
|
|
565
420
|
await expect(processor.forceFlush()).resolves.toBeUndefined();
|
|
566
421
|
});
|
|
@@ -568,107 +423,73 @@ describe('ComprehendDevSpanProcessor', () => {
|
|
|
568
423
|
expect(() => processor.onStart({}, {})).not.toThrow();
|
|
569
424
|
});
|
|
570
425
|
});
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
type: '
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
},
|
|
640
|
-
startTime: [1700000000, 0],
|
|
641
|
-
duration: [0, 25000000] // 25ms
|
|
642
|
-
});
|
|
643
|
-
processor.onEnd(span);
|
|
644
|
-
// Verify all expected messages: service, database, connection, query, observation
|
|
645
|
-
expect(sentMessages).toHaveLength(5);
|
|
646
|
-
const databaseMessage = sentMessages.find(m => m.type === 'database');
|
|
647
|
-
expect(databaseMessage).toMatchObject({
|
|
648
|
-
system: 'postgresql',
|
|
649
|
-
host: 'db.prod.com',
|
|
650
|
-
port: 5432,
|
|
651
|
-
name: 'ecommerce'
|
|
652
|
-
});
|
|
653
|
-
const connectionMessage = sentMessages.find(m => m.type === 'db-connection');
|
|
654
|
-
expect(connectionMessage).toMatchObject({
|
|
655
|
-
user: 'app_user'
|
|
656
|
-
});
|
|
657
|
-
const queryMessage = sentMessages.find(m => m.type === 'db-query');
|
|
658
|
-
expect(queryMessage).toMatchObject({
|
|
659
|
-
query: 'INSERT INTO orders SELECT FROM users WHERE active = true',
|
|
660
|
-
selects: ['users'],
|
|
661
|
-
inserts: ['orders']
|
|
662
|
-
});
|
|
663
|
-
const observationMessage = sentMessages.find(m => m.event === 'observations');
|
|
664
|
-
const observation = observationMessage.observations[0];
|
|
665
|
-
expect(observation).toMatchObject({
|
|
666
|
-
type: 'db-query',
|
|
667
|
-
subject: queryMessage.hash,
|
|
668
|
-
timestamp: [1700000000, 0],
|
|
669
|
-
duration: [0, 25000000],
|
|
670
|
-
returnedRows: 5
|
|
671
|
-
});
|
|
672
|
-
});
|
|
426
|
+
});
|
|
427
|
+
describe('matchSpanRule', () => {
|
|
428
|
+
function createSpan(kind, attributes = {}) {
|
|
429
|
+
return {
|
|
430
|
+
kind,
|
|
431
|
+
attributes,
|
|
432
|
+
name: 'test',
|
|
433
|
+
spanContext: () => ({ traceId: '', spanId: '', traceFlags: 0 }),
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
it('should match type rule', () => {
|
|
437
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.SERVER), { kind: 'type', value: 'server' })).toBe(true);
|
|
438
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.CLIENT), { kind: 'type', value: 'server' })).toBe(false);
|
|
439
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.INTERNAL), { kind: 'type', value: 'internal' })).toBe(true);
|
|
440
|
+
});
|
|
441
|
+
it('should match attribute-present rule', () => {
|
|
442
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.INTERNAL, { 'foo': 'bar' }), { kind: 'attribute-present', key: 'foo' })).toBe(true);
|
|
443
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.INTERNAL, {}), { kind: 'attribute-present', key: 'foo' })).toBe(false);
|
|
444
|
+
});
|
|
445
|
+
it('should match attribute-absent rule', () => {
|
|
446
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.INTERNAL, {}), { kind: 'attribute-absent', key: 'foo' })).toBe(true);
|
|
447
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.INTERNAL, { 'foo': 'bar' }), { kind: 'attribute-absent', key: 'foo' })).toBe(false);
|
|
448
|
+
});
|
|
449
|
+
it('should match attribute-equals rule', () => {
|
|
450
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.INTERNAL, { 'k': 'v' }), { kind: 'attribute-equals', key: 'k', value: 'v' })).toBe(true);
|
|
451
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.INTERNAL, { 'k': 'other' }), { kind: 'attribute-equals', key: 'k', value: 'v' })).toBe(false);
|
|
452
|
+
});
|
|
453
|
+
it('should match attribute-not-equals rule', () => {
|
|
454
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.INTERNAL, { 'k': 'other' }), { kind: 'attribute-not-equals', key: 'k', value: 'v' })).toBe(true);
|
|
455
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.INTERNAL, { 'k': 'v' }), { kind: 'attribute-not-equals', key: 'k', value: 'v' })).toBe(false);
|
|
456
|
+
});
|
|
457
|
+
it('should match all rule', () => {
|
|
458
|
+
const rule = {
|
|
459
|
+
kind: 'all',
|
|
460
|
+
rules: [
|
|
461
|
+
{ kind: 'type', value: 'server' },
|
|
462
|
+
{ kind: 'attribute-present', key: 'http.route' },
|
|
463
|
+
]
|
|
464
|
+
};
|
|
465
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.SERVER, { 'http.route': '/api' }), rule)).toBe(true);
|
|
466
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.SERVER, {}), rule)).toBe(false);
|
|
467
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.CLIENT, { 'http.route': '/api' }), rule)).toBe(false);
|
|
468
|
+
});
|
|
469
|
+
it('should match any rule', () => {
|
|
470
|
+
const rule = {
|
|
471
|
+
kind: 'any',
|
|
472
|
+
rules: [
|
|
473
|
+
{ kind: 'type', value: 'server' },
|
|
474
|
+
{ kind: 'type', value: 'client' },
|
|
475
|
+
]
|
|
476
|
+
};
|
|
477
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.SERVER), rule)).toBe(true);
|
|
478
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.CLIENT), rule)).toBe(true);
|
|
479
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.INTERNAL), rule)).toBe(false);
|
|
480
|
+
});
|
|
481
|
+
it('should handle nested rules', () => {
|
|
482
|
+
const rule = {
|
|
483
|
+
kind: 'all',
|
|
484
|
+
rules: [
|
|
485
|
+
{ kind: 'any', rules: [
|
|
486
|
+
{ kind: 'type', value: 'server' },
|
|
487
|
+
{ kind: 'type', value: 'client' },
|
|
488
|
+
] },
|
|
489
|
+
{ kind: 'attribute-equals', key: 'rpc.system', value: 'grpc' },
|
|
490
|
+
]
|
|
491
|
+
};
|
|
492
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.SERVER, { 'rpc.system': 'grpc' }), rule)).toBe(true);
|
|
493
|
+
expect((0, ComprehendDevSpanProcessor_1.matchSpanRule)(createSpan(api_1.SpanKind.INTERNAL, { 'rpc.system': 'grpc' }), rule)).toBe(false);
|
|
673
494
|
});
|
|
674
495
|
});
|