@autter/runtime-node 1.3.3 → 1.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/README.md +8 -1
- package/dist/index.cjs +516 -107
- package/dist/index.d.cts +39 -1
- package/dist/index.d.ts +39 -1
- package/dist/index.js +482 -78
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -34,18 +34,23 @@ __export(index_exports, {
|
|
|
34
34
|
captureMessage: () => captureMessage,
|
|
35
35
|
createBrowserRelayFetchHandler: () => createBrowserRelayFetchHandler,
|
|
36
36
|
createBrowserRelayHandler: () => createBrowserRelayHandler,
|
|
37
|
+
createRuntimeLogger: () => createRuntimeLogger,
|
|
37
38
|
emitLlmSelftestTrace: () => emitLlmSelftestTrace,
|
|
39
|
+
flushRuntimeLogs: () => flushRuntimeLogs,
|
|
38
40
|
initAutterServer: () => initAutterServer,
|
|
39
41
|
installAutterAutoFlush: () => installAutterAutoFlush,
|
|
40
42
|
instrumentLlmClient: () => instrumentLlmClient,
|
|
41
43
|
makeSafeCapture: () => makeSafeCapture,
|
|
42
44
|
redactAttributes: () => redactAttributes,
|
|
43
45
|
reportOutcome: () => reportOutcome,
|
|
46
|
+
runtimeLogStats: () => runtimeLogStats,
|
|
47
|
+
runtimeLogger: () => runtimeLogger,
|
|
44
48
|
sanitizeBrowserPayload: () => sanitizeBrowserPayload,
|
|
45
49
|
startCaughtExceptionSampler: () => startCaughtExceptionSampler,
|
|
46
50
|
trackLlmCall: () => trackLlmCall,
|
|
47
51
|
withLlmCall: () => withLlmCall,
|
|
48
|
-
withProcessSpan: () => withProcessSpan
|
|
52
|
+
withProcessSpan: () => withProcessSpan,
|
|
53
|
+
withRuntimeOperation: () => withRuntimeOperation
|
|
49
54
|
});
|
|
50
55
|
module.exports = __toCommonJS(index_exports);
|
|
51
56
|
|
|
@@ -68,8 +73,8 @@ var IpWindow = class {
|
|
|
68
73
|
return entry.count <= this.limitPerMinute;
|
|
69
74
|
}
|
|
70
75
|
};
|
|
71
|
-
function firstForwardedFor(
|
|
72
|
-
const raw = Array.isArray(
|
|
76
|
+
function firstForwardedFor(value2) {
|
|
77
|
+
const raw = Array.isArray(value2) ? value2[0] : value2;
|
|
73
78
|
return raw ? (raw.split(",")[0] ?? "").trim() : "";
|
|
74
79
|
}
|
|
75
80
|
var DEFAULT_ENDPOINT = "https://otlp.autter.dev";
|
|
@@ -83,6 +88,7 @@ var EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
|
83
88
|
"track_event",
|
|
84
89
|
"outcome",
|
|
85
90
|
"request_failure",
|
|
91
|
+
"csp_violation",
|
|
86
92
|
"timing"
|
|
87
93
|
]);
|
|
88
94
|
var SEVERITIES = /* @__PURE__ */ new Set(["fatal", "error", "warning", "info"]);
|
|
@@ -254,8 +260,8 @@ function createBrowserRelayHandler(opts) {
|
|
|
254
260
|
|
|
255
261
|
// src/server.ts
|
|
256
262
|
var import_node_http = require("http");
|
|
257
|
-
var
|
|
258
|
-
var
|
|
263
|
+
var import_node_crypto2 = require("crypto");
|
|
264
|
+
var import_api3 = require("@opentelemetry/api");
|
|
259
265
|
var import_core2 = require("@opentelemetry/core");
|
|
260
266
|
var import_exporter_metrics_otlp_http = require("@opentelemetry/exporter-metrics-otlp-http");
|
|
261
267
|
var import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
|
|
@@ -341,13 +347,13 @@ function installAutterAutoFlush(options = {}) {
|
|
|
341
347
|
const signals = options.signals ?? DEFAULT_SIGNALS;
|
|
342
348
|
const flushOnUncaughtException = options.flushOnUncaughtException !== false;
|
|
343
349
|
let disposed = false;
|
|
344
|
-
let
|
|
350
|
+
let inFlight2 = null;
|
|
345
351
|
const doFlush = async () => {
|
|
346
352
|
const targets = options.targets ?? activeFlushTargets();
|
|
347
353
|
if (targets.length === 0) return true;
|
|
348
|
-
let
|
|
354
|
+
let timer2;
|
|
349
355
|
const timedOut = new Promise((resolve) => {
|
|
350
|
-
|
|
356
|
+
timer2 = setTimeout(() => resolve(false), timeoutMs);
|
|
351
357
|
});
|
|
352
358
|
const drained = Promise.allSettled(
|
|
353
359
|
targets.map(
|
|
@@ -360,7 +366,7 @@ function installAutterAutoFlush(options = {}) {
|
|
|
360
366
|
try {
|
|
361
367
|
ok = await Promise.race([drained, timedOut]);
|
|
362
368
|
} finally {
|
|
363
|
-
clearTimeout(
|
|
369
|
+
clearTimeout(timer2);
|
|
364
370
|
}
|
|
365
371
|
if (ok) {
|
|
366
372
|
telemetryStats.markAllFlushed();
|
|
@@ -376,13 +382,13 @@ function installAutterAutoFlush(options = {}) {
|
|
|
376
382
|
};
|
|
377
383
|
const flush = (reason = "exit") => {
|
|
378
384
|
if (disposed) return Promise.resolve(false);
|
|
379
|
-
if (!
|
|
380
|
-
|
|
381
|
-
|
|
385
|
+
if (!inFlight2) {
|
|
386
|
+
inFlight2 = doFlush().finally(() => {
|
|
387
|
+
inFlight2 = null;
|
|
382
388
|
});
|
|
383
389
|
}
|
|
384
390
|
if (isDebugEnabled()) debugLog(`auto-flush requested on ${reason}`);
|
|
385
|
-
return
|
|
391
|
+
return inFlight2;
|
|
386
392
|
};
|
|
387
393
|
const signalCounts = /* @__PURE__ */ new Map();
|
|
388
394
|
let handlingUncaughtException = false;
|
|
@@ -521,8 +527,8 @@ function compile(options) {
|
|
|
521
527
|
scrubEmailValues: options?.scrubEmailValues !== false
|
|
522
528
|
};
|
|
523
529
|
}
|
|
524
|
-
function redactString(
|
|
525
|
-
let out =
|
|
530
|
+
function redactString(value2, r) {
|
|
531
|
+
let out = value2.replace(r.privateKeyBlock, r.mask);
|
|
526
532
|
for (const re of r.valuePatterns) {
|
|
527
533
|
out = out.replace(re, r.mask);
|
|
528
534
|
}
|
|
@@ -540,40 +546,40 @@ function canTraverse(depth, state) {
|
|
|
540
546
|
state.remainingWork -= 1;
|
|
541
547
|
return true;
|
|
542
548
|
}
|
|
543
|
-
function redactValue(
|
|
544
|
-
if (typeof
|
|
549
|
+
function redactValue(value2, r, state, depth) {
|
|
550
|
+
if (typeof value2 === "string") return redactString(value2, r);
|
|
545
551
|
let isArray = false;
|
|
546
552
|
try {
|
|
547
|
-
isArray = Array.isArray(
|
|
553
|
+
isArray = Array.isArray(value2);
|
|
548
554
|
} catch {
|
|
549
555
|
return r.mask;
|
|
550
556
|
}
|
|
551
557
|
if (isArray) {
|
|
552
|
-
return redactArray(
|
|
558
|
+
return redactArray(value2, r, state, depth);
|
|
553
559
|
}
|
|
554
|
-
if (typeof
|
|
555
|
-
return redactObject(
|
|
560
|
+
if (typeof value2 === "object" && value2 !== null) {
|
|
561
|
+
return redactObject(value2, r, state, depth);
|
|
556
562
|
}
|
|
557
|
-
return
|
|
563
|
+
return value2;
|
|
558
564
|
}
|
|
559
|
-
function redactArray(
|
|
560
|
-
const existing = state.ancestors.get(
|
|
565
|
+
function redactArray(value2, r, state, depth) {
|
|
566
|
+
const existing = state.ancestors.get(value2);
|
|
561
567
|
if (existing) return r.mask;
|
|
562
568
|
if (!canTraverse(depth, state)) return r.mask;
|
|
563
569
|
const out = [];
|
|
564
|
-
state.ancestors.set(
|
|
570
|
+
state.ancestors.set(value2, out);
|
|
565
571
|
let length;
|
|
566
572
|
try {
|
|
567
|
-
length =
|
|
573
|
+
length = value2.length;
|
|
568
574
|
} catch {
|
|
569
|
-
state.ancestors.delete(
|
|
575
|
+
state.ancestors.delete(value2);
|
|
570
576
|
return r.mask;
|
|
571
577
|
}
|
|
572
578
|
const limit = Math.min(length, MAX_COLLECTION_ENTRIES);
|
|
573
579
|
for (let i = 0; i < limit; i += 1) {
|
|
574
580
|
let item;
|
|
575
581
|
try {
|
|
576
|
-
item =
|
|
582
|
+
item = value2[i];
|
|
577
583
|
} catch {
|
|
578
584
|
out.push(r.mask);
|
|
579
585
|
continue;
|
|
@@ -583,21 +589,21 @@ function redactArray(value, r, state, depth) {
|
|
|
583
589
|
if (length > limit) {
|
|
584
590
|
out.push(r.mask);
|
|
585
591
|
}
|
|
586
|
-
state.ancestors.delete(
|
|
592
|
+
state.ancestors.delete(value2);
|
|
587
593
|
return out;
|
|
588
594
|
}
|
|
589
|
-
function redactObject(
|
|
590
|
-
const existing = state.ancestors.get(
|
|
595
|
+
function redactObject(value2, r, state, depth) {
|
|
596
|
+
const existing = state.ancestors.get(value2);
|
|
591
597
|
if (existing) return r.mask;
|
|
592
598
|
if (!canTraverse(depth, state)) return r.mask;
|
|
593
599
|
const out = {};
|
|
594
|
-
state.ancestors.set(
|
|
600
|
+
state.ancestors.set(value2, out);
|
|
595
601
|
let count = 0;
|
|
596
602
|
let truncated = false;
|
|
597
603
|
try {
|
|
598
|
-
for (const key in
|
|
604
|
+
for (const key in value2) {
|
|
599
605
|
if (!Object.prototype.propertyIsEnumerable.call(
|
|
600
|
-
|
|
606
|
+
value2,
|
|
601
607
|
key
|
|
602
608
|
)) {
|
|
603
609
|
continue;
|
|
@@ -608,7 +614,7 @@ function redactObject(value, r, state, depth) {
|
|
|
608
614
|
}
|
|
609
615
|
let nestedValue;
|
|
610
616
|
try {
|
|
611
|
-
nestedValue =
|
|
617
|
+
nestedValue = value2[key];
|
|
612
618
|
} catch {
|
|
613
619
|
out[key] = r.mask;
|
|
614
620
|
count += 1;
|
|
@@ -628,7 +634,7 @@ function redactObject(value, r, state, depth) {
|
|
|
628
634
|
if (truncated) {
|
|
629
635
|
out.__redaction_truncated__ = r.mask;
|
|
630
636
|
}
|
|
631
|
-
state.ancestors.delete(
|
|
637
|
+
state.ancestors.delete(value2);
|
|
632
638
|
return out;
|
|
633
639
|
}
|
|
634
640
|
var USAGE_TOKEN_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -645,10 +651,10 @@ var USAGE_TOKEN_KEYS = /* @__PURE__ */ new Set([
|
|
|
645
651
|
"gen_ai.usage.total_tokens",
|
|
646
652
|
"gen_ai.usage.token_count"
|
|
647
653
|
]);
|
|
648
|
-
function isSensitiveKey(key,
|
|
654
|
+
function isSensitiveKey(key, value2, r) {
|
|
649
655
|
const lowered = key.toLowerCase();
|
|
650
656
|
if (USAGE_TOKEN_KEYS.has(lowered)) {
|
|
651
|
-
return !(typeof
|
|
657
|
+
return !(typeof value2 === "number" && Number.isFinite(value2) && value2 >= 0);
|
|
652
658
|
}
|
|
653
659
|
return r.keyPatterns.some((re) => re.test(lowered));
|
|
654
660
|
}
|
|
@@ -677,9 +683,9 @@ function redactWith(attributes, r) {
|
|
|
677
683
|
truncated = true;
|
|
678
684
|
break;
|
|
679
685
|
}
|
|
680
|
-
let
|
|
686
|
+
let value2;
|
|
681
687
|
try {
|
|
682
|
-
|
|
688
|
+
value2 = attributes[key];
|
|
683
689
|
} catch {
|
|
684
690
|
out[key] = r.mask;
|
|
685
691
|
count += 1;
|
|
@@ -688,8 +694,8 @@ function redactWith(attributes, r) {
|
|
|
688
694
|
}
|
|
689
695
|
count += 1;
|
|
690
696
|
state.remainingWork -= 1;
|
|
691
|
-
if (
|
|
692
|
-
out[key] = isSensitiveKey(key,
|
|
697
|
+
if (value2 === void 0) continue;
|
|
698
|
+
out[key] = isSensitiveKey(key, value2, r) ? r.mask : redactValue(value2, r, state, 0);
|
|
693
699
|
}
|
|
694
700
|
} catch {
|
|
695
701
|
truncated = true;
|
|
@@ -752,6 +758,390 @@ function startMemoryMetrics() {
|
|
|
752
758
|
};
|
|
753
759
|
}
|
|
754
760
|
|
|
761
|
+
// src/logger.ts
|
|
762
|
+
var import_node_async_hooks = require("async_hooks");
|
|
763
|
+
var import_node_crypto = require("crypto");
|
|
764
|
+
var import_api2 = require("@opentelemetry/api");
|
|
765
|
+
var local = new import_node_async_hooks.AsyncLocalStorage();
|
|
766
|
+
var config = null;
|
|
767
|
+
var queue = [];
|
|
768
|
+
var queueBytes = 0;
|
|
769
|
+
var inFlightBytes = 0;
|
|
770
|
+
var timer;
|
|
771
|
+
var flushing = null;
|
|
772
|
+
var dropped = 0;
|
|
773
|
+
var inFlight = 0;
|
|
774
|
+
var stopping = false;
|
|
775
|
+
var failureStreak = 0;
|
|
776
|
+
var severity = { debug: 5, info: 9, warning: 13, error: 17 };
|
|
777
|
+
function safe(attributes = {}) {
|
|
778
|
+
const redacted = config?.redact(attributes) ?? redactAttributes(attributes);
|
|
779
|
+
let budget = 512;
|
|
780
|
+
let characters = 16384;
|
|
781
|
+
let truncated = false;
|
|
782
|
+
const visit = (input, depth) => {
|
|
783
|
+
if (--budget < 0 || depth > 6) {
|
|
784
|
+
truncated = true;
|
|
785
|
+
return "[truncated]";
|
|
786
|
+
}
|
|
787
|
+
if (typeof input === "string") {
|
|
788
|
+
const text = input.replace(/(https?:\/\/[^\s?#]+)[?#][^\s]*/gi, "$1");
|
|
789
|
+
const kept = text.slice(0, Math.max(0, characters));
|
|
790
|
+
truncated ||= kept.length < text.length;
|
|
791
|
+
characters -= kept.length;
|
|
792
|
+
return kept;
|
|
793
|
+
}
|
|
794
|
+
if (typeof input === "number")
|
|
795
|
+
return Number.isFinite(input) ? input : void 0;
|
|
796
|
+
if (typeof input === "boolean" || input === null) return input;
|
|
797
|
+
if (Array.isArray(input)) {
|
|
798
|
+
truncated ||= input.length > 64;
|
|
799
|
+
return input.slice(0, 64).map((item) => visit(item, depth + 1));
|
|
800
|
+
}
|
|
801
|
+
if (input && typeof input === "object") {
|
|
802
|
+
const entries = Object.entries(input);
|
|
803
|
+
if (depth === 0) {
|
|
804
|
+
const priority = (key) => /^autter\.(?:operation|event)\./.test(key) ? 2 : /^exception\./.test(key) ? 1 : 0;
|
|
805
|
+
entries.sort(([a], [b]) => priority(b) - priority(a));
|
|
806
|
+
}
|
|
807
|
+
truncated ||= entries.length > 100;
|
|
808
|
+
return Object.fromEntries(
|
|
809
|
+
entries.slice(0, 100).filter(([key]) => !/^(?:__proto__|constructor|prototype)$/.test(key)).map(([key, item]) => [key.slice(0, 200), visit(item, depth + 1)])
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
return void 0;
|
|
813
|
+
};
|
|
814
|
+
const result = visit(redacted, 0);
|
|
815
|
+
if (truncated) result["autter.context.truncated"] = true;
|
|
816
|
+
return result;
|
|
817
|
+
}
|
|
818
|
+
function operationAttributes(state) {
|
|
819
|
+
return state ? {
|
|
820
|
+
"autter.operation.id": state.id,
|
|
821
|
+
"autter.operation.name": state.name,
|
|
822
|
+
...state.parentId ? { "autter.operation.parent_id": state.parentId } : {},
|
|
823
|
+
...state.attributes
|
|
824
|
+
} : {};
|
|
825
|
+
}
|
|
826
|
+
function userAttributes(attributes = {}) {
|
|
827
|
+
return Object.fromEntries(
|
|
828
|
+
Object.entries(safe(attributes)).filter(
|
|
829
|
+
([key]) => !/^autter\.(?:operation|event)\./.test(key)
|
|
830
|
+
)
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
function mergeContext(current, next = {}) {
|
|
834
|
+
const merge = (left, right, depth) => {
|
|
835
|
+
const result = { ...left };
|
|
836
|
+
for (const [key, value2] of Object.entries(right)) {
|
|
837
|
+
const prior = result[key];
|
|
838
|
+
result[key] = depth < 6 && value2 && typeof value2 === "object" && !Array.isArray(value2) && prior && typeof prior === "object" && !Array.isArray(prior) ? merge(prior, value2, depth + 1) : value2;
|
|
839
|
+
}
|
|
840
|
+
return result;
|
|
841
|
+
};
|
|
842
|
+
return userAttributes(merge(current, userAttributes(next), 0));
|
|
843
|
+
}
|
|
844
|
+
function traceAttributes(state) {
|
|
845
|
+
return Object.fromEntries(
|
|
846
|
+
Object.entries(operationAttributes(state)).filter(([, item]) => item !== null && item !== void 0).map(([key, item]) => [
|
|
847
|
+
key,
|
|
848
|
+
typeof item === "object" ? JSON.stringify(item) : item
|
|
849
|
+
])
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
function scheduleFlush() {
|
|
853
|
+
if (!timer && config && !stopping && queue.length) {
|
|
854
|
+
timer = setTimeout(
|
|
855
|
+
() => {
|
|
856
|
+
timer = void 0;
|
|
857
|
+
void flushRuntimeLogs().catch(() => {
|
|
858
|
+
});
|
|
859
|
+
},
|
|
860
|
+
Math.min(3e4, 2e3 * 2 ** Math.min(failureStreak, 4))
|
|
861
|
+
);
|
|
862
|
+
timer.unref();
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
function value(input) {
|
|
866
|
+
if (typeof input === "number") return { doubleValue: input };
|
|
867
|
+
if (typeof input === "boolean") return { boolValue: input };
|
|
868
|
+
if (Array.isArray(input))
|
|
869
|
+
return { arrayValue: { values: input.slice(0, 64).map(value) } };
|
|
870
|
+
if (input && typeof input === "object")
|
|
871
|
+
return {
|
|
872
|
+
kvlistValue: {
|
|
873
|
+
values: Object.entries(input).slice(0, 128).map(([key, item]) => ({ key, value: value(item) }))
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
return { stringValue: String(input ?? "").slice(0, 32e3) };
|
|
877
|
+
}
|
|
878
|
+
function emit(level, message, attributes, extra = {}) {
|
|
879
|
+
const state = local.getStore();
|
|
880
|
+
if (state?.sealed) return;
|
|
881
|
+
if (extra["autter.event.type"] !== "operation" && severity[level] < severity[config?.options?.minLevel ?? "debug"])
|
|
882
|
+
return;
|
|
883
|
+
const span = import_api2.trace.getActiveSpan()?.spanContext();
|
|
884
|
+
const user = mergeContext(state?.attributes ?? {}, attributes);
|
|
885
|
+
const exceptions = Object.fromEntries(
|
|
886
|
+
Object.entries(user).filter(([key]) => key.startsWith("exception."))
|
|
887
|
+
);
|
|
888
|
+
const attrs = safe({
|
|
889
|
+
"autter.event.id": (0, import_node_crypto.randomUUID)(),
|
|
890
|
+
...extra,
|
|
891
|
+
...exceptions,
|
|
892
|
+
...operationAttributes(state),
|
|
893
|
+
...user
|
|
894
|
+
});
|
|
895
|
+
const record = {
|
|
896
|
+
timeUnixNano: String(BigInt(Date.now()) * 1000000n),
|
|
897
|
+
severityNumber: severity[level],
|
|
898
|
+
severityText: level.toUpperCase(),
|
|
899
|
+
body: value(String(safe({ message }).message ?? "").slice(0, 4e3)),
|
|
900
|
+
attributes: Object.entries(attrs).map(([key, item]) => ({
|
|
901
|
+
key,
|
|
902
|
+
value: value(item)
|
|
903
|
+
})),
|
|
904
|
+
...span && /^[a-f0-9]{32}$/.test(span.traceId) && !/^0+$/.test(span.traceId) ? { traceId: span.traceId, spanId: span.spanId } : {}
|
|
905
|
+
};
|
|
906
|
+
if (config?.options?.console !== false)
|
|
907
|
+
console.log(
|
|
908
|
+
JSON.stringify({ ...attrs, level, message: record.body.stringValue })
|
|
909
|
+
);
|
|
910
|
+
if (!config) return;
|
|
911
|
+
if (stopping) {
|
|
912
|
+
dropped++;
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
const bytes = Buffer.byteLength(JSON.stringify(record));
|
|
916
|
+
if (bytes > 256 * 1024 || queue.length + inFlight >= 1e3 || queueBytes + inFlightBytes + bytes > 4 * 1024 * 1024) {
|
|
917
|
+
dropped++;
|
|
918
|
+
console.warn(
|
|
919
|
+
"[autter-runtime] log buffer or record limit reached; record dropped"
|
|
920
|
+
);
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
queue.push({ record, bytes });
|
|
924
|
+
queueBytes += bytes;
|
|
925
|
+
scheduleFlush();
|
|
926
|
+
}
|
|
927
|
+
function configureRuntimeLogger(next) {
|
|
928
|
+
config = next;
|
|
929
|
+
stopping = false;
|
|
930
|
+
failureStreak = 0;
|
|
931
|
+
}
|
|
932
|
+
function runtimeLogStats() {
|
|
933
|
+
return { buffered: queue.length + inFlight, dropped };
|
|
934
|
+
}
|
|
935
|
+
async function flushRuntimeLogs() {
|
|
936
|
+
if (flushing) return flushing;
|
|
937
|
+
if (!config || !queue.length) return;
|
|
938
|
+
if (timer) {
|
|
939
|
+
clearTimeout(timer);
|
|
940
|
+
timer = void 0;
|
|
941
|
+
}
|
|
942
|
+
const activeConfig = config;
|
|
943
|
+
const deadline = Date.now() + 1e4;
|
|
944
|
+
flushing = (async () => {
|
|
945
|
+
const count = queue.length;
|
|
946
|
+
for (let remaining = count; remaining > 0; ) {
|
|
947
|
+
const batch = [];
|
|
948
|
+
inFlightBytes = 0;
|
|
949
|
+
while (batch.length < Math.min(50, remaining) && queue.length) {
|
|
950
|
+
const next = queue[0];
|
|
951
|
+
if (batch.length && inFlightBytes + next.bytes > 512 * 1024) break;
|
|
952
|
+
batch.push(queue.shift());
|
|
953
|
+
inFlightBytes += next.bytes;
|
|
954
|
+
queueBytes -= next.bytes;
|
|
955
|
+
}
|
|
956
|
+
remaining -= batch.length;
|
|
957
|
+
inFlight = batch.length;
|
|
958
|
+
const body = JSON.stringify({
|
|
959
|
+
resourceLogs: [
|
|
960
|
+
{
|
|
961
|
+
resource: {
|
|
962
|
+
attributes: Object.entries({
|
|
963
|
+
"service.name": activeConfig.service,
|
|
964
|
+
"deployment.environment.name": activeConfig.environment,
|
|
965
|
+
...activeConfig.release ? { "service.version": activeConfig.release } : {}
|
|
966
|
+
}).map(([key, item]) => ({ key, value: value(item) }))
|
|
967
|
+
},
|
|
968
|
+
scopeLogs: [
|
|
969
|
+
{
|
|
970
|
+
scope: { name: "autter-runtime" },
|
|
971
|
+
logRecords: batch.map((entry) => entry.record)
|
|
972
|
+
}
|
|
973
|
+
]
|
|
974
|
+
}
|
|
975
|
+
]
|
|
976
|
+
});
|
|
977
|
+
let failure;
|
|
978
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
979
|
+
try {
|
|
980
|
+
if (Date.now() >= deadline)
|
|
981
|
+
throw new Error("Runtime log flush exceeded 10 seconds");
|
|
982
|
+
const response = await fetch(`${activeConfig.endpoint}/v1/logs`, {
|
|
983
|
+
method: "POST",
|
|
984
|
+
headers: {
|
|
985
|
+
"content-type": "application/json",
|
|
986
|
+
authorization: `Bearer ${activeConfig.apiKey}`
|
|
987
|
+
},
|
|
988
|
+
body,
|
|
989
|
+
signal: AbortSignal.timeout(
|
|
990
|
+
Math.max(1, Math.min(3e3, deadline - Date.now()))
|
|
991
|
+
)
|
|
992
|
+
});
|
|
993
|
+
if (!response.ok)
|
|
994
|
+
throw new Error(`Runtime log export failed (${response.status})`);
|
|
995
|
+
failure = void 0;
|
|
996
|
+
break;
|
|
997
|
+
} catch (error) {
|
|
998
|
+
failure = error;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
inFlight = 0;
|
|
1002
|
+
if (failure) {
|
|
1003
|
+
failureStreak++;
|
|
1004
|
+
queue = [...batch, ...queue];
|
|
1005
|
+
queueBytes += inFlightBytes;
|
|
1006
|
+
inFlightBytes = 0;
|
|
1007
|
+
throw failure;
|
|
1008
|
+
}
|
|
1009
|
+
inFlightBytes = 0;
|
|
1010
|
+
failureStreak = 0;
|
|
1011
|
+
}
|
|
1012
|
+
})().finally(() => {
|
|
1013
|
+
flushing = null;
|
|
1014
|
+
scheduleFlush();
|
|
1015
|
+
});
|
|
1016
|
+
return flushing;
|
|
1017
|
+
}
|
|
1018
|
+
async function shutdownRuntimeLogger() {
|
|
1019
|
+
stopping = true;
|
|
1020
|
+
try {
|
|
1021
|
+
await flushRuntimeLogs();
|
|
1022
|
+
} finally {
|
|
1023
|
+
if (queue.length) {
|
|
1024
|
+
dropped += queue.length;
|
|
1025
|
+
console.warn(
|
|
1026
|
+
`[autter-runtime] ${queue.length} log records could not be delivered before shutdown`
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
queue = [];
|
|
1030
|
+
queueBytes = 0;
|
|
1031
|
+
config = null;
|
|
1032
|
+
if (timer) clearTimeout(timer);
|
|
1033
|
+
timer = void 0;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
function createRuntimeLogger(attributes = {}) {
|
|
1037
|
+
const attrs = userAttributes(attributes);
|
|
1038
|
+
return {
|
|
1039
|
+
debug: (message, extra) => emit("debug", message, mergeContext(attrs, extra)),
|
|
1040
|
+
info: (message, extra) => emit("info", message, mergeContext(attrs, extra)),
|
|
1041
|
+
warn: (message, extra) => emit("warning", message, mergeContext(attrs, extra)),
|
|
1042
|
+
error: (error, extra) => emit("error", error instanceof Error ? error.message : String(error), {
|
|
1043
|
+
...attrs,
|
|
1044
|
+
...extra,
|
|
1045
|
+
...error instanceof Error ? {
|
|
1046
|
+
"exception.type": error.name,
|
|
1047
|
+
"exception.stacktrace": error.stack ?? ""
|
|
1048
|
+
} : {}
|
|
1049
|
+
})
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
var runtimeLogger = createRuntimeLogger();
|
|
1053
|
+
function withRuntimeOperation(name, fn, attributes = {}) {
|
|
1054
|
+
const parent = local.getStore();
|
|
1055
|
+
const state = {
|
|
1056
|
+
id: (0, import_node_crypto.randomUUID)(),
|
|
1057
|
+
name: String(safe({ name }).name).slice(0, 200),
|
|
1058
|
+
parentId: parent?.sealed ? void 0 : parent?.id,
|
|
1059
|
+
attributes: mergeContext(
|
|
1060
|
+
parent?.sealed ? {} : parent?.attributes ?? {},
|
|
1061
|
+
attributes
|
|
1062
|
+
),
|
|
1063
|
+
startedAt: Date.now(),
|
|
1064
|
+
outcome: "succeeded",
|
|
1065
|
+
steps: []
|
|
1066
|
+
};
|
|
1067
|
+
const operation = {
|
|
1068
|
+
id: state.id,
|
|
1069
|
+
setContext: (attrs) => {
|
|
1070
|
+
if (!state.sealed) {
|
|
1071
|
+
state.attributes = mergeContext(state.attributes, attrs);
|
|
1072
|
+
import_api2.trace.getActiveSpan()?.setAttributes(traceAttributes(state));
|
|
1073
|
+
}
|
|
1074
|
+
},
|
|
1075
|
+
outcome: (status, message) => {
|
|
1076
|
+
if (state.sealed) return;
|
|
1077
|
+
state.outcome = status;
|
|
1078
|
+
state.message = message;
|
|
1079
|
+
if (status === "failed")
|
|
1080
|
+
state.reportingStack = new Error("Operation outcome reported here").stack ?? "";
|
|
1081
|
+
},
|
|
1082
|
+
step: async (stepName, stepFn) => {
|
|
1083
|
+
const started = Date.now();
|
|
1084
|
+
let status = "succeeded";
|
|
1085
|
+
try {
|
|
1086
|
+
return await stepFn();
|
|
1087
|
+
} catch (error) {
|
|
1088
|
+
status = "failed";
|
|
1089
|
+
throw error;
|
|
1090
|
+
} finally {
|
|
1091
|
+
if (!state.sealed && state.steps.length < 64)
|
|
1092
|
+
state.steps.push({
|
|
1093
|
+
name: String(safe({ name: stepName }).name).slice(0, 200),
|
|
1094
|
+
status,
|
|
1095
|
+
durationMs: Date.now() - started
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
const run = () => local.run(state, async () => {
|
|
1101
|
+
let thrown;
|
|
1102
|
+
try {
|
|
1103
|
+
return await fn(operation);
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
state.outcome = "failed";
|
|
1106
|
+
thrown = error;
|
|
1107
|
+
throw error;
|
|
1108
|
+
} finally {
|
|
1109
|
+
import_api2.trace.getActiveSpan()?.setAttributes({
|
|
1110
|
+
...traceAttributes(state),
|
|
1111
|
+
"autter.operation.outcome": state.outcome
|
|
1112
|
+
});
|
|
1113
|
+
if (state.outcome === "failed" && !thrown)
|
|
1114
|
+
config?.reportOutcome(
|
|
1115
|
+
state.name,
|
|
1116
|
+
state.message ?? "Operation failed",
|
|
1117
|
+
{
|
|
1118
|
+
...traceAttributes(state),
|
|
1119
|
+
"autter.outcome.stack": state.reportingStack ?? ""
|
|
1120
|
+
}
|
|
1121
|
+
);
|
|
1122
|
+
emit(
|
|
1123
|
+
state.outcome === "failed" ? "error" : "info",
|
|
1124
|
+
state.message ?? `${state.name}: ${state.outcome}`,
|
|
1125
|
+
{
|
|
1126
|
+
...operationAttributes(state),
|
|
1127
|
+
...thrown instanceof Error ? {
|
|
1128
|
+
"exception.type": thrown.name,
|
|
1129
|
+
"exception.stacktrace": thrown.stack ?? ""
|
|
1130
|
+
} : {}
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
"autter.event.type": "operation",
|
|
1134
|
+
"autter.operation.outcome": state.outcome,
|
|
1135
|
+
"autter.operation.duration_ms": Date.now() - state.startedAt,
|
|
1136
|
+
"autter.operation.steps": state.steps
|
|
1137
|
+
}
|
|
1138
|
+
);
|
|
1139
|
+
state.sealed = true;
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
return config ? config.run(state.name, run, traceAttributes(state)) : run();
|
|
1143
|
+
}
|
|
1144
|
+
|
|
755
1145
|
// src/server.ts
|
|
756
1146
|
var LLM_MARKER_ATTRIBUTES = [
|
|
757
1147
|
"gen_ai.operation.name",
|
|
@@ -814,14 +1204,14 @@ var RETENTION_MAX_BUFFERED_SPANS = 5e3;
|
|
|
814
1204
|
var RETENTION_SWEEP_INTERVAL_MS = 1e4;
|
|
815
1205
|
var RETENTION_TRACE_TTL_MS = 3e4;
|
|
816
1206
|
function spanIndicatesError(span) {
|
|
817
|
-
if (span.status.code ===
|
|
1207
|
+
if (span.status.code === import_api3.SpanStatusCode.ERROR) return true;
|
|
818
1208
|
return span.events.some((event) => event.name === "exception");
|
|
819
1209
|
}
|
|
820
1210
|
var TAIL_RETAINED_ATTR = "autter.tail_retained";
|
|
821
1211
|
function withSampledFlag(span) {
|
|
822
1212
|
const spanContext = {
|
|
823
1213
|
...span.spanContext(),
|
|
824
|
-
traceFlags: span.spanContext().traceFlags |
|
|
1214
|
+
traceFlags: span.spanContext().traceFlags | import_api3.TraceFlags.SAMPLED
|
|
825
1215
|
};
|
|
826
1216
|
return {
|
|
827
1217
|
name: span.name,
|
|
@@ -861,11 +1251,11 @@ var ErrorTraceRetentionProcessor = class {
|
|
|
861
1251
|
}
|
|
862
1252
|
onEnd(span) {
|
|
863
1253
|
const ctx = span.spanContext();
|
|
864
|
-
if ((ctx.traceFlags &
|
|
1254
|
+
if ((ctx.traceFlags & import_api3.TraceFlags.SAMPLED) !== 0) return;
|
|
865
1255
|
const entry = this.entryFor(ctx.traceId);
|
|
866
1256
|
if (entry.retained) {
|
|
867
1257
|
this.forward(span);
|
|
868
|
-
} else if (this.retainErrors && spanIndicatesError(span) || this.slowThresholdMs > 0 && span.kind ===
|
|
1258
|
+
} else if (this.retainErrors && spanIndicatesError(span) || this.slowThresholdMs > 0 && span.kind === import_api3.SpanKind.SERVER && span.duration[0] * 1e3 + span.duration[1] / 1e6 >= this.slowThresholdMs) {
|
|
869
1259
|
entry.retained = true;
|
|
870
1260
|
this.flush(entry);
|
|
871
1261
|
this.forward(span);
|
|
@@ -880,8 +1270,8 @@ var ErrorTraceRetentionProcessor = class {
|
|
|
880
1270
|
}
|
|
881
1271
|
/** Promote the active trace, if any — called on captured errors. */
|
|
882
1272
|
retainActiveTrace() {
|
|
883
|
-
const active2 =
|
|
884
|
-
if (!active2 || (active2.traceFlags &
|
|
1273
|
+
const active2 = import_api3.trace.getActiveSpan()?.spanContext();
|
|
1274
|
+
if (!active2 || (active2.traceFlags & import_api3.TraceFlags.SAMPLED) !== 0) return;
|
|
885
1275
|
const entry = this.entryFor(active2.traceId);
|
|
886
1276
|
if (entry.retained) return;
|
|
887
1277
|
entry.retained = true;
|
|
@@ -970,7 +1360,7 @@ function usageAttributes(usage) {
|
|
|
970
1360
|
}
|
|
971
1361
|
async function runLlmSpan(tracer, info, fn) {
|
|
972
1362
|
const span = tracer.startSpan(`${info.operation ?? "chat"} ${info.model}`, {
|
|
973
|
-
kind:
|
|
1363
|
+
kind: import_api3.SpanKind.CLIENT,
|
|
974
1364
|
attributes: llmBaseAttributes(info)
|
|
975
1365
|
});
|
|
976
1366
|
const setResponseModel = (model) => span.setAttribute("gen_ai.response.model", model);
|
|
@@ -982,11 +1372,11 @@ async function runLlmSpan(tracer, info, fn) {
|
|
|
982
1372
|
setAttributes: (attributes) => span.setAttributes(attributes)
|
|
983
1373
|
};
|
|
984
1374
|
try {
|
|
985
|
-
const result = await
|
|
986
|
-
|
|
1375
|
+
const result = await import_api3.context.with(
|
|
1376
|
+
import_api3.trace.setSpan(import_api3.context.active(), span),
|
|
987
1377
|
() => fn(handle)
|
|
988
1378
|
);
|
|
989
|
-
span.setStatus({ code:
|
|
1379
|
+
span.setStatus({ code: import_api3.SpanStatusCode.OK });
|
|
990
1380
|
return result;
|
|
991
1381
|
} catch (err) {
|
|
992
1382
|
if (err instanceof Error) {
|
|
@@ -994,7 +1384,7 @@ async function runLlmSpan(tracer, info, fn) {
|
|
|
994
1384
|
span.setAttribute("error.type", err.name);
|
|
995
1385
|
}
|
|
996
1386
|
span.setStatus({
|
|
997
|
-
code:
|
|
1387
|
+
code: import_api3.SpanStatusCode.ERROR,
|
|
998
1388
|
message: err instanceof Error ? err.message : String(err)
|
|
999
1389
|
});
|
|
1000
1390
|
throw err;
|
|
@@ -1009,7 +1399,7 @@ function recordLlmCall(tracer, call) {
|
|
|
1009
1399
|
const span = tracer.startSpan(
|
|
1010
1400
|
`${call.operation ?? "chat"} ${call.model}`,
|
|
1011
1401
|
{
|
|
1012
|
-
kind:
|
|
1402
|
+
kind: import_api3.SpanKind.CLIENT,
|
|
1013
1403
|
startTime,
|
|
1014
1404
|
attributes: { ...llmBaseAttributes(call), ...usageAttributes(call) }
|
|
1015
1405
|
}
|
|
@@ -1020,27 +1410,27 @@ function recordLlmCall(tracer, call) {
|
|
|
1020
1410
|
span.setAttribute("error.type", call.error.name);
|
|
1021
1411
|
}
|
|
1022
1412
|
span.setStatus({
|
|
1023
|
-
code:
|
|
1413
|
+
code: import_api3.SpanStatusCode.ERROR,
|
|
1024
1414
|
message: call.error instanceof Error ? call.error.message : String(call.error)
|
|
1025
1415
|
});
|
|
1026
1416
|
} else {
|
|
1027
|
-
span.setStatus({ code:
|
|
1417
|
+
span.setStatus({ code: import_api3.SpanStatusCode.OK });
|
|
1028
1418
|
}
|
|
1029
1419
|
span.end(endTime);
|
|
1030
1420
|
}
|
|
1031
1421
|
async function runWithSpan(tracer, name, fn, attributes) {
|
|
1032
1422
|
const span = tracer.startSpan(name, { attributes });
|
|
1033
1423
|
try {
|
|
1034
|
-
const result = await
|
|
1035
|
-
|
|
1424
|
+
const result = await import_api3.context.with(
|
|
1425
|
+
import_api3.trace.setSpan(import_api3.context.active(), span),
|
|
1036
1426
|
fn
|
|
1037
1427
|
);
|
|
1038
|
-
span.setStatus({ code:
|
|
1428
|
+
span.setStatus({ code: import_api3.SpanStatusCode.OK });
|
|
1039
1429
|
return result;
|
|
1040
1430
|
} catch (err) {
|
|
1041
1431
|
if (err instanceof Error) span.recordException(err);
|
|
1042
1432
|
span.setStatus({
|
|
1043
|
-
code:
|
|
1433
|
+
code: import_api3.SpanStatusCode.ERROR,
|
|
1044
1434
|
message: err instanceof Error ? err.message : String(err)
|
|
1045
1435
|
});
|
|
1046
1436
|
throw err;
|
|
@@ -1058,7 +1448,7 @@ function expressRouteOf(req) {
|
|
|
1058
1448
|
}
|
|
1059
1449
|
function captureExpressRoute(_span, response) {
|
|
1060
1450
|
if (!(response instanceof import_node_http.ServerResponse)) return;
|
|
1061
|
-
const rpcMetadata = (0, import_core2.getRPCMetadata)(
|
|
1451
|
+
const rpcMetadata = (0, import_core2.getRPCMetadata)(import_api3.context.active());
|
|
1062
1452
|
if (rpcMetadata?.type !== import_core2.RPCType.HTTP) return;
|
|
1063
1453
|
const setRoute = () => {
|
|
1064
1454
|
if (rpcMetadata.route) return;
|
|
@@ -1084,7 +1474,7 @@ function initAutterServer(options) {
|
|
|
1084
1474
|
activeRedactor = makeRedactor(options.redactAttributes ?? true);
|
|
1085
1475
|
const resource = new import_resources.Resource({
|
|
1086
1476
|
[import_semantic_conventions.ATTR_SERVICE_NAME]: options.service,
|
|
1087
|
-
"service.instance.id": (options.instanceId || process.env.AUTTER_RUNTIME_INSTANCE_ID || (0,
|
|
1477
|
+
"service.instance.id": (options.instanceId || process.env.AUTTER_RUNTIME_INSTANCE_ID || (0, import_node_crypto2.randomUUID)()).slice(0, 128),
|
|
1088
1478
|
...options.release ? { [import_semantic_conventions.ATTR_SERVICE_VERSION]: options.release } : {},
|
|
1089
1479
|
"deployment.environment": environment,
|
|
1090
1480
|
// Tells the ingester request metrics arrive on the metrics pipe, so
|
|
@@ -1171,7 +1561,7 @@ function initAutterServer(options) {
|
|
|
1171
1561
|
const isError = error instanceof Error;
|
|
1172
1562
|
const message = isError ? error.message : String(error);
|
|
1173
1563
|
const redacted = activeRedactor(attributes);
|
|
1174
|
-
const activeSpan =
|
|
1564
|
+
const activeSpan = import_api3.trace.getActiveSpan();
|
|
1175
1565
|
const reuseActive = activeSpan?.isRecording() === true;
|
|
1176
1566
|
const span = reuseActive ? activeSpan : errorTracer.startSpan(isError ? error.name : "Error");
|
|
1177
1567
|
span.setAttributes({ "autter.severity": "error", ...redacted });
|
|
@@ -1196,31 +1586,31 @@ function initAutterServer(options) {
|
|
|
1196
1586
|
...redacted["autter.handled"] === true ? { "autter.handled": true, "autter.sampled": redacted["autter.sampled"] === true } : {}
|
|
1197
1587
|
});
|
|
1198
1588
|
}
|
|
1199
|
-
span.setStatus({ code:
|
|
1589
|
+
span.setStatus({ code: import_api3.SpanStatusCode.ERROR, message });
|
|
1200
1590
|
if (!reuseActive) span.end();
|
|
1201
1591
|
}
|
|
1202
|
-
function captureMessage2(message,
|
|
1203
|
-
if (
|
|
1592
|
+
function captureMessage2(message, severity2 = "warning", attributes) {
|
|
1593
|
+
if (severity2 === "error" || severity2 === "fatal") {
|
|
1204
1594
|
errorTraceBuffer?.retainActiveTrace();
|
|
1205
1595
|
}
|
|
1206
1596
|
telemetryStats.markCaptured();
|
|
1207
1597
|
const stack = new Error().stack?.split("\n").filter((line, i) => i === 0 || !line.includes("captureMessage")).join("\n");
|
|
1208
1598
|
const span = errorTracer.startSpan("Message", {
|
|
1209
|
-
attributes: { "autter.severity":
|
|
1599
|
+
attributes: { "autter.severity": severity2, ...activeRedactor(attributes) }
|
|
1210
1600
|
});
|
|
1211
1601
|
span.addEvent("exception", {
|
|
1212
1602
|
"exception.type": "Message",
|
|
1213
1603
|
"exception.message": message,
|
|
1214
1604
|
...stack ? { "exception.stacktrace": stack } : {},
|
|
1215
|
-
"autter.severity":
|
|
1605
|
+
"autter.severity": severity2
|
|
1216
1606
|
});
|
|
1217
|
-
span.setStatus({ code:
|
|
1607
|
+
span.setStatus({ code: import_api3.SpanStatusCode.ERROR, message });
|
|
1218
1608
|
span.end();
|
|
1219
1609
|
}
|
|
1220
1610
|
function reportOutcome2(name, message, attributes) {
|
|
1221
1611
|
errorTraceBuffer?.retainActiveTrace();
|
|
1222
1612
|
telemetryStats.markCaptured();
|
|
1223
|
-
const activeSpan =
|
|
1613
|
+
const activeSpan = import_api3.trace.getActiveSpan();
|
|
1224
1614
|
const reuseActive = activeSpan?.isRecording() === true;
|
|
1225
1615
|
const span = reuseActive ? activeSpan : errorTracer.startSpan("OutcomeFailure");
|
|
1226
1616
|
const safeOutcome = activeRedactor({ "autter.outcome.name": name.slice(0, 200), "autter.outcome.message": message.slice(0, 1e3) });
|
|
@@ -1232,9 +1622,20 @@ function initAutterServer(options) {
|
|
|
1232
1622
|
"autter.outcome.name": safeName,
|
|
1233
1623
|
"autter.outcome.message": safeMessage
|
|
1234
1624
|
});
|
|
1235
|
-
span.setStatus({ code:
|
|
1625
|
+
span.setStatus({ code: import_api3.SpanStatusCode.ERROR, message: safeMessage });
|
|
1236
1626
|
if (!reuseActive) span.end();
|
|
1237
1627
|
}
|
|
1628
|
+
configureRuntimeLogger({
|
|
1629
|
+
endpoint,
|
|
1630
|
+
apiKey: options.apiKey,
|
|
1631
|
+
service: options.service,
|
|
1632
|
+
environment,
|
|
1633
|
+
release: options.release,
|
|
1634
|
+
options: options.logging,
|
|
1635
|
+
redact: activeRedactor,
|
|
1636
|
+
run: (name, fn, attributes) => runWithSpan(processTracer, name, fn, activeRedactor(attributes)),
|
|
1637
|
+
reportOutcome: reportOutcome2
|
|
1638
|
+
});
|
|
1238
1639
|
if (options.captureGlobalErrors !== false) {
|
|
1239
1640
|
process.on("uncaughtExceptionMonitor", (err) => {
|
|
1240
1641
|
captureException2(err, { "autter.unhandled": true });
|
|
@@ -1245,6 +1646,7 @@ function initAutterServer(options) {
|
|
|
1245
1646
|
const flushTarget = {
|
|
1246
1647
|
forceFlush: async () => {
|
|
1247
1648
|
const results = await Promise.allSettled([
|
|
1649
|
+
flushRuntimeLogs(),
|
|
1248
1650
|
Promise.resolve().then(() => alwaysOnProvider.forceFlush()),
|
|
1249
1651
|
Promise.resolve().then(() => mainSpanProcessor.forceFlush()),
|
|
1250
1652
|
...errorTraceBuffer ? [Promise.resolve().then(() => errorTraceBuffer.forceFlush())] : [],
|
|
@@ -1271,6 +1673,7 @@ function initAutterServer(options) {
|
|
|
1271
1673
|
withLlmCall: (info, fn) => runLlmSpan(llmTracer, info, fn),
|
|
1272
1674
|
trackLlmCall: (call) => recordLlmCall(llmTracer, call),
|
|
1273
1675
|
shutdown: async () => {
|
|
1676
|
+
const logShutdown = await Promise.allSettled([shutdownRuntimeLogger()]);
|
|
1274
1677
|
stopMemoryMetrics?.();
|
|
1275
1678
|
active = null;
|
|
1276
1679
|
activeAlwaysOnProvider = null;
|
|
@@ -1279,6 +1682,7 @@ function initAutterServer(options) {
|
|
|
1279
1682
|
unregisterFlushTargets();
|
|
1280
1683
|
telemetryStats.markAllFlushed();
|
|
1281
1684
|
await Promise.allSettled([alwaysOnProvider.shutdown(), sdk.shutdown()]);
|
|
1685
|
+
if (logShutdown[0]?.status === "rejected") throw logShutdown[0].reason;
|
|
1282
1686
|
}
|
|
1283
1687
|
};
|
|
1284
1688
|
active = server;
|
|
@@ -1290,48 +1694,48 @@ function captureException(error, attributes) {
|
|
|
1290
1694
|
active.captureException(error, attributes);
|
|
1291
1695
|
return;
|
|
1292
1696
|
}
|
|
1293
|
-
const span =
|
|
1697
|
+
const span = import_api3.trace.getTracer("autter-errors").startSpan(error instanceof Error ? error.name : "Error", { attributes });
|
|
1294
1698
|
if (error instanceof Error) span.recordException(error);
|
|
1295
1699
|
span.setStatus({
|
|
1296
|
-
code:
|
|
1700
|
+
code: import_api3.SpanStatusCode.ERROR,
|
|
1297
1701
|
message: error instanceof Error ? error.message : String(error)
|
|
1298
1702
|
});
|
|
1299
1703
|
span.end();
|
|
1300
1704
|
}
|
|
1301
1705
|
function withProcessSpan(name, fn, attributes) {
|
|
1302
1706
|
if (active) return active.withProcessSpan(name, fn, attributes);
|
|
1303
|
-
return runWithSpan(
|
|
1707
|
+
return runWithSpan(import_api3.trace.getTracer("autter-processes"), name, fn, attributes);
|
|
1304
1708
|
}
|
|
1305
1709
|
function withLlmCall(info, fn) {
|
|
1306
1710
|
if (active) return active.withLlmCall(info, fn);
|
|
1307
|
-
return runLlmSpan(
|
|
1711
|
+
return runLlmSpan(import_api3.trace.getTracer("autter-llm"), info, fn);
|
|
1308
1712
|
}
|
|
1309
1713
|
function trackLlmCall(call) {
|
|
1310
1714
|
if (active) {
|
|
1311
1715
|
active.trackLlmCall(call);
|
|
1312
1716
|
return;
|
|
1313
1717
|
}
|
|
1314
|
-
recordLlmCall(
|
|
1718
|
+
recordLlmCall(import_api3.trace.getTracer("autter-llm"), call);
|
|
1315
1719
|
}
|
|
1316
|
-
function captureMessage(message,
|
|
1720
|
+
function captureMessage(message, severity2 = "warning", attributes) {
|
|
1317
1721
|
if (active) {
|
|
1318
|
-
active.captureMessage(message,
|
|
1722
|
+
active.captureMessage(message, severity2, attributes);
|
|
1319
1723
|
return;
|
|
1320
1724
|
}
|
|
1321
|
-
const span =
|
|
1322
|
-
attributes: { "autter.severity":
|
|
1725
|
+
const span = import_api3.trace.getTracer("autter-errors").startSpan("Message", {
|
|
1726
|
+
attributes: { "autter.severity": severity2, ...attributes }
|
|
1323
1727
|
});
|
|
1324
1728
|
span.addEvent("exception", {
|
|
1325
1729
|
"exception.type": "Message",
|
|
1326
1730
|
"exception.message": message,
|
|
1327
|
-
"autter.severity":
|
|
1731
|
+
"autter.severity": severity2
|
|
1328
1732
|
});
|
|
1329
|
-
span.setStatus({ code:
|
|
1733
|
+
span.setStatus({ code: import_api3.SpanStatusCode.ERROR, message });
|
|
1330
1734
|
span.end();
|
|
1331
1735
|
}
|
|
1332
1736
|
function reportOutcome(name, message, attributes) {
|
|
1333
1737
|
if (active) return active.reportOutcome(name, message, attributes);
|
|
1334
|
-
const span =
|
|
1738
|
+
const span = import_api3.trace.getTracer("autter-outcomes").startSpan("OutcomeFailure");
|
|
1335
1739
|
const safeOutcome = redactAttributes({ "autter.outcome.name": name.slice(0, 200), "autter.outcome.message": message.slice(0, 1e3) });
|
|
1336
1740
|
const safeName = String(safeOutcome["autter.outcome.name"] ?? "operation");
|
|
1337
1741
|
const safeMessage = String(safeOutcome["autter.outcome.message"] ?? "failed");
|
|
@@ -1341,18 +1745,18 @@ function reportOutcome(name, message, attributes) {
|
|
|
1341
1745
|
"autter.outcome.name": safeName,
|
|
1342
1746
|
"autter.outcome.message": safeMessage
|
|
1343
1747
|
});
|
|
1344
|
-
span.setStatus({ code:
|
|
1748
|
+
span.setStatus({ code: import_api3.SpanStatusCode.ERROR, message: safeMessage });
|
|
1345
1749
|
span.end();
|
|
1346
1750
|
}
|
|
1347
1751
|
function makeSafeCapture(options) {
|
|
1348
1752
|
const clean = (attributes) => attributes ? { ...redactAttributes(attributes, options) } : void 0;
|
|
1349
1753
|
return {
|
|
1350
1754
|
captureException: (error, attributes) => captureException(error, clean(attributes)),
|
|
1351
|
-
captureMessage: (message,
|
|
1755
|
+
captureMessage: (message, severity2, attributes) => captureMessage(message, severity2 ?? "warning", clean(attributes))
|
|
1352
1756
|
};
|
|
1353
1757
|
}
|
|
1354
1758
|
function alwaysOnTracer(name) {
|
|
1355
|
-
return activeAlwaysOnProvider ? activeAlwaysOnProvider.getTracer(name) :
|
|
1759
|
+
return activeAlwaysOnProvider ? activeAlwaysOnProvider.getTracer(name) : import_api3.trace.getTracer(name);
|
|
1356
1760
|
}
|
|
1357
1761
|
function autterLlmTracer() {
|
|
1358
1762
|
return alwaysOnTracer("autter-llm");
|
|
@@ -1372,7 +1776,7 @@ async function emitLlmSelftestTrace() {
|
|
|
1372
1776
|
attributes: { "autter.selftest": true }
|
|
1373
1777
|
},
|
|
1374
1778
|
(llm) => {
|
|
1375
|
-
traceId =
|
|
1779
|
+
traceId = import_api3.trace.getActiveSpan()?.spanContext().traceId ?? "";
|
|
1376
1780
|
llm.setUsage({ inputTokens: 1, outputTokens: 1 });
|
|
1377
1781
|
llm.setCost(0);
|
|
1378
1782
|
}
|
|
@@ -1382,7 +1786,7 @@ async function emitLlmSelftestTrace() {
|
|
|
1382
1786
|
}
|
|
1383
1787
|
|
|
1384
1788
|
// src/llm-instrument.ts
|
|
1385
|
-
var
|
|
1789
|
+
var import_api4 = require("@opentelemetry/api");
|
|
1386
1790
|
var CALL_METHODS = /* @__PURE__ */ new Set([
|
|
1387
1791
|
"create",
|
|
1388
1792
|
// openai chat/completions/embeddings/responses, anthropic messages
|
|
@@ -1440,7 +1844,7 @@ function applyUsage(span, usage) {
|
|
|
1440
1844
|
function endWithError(span, err) {
|
|
1441
1845
|
if (err instanceof Error) span.recordException(err);
|
|
1442
1846
|
span.setStatus({
|
|
1443
|
-
code:
|
|
1847
|
+
code: import_api4.SpanStatusCode.ERROR,
|
|
1444
1848
|
message: err instanceof Error ? err.message : String(err)
|
|
1445
1849
|
});
|
|
1446
1850
|
span.end();
|
|
@@ -1460,7 +1864,7 @@ function wrapStream(stream, span) {
|
|
|
1460
1864
|
if (err !== void 0) {
|
|
1461
1865
|
endWithError(span, err);
|
|
1462
1866
|
} else {
|
|
1463
|
-
span.setStatus({ code:
|
|
1867
|
+
span.setStatus({ code: import_api4.SpanStatusCode.OK });
|
|
1464
1868
|
span.end();
|
|
1465
1869
|
}
|
|
1466
1870
|
};
|
|
@@ -1487,9 +1891,9 @@ function wrapStream(stream, span) {
|
|
|
1487
1891
|
throw err;
|
|
1488
1892
|
}
|
|
1489
1893
|
},
|
|
1490
|
-
async return(
|
|
1894
|
+
async return(value3) {
|
|
1491
1895
|
finish();
|
|
1492
|
-
return inner.return ? inner.return(
|
|
1896
|
+
return inner.return ? inner.return(value3) : { done: true, value: void 0 };
|
|
1493
1897
|
},
|
|
1494
1898
|
async throw(err) {
|
|
1495
1899
|
finish(err);
|
|
@@ -1499,8 +1903,8 @@ function wrapStream(stream, span) {
|
|
|
1499
1903
|
};
|
|
1500
1904
|
};
|
|
1501
1905
|
}
|
|
1502
|
-
const
|
|
1503
|
-
return typeof
|
|
1906
|
+
const value2 = Reflect.get(target, prop, receiver);
|
|
1907
|
+
return typeof value2 === "function" ? value2.bind(target) : value2;
|
|
1504
1908
|
}
|
|
1505
1909
|
});
|
|
1506
1910
|
}
|
|
@@ -1515,7 +1919,7 @@ function instrumentLlmClient(client, options) {
|
|
|
1515
1919
|
const operation = /embed/i.test(callPath) ? "embeddings" : "chat";
|
|
1516
1920
|
const userId = resolveUserId();
|
|
1517
1921
|
const span = autterLlmTracer().startSpan(`${operation} ${request.model}`, {
|
|
1518
|
-
kind:
|
|
1922
|
+
kind: import_api4.SpanKind.CLIENT,
|
|
1519
1923
|
attributes: {
|
|
1520
1924
|
"gen_ai.operation.name": operation,
|
|
1521
1925
|
"gen_ai.provider.name": provider,
|
|
@@ -1537,7 +1941,7 @@ function instrumentLlmClient(client, options) {
|
|
|
1537
1941
|
return wrapStream(result, span);
|
|
1538
1942
|
}
|
|
1539
1943
|
applyUsage(span, extractUsage(result));
|
|
1540
|
-
span.setStatus({ code:
|
|
1944
|
+
span.setStatus({ code: import_api4.SpanStatusCode.OK });
|
|
1541
1945
|
span.end();
|
|
1542
1946
|
return result;
|
|
1543
1947
|
}
|
|
@@ -1547,7 +1951,7 @@ function instrumentLlmClient(client, options) {
|
|
|
1547
1951
|
return wrapStream(resolved, span);
|
|
1548
1952
|
}
|
|
1549
1953
|
applyUsage(span, extractUsage(resolved));
|
|
1550
|
-
span.setStatus({ code:
|
|
1954
|
+
span.setStatus({ code: import_api4.SpanStatusCode.OK });
|
|
1551
1955
|
span.end();
|
|
1552
1956
|
return resolved;
|
|
1553
1957
|
},
|
|
@@ -1562,30 +1966,30 @@ function instrumentLlmClient(client, options) {
|
|
|
1562
1966
|
if (cached) return cached;
|
|
1563
1967
|
const proxy = new Proxy(target, {
|
|
1564
1968
|
get(t, prop, receiver) {
|
|
1565
|
-
const
|
|
1566
|
-
if (typeof prop === "symbol") return
|
|
1969
|
+
const value2 = Reflect.get(t, prop, receiver);
|
|
1970
|
+
if (typeof prop === "symbol") return value2;
|
|
1567
1971
|
const propPath = path ? `${path}.${prop}` : prop;
|
|
1568
|
-
if (typeof
|
|
1972
|
+
if (typeof value2 === "function") {
|
|
1569
1973
|
if (CALL_METHODS.has(prop)) {
|
|
1570
1974
|
return (...args) => {
|
|
1571
1975
|
const first = args[0];
|
|
1572
1976
|
if (first && typeof first === "object" && typeof first.model === "string") {
|
|
1573
1977
|
return traceCall(
|
|
1574
|
-
|
|
1978
|
+
value2,
|
|
1575
1979
|
t,
|
|
1576
1980
|
propPath,
|
|
1577
1981
|
args
|
|
1578
1982
|
);
|
|
1579
1983
|
}
|
|
1580
|
-
return
|
|
1984
|
+
return value2.apply(t, args);
|
|
1581
1985
|
};
|
|
1582
1986
|
}
|
|
1583
|
-
return
|
|
1987
|
+
return value2.bind(t);
|
|
1584
1988
|
}
|
|
1585
|
-
if (
|
|
1586
|
-
return deepProxy(
|
|
1989
|
+
if (value2 !== null && typeof value2 === "object" && !(value2 instanceof Promise)) {
|
|
1990
|
+
return deepProxy(value2, propPath);
|
|
1587
1991
|
}
|
|
1588
|
-
return
|
|
1992
|
+
return value2;
|
|
1589
1993
|
}
|
|
1590
1994
|
});
|
|
1591
1995
|
proxied.set(target, proxy);
|
|
@@ -1643,16 +2047,21 @@ ${frames.join("\n")}`;
|
|
|
1643
2047
|
captureMessage,
|
|
1644
2048
|
createBrowserRelayFetchHandler,
|
|
1645
2049
|
createBrowserRelayHandler,
|
|
2050
|
+
createRuntimeLogger,
|
|
1646
2051
|
emitLlmSelftestTrace,
|
|
2052
|
+
flushRuntimeLogs,
|
|
1647
2053
|
initAutterServer,
|
|
1648
2054
|
installAutterAutoFlush,
|
|
1649
2055
|
instrumentLlmClient,
|
|
1650
2056
|
makeSafeCapture,
|
|
1651
2057
|
redactAttributes,
|
|
1652
2058
|
reportOutcome,
|
|
2059
|
+
runtimeLogStats,
|
|
2060
|
+
runtimeLogger,
|
|
1653
2061
|
sanitizeBrowserPayload,
|
|
1654
2062
|
startCaughtExceptionSampler,
|
|
1655
2063
|
trackLlmCall,
|
|
1656
2064
|
withLlmCall,
|
|
1657
|
-
withProcessSpan
|
|
2065
|
+
withProcessSpan,
|
|
2066
|
+
withRuntimeOperation
|
|
1658
2067
|
});
|