@fabbahiense/pulsar-pino-transport 0.2.1 → 0.4.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/CHANGELOG.md +43 -0
- package/LICENSE +21 -0
- package/README.md +205 -110
- package/dist/index.cjs +1438 -86
- package/dist/index.d.cts +278 -4
- package/dist/index.d.ts +278 -4
- package/dist/index.js +1423 -85
- package/dist/middleware.cjs +387 -0
- package/dist/middleware.d.cts +70 -0
- package/dist/middleware.d.ts +70 -0
- package/dist/middleware.js +360 -0
- package/package.json +35 -8
package/dist/index.cjs
CHANGED
|
@@ -31,9 +31,1316 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
createPulsarTransport: () => createPulsarTransport,
|
|
34
|
-
default: () => index_default
|
|
34
|
+
default: () => index_default,
|
|
35
|
+
extractTrace: () => extractTrace,
|
|
36
|
+
formatTraceparent: () => formatTraceparent,
|
|
37
|
+
getTraceContext: () => getTraceContext,
|
|
38
|
+
injectTrace: () => injectTrace,
|
|
39
|
+
instrumentAxios: () => instrumentAxios,
|
|
40
|
+
instrumentFetch: () => instrumentFetch,
|
|
41
|
+
parseTraceparent: () => parseTraceparent,
|
|
42
|
+
pinoSpanSink: () => pinoSpanSink,
|
|
43
|
+
pulsarMixin: () => pulsarMixin,
|
|
44
|
+
runWithTrace: () => runWithTrace,
|
|
45
|
+
startSpan: () => startSpan,
|
|
46
|
+
traceHeaders: () => traceHeaders
|
|
35
47
|
});
|
|
36
48
|
module.exports = __toCommonJS(index_exports);
|
|
49
|
+
|
|
50
|
+
// ../shared/src/serialize.ts
|
|
51
|
+
var MAX_STRING_CHARS = 32768;
|
|
52
|
+
var MAX_ITEMS = 1e3;
|
|
53
|
+
var MAX_DEPTH = 10;
|
|
54
|
+
var MAX_NODES = 2e4;
|
|
55
|
+
var MAX_TOTAL_CHARS = 1e6;
|
|
56
|
+
var MAX_MESSAGE_CHARS = 32768;
|
|
57
|
+
var MAX_CAUSES = 5;
|
|
58
|
+
function truncateString(value, max) {
|
|
59
|
+
if (value.length <= max) return value;
|
|
60
|
+
let end = max;
|
|
61
|
+
const code = value.charCodeAt(end - 1);
|
|
62
|
+
if (code >= 55296 && code <= 56319) end--;
|
|
63
|
+
return `${value.slice(0, end)}\u2026 [truncated ${value.length - end} chars]`;
|
|
64
|
+
}
|
|
65
|
+
function utf8Length(value) {
|
|
66
|
+
let bytes = 0;
|
|
67
|
+
for (let i = 0; i < value.length; i++) {
|
|
68
|
+
const code = value.charCodeAt(i);
|
|
69
|
+
if (code < 128) bytes += 1;
|
|
70
|
+
else if (code < 2048) bytes += 2;
|
|
71
|
+
else if (code >= 55296 && code <= 56319 && i + 1 < value.length) {
|
|
72
|
+
const next = value.charCodeAt(i + 1);
|
|
73
|
+
if (next >= 56320 && next <= 57343) {
|
|
74
|
+
bytes += 4;
|
|
75
|
+
i++;
|
|
76
|
+
} else {
|
|
77
|
+
bytes += 3;
|
|
78
|
+
}
|
|
79
|
+
} else bytes += 3;
|
|
80
|
+
}
|
|
81
|
+
return bytes;
|
|
82
|
+
}
|
|
83
|
+
function describeError(err) {
|
|
84
|
+
try {
|
|
85
|
+
return err instanceof Error ? err.message : String(err);
|
|
86
|
+
} catch {
|
|
87
|
+
return "unknown error";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function functionName(fn) {
|
|
91
|
+
try {
|
|
92
|
+
return fn.name ? `[Function: ${fn.name}]` : "[Function (anonymous)]";
|
|
93
|
+
} catch {
|
|
94
|
+
return "[Function]";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function isPlainContainer(value) {
|
|
98
|
+
return Array.isArray(value) || value instanceof Map || value instanceof Set;
|
|
99
|
+
}
|
|
100
|
+
function toJsonSafe(value, options = {}) {
|
|
101
|
+
const state = {
|
|
102
|
+
maxDepth: options.maxDepth ?? MAX_DEPTH,
|
|
103
|
+
maxItems: options.maxItems ?? MAX_ITEMS,
|
|
104
|
+
maxStringChars: options.maxStringChars ?? MAX_STRING_CHARS,
|
|
105
|
+
nodesLeft: options.maxNodes ?? MAX_NODES,
|
|
106
|
+
charsLeft: options.maxTotalChars ?? MAX_TOTAL_CHARS,
|
|
107
|
+
ancestors: /* @__PURE__ */ new Set()
|
|
108
|
+
};
|
|
109
|
+
return walk(value, 0, state, false);
|
|
110
|
+
}
|
|
111
|
+
function walk(value, depth, state, inArray) {
|
|
112
|
+
if (state.nodesLeft <= 0) return "[Truncated]";
|
|
113
|
+
state.nodesLeft--;
|
|
114
|
+
switch (typeof value) {
|
|
115
|
+
case "string":
|
|
116
|
+
return walkString(value, state);
|
|
117
|
+
case "number":
|
|
118
|
+
case "boolean":
|
|
119
|
+
return value;
|
|
120
|
+
case "bigint":
|
|
121
|
+
return value.toString();
|
|
122
|
+
case "symbol":
|
|
123
|
+
return value.toString();
|
|
124
|
+
case "undefined":
|
|
125
|
+
return inArray ? null : void 0;
|
|
126
|
+
case "function":
|
|
127
|
+
return inArray ? null : void 0;
|
|
128
|
+
}
|
|
129
|
+
if (value === null) return null;
|
|
130
|
+
const obj = value;
|
|
131
|
+
if (state.ancestors.has(obj)) return "[Circular]";
|
|
132
|
+
try {
|
|
133
|
+
if (obj instanceof Error) return walkError(obj, depth, state);
|
|
134
|
+
if (ArrayBuffer.isView(obj)) {
|
|
135
|
+
return `[${obj.constructor?.name ?? "Binary"}: ${obj.byteLength} bytes]`;
|
|
136
|
+
}
|
|
137
|
+
if (obj instanceof ArrayBuffer) return `[ArrayBuffer: ${obj.byteLength} bytes]`;
|
|
138
|
+
if (obj instanceof Date) {
|
|
139
|
+
return Number.isNaN(obj.getTime()) ? null : obj.toISOString();
|
|
140
|
+
}
|
|
141
|
+
if (obj instanceof RegExp) return obj.toString();
|
|
142
|
+
const toJSON = obj.toJSON;
|
|
143
|
+
if (typeof toJSON === "function") {
|
|
144
|
+
const replaced = toJSON.call(obj);
|
|
145
|
+
if (replaced !== obj) {
|
|
146
|
+
state.ancestors.add(obj);
|
|
147
|
+
try {
|
|
148
|
+
return walk(replaced, depth, state, inArray);
|
|
149
|
+
} finally {
|
|
150
|
+
state.ancestors.delete(obj);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (depth >= state.maxDepth) {
|
|
155
|
+
return isPlainContainer(obj) ? "[Array]" : "[Object]";
|
|
156
|
+
}
|
|
157
|
+
state.ancestors.add(obj);
|
|
158
|
+
try {
|
|
159
|
+
if (Array.isArray(obj)) return walkList(obj, obj.length, depth, state);
|
|
160
|
+
if (obj instanceof Set) return walkList(obj, obj.size, depth, state);
|
|
161
|
+
if (obj instanceof Map) {
|
|
162
|
+
const pairs = [];
|
|
163
|
+
let i = 0;
|
|
164
|
+
for (const [k, v] of obj) {
|
|
165
|
+
if (i++ >= state.maxItems) {
|
|
166
|
+
pairs.push(`\u2026 ${obj.size - state.maxItems} more items`);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
pairs.push([walk(k, depth + 2, state, true), walk(v, depth + 2, state, true)]);
|
|
170
|
+
}
|
|
171
|
+
return pairs;
|
|
172
|
+
}
|
|
173
|
+
return walkObject(obj, depth, state);
|
|
174
|
+
} finally {
|
|
175
|
+
state.ancestors.delete(obj);
|
|
176
|
+
}
|
|
177
|
+
} catch (err) {
|
|
178
|
+
return `[Unserializable: ${describeError(err)}]`;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function walkString(value, state) {
|
|
182
|
+
if (state.charsLeft <= 0) return "[Truncated]";
|
|
183
|
+
const kept = truncateString(value, Math.min(state.maxStringChars, state.charsLeft));
|
|
184
|
+
state.charsLeft -= kept.length;
|
|
185
|
+
return kept;
|
|
186
|
+
}
|
|
187
|
+
function walkList(list, size, depth, state) {
|
|
188
|
+
const out = [];
|
|
189
|
+
let i = 0;
|
|
190
|
+
for (const item of list) {
|
|
191
|
+
if (i++ >= state.maxItems) {
|
|
192
|
+
out.push(`\u2026 ${size - state.maxItems} more items`);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
out.push(walk(item, depth + 1, state, true));
|
|
196
|
+
}
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
function emptyObject() {
|
|
200
|
+
return /* @__PURE__ */ Object.create(null);
|
|
201
|
+
}
|
|
202
|
+
function walkObject(obj, depth, state) {
|
|
203
|
+
const out = emptyObject();
|
|
204
|
+
const keys = Object.keys(obj);
|
|
205
|
+
let limit = Math.min(keys.length, state.maxItems);
|
|
206
|
+
for (let i = 0; i < limit; i++) {
|
|
207
|
+
const key = keys[i];
|
|
208
|
+
if (state.charsLeft <= 0) {
|
|
209
|
+
limit = i;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
state.charsLeft -= key.length;
|
|
213
|
+
let child;
|
|
214
|
+
try {
|
|
215
|
+
child = obj[key];
|
|
216
|
+
} catch (err) {
|
|
217
|
+
out[key] = `[Unserializable: ${describeError(err)}]`;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const safe = walk(child, depth + 1, state, false);
|
|
221
|
+
if (safe !== void 0) out[key] = safe;
|
|
222
|
+
}
|
|
223
|
+
if (keys.length > limit) out["\u2026"] = `${keys.length - limit} more keys`;
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
function walkError(err, depth, state) {
|
|
227
|
+
const out = emptyObject();
|
|
228
|
+
state.ancestors.add(err);
|
|
229
|
+
try {
|
|
230
|
+
out.name = walk(readProp(err, "name"), depth + 1, state, false);
|
|
231
|
+
out.message = walk(readProp(err, "message"), depth + 1, state, false);
|
|
232
|
+
const stack = readProp(err, "stack");
|
|
233
|
+
if (stack !== void 0) out.stack = walk(stack, depth + 1, state, false);
|
|
234
|
+
for (const key of Object.keys(err)) {
|
|
235
|
+
if (key in out || key === "cause") continue;
|
|
236
|
+
const safe = walk(readProp(err, key), depth + 1, state, false);
|
|
237
|
+
if (safe !== void 0) out[key] = safe;
|
|
238
|
+
}
|
|
239
|
+
const cause = readProp(err, "cause");
|
|
240
|
+
if (cause !== void 0) {
|
|
241
|
+
out.cause = depth + 1 >= state.maxDepth ? "[Object]" : walk(cause, depth + 1, state, false);
|
|
242
|
+
}
|
|
243
|
+
return out;
|
|
244
|
+
} finally {
|
|
245
|
+
state.ancestors.delete(err);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function readProp(obj, key) {
|
|
249
|
+
try {
|
|
250
|
+
return obj[key];
|
|
251
|
+
} catch (err) {
|
|
252
|
+
return `[Unserializable: ${describeError(err)}]`;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function safeStringify(value, options) {
|
|
256
|
+
const json = JSON.stringify(toJsonSafe(value, options));
|
|
257
|
+
return json === void 0 ? String(json) : json;
|
|
258
|
+
}
|
|
259
|
+
function errorSummary(err) {
|
|
260
|
+
try {
|
|
261
|
+
const text2 = String(err);
|
|
262
|
+
if (text2 && text2 !== "[object Object]") return text2;
|
|
263
|
+
} catch {
|
|
264
|
+
}
|
|
265
|
+
const name = readProp(err, "name");
|
|
266
|
+
const message = readProp(err, "message");
|
|
267
|
+
return `${typeof name === "string" ? name : "Error"}: ${typeof message === "string" ? message : ""}`;
|
|
268
|
+
}
|
|
269
|
+
function errorDetails(err) {
|
|
270
|
+
const rawMessage = readProp(err, "message");
|
|
271
|
+
const message = truncateString(typeof rawMessage === "string" ? rawMessage : safeStringify(rawMessage), MAX_STRING_CHARS);
|
|
272
|
+
const parts = [stackOf(err)];
|
|
273
|
+
const seen = /* @__PURE__ */ new Set([err]);
|
|
274
|
+
let cause = readProp(err, "cause");
|
|
275
|
+
for (let i = 0; i < MAX_CAUSES && cause !== void 0 && cause !== null && !seen.has(cause); i++) {
|
|
276
|
+
seen.add(cause);
|
|
277
|
+
parts.push(`Caused by: ${cause instanceof Error ? stackOf(cause) : safeStringify(cause)}`);
|
|
278
|
+
cause = cause instanceof Error ? readProp(cause, "cause") : void 0;
|
|
279
|
+
}
|
|
280
|
+
return { message, stack: truncateString(parts.join("\n"), MAX_STRING_CHARS * 2) };
|
|
281
|
+
}
|
|
282
|
+
function stackOf(err) {
|
|
283
|
+
const stack = readProp(err, "stack");
|
|
284
|
+
return typeof stack === "string" && stack.length > 0 ? stack : errorSummary(err);
|
|
285
|
+
}
|
|
286
|
+
function formatArgs(args) {
|
|
287
|
+
const parts = [];
|
|
288
|
+
let length = 0;
|
|
289
|
+
for (const arg of args) {
|
|
290
|
+
const part = formatArg(arg);
|
|
291
|
+
parts.push(part);
|
|
292
|
+
length += part.length + 1;
|
|
293
|
+
if (length > MAX_MESSAGE_CHARS) break;
|
|
294
|
+
}
|
|
295
|
+
return truncateString(parts.join(" "), MAX_MESSAGE_CHARS);
|
|
296
|
+
}
|
|
297
|
+
function formatArg(arg) {
|
|
298
|
+
switch (typeof arg) {
|
|
299
|
+
case "string":
|
|
300
|
+
return truncateString(arg, MAX_MESSAGE_CHARS);
|
|
301
|
+
case "bigint":
|
|
302
|
+
return `${arg}n`;
|
|
303
|
+
case "symbol":
|
|
304
|
+
return arg.toString();
|
|
305
|
+
case "function":
|
|
306
|
+
return functionName(arg);
|
|
307
|
+
case "undefined":
|
|
308
|
+
return "undefined";
|
|
309
|
+
}
|
|
310
|
+
if (arg instanceof Error) return errorSummary(arg);
|
|
311
|
+
return safeStringify(arg, { maxStringChars: MAX_MESSAGE_CHARS });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ../shared/src/delivery.ts
|
|
315
|
+
var SERVER_MAX_BATCH = 1e3;
|
|
316
|
+
var DEFAULT_BATCH_SIZE = 50;
|
|
317
|
+
var DEFAULT_FLUSH_INTERVAL = 2e3;
|
|
318
|
+
var DEFAULT_MAX_BUFFER_SIZE = 5e3;
|
|
319
|
+
var DEFAULT_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
320
|
+
var DEFAULT_MAX_RETRIES = 10;
|
|
321
|
+
var DEFAULT_REQUEST_TIMEOUT = 1e4;
|
|
322
|
+
var DEFAULT_SHUTDOWN_TIMEOUT = 3e3;
|
|
323
|
+
var DEFAULT_MAX_ENTRY_BYTES = 256 * 1024;
|
|
324
|
+
var DEFAULT_MAX_BATCH_BYTES = 512 * 1024;
|
|
325
|
+
var RETRY_BASE_MS = 1e3;
|
|
326
|
+
var RETRY_MAX_MS = 3e4;
|
|
327
|
+
var RETRY_AFTER_MAX_MS = 10 * 6e4;
|
|
328
|
+
var MIN_BATCH_BYTES = 1024;
|
|
329
|
+
var INTERNAL_REQUEST = /* @__PURE__ */ Symbol.for("@fabbahiense/pulsar.internal-request");
|
|
330
|
+
function unref(timer) {
|
|
331
|
+
const t = timer;
|
|
332
|
+
if (typeof t === "object" && t !== null && typeof t.unref === "function") t.unref();
|
|
333
|
+
return timer;
|
|
334
|
+
}
|
|
335
|
+
function ref(timer) {
|
|
336
|
+
const t = timer;
|
|
337
|
+
if (typeof t === "object" && t !== null && typeof t.ref === "function") t.ref();
|
|
338
|
+
}
|
|
339
|
+
function positive(value, fallback, min = 1) {
|
|
340
|
+
return typeof value === "number" && Number.isFinite(value) && value >= min ? value : fallback;
|
|
341
|
+
}
|
|
342
|
+
function nonNegative(value, fallback) {
|
|
343
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : fallback;
|
|
344
|
+
}
|
|
345
|
+
function generateId() {
|
|
346
|
+
const c = globalThis.crypto;
|
|
347
|
+
if (c && typeof c.randomUUID === "function") return c.randomUUID();
|
|
348
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}${Math.random().toString(36).slice(2, 12)}`;
|
|
349
|
+
}
|
|
350
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
351
|
+
if (value === null || value === void 0) return null;
|
|
352
|
+
const trimmed = value.trim();
|
|
353
|
+
if (trimmed === "") return null;
|
|
354
|
+
let ms;
|
|
355
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
356
|
+
ms = Number(trimmed) * 1e3;
|
|
357
|
+
} else {
|
|
358
|
+
const date = Date.parse(trimmed);
|
|
359
|
+
if (Number.isNaN(date)) return null;
|
|
360
|
+
ms = date - now;
|
|
361
|
+
}
|
|
362
|
+
return Math.min(Math.max(ms, 0), RETRY_AFTER_MAX_MS);
|
|
363
|
+
}
|
|
364
|
+
function serializeEntry(entry, maxBytes) {
|
|
365
|
+
const safe = toJsonSafe(entry, { maxTotalChars: maxBytes });
|
|
366
|
+
let json = JSON.stringify(safe);
|
|
367
|
+
let bytes = utf8Length(json);
|
|
368
|
+
if (bytes <= maxBytes) return { json, bytes };
|
|
369
|
+
if (safe.metadata !== void 0 && safe.metadata !== null) {
|
|
370
|
+
const metaJson = JSON.stringify(safe.metadata);
|
|
371
|
+
safe.metadata = {
|
|
372
|
+
_truncated: `metadata was ${utf8Length(metaJson)} bytes; entries are capped at ${maxBytes} bytes`,
|
|
373
|
+
_preview: truncateString(metaJson, 2048)
|
|
374
|
+
};
|
|
375
|
+
json = JSON.stringify(safe);
|
|
376
|
+
bytes = utf8Length(json);
|
|
377
|
+
if (bytes <= maxBytes) return { json, bytes };
|
|
378
|
+
}
|
|
379
|
+
for (const share of [8, 32]) {
|
|
380
|
+
const limit = Math.max(256, Math.floor(maxBytes / share));
|
|
381
|
+
for (const field of ["msg", "errorStack", "errorMessage", "url", "traceId"]) {
|
|
382
|
+
const value = safe[field];
|
|
383
|
+
if (typeof value === "string") safe[field] = truncateString(value, limit);
|
|
384
|
+
}
|
|
385
|
+
json = JSON.stringify(safe);
|
|
386
|
+
bytes = utf8Length(json);
|
|
387
|
+
if (bytes <= maxBytes) return { json, bytes };
|
|
388
|
+
}
|
|
389
|
+
const minimal = {};
|
|
390
|
+
for (const [key, value] of Object.entries(safe)) {
|
|
391
|
+
if (typeof value === "string") minimal[key] = truncateString(value, 256);
|
|
392
|
+
else if (typeof value !== "object" || value === null) minimal[key] = value;
|
|
393
|
+
}
|
|
394
|
+
minimal.metadata = { _truncated: `entry exceeded ${maxBytes} bytes` };
|
|
395
|
+
json = JSON.stringify(minimal);
|
|
396
|
+
return { json, bytes: utf8Length(json) };
|
|
397
|
+
}
|
|
398
|
+
function createDropWarner(name, intervalMs = 6e4) {
|
|
399
|
+
let lastAt = 0;
|
|
400
|
+
const pending = { overflow: 0, rejected: 0, retriesExhausted: 0, shutdown: 0 };
|
|
401
|
+
let detail = "";
|
|
402
|
+
return (event) => {
|
|
403
|
+
pending[event.reason] += event.count;
|
|
404
|
+
if (event.error || event.status) detail = `${event.status ?? ""}${event.status && event.error ? " " : ""}${event.error ?? ""}`;
|
|
405
|
+
const now = Date.now();
|
|
406
|
+
if (now - lastAt < intervalMs && event.reason !== "shutdown") return;
|
|
407
|
+
lastAt = now;
|
|
408
|
+
const parts = Object.keys(pending).filter((r) => pending[r] > 0).map((r) => `${pending[r]} ${r}`);
|
|
409
|
+
const line = `[${name}] dropped logs: ${parts.join(", ")}${detail ? ` (last error: ${detail})` : ""}
|
|
410
|
+
`;
|
|
411
|
+
for (const r of Object.keys(pending)) pending[r] = 0;
|
|
412
|
+
detail = "";
|
|
413
|
+
writeStderr(line);
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function writeStderr(line, target = globalThis.process) {
|
|
417
|
+
try {
|
|
418
|
+
const proc = target;
|
|
419
|
+
if (proc?.stderr && typeof proc.stderr.write === "function") {
|
|
420
|
+
proc.stderr.write(line);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (typeof console !== "undefined") console.warn(line.trimEnd());
|
|
424
|
+
} catch {
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
var Queue = class {
|
|
428
|
+
items = [];
|
|
429
|
+
head = 0;
|
|
430
|
+
bytes = 0;
|
|
431
|
+
get length() {
|
|
432
|
+
return this.items.length - this.head;
|
|
433
|
+
}
|
|
434
|
+
push(rec) {
|
|
435
|
+
this.items.push(rec);
|
|
436
|
+
this.bytes += rec.bytes;
|
|
437
|
+
}
|
|
438
|
+
shift() {
|
|
439
|
+
if (this.head >= this.items.length) return void 0;
|
|
440
|
+
const rec = this.items[this.head];
|
|
441
|
+
this.items[this.head] = void 0;
|
|
442
|
+
this.head++;
|
|
443
|
+
this.bytes -= rec.bytes;
|
|
444
|
+
if (this.head > 1024 && this.head * 2 > this.items.length) {
|
|
445
|
+
this.items = this.items.slice(this.head);
|
|
446
|
+
this.head = 0;
|
|
447
|
+
}
|
|
448
|
+
return rec;
|
|
449
|
+
}
|
|
450
|
+
peek() {
|
|
451
|
+
return this.head < this.items.length ? this.items[this.head] : void 0;
|
|
452
|
+
}
|
|
453
|
+
/** Puts entries back at the front, keeping their order. */
|
|
454
|
+
unshiftAll(recs) {
|
|
455
|
+
if (recs.length === 0) return;
|
|
456
|
+
this.items = recs.concat(this.items.slice(this.head));
|
|
457
|
+
this.head = 0;
|
|
458
|
+
for (const rec of recs) this.bytes += rec.bytes;
|
|
459
|
+
}
|
|
460
|
+
clear() {
|
|
461
|
+
const n = this.length;
|
|
462
|
+
this.items = [];
|
|
463
|
+
this.head = 0;
|
|
464
|
+
this.bytes = 0;
|
|
465
|
+
return n;
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
var EXIT_GRACE_MS = 1e3;
|
|
469
|
+
var Delivery = class {
|
|
470
|
+
queue = new Queue();
|
|
471
|
+
endpoint;
|
|
472
|
+
apiKey;
|
|
473
|
+
batchSize;
|
|
474
|
+
flushInterval;
|
|
475
|
+
maxBufferSize;
|
|
476
|
+
maxBufferBytes;
|
|
477
|
+
maxRetries;
|
|
478
|
+
requestTimeout;
|
|
479
|
+
shutdownTimeout;
|
|
480
|
+
maxEntryBytes;
|
|
481
|
+
maxBatchBytes;
|
|
482
|
+
/** Entries per request; lowered by a 413 split, then grows back. */
|
|
483
|
+
maxBatchEntries;
|
|
484
|
+
single;
|
|
485
|
+
keepalive;
|
|
486
|
+
onDrop;
|
|
487
|
+
fetchImpl;
|
|
488
|
+
random;
|
|
489
|
+
/** Pending flush-interval or backoff timer (never both). */
|
|
490
|
+
timer = null;
|
|
491
|
+
/** True while waiting out a backoff; sends are held until the timer fires. */
|
|
492
|
+
backingOff = false;
|
|
493
|
+
backoffUntil = 0;
|
|
494
|
+
backoffWaiters = [];
|
|
495
|
+
inflight = null;
|
|
496
|
+
inflightCount = 0;
|
|
497
|
+
/** Consecutive retryable failures of the batch at the head of the queue. */
|
|
498
|
+
attempt = 0;
|
|
499
|
+
/** Size of the batch at the head of the queue that awaits a retry (0 = none). */
|
|
500
|
+
retryCount = 0;
|
|
501
|
+
/** >0 while a flush()/close() wants everything sent now, not just full batches. */
|
|
502
|
+
draining = 0;
|
|
503
|
+
/** Deadline of the innermost running flush (0 = none). */
|
|
504
|
+
drainDeadline = 0;
|
|
505
|
+
/** Deadline of the current beforeExit episode (0 = none). */
|
|
506
|
+
exitDeadline = 0;
|
|
507
|
+
closed = false;
|
|
508
|
+
counters = {
|
|
509
|
+
sent: 0,
|
|
510
|
+
buffered: 0,
|
|
511
|
+
retries: 0,
|
|
512
|
+
dropped: { overflow: 0, rejected: 0, retriesExhausted: 0, shutdown: 0 }
|
|
513
|
+
};
|
|
514
|
+
constructor(options) {
|
|
515
|
+
const base = String(options.url ?? "").replace(/\/+$/, "");
|
|
516
|
+
this.single = options.single === true;
|
|
517
|
+
this.endpoint = `${base}${this.single ? "/api/ingest" : "/api/ingest/batch"}`;
|
|
518
|
+
this.apiKey = options.apiKey ?? "";
|
|
519
|
+
this.batchSize = this.single ? 1 : Math.min(Math.floor(positive(options.batchSize, DEFAULT_BATCH_SIZE)), SERVER_MAX_BATCH);
|
|
520
|
+
this.flushInterval = positive(options.flushInterval, DEFAULT_FLUSH_INTERVAL);
|
|
521
|
+
this.maxBufferSize = Math.floor(positive(options.maxBufferSize, DEFAULT_MAX_BUFFER_SIZE));
|
|
522
|
+
this.maxBufferBytes = positive(options.maxBufferBytes, DEFAULT_MAX_BUFFER_BYTES);
|
|
523
|
+
this.maxRetries = nonNegative(options.maxRetries, DEFAULT_MAX_RETRIES);
|
|
524
|
+
this.requestTimeout = positive(options.requestTimeout, DEFAULT_REQUEST_TIMEOUT);
|
|
525
|
+
this.shutdownTimeout = nonNegative(options.shutdownTimeout, DEFAULT_SHUTDOWN_TIMEOUT);
|
|
526
|
+
this.maxEntryBytes = positive(options.maxEntryBytes, DEFAULT_MAX_ENTRY_BYTES, 512);
|
|
527
|
+
this.maxBatchBytes = Math.max(positive(options.maxBatchBytes, DEFAULT_MAX_BATCH_BYTES, MIN_BATCH_BYTES), MIN_BATCH_BYTES);
|
|
528
|
+
this.maxBatchEntries = this.single ? 1 : SERVER_MAX_BATCH;
|
|
529
|
+
this.keepalive = options.keepalive === true;
|
|
530
|
+
this.onDrop = options.onDrop ?? createDropWarner(options.name ?? "pulsar");
|
|
531
|
+
this.fetchImpl = options.fetch;
|
|
532
|
+
this.random = options.random ?? (() => Math.random());
|
|
533
|
+
}
|
|
534
|
+
/** Queues one entry. Synchronous; never throws. */
|
|
535
|
+
push(entry) {
|
|
536
|
+
try {
|
|
537
|
+
if (this.closed) {
|
|
538
|
+
this.drop("shutdown", 1);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (!entry.id) entry.id = generateId();
|
|
542
|
+
this.queue.push(serializeEntry(entry, this.maxEntryBytes));
|
|
543
|
+
this.enforceLimits();
|
|
544
|
+
const now = Date.now();
|
|
545
|
+
if (this.exitDeadline !== 0 && now < this.exitDeadline) {
|
|
546
|
+
this.kick();
|
|
547
|
+
} else if (this.exitDeadline !== 0 && now <= this.exitDeadline + EXIT_GRACE_MS) {
|
|
548
|
+
this.armFlushTimer();
|
|
549
|
+
} else if (this.queue.length >= this.batchSize) {
|
|
550
|
+
this.kick();
|
|
551
|
+
} else {
|
|
552
|
+
this.armFlushTimer();
|
|
553
|
+
}
|
|
554
|
+
} catch {
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
/** Snapshot of the delivery counters. */
|
|
558
|
+
stats() {
|
|
559
|
+
return {
|
|
560
|
+
...this.counters,
|
|
561
|
+
buffered: this.queue.length + this.inflightCount,
|
|
562
|
+
dropped: { ...this.counters.dropped }
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Sends everything buffered now, retrying retryable failures (and honouring
|
|
567
|
+
* backoff) until the buffer is empty or `timeout` ms pass; it resolves by
|
|
568
|
+
* then at the latest. Entries still undelivered stay buffered for the
|
|
569
|
+
* background retry. Never rejects.
|
|
570
|
+
*/
|
|
571
|
+
async flush(timeout = this.shutdownTimeout) {
|
|
572
|
+
const deadline = Date.now() + Math.max(0, timeout);
|
|
573
|
+
this.draining++;
|
|
574
|
+
const previousDeadline = this.drainDeadline;
|
|
575
|
+
this.drainDeadline = previousDeadline === 0 ? deadline : Math.min(previousDeadline, deadline);
|
|
576
|
+
try {
|
|
577
|
+
for (; ; ) {
|
|
578
|
+
const left = deadline - Date.now();
|
|
579
|
+
if (this.inflight) {
|
|
580
|
+
if (left <= 0) return;
|
|
581
|
+
await settleWithin(this.inflight, left);
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (this.queue.length === 0 || left <= 0) return;
|
|
585
|
+
if (this.backingOff) {
|
|
586
|
+
if (this.backoffUntil > deadline) return;
|
|
587
|
+
ref(this.timer);
|
|
588
|
+
await new Promise((resolve) => this.backoffWaiters.push(resolve));
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
this.kick();
|
|
592
|
+
if (!this.inflight) return;
|
|
593
|
+
}
|
|
594
|
+
} catch {
|
|
595
|
+
} finally {
|
|
596
|
+
this.draining--;
|
|
597
|
+
this.drainDeadline = this.draining === 0 ? 0 : previousDeadline;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Flushes (bounded by `shutdownTimeout`), stops timers and drops whatever
|
|
602
|
+
* is still undelivered, counted as `shutdown`. Entries pushed afterwards
|
|
603
|
+
* are dropped too. Never rejects.
|
|
604
|
+
*/
|
|
605
|
+
async close() {
|
|
606
|
+
if (this.closed) return;
|
|
607
|
+
this.closed = true;
|
|
608
|
+
await this.flush(this.shutdownTimeout);
|
|
609
|
+
this.dropBuffered("shutdown");
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Hook for `process.on('beforeExit')`. Sends what is left, keeping the
|
|
613
|
+
* process alive only while a request (or a retry due before the deadline)
|
|
614
|
+
* is pending, for at most `shutdownTimeout` per exit. What can't be sent in
|
|
615
|
+
* time is dropped and counted (`shutdown`) so the process can exit.
|
|
616
|
+
*/
|
|
617
|
+
onBeforeExit() {
|
|
618
|
+
if (this.closed) return;
|
|
619
|
+
const now = Date.now();
|
|
620
|
+
if (this.exitDeadline === 0 || now > this.exitDeadline + EXIT_GRACE_MS) {
|
|
621
|
+
this.exitDeadline = now + this.shutdownTimeout;
|
|
622
|
+
}
|
|
623
|
+
if (this.queue.length === 0 && !this.inflight) return;
|
|
624
|
+
const remaining = this.exitDeadline - now;
|
|
625
|
+
if (remaining <= 0 || this.backingOff && this.backoffUntil > this.exitDeadline) {
|
|
626
|
+
this.dropBuffered("shutdown");
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
void this.flush(remaining);
|
|
630
|
+
}
|
|
631
|
+
/** Hook for termination signals: flush within `shutdownTimeout`, then drop the rest. */
|
|
632
|
+
async onSignal() {
|
|
633
|
+
if (this.closed) return;
|
|
634
|
+
await this.flush(this.shutdownTimeout);
|
|
635
|
+
this.dropBuffered("shutdown");
|
|
636
|
+
}
|
|
637
|
+
dropBuffered(reason) {
|
|
638
|
+
this.clearTimer();
|
|
639
|
+
this.backingOff = false;
|
|
640
|
+
this.backoffUntil = 0;
|
|
641
|
+
this.wakeBackoffWaiters();
|
|
642
|
+
const n = this.queue.clear();
|
|
643
|
+
if (n > 0) this.drop(reason, n);
|
|
644
|
+
}
|
|
645
|
+
drop(reason, count, status, error) {
|
|
646
|
+
if (count <= 0) return;
|
|
647
|
+
this.counters.dropped[reason] += count;
|
|
648
|
+
try {
|
|
649
|
+
this.onDrop({ reason, count, ...status !== void 0 ? { status } : {}, ...error ? { error } : {} });
|
|
650
|
+
} catch {
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
enforceLimits() {
|
|
654
|
+
let evicted = 0;
|
|
655
|
+
while (this.queue.length > this.maxBufferSize || this.queue.bytes > this.maxBufferBytes && this.queue.length > 1) {
|
|
656
|
+
this.queue.shift();
|
|
657
|
+
evicted++;
|
|
658
|
+
}
|
|
659
|
+
if (evicted > 0) {
|
|
660
|
+
if (this.retryCount > 0) {
|
|
661
|
+
this.retryCount = Math.max(0, this.retryCount - evicted);
|
|
662
|
+
if (this.retryCount === 0) this.attempt = 0;
|
|
663
|
+
}
|
|
664
|
+
this.drop("overflow", evicted);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
clearTimer() {
|
|
668
|
+
if (this.timer !== null) {
|
|
669
|
+
clearTimeout(this.timer);
|
|
670
|
+
this.timer = null;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
/** Arms the flush-interval timer; `fromLoop` is the send loop handing over. */
|
|
674
|
+
armFlushTimer(fromLoop = false) {
|
|
675
|
+
if (this.timer !== null || this.inflight && !fromLoop || this.backingOff || this.closed || this.queue.length === 0) return;
|
|
676
|
+
this.timer = unref(setTimeout(() => {
|
|
677
|
+
this.timer = null;
|
|
678
|
+
this.kick();
|
|
679
|
+
}, this.flushInterval));
|
|
680
|
+
}
|
|
681
|
+
wakeBackoffWaiters() {
|
|
682
|
+
const waiters = this.backoffWaiters;
|
|
683
|
+
this.backoffWaiters = [];
|
|
684
|
+
for (const wake of waiters) wake();
|
|
685
|
+
}
|
|
686
|
+
/** Starts the send loop unless a request is in flight or a backoff is pending. */
|
|
687
|
+
kick() {
|
|
688
|
+
if (this.inflight || this.backingOff || this.queue.length === 0) return;
|
|
689
|
+
this.clearTimer();
|
|
690
|
+
const run = this.sendLoop().catch(() => void 0).finally(() => {
|
|
691
|
+
if (this.inflight === run) this.inflight = null;
|
|
692
|
+
});
|
|
693
|
+
this.inflight = run;
|
|
694
|
+
}
|
|
695
|
+
/** Whether to send the next batch now rather than wait for it to fill. */
|
|
696
|
+
eager() {
|
|
697
|
+
const now = Date.now();
|
|
698
|
+
if (this.queue.length >= this.batchSize) return true;
|
|
699
|
+
if (this.draining > 0 && (this.drainDeadline === 0 || now < this.drainDeadline)) return true;
|
|
700
|
+
return this.exitDeadline !== 0 && now < this.exitDeadline;
|
|
701
|
+
}
|
|
702
|
+
async sendLoop() {
|
|
703
|
+
while (this.queue.length > 0) {
|
|
704
|
+
const batch = this.takeBatch();
|
|
705
|
+
this.retryCount = 0;
|
|
706
|
+
this.inflightCount = batch.length;
|
|
707
|
+
let outcome;
|
|
708
|
+
try {
|
|
709
|
+
outcome = await this.send(batch);
|
|
710
|
+
} catch (err) {
|
|
711
|
+
outcome = { kind: "retry", retryAfter: null, error: err instanceof Error ? err.message : "send failed" };
|
|
712
|
+
}
|
|
713
|
+
this.inflightCount = 0;
|
|
714
|
+
if (this.closed && this.draining === 0 && (outcome.kind === "retry" || outcome.kind === "tooLarge")) {
|
|
715
|
+
this.drop("shutdown", batch.length, outcome.status);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
switch (outcome.kind) {
|
|
719
|
+
case "ok":
|
|
720
|
+
this.attempt = 0;
|
|
721
|
+
this.counters.sent += batch.length - outcome.rejected;
|
|
722
|
+
if (outcome.rejected > 0) this.drop("rejected", outcome.rejected, void 0, "entry rejected by server");
|
|
723
|
+
this.maxBatchEntries = Math.min(SERVER_MAX_BATCH, this.maxBatchEntries * 2);
|
|
724
|
+
break;
|
|
725
|
+
case "reject":
|
|
726
|
+
this.attempt = 0;
|
|
727
|
+
this.drop("rejected", batch.length, outcome.status, outcome.error);
|
|
728
|
+
break;
|
|
729
|
+
case "tooLarge":
|
|
730
|
+
if (batch.length > 1) {
|
|
731
|
+
const bytes = batch.reduce((sum, r) => sum + r.bytes, 0);
|
|
732
|
+
this.maxBatchBytes = Math.max(MIN_BATCH_BYTES, Math.floor(bytes / 2));
|
|
733
|
+
this.maxBatchEntries = Math.max(1, Math.floor(batch.length / 2));
|
|
734
|
+
this.queue.unshiftAll(batch);
|
|
735
|
+
this.enforceLimits();
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
this.attempt = 0;
|
|
739
|
+
this.drop("rejected", 1, 413, "entry too large for the server");
|
|
740
|
+
break;
|
|
741
|
+
case "retry":
|
|
742
|
+
this.counters.retries++;
|
|
743
|
+
this.attempt++;
|
|
744
|
+
if (this.attempt > this.maxRetries) {
|
|
745
|
+
this.attempt = 0;
|
|
746
|
+
this.drop("retriesExhausted", batch.length, outcome.status, outcome.error);
|
|
747
|
+
if (this.queue.length > 0) this.startBackoff(this.backoffDelay(1, outcome.retryAfter));
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
this.queue.unshiftAll(batch);
|
|
751
|
+
this.retryCount = batch.length;
|
|
752
|
+
this.enforceLimits();
|
|
753
|
+
this.startBackoff(this.backoffDelay(this.attempt, outcome.retryAfter));
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
if (this.queue.length === 0) return;
|
|
757
|
+
if (!this.eager()) {
|
|
758
|
+
this.armFlushTimer(true);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
backoffDelay(attempt, retryAfter) {
|
|
764
|
+
if (retryAfter !== null) {
|
|
765
|
+
return retryAfter + Math.floor(this.random() * Math.min(1e3, retryAfter * 0.1 + 100));
|
|
766
|
+
}
|
|
767
|
+
const exp = Math.min(RETRY_MAX_MS, RETRY_BASE_MS * 2 ** (attempt - 1));
|
|
768
|
+
return Math.floor(exp / 2 + this.random() * (exp / 2));
|
|
769
|
+
}
|
|
770
|
+
startBackoff(delay) {
|
|
771
|
+
this.clearTimer();
|
|
772
|
+
this.backingOff = true;
|
|
773
|
+
this.backoffUntil = Date.now() + delay;
|
|
774
|
+
this.timer = unref(setTimeout(() => {
|
|
775
|
+
this.timer = null;
|
|
776
|
+
this.backingOff = false;
|
|
777
|
+
this.backoffUntil = 0;
|
|
778
|
+
this.wakeBackoffWaiters();
|
|
779
|
+
this.kick();
|
|
780
|
+
}, delay));
|
|
781
|
+
}
|
|
782
|
+
takeBatch() {
|
|
783
|
+
const batch = [];
|
|
784
|
+
let bytes = 12;
|
|
785
|
+
const retrying = this.retryCount > 0;
|
|
786
|
+
const maxCount = this.single ? 1 : retrying ? this.retryCount : this.maxBatchEntries;
|
|
787
|
+
for (; ; ) {
|
|
788
|
+
const next = this.queue.peek();
|
|
789
|
+
if (!next || batch.length >= maxCount) break;
|
|
790
|
+
if (!retrying && batch.length > 0 && bytes + next.bytes + 1 > this.maxBatchBytes) break;
|
|
791
|
+
this.queue.shift();
|
|
792
|
+
batch.push(next);
|
|
793
|
+
bytes += next.bytes + 1;
|
|
794
|
+
}
|
|
795
|
+
return batch;
|
|
796
|
+
}
|
|
797
|
+
requestTimeoutNow() {
|
|
798
|
+
const now = Date.now();
|
|
799
|
+
let timeout = this.requestTimeout;
|
|
800
|
+
for (const deadline of [this.drainDeadline, this.exitDeadline]) {
|
|
801
|
+
if (deadline !== 0 && deadline > now) timeout = Math.min(timeout, deadline - now);
|
|
802
|
+
}
|
|
803
|
+
return Math.max(1, timeout);
|
|
804
|
+
}
|
|
805
|
+
async send(batch) {
|
|
806
|
+
const fetchImpl = this.fetchImpl ?? globalThis.fetch;
|
|
807
|
+
if (typeof fetchImpl !== "function") {
|
|
808
|
+
return { kind: "reject", error: "fetch is not available in this runtime" };
|
|
809
|
+
}
|
|
810
|
+
const body = this.single ? batch[0].json : `{"logs":[${batch.map((r) => r.json).join(",")}]}`;
|
|
811
|
+
const headers = { "Content-Type": "application/json" };
|
|
812
|
+
if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
|
|
813
|
+
const timeout = this.requestTimeoutNow();
|
|
814
|
+
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
815
|
+
const timer = controller ? unref(setTimeout(() => controller.abort(), timeout)) : null;
|
|
816
|
+
const init = {
|
|
817
|
+
method: "POST",
|
|
818
|
+
headers,
|
|
819
|
+
body,
|
|
820
|
+
...controller ? { signal: controller.signal } : {},
|
|
821
|
+
...this.keepalive ? { keepalive: true } : {}
|
|
822
|
+
};
|
|
823
|
+
init[INTERNAL_REQUEST] = true;
|
|
824
|
+
try {
|
|
825
|
+
const res = await fetchImpl(this.endpoint, init);
|
|
826
|
+
const text2 = await readText(res);
|
|
827
|
+
return classify(res, text2, batch.length);
|
|
828
|
+
} catch (err) {
|
|
829
|
+
const aborted = controller?.signal.aborted === true;
|
|
830
|
+
return {
|
|
831
|
+
kind: "retry",
|
|
832
|
+
retryAfter: null,
|
|
833
|
+
error: aborted ? `request timed out after ${timeout} ms` : err instanceof Error ? err.message : "network error"
|
|
834
|
+
};
|
|
835
|
+
} finally {
|
|
836
|
+
if (timer !== null) clearTimeout(timer);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
function settleWithin(promise, ms) {
|
|
841
|
+
return new Promise((resolve) => {
|
|
842
|
+
const timer = unref(setTimeout(resolve, ms));
|
|
843
|
+
promise.then(
|
|
844
|
+
() => {
|
|
845
|
+
clearTimeout(timer);
|
|
846
|
+
resolve();
|
|
847
|
+
},
|
|
848
|
+
() => {
|
|
849
|
+
clearTimeout(timer);
|
|
850
|
+
resolve();
|
|
851
|
+
}
|
|
852
|
+
);
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
async function readText(res) {
|
|
856
|
+
try {
|
|
857
|
+
return await res.text();
|
|
858
|
+
} catch {
|
|
859
|
+
return "";
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
function errorText(res, text2) {
|
|
863
|
+
try {
|
|
864
|
+
const parsed = JSON.parse(text2);
|
|
865
|
+
if (typeof parsed?.error === "string") return truncateString(parsed.error, 200);
|
|
866
|
+
} catch {
|
|
867
|
+
}
|
|
868
|
+
return res.statusText || `HTTP ${res.status}`;
|
|
869
|
+
}
|
|
870
|
+
function classify(res, text2, count) {
|
|
871
|
+
const status = res.status;
|
|
872
|
+
if (status >= 200 && status < 300) {
|
|
873
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
874
|
+
if (res.redirected && !contentType.includes("json")) {
|
|
875
|
+
return { kind: "reject", status, error: `redirected to ${res.url}; set url to the final address` };
|
|
876
|
+
}
|
|
877
|
+
let rejected = 0;
|
|
878
|
+
if (contentType.includes("json") && text2.length > 0 && text2.length < 1e6) {
|
|
879
|
+
try {
|
|
880
|
+
const parsed = JSON.parse(text2);
|
|
881
|
+
if (Array.isArray(parsed?.failed)) rejected = Math.min(parsed.failed.length, count);
|
|
882
|
+
} catch {
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return { kind: "ok", rejected };
|
|
886
|
+
}
|
|
887
|
+
if (status === 413) return { kind: "tooLarge" };
|
|
888
|
+
if (status === 429 || status === 408 || status >= 500) {
|
|
889
|
+
return { kind: "retry", retryAfter: parseRetryAfter(res.headers.get("retry-after")), status, error: errorText(res, text2) };
|
|
890
|
+
}
|
|
891
|
+
return { kind: "reject", status, error: errorText(res, text2) };
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// ../shared/src/trace.ts
|
|
895
|
+
var import_node_async_hooks = require("async_hooks");
|
|
896
|
+
var import_node_crypto = require("crypto");
|
|
897
|
+
var TRACEPARENT_HEADER = "traceparent";
|
|
898
|
+
var TRACESTATE_HEADER = "tracestate";
|
|
899
|
+
var TRACE_ID_HEADER = "x-trace-id";
|
|
900
|
+
var MAX_TRACESTATE_CHARS = 512;
|
|
901
|
+
var ZERO_TRACE_ID = "0".repeat(32);
|
|
902
|
+
var ZERO_SPAN_ID = "0".repeat(16);
|
|
903
|
+
var TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})(-.*)?$/;
|
|
904
|
+
var TRACE_ID_RE = /^[0-9a-f]{32}$/;
|
|
905
|
+
var SPAN_ID_RE = /^[0-9a-f]{16}$/;
|
|
906
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
907
|
+
function newTraceId() {
|
|
908
|
+
for (; ; ) {
|
|
909
|
+
const id = (0, import_node_crypto.randomBytes)(16).toString("hex");
|
|
910
|
+
if (id !== ZERO_TRACE_ID) return id;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
function newSpanId() {
|
|
914
|
+
for (; ; ) {
|
|
915
|
+
const id = (0, import_node_crypto.randomBytes)(8).toString("hex");
|
|
916
|
+
if (id !== ZERO_SPAN_ID) return id;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
function sha256Hex(value) {
|
|
920
|
+
return (0, import_node_crypto.createHash)("sha256").update(value, "utf8").digest("hex");
|
|
921
|
+
}
|
|
922
|
+
function toW3CTraceId(traceId) {
|
|
923
|
+
const lower = traceId.toLowerCase();
|
|
924
|
+
if (TRACE_ID_RE.test(lower) && lower !== ZERO_TRACE_ID) return lower;
|
|
925
|
+
if (UUID_RE.test(lower)) {
|
|
926
|
+
const hex = lower.replace(/-/g, "");
|
|
927
|
+
if (hex !== ZERO_TRACE_ID) return hex;
|
|
928
|
+
}
|
|
929
|
+
return sha256Hex(traceId).slice(0, 32);
|
|
930
|
+
}
|
|
931
|
+
function toW3CSpanId(spanId) {
|
|
932
|
+
const lower = spanId.toLowerCase();
|
|
933
|
+
if (SPAN_ID_RE.test(lower) && lower !== ZERO_SPAN_ID) return lower;
|
|
934
|
+
return sha256Hex(spanId).slice(0, 16);
|
|
935
|
+
}
|
|
936
|
+
function parseTraceparent(value) {
|
|
937
|
+
if (typeof value !== "string") return null;
|
|
938
|
+
const header = value.trim();
|
|
939
|
+
const match = TRACEPARENT_RE.exec(header);
|
|
940
|
+
if (!match) return null;
|
|
941
|
+
const [, version, traceId, parentId, flagsHex, rest] = match;
|
|
942
|
+
if (version === "ff") return null;
|
|
943
|
+
if (version === "00" && rest !== void 0) return null;
|
|
944
|
+
if (traceId === ZERO_TRACE_ID || parentId === ZERO_SPAN_ID) return null;
|
|
945
|
+
const flags = parseInt(flagsHex, 16);
|
|
946
|
+
return { version, traceId, parentId, flags, sampled: (flags & 1) === 1 };
|
|
947
|
+
}
|
|
948
|
+
function formatTraceparent(ctx) {
|
|
949
|
+
return `00-${toW3CTraceId(ctx.traceId)}-${toW3CSpanId(ctx.spanId)}-${ctx.sampled === false ? "00" : "01"}`;
|
|
950
|
+
}
|
|
951
|
+
var STORE_KEY = /* @__PURE__ */ Symbol.for("@fabbahiense/pulsar.trace-context.v1");
|
|
952
|
+
function sharedStore() {
|
|
953
|
+
const g = globalThis;
|
|
954
|
+
let store = g[STORE_KEY];
|
|
955
|
+
if (!store) {
|
|
956
|
+
store = new import_node_async_hooks.AsyncLocalStorage();
|
|
957
|
+
g[STORE_KEY] = store;
|
|
958
|
+
}
|
|
959
|
+
return store;
|
|
960
|
+
}
|
|
961
|
+
var traceStore = sharedStore();
|
|
962
|
+
function getTraceContext() {
|
|
963
|
+
return traceStore.getStore() ?? null;
|
|
964
|
+
}
|
|
965
|
+
function text(value) {
|
|
966
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
967
|
+
}
|
|
968
|
+
function completeContext(init) {
|
|
969
|
+
const ctx = {
|
|
970
|
+
traceId: text(init?.traceId) ?? newTraceId(),
|
|
971
|
+
spanId: text(init?.spanId) ?? newSpanId(),
|
|
972
|
+
sampled: init?.sampled !== false
|
|
973
|
+
};
|
|
974
|
+
const parentSpanId = text(init?.parentSpanId);
|
|
975
|
+
if (parentSpanId) ctx.parentSpanId = parentSpanId;
|
|
976
|
+
const traceState = text(init?.traceState);
|
|
977
|
+
if (traceState) ctx.traceState = traceState;
|
|
978
|
+
const method = text(init?.method);
|
|
979
|
+
if (method) ctx.method = method;
|
|
980
|
+
const url = text(init?.url);
|
|
981
|
+
if (url) ctx.url = url;
|
|
982
|
+
return ctx;
|
|
983
|
+
}
|
|
984
|
+
function childContext(parent) {
|
|
985
|
+
if (!parent || !text(parent.traceId)) return completeContext(null);
|
|
986
|
+
const local = parent;
|
|
987
|
+
return completeContext({
|
|
988
|
+
traceId: parent.traceId,
|
|
989
|
+
parentSpanId: parent.spanId,
|
|
990
|
+
sampled: parent.sampled,
|
|
991
|
+
traceState: parent.traceState,
|
|
992
|
+
method: local.method,
|
|
993
|
+
url: local.url
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
function runWithTrace(ctx, fn) {
|
|
997
|
+
return traceStore.run(completeContext(ctx), fn);
|
|
998
|
+
}
|
|
999
|
+
function read(carrier, name) {
|
|
1000
|
+
let value;
|
|
1001
|
+
if (typeof carrier.get === "function") {
|
|
1002
|
+
value = carrier.get(name);
|
|
1003
|
+
} else {
|
|
1004
|
+
const record = carrier;
|
|
1005
|
+
value = record[name];
|
|
1006
|
+
if (value === void 0) {
|
|
1007
|
+
for (const key of Object.keys(record)) {
|
|
1008
|
+
if (key.toLowerCase() === name) {
|
|
1009
|
+
value = record[key];
|
|
1010
|
+
break;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
if (Array.isArray(value)) value = value.find((v) => typeof v === "string");
|
|
1016
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1017
|
+
}
|
|
1018
|
+
function readTraceState(carrier) {
|
|
1019
|
+
let raw;
|
|
1020
|
+
if (typeof carrier.get === "function") {
|
|
1021
|
+
raw = carrier.get(TRACESTATE_HEADER);
|
|
1022
|
+
} else {
|
|
1023
|
+
const record = carrier;
|
|
1024
|
+
raw = record[TRACESTATE_HEADER];
|
|
1025
|
+
if (raw === void 0) {
|
|
1026
|
+
const key = Object.keys(record).find((k) => k.toLowerCase() === TRACESTATE_HEADER);
|
|
1027
|
+
raw = key === void 0 ? void 0 : record[key];
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
const joined = Array.isArray(raw) ? raw.filter((v) => typeof v === "string").join(",") : raw;
|
|
1031
|
+
if (typeof joined !== "string") return void 0;
|
|
1032
|
+
const value = joined.trim();
|
|
1033
|
+
return value.length > 0 && value.length <= MAX_TRACESTATE_CHARS ? value : void 0;
|
|
1034
|
+
}
|
|
1035
|
+
function extractTrace(carrier) {
|
|
1036
|
+
if (typeof carrier !== "object" || carrier === null) return null;
|
|
1037
|
+
try {
|
|
1038
|
+
const source = carrier;
|
|
1039
|
+
const legacy = read(source, TRACE_ID_HEADER);
|
|
1040
|
+
const parent = parseTraceparent(read(source, TRACEPARENT_HEADER));
|
|
1041
|
+
if (legacy !== void 0) {
|
|
1042
|
+
if (parent && parent.traceId === toW3CTraceId(legacy)) {
|
|
1043
|
+
return withTraceState({ traceId: legacy, spanId: parent.parentId, sampled: parent.sampled }, source);
|
|
1044
|
+
}
|
|
1045
|
+
return { traceId: legacy, sampled: true };
|
|
1046
|
+
}
|
|
1047
|
+
if (parent) return withTraceState({ traceId: parent.traceId, spanId: parent.parentId, sampled: parent.sampled }, source);
|
|
1048
|
+
return null;
|
|
1049
|
+
} catch {
|
|
1050
|
+
return null;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
function withTraceState(trace, carrier) {
|
|
1054
|
+
const traceState = readTraceState(carrier);
|
|
1055
|
+
if (traceState) trace.traceState = traceState;
|
|
1056
|
+
return trace;
|
|
1057
|
+
}
|
|
1058
|
+
function traceHeaders(ctx = getTraceContext()) {
|
|
1059
|
+
if (!ctx) return {};
|
|
1060
|
+
const headers = {
|
|
1061
|
+
[TRACEPARENT_HEADER]: formatTraceparent(ctx),
|
|
1062
|
+
[TRACE_ID_HEADER]: ctx.traceId
|
|
1063
|
+
};
|
|
1064
|
+
if (ctx.traceState) headers[TRACESTATE_HEADER] = ctx.traceState;
|
|
1065
|
+
return headers;
|
|
1066
|
+
}
|
|
1067
|
+
function injectTrace(carrier, ctx = getTraceContext()) {
|
|
1068
|
+
if (!ctx || typeof carrier !== "object" || carrier === null) return carrier;
|
|
1069
|
+
try {
|
|
1070
|
+
const headers = traceHeaders(ctx);
|
|
1071
|
+
const target = carrier;
|
|
1072
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
1073
|
+
if (typeof target.set === "function") target.set(name, value);
|
|
1074
|
+
else carrier[name] = value;
|
|
1075
|
+
}
|
|
1076
|
+
} catch {
|
|
1077
|
+
}
|
|
1078
|
+
return carrier;
|
|
1079
|
+
}
|
|
1080
|
+
function round3(ms) {
|
|
1081
|
+
return Math.round(ms * 1e3) / 1e3;
|
|
1082
|
+
}
|
|
1083
|
+
function isThenable(value) {
|
|
1084
|
+
return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
|
|
1085
|
+
}
|
|
1086
|
+
function openSpan(ctx, kind, sink, base = {}) {
|
|
1087
|
+
const start = Date.now();
|
|
1088
|
+
const t0 = performance.now();
|
|
1089
|
+
let done = false;
|
|
1090
|
+
return (name, extra = {}) => {
|
|
1091
|
+
if (done || !sink) return;
|
|
1092
|
+
done = true;
|
|
1093
|
+
try {
|
|
1094
|
+
const duration = round3(performance.now() - t0);
|
|
1095
|
+
sink({ ...base, ...extra, name, kind, context: ctx, start, end: start + duration, duration });
|
|
1096
|
+
} catch {
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
function runSpan(name, fn, options = {}, sink = null) {
|
|
1101
|
+
const parent = options.parent === void 0 ? getTraceContext() : options.parent;
|
|
1102
|
+
const ctx = childContext(parent);
|
|
1103
|
+
const spanName = typeof name === "string" ? name || "span" : name === void 0 || name === null ? "span" : formatArgs([name]);
|
|
1104
|
+
const finish = openSpan(ctx, options.kind ?? "internal", sink, {
|
|
1105
|
+
attributes: options.attributes,
|
|
1106
|
+
serviceName: options.serviceName
|
|
1107
|
+
});
|
|
1108
|
+
let result;
|
|
1109
|
+
try {
|
|
1110
|
+
result = traceStore.run(ctx, () => fn(ctx));
|
|
1111
|
+
} catch (err) {
|
|
1112
|
+
finish(spanName, { error: err });
|
|
1113
|
+
throw err;
|
|
1114
|
+
}
|
|
1115
|
+
if (isThenable(result)) {
|
|
1116
|
+
return result.then(
|
|
1117
|
+
(value) => {
|
|
1118
|
+
finish(spanName);
|
|
1119
|
+
return value;
|
|
1120
|
+
},
|
|
1121
|
+
(err) => {
|
|
1122
|
+
finish(spanName, { error: err });
|
|
1123
|
+
throw err;
|
|
1124
|
+
}
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
finish(spanName);
|
|
1128
|
+
return result;
|
|
1129
|
+
}
|
|
1130
|
+
function asError(value) {
|
|
1131
|
+
return value instanceof Error ? value : new Error(formatArgs([value]));
|
|
1132
|
+
}
|
|
1133
|
+
function spanLevel(span) {
|
|
1134
|
+
if (span.error !== void 0) return "error";
|
|
1135
|
+
const status = span.statusCode;
|
|
1136
|
+
if (typeof status === "number" && status >= 500) return "error";
|
|
1137
|
+
if (typeof status === "number" && status >= 400) return "warn";
|
|
1138
|
+
return "info";
|
|
1139
|
+
}
|
|
1140
|
+
function spanEntry(span) {
|
|
1141
|
+
const details = span.error !== void 0 ? errorDetails(asError(span.error)) : void 0;
|
|
1142
|
+
const entry = {
|
|
1143
|
+
level: spanLevel(span),
|
|
1144
|
+
msg: span.name,
|
|
1145
|
+
timestamp: Math.round(span.end),
|
|
1146
|
+
traceId: span.context.traceId,
|
|
1147
|
+
spanId: span.context.spanId,
|
|
1148
|
+
spanKind: span.kind,
|
|
1149
|
+
responseTime: span.duration,
|
|
1150
|
+
metadata: span.attributes ? toJsonSafe(span.attributes) : null
|
|
1151
|
+
};
|
|
1152
|
+
if (span.context.parentSpanId) entry.parentSpanId = span.context.parentSpanId;
|
|
1153
|
+
if (span.method) entry.method = span.method;
|
|
1154
|
+
if (span.url) entry.url = span.url;
|
|
1155
|
+
if (span.statusCode !== void 0) entry.statusCode = span.statusCode;
|
|
1156
|
+
if (details) {
|
|
1157
|
+
entry.errorMessage = details.message;
|
|
1158
|
+
entry.errorStack = details.stack;
|
|
1159
|
+
}
|
|
1160
|
+
return entry;
|
|
1161
|
+
}
|
|
1162
|
+
var INTERNAL_REQUEST2 = /* @__PURE__ */ Symbol.for("@fabbahiense/pulsar.internal-request");
|
|
1163
|
+
var INSTRUMENTED = /* @__PURE__ */ Symbol.for("@fabbahiense/pulsar.instrumented");
|
|
1164
|
+
function safeUrl(raw) {
|
|
1165
|
+
try {
|
|
1166
|
+
const url = new URL(raw);
|
|
1167
|
+
url.username = "";
|
|
1168
|
+
url.password = "";
|
|
1169
|
+
url.search = "";
|
|
1170
|
+
url.hash = "";
|
|
1171
|
+
return url.toString();
|
|
1172
|
+
} catch {
|
|
1173
|
+
return raw.split(/[?#]/)[0];
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
function clientSpanName(method, url, status) {
|
|
1177
|
+
return `${method} ${url} ${status === void 0 ? "failed" : status}`;
|
|
1178
|
+
}
|
|
1179
|
+
function startOutbound(options, getSink, method, url) {
|
|
1180
|
+
const parent = getTraceContext();
|
|
1181
|
+
if (!parent) return null;
|
|
1182
|
+
const sink = options.spans === false ? null : getSink();
|
|
1183
|
+
const span = sink ? childContext(parent) : parent;
|
|
1184
|
+
const finish = openSpan(span, "client", sink, { method, url, serviceName: options.serviceName });
|
|
1185
|
+
return {
|
|
1186
|
+
headers: traceHeaders(span),
|
|
1187
|
+
end(status, error) {
|
|
1188
|
+
finish(clientSpanName(method, url, status), status === void 0 ? { error } : { statusCode: status });
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
function requestParts(input, init) {
|
|
1193
|
+
const isRequest = typeof Request === "function" && input instanceof Request;
|
|
1194
|
+
const method = String(init?.method ?? (isRequest ? input.method : "GET")).toUpperCase();
|
|
1195
|
+
const url = isRequest ? input.url : String(input);
|
|
1196
|
+
const base = init?.headers ?? (isRequest ? input.headers : void 0);
|
|
1197
|
+
return { method, url, base };
|
|
1198
|
+
}
|
|
1199
|
+
function wrapFetch(fetchImpl, options, getSink) {
|
|
1200
|
+
if (typeof fetchImpl !== "function") throw new TypeError("instrumentFetch: no fetch implementation to wrap");
|
|
1201
|
+
if (fetchImpl[INSTRUMENTED]) return fetchImpl;
|
|
1202
|
+
const wrapped = function(input, init) {
|
|
1203
|
+
let call = null;
|
|
1204
|
+
let nextInit = init;
|
|
1205
|
+
try {
|
|
1206
|
+
if (!init?.[INTERNAL_REQUEST2]) {
|
|
1207
|
+
const { method, url, base } = requestParts(input, init);
|
|
1208
|
+
call = startOutbound(options, getSink, method, safeUrl(url));
|
|
1209
|
+
if (call) {
|
|
1210
|
+
const headers = new Headers(base);
|
|
1211
|
+
for (const [name, value] of Object.entries(call.headers)) headers.set(name, value);
|
|
1212
|
+
nextInit = { ...init, headers };
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
} catch {
|
|
1216
|
+
call = null;
|
|
1217
|
+
nextInit = init;
|
|
1218
|
+
}
|
|
1219
|
+
const pending = fetchImpl(input, nextInit);
|
|
1220
|
+
if (!call) return pending;
|
|
1221
|
+
const active = call;
|
|
1222
|
+
return pending.then(
|
|
1223
|
+
(res) => {
|
|
1224
|
+
active.end(res.status);
|
|
1225
|
+
return res;
|
|
1226
|
+
},
|
|
1227
|
+
(err) => {
|
|
1228
|
+
active.end(void 0, err);
|
|
1229
|
+
throw err;
|
|
1230
|
+
}
|
|
1231
|
+
);
|
|
1232
|
+
};
|
|
1233
|
+
Object.defineProperty(wrapped, INSTRUMENTED, { value: true });
|
|
1234
|
+
return wrapped;
|
|
1235
|
+
}
|
|
1236
|
+
function axiosUrl(config) {
|
|
1237
|
+
const url = typeof config.url === "string" ? config.url : "";
|
|
1238
|
+
const base = typeof config.baseURL === "string" ? config.baseURL : "";
|
|
1239
|
+
if (!base || /^[a-z][a-z\d+\-.]*:\/\//i.test(url)) return url;
|
|
1240
|
+
return `${base.replace(/\/+$/, "")}/${url.replace(/^\/+/, "")}`;
|
|
1241
|
+
}
|
|
1242
|
+
function setAxiosHeaders(config, headers) {
|
|
1243
|
+
const current = config.headers;
|
|
1244
|
+
if (current && typeof current.set === "function") {
|
|
1245
|
+
for (const [name, value] of Object.entries(headers)) current.set(name, value);
|
|
1246
|
+
} else if (current && typeof current === "object") {
|
|
1247
|
+
Object.assign(current, headers);
|
|
1248
|
+
} else {
|
|
1249
|
+
config.headers = { ...headers };
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
function instrumentAxiosInstance(instance, options, getSink) {
|
|
1253
|
+
if (!instance || typeof instance.interceptors?.request?.use !== "function" || typeof instance.interceptors?.response?.use !== "function") {
|
|
1254
|
+
throw new TypeError("instrumentAxios: expected an axios instance (with interceptors.request.use and interceptors.response.use)");
|
|
1255
|
+
}
|
|
1256
|
+
const target = instance;
|
|
1257
|
+
if (target[INSTRUMENTED]) return instance;
|
|
1258
|
+
Object.defineProperty(instance, INSTRUMENTED, { value: true });
|
|
1259
|
+
const calls = /* @__PURE__ */ new WeakMap();
|
|
1260
|
+
const finish = (config, status, error) => {
|
|
1261
|
+
if (typeof config !== "object" || config === null) return;
|
|
1262
|
+
const call = calls.get(config);
|
|
1263
|
+
if (!call) return;
|
|
1264
|
+
calls.delete(config);
|
|
1265
|
+
call.end(status, error);
|
|
1266
|
+
};
|
|
1267
|
+
instance.interceptors.request.use((config) => {
|
|
1268
|
+
try {
|
|
1269
|
+
if (config && typeof config === "object" && !config[INTERNAL_REQUEST2]) {
|
|
1270
|
+
const method = String(config.method ?? "get").toUpperCase();
|
|
1271
|
+
const call = startOutbound(options, getSink, method, safeUrl(axiosUrl(config)));
|
|
1272
|
+
if (call) {
|
|
1273
|
+
setAxiosHeaders(config, call.headers);
|
|
1274
|
+
calls.set(config, call);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
} catch {
|
|
1278
|
+
}
|
|
1279
|
+
return config;
|
|
1280
|
+
});
|
|
1281
|
+
instance.interceptors.response.use(
|
|
1282
|
+
(response) => {
|
|
1283
|
+
finish(response?.config, typeof response?.status === "number" ? response.status : void 0);
|
|
1284
|
+
return response;
|
|
1285
|
+
},
|
|
1286
|
+
(error) => {
|
|
1287
|
+
const status = error?.response?.status;
|
|
1288
|
+
finish(error?.config, typeof status === "number" ? status : void 0, error);
|
|
1289
|
+
return Promise.reject(error);
|
|
1290
|
+
}
|
|
1291
|
+
);
|
|
1292
|
+
return instance;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// src/trace.ts
|
|
1296
|
+
function pulsarMixin() {
|
|
1297
|
+
return () => {
|
|
1298
|
+
const ctx = getTraceContext();
|
|
1299
|
+
if (!ctx) return {};
|
|
1300
|
+
const fields = { traceId: ctx.traceId, spanId: ctx.spanId };
|
|
1301
|
+
if (ctx.parentSpanId) fields.parentSpanId = ctx.parentSpanId;
|
|
1302
|
+
return fields;
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
function asError2(value) {
|
|
1306
|
+
return value instanceof Error ? value : new Error(formatArgs([value]));
|
|
1307
|
+
}
|
|
1308
|
+
function pinoSpanSink(logger) {
|
|
1309
|
+
return (span) => {
|
|
1310
|
+
const entry = spanEntry(span);
|
|
1311
|
+
const record = {
|
|
1312
|
+
traceId: entry.traceId,
|
|
1313
|
+
spanId: entry.spanId,
|
|
1314
|
+
spanKind: entry.spanKind,
|
|
1315
|
+
responseTime: entry.responseTime
|
|
1316
|
+
};
|
|
1317
|
+
if (entry.parentSpanId) record.parentSpanId = entry.parentSpanId;
|
|
1318
|
+
if (entry.method || entry.url) record.req = { method: entry.method, url: entry.url };
|
|
1319
|
+
if (entry.statusCode !== void 0) record.res = { statusCode: entry.statusCode };
|
|
1320
|
+
if (span.error !== void 0) record.err = asError2(span.error);
|
|
1321
|
+
if (entry.metadata) record.metadata = entry.metadata;
|
|
1322
|
+
const level = entry.level;
|
|
1323
|
+
logger[level](record, entry.msg);
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
function startSpan(name, fn, options) {
|
|
1327
|
+
const logger = options?.logger;
|
|
1328
|
+
return runSpan(name, fn, options, logger ? pinoSpanSink(logger) : null);
|
|
1329
|
+
}
|
|
1330
|
+
function sinkFor(options) {
|
|
1331
|
+
const sink = options.logger ? pinoSpanSink(options.logger) : null;
|
|
1332
|
+
return () => sink;
|
|
1333
|
+
}
|
|
1334
|
+
function instrumentFetch(first, second) {
|
|
1335
|
+
const fetchImpl = typeof first === "function" ? first : globalThis.fetch;
|
|
1336
|
+
const options = (typeof first === "function" ? second : first) ?? {};
|
|
1337
|
+
return wrapFetch(fetchImpl, options, sinkFor(options));
|
|
1338
|
+
}
|
|
1339
|
+
function instrumentAxios(instance, options = {}) {
|
|
1340
|
+
return instrumentAxiosInstance(instance, options, sinkFor(options));
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
// src/index.ts
|
|
37
1344
|
var GENERIC_HTTP_MSGS = /* @__PURE__ */ new Set([
|
|
38
1345
|
"request completed",
|
|
39
1346
|
"request errored",
|
|
@@ -59,6 +1366,10 @@ var PINO_INTERNAL_FIELDS = /* @__PURE__ */ new Set([
|
|
|
59
1366
|
"err",
|
|
60
1367
|
"metadata"
|
|
61
1368
|
]);
|
|
1369
|
+
var SPAN_FIELDS = ["spanId", "parentSpanId", "spanKind"];
|
|
1370
|
+
function spanField(value) {
|
|
1371
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1372
|
+
}
|
|
62
1373
|
function pickFirst(obj, keys) {
|
|
63
1374
|
for (const k of keys) {
|
|
64
1375
|
if (obj[k] !== void 0 && obj[k] !== null) return obj[k];
|
|
@@ -98,37 +1409,94 @@ var LEVEL_MAP = {
|
|
|
98
1409
|
50: "error",
|
|
99
1410
|
60: "fatal"
|
|
100
1411
|
};
|
|
101
|
-
var
|
|
102
|
-
|
|
1412
|
+
var LEVEL_NAMES = new Set(Object.values(LEVEL_MAP));
|
|
1413
|
+
function levelName(level) {
|
|
1414
|
+
if (typeof level === "number" && Number.isFinite(level)) {
|
|
1415
|
+
if (LEVEL_MAP[level]) return LEVEL_MAP[level];
|
|
1416
|
+
if (level >= 60) return "fatal";
|
|
1417
|
+
if (level >= 50) return "error";
|
|
1418
|
+
if (level >= 40) return "warn";
|
|
1419
|
+
if (level >= 30) return "info";
|
|
1420
|
+
if (level >= 20) return "debug";
|
|
1421
|
+
return "trace";
|
|
1422
|
+
}
|
|
1423
|
+
if (typeof level === "string") {
|
|
1424
|
+
const lower = level.toLowerCase();
|
|
1425
|
+
if (LEVEL_NAMES.has(lower)) return lower;
|
|
1426
|
+
if (lower === "warning") return "warn";
|
|
1427
|
+
if (/^\d+$/.test(lower)) return levelName(Number(lower));
|
|
1428
|
+
}
|
|
1429
|
+
return "info";
|
|
1430
|
+
}
|
|
1431
|
+
var SECONDS_THRESHOLD = 1e11;
|
|
103
1432
|
function parseTimestamp(time) {
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
1433
|
+
if (typeof time === "number" && Number.isFinite(time) && time > 0) {
|
|
1434
|
+
return time < SECONDS_THRESHOLD ? Math.round(time * 1e3) : time;
|
|
1435
|
+
}
|
|
1436
|
+
if (typeof time === "string" && time.length > 0) {
|
|
1437
|
+
if (/^\d+(\.\d+)?$/.test(time)) return parseTimestamp(Number(time));
|
|
1438
|
+
const parsed = new Date(time).getTime();
|
|
1439
|
+
if (!isNaN(parsed)) return parsed;
|
|
1440
|
+
}
|
|
1441
|
+
return Date.now();
|
|
109
1442
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
1443
|
+
function asString(value) {
|
|
1444
|
+
if (value === void 0 || value === null) return void 0;
|
|
1445
|
+
return typeof value === "string" ? value : safeStringify(value);
|
|
1446
|
+
}
|
|
1447
|
+
function toEntry(obj, serviceName) {
|
|
1448
|
+
const method = typeof obj.req?.method === "string" && obj.req.method ? obj.req.method : void 0;
|
|
1449
|
+
const url = typeof obj.req?.url === "string" && obj.req.url ? obj.req.url : void 0;
|
|
1450
|
+
const statusCode = typeof obj.res?.statusCode === "number" && obj.res.statusCode ? obj.res.statusCode : void 0;
|
|
1451
|
+
const usesMessageKey = obj.msg === void 0 && typeof obj.message === "string";
|
|
1452
|
+
let msg = usesMessageKey ? obj.message : asString(obj.msg) ?? "";
|
|
1453
|
+
const lower = msg.toLowerCase().trim();
|
|
1454
|
+
if (GENERIC_HTTP_MSGS.has(lower) && method && url) {
|
|
1455
|
+
msg = `${method.toUpperCase()} ${url}${statusCode ? ` ${statusCode}` : ""}`;
|
|
1456
|
+
}
|
|
1457
|
+
const extracted = extractPayloads(obj);
|
|
1458
|
+
const baseMeta = isObj(obj.metadata) ? obj.metadata : {};
|
|
1459
|
+
const span = {};
|
|
1460
|
+
for (const key of SPAN_FIELDS) {
|
|
1461
|
+
const value = spanField(obj[key]);
|
|
1462
|
+
if (value) span[key] = value;
|
|
1463
|
+
}
|
|
1464
|
+
const customFields = {};
|
|
1465
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1466
|
+
if (PINO_INTERNAL_FIELDS.has(key) || usesMessageKey && key === "message") continue;
|
|
1467
|
+
if (span[key] !== void 0) continue;
|
|
1468
|
+
if (value !== void 0 && value !== null) customFields[key] = value;
|
|
1469
|
+
}
|
|
1470
|
+
const merged = { ...baseMeta, ...customFields, ...extracted };
|
|
1471
|
+
const metadata = Object.keys(merged).length > 0 ? merged : null;
|
|
1472
|
+
const traceId = obj.traceId || obj.trace_id || obj.requestId || obj.req?.id;
|
|
1473
|
+
const err = isObj(obj.err) ? obj.err : void 0;
|
|
1474
|
+
return {
|
|
1475
|
+
level: levelName(obj.level),
|
|
1476
|
+
msg,
|
|
1477
|
+
timestamp: parseTimestamp(obj.time),
|
|
1478
|
+
serviceName,
|
|
1479
|
+
// pino-http's default request id is a number.
|
|
1480
|
+
traceId: traceId === void 0 || traceId === null || traceId === "" ? null : String(traceId),
|
|
1481
|
+
spanId: span.spanId,
|
|
1482
|
+
parentSpanId: span.parentSpanId,
|
|
1483
|
+
spanKind: span.spanKind,
|
|
1484
|
+
method,
|
|
1485
|
+
url,
|
|
1486
|
+
statusCode,
|
|
1487
|
+
responseTime: typeof obj.responseTime === "number" ? obj.responseTime : void 0,
|
|
1488
|
+
errorMessage: asString(err?.message),
|
|
1489
|
+
errorStack: asString(err?.stack),
|
|
1490
|
+
metadata
|
|
114
1491
|
};
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
});
|
|
122
|
-
if (res.ok) return;
|
|
123
|
-
if (res.status >= 400 && res.status < 500) return;
|
|
124
|
-
} catch {
|
|
125
|
-
}
|
|
126
|
-
if (attempt < MAX_RETRIES - 1) {
|
|
127
|
-
const delay = RETRY_BASE_MS * Math.pow(2, attempt);
|
|
128
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
129
|
-
}
|
|
1492
|
+
}
|
|
1493
|
+
var PACKAGE_NAME = "@fabbahiense/pulsar-pino-transport";
|
|
1494
|
+
function workerOptions(options) {
|
|
1495
|
+
const out = {};
|
|
1496
|
+
for (const key of ["maxBufferSize", "maxRetries", "requestTimeout", "shutdownTimeout", "warnOnDrop"]) {
|
|
1497
|
+
if (options[key] !== void 0) out[key] = options[key];
|
|
130
1498
|
}
|
|
131
|
-
|
|
1499
|
+
return out;
|
|
132
1500
|
}
|
|
133
1501
|
function createPulsarTransport(options) {
|
|
134
1502
|
const {
|
|
@@ -142,8 +1510,8 @@ function createPulsarTransport(options) {
|
|
|
142
1510
|
const resolvedSource = source || serviceName || "default";
|
|
143
1511
|
return {
|
|
144
1512
|
transport: {
|
|
145
|
-
target:
|
|
146
|
-
options: { url, apiKey, source: resolvedSource, batchSize, flushInterval }
|
|
1513
|
+
target: PACKAGE_NAME,
|
|
1514
|
+
options: { url, apiKey, source: resolvedSource, batchSize, flushInterval, ...workerOptions(options) }
|
|
147
1515
|
}
|
|
148
1516
|
};
|
|
149
1517
|
}
|
|
@@ -158,67 +1526,51 @@ async function index_default(options) {
|
|
|
158
1526
|
} = options;
|
|
159
1527
|
const resolvedSource = source || serviceName || "default";
|
|
160
1528
|
const { default: build } = await import("pino-abstract-transport");
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
msg = `${method.toUpperCase()} ${url2}${statusCode ? ` ${statusCode}` : ""}`;
|
|
181
|
-
}
|
|
182
|
-
const extracted = extractPayloads(obj);
|
|
183
|
-
const baseMeta = isObj(obj.metadata) ? obj.metadata : {};
|
|
184
|
-
const customFields = {};
|
|
185
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
186
|
-
if (!PINO_INTERNAL_FIELDS.has(key) && value !== void 0 && value !== null) {
|
|
187
|
-
customFields[key] = value;
|
|
1529
|
+
const delivery = new Delivery({
|
|
1530
|
+
url,
|
|
1531
|
+
apiKey,
|
|
1532
|
+
batchSize,
|
|
1533
|
+
flushInterval,
|
|
1534
|
+
maxBufferSize: options.maxBufferSize,
|
|
1535
|
+
maxRetries: options.maxRetries,
|
|
1536
|
+
requestTimeout: options.requestTimeout,
|
|
1537
|
+
shutdownTimeout: options.shutdownTimeout,
|
|
1538
|
+
name: "pulsar-pino-transport",
|
|
1539
|
+
...options.warnOnDrop === false ? { onDrop: () => {
|
|
1540
|
+
} } : {}
|
|
1541
|
+
});
|
|
1542
|
+
return build(
|
|
1543
|
+
function(source2) {
|
|
1544
|
+
source2.on("data", (obj) => {
|
|
1545
|
+
try {
|
|
1546
|
+
delivery.push(toEntry(obj, resolvedSource));
|
|
1547
|
+
} catch {
|
|
188
1548
|
}
|
|
189
|
-
}
|
|
190
|
-
const merged = { ...baseMeta, ...customFields, ...extracted };
|
|
191
|
-
const metadata = Object.keys(merged).length > 0 ? merged : null;
|
|
192
|
-
buffer.push({
|
|
193
|
-
level: LEVEL_MAP[obj.level] || "info",
|
|
194
|
-
msg,
|
|
195
|
-
timestamp: parseTimestamp(obj.time),
|
|
196
|
-
serviceName: resolvedSource,
|
|
197
|
-
traceId: obj.traceId || obj.trace_id || obj.requestId || obj.req?.id || null,
|
|
198
|
-
method,
|
|
199
|
-
url: url2,
|
|
200
|
-
statusCode,
|
|
201
|
-
responseTime: obj.responseTime,
|
|
202
|
-
errorMessage: obj.err?.message,
|
|
203
|
-
errorStack: obj.err?.stack,
|
|
204
|
-
metadata
|
|
205
1549
|
});
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
1550
|
+
},
|
|
1551
|
+
{
|
|
1552
|
+
// pino-abstract-transport waits for this before emitting 'close', and
|
|
1553
|
+
// pino's worker exits right after 'close', so the final flush has to
|
|
1554
|
+
// happen here (bounded by shutdownTimeout).
|
|
1555
|
+
async close() {
|
|
1556
|
+
await delivery.close();
|
|
211
1557
|
}
|
|
212
1558
|
}
|
|
213
|
-
|
|
214
|
-
await flush();
|
|
215
|
-
if (retryQueue.length > 0) {
|
|
216
|
-
buffer.push(...retryQueue.splice(0));
|
|
217
|
-
await flush();
|
|
218
|
-
}
|
|
219
|
-
});
|
|
1559
|
+
);
|
|
220
1560
|
}
|
|
221
1561
|
// Annotate the CommonJS export names for ESM import in node:
|
|
222
1562
|
0 && (module.exports = {
|
|
223
|
-
createPulsarTransport
|
|
1563
|
+
createPulsarTransport,
|
|
1564
|
+
extractTrace,
|
|
1565
|
+
formatTraceparent,
|
|
1566
|
+
getTraceContext,
|
|
1567
|
+
injectTrace,
|
|
1568
|
+
instrumentAxios,
|
|
1569
|
+
instrumentFetch,
|
|
1570
|
+
parseTraceparent,
|
|
1571
|
+
pinoSpanSink,
|
|
1572
|
+
pulsarMixin,
|
|
1573
|
+
runWithTrace,
|
|
1574
|
+
startSpan,
|
|
1575
|
+
traceHeaders
|
|
224
1576
|
});
|