@hue-run/sdk 0.21.1 → 0.22.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/CLI.md +246 -52
- package/README.md +54 -13
- package/WORLDS.md +6 -3
- package/dist/ai-sdk.d.ts +23 -1
- package/dist/ai-sdk.js +60 -2
- package/dist/cli/import-formats/adapter.d.ts +80 -0
- package/dist/cli/import-formats/adapter.js +1 -0
- package/dist/cli/import-formats/agent-events.d.ts +5 -0
- package/dist/cli/import-formats/agent-events.js +16 -0
- package/dist/cli/import-formats/atif.d.ts +3 -0
- package/dist/cli/import-formats/atif.js +15 -0
- package/dist/cli/import-formats/braintrust.d.ts +2 -0
- package/dist/cli/import-formats/braintrust.js +222 -0
- package/dist/cli/import-formats/containers.d.ts +32 -0
- package/dist/cli/import-formats/containers.js +399 -0
- package/dist/cli/import-formats/datadog.d.ts +2 -0
- package/dist/cli/import-formats/datadog.js +264 -0
- package/dist/cli/import-formats/helicone.d.ts +2 -0
- package/dist/cli/import-formats/helicone.js +161 -0
- package/dist/cli/import-formats/identity.d.ts +112 -0
- package/dist/cli/import-formats/identity.js +594 -0
- package/dist/cli/import-formats/jaeger.d.ts +3 -0
- package/dist/cli/import-formats/jaeger.js +266 -0
- package/dist/cli/import-formats/langfuse.d.ts +7 -0
- package/dist/cli/import-formats/langfuse.js +510 -0
- package/dist/cli/import-formats/langsmith.d.ts +2 -0
- package/dist/cli/import-formats/langsmith.js +244 -0
- package/dist/cli/import-formats/messages.d.ts +28 -0
- package/dist/cli/import-formats/messages.js +301 -0
- package/dist/cli/import-formats/mlflow.d.ts +2 -0
- package/dist/cli/import-formats/mlflow.js +260 -0
- package/dist/cli/import-formats/openai-agents.d.ts +2 -0
- package/dist/cli/import-formats/openai-agents.js +221 -0
- package/dist/cli/import-formats/opik.d.ts +2 -0
- package/dist/cli/import-formats/opik.js +193 -0
- package/dist/cli/import-formats/otel-family.d.ts +5 -0
- package/dist/cli/import-formats/otel-family.js +17 -0
- package/dist/cli/import-formats/otel-spans.d.ts +94 -0
- package/dist/cli/import-formats/otel-spans.js +262 -0
- package/dist/cli/import-formats/otlp-protobuf.d.ts +35 -0
- package/dist/cli/import-formats/otlp-protobuf.js +486 -0
- package/dist/cli/import-formats/otlp.d.ts +4 -0
- package/dist/cli/import-formats/otlp.js +15 -0
- package/dist/cli/import-formats/phoenix.d.ts +3 -0
- package/dist/cli/import-formats/phoenix.js +256 -0
- package/dist/cli/import-formats/raindrop.d.ts +21 -0
- package/dist/cli/import-formats/raindrop.js +255 -0
- package/dist/cli/import-formats/reader.d.ts +56 -0
- package/dist/cli/import-formats/reader.js +364 -0
- package/dist/cli/import-formats/registry.d.ts +36 -0
- package/dist/cli/import-formats/registry.js +71 -0
- package/dist/cli/import-formats/row-platform-fields.d.ts +32 -0
- package/dist/cli/import-formats/row-platform-fields.js +76 -0
- package/dist/cli/import-formats/rows.d.ts +165 -0
- package/dist/cli/import-formats/rows.js +638 -0
- package/dist/cli/import-formats/transcripts.d.ts +25 -0
- package/dist/cli/import-formats/transcripts.js +498 -0
- package/dist/cli/import-formats/weave.d.ts +2 -0
- package/dist/cli/import-formats/weave.js +159 -0
- package/dist/cli/import-formats/zipkin.d.ts +2 -0
- package/dist/cli/import-formats/zipkin.js +219 -0
- package/dist/cli/import.d.ts +1 -1
- package/dist/cli/import.js +4 -2
- package/dist/cli/traces-import-convert.d.ts +65 -33
- package/dist/cli/traces-import-convert.js +211 -214
- package/dist/cli/traces-import-prepare.d.ts +26 -22
- package/dist/cli/traces-import-prepare.js +51 -122
- package/dist/cli/traces-import.d.ts +4 -4
- package/dist/cli/traces-import.js +63 -11
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import * as zlib from "node:zlib";
|
|
2
|
+
import { ConversionError, isObject, OTLP_SPANS, OtlpTraceCollector, parseJsonText, } from "../traces-import-convert.js";
|
|
3
|
+
/**
|
|
4
|
+
* Binary OTLP trace files: one protobuf `ExportTraceServiceRequest` (an OTLP/HTTP request body,
|
|
5
|
+
* a `.pb` or `.binpb` file), and the OpenTelemetry Collector file exporter's length-prefixed
|
|
6
|
+
* output (`format: proto`, or `compression: zstd`, each message preceded by its size as a 4-byte
|
|
7
|
+
* big-endian integer). A zstd-compressed message is decompressed first; a message that is JSON
|
|
8
|
+
* inside (`format: json` with `compression: zstd`) is read as OTLP/JSON. A collector file written
|
|
9
|
+
* as one zstd stream is undone by the containers before this reads it.
|
|
10
|
+
*
|
|
11
|
+
* The reader below decodes the OTLP trace messages the files hold (opentelemetry-proto v1) into
|
|
12
|
+
* their OTLP/JSON form, ids as hex and 64-bit integers as decimal text, and the spans are then
|
|
13
|
+
* kept as recorded, exactly as an OTLP/JSON export's are. It is strict: a span with a field of the
|
|
14
|
+
* wrong wire type, a truncated field or text that is not UTF-8 refuses its trace (the other traces
|
|
15
|
+
* of the message are read), and damage outside any span makes the message unreadable, that message
|
|
16
|
+
* alone reported. It is written here because the package carries no protobuf runtime.
|
|
17
|
+
*
|
|
18
|
+
* The messages of a length-prefixed file are read one at a time, and their decompressed sizes
|
|
19
|
+
* together, those of messages that then do not read included, are bounded as one file's are.
|
|
20
|
+
*/
|
|
21
|
+
/** Marks a value decoded from protobuf: no JSON document can carry a symbol key. */
|
|
22
|
+
const PROTOBUF = Symbol("otlp-protobuf");
|
|
23
|
+
const MAX_DEPTH = 64;
|
|
24
|
+
const utf8 = new TextDecoder("utf-8", { fatal: true });
|
|
25
|
+
class Reader {
|
|
26
|
+
bytes;
|
|
27
|
+
end;
|
|
28
|
+
pos;
|
|
29
|
+
constructor(bytes, start = 0, end = bytes.byteLength) {
|
|
30
|
+
this.bytes = bytes;
|
|
31
|
+
this.end = end;
|
|
32
|
+
this.pos = start;
|
|
33
|
+
}
|
|
34
|
+
get done() {
|
|
35
|
+
return this.pos >= this.end;
|
|
36
|
+
}
|
|
37
|
+
varint() {
|
|
38
|
+
let result = 0n;
|
|
39
|
+
for (let shift = 0n; shift < 70n; shift += 7n) {
|
|
40
|
+
if (this.pos >= this.end)
|
|
41
|
+
throw new ConversionError("a truncated protobuf varint");
|
|
42
|
+
const byte = this.bytes[this.pos++];
|
|
43
|
+
result |= BigInt(byte & 0x7f) << shift;
|
|
44
|
+
if (!(byte & 0x80))
|
|
45
|
+
return result & 0xffffffffffffffffn;
|
|
46
|
+
}
|
|
47
|
+
throw new ConversionError("a protobuf varint over ten bytes");
|
|
48
|
+
}
|
|
49
|
+
/** A varint that must fit a length or a small number. */
|
|
50
|
+
small() {
|
|
51
|
+
const value = this.varint();
|
|
52
|
+
if (value > BigInt(Number.MAX_SAFE_INTEGER))
|
|
53
|
+
throw new ConversionError("a protobuf length too large");
|
|
54
|
+
return Number(value);
|
|
55
|
+
}
|
|
56
|
+
fixed(size) {
|
|
57
|
+
if (this.pos + size > this.end)
|
|
58
|
+
throw new ConversionError("a truncated protobuf field");
|
|
59
|
+
const view = new DataView(this.bytes.buffer, this.bytes.byteOffset + this.pos, size);
|
|
60
|
+
this.pos += size;
|
|
61
|
+
return view;
|
|
62
|
+
}
|
|
63
|
+
/** A length-delimited field as its own reader. */
|
|
64
|
+
sub() {
|
|
65
|
+
const length = this.small();
|
|
66
|
+
if (this.pos + length > this.end)
|
|
67
|
+
throw new ConversionError("a truncated protobuf field");
|
|
68
|
+
const reader = new Reader(this.bytes, this.pos, this.pos + length);
|
|
69
|
+
this.pos += length;
|
|
70
|
+
return reader;
|
|
71
|
+
}
|
|
72
|
+
bytesField() {
|
|
73
|
+
const sub = this.sub();
|
|
74
|
+
return this.bytes.subarray(sub.pos, sub.end);
|
|
75
|
+
}
|
|
76
|
+
text() {
|
|
77
|
+
try {
|
|
78
|
+
return utf8.decode(this.bytesField());
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (error instanceof ConversionError)
|
|
82
|
+
throw error;
|
|
83
|
+
throw new ConversionError("protobuf text that is not UTF-8");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
skip(wire) {
|
|
87
|
+
if (wire === 0)
|
|
88
|
+
this.varint();
|
|
89
|
+
else if (wire === 1)
|
|
90
|
+
this.fixed(8);
|
|
91
|
+
else if (wire === 2)
|
|
92
|
+
this.sub();
|
|
93
|
+
else if (wire === 5)
|
|
94
|
+
this.fixed(4);
|
|
95
|
+
else
|
|
96
|
+
throw new ConversionError(`a protobuf field of wire type ${wire}`);
|
|
97
|
+
}
|
|
98
|
+
/** Reads every field, handing known field numbers to `read` with the wire type they require. */
|
|
99
|
+
fields(read) {
|
|
100
|
+
while (!this.done) {
|
|
101
|
+
const tag = this.small();
|
|
102
|
+
const field = Math.floor(tag / 8);
|
|
103
|
+
const wire = tag % 8;
|
|
104
|
+
if (field === 0)
|
|
105
|
+
throw new ConversionError("a protobuf field numbered zero");
|
|
106
|
+
const known = read[field];
|
|
107
|
+
if (!known)
|
|
108
|
+
this.skip(wire);
|
|
109
|
+
else if (known[0] !== wire)
|
|
110
|
+
throw new ConversionError(`protobuf field ${field} of the wrong wire type`);
|
|
111
|
+
else
|
|
112
|
+
known[1](this);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const hex = (bytes) => Buffer.from(bytes).toString("hex");
|
|
117
|
+
const signed = (value) => (value >= 0x8000000000000000n ? value - 0x10000000000000000n : value).toString();
|
|
118
|
+
function anyValue(reader, depth) {
|
|
119
|
+
if (depth > MAX_DEPTH)
|
|
120
|
+
throw new ConversionError("protobuf values nested too deep");
|
|
121
|
+
let value = {};
|
|
122
|
+
reader.fields({
|
|
123
|
+
1: [2, (r) => (value = { stringValue: r.text() })],
|
|
124
|
+
2: [0, (r) => (value = { boolValue: r.varint() !== 0n })],
|
|
125
|
+
3: [0, (r) => (value = { intValue: signed(r.varint()) })],
|
|
126
|
+
4: [1, (r) => (value = { doubleValue: r.fixed(8).getFloat64(0, true) })],
|
|
127
|
+
5: [
|
|
128
|
+
2,
|
|
129
|
+
(r) => {
|
|
130
|
+
const values = [];
|
|
131
|
+
r.sub().fields({ 1: [2, (item) => values.push(anyValue(item.sub(), depth + 1))] });
|
|
132
|
+
value = { arrayValue: { values } };
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
6: [
|
|
136
|
+
2,
|
|
137
|
+
(r) => {
|
|
138
|
+
const values = [];
|
|
139
|
+
r.sub().fields({ 1: [2, (item) => values.push(keyValue(item.sub(), depth + 1))] });
|
|
140
|
+
value = { kvlistValue: { values } };
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
7: [2, (r) => (value = { bytesValue: Buffer.from(r.bytesField()).toString("base64") })],
|
|
144
|
+
});
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
function keyValue(reader, depth = 0) {
|
|
148
|
+
let key = "";
|
|
149
|
+
let value = {};
|
|
150
|
+
reader.fields({
|
|
151
|
+
1: [2, (r) => (key = r.text())],
|
|
152
|
+
2: [2, (r) => (value = anyValue(r.sub(), depth + 1))],
|
|
153
|
+
});
|
|
154
|
+
return { key, value };
|
|
155
|
+
}
|
|
156
|
+
const attributeField = (into) => [
|
|
157
|
+
2,
|
|
158
|
+
(r) => into.push(keyValue(r.sub())),
|
|
159
|
+
];
|
|
160
|
+
/** Reads fields into an object, leaving out fields the message did not hold, empty lists
|
|
161
|
+
* included, as OTLP/JSON encoders do. */
|
|
162
|
+
function message(reader, read) {
|
|
163
|
+
const out = {};
|
|
164
|
+
reader.fields(read(out));
|
|
165
|
+
for (const [key, value] of Object.entries(out))
|
|
166
|
+
if (Array.isArray(value) && !value.length)
|
|
167
|
+
delete out[key];
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
const fixed64 = (r) => r.fixed(8).getBigUint64(0, true).toString();
|
|
171
|
+
const uint32 = (r) => Number(r.varint() & 0xffffffffn);
|
|
172
|
+
function attributesInto(out, field) {
|
|
173
|
+
const list = [];
|
|
174
|
+
out[field] = list;
|
|
175
|
+
return list;
|
|
176
|
+
}
|
|
177
|
+
function span(reader) {
|
|
178
|
+
return message(reader, (out) => {
|
|
179
|
+
const attributes = attributesInto(out, "attributes");
|
|
180
|
+
const events = [];
|
|
181
|
+
const links = [];
|
|
182
|
+
out.events = events;
|
|
183
|
+
out.links = links;
|
|
184
|
+
return {
|
|
185
|
+
1: [2, (r) => (out.traceId = hex(r.bytesField()))],
|
|
186
|
+
2: [2, (r) => (out.spanId = hex(r.bytesField()))],
|
|
187
|
+
3: [2, (r) => (out.traceState = r.text())],
|
|
188
|
+
4: [
|
|
189
|
+
2,
|
|
190
|
+
(r) => {
|
|
191
|
+
const parent = hex(r.bytesField());
|
|
192
|
+
if (parent)
|
|
193
|
+
out.parentSpanId = parent;
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
5: [2, (r) => (out.name = r.text())],
|
|
197
|
+
6: [0, (r) => (out.kind = uint32(r))],
|
|
198
|
+
7: [1, (r) => (out.startTimeUnixNano = fixed64(r))],
|
|
199
|
+
8: [1, (r) => (out.endTimeUnixNano = fixed64(r))],
|
|
200
|
+
9: attributeField(attributes),
|
|
201
|
+
10: [0, (r) => (out.droppedAttributesCount = uint32(r))],
|
|
202
|
+
11: [
|
|
203
|
+
2,
|
|
204
|
+
(r) => events.push(message(r.sub(), (event) => {
|
|
205
|
+
const list = attributesInto(event, "attributes");
|
|
206
|
+
return {
|
|
207
|
+
1: [1, (item) => (event.timeUnixNano = fixed64(item))],
|
|
208
|
+
2: [2, (item) => (event.name = item.text())],
|
|
209
|
+
3: attributeField(list),
|
|
210
|
+
4: [0, (item) => (event.droppedAttributesCount = uint32(item))],
|
|
211
|
+
};
|
|
212
|
+
})),
|
|
213
|
+
],
|
|
214
|
+
12: [0, (r) => (out.droppedEventsCount = uint32(r))],
|
|
215
|
+
13: [
|
|
216
|
+
2,
|
|
217
|
+
(r) => links.push(message(r.sub(), (link) => {
|
|
218
|
+
const list = attributesInto(link, "attributes");
|
|
219
|
+
return {
|
|
220
|
+
1: [2, (item) => (link.traceId = hex(item.bytesField()))],
|
|
221
|
+
2: [2, (item) => (link.spanId = hex(item.bytesField()))],
|
|
222
|
+
3: [2, (item) => (link.traceState = item.text())],
|
|
223
|
+
4: attributeField(list),
|
|
224
|
+
5: [0, (item) => (link.droppedAttributesCount = uint32(item))],
|
|
225
|
+
6: [5, (item) => (link.flags = item.fixed(4).getUint32(0, true))],
|
|
226
|
+
};
|
|
227
|
+
})),
|
|
228
|
+
],
|
|
229
|
+
14: [0, (r) => (out.droppedLinksCount = uint32(r))],
|
|
230
|
+
15: [
|
|
231
|
+
2,
|
|
232
|
+
(r) => (out.status = message(r.sub(), (status) => ({
|
|
233
|
+
2: [2, (item) => (status.message = item.text())],
|
|
234
|
+
3: [0, (item) => (status.code = uint32(item))],
|
|
235
|
+
}))),
|
|
236
|
+
],
|
|
237
|
+
16: [5, (r) => (out.flags = r.fixed(4).getUint32(0, true))],
|
|
238
|
+
};
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/** A span the message holds, or why it cannot be read and the trace it names when its trace id
|
|
242
|
+
* was read before the damage. */
|
|
243
|
+
function readSpan(reader, index, refused) {
|
|
244
|
+
const start = reader.pos;
|
|
245
|
+
try {
|
|
246
|
+
return span(reader);
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
if (!(error instanceof ConversionError))
|
|
250
|
+
throw error;
|
|
251
|
+
let traceId;
|
|
252
|
+
try {
|
|
253
|
+
new Reader(reader.bytes, start, reader.end).fields({
|
|
254
|
+
1: [2, (field) => (traceId ??= hex(field.bytesField()))],
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
// The trace id came before the damage, or the span names none.
|
|
259
|
+
}
|
|
260
|
+
refused.push({
|
|
261
|
+
...(traceId ? { traceId } : {}),
|
|
262
|
+
reason: `span ${index} is not an OTLP span: ${error.message}`,
|
|
263
|
+
});
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/** A `TracesData` or `ExportTraceServiceRequest` (the same fields) as OTLP/JSON, with the spans
|
|
268
|
+
* that could not be read. */
|
|
269
|
+
export function decodeTraces(bytes) {
|
|
270
|
+
const resourceSpans = [];
|
|
271
|
+
const refused = [];
|
|
272
|
+
let spanCount = 0;
|
|
273
|
+
new Reader(bytes).fields({
|
|
274
|
+
1: [
|
|
275
|
+
2,
|
|
276
|
+
(r) => resourceSpans.push(message(r.sub(), (group) => {
|
|
277
|
+
const scopeSpans = [];
|
|
278
|
+
group.scopeSpans = scopeSpans;
|
|
279
|
+
return {
|
|
280
|
+
1: [
|
|
281
|
+
2,
|
|
282
|
+
(item) => (group.resource = message(item.sub(), (resource) => {
|
|
283
|
+
const list = attributesInto(resource, "attributes");
|
|
284
|
+
return {
|
|
285
|
+
1: attributeField(list),
|
|
286
|
+
2: [0, (value) => (resource.droppedAttributesCount = uint32(value))],
|
|
287
|
+
};
|
|
288
|
+
})),
|
|
289
|
+
],
|
|
290
|
+
2: [
|
|
291
|
+
2,
|
|
292
|
+
(item) => scopeSpans.push(message(item.sub(), (scopeGroup) => {
|
|
293
|
+
const spans = [];
|
|
294
|
+
scopeGroup.spans = spans;
|
|
295
|
+
return {
|
|
296
|
+
1: [
|
|
297
|
+
2,
|
|
298
|
+
(value) => (scopeGroup.scope = message(value.sub(), (scope) => {
|
|
299
|
+
const list = attributesInto(scope, "attributes");
|
|
300
|
+
return {
|
|
301
|
+
1: [2, (field) => (scope.name = field.text())],
|
|
302
|
+
2: [2, (field) => (scope.version = field.text())],
|
|
303
|
+
3: attributeField(list),
|
|
304
|
+
4: [0, (field) => (scope.droppedAttributesCount = uint32(field))],
|
|
305
|
+
};
|
|
306
|
+
})),
|
|
307
|
+
],
|
|
308
|
+
2: [
|
|
309
|
+
2,
|
|
310
|
+
(value) => {
|
|
311
|
+
const read = readSpan(value.sub(), ++spanCount, refused);
|
|
312
|
+
if (read)
|
|
313
|
+
spans.push(read);
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
3: [2, (value) => (scopeGroup.schemaUrl = value.text())],
|
|
317
|
+
};
|
|
318
|
+
})),
|
|
319
|
+
],
|
|
320
|
+
3: [2, (item) => (group.schemaUrl = item.text())],
|
|
321
|
+
};
|
|
322
|
+
})),
|
|
323
|
+
],
|
|
324
|
+
});
|
|
325
|
+
return { request: { resourceSpans }, refused };
|
|
326
|
+
}
|
|
327
|
+
const ZSTD = [0x28, 0xb5, 0x2f, 0xfd];
|
|
328
|
+
const isZstd = (bytes) => ZSTD.every((byte, index) => bytes[index] === byte);
|
|
329
|
+
/** Whether bytes cannot be JSON text: JSON holds no raw control character but tab, line feed
|
|
330
|
+
* and carriage return, while protobuf ids, timestamps and length prefixes are full of them. */
|
|
331
|
+
function binary(bytes) {
|
|
332
|
+
for (const byte of bytes)
|
|
333
|
+
if (byte < 0x20 && byte !== 0x09 && byte !== 0x0a && byte !== 0x0d)
|
|
334
|
+
return true;
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
/** A decoded request as a value; its JSON text is made only when a format asks for it. */
|
|
338
|
+
function decoded(traces, line) {
|
|
339
|
+
let text;
|
|
340
|
+
return {
|
|
341
|
+
value: { [PROTOBUF]: traces },
|
|
342
|
+
get text() {
|
|
343
|
+
return (text ??= JSON.stringify(traces.request));
|
|
344
|
+
},
|
|
345
|
+
unsafeIntegers: false,
|
|
346
|
+
...(line !== undefined ? { line } : {}),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
/** One length-prefixed message's bytes, a zstd message decompressed. `budget` is what the file
|
|
350
|
+
* may still decompress to. */
|
|
351
|
+
function frameBytes(frame, budget) {
|
|
352
|
+
let bytes = frame;
|
|
353
|
+
if (isZstd(frame)) {
|
|
354
|
+
if (typeof zlib.zstdDecompressSync !== "function")
|
|
355
|
+
throw new ConversionError("a zstd message, which this Node.js cannot decompress (Node.js 22.15 or later can)");
|
|
356
|
+
try {
|
|
357
|
+
bytes = zlib.zstdDecompressSync(frame, { maxOutputLength: Math.max(0, budget) });
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
if (error instanceof RangeError ||
|
|
361
|
+
error?.code === "ERR_BUFFER_TOO_LARGE")
|
|
362
|
+
throw new Oversized();
|
|
363
|
+
throw new ConversionError("a damaged zstd message");
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (bytes.byteLength > budget)
|
|
367
|
+
throw new Oversized();
|
|
368
|
+
return bytes;
|
|
369
|
+
}
|
|
370
|
+
/** One message read: protobuf, or OTLP/JSON that a zstd message held. */
|
|
371
|
+
function messageValue(bytes, zstd, line) {
|
|
372
|
+
if (zstd &&
|
|
373
|
+
!binary(bytes) &&
|
|
374
|
+
/^\s*\{/.test(Buffer.from(bytes.subarray(0, 64)).toString("utf8"))) {
|
|
375
|
+
let text;
|
|
376
|
+
try {
|
|
377
|
+
text = utf8.decode(bytes);
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
throw new ConversionError("a JSON message that is not UTF-8");
|
|
381
|
+
}
|
|
382
|
+
try {
|
|
383
|
+
const parsed = parseJsonText(text.trim());
|
|
384
|
+
return { value: parsed.value, text, unsafeIntegers: parsed.unsafeIntegers, line };
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
throw new ConversionError("a message that is not JSON");
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return decoded(decodeTraces(bytes), line);
|
|
391
|
+
}
|
|
392
|
+
/** The file's messages together decompress past the bound of one file. */
|
|
393
|
+
class Oversized extends Error {
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* The values of a binary OTLP file; undefined when the content is not binary or holds neither
|
|
397
|
+
* form, so that it is read as text; or why a binary OTLP request cannot be read. In a
|
|
398
|
+
* length-prefixed file each message is one value, its 1-based position given as its line; a
|
|
399
|
+
* message that cannot be read is reported with that position and the rest are read, until the
|
|
400
|
+
* messages together decompress to more than `maxBytes`.
|
|
401
|
+
*/
|
|
402
|
+
export function binaryValues(bytes, maxBytes) {
|
|
403
|
+
if (!binary(bytes))
|
|
404
|
+
return undefined;
|
|
405
|
+
let requestError;
|
|
406
|
+
if (bytes[0] === 0x0a)
|
|
407
|
+
try {
|
|
408
|
+
const value = decoded(decodeTraces(bytes));
|
|
409
|
+
return { values: [value], problems: [], bytes: () => bytes.byteLength };
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
if (!(error instanceof ConversionError))
|
|
413
|
+
throw error;
|
|
414
|
+
// Not one message: perhaps length-prefixed messages.
|
|
415
|
+
requestError = error.message;
|
|
416
|
+
}
|
|
417
|
+
const frames = [];
|
|
418
|
+
let pos = 0;
|
|
419
|
+
let truncated = false;
|
|
420
|
+
while (pos < bytes.byteLength) {
|
|
421
|
+
if (pos + 4 > bytes.byteLength) {
|
|
422
|
+
truncated = true;
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
const length = new DataView(bytes.buffer, bytes.byteOffset + pos, 4).getUint32(0, false);
|
|
426
|
+
if (pos + 4 + length > bytes.byteLength) {
|
|
427
|
+
truncated = true;
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
frames.push(bytes.subarray(pos + 4, pos + 4 + length));
|
|
431
|
+
pos += 4 + length;
|
|
432
|
+
}
|
|
433
|
+
if (!frames.length)
|
|
434
|
+
return requestError === undefined
|
|
435
|
+
? undefined
|
|
436
|
+
: { unsupported: `not an OTLP trace request: ${requestError}` };
|
|
437
|
+
const problems = [];
|
|
438
|
+
let total = 0;
|
|
439
|
+
function* values() {
|
|
440
|
+
for (const [index, frame] of frames.entries()) {
|
|
441
|
+
// A message counts what it decompressed to, whether or not it then reads.
|
|
442
|
+
let size = frame.byteLength;
|
|
443
|
+
try {
|
|
444
|
+
const bytes = frameBytes(frame, maxBytes - total);
|
|
445
|
+
size = bytes.byteLength;
|
|
446
|
+
const value = messageValue(bytes, isZstd(frame), index + 1);
|
|
447
|
+
total += size;
|
|
448
|
+
yield value;
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
if (error instanceof Oversized) {
|
|
452
|
+
problems.push({
|
|
453
|
+
line: index + 1,
|
|
454
|
+
reason: `the messages decompress to more than ${maxBytes} bytes; this message and the ${frames.length - index - 1} after it were not read`,
|
|
455
|
+
});
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (!(error instanceof ConversionError))
|
|
459
|
+
throw error;
|
|
460
|
+
total += size;
|
|
461
|
+
problems.push({ line: index + 1, reason: `not an OTLP trace message: ${error.message}` });
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (truncated)
|
|
465
|
+
problems.push({ line: frames.length + 1, reason: "a truncated length-prefixed message" });
|
|
466
|
+
}
|
|
467
|
+
return { values: values(), problems, bytes: () => Math.max(total, bytes.byteLength) };
|
|
468
|
+
}
|
|
469
|
+
export const otlpProtobuf = {
|
|
470
|
+
id: "otlp_protobuf",
|
|
471
|
+
manifestFormat: "otlp_json",
|
|
472
|
+
detect: (value) => isObject(value) && PROTOBUF in value,
|
|
473
|
+
convert(value, context) {
|
|
474
|
+
const traces = isObject(value) && PROTOBUF in value ? value[PROTOBUF] : undefined;
|
|
475
|
+
if (!traces)
|
|
476
|
+
return;
|
|
477
|
+
const collector = context.collector(() => new OtlpTraceCollector(), OTLP_SPANS);
|
|
478
|
+
// A span that could not be decoded refuses its trace, as a malformed OTLP/JSON span does.
|
|
479
|
+
for (const problem of traces.refused) {
|
|
480
|
+
const refusal = collector.refuse(problem.traceId, problem.reason);
|
|
481
|
+
context.refuse(refusal.reason, refusal.traceId);
|
|
482
|
+
}
|
|
483
|
+
for (const refusal of collector.add([traces.request], context.source.digest, otlpProtobuf.id))
|
|
484
|
+
context.refuse(refusal.reason, refusal.traceId);
|
|
485
|
+
},
|
|
486
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ImportAdapter } from "./adapter.js";
|
|
2
|
+
/** OTLP/JSON trace exports (`{"resourceSpans": […]}`, proto field names too): spans kept as
|
|
3
|
+
* recorded, gathered by trace across files and lines, and with binary OTLP and Jaeger API v3. */
|
|
4
|
+
export declare const otlpJson: ImportAdapter;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { isObject, OTLP_SPANS, OtlpTraceCollector } from "../traces-import-convert.js";
|
|
2
|
+
/** OTLP/JSON trace exports (`{"resourceSpans": […]}`, proto field names too): spans kept as
|
|
3
|
+
* recorded, gathered by trace across files and lines, and with binary OTLP and Jaeger API v3. */
|
|
4
|
+
export const otlpJson = {
|
|
5
|
+
id: "otlp_json",
|
|
6
|
+
manifestFormat: "otlp_json",
|
|
7
|
+
detect: (value) => isObject(value) && (Array.isArray(value.resourceSpans) || Array.isArray(value.resource_spans)),
|
|
8
|
+
convert(value, context) {
|
|
9
|
+
if (!isObject(value))
|
|
10
|
+
return;
|
|
11
|
+
const collector = context.collector(() => new OtlpTraceCollector(), OTLP_SPANS);
|
|
12
|
+
for (const refusal of collector.add([value], context.source.digest, otlpJson.id))
|
|
13
|
+
context.refuse(refusal.reason, refusal.traceId);
|
|
14
|
+
},
|
|
15
|
+
};
|