memory-braid 0.2.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/src/logger.ts ADDED
@@ -0,0 +1,128 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import type { MemoryBraidConfig } from "./config.js";
3
+
4
+ type LoggerLike = {
5
+ debug?: (message: string) => void;
6
+ info: (message: string) => void;
7
+ warn: (message: string) => void;
8
+ error: (message: string) => void;
9
+ };
10
+
11
+ export type LogContext = {
12
+ runId?: string;
13
+ agentId?: string;
14
+ sessionKey?: string;
15
+ workspaceHash?: string;
16
+ durMs?: number;
17
+ [key: string]: unknown;
18
+ };
19
+
20
+ function truncateString(value: string, maxChars: number): string {
21
+ if (value.length <= maxChars) {
22
+ return value;
23
+ }
24
+ return `${value.slice(0, maxChars)}...`;
25
+ }
26
+
27
+ function sanitizeValue(value: unknown, maxChars: number, includePayloads: boolean): unknown {
28
+ if (value == null) {
29
+ return value;
30
+ }
31
+ if (typeof value === "string") {
32
+ return truncateString(value, maxChars);
33
+ }
34
+ if (typeof value === "number" || typeof value === "boolean") {
35
+ return value;
36
+ }
37
+ if (Array.isArray(value)) {
38
+ return value.slice(0, 20).map((entry) => sanitizeValue(entry, maxChars, includePayloads));
39
+ }
40
+ if (typeof value !== "object") {
41
+ return String(value);
42
+ }
43
+ const record = value as Record<string, unknown>;
44
+ const out: Record<string, unknown> = {};
45
+ for (const [key, entry] of Object.entries(record)) {
46
+ if (!includePayloads && /payload|content|snippet|text|prompt|body/i.test(key)) {
47
+ out[key] = "[omitted]";
48
+ continue;
49
+ }
50
+ out[key] = sanitizeValue(entry, maxChars, includePayloads);
51
+ }
52
+ return out;
53
+ }
54
+
55
+ export class MemoryBraidLogger {
56
+ private readonly base: LoggerLike;
57
+ private readonly cfg: MemoryBraidConfig["debug"];
58
+
59
+ constructor(base: LoggerLike, cfg: MemoryBraidConfig["debug"]) {
60
+ this.base = base;
61
+ this.cfg = cfg;
62
+ }
63
+
64
+ newRunId(): string {
65
+ return randomUUID();
66
+ }
67
+
68
+ info(event: string, context: LogContext = {}): void {
69
+ this.emit("info", event, context);
70
+ }
71
+
72
+ warn(event: string, context: LogContext = {}): void {
73
+ this.emit("warn", event, context);
74
+ }
75
+
76
+ error(event: string, context: LogContext = {}): void {
77
+ this.emit("error", event, context);
78
+ }
79
+
80
+ debug(event: string, context: LogContext = {}, always = false): void {
81
+ this.emit("debug", event, context, always);
82
+ }
83
+
84
+ private shouldSample(): boolean {
85
+ if (this.cfg.logSamplingRate >= 1) {
86
+ return true;
87
+ }
88
+ if (this.cfg.logSamplingRate <= 0) {
89
+ return false;
90
+ }
91
+ return Math.random() <= this.cfg.logSamplingRate;
92
+ }
93
+
94
+ private emit(level: "debug" | "info" | "warn" | "error", event: string, context: LogContext, always = false): void {
95
+ if (level === "debug" && !always && !this.cfg.enabled) {
96
+ return;
97
+ }
98
+ if (!always && !this.shouldSample()) {
99
+ return;
100
+ }
101
+
102
+ const payload = {
103
+ event,
104
+ ts: new Date().toISOString(),
105
+ ...sanitizeValue(context, this.cfg.maxSnippetChars, this.cfg.includePayloads),
106
+ };
107
+ const message = `memory-braid ${JSON.stringify(payload)}`;
108
+
109
+ if (level === "debug") {
110
+ if (this.base.debug) {
111
+ this.base.debug(message);
112
+ } else {
113
+ this.base.info(`[debug] ${message}`);
114
+ }
115
+ return;
116
+ }
117
+
118
+ if (level === "info") {
119
+ this.base.info(message);
120
+ return;
121
+ }
122
+ if (level === "warn") {
123
+ this.base.warn(message);
124
+ return;
125
+ }
126
+ this.base.error(message);
127
+ }
128
+ }