agent-telemetry 0.1.0 → 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/README.md +125 -14
- package/package.json +33 -5
- package/src/adapters/express.ts +176 -0
- package/src/adapters/fastify.ts +191 -0
- package/src/adapters/fetch.ts +133 -0
- package/src/adapters/hono.ts +7 -2
- package/src/adapters/inngest.ts +2 -3
- package/src/adapters/prisma.ts +112 -0
- package/src/adapters/supabase.ts +251 -0
- package/src/error.ts +16 -0
- package/src/index.ts +3 -0
- package/src/traceparent.ts +20 -19
- package/src/types.ts +21 -1
- package/src/writer.ts +134 -59
package/src/writer.ts
CHANGED
|
@@ -10,23 +10,32 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
export interface WriterConfig {
|
|
13
|
-
logDir: string
|
|
14
|
-
filename: string
|
|
15
|
-
maxSize: number
|
|
16
|
-
maxBackups: number
|
|
17
|
-
prefix: string
|
|
13
|
+
logDir: string;
|
|
14
|
+
filename: string;
|
|
15
|
+
maxSize: number;
|
|
16
|
+
maxBackups: number;
|
|
17
|
+
prefix: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export interface Writer {
|
|
21
|
-
write: (line: string) => void
|
|
21
|
+
write: (line: string) => void;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const DEFAULTS: WriterConfig = {
|
|
25
|
-
logDir:
|
|
26
|
-
filename:
|
|
25
|
+
logDir: "logs",
|
|
26
|
+
filename: "telemetry.jsonl",
|
|
27
27
|
maxSize: 5_000_000,
|
|
28
28
|
maxBackups: 3,
|
|
29
|
-
prefix:
|
|
29
|
+
prefix: "[TEL]",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function hasErrnoCode(err: unknown, code: string): boolean {
|
|
33
|
+
return (
|
|
34
|
+
typeof err === "object" &&
|
|
35
|
+
err !== null &&
|
|
36
|
+
"code" in err &&
|
|
37
|
+
(err as { code?: unknown }).code === code
|
|
38
|
+
);
|
|
30
39
|
}
|
|
31
40
|
|
|
32
41
|
/**
|
|
@@ -40,92 +49,158 @@ export async function createWriter(config?: Partial<WriterConfig>): Promise<Writ
|
|
|
40
49
|
maxSize: config?.maxSize ?? DEFAULTS.maxSize,
|
|
41
50
|
maxBackups: config?.maxBackups ?? DEFAULTS.maxBackups,
|
|
42
51
|
prefix: config?.prefix ?? DEFAULTS.prefix,
|
|
43
|
-
}
|
|
52
|
+
};
|
|
44
53
|
const writeToConsole = (line: string): void => {
|
|
45
54
|
// biome-ignore lint/suspicious/noConsole: intentional fallback for runtimes without filesystem
|
|
46
|
-
console.log(`${cfg.prefix} ${line}`)
|
|
47
|
-
}
|
|
55
|
+
console.log(`${cfg.prefix} ${line}`);
|
|
56
|
+
};
|
|
48
57
|
|
|
49
58
|
try {
|
|
50
|
-
const fs = await import(
|
|
51
|
-
const
|
|
59
|
+
const fs = await import("node:fs");
|
|
60
|
+
const fsPromises = await import("node:fs/promises");
|
|
61
|
+
const path = await import("node:path");
|
|
52
62
|
|
|
53
|
-
const logDir = path.resolve(cfg.logDir)
|
|
54
|
-
const logFile = path.join(logDir, cfg.filename)
|
|
63
|
+
const logDir = path.resolve(cfg.logDir);
|
|
64
|
+
const logFile = path.join(logDir, cfg.filename);
|
|
55
65
|
|
|
56
66
|
// Probe: verify filesystem actually works
|
|
57
67
|
// (Cloudflare's nodejs_compat stubs succeed silently)
|
|
58
|
-
|
|
68
|
+
await fsPromises.mkdir(logDir, { recursive: true });
|
|
59
69
|
if (!fs.existsSync(logDir)) {
|
|
60
|
-
throw new Error(
|
|
70
|
+
throw new Error("Filesystem probe failed");
|
|
61
71
|
}
|
|
62
72
|
|
|
63
|
-
let useConsoleFallback = false
|
|
73
|
+
let useConsoleFallback = false;
|
|
74
|
+
let flushScheduled = false;
|
|
75
|
+
let flushInProgress = false;
|
|
76
|
+
let sizeCache: number | undefined;
|
|
77
|
+
let pending: string[] = [];
|
|
78
|
+
|
|
79
|
+
const unlinkIfExists = async (filePath: string): Promise<void> => {
|
|
80
|
+
try {
|
|
81
|
+
await fsPromises.unlink(filePath);
|
|
82
|
+
} catch (err) {
|
|
83
|
+
if (!hasErrnoCode(err, "ENOENT")) throw err;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const fileExists = async (filePath: string): Promise<boolean> => {
|
|
88
|
+
try {
|
|
89
|
+
await fsPromises.access(filePath);
|
|
90
|
+
return true;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if (hasErrnoCode(err, "ENOENT")) return false;
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const getCurrentSize = async (): Promise<number> => {
|
|
98
|
+
if (sizeCache !== undefined) return sizeCache;
|
|
99
|
+
try {
|
|
100
|
+
sizeCache = (await fsPromises.stat(logFile)).size;
|
|
101
|
+
} catch (err) {
|
|
102
|
+
if (!hasErrnoCode(err, "ENOENT")) throw err;
|
|
103
|
+
sizeCache = 0;
|
|
104
|
+
}
|
|
105
|
+
return sizeCache;
|
|
106
|
+
};
|
|
64
107
|
|
|
65
|
-
const rotate = (): void => {
|
|
66
|
-
if (!
|
|
108
|
+
const rotate = async (): Promise<void> => {
|
|
109
|
+
if (!(await fileExists(logFile))) {
|
|
110
|
+
sizeCache = 0;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
67
113
|
|
|
68
114
|
if (cfg.maxBackups <= 0) {
|
|
69
|
-
|
|
70
|
-
|
|
115
|
+
await unlinkIfExists(logFile);
|
|
116
|
+
sizeCache = 0;
|
|
117
|
+
return;
|
|
71
118
|
}
|
|
72
119
|
|
|
73
|
-
const oldestBackup = `${logFile}.${cfg.maxBackups}
|
|
74
|
-
|
|
75
|
-
fs.unlinkSync(oldestBackup)
|
|
76
|
-
}
|
|
120
|
+
const oldestBackup = `${logFile}.${cfg.maxBackups}`;
|
|
121
|
+
await unlinkIfExists(oldestBackup);
|
|
77
122
|
|
|
78
123
|
for (let i = cfg.maxBackups - 1; i >= 1; i--) {
|
|
79
|
-
const from = `${logFile}.${i}
|
|
80
|
-
const to = `${logFile}.${i + 1}
|
|
81
|
-
if (
|
|
82
|
-
|
|
124
|
+
const from = `${logFile}.${i}`;
|
|
125
|
+
const to = `${logFile}.${i + 1}`;
|
|
126
|
+
if (await fileExists(from)) {
|
|
127
|
+
await fsPromises.rename(from, to);
|
|
83
128
|
}
|
|
84
129
|
}
|
|
85
130
|
|
|
86
|
-
|
|
87
|
-
|
|
131
|
+
await fsPromises.rename(logFile, `${logFile}.1`);
|
|
132
|
+
sizeCache = 0;
|
|
133
|
+
};
|
|
88
134
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
135
|
+
const scheduleFlush = (): void => {
|
|
136
|
+
if (flushScheduled || flushInProgress || useConsoleFallback) return;
|
|
137
|
+
flushScheduled = true;
|
|
138
|
+
queueMicrotask(() => {
|
|
139
|
+
flushScheduled = false;
|
|
140
|
+
void flushPending();
|
|
141
|
+
});
|
|
142
|
+
};
|
|
95
143
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
144
|
+
const flushPending = async (): Promise<void> => {
|
|
145
|
+
if (flushInProgress || useConsoleFallback) return;
|
|
146
|
+
flushInProgress = true;
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
while (pending.length > 0 && !useConsoleFallback) {
|
|
150
|
+
const batch = pending;
|
|
151
|
+
pending = [];
|
|
152
|
+
|
|
153
|
+
const chunk = batch.map((line) => `${line}\n`).join("");
|
|
154
|
+
const incomingSize = Buffer.byteLength(chunk);
|
|
100
155
|
|
|
101
156
|
try {
|
|
102
|
-
currentSize =
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
157
|
+
let currentSize = await getCurrentSize();
|
|
158
|
+
if (cfg.maxSize > 0 && currentSize + incomingSize > cfg.maxSize) {
|
|
159
|
+
await rotate();
|
|
160
|
+
currentSize = 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
await fsPromises.appendFile(logFile, chunk);
|
|
164
|
+
sizeCache = currentSize + incomingSize;
|
|
165
|
+
} catch {
|
|
166
|
+
useConsoleFallback = true;
|
|
167
|
+
for (const line of batch) {
|
|
168
|
+
writeToConsole(line);
|
|
111
169
|
}
|
|
170
|
+
for (const line of pending) {
|
|
171
|
+
writeToConsole(line);
|
|
172
|
+
}
|
|
173
|
+
pending = [];
|
|
174
|
+
sizeCache = undefined;
|
|
112
175
|
}
|
|
176
|
+
}
|
|
177
|
+
} finally {
|
|
178
|
+
flushInProgress = false;
|
|
179
|
+
if (pending.length > 0 && !useConsoleFallback) {
|
|
180
|
+
scheduleFlush();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
113
184
|
|
|
114
|
-
|
|
115
|
-
|
|
185
|
+
return {
|
|
186
|
+
write(line: string) {
|
|
187
|
+
try {
|
|
188
|
+
if (useConsoleFallback) {
|
|
189
|
+
writeToConsole(line);
|
|
190
|
+
return;
|
|
116
191
|
}
|
|
117
192
|
|
|
118
|
-
|
|
193
|
+
pending.push(line);
|
|
194
|
+
scheduleFlush();
|
|
119
195
|
} catch {
|
|
120
|
-
|
|
121
|
-
writeToConsole(line)
|
|
196
|
+
writeToConsole(line);
|
|
122
197
|
}
|
|
123
198
|
},
|
|
124
|
-
}
|
|
199
|
+
};
|
|
125
200
|
} catch {
|
|
126
201
|
// Import failed or filesystem probe failed — console fallback
|
|
127
202
|
return {
|
|
128
203
|
write: writeToConsole,
|
|
129
|
-
}
|
|
204
|
+
};
|
|
130
205
|
}
|
|
131
206
|
}
|