autotel-devtools 8.1.1 → 10.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.
Files changed (63) hide show
  1. package/dist/cli.cjs +108 -1429
  2. package/dist/cli.cjs.map +1 -1
  3. package/dist/cli.d.cts +1 -1
  4. package/dist/cli.d.ts +1 -1
  5. package/dist/cli.js +109 -1422
  6. package/dist/cli.js.map +1 -1
  7. package/dist/error-aggregator-BvNmgn7E.d.ts +120 -0
  8. package/dist/error-aggregator-nnfbpSR7.d.cts +120 -0
  9. package/dist/exporter-1Y3GmLVS.d.cts +182 -0
  10. package/dist/exporter-CZ5HdD3o.d.ts +182 -0
  11. package/dist/genai/index.cjs +650 -537
  12. package/dist/genai/index.cjs.map +1 -1
  13. package/dist/genai/index.d.cts +164 -157
  14. package/dist/genai/index.d.ts +164 -157
  15. package/dist/genai/index.js +649 -536
  16. package/dist/genai/index.js.map +1 -1
  17. package/dist/http-BkkKa9C_.js +1128 -0
  18. package/dist/http-BkkKa9C_.js.map +1 -0
  19. package/dist/http-Yj6iSrMX.cjs +1275 -0
  20. package/dist/http-Yj6iSrMX.cjs.map +1 -0
  21. package/dist/index.cjs +50 -1728
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +21 -23
  24. package/dist/index.d.ts +21 -23
  25. package/dist/index.js +44 -1716
  26. package/dist/index.js.map +1 -1
  27. package/dist/listen-BBsxO0wm.cjs +125 -0
  28. package/dist/listen-BBsxO0wm.cjs.map +1 -0
  29. package/dist/listen-DfOCquUq.js +120 -0
  30. package/dist/listen-DfOCquUq.js.map +1 -0
  31. package/dist/resource-utils-B4UVvfnH.js +18 -0
  32. package/dist/resource-utils-B4UVvfnH.js.map +1 -0
  33. package/dist/resource-utils-DjHJB6uc.cjs +24 -0
  34. package/dist/resource-utils-DjHJB6uc.cjs.map +1 -0
  35. package/dist/server/exporter.cjs +135 -159
  36. package/dist/server/exporter.cjs.map +1 -1
  37. package/dist/server/exporter.d.cts +2 -4
  38. package/dist/server/exporter.d.ts +2 -4
  39. package/dist/server/exporter.js +134 -158
  40. package/dist/server/exporter.js.map +1 -1
  41. package/dist/server/index.cjs +29 -1660
  42. package/dist/server/index.d.cts +34 -31
  43. package/dist/server/index.d.ts +34 -31
  44. package/dist/server/index.js +5 -1630
  45. package/dist/server/log-exporter.cjs +75 -102
  46. package/dist/server/log-exporter.cjs.map +1 -1
  47. package/dist/server/log-exporter.d.cts +27 -46
  48. package/dist/server/log-exporter.d.ts +27 -46
  49. package/dist/server/log-exporter.js +74 -100
  50. package/dist/server/log-exporter.js.map +1 -1
  51. package/dist/server/remote-exporter.cjs +171 -213
  52. package/dist/server/remote-exporter.cjs.map +1 -1
  53. package/dist/server/remote-exporter.d.cts +62 -82
  54. package/dist/server/remote-exporter.d.ts +62 -82
  55. package/dist/server/remote-exporter.js +170 -212
  56. package/dist/server/remote-exporter.js.map +1 -1
  57. package/package.json +5 -5
  58. package/dist/error-aggregator-D0Uu5r38.d.ts +0 -147
  59. package/dist/error-aggregator-D1Mr221Y.d.cts +0 -147
  60. package/dist/exporter-De6p4iAD.d.cts +0 -182
  61. package/dist/exporter-De6p4iAD.d.ts +0 -182
  62. package/dist/server/index.cjs.map +0 -1
  63. package/dist/server/index.js.map +0 -1
@@ -1,87 +1,67 @@
1
- import { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';
2
- import { ExportResult } from '@opentelemetry/core';
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
- * Base URL of the Devtools server
29
- * e.g., 'https://autotel.mycompany.com' or 'http://localhost:8082'
30
- */
31
- endpoint: string;
32
- /**
33
- * API key for authentication (if server requires it)
34
- */
35
- apiKey?: string;
36
- /**
37
- * Service name for traces (default: 'unknown-service')
38
- */
39
- serviceName?: string;
40
- /**
41
- * Request timeout in milliseconds (default: 5000)
42
- */
43
- timeout?: number;
44
- /**
45
- * Retry failed requests (default: true)
46
- */
47
- retry?: boolean;
48
- /**
49
- * Number of retries (default: 3)
50
- */
51
- retryCount?: number;
52
- /**
53
- * Retry delay in milliseconds (default: 1000)
54
- */
55
- retryDelay?: number;
56
- /**
57
- * Enable verbose logging (default: false)
58
- */
59
- verbose?: boolean;
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
- private options;
63
- private pendingExports;
64
- constructor(options: DevtoolsRemoteExporterOptions);
65
- /**
66
- * Export spans to the remote server
67
- */
68
- export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): Promise<void>;
69
- private doExport;
70
- private sendWithRetry;
71
- private send;
72
- /**
73
- * Shutdown the exporter, waiting for pending exports
74
- */
75
- shutdown(): Promise<void>;
76
- /**
77
- * Force flush pending exports
78
- */
79
- forceFlush(): Promise<void>;
80
- private convertToTraceData;
81
- private convertSpan;
82
- private convertSpanKind;
83
- private sleep;
84
- private log;
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, type DevtoolsRemoteExporterOptions };
65
+ //#endregion
66
+ export { DevtoolsRemoteExporter, DevtoolsRemoteExporterOptions };
67
+ //# sourceMappingURL=remote-exporter.d.ts.map
@@ -1,217 +1,175 @@
1
- // src/server/remote-exporter.ts
1
+ //#region src/server/remote-exporter.ts
2
2
  var DevtoolsRemoteExporter = class {
3
- options;
4
- pendingExports = [];
5
- constructor(options) {
6
- this.options = {
7
- endpoint: options.endpoint.replace(/\/$/, ""),
8
- // Remove trailing slash
9
- apiKey: options.apiKey ?? "",
10
- serviceName: options.serviceName ?? "unknown-service",
11
- timeout: options.timeout ?? 5e3,
12
- retry: options.retry ?? true,
13
- retryCount: options.retryCount ?? 3,
14
- retryDelay: options.retryDelay ?? 1e3,
15
- verbose: options.verbose ?? false
16
- };
17
- }
18
- /**
19
- * Export spans to the remote server
20
- */
21
- async export(spans, resultCallback) {
22
- const exportPromise = this.doExport(spans).then(() => {
23
- resultCallback({ code: 0 });
24
- }).catch((error) => {
25
- this.log(`Export failed: ${error.message}`);
26
- resultCallback({ code: 1 });
27
- });
28
- this.pendingExports.push(exportPromise);
29
- exportPromise.finally(() => {
30
- const index = this.pendingExports.indexOf(exportPromise);
31
- if (index !== -1) {
32
- this.pendingExports.splice(index, 1);
33
- }
34
- });
35
- }
36
- async doExport(spans) {
37
- if (spans.length === 0) return;
38
- this.log(`Exporting ${spans.length} span(s) to ${this.options.endpoint}`);
39
- const traceMap = /* @__PURE__ */ new Map();
40
- for (const span of spans) {
41
- const traceId = span.spanContext().traceId;
42
- if (!traceMap.has(traceId)) {
43
- traceMap.set(traceId, []);
44
- }
45
- traceMap.get(traceId).push(span);
46
- }
47
- const traces = [];
48
- for (const [traceId, traceSpans] of traceMap) {
49
- traces.push(this.convertToTraceData(traceId, traceSpans));
50
- }
51
- await this.sendWithRetry({ traces });
52
- }
53
- async sendWithRetry(payload) {
54
- let lastError = null;
55
- const maxAttempts = this.options.retry ? this.options.retryCount : 1;
56
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
57
- try {
58
- await this.send(payload);
59
- return;
60
- } catch (error) {
61
- lastError = error instanceof Error ? error : new Error(String(error));
62
- this.log(
63
- `Attempt ${attempt}/${maxAttempts} failed: ${lastError.message}`
64
- );
65
- if (attempt < maxAttempts) {
66
- await this.sleep(this.options.retryDelay * attempt);
67
- }
68
- }
69
- }
70
- throw lastError || new Error("Export failed");
71
- }
72
- async send(payload) {
73
- const controller = new AbortController();
74
- const timeoutId = setTimeout(
75
- () => controller.abort(),
76
- this.options.timeout
77
- );
78
- try {
79
- const headers = {
80
- "Content-Type": "application/json"
81
- };
82
- if (this.options.apiKey) {
83
- headers["Authorization"] = `Bearer ${this.options.apiKey}`;
84
- }
85
- const response = await fetch(`${this.options.endpoint}/ingest/traces`, {
86
- method: "POST",
87
- headers,
88
- body: JSON.stringify(payload),
89
- signal: controller.signal
90
- });
91
- if (!response.ok) {
92
- const text = await response.text();
93
- throw new Error(`HTTP ${response.status}: ${text}`);
94
- }
95
- const result = await response.json();
96
- this.log(`Successfully sent ${result.processed} trace(s)`);
97
- } finally {
98
- clearTimeout(timeoutId);
99
- }
100
- }
101
- /**
102
- * Shutdown the exporter, waiting for pending exports
103
- */
104
- async shutdown() {
105
- this.log("Shutting down, waiting for pending exports...");
106
- await Promise.allSettled(this.pendingExports);
107
- this.log("Shutdown complete");
108
- }
109
- /**
110
- * Force flush pending exports
111
- */
112
- async forceFlush() {
113
- await Promise.allSettled(this.pendingExports);
114
- }
115
- convertToTraceData(traceId, spans) {
116
- const spanData = spans.map((span) => this.convertSpan(span));
117
- const rootSpan = spanData.find((s) => !s.parentSpanId) || spanData[0];
118
- spanData.sort((a, b) => a.startTime - b.startTime);
119
- const startTime = Math.min(...spanData.map((s) => s.startTime));
120
- const endTime = Math.max(...spanData.map((s) => s.endTime));
121
- const hasError = spanData.some((s) => s.status.code === "ERROR");
122
- const status = hasError ? "ERROR" : "OK";
123
- return {
124
- traceId,
125
- correlationId: traceId.slice(0, 16),
126
- rootSpan,
127
- spans: spanData,
128
- startTime,
129
- endTime,
130
- duration: endTime - startTime,
131
- status,
132
- service: this.options.serviceName
133
- };
134
- }
135
- convertSpan(span) {
136
- const spanContext = span.spanContext();
137
- const startTime = span.startTime[0] * 1e3 + span.startTime[1] / 1e6;
138
- const endTime = span.endTime[0] * 1e3 + span.endTime[1] / 1e6;
139
- const attributes = {};
140
- for (const [key, value] of Object.entries(span.attributes)) {
141
- attributes[key] = value;
142
- }
143
- let status;
144
- switch (span.status.code) {
145
- case 0: {
146
- status = "UNSET";
147
- break;
148
- }
149
- case 1: {
150
- status = "OK";
151
- break;
152
- }
153
- case 2: {
154
- status = "ERROR";
155
- break;
156
- }
157
- default: {
158
- status = "UNSET";
159
- }
160
- }
161
- const events = span.events.map((event) => ({
162
- name: event.name,
163
- timestamp: event.time[0] * 1e3 + event.time[1] / 1e6,
164
- attributes: event.attributes ? Object.fromEntries(Object.entries(event.attributes)) : void 0
165
- }));
166
- return {
167
- traceId: spanContext.traceId,
168
- spanId: spanContext.spanId,
169
- parentSpanId: span.parentSpanId,
170
- name: span.name,
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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autotel-devtools",
3
- "version": "8.1.1",
3
+ "version": "10.0.0",
4
4
  "description": "Standalone OTLP receiver with web UI for local development",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -82,7 +82,7 @@
82
82
  "storybook": "^10.4.2",
83
83
  "svelte-check": "^4.6.0",
84
84
  "tailwindcss": "^4.3.0",
85
- "tsup": "^8.5.1",
85
+ "tsdown": "^0.22.2",
86
86
  "typescript": "^6.0.3",
87
87
  "vite": "^8.0.16",
88
88
  "vitest": "^4.1.8",
@@ -90,7 +90,7 @@
90
90
  "vitest-mock-extended": "^4.0.0"
91
91
  },
92
92
  "peerDependencies": {
93
- "autotel": "3.6.0"
93
+ "autotel": "4.0.0"
94
94
  },
95
95
  "peerDependenciesMeta": {
96
96
  "autotel": {
@@ -104,8 +104,8 @@
104
104
  },
105
105
  "license": "MIT",
106
106
  "scripts": {
107
- "build": "tsup && vite build --config vite.widget.config.ts",
108
- "dev": "tsup --watch",
107
+ "build": "tsdown && vite build --config vite.widget.config.ts",
108
+ "dev": "tsdown --watch",
109
109
  "type-check": "svelte-check --tsconfig ./tsconfig.json && tsc --noEmit",
110
110
  "lint": "eslint src/",
111
111
  "test": "vitest run --config vitest.config.ts --project server && vitest run --config vitest.config.ts --project widget && vitest run --config vitest.storybook.config.ts",