@autter/runtime-node 1.3.4 → 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 +515 -107
- package/dist/index.d.cts +39 -1
- package/dist/index.d.ts +39 -1
- package/dist/index.js +481 -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";
|
|
@@ -255,8 +260,8 @@ function createBrowserRelayHandler(opts) {
|
|
|
255
260
|
|
|
256
261
|
// src/server.ts
|
|
257
262
|
var import_node_http = require("http");
|
|
258
|
-
var
|
|
259
|
-
var
|
|
263
|
+
var import_node_crypto2 = require("crypto");
|
|
264
|
+
var import_api3 = require("@opentelemetry/api");
|
|
260
265
|
var import_core2 = require("@opentelemetry/core");
|
|
261
266
|
var import_exporter_metrics_otlp_http = require("@opentelemetry/exporter-metrics-otlp-http");
|
|
262
267
|
var import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
|
|
@@ -342,13 +347,13 @@ function installAutterAutoFlush(options = {}) {
|
|
|
342
347
|
const signals = options.signals ?? DEFAULT_SIGNALS;
|
|
343
348
|
const flushOnUncaughtException = options.flushOnUncaughtException !== false;
|
|
344
349
|
let disposed = false;
|
|
345
|
-
let
|
|
350
|
+
let inFlight2 = null;
|
|
346
351
|
const doFlush = async () => {
|
|
347
352
|
const targets = options.targets ?? activeFlushTargets();
|
|
348
353
|
if (targets.length === 0) return true;
|
|
349
|
-
let
|
|
354
|
+
let timer2;
|
|
350
355
|
const timedOut = new Promise((resolve) => {
|
|
351
|
-
|
|
356
|
+
timer2 = setTimeout(() => resolve(false), timeoutMs);
|
|
352
357
|
});
|
|
353
358
|
const drained = Promise.allSettled(
|
|
354
359
|
targets.map(
|
|
@@ -361,7 +366,7 @@ function installAutterAutoFlush(options = {}) {
|
|
|
361
366
|
try {
|
|
362
367
|
ok = await Promise.race([drained, timedOut]);
|
|
363
368
|
} finally {
|
|
364
|
-
clearTimeout(
|
|
369
|
+
clearTimeout(timer2);
|
|
365
370
|
}
|
|
366
371
|
if (ok) {
|
|
367
372
|
telemetryStats.markAllFlushed();
|
|
@@ -377,13 +382,13 @@ function installAutterAutoFlush(options = {}) {
|
|
|
377
382
|
};
|
|
378
383
|
const flush = (reason = "exit") => {
|
|
379
384
|
if (disposed) return Promise.resolve(false);
|
|
380
|
-
if (!
|
|
381
|
-
|
|
382
|
-
|
|
385
|
+
if (!inFlight2) {
|
|
386
|
+
inFlight2 = doFlush().finally(() => {
|
|
387
|
+
inFlight2 = null;
|
|
383
388
|
});
|
|
384
389
|
}
|
|
385
390
|
if (isDebugEnabled()) debugLog(`auto-flush requested on ${reason}`);
|
|
386
|
-
return
|
|
391
|
+
return inFlight2;
|
|
387
392
|
};
|
|
388
393
|
const signalCounts = /* @__PURE__ */ new Map();
|
|
389
394
|
let handlingUncaughtException = false;
|
|
@@ -522,8 +527,8 @@ function compile(options) {
|
|
|
522
527
|
scrubEmailValues: options?.scrubEmailValues !== false
|
|
523
528
|
};
|
|
524
529
|
}
|
|
525
|
-
function redactString(
|
|
526
|
-
let out =
|
|
530
|
+
function redactString(value2, r) {
|
|
531
|
+
let out = value2.replace(r.privateKeyBlock, r.mask);
|
|
527
532
|
for (const re of r.valuePatterns) {
|
|
528
533
|
out = out.replace(re, r.mask);
|
|
529
534
|
}
|
|
@@ -541,40 +546,40 @@ function canTraverse(depth, state) {
|
|
|
541
546
|
state.remainingWork -= 1;
|
|
542
547
|
return true;
|
|
543
548
|
}
|
|
544
|
-
function redactValue(
|
|
545
|
-
if (typeof
|
|
549
|
+
function redactValue(value2, r, state, depth) {
|
|
550
|
+
if (typeof value2 === "string") return redactString(value2, r);
|
|
546
551
|
let isArray = false;
|
|
547
552
|
try {
|
|
548
|
-
isArray = Array.isArray(
|
|
553
|
+
isArray = Array.isArray(value2);
|
|
549
554
|
} catch {
|
|
550
555
|
return r.mask;
|
|
551
556
|
}
|
|
552
557
|
if (isArray) {
|
|
553
|
-
return redactArray(
|
|
558
|
+
return redactArray(value2, r, state, depth);
|
|
554
559
|
}
|
|
555
|
-
if (typeof
|
|
556
|
-
return redactObject(
|
|
560
|
+
if (typeof value2 === "object" && value2 !== null) {
|
|
561
|
+
return redactObject(value2, r, state, depth);
|
|
557
562
|
}
|
|
558
|
-
return
|
|
563
|
+
return value2;
|
|
559
564
|
}
|
|
560
|
-
function redactArray(
|
|
561
|
-
const existing = state.ancestors.get(
|
|
565
|
+
function redactArray(value2, r, state, depth) {
|
|
566
|
+
const existing = state.ancestors.get(value2);
|
|
562
567
|
if (existing) return r.mask;
|
|
563
568
|
if (!canTraverse(depth, state)) return r.mask;
|
|
564
569
|
const out = [];
|
|
565
|
-
state.ancestors.set(
|
|
570
|
+
state.ancestors.set(value2, out);
|
|
566
571
|
let length;
|
|
567
572
|
try {
|
|
568
|
-
length =
|
|
573
|
+
length = value2.length;
|
|
569
574
|
} catch {
|
|
570
|
-
state.ancestors.delete(
|
|
575
|
+
state.ancestors.delete(value2);
|
|
571
576
|
return r.mask;
|
|
572
577
|
}
|
|
573
578
|
const limit = Math.min(length, MAX_COLLECTION_ENTRIES);
|
|
574
579
|
for (let i = 0; i < limit; i += 1) {
|
|
575
580
|
let item;
|
|
576
581
|
try {
|
|
577
|
-
item =
|
|
582
|
+
item = value2[i];
|
|
578
583
|
} catch {
|
|
579
584
|
out.push(r.mask);
|
|
580
585
|
continue;
|
|
@@ -584,21 +589,21 @@ function redactArray(value, r, state, depth) {
|
|
|
584
589
|
if (length > limit) {
|
|
585
590
|
out.push(r.mask);
|
|
586
591
|
}
|
|
587
|
-
state.ancestors.delete(
|
|
592
|
+
state.ancestors.delete(value2);
|
|
588
593
|
return out;
|
|
589
594
|
}
|
|
590
|
-
function redactObject(
|
|
591
|
-
const existing = state.ancestors.get(
|
|
595
|
+
function redactObject(value2, r, state, depth) {
|
|
596
|
+
const existing = state.ancestors.get(value2);
|
|
592
597
|
if (existing) return r.mask;
|
|
593
598
|
if (!canTraverse(depth, state)) return r.mask;
|
|
594
599
|
const out = {};
|
|
595
|
-
state.ancestors.set(
|
|
600
|
+
state.ancestors.set(value2, out);
|
|
596
601
|
let count = 0;
|
|
597
602
|
let truncated = false;
|
|
598
603
|
try {
|
|
599
|
-
for (const key in
|
|
604
|
+
for (const key in value2) {
|
|
600
605
|
if (!Object.prototype.propertyIsEnumerable.call(
|
|
601
|
-
|
|
606
|
+
value2,
|
|
602
607
|
key
|
|
603
608
|
)) {
|
|
604
609
|
continue;
|
|
@@ -609,7 +614,7 @@ function redactObject(value, r, state, depth) {
|
|
|
609
614
|
}
|
|
610
615
|
let nestedValue;
|
|
611
616
|
try {
|
|
612
|
-
nestedValue =
|
|
617
|
+
nestedValue = value2[key];
|
|
613
618
|
} catch {
|
|
614
619
|
out[key] = r.mask;
|
|
615
620
|
count += 1;
|
|
@@ -629,7 +634,7 @@ function redactObject(value, r, state, depth) {
|
|
|
629
634
|
if (truncated) {
|
|
630
635
|
out.__redaction_truncated__ = r.mask;
|
|
631
636
|
}
|
|
632
|
-
state.ancestors.delete(
|
|
637
|
+
state.ancestors.delete(value2);
|
|
633
638
|
return out;
|
|
634
639
|
}
|
|
635
640
|
var USAGE_TOKEN_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -646,10 +651,10 @@ var USAGE_TOKEN_KEYS = /* @__PURE__ */ new Set([
|
|
|
646
651
|
"gen_ai.usage.total_tokens",
|
|
647
652
|
"gen_ai.usage.token_count"
|
|
648
653
|
]);
|
|
649
|
-
function isSensitiveKey(key,
|
|
654
|
+
function isSensitiveKey(key, value2, r) {
|
|
650
655
|
const lowered = key.toLowerCase();
|
|
651
656
|
if (USAGE_TOKEN_KEYS.has(lowered)) {
|
|
652
|
-
return !(typeof
|
|
657
|
+
return !(typeof value2 === "number" && Number.isFinite(value2) && value2 >= 0);
|
|
653
658
|
}
|
|
654
659
|
return r.keyPatterns.some((re) => re.test(lowered));
|
|
655
660
|
}
|
|
@@ -678,9 +683,9 @@ function redactWith(attributes, r) {
|
|
|
678
683
|
truncated = true;
|
|
679
684
|
break;
|
|
680
685
|
}
|
|
681
|
-
let
|
|
686
|
+
let value2;
|
|
682
687
|
try {
|
|
683
|
-
|
|
688
|
+
value2 = attributes[key];
|
|
684
689
|
} catch {
|
|
685
690
|
out[key] = r.mask;
|
|
686
691
|
count += 1;
|
|
@@ -689,8 +694,8 @@ function redactWith(attributes, r) {
|
|
|
689
694
|
}
|
|
690
695
|
count += 1;
|
|
691
696
|
state.remainingWork -= 1;
|
|
692
|
-
if (
|
|
693
|
-
out[key] = isSensitiveKey(key,
|
|
697
|
+
if (value2 === void 0) continue;
|
|
698
|
+
out[key] = isSensitiveKey(key, value2, r) ? r.mask : redactValue(value2, r, state, 0);
|
|
694
699
|
}
|
|
695
700
|
} catch {
|
|
696
701
|
truncated = true;
|
|
@@ -753,6 +758,390 @@ function startMemoryMetrics() {
|
|
|
753
758
|
};
|
|
754
759
|
}
|
|
755
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
|
+
|
|
756
1145
|
// src/server.ts
|
|
757
1146
|
var LLM_MARKER_ATTRIBUTES = [
|
|
758
1147
|
"gen_ai.operation.name",
|
|
@@ -815,14 +1204,14 @@ var RETENTION_MAX_BUFFERED_SPANS = 5e3;
|
|
|
815
1204
|
var RETENTION_SWEEP_INTERVAL_MS = 1e4;
|
|
816
1205
|
var RETENTION_TRACE_TTL_MS = 3e4;
|
|
817
1206
|
function spanIndicatesError(span) {
|
|
818
|
-
if (span.status.code ===
|
|
1207
|
+
if (span.status.code === import_api3.SpanStatusCode.ERROR) return true;
|
|
819
1208
|
return span.events.some((event) => event.name === "exception");
|
|
820
1209
|
}
|
|
821
1210
|
var TAIL_RETAINED_ATTR = "autter.tail_retained";
|
|
822
1211
|
function withSampledFlag(span) {
|
|
823
1212
|
const spanContext = {
|
|
824
1213
|
...span.spanContext(),
|
|
825
|
-
traceFlags: span.spanContext().traceFlags |
|
|
1214
|
+
traceFlags: span.spanContext().traceFlags | import_api3.TraceFlags.SAMPLED
|
|
826
1215
|
};
|
|
827
1216
|
return {
|
|
828
1217
|
name: span.name,
|
|
@@ -862,11 +1251,11 @@ var ErrorTraceRetentionProcessor = class {
|
|
|
862
1251
|
}
|
|
863
1252
|
onEnd(span) {
|
|
864
1253
|
const ctx = span.spanContext();
|
|
865
|
-
if ((ctx.traceFlags &
|
|
1254
|
+
if ((ctx.traceFlags & import_api3.TraceFlags.SAMPLED) !== 0) return;
|
|
866
1255
|
const entry = this.entryFor(ctx.traceId);
|
|
867
1256
|
if (entry.retained) {
|
|
868
1257
|
this.forward(span);
|
|
869
|
-
} 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) {
|
|
870
1259
|
entry.retained = true;
|
|
871
1260
|
this.flush(entry);
|
|
872
1261
|
this.forward(span);
|
|
@@ -881,8 +1270,8 @@ var ErrorTraceRetentionProcessor = class {
|
|
|
881
1270
|
}
|
|
882
1271
|
/** Promote the active trace, if any — called on captured errors. */
|
|
883
1272
|
retainActiveTrace() {
|
|
884
|
-
const active2 =
|
|
885
|
-
if (!active2 || (active2.traceFlags &
|
|
1273
|
+
const active2 = import_api3.trace.getActiveSpan()?.spanContext();
|
|
1274
|
+
if (!active2 || (active2.traceFlags & import_api3.TraceFlags.SAMPLED) !== 0) return;
|
|
886
1275
|
const entry = this.entryFor(active2.traceId);
|
|
887
1276
|
if (entry.retained) return;
|
|
888
1277
|
entry.retained = true;
|
|
@@ -971,7 +1360,7 @@ function usageAttributes(usage) {
|
|
|
971
1360
|
}
|
|
972
1361
|
async function runLlmSpan(tracer, info, fn) {
|
|
973
1362
|
const span = tracer.startSpan(`${info.operation ?? "chat"} ${info.model}`, {
|
|
974
|
-
kind:
|
|
1363
|
+
kind: import_api3.SpanKind.CLIENT,
|
|
975
1364
|
attributes: llmBaseAttributes(info)
|
|
976
1365
|
});
|
|
977
1366
|
const setResponseModel = (model) => span.setAttribute("gen_ai.response.model", model);
|
|
@@ -983,11 +1372,11 @@ async function runLlmSpan(tracer, info, fn) {
|
|
|
983
1372
|
setAttributes: (attributes) => span.setAttributes(attributes)
|
|
984
1373
|
};
|
|
985
1374
|
try {
|
|
986
|
-
const result = await
|
|
987
|
-
|
|
1375
|
+
const result = await import_api3.context.with(
|
|
1376
|
+
import_api3.trace.setSpan(import_api3.context.active(), span),
|
|
988
1377
|
() => fn(handle)
|
|
989
1378
|
);
|
|
990
|
-
span.setStatus({ code:
|
|
1379
|
+
span.setStatus({ code: import_api3.SpanStatusCode.OK });
|
|
991
1380
|
return result;
|
|
992
1381
|
} catch (err) {
|
|
993
1382
|
if (err instanceof Error) {
|
|
@@ -995,7 +1384,7 @@ async function runLlmSpan(tracer, info, fn) {
|
|
|
995
1384
|
span.setAttribute("error.type", err.name);
|
|
996
1385
|
}
|
|
997
1386
|
span.setStatus({
|
|
998
|
-
code:
|
|
1387
|
+
code: import_api3.SpanStatusCode.ERROR,
|
|
999
1388
|
message: err instanceof Error ? err.message : String(err)
|
|
1000
1389
|
});
|
|
1001
1390
|
throw err;
|
|
@@ -1010,7 +1399,7 @@ function recordLlmCall(tracer, call) {
|
|
|
1010
1399
|
const span = tracer.startSpan(
|
|
1011
1400
|
`${call.operation ?? "chat"} ${call.model}`,
|
|
1012
1401
|
{
|
|
1013
|
-
kind:
|
|
1402
|
+
kind: import_api3.SpanKind.CLIENT,
|
|
1014
1403
|
startTime,
|
|
1015
1404
|
attributes: { ...llmBaseAttributes(call), ...usageAttributes(call) }
|
|
1016
1405
|
}
|
|
@@ -1021,27 +1410,27 @@ function recordLlmCall(tracer, call) {
|
|
|
1021
1410
|
span.setAttribute("error.type", call.error.name);
|
|
1022
1411
|
}
|
|
1023
1412
|
span.setStatus({
|
|
1024
|
-
code:
|
|
1413
|
+
code: import_api3.SpanStatusCode.ERROR,
|
|
1025
1414
|
message: call.error instanceof Error ? call.error.message : String(call.error)
|
|
1026
1415
|
});
|
|
1027
1416
|
} else {
|
|
1028
|
-
span.setStatus({ code:
|
|
1417
|
+
span.setStatus({ code: import_api3.SpanStatusCode.OK });
|
|
1029
1418
|
}
|
|
1030
1419
|
span.end(endTime);
|
|
1031
1420
|
}
|
|
1032
1421
|
async function runWithSpan(tracer, name, fn, attributes) {
|
|
1033
1422
|
const span = tracer.startSpan(name, { attributes });
|
|
1034
1423
|
try {
|
|
1035
|
-
const result = await
|
|
1036
|
-
|
|
1424
|
+
const result = await import_api3.context.with(
|
|
1425
|
+
import_api3.trace.setSpan(import_api3.context.active(), span),
|
|
1037
1426
|
fn
|
|
1038
1427
|
);
|
|
1039
|
-
span.setStatus({ code:
|
|
1428
|
+
span.setStatus({ code: import_api3.SpanStatusCode.OK });
|
|
1040
1429
|
return result;
|
|
1041
1430
|
} catch (err) {
|
|
1042
1431
|
if (err instanceof Error) span.recordException(err);
|
|
1043
1432
|
span.setStatus({
|
|
1044
|
-
code:
|
|
1433
|
+
code: import_api3.SpanStatusCode.ERROR,
|
|
1045
1434
|
message: err instanceof Error ? err.message : String(err)
|
|
1046
1435
|
});
|
|
1047
1436
|
throw err;
|
|
@@ -1059,7 +1448,7 @@ function expressRouteOf(req) {
|
|
|
1059
1448
|
}
|
|
1060
1449
|
function captureExpressRoute(_span, response) {
|
|
1061
1450
|
if (!(response instanceof import_node_http.ServerResponse)) return;
|
|
1062
|
-
const rpcMetadata = (0, import_core2.getRPCMetadata)(
|
|
1451
|
+
const rpcMetadata = (0, import_core2.getRPCMetadata)(import_api3.context.active());
|
|
1063
1452
|
if (rpcMetadata?.type !== import_core2.RPCType.HTTP) return;
|
|
1064
1453
|
const setRoute = () => {
|
|
1065
1454
|
if (rpcMetadata.route) return;
|
|
@@ -1085,7 +1474,7 @@ function initAutterServer(options) {
|
|
|
1085
1474
|
activeRedactor = makeRedactor(options.redactAttributes ?? true);
|
|
1086
1475
|
const resource = new import_resources.Resource({
|
|
1087
1476
|
[import_semantic_conventions.ATTR_SERVICE_NAME]: options.service,
|
|
1088
|
-
"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),
|
|
1089
1478
|
...options.release ? { [import_semantic_conventions.ATTR_SERVICE_VERSION]: options.release } : {},
|
|
1090
1479
|
"deployment.environment": environment,
|
|
1091
1480
|
// Tells the ingester request metrics arrive on the metrics pipe, so
|
|
@@ -1172,7 +1561,7 @@ function initAutterServer(options) {
|
|
|
1172
1561
|
const isError = error instanceof Error;
|
|
1173
1562
|
const message = isError ? error.message : String(error);
|
|
1174
1563
|
const redacted = activeRedactor(attributes);
|
|
1175
|
-
const activeSpan =
|
|
1564
|
+
const activeSpan = import_api3.trace.getActiveSpan();
|
|
1176
1565
|
const reuseActive = activeSpan?.isRecording() === true;
|
|
1177
1566
|
const span = reuseActive ? activeSpan : errorTracer.startSpan(isError ? error.name : "Error");
|
|
1178
1567
|
span.setAttributes({ "autter.severity": "error", ...redacted });
|
|
@@ -1197,31 +1586,31 @@ function initAutterServer(options) {
|
|
|
1197
1586
|
...redacted["autter.handled"] === true ? { "autter.handled": true, "autter.sampled": redacted["autter.sampled"] === true } : {}
|
|
1198
1587
|
});
|
|
1199
1588
|
}
|
|
1200
|
-
span.setStatus({ code:
|
|
1589
|
+
span.setStatus({ code: import_api3.SpanStatusCode.ERROR, message });
|
|
1201
1590
|
if (!reuseActive) span.end();
|
|
1202
1591
|
}
|
|
1203
|
-
function captureMessage2(message,
|
|
1204
|
-
if (
|
|
1592
|
+
function captureMessage2(message, severity2 = "warning", attributes) {
|
|
1593
|
+
if (severity2 === "error" || severity2 === "fatal") {
|
|
1205
1594
|
errorTraceBuffer?.retainActiveTrace();
|
|
1206
1595
|
}
|
|
1207
1596
|
telemetryStats.markCaptured();
|
|
1208
1597
|
const stack = new Error().stack?.split("\n").filter((line, i) => i === 0 || !line.includes("captureMessage")).join("\n");
|
|
1209
1598
|
const span = errorTracer.startSpan("Message", {
|
|
1210
|
-
attributes: { "autter.severity":
|
|
1599
|
+
attributes: { "autter.severity": severity2, ...activeRedactor(attributes) }
|
|
1211
1600
|
});
|
|
1212
1601
|
span.addEvent("exception", {
|
|
1213
1602
|
"exception.type": "Message",
|
|
1214
1603
|
"exception.message": message,
|
|
1215
1604
|
...stack ? { "exception.stacktrace": stack } : {},
|
|
1216
|
-
"autter.severity":
|
|
1605
|
+
"autter.severity": severity2
|
|
1217
1606
|
});
|
|
1218
|
-
span.setStatus({ code:
|
|
1607
|
+
span.setStatus({ code: import_api3.SpanStatusCode.ERROR, message });
|
|
1219
1608
|
span.end();
|
|
1220
1609
|
}
|
|
1221
1610
|
function reportOutcome2(name, message, attributes) {
|
|
1222
1611
|
errorTraceBuffer?.retainActiveTrace();
|
|
1223
1612
|
telemetryStats.markCaptured();
|
|
1224
|
-
const activeSpan =
|
|
1613
|
+
const activeSpan = import_api3.trace.getActiveSpan();
|
|
1225
1614
|
const reuseActive = activeSpan?.isRecording() === true;
|
|
1226
1615
|
const span = reuseActive ? activeSpan : errorTracer.startSpan("OutcomeFailure");
|
|
1227
1616
|
const safeOutcome = activeRedactor({ "autter.outcome.name": name.slice(0, 200), "autter.outcome.message": message.slice(0, 1e3) });
|
|
@@ -1233,9 +1622,20 @@ function initAutterServer(options) {
|
|
|
1233
1622
|
"autter.outcome.name": safeName,
|
|
1234
1623
|
"autter.outcome.message": safeMessage
|
|
1235
1624
|
});
|
|
1236
|
-
span.setStatus({ code:
|
|
1625
|
+
span.setStatus({ code: import_api3.SpanStatusCode.ERROR, message: safeMessage });
|
|
1237
1626
|
if (!reuseActive) span.end();
|
|
1238
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
|
+
});
|
|
1239
1639
|
if (options.captureGlobalErrors !== false) {
|
|
1240
1640
|
process.on("uncaughtExceptionMonitor", (err) => {
|
|
1241
1641
|
captureException2(err, { "autter.unhandled": true });
|
|
@@ -1246,6 +1646,7 @@ function initAutterServer(options) {
|
|
|
1246
1646
|
const flushTarget = {
|
|
1247
1647
|
forceFlush: async () => {
|
|
1248
1648
|
const results = await Promise.allSettled([
|
|
1649
|
+
flushRuntimeLogs(),
|
|
1249
1650
|
Promise.resolve().then(() => alwaysOnProvider.forceFlush()),
|
|
1250
1651
|
Promise.resolve().then(() => mainSpanProcessor.forceFlush()),
|
|
1251
1652
|
...errorTraceBuffer ? [Promise.resolve().then(() => errorTraceBuffer.forceFlush())] : [],
|
|
@@ -1272,6 +1673,7 @@ function initAutterServer(options) {
|
|
|
1272
1673
|
withLlmCall: (info, fn) => runLlmSpan(llmTracer, info, fn),
|
|
1273
1674
|
trackLlmCall: (call) => recordLlmCall(llmTracer, call),
|
|
1274
1675
|
shutdown: async () => {
|
|
1676
|
+
const logShutdown = await Promise.allSettled([shutdownRuntimeLogger()]);
|
|
1275
1677
|
stopMemoryMetrics?.();
|
|
1276
1678
|
active = null;
|
|
1277
1679
|
activeAlwaysOnProvider = null;
|
|
@@ -1280,6 +1682,7 @@ function initAutterServer(options) {
|
|
|
1280
1682
|
unregisterFlushTargets();
|
|
1281
1683
|
telemetryStats.markAllFlushed();
|
|
1282
1684
|
await Promise.allSettled([alwaysOnProvider.shutdown(), sdk.shutdown()]);
|
|
1685
|
+
if (logShutdown[0]?.status === "rejected") throw logShutdown[0].reason;
|
|
1283
1686
|
}
|
|
1284
1687
|
};
|
|
1285
1688
|
active = server;
|
|
@@ -1291,48 +1694,48 @@ function captureException(error, attributes) {
|
|
|
1291
1694
|
active.captureException(error, attributes);
|
|
1292
1695
|
return;
|
|
1293
1696
|
}
|
|
1294
|
-
const span =
|
|
1697
|
+
const span = import_api3.trace.getTracer("autter-errors").startSpan(error instanceof Error ? error.name : "Error", { attributes });
|
|
1295
1698
|
if (error instanceof Error) span.recordException(error);
|
|
1296
1699
|
span.setStatus({
|
|
1297
|
-
code:
|
|
1700
|
+
code: import_api3.SpanStatusCode.ERROR,
|
|
1298
1701
|
message: error instanceof Error ? error.message : String(error)
|
|
1299
1702
|
});
|
|
1300
1703
|
span.end();
|
|
1301
1704
|
}
|
|
1302
1705
|
function withProcessSpan(name, fn, attributes) {
|
|
1303
1706
|
if (active) return active.withProcessSpan(name, fn, attributes);
|
|
1304
|
-
return runWithSpan(
|
|
1707
|
+
return runWithSpan(import_api3.trace.getTracer("autter-processes"), name, fn, attributes);
|
|
1305
1708
|
}
|
|
1306
1709
|
function withLlmCall(info, fn) {
|
|
1307
1710
|
if (active) return active.withLlmCall(info, fn);
|
|
1308
|
-
return runLlmSpan(
|
|
1711
|
+
return runLlmSpan(import_api3.trace.getTracer("autter-llm"), info, fn);
|
|
1309
1712
|
}
|
|
1310
1713
|
function trackLlmCall(call) {
|
|
1311
1714
|
if (active) {
|
|
1312
1715
|
active.trackLlmCall(call);
|
|
1313
1716
|
return;
|
|
1314
1717
|
}
|
|
1315
|
-
recordLlmCall(
|
|
1718
|
+
recordLlmCall(import_api3.trace.getTracer("autter-llm"), call);
|
|
1316
1719
|
}
|
|
1317
|
-
function captureMessage(message,
|
|
1720
|
+
function captureMessage(message, severity2 = "warning", attributes) {
|
|
1318
1721
|
if (active) {
|
|
1319
|
-
active.captureMessage(message,
|
|
1722
|
+
active.captureMessage(message, severity2, attributes);
|
|
1320
1723
|
return;
|
|
1321
1724
|
}
|
|
1322
|
-
const span =
|
|
1323
|
-
attributes: { "autter.severity":
|
|
1725
|
+
const span = import_api3.trace.getTracer("autter-errors").startSpan("Message", {
|
|
1726
|
+
attributes: { "autter.severity": severity2, ...attributes }
|
|
1324
1727
|
});
|
|
1325
1728
|
span.addEvent("exception", {
|
|
1326
1729
|
"exception.type": "Message",
|
|
1327
1730
|
"exception.message": message,
|
|
1328
|
-
"autter.severity":
|
|
1731
|
+
"autter.severity": severity2
|
|
1329
1732
|
});
|
|
1330
|
-
span.setStatus({ code:
|
|
1733
|
+
span.setStatus({ code: import_api3.SpanStatusCode.ERROR, message });
|
|
1331
1734
|
span.end();
|
|
1332
1735
|
}
|
|
1333
1736
|
function reportOutcome(name, message, attributes) {
|
|
1334
1737
|
if (active) return active.reportOutcome(name, message, attributes);
|
|
1335
|
-
const span =
|
|
1738
|
+
const span = import_api3.trace.getTracer("autter-outcomes").startSpan("OutcomeFailure");
|
|
1336
1739
|
const safeOutcome = redactAttributes({ "autter.outcome.name": name.slice(0, 200), "autter.outcome.message": message.slice(0, 1e3) });
|
|
1337
1740
|
const safeName = String(safeOutcome["autter.outcome.name"] ?? "operation");
|
|
1338
1741
|
const safeMessage = String(safeOutcome["autter.outcome.message"] ?? "failed");
|
|
@@ -1342,18 +1745,18 @@ function reportOutcome(name, message, attributes) {
|
|
|
1342
1745
|
"autter.outcome.name": safeName,
|
|
1343
1746
|
"autter.outcome.message": safeMessage
|
|
1344
1747
|
});
|
|
1345
|
-
span.setStatus({ code:
|
|
1748
|
+
span.setStatus({ code: import_api3.SpanStatusCode.ERROR, message: safeMessage });
|
|
1346
1749
|
span.end();
|
|
1347
1750
|
}
|
|
1348
1751
|
function makeSafeCapture(options) {
|
|
1349
1752
|
const clean = (attributes) => attributes ? { ...redactAttributes(attributes, options) } : void 0;
|
|
1350
1753
|
return {
|
|
1351
1754
|
captureException: (error, attributes) => captureException(error, clean(attributes)),
|
|
1352
|
-
captureMessage: (message,
|
|
1755
|
+
captureMessage: (message, severity2, attributes) => captureMessage(message, severity2 ?? "warning", clean(attributes))
|
|
1353
1756
|
};
|
|
1354
1757
|
}
|
|
1355
1758
|
function alwaysOnTracer(name) {
|
|
1356
|
-
return activeAlwaysOnProvider ? activeAlwaysOnProvider.getTracer(name) :
|
|
1759
|
+
return activeAlwaysOnProvider ? activeAlwaysOnProvider.getTracer(name) : import_api3.trace.getTracer(name);
|
|
1357
1760
|
}
|
|
1358
1761
|
function autterLlmTracer() {
|
|
1359
1762
|
return alwaysOnTracer("autter-llm");
|
|
@@ -1373,7 +1776,7 @@ async function emitLlmSelftestTrace() {
|
|
|
1373
1776
|
attributes: { "autter.selftest": true }
|
|
1374
1777
|
},
|
|
1375
1778
|
(llm) => {
|
|
1376
|
-
traceId =
|
|
1779
|
+
traceId = import_api3.trace.getActiveSpan()?.spanContext().traceId ?? "";
|
|
1377
1780
|
llm.setUsage({ inputTokens: 1, outputTokens: 1 });
|
|
1378
1781
|
llm.setCost(0);
|
|
1379
1782
|
}
|
|
@@ -1383,7 +1786,7 @@ async function emitLlmSelftestTrace() {
|
|
|
1383
1786
|
}
|
|
1384
1787
|
|
|
1385
1788
|
// src/llm-instrument.ts
|
|
1386
|
-
var
|
|
1789
|
+
var import_api4 = require("@opentelemetry/api");
|
|
1387
1790
|
var CALL_METHODS = /* @__PURE__ */ new Set([
|
|
1388
1791
|
"create",
|
|
1389
1792
|
// openai chat/completions/embeddings/responses, anthropic messages
|
|
@@ -1441,7 +1844,7 @@ function applyUsage(span, usage) {
|
|
|
1441
1844
|
function endWithError(span, err) {
|
|
1442
1845
|
if (err instanceof Error) span.recordException(err);
|
|
1443
1846
|
span.setStatus({
|
|
1444
|
-
code:
|
|
1847
|
+
code: import_api4.SpanStatusCode.ERROR,
|
|
1445
1848
|
message: err instanceof Error ? err.message : String(err)
|
|
1446
1849
|
});
|
|
1447
1850
|
span.end();
|
|
@@ -1461,7 +1864,7 @@ function wrapStream(stream, span) {
|
|
|
1461
1864
|
if (err !== void 0) {
|
|
1462
1865
|
endWithError(span, err);
|
|
1463
1866
|
} else {
|
|
1464
|
-
span.setStatus({ code:
|
|
1867
|
+
span.setStatus({ code: import_api4.SpanStatusCode.OK });
|
|
1465
1868
|
span.end();
|
|
1466
1869
|
}
|
|
1467
1870
|
};
|
|
@@ -1488,9 +1891,9 @@ function wrapStream(stream, span) {
|
|
|
1488
1891
|
throw err;
|
|
1489
1892
|
}
|
|
1490
1893
|
},
|
|
1491
|
-
async return(
|
|
1894
|
+
async return(value3) {
|
|
1492
1895
|
finish();
|
|
1493
|
-
return inner.return ? inner.return(
|
|
1896
|
+
return inner.return ? inner.return(value3) : { done: true, value: void 0 };
|
|
1494
1897
|
},
|
|
1495
1898
|
async throw(err) {
|
|
1496
1899
|
finish(err);
|
|
@@ -1500,8 +1903,8 @@ function wrapStream(stream, span) {
|
|
|
1500
1903
|
};
|
|
1501
1904
|
};
|
|
1502
1905
|
}
|
|
1503
|
-
const
|
|
1504
|
-
return typeof
|
|
1906
|
+
const value2 = Reflect.get(target, prop, receiver);
|
|
1907
|
+
return typeof value2 === "function" ? value2.bind(target) : value2;
|
|
1505
1908
|
}
|
|
1506
1909
|
});
|
|
1507
1910
|
}
|
|
@@ -1516,7 +1919,7 @@ function instrumentLlmClient(client, options) {
|
|
|
1516
1919
|
const operation = /embed/i.test(callPath) ? "embeddings" : "chat";
|
|
1517
1920
|
const userId = resolveUserId();
|
|
1518
1921
|
const span = autterLlmTracer().startSpan(`${operation} ${request.model}`, {
|
|
1519
|
-
kind:
|
|
1922
|
+
kind: import_api4.SpanKind.CLIENT,
|
|
1520
1923
|
attributes: {
|
|
1521
1924
|
"gen_ai.operation.name": operation,
|
|
1522
1925
|
"gen_ai.provider.name": provider,
|
|
@@ -1538,7 +1941,7 @@ function instrumentLlmClient(client, options) {
|
|
|
1538
1941
|
return wrapStream(result, span);
|
|
1539
1942
|
}
|
|
1540
1943
|
applyUsage(span, extractUsage(result));
|
|
1541
|
-
span.setStatus({ code:
|
|
1944
|
+
span.setStatus({ code: import_api4.SpanStatusCode.OK });
|
|
1542
1945
|
span.end();
|
|
1543
1946
|
return result;
|
|
1544
1947
|
}
|
|
@@ -1548,7 +1951,7 @@ function instrumentLlmClient(client, options) {
|
|
|
1548
1951
|
return wrapStream(resolved, span);
|
|
1549
1952
|
}
|
|
1550
1953
|
applyUsage(span, extractUsage(resolved));
|
|
1551
|
-
span.setStatus({ code:
|
|
1954
|
+
span.setStatus({ code: import_api4.SpanStatusCode.OK });
|
|
1552
1955
|
span.end();
|
|
1553
1956
|
return resolved;
|
|
1554
1957
|
},
|
|
@@ -1563,30 +1966,30 @@ function instrumentLlmClient(client, options) {
|
|
|
1563
1966
|
if (cached) return cached;
|
|
1564
1967
|
const proxy = new Proxy(target, {
|
|
1565
1968
|
get(t, prop, receiver) {
|
|
1566
|
-
const
|
|
1567
|
-
if (typeof prop === "symbol") return
|
|
1969
|
+
const value2 = Reflect.get(t, prop, receiver);
|
|
1970
|
+
if (typeof prop === "symbol") return value2;
|
|
1568
1971
|
const propPath = path ? `${path}.${prop}` : prop;
|
|
1569
|
-
if (typeof
|
|
1972
|
+
if (typeof value2 === "function") {
|
|
1570
1973
|
if (CALL_METHODS.has(prop)) {
|
|
1571
1974
|
return (...args) => {
|
|
1572
1975
|
const first = args[0];
|
|
1573
1976
|
if (first && typeof first === "object" && typeof first.model === "string") {
|
|
1574
1977
|
return traceCall(
|
|
1575
|
-
|
|
1978
|
+
value2,
|
|
1576
1979
|
t,
|
|
1577
1980
|
propPath,
|
|
1578
1981
|
args
|
|
1579
1982
|
);
|
|
1580
1983
|
}
|
|
1581
|
-
return
|
|
1984
|
+
return value2.apply(t, args);
|
|
1582
1985
|
};
|
|
1583
1986
|
}
|
|
1584
|
-
return
|
|
1987
|
+
return value2.bind(t);
|
|
1585
1988
|
}
|
|
1586
|
-
if (
|
|
1587
|
-
return deepProxy(
|
|
1989
|
+
if (value2 !== null && typeof value2 === "object" && !(value2 instanceof Promise)) {
|
|
1990
|
+
return deepProxy(value2, propPath);
|
|
1588
1991
|
}
|
|
1589
|
-
return
|
|
1992
|
+
return value2;
|
|
1590
1993
|
}
|
|
1591
1994
|
});
|
|
1592
1995
|
proxied.set(target, proxy);
|
|
@@ -1644,16 +2047,21 @@ ${frames.join("\n")}`;
|
|
|
1644
2047
|
captureMessage,
|
|
1645
2048
|
createBrowserRelayFetchHandler,
|
|
1646
2049
|
createBrowserRelayHandler,
|
|
2050
|
+
createRuntimeLogger,
|
|
1647
2051
|
emitLlmSelftestTrace,
|
|
2052
|
+
flushRuntimeLogs,
|
|
1648
2053
|
initAutterServer,
|
|
1649
2054
|
installAutterAutoFlush,
|
|
1650
2055
|
instrumentLlmClient,
|
|
1651
2056
|
makeSafeCapture,
|
|
1652
2057
|
redactAttributes,
|
|
1653
2058
|
reportOutcome,
|
|
2059
|
+
runtimeLogStats,
|
|
2060
|
+
runtimeLogger,
|
|
1654
2061
|
sanitizeBrowserPayload,
|
|
1655
2062
|
startCaughtExceptionSampler,
|
|
1656
2063
|
trackLlmCall,
|
|
1657
2064
|
withLlmCall,
|
|
1658
|
-
withProcessSpan
|
|
2065
|
+
withProcessSpan,
|
|
2066
|
+
withRuntimeOperation
|
|
1659
2067
|
});
|