@probelabs/probe 0.6.0-rc147 → 0.6.0-rc149

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.
@@ -1,167 +0,0 @@
1
- import { createWriteStream } from 'fs';
2
- import { ExportResultCode } from '@opentelemetry/core';
3
-
4
- /**
5
- * File exporter for OpenTelemetry spans
6
- * Exports spans to a file in JSON Lines format (one JSON object per line)
7
- * Following the OTLP JSON format specification
8
- */
9
- export class FileSpanExporter {
10
- constructor(filePath = './traces.jsonl') {
11
- this.filePath = filePath;
12
- this.stream = createWriteStream(filePath, { flags: 'a' });
13
- this.stream.on('error', (error) => {
14
- console.error(`[FileSpanExporter] Stream error: ${error.message}`);
15
- });
16
- }
17
-
18
- /**
19
- * Export spans to file
20
- * @param {ReadableSpan[]} spans - Array of spans to export
21
- * @param {function} resultCallback - Callback to call with the export result
22
- */
23
- export(spans, resultCallback) {
24
- if (!spans || spans.length === 0) {
25
- resultCallback({ code: ExportResultCode.SUCCESS });
26
- return;
27
- }
28
-
29
- try {
30
- const timestamp = Date.now();
31
-
32
- spans.forEach((span, index) => {
33
- // Extract parent span ID - check various possible properties
34
- let parentSpanId = undefined;
35
-
36
- if (span.parentSpanContext) {
37
- parentSpanId = span.parentSpanContext.spanId;
38
- } else if (span._parentSpanContext) {
39
- parentSpanId = span._parentSpanContext.spanId;
40
- } else if (span.parent) {
41
- parentSpanId = span.parent.spanId;
42
- } else if (span._parent) {
43
- parentSpanId = span._parent.spanId;
44
- } else if (span._parentId) {
45
- parentSpanId = span._parentId;
46
- } else if (span.parentSpanId) {
47
- parentSpanId = span.parentSpanId;
48
- }
49
-
50
- // Convert span to OTLP JSON format
51
- const spanData = {
52
- traceId: span.spanContext().traceId,
53
- spanId: span.spanContext().spanId,
54
- parentSpanId: parentSpanId,
55
- name: span.name,
56
- kind: span.kind,
57
- startTimeUnixNano: span.startTime[0] * 1_000_000_000 + span.startTime[1],
58
- endTimeUnixNano: span.endTime[0] * 1_000_000_000 + span.endTime[1],
59
- attributes: this.convertAttributes(span.attributes),
60
- status: span.status,
61
- events: span.events?.map(event => ({
62
- timeUnixNano: event.time[0] * 1_000_000_000 + event.time[1],
63
- name: event.name,
64
- attributes: this.convertAttributes(event.attributes),
65
- })) || [],
66
- links: span.links?.map(link => ({
67
- traceId: link.context.traceId,
68
- spanId: link.context.spanId,
69
- attributes: this.convertAttributes(link.attributes),
70
- })) || [],
71
- resource: {
72
- attributes: this.convertAttributes(span.resource?.attributes || {}),
73
- },
74
- instrumentationLibrary: {
75
- name: span.instrumentationLibrary?.name || 'unknown',
76
- version: span.instrumentationLibrary?.version || 'unknown',
77
- },
78
- timestamp,
79
- };
80
-
81
- // Write as JSON Lines format (one JSON object per line)
82
- this.stream.write(JSON.stringify(spanData) + '\n');
83
- });
84
-
85
- resultCallback({ code: ExportResultCode.SUCCESS });
86
- } catch (error) {
87
- console.error(`[FileSpanExporter] Export error: ${error.message}`);
88
- resultCallback({
89
- code: ExportResultCode.FAILED,
90
- error: error
91
- });
92
- }
93
- }
94
-
95
- /**
96
- * Convert OpenTelemetry attributes to plain object
97
- * @param {Object} attributes - OpenTelemetry attributes
98
- * @returns {Object} Plain object with string values
99
- */
100
- convertAttributes(attributes) {
101
- if (!attributes) return {};
102
-
103
- const result = {};
104
- for (const [key, value] of Object.entries(attributes)) {
105
- // Convert all values to strings for JSON compatibility
106
- if (typeof value === 'object' && value !== null) {
107
- result[key] = JSON.stringify(value);
108
- } else {
109
- result[key] = String(value);
110
- }
111
- }
112
- return result;
113
- }
114
-
115
- /**
116
- * Shutdown the exporter
117
- * @returns {Promise<void>}
118
- */
119
- async shutdown() {
120
- return new Promise((resolve) => {
121
- if (this.stream) {
122
- this.stream.end(() => {
123
- console.log(`[FileSpanExporter] File stream closed: ${this.filePath}`);
124
- resolve();
125
- });
126
- } else {
127
- resolve();
128
- }
129
- });
130
- }
131
-
132
- /**
133
- * Force flush any pending spans
134
- * @returns {Promise<void>}
135
- */
136
- async forceFlush() {
137
- return new Promise((resolve, reject) => {
138
- if (this.stream) {
139
- const flushTimeout = setTimeout(() => {
140
- console.warn('[FileSpanExporter] Flush timeout after 5 seconds');
141
- resolve();
142
- }, 5000);
143
-
144
- // Uncork the stream to force buffered writes
145
- if (this.stream.writableCorked) {
146
- this.stream.uncork();
147
- }
148
-
149
- // If there's buffered data, wait for drain event
150
- if (this.stream.writableNeedDrain) {
151
- this.stream.once('drain', () => {
152
- clearTimeout(flushTimeout);
153
- resolve();
154
- });
155
- } else {
156
- // No buffered data, but still give it a moment to ensure writes complete
157
- setImmediate(() => {
158
- clearTimeout(flushTimeout);
159
- resolve();
160
- });
161
- }
162
- } else {
163
- resolve();
164
- }
165
- });
166
- }
167
- }
@@ -1,219 +0,0 @@
1
- import { NodeSDK } from '@opentelemetry/sdk-node';
2
- import { resourceFromAttributes } from '@opentelemetry/resources';
3
- import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
4
- import { trace, context, SpanStatusCode } from '@opentelemetry/api';
5
- import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
6
- import { BatchSpanProcessor, ConsoleSpanExporter } from '@opentelemetry/sdk-trace-base';
7
-
8
- import { existsSync, mkdirSync } from 'fs';
9
- import { dirname } from 'path';
10
- import { FileSpanExporter } from './fileSpanExporter.js';
11
-
12
- /**
13
- * Custom OpenTelemetry configuration for probe-agent
14
- */
15
- export class TelemetryConfig {
16
- constructor(options = {}) {
17
- this.serviceName = options.serviceName || 'probe-agent';
18
- this.serviceVersion = options.serviceVersion || '1.0.0';
19
- this.enableFile = options.enableFile || false;
20
- this.enableRemote = options.enableRemote || false;
21
- this.enableConsole = options.enableConsole || false;
22
- this.filePath = options.filePath || './traces.jsonl';
23
- this.remoteEndpoint = options.remoteEndpoint || 'http://localhost:4318/v1/traces';
24
- this.sdk = null;
25
- this.tracer = null;
26
- }
27
-
28
- /**
29
- * Initialize OpenTelemetry SDK
30
- */
31
- initialize() {
32
- if (this.sdk) {
33
- console.warn('Telemetry already initialized');
34
- return;
35
- }
36
-
37
- const resource = resourceFromAttributes({
38
- [ATTR_SERVICE_NAME]: this.serviceName,
39
- [ATTR_SERVICE_VERSION]: this.serviceVersion,
40
- });
41
-
42
- const spanProcessors = [];
43
-
44
- // Add file exporter if enabled
45
- if (this.enableFile) {
46
- try {
47
- // Ensure the directory exists
48
- const dir = dirname(this.filePath);
49
- if (!existsSync(dir)) {
50
- mkdirSync(dir, { recursive: true });
51
- }
52
-
53
- const fileExporter = new FileSpanExporter(this.filePath);
54
- spanProcessors.push(new BatchSpanProcessor(fileExporter, {
55
- maxQueueSize: 2048,
56
- maxExportBatchSize: 512,
57
- scheduledDelayMillis: 500,
58
- exportTimeoutMillis: 30000,
59
- }));
60
- console.log(`[Telemetry] File exporter enabled, writing to: ${this.filePath}`);
61
- } catch (error) {
62
- console.error(`[Telemetry] Failed to initialize file exporter: ${error.message}`);
63
- }
64
- }
65
-
66
- // Add remote exporter if enabled
67
- if (this.enableRemote) {
68
- try {
69
- const remoteExporter = new OTLPTraceExporter({
70
- url: this.remoteEndpoint,
71
- });
72
- spanProcessors.push(new BatchSpanProcessor(remoteExporter, {
73
- maxQueueSize: 2048,
74
- maxExportBatchSize: 512,
75
- scheduledDelayMillis: 500,
76
- exportTimeoutMillis: 30000,
77
- }));
78
- console.log(`[Telemetry] Remote exporter enabled, endpoint: ${this.remoteEndpoint}`);
79
- } catch (error) {
80
- console.error(`[Telemetry] Failed to initialize remote exporter: ${error.message}`);
81
- }
82
- }
83
-
84
- // Add console exporter if enabled (useful for debugging)
85
- if (this.enableConsole) {
86
- const consoleExporter = new ConsoleSpanExporter();
87
- spanProcessors.push(new BatchSpanProcessor(consoleExporter, {
88
- maxQueueSize: 2048,
89
- maxExportBatchSize: 512,
90
- scheduledDelayMillis: 500,
91
- exportTimeoutMillis: 30000,
92
- }));
93
- console.log(`[Telemetry] Console exporter enabled`);
94
- }
95
-
96
- if (spanProcessors.length === 0) {
97
- console.log('[Telemetry] No exporters configured, telemetry will not be collected');
98
- return;
99
- }
100
-
101
- this.sdk = new NodeSDK({
102
- resource,
103
- spanProcessors,
104
- });
105
-
106
- try {
107
- this.sdk.start();
108
- this.tracer = trace.getTracer(this.serviceName, this.serviceVersion);
109
- console.log(`[Telemetry] OpenTelemetry SDK initialized successfully`);
110
- } catch (error) {
111
- console.error(`[Telemetry] Failed to start OpenTelemetry SDK: ${error.message}`);
112
- }
113
- }
114
-
115
- /**
116
- * Get the tracer instance
117
- */
118
- getTracer() {
119
- return this.tracer;
120
- }
121
-
122
- /**
123
- * Create a span with the given name and attributes
124
- */
125
- createSpan(name, attributes = {}) {
126
- if (!this.tracer) {
127
- return null;
128
- }
129
-
130
- return this.tracer.startSpan(name, {
131
- attributes,
132
- });
133
- }
134
-
135
- /**
136
- * Wrap a function to automatically create spans
137
- */
138
- wrapFunction(name, fn, attributes = {}) {
139
- if (!this.tracer) {
140
- return fn;
141
- }
142
-
143
- return async (...args) => {
144
- const span = this.createSpan(name, attributes);
145
- if (!span) {
146
- return fn(...args);
147
- }
148
-
149
- try {
150
- const result = await context.with(trace.setSpan(context.active(), span), () => fn(...args));
151
- span.setStatus({ code: SpanStatusCode.OK });
152
- return result;
153
- } catch (error) {
154
- span.setStatus({
155
- code: SpanStatusCode.ERROR,
156
- message: error.message,
157
- });
158
- span.recordException(error);
159
- throw error;
160
- } finally {
161
- span.end();
162
- }
163
- };
164
- }
165
-
166
- /**
167
- * Force flush all pending spans
168
- */
169
- async forceFlush() {
170
- if (this.sdk) {
171
- try {
172
- const tracerProvider = trace.getTracerProvider();
173
-
174
- if (tracerProvider && typeof tracerProvider.forceFlush === 'function') {
175
- await tracerProvider.forceFlush();
176
- }
177
-
178
- // Add a small delay to ensure file writes complete
179
- await new Promise(resolve => setTimeout(resolve, 100));
180
-
181
- console.log('[Telemetry] OpenTelemetry spans flushed successfully');
182
- } catch (error) {
183
- console.error(`[Telemetry] Failed to flush OpenTelemetry spans: ${error.message}`);
184
- }
185
- }
186
- }
187
-
188
- /**
189
- * Shutdown telemetry
190
- */
191
- async shutdown() {
192
- if (this.sdk) {
193
- try {
194
- await this.sdk.shutdown();
195
- console.log('[Telemetry] OpenTelemetry SDK shutdown successfully');
196
- } catch (error) {
197
- console.error(`[Telemetry] Failed to shutdown OpenTelemetry SDK: ${error.message}`);
198
- }
199
- }
200
- }
201
- }
202
-
203
- /**
204
- * Initialize telemetry from CLI options
205
- */
206
- export function initializeTelemetryFromOptions(options) {
207
- const config = new TelemetryConfig({
208
- serviceName: 'probe-agent',
209
- serviceVersion: '1.0.0',
210
- enableFile: options.traceFile !== undefined,
211
- enableRemote: options.traceRemote !== undefined,
212
- enableConsole: options.traceConsole,
213
- filePath: options.traceFile || './traces.jsonl',
214
- remoteEndpoint: options.traceRemote || 'http://localhost:4318/v1/traces',
215
- });
216
-
217
- config.initialize();
218
- return config;
219
- }