@probelabs/probe 0.6.0-rc100
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 +583 -0
- package/bin/.gitkeep +0 -0
- package/bin/probe +158 -0
- package/bin/probe-binary +0 -0
- package/build/agent/ProbeAgent.d.ts +199 -0
- package/build/agent/ProbeAgent.js +1486 -0
- package/build/agent/acp/README.md +347 -0
- package/build/agent/acp/connection.js +237 -0
- package/build/agent/acp/connection.test.js +311 -0
- package/build/agent/acp/examples/simple-client.js +212 -0
- package/build/agent/acp/examples/tool-lifecycle.js +230 -0
- package/build/agent/acp/final-test.js +173 -0
- package/build/agent/acp/index.js +5 -0
- package/build/agent/acp/integration.test.js +385 -0
- package/build/agent/acp/manual-test.js +410 -0
- package/build/agent/acp/protocol-test.js +190 -0
- package/build/agent/acp/server.js +448 -0
- package/build/agent/acp/server.test.js +371 -0
- package/build/agent/acp/test-runner.js +216 -0
- package/build/agent/acp/test-utils/README.md +315 -0
- package/build/agent/acp/test-utils/acp-tester.js +484 -0
- package/build/agent/acp/test-utils/mock-acp-client.js +434 -0
- package/build/agent/acp/tools.js +368 -0
- package/build/agent/acp/tools.test.js +334 -0
- package/build/agent/acp/types.js +218 -0
- package/build/agent/acp/types.test.js +327 -0
- package/build/agent/appTracer.js +360 -0
- package/build/agent/fileSpanExporter.js +169 -0
- package/build/agent/index.js +7426 -0
- package/build/agent/mcp/client.js +338 -0
- package/build/agent/mcp/config.js +313 -0
- package/build/agent/mcp/index.js +64 -0
- package/build/agent/mcp/xmlBridge.js +371 -0
- package/build/agent/mockProvider.js +53 -0
- package/build/agent/probeTool.js +257 -0
- package/build/agent/schemaUtils.js +1726 -0
- package/build/agent/simpleTelemetry.js +267 -0
- package/build/agent/telemetry.js +225 -0
- package/build/agent/tokenCounter.js +395 -0
- package/build/agent/tools.js +163 -0
- package/build/cli.js +49 -0
- package/build/delegate.js +267 -0
- package/build/directory-resolver.js +237 -0
- package/build/downloader.js +750 -0
- package/build/extract.js +149 -0
- package/build/index.js +70 -0
- package/build/mcp/index.js +514 -0
- package/build/mcp/index.ts +608 -0
- package/build/query.js +116 -0
- package/build/search.js +247 -0
- package/build/tools/common.js +410 -0
- package/build/tools/index.js +40 -0
- package/build/tools/langchain.js +88 -0
- package/build/tools/system-message.js +121 -0
- package/build/tools/vercel.js +271 -0
- package/build/utils/file-lister.js +193 -0
- package/build/utils.js +128 -0
- package/cjs/agent/ProbeAgent.cjs +5829 -0
- package/cjs/index.cjs +6217 -0
- package/cjs/package.json +3 -0
- package/index.d.ts +401 -0
- package/package.json +114 -0
- package/scripts/postinstall.js +172 -0
- package/src/agent/ProbeAgent.d.ts +199 -0
- package/src/agent/ProbeAgent.js +1486 -0
- package/src/agent/acp/README.md +347 -0
- package/src/agent/acp/connection.js +237 -0
- package/src/agent/acp/connection.test.js +311 -0
- package/src/agent/acp/examples/simple-client.js +212 -0
- package/src/agent/acp/examples/tool-lifecycle.js +230 -0
- package/src/agent/acp/final-test.js +173 -0
- package/src/agent/acp/index.js +5 -0
- package/src/agent/acp/integration.test.js +385 -0
- package/src/agent/acp/manual-test.js +410 -0
- package/src/agent/acp/protocol-test.js +190 -0
- package/src/agent/acp/server.js +448 -0
- package/src/agent/acp/server.test.js +371 -0
- package/src/agent/acp/test-runner.js +216 -0
- package/src/agent/acp/test-utils/README.md +315 -0
- package/src/agent/acp/test-utils/acp-tester.js +484 -0
- package/src/agent/acp/test-utils/mock-acp-client.js +434 -0
- package/src/agent/acp/tools.js +368 -0
- package/src/agent/acp/tools.test.js +334 -0
- package/src/agent/acp/types.js +218 -0
- package/src/agent/acp/types.test.js +327 -0
- package/src/agent/appTracer.js +360 -0
- package/src/agent/fileSpanExporter.js +169 -0
- package/src/agent/index.js +813 -0
- package/src/agent/mcp/client.js +338 -0
- package/src/agent/mcp/config.js +313 -0
- package/src/agent/mcp/index.js +64 -0
- package/src/agent/mcp/xmlBridge.js +371 -0
- package/src/agent/mockProvider.js +53 -0
- package/src/agent/probeTool.js +257 -0
- package/src/agent/schemaUtils.js +1726 -0
- package/src/agent/simpleTelemetry.js +267 -0
- package/src/agent/telemetry.js +225 -0
- package/src/agent/tokenCounter.js +395 -0
- package/src/agent/tools.js +163 -0
- package/src/cli.js +49 -0
- package/src/delegate.js +267 -0
- package/src/directory-resolver.js +237 -0
- package/src/downloader.js +750 -0
- package/src/extract.js +149 -0
- package/src/index.js +70 -0
- package/src/mcp/index.ts +608 -0
- package/src/query.js +116 -0
- package/src/search.js +247 -0
- package/src/tools/common.js +410 -0
- package/src/tools/index.js +40 -0
- package/src/tools/langchain.js +88 -0
- package/src/tools/system-message.js +121 -0
- package/src/tools/vercel.js +271 -0
- package/src/utils/file-lister.js +193 -0
- package/src/utils.js +128 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { trace, context, SpanStatusCode } from '@opentelemetry/api';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Application-specific tracing layer for probe-agent
|
|
5
|
+
* Provides higher-level tracing functions for AI operations and tool calls
|
|
6
|
+
*/
|
|
7
|
+
export class AppTracer {
|
|
8
|
+
constructor(telemetryConfig, sessionId = null) {
|
|
9
|
+
this.telemetryConfig = telemetryConfig;
|
|
10
|
+
this.tracer = telemetryConfig?.getTracer();
|
|
11
|
+
this.sessionId = sessionId || this.generateSessionId();
|
|
12
|
+
this.traceId = this.generateTraceId();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate a unique session ID
|
|
17
|
+
*/
|
|
18
|
+
generateSessionId() {
|
|
19
|
+
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generate trace ID from session ID for consistent tracing
|
|
24
|
+
*/
|
|
25
|
+
generateTraceId() {
|
|
26
|
+
if (!this.sessionId) return null;
|
|
27
|
+
|
|
28
|
+
// Create a deterministic trace ID from session ID
|
|
29
|
+
const hash = this.hashString(this.sessionId);
|
|
30
|
+
return hash.padEnd(32, '0').substring(0, 32);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Simple hash function for session ID
|
|
35
|
+
*/
|
|
36
|
+
hashString(str) {
|
|
37
|
+
let hash = 0;
|
|
38
|
+
for (let i = 0; i < str.length; i++) {
|
|
39
|
+
const char = str.charCodeAt(i);
|
|
40
|
+
hash = ((hash << 5) - hash) + char;
|
|
41
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
42
|
+
}
|
|
43
|
+
return Math.abs(hash).toString(16);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check if tracing is enabled
|
|
48
|
+
*/
|
|
49
|
+
isEnabled() {
|
|
50
|
+
return this.tracer !== null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Create a root span for the agent session
|
|
55
|
+
*/
|
|
56
|
+
createSessionSpan(attributes = {}) {
|
|
57
|
+
if (!this.isEnabled()) return null;
|
|
58
|
+
|
|
59
|
+
return this.tracer.startSpan('agent.session', {
|
|
60
|
+
attributes: {
|
|
61
|
+
'session.id': this.sessionId,
|
|
62
|
+
'trace.id': this.traceId,
|
|
63
|
+
...attributes,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Create a span for AI model requests
|
|
70
|
+
*/
|
|
71
|
+
createAISpan(modelName, provider, attributes = {}) {
|
|
72
|
+
if (!this.isEnabled()) return null;
|
|
73
|
+
|
|
74
|
+
return this.tracer.startSpan('ai.request', {
|
|
75
|
+
attributes: {
|
|
76
|
+
'ai.model': modelName,
|
|
77
|
+
'ai.provider': provider,
|
|
78
|
+
'session.id': this.sessionId,
|
|
79
|
+
...attributes,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Create a span for tool calls
|
|
86
|
+
*/
|
|
87
|
+
createToolSpan(toolName, attributes = {}) {
|
|
88
|
+
if (!this.isEnabled()) return null;
|
|
89
|
+
|
|
90
|
+
return this.tracer.startSpan('tool.call', {
|
|
91
|
+
attributes: {
|
|
92
|
+
'tool.name': toolName,
|
|
93
|
+
'session.id': this.sessionId,
|
|
94
|
+
...attributes,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Create a span for code search operations
|
|
101
|
+
*/
|
|
102
|
+
createSearchSpan(query, attributes = {}) {
|
|
103
|
+
if (!this.isEnabled()) return null;
|
|
104
|
+
|
|
105
|
+
return this.tracer.startSpan('search.query', {
|
|
106
|
+
attributes: {
|
|
107
|
+
'search.query': query,
|
|
108
|
+
'session.id': this.sessionId,
|
|
109
|
+
...attributes,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Create a span for code extraction operations
|
|
116
|
+
*/
|
|
117
|
+
createExtractSpan(files, attributes = {}) {
|
|
118
|
+
if (!this.isEnabled()) return null;
|
|
119
|
+
|
|
120
|
+
return this.tracer.startSpan('extract.files', {
|
|
121
|
+
attributes: {
|
|
122
|
+
'extract.file_count': Array.isArray(files) ? files.length : 1,
|
|
123
|
+
'extract.files': Array.isArray(files) ? files.join(',') : files,
|
|
124
|
+
'session.id': this.sessionId,
|
|
125
|
+
...attributes,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Create a span for agent iterations
|
|
132
|
+
*/
|
|
133
|
+
createIterationSpan(iteration, attributes = {}) {
|
|
134
|
+
if (!this.isEnabled()) return null;
|
|
135
|
+
|
|
136
|
+
return this.tracer.startSpan('agent.iteration', {
|
|
137
|
+
attributes: {
|
|
138
|
+
'iteration.number': iteration,
|
|
139
|
+
'session.id': this.sessionId,
|
|
140
|
+
...attributes,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Create a span for delegation operations
|
|
147
|
+
*/
|
|
148
|
+
createDelegationSpan(task, attributes = {}) {
|
|
149
|
+
if (!this.isEnabled()) return null;
|
|
150
|
+
|
|
151
|
+
return this.tracer.startSpan('agent.delegation', {
|
|
152
|
+
attributes: {
|
|
153
|
+
'delegation.task': task.substring(0, 200) + (task.length > 200 ? '...' : ''),
|
|
154
|
+
'delegation.task_length': task.length,
|
|
155
|
+
'session.id': this.sessionId,
|
|
156
|
+
...attributes,
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Create a span for JSON validation operations
|
|
163
|
+
*/
|
|
164
|
+
createJsonValidationSpan(responseLength, attributes = {}) {
|
|
165
|
+
if (!this.isEnabled()) return null;
|
|
166
|
+
|
|
167
|
+
return this.tracer.startSpan('validation.json', {
|
|
168
|
+
attributes: {
|
|
169
|
+
'validation.response_length': responseLength,
|
|
170
|
+
'session.id': this.sessionId,
|
|
171
|
+
...attributes,
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Create a span for Mermaid validation operations
|
|
178
|
+
*/
|
|
179
|
+
createMermaidValidationSpan(diagramCount, attributes = {}) {
|
|
180
|
+
if (!this.isEnabled()) return null;
|
|
181
|
+
|
|
182
|
+
return this.tracer.startSpan('validation.mermaid', {
|
|
183
|
+
attributes: {
|
|
184
|
+
'validation.diagram_count': diagramCount,
|
|
185
|
+
'session.id': this.sessionId,
|
|
186
|
+
...attributes,
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Create a span for schema processing operations
|
|
193
|
+
*/
|
|
194
|
+
createSchemaProcessingSpan(schemaType, attributes = {}) {
|
|
195
|
+
if (!this.isEnabled()) return null;
|
|
196
|
+
|
|
197
|
+
return this.tracer.startSpan('schema.processing', {
|
|
198
|
+
attributes: {
|
|
199
|
+
'schema.type': schemaType,
|
|
200
|
+
'session.id': this.sessionId,
|
|
201
|
+
...attributes,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Record delegation events
|
|
208
|
+
*/
|
|
209
|
+
recordDelegationEvent(eventType, data = {}) {
|
|
210
|
+
if (!this.isEnabled()) return;
|
|
211
|
+
|
|
212
|
+
this.addEvent(`delegation.${eventType}`, {
|
|
213
|
+
'session.id': this.sessionId,
|
|
214
|
+
...data
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Record JSON validation events
|
|
220
|
+
*/
|
|
221
|
+
recordJsonValidationEvent(eventType, data = {}) {
|
|
222
|
+
if (!this.isEnabled()) return;
|
|
223
|
+
|
|
224
|
+
this.addEvent(`json_validation.${eventType}`, {
|
|
225
|
+
'session.id': this.sessionId,
|
|
226
|
+
...data
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Record Mermaid validation events
|
|
232
|
+
*/
|
|
233
|
+
recordMermaidValidationEvent(eventType, data = {}) {
|
|
234
|
+
if (!this.isEnabled()) return;
|
|
235
|
+
|
|
236
|
+
this.addEvent(`mermaid_validation.${eventType}`, {
|
|
237
|
+
'session.id': this.sessionId,
|
|
238
|
+
...data
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Add an event to the current or most recent span
|
|
244
|
+
*/
|
|
245
|
+
addEvent(name, attributes = {}) {
|
|
246
|
+
if (!this.isEnabled()) return;
|
|
247
|
+
|
|
248
|
+
// Try to add to the current span in context
|
|
249
|
+
const activeSpan = trace.getActiveSpan();
|
|
250
|
+
if (activeSpan) {
|
|
251
|
+
activeSpan.addEvent(name, {
|
|
252
|
+
'session.id': this.sessionId,
|
|
253
|
+
...attributes,
|
|
254
|
+
});
|
|
255
|
+
} else {
|
|
256
|
+
// Fallback: log as structured data if no active span
|
|
257
|
+
if (this.telemetryConfig?.enableConsole) {
|
|
258
|
+
console.log(`[Event] ${name}:`, attributes);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Set attributes on the current span
|
|
265
|
+
*/
|
|
266
|
+
setAttributes(attributes) {
|
|
267
|
+
if (!this.isEnabled()) return;
|
|
268
|
+
|
|
269
|
+
const activeSpan = trace.getActiveSpan();
|
|
270
|
+
if (activeSpan) {
|
|
271
|
+
activeSpan.setAttributes({
|
|
272
|
+
'session.id': this.sessionId,
|
|
273
|
+
...attributes,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Wrap a function with automatic span creation
|
|
280
|
+
*/
|
|
281
|
+
wrapFunction(spanName, fn, attributes = {}) {
|
|
282
|
+
if (!this.isEnabled()) {
|
|
283
|
+
return fn;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return async (...args) => {
|
|
287
|
+
const span = this.tracer.startSpan(spanName, {
|
|
288
|
+
attributes: {
|
|
289
|
+
'session.id': this.sessionId,
|
|
290
|
+
...attributes,
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
const result = await context.with(trace.setSpan(context.active(), span), () => fn(...args));
|
|
296
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
297
|
+
return result;
|
|
298
|
+
} catch (error) {
|
|
299
|
+
span.setStatus({
|
|
300
|
+
code: SpanStatusCode.ERROR,
|
|
301
|
+
message: error.message,
|
|
302
|
+
});
|
|
303
|
+
span.recordException(error);
|
|
304
|
+
throw error;
|
|
305
|
+
} finally {
|
|
306
|
+
span.end();
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Execute a function within a span context
|
|
313
|
+
*/
|
|
314
|
+
async withSpan(spanName, fn, attributes = {}) {
|
|
315
|
+
if (!this.isEnabled()) {
|
|
316
|
+
return fn();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const span = this.tracer.startSpan(spanName, {
|
|
320
|
+
attributes: {
|
|
321
|
+
'session.id': this.sessionId,
|
|
322
|
+
...attributes,
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
const result = await context.with(trace.setSpan(context.active(), span), () => fn());
|
|
328
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
329
|
+
return result;
|
|
330
|
+
} catch (error) {
|
|
331
|
+
span.setStatus({
|
|
332
|
+
code: SpanStatusCode.ERROR,
|
|
333
|
+
message: error.message,
|
|
334
|
+
});
|
|
335
|
+
span.recordException(error);
|
|
336
|
+
throw error;
|
|
337
|
+
} finally {
|
|
338
|
+
span.end();
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Force flush all pending spans
|
|
345
|
+
*/
|
|
346
|
+
async flush() {
|
|
347
|
+
if (this.telemetryConfig) {
|
|
348
|
+
await this.telemetryConfig.forceFlush();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Shutdown tracing
|
|
354
|
+
*/
|
|
355
|
+
async shutdown() {
|
|
356
|
+
if (this.telemetryConfig) {
|
|
357
|
+
await this.telemetryConfig.shutdown();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { createWriteStream } from 'fs';
|
|
2
|
+
import corePkg from '@opentelemetry/core';
|
|
3
|
+
|
|
4
|
+
const { ExportResultCode } = corePkg;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* File exporter for OpenTelemetry spans
|
|
8
|
+
* Exports spans to a file in JSON Lines format (one JSON object per line)
|
|
9
|
+
* Following the OTLP JSON format specification
|
|
10
|
+
*/
|
|
11
|
+
export class FileSpanExporter {
|
|
12
|
+
constructor(filePath = './traces.jsonl') {
|
|
13
|
+
this.filePath = filePath;
|
|
14
|
+
this.stream = createWriteStream(filePath, { flags: 'a' });
|
|
15
|
+
this.stream.on('error', (error) => {
|
|
16
|
+
console.error(`[FileSpanExporter] Stream error: ${error.message}`);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Export spans to file
|
|
22
|
+
* @param {ReadableSpan[]} spans - Array of spans to export
|
|
23
|
+
* @param {function} resultCallback - Callback to call with the export result
|
|
24
|
+
*/
|
|
25
|
+
export(spans, resultCallback) {
|
|
26
|
+
if (!spans || spans.length === 0) {
|
|
27
|
+
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const timestamp = Date.now();
|
|
33
|
+
|
|
34
|
+
spans.forEach((span, index) => {
|
|
35
|
+
// Extract parent span ID - check various possible properties
|
|
36
|
+
let parentSpanId = undefined;
|
|
37
|
+
|
|
38
|
+
if (span.parentSpanContext) {
|
|
39
|
+
parentSpanId = span.parentSpanContext.spanId;
|
|
40
|
+
} else if (span._parentSpanContext) {
|
|
41
|
+
parentSpanId = span._parentSpanContext.spanId;
|
|
42
|
+
} else if (span.parent) {
|
|
43
|
+
parentSpanId = span.parent.spanId;
|
|
44
|
+
} else if (span._parent) {
|
|
45
|
+
parentSpanId = span._parent.spanId;
|
|
46
|
+
} else if (span._parentId) {
|
|
47
|
+
parentSpanId = span._parentId;
|
|
48
|
+
} else if (span.parentSpanId) {
|
|
49
|
+
parentSpanId = span.parentSpanId;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Convert span to OTLP JSON format
|
|
53
|
+
const spanData = {
|
|
54
|
+
traceId: span.spanContext().traceId,
|
|
55
|
+
spanId: span.spanContext().spanId,
|
|
56
|
+
parentSpanId: parentSpanId,
|
|
57
|
+
name: span.name,
|
|
58
|
+
kind: span.kind,
|
|
59
|
+
startTimeUnixNano: span.startTime[0] * 1_000_000_000 + span.startTime[1],
|
|
60
|
+
endTimeUnixNano: span.endTime[0] * 1_000_000_000 + span.endTime[1],
|
|
61
|
+
attributes: this.convertAttributes(span.attributes),
|
|
62
|
+
status: span.status,
|
|
63
|
+
events: span.events?.map(event => ({
|
|
64
|
+
timeUnixNano: event.time[0] * 1_000_000_000 + event.time[1],
|
|
65
|
+
name: event.name,
|
|
66
|
+
attributes: this.convertAttributes(event.attributes),
|
|
67
|
+
})) || [],
|
|
68
|
+
links: span.links?.map(link => ({
|
|
69
|
+
traceId: link.context.traceId,
|
|
70
|
+
spanId: link.context.spanId,
|
|
71
|
+
attributes: this.convertAttributes(link.attributes),
|
|
72
|
+
})) || [],
|
|
73
|
+
resource: {
|
|
74
|
+
attributes: this.convertAttributes(span.resource?.attributes || {}),
|
|
75
|
+
},
|
|
76
|
+
instrumentationLibrary: {
|
|
77
|
+
name: span.instrumentationLibrary?.name || 'unknown',
|
|
78
|
+
version: span.instrumentationLibrary?.version || 'unknown',
|
|
79
|
+
},
|
|
80
|
+
timestamp,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Write as JSON Lines format (one JSON object per line)
|
|
84
|
+
this.stream.write(JSON.stringify(spanData) + '\n');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error(`[FileSpanExporter] Export error: ${error.message}`);
|
|
90
|
+
resultCallback({
|
|
91
|
+
code: ExportResultCode.FAILED,
|
|
92
|
+
error: error
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Convert OpenTelemetry attributes to plain object
|
|
99
|
+
* @param {Object} attributes - OpenTelemetry attributes
|
|
100
|
+
* @returns {Object} Plain object with string values
|
|
101
|
+
*/
|
|
102
|
+
convertAttributes(attributes) {
|
|
103
|
+
if (!attributes) return {};
|
|
104
|
+
|
|
105
|
+
const result = {};
|
|
106
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
107
|
+
// Convert all values to strings for JSON compatibility
|
|
108
|
+
if (typeof value === 'object' && value !== null) {
|
|
109
|
+
result[key] = JSON.stringify(value);
|
|
110
|
+
} else {
|
|
111
|
+
result[key] = String(value);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Shutdown the exporter
|
|
119
|
+
* @returns {Promise<void>}
|
|
120
|
+
*/
|
|
121
|
+
async shutdown() {
|
|
122
|
+
return new Promise((resolve) => {
|
|
123
|
+
if (this.stream) {
|
|
124
|
+
this.stream.end(() => {
|
|
125
|
+
console.log(`[FileSpanExporter] File stream closed: ${this.filePath}`);
|
|
126
|
+
resolve();
|
|
127
|
+
});
|
|
128
|
+
} else {
|
|
129
|
+
resolve();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Force flush any pending spans
|
|
136
|
+
* @returns {Promise<void>}
|
|
137
|
+
*/
|
|
138
|
+
async forceFlush() {
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
if (this.stream) {
|
|
141
|
+
const flushTimeout = setTimeout(() => {
|
|
142
|
+
console.warn('[FileSpanExporter] Flush timeout after 5 seconds');
|
|
143
|
+
resolve();
|
|
144
|
+
}, 5000);
|
|
145
|
+
|
|
146
|
+
// Uncork the stream to force buffered writes
|
|
147
|
+
if (this.stream.writableCorked) {
|
|
148
|
+
this.stream.uncork();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// If there's buffered data, wait for drain event
|
|
152
|
+
if (this.stream.writableNeedDrain) {
|
|
153
|
+
this.stream.once('drain', () => {
|
|
154
|
+
clearTimeout(flushTimeout);
|
|
155
|
+
resolve();
|
|
156
|
+
});
|
|
157
|
+
} else {
|
|
158
|
+
// No buffered data, but still give it a moment to ensure writes complete
|
|
159
|
+
setImmediate(() => {
|
|
160
|
+
clearTimeout(flushTimeout);
|
|
161
|
+
resolve();
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
resolve();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|