autotel-devtools 8.1.0 → 9.0.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/dist/cli.cjs +108 -1409
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +109 -1402
- package/dist/cli.js.map +1 -1
- package/dist/error-aggregator-BvNmgn7E.d.ts +120 -0
- package/dist/error-aggregator-nnfbpSR7.d.cts +120 -0
- package/dist/exporter-1Y3GmLVS.d.cts +182 -0
- package/dist/exporter-CZ5HdD3o.d.ts +182 -0
- package/dist/genai/index.cjs +650 -537
- package/dist/genai/index.cjs.map +1 -1
- package/dist/genai/index.d.cts +164 -157
- package/dist/genai/index.d.ts +164 -157
- package/dist/genai/index.js +649 -536
- package/dist/genai/index.js.map +1 -1
- package/dist/http-BkkKa9C_.js +1128 -0
- package/dist/http-BkkKa9C_.js.map +1 -0
- package/dist/http-Yj6iSrMX.cjs +1275 -0
- package/dist/http-Yj6iSrMX.cjs.map +1 -0
- package/dist/index.cjs +50 -1708
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -23
- package/dist/index.d.ts +21 -23
- package/dist/index.js +44 -1696
- package/dist/index.js.map +1 -1
- package/dist/listen-BBsxO0wm.cjs +125 -0
- package/dist/listen-BBsxO0wm.cjs.map +1 -0
- package/dist/listen-DfOCquUq.js +120 -0
- package/dist/listen-DfOCquUq.js.map +1 -0
- package/dist/resource-utils-B4UVvfnH.js +18 -0
- package/dist/resource-utils-B4UVvfnH.js.map +1 -0
- package/dist/resource-utils-DjHJB6uc.cjs +24 -0
- package/dist/resource-utils-DjHJB6uc.cjs.map +1 -0
- package/dist/server/exporter.cjs +135 -159
- package/dist/server/exporter.cjs.map +1 -1
- package/dist/server/exporter.d.cts +2 -4
- package/dist/server/exporter.d.ts +2 -4
- package/dist/server/exporter.js +134 -158
- package/dist/server/exporter.js.map +1 -1
- package/dist/server/index.cjs +29 -1640
- package/dist/server/index.d.cts +34 -31
- package/dist/server/index.d.ts +34 -31
- package/dist/server/index.js +5 -1610
- package/dist/server/log-exporter.cjs +75 -102
- package/dist/server/log-exporter.cjs.map +1 -1
- package/dist/server/log-exporter.d.cts +27 -46
- package/dist/server/log-exporter.d.ts +27 -46
- package/dist/server/log-exporter.js +74 -100
- package/dist/server/log-exporter.js.map +1 -1
- package/dist/server/remote-exporter.cjs +171 -213
- package/dist/server/remote-exporter.cjs.map +1 -1
- package/dist/server/remote-exporter.d.cts +62 -82
- package/dist/server/remote-exporter.d.ts +62 -82
- package/dist/server/remote-exporter.js +170 -212
- package/dist/server/remote-exporter.js.map +1 -1
- package/dist/widget.global.js +10 -10
- package/package.json +5 -5
- package/dist/error-aggregator-D0Uu5r38.d.ts +0 -147
- package/dist/error-aggregator-D1Mr221Y.d.cts +0 -147
- package/dist/exporter-De6p4iAD.d.cts +0 -182
- package/dist/exporter-De6p4iAD.d.ts +0 -182
- package/dist/server/index.cjs.map +0 -1
- package/dist/server/index.js.map +0 -1
|
@@ -1,87 +1,67 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Remote Span Exporter for sending traces to a hosted DevtoolsServer
|
|
6
|
-
*
|
|
7
|
-
* Use this when the DevtoolsServer is running on a different machine/process.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```typescript
|
|
11
|
-
* import { DevtoolsRemoteExporter } from '@autotel/devtools/server';
|
|
12
|
-
* import { init } from 'autotel';
|
|
13
|
-
*
|
|
14
|
-
* init({
|
|
15
|
-
* service: 'my-app',
|
|
16
|
-
* spanExporters: [
|
|
17
|
-
* new DevtoolsRemoteExporter({
|
|
18
|
-
* endpoint: 'https://autotel.mycompany.com',
|
|
19
|
-
* apiKey: process.env.AUTOTEL_API_KEY,
|
|
20
|
-
* })
|
|
21
|
-
* ]
|
|
22
|
-
* });
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
1
|
+
import { ExportResult } from "@opentelemetry/core";
|
|
2
|
+
import { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base";
|
|
25
3
|
|
|
4
|
+
//#region src/server/remote-exporter.d.ts
|
|
26
5
|
interface DevtoolsRemoteExporterOptions {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Base URL of the Devtools server
|
|
8
|
+
* e.g., 'https://autotel.mycompany.com' or 'http://localhost:8082'
|
|
9
|
+
*/
|
|
10
|
+
endpoint: string;
|
|
11
|
+
/**
|
|
12
|
+
* API key for authentication (if server requires it)
|
|
13
|
+
*/
|
|
14
|
+
apiKey?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Service name for traces (default: 'unknown-service')
|
|
17
|
+
*/
|
|
18
|
+
serviceName?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Request timeout in milliseconds (default: 5000)
|
|
21
|
+
*/
|
|
22
|
+
timeout?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Retry failed requests (default: true)
|
|
25
|
+
*/
|
|
26
|
+
retry?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Number of retries (default: 3)
|
|
29
|
+
*/
|
|
30
|
+
retryCount?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Retry delay in milliseconds (default: 1000)
|
|
33
|
+
*/
|
|
34
|
+
retryDelay?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Enable verbose logging (default: false)
|
|
37
|
+
*/
|
|
38
|
+
verbose?: boolean;
|
|
60
39
|
}
|
|
61
40
|
declare class DevtoolsRemoteExporter implements SpanExporter {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
41
|
+
private options;
|
|
42
|
+
private pendingExports;
|
|
43
|
+
constructor(options: DevtoolsRemoteExporterOptions);
|
|
44
|
+
/**
|
|
45
|
+
* Export spans to the remote server
|
|
46
|
+
*/
|
|
47
|
+
export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): Promise<void>;
|
|
48
|
+
private doExport;
|
|
49
|
+
private sendWithRetry;
|
|
50
|
+
private send;
|
|
51
|
+
/**
|
|
52
|
+
* Shutdown the exporter, waiting for pending exports
|
|
53
|
+
*/
|
|
54
|
+
shutdown(): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Force flush pending exports
|
|
57
|
+
*/
|
|
58
|
+
forceFlush(): Promise<void>;
|
|
59
|
+
private convertToTraceData;
|
|
60
|
+
private convertSpan;
|
|
61
|
+
private convertSpanKind;
|
|
62
|
+
private sleep;
|
|
63
|
+
private log;
|
|
85
64
|
}
|
|
86
|
-
|
|
87
|
-
export { DevtoolsRemoteExporter,
|
|
65
|
+
//#endregion
|
|
66
|
+
export { DevtoolsRemoteExporter, DevtoolsRemoteExporterOptions };
|
|
67
|
+
//# sourceMappingURL=remote-exporter.d.ts.map
|
|
@@ -1,217 +1,175 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/server/remote-exporter.ts
|
|
2
2
|
var DevtoolsRemoteExporter = class {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
kind: this.convertSpanKind(span.kind),
|
|
172
|
-
startTime,
|
|
173
|
-
endTime,
|
|
174
|
-
duration: endTime - startTime,
|
|
175
|
-
attributes,
|
|
176
|
-
status: {
|
|
177
|
-
code: status,
|
|
178
|
-
message: span.status.message
|
|
179
|
-
},
|
|
180
|
-
events: events.length > 0 ? events : void 0
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
convertSpanKind(kind) {
|
|
184
|
-
switch (kind) {
|
|
185
|
-
case 0: {
|
|
186
|
-
return "INTERNAL";
|
|
187
|
-
}
|
|
188
|
-
case 1: {
|
|
189
|
-
return "SERVER";
|
|
190
|
-
}
|
|
191
|
-
case 2: {
|
|
192
|
-
return "CLIENT";
|
|
193
|
-
}
|
|
194
|
-
case 3: {
|
|
195
|
-
return "PRODUCER";
|
|
196
|
-
}
|
|
197
|
-
case 4: {
|
|
198
|
-
return "CONSUMER";
|
|
199
|
-
}
|
|
200
|
-
default: {
|
|
201
|
-
return "INTERNAL";
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
sleep(ms) {
|
|
206
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
207
|
-
}
|
|
208
|
-
log(message) {
|
|
209
|
-
if (this.options.verbose) {
|
|
210
|
-
console.log(`[Devtools Remote Exporter] ${message}`);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
3
|
+
options;
|
|
4
|
+
pendingExports = [];
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.options = {
|
|
7
|
+
endpoint: options.endpoint.replace(/\/$/, ""),
|
|
8
|
+
apiKey: options.apiKey ?? "",
|
|
9
|
+
serviceName: options.serviceName ?? "unknown-service",
|
|
10
|
+
timeout: options.timeout ?? 5e3,
|
|
11
|
+
retry: options.retry ?? true,
|
|
12
|
+
retryCount: options.retryCount ?? 3,
|
|
13
|
+
retryDelay: options.retryDelay ?? 1e3,
|
|
14
|
+
verbose: options.verbose ?? false
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Export spans to the remote server
|
|
19
|
+
*/
|
|
20
|
+
async export(spans, resultCallback) {
|
|
21
|
+
const exportPromise = this.doExport(spans).then(() => {
|
|
22
|
+
resultCallback({ code: 0 });
|
|
23
|
+
}).catch((error) => {
|
|
24
|
+
this.log(`Export failed: ${error.message}`);
|
|
25
|
+
resultCallback({ code: 1 });
|
|
26
|
+
});
|
|
27
|
+
this.pendingExports.push(exportPromise);
|
|
28
|
+
exportPromise.finally(() => {
|
|
29
|
+
const index = this.pendingExports.indexOf(exportPromise);
|
|
30
|
+
if (index !== -1) this.pendingExports.splice(index, 1);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async doExport(spans) {
|
|
34
|
+
if (spans.length === 0) return;
|
|
35
|
+
this.log(`Exporting ${spans.length} span(s) to ${this.options.endpoint}`);
|
|
36
|
+
const traceMap = /* @__PURE__ */ new Map();
|
|
37
|
+
for (const span of spans) {
|
|
38
|
+
const traceId = span.spanContext().traceId;
|
|
39
|
+
if (!traceMap.has(traceId)) traceMap.set(traceId, []);
|
|
40
|
+
traceMap.get(traceId).push(span);
|
|
41
|
+
}
|
|
42
|
+
const traces = [];
|
|
43
|
+
for (const [traceId, traceSpans] of traceMap) traces.push(this.convertToTraceData(traceId, traceSpans));
|
|
44
|
+
await this.sendWithRetry({ traces });
|
|
45
|
+
}
|
|
46
|
+
async sendWithRetry(payload) {
|
|
47
|
+
let lastError = null;
|
|
48
|
+
const maxAttempts = this.options.retry ? this.options.retryCount : 1;
|
|
49
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
|
|
50
|
+
await this.send(payload);
|
|
51
|
+
return;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
54
|
+
this.log(`Attempt ${attempt}/${maxAttempts} failed: ${lastError.message}`);
|
|
55
|
+
if (attempt < maxAttempts) await this.sleep(this.options.retryDelay * attempt);
|
|
56
|
+
}
|
|
57
|
+
throw lastError || /* @__PURE__ */ new Error("Export failed");
|
|
58
|
+
}
|
|
59
|
+
async send(payload) {
|
|
60
|
+
const controller = new AbortController();
|
|
61
|
+
const timeoutId = setTimeout(() => controller.abort(), this.options.timeout);
|
|
62
|
+
try {
|
|
63
|
+
const headers = { "Content-Type": "application/json" };
|
|
64
|
+
if (this.options.apiKey) headers["Authorization"] = `Bearer ${this.options.apiKey}`;
|
|
65
|
+
const response = await fetch(`${this.options.endpoint}/ingest/traces`, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers,
|
|
68
|
+
body: JSON.stringify(payload),
|
|
69
|
+
signal: controller.signal
|
|
70
|
+
});
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
const text = await response.text();
|
|
73
|
+
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
74
|
+
}
|
|
75
|
+
const result = await response.json();
|
|
76
|
+
this.log(`Successfully sent ${result.processed} trace(s)`);
|
|
77
|
+
} finally {
|
|
78
|
+
clearTimeout(timeoutId);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Shutdown the exporter, waiting for pending exports
|
|
83
|
+
*/
|
|
84
|
+
async shutdown() {
|
|
85
|
+
this.log("Shutting down, waiting for pending exports...");
|
|
86
|
+
await Promise.allSettled(this.pendingExports);
|
|
87
|
+
this.log("Shutdown complete");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Force flush pending exports
|
|
91
|
+
*/
|
|
92
|
+
async forceFlush() {
|
|
93
|
+
await Promise.allSettled(this.pendingExports);
|
|
94
|
+
}
|
|
95
|
+
convertToTraceData(traceId, spans) {
|
|
96
|
+
const spanData = spans.map((span) => this.convertSpan(span));
|
|
97
|
+
const rootSpan = spanData.find((s) => !s.parentSpanId) || spanData[0];
|
|
98
|
+
spanData.sort((a, b) => a.startTime - b.startTime);
|
|
99
|
+
const startTime = Math.min(...spanData.map((s) => s.startTime));
|
|
100
|
+
const endTime = Math.max(...spanData.map((s) => s.endTime));
|
|
101
|
+
const status = spanData.some((s) => s.status.code === "ERROR") ? "ERROR" : "OK";
|
|
102
|
+
return {
|
|
103
|
+
traceId,
|
|
104
|
+
correlationId: traceId.slice(0, 16),
|
|
105
|
+
rootSpan,
|
|
106
|
+
spans: spanData,
|
|
107
|
+
startTime,
|
|
108
|
+
endTime,
|
|
109
|
+
duration: endTime - startTime,
|
|
110
|
+
status,
|
|
111
|
+
service: this.options.serviceName
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
convertSpan(span) {
|
|
115
|
+
const spanContext = span.spanContext();
|
|
116
|
+
const startTime = span.startTime[0] * 1e3 + span.startTime[1] / 1e6;
|
|
117
|
+
const endTime = span.endTime[0] * 1e3 + span.endTime[1] / 1e6;
|
|
118
|
+
const attributes = {};
|
|
119
|
+
for (const [key, value] of Object.entries(span.attributes)) attributes[key] = value;
|
|
120
|
+
let status;
|
|
121
|
+
switch (span.status.code) {
|
|
122
|
+
case 0:
|
|
123
|
+
status = "UNSET";
|
|
124
|
+
break;
|
|
125
|
+
case 1:
|
|
126
|
+
status = "OK";
|
|
127
|
+
break;
|
|
128
|
+
case 2:
|
|
129
|
+
status = "ERROR";
|
|
130
|
+
break;
|
|
131
|
+
default: status = "UNSET";
|
|
132
|
+
}
|
|
133
|
+
const events = span.events.map((event) => ({
|
|
134
|
+
name: event.name,
|
|
135
|
+
timestamp: event.time[0] * 1e3 + event.time[1] / 1e6,
|
|
136
|
+
attributes: event.attributes ? Object.fromEntries(Object.entries(event.attributes)) : void 0
|
|
137
|
+
}));
|
|
138
|
+
return {
|
|
139
|
+
traceId: spanContext.traceId,
|
|
140
|
+
spanId: spanContext.spanId,
|
|
141
|
+
parentSpanId: span.parentSpanId,
|
|
142
|
+
name: span.name,
|
|
143
|
+
kind: this.convertSpanKind(span.kind),
|
|
144
|
+
startTime,
|
|
145
|
+
endTime,
|
|
146
|
+
duration: endTime - startTime,
|
|
147
|
+
attributes,
|
|
148
|
+
status: {
|
|
149
|
+
code: status,
|
|
150
|
+
message: span.status.message
|
|
151
|
+
},
|
|
152
|
+
events: events.length > 0 ? events : void 0
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
convertSpanKind(kind) {
|
|
156
|
+
switch (kind) {
|
|
157
|
+
case 0: return "INTERNAL";
|
|
158
|
+
case 1: return "SERVER";
|
|
159
|
+
case 2: return "CLIENT";
|
|
160
|
+
case 3: return "PRODUCER";
|
|
161
|
+
case 4: return "CONSUMER";
|
|
162
|
+
default: return "INTERNAL";
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
sleep(ms) {
|
|
166
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
167
|
+
}
|
|
168
|
+
log(message) {
|
|
169
|
+
if (this.options.verbose) console.log(`[Devtools Remote Exporter] ${message}`);
|
|
170
|
+
}
|
|
213
171
|
};
|
|
214
172
|
|
|
173
|
+
//#endregion
|
|
215
174
|
export { DevtoolsRemoteExporter };
|
|
216
|
-
//# sourceMappingURL=remote-exporter.js.map
|
|
217
175
|
//# sourceMappingURL=remote-exporter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/remote-exporter.ts"],"names":[],"mappings":"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","file":"remote-exporter.js","sourcesContent":["/**\n * Remote Span Exporter for sending traces to a hosted DevtoolsServer\n *\n * Use this when the DevtoolsServer is running on a different machine/process.\n *\n * @example\n * ```typescript\n * import { DevtoolsRemoteExporter } from '@autotel/devtools/server';\n * import { init } from 'autotel';\n *\n * init({\n * service: 'my-app',\n * spanExporters: [\n * new DevtoolsRemoteExporter({\n * endpoint: 'https://autotel.mycompany.com',\n * apiKey: process.env.AUTOTEL_API_KEY,\n * })\n * ]\n * });\n * ```\n */\n\nimport type { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base';\nimport type { ExportResult, ExportResultCode } from '@opentelemetry/core';\nimport type { TraceData, SpanData } from './types';\n\nexport interface DevtoolsRemoteExporterOptions {\n /**\n * Base URL of the Devtools server\n * e.g., 'https://autotel.mycompany.com' or 'http://localhost:8082'\n */\n endpoint: string;\n\n /**\n * API key for authentication (if server requires it)\n */\n apiKey?: string;\n\n /**\n * Service name for traces (default: 'unknown-service')\n */\n serviceName?: string;\n\n /**\n * Request timeout in milliseconds (default: 5000)\n */\n timeout?: number;\n\n /**\n * Retry failed requests (default: true)\n */\n retry?: boolean;\n\n /**\n * Number of retries (default: 3)\n */\n retryCount?: number;\n\n /**\n * Retry delay in milliseconds (default: 1000)\n */\n retryDelay?: number;\n\n /**\n * Enable verbose logging (default: false)\n */\n verbose?: boolean;\n}\n\nexport class DevtoolsRemoteExporter implements SpanExporter {\n private options: Required<DevtoolsRemoteExporterOptions>;\n private pendingExports: Promise<void>[] = [];\n\n constructor(options: DevtoolsRemoteExporterOptions) {\n this.options = {\n endpoint: options.endpoint.replace(/\\/$/, ''), // Remove trailing slash\n apiKey: options.apiKey ?? '',\n serviceName: options.serviceName ?? 'unknown-service',\n timeout: options.timeout ?? 5000,\n retry: options.retry ?? true,\n retryCount: options.retryCount ?? 3,\n retryDelay: options.retryDelay ?? 1000,\n verbose: options.verbose ?? false,\n };\n }\n\n /**\n * Export spans to the remote server\n */\n async export(\n spans: ReadableSpan[],\n resultCallback: (result: ExportResult) => void,\n ): Promise<void> {\n // Start export asynchronously\n const exportPromise = this.doExport(spans)\n .then(() => {\n resultCallback({ code: 0 as ExportResultCode }); // SUCCESS\n })\n .catch((error) => {\n this.log(`Export failed: ${error.message}`);\n resultCallback({ code: 1 as ExportResultCode }); // FAILED\n });\n\n this.pendingExports.push(exportPromise);\n\n // Clean up completed exports\n exportPromise.finally(() => {\n const index = this.pendingExports.indexOf(exportPromise);\n if (index !== -1) {\n this.pendingExports.splice(index, 1);\n }\n });\n }\n\n private async doExport(spans: ReadableSpan[]): Promise<void> {\n if (spans.length === 0) return;\n\n this.log(`Exporting ${spans.length} span(s) to ${this.options.endpoint}`);\n\n // Group spans by trace ID and convert\n const traceMap = new Map<string, ReadableSpan[]>();\n for (const span of spans) {\n const traceId = span.spanContext().traceId;\n if (!traceMap.has(traceId)) {\n traceMap.set(traceId, []);\n }\n traceMap.get(traceId)!.push(span);\n }\n\n const traces: TraceData[] = [];\n for (const [traceId, traceSpans] of traceMap) {\n traces.push(this.convertToTraceData(traceId, traceSpans));\n }\n\n // Send with retry\n await this.sendWithRetry({ traces });\n }\n\n private async sendWithRetry(payload: { traces: TraceData[] }): Promise<void> {\n let lastError: Error | null = null;\n const maxAttempts = this.options.retry ? this.options.retryCount : 1;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n await this.send(payload);\n return;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n this.log(\n `Attempt ${attempt}/${maxAttempts} failed: ${lastError.message}`,\n );\n\n if (attempt < maxAttempts) {\n await this.sleep(this.options.retryDelay * attempt); // Exponential backoff\n }\n }\n }\n\n throw lastError || new Error('Export failed');\n }\n\n private async send(payload: { traces: TraceData[] }): Promise<void> {\n const controller = new AbortController();\n const timeoutId = setTimeout(\n () => controller.abort(),\n this.options.timeout,\n );\n\n try {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (this.options.apiKey) {\n headers['Authorization'] = `Bearer ${this.options.apiKey}`;\n }\n\n const response = await fetch(`${this.options.endpoint}/ingest/traces`, {\n method: 'POST',\n headers,\n body: JSON.stringify(payload),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`HTTP ${response.status}: ${text}`);\n }\n\n const result = (await response.json()) as { processed: number };\n this.log(`Successfully sent ${result.processed} trace(s)`);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Shutdown the exporter, waiting for pending exports\n */\n async shutdown(): Promise<void> {\n this.log('Shutting down, waiting for pending exports...');\n await Promise.allSettled(this.pendingExports);\n this.log('Shutdown complete');\n }\n\n /**\n * Force flush pending exports\n */\n async forceFlush(): Promise<void> {\n await Promise.allSettled(this.pendingExports);\n }\n\n private convertToTraceData(\n traceId: string,\n spans: ReadableSpan[],\n ): TraceData {\n const spanData: SpanData[] = spans.map((span) => this.convertSpan(span));\n\n // Find root span (no parent)\n const rootSpan = spanData.find((s) => !s.parentSpanId) || spanData[0];\n\n // Sort spans by start time\n spanData.sort((a, b) => a.startTime - b.startTime);\n\n const startTime = Math.min(...spanData.map((s) => s.startTime));\n const endTime = Math.max(...spanData.map((s) => s.endTime));\n\n const hasError = spanData.some((s) => s.status.code === 'ERROR');\n const status = hasError ? 'ERROR' : 'OK';\n\n return {\n traceId,\n correlationId: traceId.slice(0, 16),\n rootSpan,\n spans: spanData,\n startTime,\n endTime,\n duration: endTime - startTime,\n status: status as 'OK' | 'ERROR' | 'UNSET',\n service: this.options.serviceName,\n };\n }\n\n private convertSpan(span: ReadableSpan): SpanData {\n const spanContext = span.spanContext();\n const startTime = span.startTime[0] * 1000 + span.startTime[1] / 1_000_000;\n const endTime = span.endTime[0] * 1000 + span.endTime[1] / 1_000_000;\n\n const attributes: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(span.attributes)) {\n attributes[key] = value;\n }\n\n let status: 'OK' | 'ERROR' | 'UNSET';\n switch (span.status.code) {\n case 0: {\n status = 'UNSET';\n break;\n }\n case 1: {\n status = 'OK';\n break;\n }\n case 2: {\n status = 'ERROR';\n break;\n }\n default: {\n status = 'UNSET';\n }\n }\n\n const events = span.events.map((event) => ({\n name: event.name,\n timestamp: event.time[0] * 1000 + event.time[1] / 1_000_000,\n attributes: event.attributes\n ? Object.fromEntries(Object.entries(event.attributes))\n : undefined,\n }));\n\n return {\n traceId: spanContext.traceId,\n spanId: spanContext.spanId,\n parentSpanId: (span as any).parentSpanId,\n name: span.name,\n kind: this.convertSpanKind(span.kind),\n startTime,\n endTime,\n duration: endTime - startTime,\n attributes,\n status: {\n code: status,\n message: span.status.message,\n },\n events: events.length > 0 ? events : undefined,\n };\n }\n\n private convertSpanKind(\n kind: number,\n ): 'INTERNAL' | 'SERVER' | 'CLIENT' | 'PRODUCER' | 'CONSUMER' {\n switch (kind) {\n case 0: {\n return 'INTERNAL';\n }\n case 1: {\n return 'SERVER';\n }\n case 2: {\n return 'CLIENT';\n }\n case 3: {\n return 'PRODUCER';\n }\n case 4: {\n return 'CONSUMER';\n }\n default: {\n return 'INTERNAL';\n }\n }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n private log(message: string): void {\n if (this.options.verbose) {\n console.log(`[Devtools Remote Exporter] ${message}`);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"remote-exporter.js","names":[],"sources":["../../src/server/remote-exporter.ts"],"sourcesContent":["/**\n * Remote Span Exporter for sending traces to a hosted DevtoolsServer\n *\n * Use this when the DevtoolsServer is running on a different machine/process.\n *\n * @example\n * ```typescript\n * import { DevtoolsRemoteExporter } from '@autotel/devtools/server';\n * import { init } from 'autotel';\n *\n * init({\n * service: 'my-app',\n * spanExporters: [\n * new DevtoolsRemoteExporter({\n * endpoint: 'https://autotel.mycompany.com',\n * apiKey: process.env.AUTOTEL_API_KEY,\n * })\n * ]\n * });\n * ```\n */\n\nimport type { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base';\nimport type { ExportResult, ExportResultCode } from '@opentelemetry/core';\nimport type { TraceData, SpanData } from './types';\n\nexport interface DevtoolsRemoteExporterOptions {\n /**\n * Base URL of the Devtools server\n * e.g., 'https://autotel.mycompany.com' or 'http://localhost:8082'\n */\n endpoint: string;\n\n /**\n * API key for authentication (if server requires it)\n */\n apiKey?: string;\n\n /**\n * Service name for traces (default: 'unknown-service')\n */\n serviceName?: string;\n\n /**\n * Request timeout in milliseconds (default: 5000)\n */\n timeout?: number;\n\n /**\n * Retry failed requests (default: true)\n */\n retry?: boolean;\n\n /**\n * Number of retries (default: 3)\n */\n retryCount?: number;\n\n /**\n * Retry delay in milliseconds (default: 1000)\n */\n retryDelay?: number;\n\n /**\n * Enable verbose logging (default: false)\n */\n verbose?: boolean;\n}\n\nexport class DevtoolsRemoteExporter implements SpanExporter {\n private options: Required<DevtoolsRemoteExporterOptions>;\n private pendingExports: Promise<void>[] = [];\n\n constructor(options: DevtoolsRemoteExporterOptions) {\n this.options = {\n endpoint: options.endpoint.replace(/\\/$/, ''), // Remove trailing slash\n apiKey: options.apiKey ?? '',\n serviceName: options.serviceName ?? 'unknown-service',\n timeout: options.timeout ?? 5000,\n retry: options.retry ?? true,\n retryCount: options.retryCount ?? 3,\n retryDelay: options.retryDelay ?? 1000,\n verbose: options.verbose ?? false,\n };\n }\n\n /**\n * Export spans to the remote server\n */\n async export(\n spans: ReadableSpan[],\n resultCallback: (result: ExportResult) => void,\n ): Promise<void> {\n // Start export asynchronously\n const exportPromise = this.doExport(spans)\n .then(() => {\n resultCallback({ code: 0 as ExportResultCode }); // SUCCESS\n })\n .catch((error) => {\n this.log(`Export failed: ${error.message}`);\n resultCallback({ code: 1 as ExportResultCode }); // FAILED\n });\n\n this.pendingExports.push(exportPromise);\n\n // Clean up completed exports\n exportPromise.finally(() => {\n const index = this.pendingExports.indexOf(exportPromise);\n if (index !== -1) {\n this.pendingExports.splice(index, 1);\n }\n });\n }\n\n private async doExport(spans: ReadableSpan[]): Promise<void> {\n if (spans.length === 0) return;\n\n this.log(`Exporting ${spans.length} span(s) to ${this.options.endpoint}`);\n\n // Group spans by trace ID and convert\n const traceMap = new Map<string, ReadableSpan[]>();\n for (const span of spans) {\n const traceId = span.spanContext().traceId;\n if (!traceMap.has(traceId)) {\n traceMap.set(traceId, []);\n }\n traceMap.get(traceId)!.push(span);\n }\n\n const traces: TraceData[] = [];\n for (const [traceId, traceSpans] of traceMap) {\n traces.push(this.convertToTraceData(traceId, traceSpans));\n }\n\n // Send with retry\n await this.sendWithRetry({ traces });\n }\n\n private async sendWithRetry(payload: { traces: TraceData[] }): Promise<void> {\n let lastError: Error | null = null;\n const maxAttempts = this.options.retry ? this.options.retryCount : 1;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n await this.send(payload);\n return;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n this.log(\n `Attempt ${attempt}/${maxAttempts} failed: ${lastError.message}`,\n );\n\n if (attempt < maxAttempts) {\n await this.sleep(this.options.retryDelay * attempt); // Exponential backoff\n }\n }\n }\n\n throw lastError || new Error('Export failed');\n }\n\n private async send(payload: { traces: TraceData[] }): Promise<void> {\n const controller = new AbortController();\n const timeoutId = setTimeout(\n () => controller.abort(),\n this.options.timeout,\n );\n\n try {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (this.options.apiKey) {\n headers['Authorization'] = `Bearer ${this.options.apiKey}`;\n }\n\n const response = await fetch(`${this.options.endpoint}/ingest/traces`, {\n method: 'POST',\n headers,\n body: JSON.stringify(payload),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`HTTP ${response.status}: ${text}`);\n }\n\n const result = (await response.json()) as { processed: number };\n this.log(`Successfully sent ${result.processed} trace(s)`);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Shutdown the exporter, waiting for pending exports\n */\n async shutdown(): Promise<void> {\n this.log('Shutting down, waiting for pending exports...');\n await Promise.allSettled(this.pendingExports);\n this.log('Shutdown complete');\n }\n\n /**\n * Force flush pending exports\n */\n async forceFlush(): Promise<void> {\n await Promise.allSettled(this.pendingExports);\n }\n\n private convertToTraceData(\n traceId: string,\n spans: ReadableSpan[],\n ): TraceData {\n const spanData: SpanData[] = spans.map((span) => this.convertSpan(span));\n\n // Find root span (no parent)\n const rootSpan = spanData.find((s) => !s.parentSpanId) || spanData[0];\n\n // Sort spans by start time\n spanData.sort((a, b) => a.startTime - b.startTime);\n\n const startTime = Math.min(...spanData.map((s) => s.startTime));\n const endTime = Math.max(...spanData.map((s) => s.endTime));\n\n const hasError = spanData.some((s) => s.status.code === 'ERROR');\n const status = hasError ? 'ERROR' : 'OK';\n\n return {\n traceId,\n correlationId: traceId.slice(0, 16),\n rootSpan,\n spans: spanData,\n startTime,\n endTime,\n duration: endTime - startTime,\n status: status as 'OK' | 'ERROR' | 'UNSET',\n service: this.options.serviceName,\n };\n }\n\n private convertSpan(span: ReadableSpan): SpanData {\n const spanContext = span.spanContext();\n const startTime = span.startTime[0] * 1000 + span.startTime[1] / 1_000_000;\n const endTime = span.endTime[0] * 1000 + span.endTime[1] / 1_000_000;\n\n const attributes: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(span.attributes)) {\n attributes[key] = value;\n }\n\n let status: 'OK' | 'ERROR' | 'UNSET';\n switch (span.status.code) {\n case 0: {\n status = 'UNSET';\n break;\n }\n case 1: {\n status = 'OK';\n break;\n }\n case 2: {\n status = 'ERROR';\n break;\n }\n default: {\n status = 'UNSET';\n }\n }\n\n const events = span.events.map((event) => ({\n name: event.name,\n timestamp: event.time[0] * 1000 + event.time[1] / 1_000_000,\n attributes: event.attributes\n ? Object.fromEntries(Object.entries(event.attributes))\n : undefined,\n }));\n\n return {\n traceId: spanContext.traceId,\n spanId: spanContext.spanId,\n parentSpanId: (span as any).parentSpanId,\n name: span.name,\n kind: this.convertSpanKind(span.kind),\n startTime,\n endTime,\n duration: endTime - startTime,\n attributes,\n status: {\n code: status,\n message: span.status.message,\n },\n events: events.length > 0 ? events : undefined,\n };\n }\n\n private convertSpanKind(\n kind: number,\n ): 'INTERNAL' | 'SERVER' | 'CLIENT' | 'PRODUCER' | 'CONSUMER' {\n switch (kind) {\n case 0: {\n return 'INTERNAL';\n }\n case 1: {\n return 'SERVER';\n }\n case 2: {\n return 'CLIENT';\n }\n case 3: {\n return 'PRODUCER';\n }\n case 4: {\n return 'CONSUMER';\n }\n default: {\n return 'INTERNAL';\n }\n }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n private log(message: string): void {\n if (this.options.verbose) {\n console.log(`[Devtools Remote Exporter] ${message}`);\n }\n }\n}\n"],"mappings":"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"}
|