govyn 0.0.1 → 0.2.5
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/LICENSE +21 -0
- package/README.md +263 -1
- package/configs/multi-provider.yaml +68 -0
- package/configs/openai-only.yaml +45 -0
- package/configs/team-setup.yaml +88 -0
- package/dist/action-logger.d.ts +128 -0
- package/dist/action-logger.js +356 -0
- package/dist/action-logger.js.map +1 -0
- package/dist/admin-cli.d.ts +2 -0
- package/dist/admin-cli.js +36 -0
- package/dist/admin-cli.js.map +1 -0
- package/dist/agents.d.ts +23 -0
- package/dist/agents.js +59 -0
- package/dist/agents.js.map +1 -0
- package/dist/alert-api.d.ts +14 -0
- package/dist/alert-api.js +355 -0
- package/dist/alert-api.js.map +1 -0
- package/dist/alert-manager.d.ts +77 -0
- package/dist/alert-manager.js +267 -0
- package/dist/alert-manager.js.map +1 -0
- package/dist/approval-api.d.ts +19 -0
- package/dist/approval-api.js +82 -0
- package/dist/approval-api.js.map +1 -0
- package/dist/approval-timeout.d.ts +29 -0
- package/dist/approval-timeout.js +45 -0
- package/dist/approval-timeout.js.map +1 -0
- package/dist/approval.d.ts +78 -0
- package/dist/approval.js +101 -0
- package/dist/approval.js.map +1 -0
- package/dist/auth.d.ts +47 -0
- package/dist/auth.js +335 -0
- package/dist/auth.js.map +1 -0
- package/dist/budget-api.d.ts +20 -0
- package/dist/budget-api.js +85 -0
- package/dist/budget-api.js.map +1 -0
- package/dist/budget-enforcer.d.ts +102 -0
- package/dist/budget-enforcer.js +294 -0
- package/dist/budget-enforcer.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +200 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.js +267 -0
- package/dist/config.js.map +1 -0
- package/dist/cost-aggregator.d.ts +69 -0
- package/dist/cost-aggregator.js +305 -0
- package/dist/cost-aggregator.js.map +1 -0
- package/dist/cost-api.d.ts +29 -0
- package/dist/cost-api.js +128 -0
- package/dist/cost-api.js.map +1 -0
- package/dist/database-url.d.ts +6 -0
- package/dist/database-url.js +47 -0
- package/dist/database-url.js.map +1 -0
- package/dist/db-retention.d.ts +53 -0
- package/dist/db-retention.js +82 -0
- package/dist/db-retention.js.map +1 -0
- package/dist/db-schema.d.ts +17 -0
- package/dist/db-schema.js +167 -0
- package/dist/db-schema.js.map +1 -0
- package/dist/db-writer.d.ts +55 -0
- package/dist/db-writer.js +115 -0
- package/dist/db-writer.js.map +1 -0
- package/dist/db.d.ts +33 -0
- package/dist/db.js +78 -0
- package/dist/db.js.map +1 -0
- package/dist/events.d.ts +77 -0
- package/dist/events.js +12 -0
- package/dist/events.js.map +1 -0
- package/dist/health.d.ts +14 -0
- package/dist/health.js +49 -0
- package/dist/health.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/init-wizard.d.ts +12 -0
- package/dist/init-wizard.js +206 -0
- package/dist/init-wizard.js.map +1 -0
- package/dist/log-api.d.ts +20 -0
- package/dist/log-api.js +371 -0
- package/dist/log-api.js.map +1 -0
- package/dist/log-rotator.d.ts +55 -0
- package/dist/log-rotator.js +157 -0
- package/dist/log-rotator.js.map +1 -0
- package/dist/loop-detector.d.ts +71 -0
- package/dist/loop-detector.js +122 -0
- package/dist/loop-detector.js.map +1 -0
- package/dist/persistence-types.d.ts +165 -0
- package/dist/persistence-types.js +2 -0
- package/dist/persistence-types.js.map +1 -0
- package/dist/persistence.d.ts +185 -0
- package/dist/persistence.js +785 -0
- package/dist/persistence.js.map +1 -0
- package/dist/policy-api.d.ts +25 -0
- package/dist/policy-api.js +347 -0
- package/dist/policy-api.js.map +1 -0
- package/dist/policy-engine.d.ts +76 -0
- package/dist/policy-engine.js +835 -0
- package/dist/policy-engine.js.map +1 -0
- package/dist/policy-file.d.ts +10 -0
- package/dist/policy-file.js +52 -0
- package/dist/policy-file.js.map +1 -0
- package/dist/policy-parser.d.ts +21 -0
- package/dist/policy-parser.js +560 -0
- package/dist/policy-parser.js.map +1 -0
- package/dist/policy-types.d.ts +216 -0
- package/dist/policy-types.js +8 -0
- package/dist/policy-types.js.map +1 -0
- package/dist/policy-watcher.d.ts +54 -0
- package/dist/policy-watcher.js +116 -0
- package/dist/policy-watcher.js.map +1 -0
- package/dist/pricing.d.ts +69 -0
- package/dist/pricing.js +93 -0
- package/dist/pricing.js.map +1 -0
- package/dist/prompt.d.ts +6 -0
- package/dist/prompt.js +47 -0
- package/dist/prompt.js.map +1 -0
- package/dist/providers/anthropic.d.ts +18 -0
- package/dist/providers/anthropic.js +61 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/custom.d.ts +19 -0
- package/dist/providers/custom.js +54 -0
- package/dist/providers/custom.js.map +1 -0
- package/dist/providers/openai.d.ts +17 -0
- package/dist/providers/openai.js +48 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/proxy.d.ts +57 -0
- package/dist/proxy.js +477 -0
- package/dist/proxy.js.map +1 -0
- package/dist/router.d.ts +23 -0
- package/dist/router.js +89 -0
- package/dist/router.js.map +1 -0
- package/dist/runtime.d.ts +1 -0
- package/dist/runtime.js +139 -0
- package/dist/runtime.js.map +1 -0
- package/dist/security.d.ts +64 -0
- package/dist/security.js +422 -0
- package/dist/security.js.map +1 -0
- package/dist/server.d.ts +33 -0
- package/dist/server.js +1147 -0
- package/dist/server.js.map +1 -0
- package/dist/sqlite-schema.d.ts +6 -0
- package/dist/sqlite-schema.js +134 -0
- package/dist/sqlite-schema.js.map +1 -0
- package/dist/streaming.d.ts +24 -0
- package/dist/streaming.js +63 -0
- package/dist/streaming.js.map +1 -0
- package/dist/tokens.d.ts +45 -0
- package/dist/tokens.js +237 -0
- package/dist/tokens.js.map +1 -0
- package/dist/types.d.ts +344 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +66 -2
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log query API handler for the Govyn proxy server.
|
|
3
|
+
*
|
|
4
|
+
* Routes:
|
|
5
|
+
* GET /api/logs - List log entries with filtering and cursor-based pagination
|
|
6
|
+
* GET /api/logs/:id - Get a single log entry by ID
|
|
7
|
+
* GET /api/logs/:id/payload - Get stored payload content for a log entry
|
|
8
|
+
*
|
|
9
|
+
* Non-GET methods return 405. Logging disabled returns 503.
|
|
10
|
+
*/
|
|
11
|
+
import type * as http from 'node:http';
|
|
12
|
+
import type { ActionLogger } from './action-logger.js';
|
|
13
|
+
/**
|
|
14
|
+
* Handle log API requests.
|
|
15
|
+
*
|
|
16
|
+
* @param req - The incoming HTTP request
|
|
17
|
+
* @param res - The outgoing HTTP response
|
|
18
|
+
* @param actionLogger - The ActionLogger instance (provides logDirectory and getPayloadPath)
|
|
19
|
+
*/
|
|
20
|
+
export declare function handleLogApi(req: http.IncomingMessage, res: http.ServerResponse, actionLogger: ActionLogger): void;
|
package/dist/log-api.js
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log query API handler for the Govyn proxy server.
|
|
3
|
+
*
|
|
4
|
+
* Routes:
|
|
5
|
+
* GET /api/logs - List log entries with filtering and cursor-based pagination
|
|
6
|
+
* GET /api/logs/:id - Get a single log entry by ID
|
|
7
|
+
* GET /api/logs/:id/payload - Get stored payload content for a log entry
|
|
8
|
+
*
|
|
9
|
+
* Non-GET methods return 405. Logging disabled returns 503.
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
/**
|
|
14
|
+
* Send a JSON response.
|
|
15
|
+
*/
|
|
16
|
+
function sendJson(res, statusCode, body) {
|
|
17
|
+
const bodyStr = JSON.stringify(body);
|
|
18
|
+
res.writeHead(statusCode, {
|
|
19
|
+
'content-type': 'application/json',
|
|
20
|
+
'content-length': Buffer.byteLength(bodyStr).toString(),
|
|
21
|
+
});
|
|
22
|
+
res.end(bodyStr);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Decode a cursor string to a file+line position.
|
|
26
|
+
* Cursor format: base64-encoded "{file}:{line}"
|
|
27
|
+
*/
|
|
28
|
+
function decodeCursor(cursor) {
|
|
29
|
+
try {
|
|
30
|
+
const decoded = Buffer.from(cursor, 'base64').toString('utf8');
|
|
31
|
+
const colonIdx = decoded.lastIndexOf(':');
|
|
32
|
+
if (colonIdx === -1)
|
|
33
|
+
return null;
|
|
34
|
+
const file = decoded.slice(0, colonIdx);
|
|
35
|
+
const line = parseInt(decoded.slice(colonIdx + 1), 10);
|
|
36
|
+
if (isNaN(line))
|
|
37
|
+
return null;
|
|
38
|
+
return { file, line };
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Encode a file+line position to a cursor string.
|
|
46
|
+
*/
|
|
47
|
+
function encodeCursor(file, line) {
|
|
48
|
+
return Buffer.from(`${file}:${line}`).toString('base64');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Check whether a log entry matches the given filters.
|
|
52
|
+
*/
|
|
53
|
+
function matchesFilters(entry, filters) {
|
|
54
|
+
if (filters.agent && entry.agent_id !== filters.agent)
|
|
55
|
+
return false;
|
|
56
|
+
if (filters.status !== undefined && entry.status !== filters.status)
|
|
57
|
+
return false;
|
|
58
|
+
if (filters.start && entry.timestamp < filters.start)
|
|
59
|
+
return false;
|
|
60
|
+
if (filters.end && entry.timestamp > filters.end)
|
|
61
|
+
return false;
|
|
62
|
+
if (filters.model && entry.model !== filters.model)
|
|
63
|
+
return false;
|
|
64
|
+
if (filters.provider && entry.provider !== filters.provider)
|
|
65
|
+
return false;
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Read all JSONL files in the log directory, sorted newest-first by filename.
|
|
70
|
+
* Returns array of { filename, filepath } objects.
|
|
71
|
+
*/
|
|
72
|
+
function getLogFiles(logDirectory) {
|
|
73
|
+
const resolvedDir = path.resolve(logDirectory);
|
|
74
|
+
if (!fs.existsSync(resolvedDir))
|
|
75
|
+
return [];
|
|
76
|
+
const files = fs.readdirSync(resolvedDir)
|
|
77
|
+
.filter((f) => f.endsWith('.jsonl'))
|
|
78
|
+
.sort((a, b) => b.localeCompare(a)); // Newest first (descending)
|
|
79
|
+
return files.map((filename) => ({
|
|
80
|
+
filename,
|
|
81
|
+
filepath: path.join(resolvedDir, filename),
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Parse JSONL file into an array of LogEntry objects.
|
|
86
|
+
* Skips malformed lines silently.
|
|
87
|
+
*/
|
|
88
|
+
function parseJsonlFile(filepath) {
|
|
89
|
+
try {
|
|
90
|
+
const content = fs.readFileSync(filepath, 'utf8');
|
|
91
|
+
const lines = content.trim().split('\n').filter((l) => l.length > 0);
|
|
92
|
+
const entries = [];
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
try {
|
|
95
|
+
entries.push(JSON.parse(line));
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Skip malformed lines
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return entries;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Handle log API requests.
|
|
109
|
+
*
|
|
110
|
+
* @param req - The incoming HTTP request
|
|
111
|
+
* @param res - The outgoing HTTP response
|
|
112
|
+
* @param actionLogger - The ActionLogger instance (provides logDirectory and getPayloadPath)
|
|
113
|
+
*/
|
|
114
|
+
export function handleLogApi(req, res, actionLogger) {
|
|
115
|
+
const method = req.method ?? 'GET';
|
|
116
|
+
const rawUrl = req.url ?? '/api/logs';
|
|
117
|
+
// Handle DELETE for log purge
|
|
118
|
+
if (method === 'DELETE') {
|
|
119
|
+
handlePurge(req, res, actionLogger);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Only allow GET (and DELETE handled above)
|
|
123
|
+
if (method !== 'GET') {
|
|
124
|
+
sendJson(res, 405, {
|
|
125
|
+
error: {
|
|
126
|
+
type: 'method_not_allowed',
|
|
127
|
+
code: 'method_not_allowed',
|
|
128
|
+
message: 'Only GET and DELETE requests are supported for /api/logs',
|
|
129
|
+
details: { allowed_methods: ['GET', 'DELETE'] },
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Parse URL
|
|
135
|
+
const parsedUrl = new URL(rawUrl, 'http://localhost');
|
|
136
|
+
const pathname = parsedUrl.pathname;
|
|
137
|
+
// Route: GET /api/logs/:id/payload
|
|
138
|
+
const payloadMatch = pathname.match(/^\/api\/logs\/([^/]+)\/payload$/);
|
|
139
|
+
if (payloadMatch) {
|
|
140
|
+
handleGetPayload(res, actionLogger, payloadMatch[1]);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Route: GET /api/logs/:id
|
|
144
|
+
const idMatch = pathname.match(/^\/api\/logs\/([^/]+)$/);
|
|
145
|
+
if (idMatch && idMatch[1] !== undefined) {
|
|
146
|
+
// Avoid matching the bare /api/logs path (idMatch[1] would be empty string with some regex variants)
|
|
147
|
+
const id = idMatch[1];
|
|
148
|
+
if (id.length > 0) {
|
|
149
|
+
handleGetById(res, actionLogger, id);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Route: GET /api/logs (list with filters)
|
|
154
|
+
handleList(res, actionLogger, parsedUrl.searchParams);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* GET /api/logs - List log entries with filtering and cursor-based pagination.
|
|
158
|
+
*/
|
|
159
|
+
function handleList(res, actionLogger, params) {
|
|
160
|
+
const logDir = actionLogger.logDirectory;
|
|
161
|
+
// Parse filters
|
|
162
|
+
const filters = {};
|
|
163
|
+
const agentParam = params.get('agent');
|
|
164
|
+
if (agentParam)
|
|
165
|
+
filters.agent = agentParam;
|
|
166
|
+
const statusParam = params.get('status');
|
|
167
|
+
if (statusParam) {
|
|
168
|
+
const statusNum = parseInt(statusParam, 10);
|
|
169
|
+
if (!isNaN(statusNum))
|
|
170
|
+
filters.status = statusNum;
|
|
171
|
+
}
|
|
172
|
+
const startParam = params.get('start');
|
|
173
|
+
if (startParam)
|
|
174
|
+
filters.start = startParam;
|
|
175
|
+
const endParam = params.get('end');
|
|
176
|
+
if (endParam)
|
|
177
|
+
filters.end = endParam;
|
|
178
|
+
const modelParam = params.get('model');
|
|
179
|
+
if (modelParam)
|
|
180
|
+
filters.model = modelParam;
|
|
181
|
+
const providerParam = params.get('provider');
|
|
182
|
+
if (providerParam)
|
|
183
|
+
filters.provider = providerParam;
|
|
184
|
+
// Parse pagination
|
|
185
|
+
const limitParam = params.get('limit');
|
|
186
|
+
let limit = 50;
|
|
187
|
+
if (limitParam) {
|
|
188
|
+
const parsed = parseInt(limitParam, 10);
|
|
189
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
190
|
+
limit = Math.min(parsed, 200);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const cursorParam = params.get('cursor');
|
|
194
|
+
const cursorPos = cursorParam ? decodeCursor(cursorParam) : null;
|
|
195
|
+
// Get log files (newest first)
|
|
196
|
+
const logFiles = getLogFiles(logDir);
|
|
197
|
+
const results = [];
|
|
198
|
+
let skipMode = cursorPos !== null;
|
|
199
|
+
let nextCursorFile = null;
|
|
200
|
+
let nextCursorLine = null;
|
|
201
|
+
let done = false;
|
|
202
|
+
for (const { filename, filepath } of logFiles) {
|
|
203
|
+
if (done)
|
|
204
|
+
break;
|
|
205
|
+
const entries = parseJsonlFile(filepath);
|
|
206
|
+
for (let lineIdx = 0; lineIdx < entries.length; lineIdx++) {
|
|
207
|
+
if (done)
|
|
208
|
+
break;
|
|
209
|
+
// If we have a cursor, skip until we reach the cursor position
|
|
210
|
+
if (skipMode) {
|
|
211
|
+
if (filename === cursorPos.file && lineIdx === cursorPos.line) {
|
|
212
|
+
skipMode = false;
|
|
213
|
+
// Fall through to process this entry (cursor points to first entry of next page)
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const entry = entries[lineIdx];
|
|
220
|
+
if (!matchesFilters(entry, filters))
|
|
221
|
+
continue;
|
|
222
|
+
if (results.length < limit) {
|
|
223
|
+
results.push(entry);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
// We have limit + 1 match — there's a next page
|
|
227
|
+
nextCursorFile = filename;
|
|
228
|
+
nextCursorLine = lineIdx;
|
|
229
|
+
done = true;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const hasMore = nextCursorFile !== null;
|
|
234
|
+
const cursor = hasMore ? encodeCursor(nextCursorFile, nextCursorLine) : null;
|
|
235
|
+
sendJson(res, 200, {
|
|
236
|
+
entries: results,
|
|
237
|
+
cursor,
|
|
238
|
+
has_more: hasMore,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* GET /api/logs/:id - Get a single log entry by ID.
|
|
243
|
+
*/
|
|
244
|
+
function handleGetById(res, actionLogger, id) {
|
|
245
|
+
const logDir = actionLogger.logDirectory;
|
|
246
|
+
const logFiles = getLogFiles(logDir);
|
|
247
|
+
for (const { filepath } of logFiles) {
|
|
248
|
+
const entries = parseJsonlFile(filepath);
|
|
249
|
+
for (const entry of entries) {
|
|
250
|
+
if (entry.id === id) {
|
|
251
|
+
sendJson(res, 200, entry);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
sendJson(res, 404, {
|
|
257
|
+
error: {
|
|
258
|
+
type: 'not_found',
|
|
259
|
+
code: 'log_entry_not_found',
|
|
260
|
+
message: `Log entry not found: ${id}`,
|
|
261
|
+
details: { id },
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* DELETE /api/logs?before=DATE - Purge log entries and payloads older than the given date.
|
|
267
|
+
*/
|
|
268
|
+
function handlePurge(req, res, actionLogger) {
|
|
269
|
+
const rawUrl = req.url ?? '/api/logs';
|
|
270
|
+
const parsedUrl = new URL(rawUrl, 'http://localhost');
|
|
271
|
+
const beforeParam = parsedUrl.searchParams.get('before');
|
|
272
|
+
if (!beforeParam) {
|
|
273
|
+
sendJson(res, 400, {
|
|
274
|
+
error: {
|
|
275
|
+
type: 'invalid_request',
|
|
276
|
+
code: 'missing_parameter',
|
|
277
|
+
message: "Missing required 'before' query parameter",
|
|
278
|
+
details: { expected: 'ISO 8601 date string (e.g., 2024-01-15T00:00:00Z)' },
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const beforeDate = new Date(beforeParam);
|
|
284
|
+
if (isNaN(beforeDate.getTime())) {
|
|
285
|
+
sendJson(res, 400, {
|
|
286
|
+
error: {
|
|
287
|
+
type: 'invalid_request',
|
|
288
|
+
code: 'invalid_date',
|
|
289
|
+
message: `Invalid date format: '${beforeParam}'. Use ISO 8601 format.`,
|
|
290
|
+
details: { expected: 'ISO 8601 date string (e.g., 2024-01-15T00:00:00Z)' },
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const result = actionLogger.purgeBefore(beforeDate);
|
|
296
|
+
sendJson(res, 200, {
|
|
297
|
+
success: true,
|
|
298
|
+
deleted_logs: result.deletedLogs,
|
|
299
|
+
deleted_payloads: result.deletedPayloads,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* GET /api/logs/:id/payload - Get stored payload for a log entry.
|
|
304
|
+
*/
|
|
305
|
+
function handleGetPayload(res, actionLogger, id) {
|
|
306
|
+
const logDir = actionLogger.logDirectory;
|
|
307
|
+
const logFiles = getLogFiles(logDir);
|
|
308
|
+
// Find the log entry first
|
|
309
|
+
let entry = null;
|
|
310
|
+
for (const { filepath } of logFiles) {
|
|
311
|
+
const entries = parseJsonlFile(filepath);
|
|
312
|
+
for (const e of entries) {
|
|
313
|
+
if (e.id === id) {
|
|
314
|
+
entry = e;
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (entry)
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
if (!entry) {
|
|
322
|
+
sendJson(res, 404, {
|
|
323
|
+
error: {
|
|
324
|
+
type: 'not_found',
|
|
325
|
+
code: 'log_entry_not_found',
|
|
326
|
+
message: `Log entry not found: ${id}`,
|
|
327
|
+
details: { id },
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (!entry.has_payload || !entry.payload_id) {
|
|
333
|
+
sendJson(res, 404, {
|
|
334
|
+
error: {
|
|
335
|
+
type: 'not_found',
|
|
336
|
+
code: 'no_payload',
|
|
337
|
+
message: 'No payload stored for this log entry',
|
|
338
|
+
details: { id },
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const payloadPath = actionLogger.getPayloadPath(entry.payload_id);
|
|
344
|
+
if (!fs.existsSync(payloadPath)) {
|
|
345
|
+
sendJson(res, 404, {
|
|
346
|
+
error: {
|
|
347
|
+
type: 'not_found',
|
|
348
|
+
code: 'payload_expired',
|
|
349
|
+
message: 'Payload file not found (may have been cleaned up by retention)',
|
|
350
|
+
details: { id, payload_id: entry.payload_id },
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
const content = fs.readFileSync(payloadPath, 'utf8');
|
|
357
|
+
const payload = JSON.parse(content);
|
|
358
|
+
sendJson(res, 200, payload);
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
sendJson(res, 500, {
|
|
362
|
+
error: {
|
|
363
|
+
type: 'internal_error',
|
|
364
|
+
code: 'payload_read_error',
|
|
365
|
+
message: 'Failed to read payload file',
|
|
366
|
+
details: { id, payload_id: entry.payload_id },
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
//# sourceMappingURL=log-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-api.js","sourceRoot":"","sources":["../src/log-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log file rotation and retention for the Govyn proxy server.
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - Size-based rotation: rotates when a log file exceeds configured max size
|
|
6
|
+
* - Time-based rotation: rotates when a log file is older than the configured interval
|
|
7
|
+
* - Gzip compression: rotated files are compressed with zlib.gzipSync()
|
|
8
|
+
* - Retention cleanup: auto-deletes expired log files and payload files
|
|
9
|
+
*
|
|
10
|
+
* Design:
|
|
11
|
+
* - All rotation I/O is synchronous (runs inside flush() which is already on a timer)
|
|
12
|
+
* - Cleanup runs on a 1-hour unref'd interval (does not prevent process exit)
|
|
13
|
+
* - Cleanup failures never crash the process (wrapped in try/catch)
|
|
14
|
+
*/
|
|
15
|
+
import type { LoggingConfig } from './types.js';
|
|
16
|
+
/**
|
|
17
|
+
* LogRotator manages log file rotation with gzip compression and
|
|
18
|
+
* configurable retention cleanup for both log files and payload files.
|
|
19
|
+
*/
|
|
20
|
+
export declare class LogRotator {
|
|
21
|
+
private readonly config;
|
|
22
|
+
private cleanupInterval;
|
|
23
|
+
constructor(config: LoggingConfig);
|
|
24
|
+
/**
|
|
25
|
+
* Check whether the given log file should be rotated.
|
|
26
|
+
*
|
|
27
|
+
* @param currentFilePath - Path to the active JSONL log file
|
|
28
|
+
* @returns Whether rotation is needed and which trigger fired
|
|
29
|
+
*/
|
|
30
|
+
checkRotation(currentFilePath: string): {
|
|
31
|
+
shouldRotate: boolean;
|
|
32
|
+
reason: 'size' | 'time' | null;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Rotate the given log file: compress with gzip, write to rotated path,
|
|
36
|
+
* and delete the original.
|
|
37
|
+
*
|
|
38
|
+
* @param currentFilePath - Path to the active JSONL log file to rotate
|
|
39
|
+
* @returns Path to a fresh log file (ActionLogger should create on next write)
|
|
40
|
+
*/
|
|
41
|
+
rotate(currentFilePath: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Scan for and delete expired log files and payload files.
|
|
44
|
+
*
|
|
45
|
+
* - Log files (.jsonl.gz): deleted if older than retentionDays
|
|
46
|
+
* - Payload files (.json in payloads/): deleted if older than payloadRetentionDays
|
|
47
|
+
*
|
|
48
|
+
* Failures are logged to stderr but never crash the process.
|
|
49
|
+
*/
|
|
50
|
+
cleanupExpired(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Stop the periodic cleanup interval.
|
|
53
|
+
*/
|
|
54
|
+
stop(): void;
|
|
55
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log file rotation and retention for the Govyn proxy server.
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - Size-based rotation: rotates when a log file exceeds configured max size
|
|
6
|
+
* - Time-based rotation: rotates when a log file is older than the configured interval
|
|
7
|
+
* - Gzip compression: rotated files are compressed with zlib.gzipSync()
|
|
8
|
+
* - Retention cleanup: auto-deletes expired log files and payload files
|
|
9
|
+
*
|
|
10
|
+
* Design:
|
|
11
|
+
* - All rotation I/O is synchronous (runs inside flush() which is already on a timer)
|
|
12
|
+
* - Cleanup runs on a 1-hour unref'd interval (does not prevent process exit)
|
|
13
|
+
* - Cleanup failures never crash the process (wrapped in try/catch)
|
|
14
|
+
*/
|
|
15
|
+
import * as fs from 'node:fs';
|
|
16
|
+
import * as path from 'node:path';
|
|
17
|
+
import * as zlib from 'node:zlib';
|
|
18
|
+
/**
|
|
19
|
+
* LogRotator manages log file rotation with gzip compression and
|
|
20
|
+
* configurable retention cleanup for both log files and payload files.
|
|
21
|
+
*/
|
|
22
|
+
export class LogRotator {
|
|
23
|
+
config;
|
|
24
|
+
cleanupInterval = null;
|
|
25
|
+
constructor(config) {
|
|
26
|
+
this.config = config;
|
|
27
|
+
// Run cleanup every hour; unref'd so it doesn't keep the process alive
|
|
28
|
+
this.cleanupInterval = setInterval(() => this.cleanupExpired(), 3600 * 1000);
|
|
29
|
+
this.cleanupInterval.unref();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Check whether the given log file should be rotated.
|
|
33
|
+
*
|
|
34
|
+
* @param currentFilePath - Path to the active JSONL log file
|
|
35
|
+
* @returns Whether rotation is needed and which trigger fired
|
|
36
|
+
*/
|
|
37
|
+
checkRotation(currentFilePath) {
|
|
38
|
+
try {
|
|
39
|
+
const stat = fs.statSync(currentFilePath);
|
|
40
|
+
// Size check: file exceeds rotationMaxSizeMb
|
|
41
|
+
const maxBytes = this.config.rotationMaxSizeMb * 1024 * 1024;
|
|
42
|
+
if (stat.size > maxBytes) {
|
|
43
|
+
return { shouldRotate: true, reason: 'size' };
|
|
44
|
+
}
|
|
45
|
+
// Time check: file is older than rotationIntervalHours
|
|
46
|
+
const maxAge = this.config.rotationIntervalHours * 3600 * 1000;
|
|
47
|
+
const fileAge = Date.now() - stat.mtimeMs;
|
|
48
|
+
if (fileAge > maxAge) {
|
|
49
|
+
return { shouldRotate: true, reason: 'time' };
|
|
50
|
+
}
|
|
51
|
+
return { shouldRotate: false, reason: null };
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// File doesn't exist or can't be read — no rotation needed
|
|
55
|
+
return { shouldRotate: false, reason: null };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Rotate the given log file: compress with gzip, write to rotated path,
|
|
60
|
+
* and delete the original.
|
|
61
|
+
*
|
|
62
|
+
* @param currentFilePath - Path to the active JSONL log file to rotate
|
|
63
|
+
* @returns Path to a fresh log file (ActionLogger should create on next write)
|
|
64
|
+
*/
|
|
65
|
+
rotate(currentFilePath) {
|
|
66
|
+
const dir = path.dirname(currentFilePath);
|
|
67
|
+
const now = new Date();
|
|
68
|
+
const dateStr = now.toISOString().slice(0, 10);
|
|
69
|
+
const timeStr = now.toISOString().slice(11, 19).replace(/:/g, '');
|
|
70
|
+
// Rotated filename: govyn-YYYY-MM-DD-HHmmss.jsonl.gz
|
|
71
|
+
const rotatedName = `govyn-${dateStr}-${timeStr}.jsonl.gz`;
|
|
72
|
+
const rotatedPath = path.join(dir, rotatedName);
|
|
73
|
+
// Read, compress, write
|
|
74
|
+
const content = fs.readFileSync(currentFilePath);
|
|
75
|
+
const compressed = zlib.gzipSync(content);
|
|
76
|
+
fs.writeFileSync(rotatedPath, compressed);
|
|
77
|
+
// Delete original
|
|
78
|
+
fs.unlinkSync(currentFilePath);
|
|
79
|
+
// Return the path for the new current file (same date-based naming)
|
|
80
|
+
const freshDateStr = new Date().toISOString().slice(0, 10);
|
|
81
|
+
return path.join(dir, `govyn-${freshDateStr}.jsonl`);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Scan for and delete expired log files and payload files.
|
|
85
|
+
*
|
|
86
|
+
* - Log files (.jsonl.gz): deleted if older than retentionDays
|
|
87
|
+
* - Payload files (.json in payloads/): deleted if older than payloadRetentionDays
|
|
88
|
+
*
|
|
89
|
+
* Failures are logged to stderr but never crash the process.
|
|
90
|
+
*/
|
|
91
|
+
cleanupExpired() {
|
|
92
|
+
try {
|
|
93
|
+
const logDir = path.resolve(this.config.directory);
|
|
94
|
+
let logsCleaned = 0;
|
|
95
|
+
let payloadsCleaned = 0;
|
|
96
|
+
// Clean up rotated log files (.jsonl.gz)
|
|
97
|
+
const maxLogAge = this.config.retentionDays * 24 * 3600 * 1000;
|
|
98
|
+
const now = Date.now();
|
|
99
|
+
if (fs.existsSync(logDir)) {
|
|
100
|
+
const files = fs.readdirSync(logDir);
|
|
101
|
+
for (const file of files) {
|
|
102
|
+
if (!file.endsWith('.jsonl.gz'))
|
|
103
|
+
continue;
|
|
104
|
+
const filePath = path.join(logDir, file);
|
|
105
|
+
try {
|
|
106
|
+
const stat = fs.statSync(filePath);
|
|
107
|
+
if (now - stat.mtimeMs > maxLogAge) {
|
|
108
|
+
fs.unlinkSync(filePath);
|
|
109
|
+
logsCleaned++;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Skip individual file errors
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Clean up payload files (.json in payloads/)
|
|
118
|
+
const payloadsDir = path.join(logDir, 'payloads');
|
|
119
|
+
const maxPayloadAge = this.config.payloadRetentionDays * 24 * 3600 * 1000;
|
|
120
|
+
if (fs.existsSync(payloadsDir)) {
|
|
121
|
+
const payloadFiles = fs.readdirSync(payloadsDir);
|
|
122
|
+
for (const file of payloadFiles) {
|
|
123
|
+
if (!file.endsWith('.json'))
|
|
124
|
+
continue;
|
|
125
|
+
const filePath = path.join(payloadsDir, file);
|
|
126
|
+
try {
|
|
127
|
+
const stat = fs.statSync(filePath);
|
|
128
|
+
if (now - stat.mtimeMs > maxPayloadAge) {
|
|
129
|
+
fs.unlinkSync(filePath);
|
|
130
|
+
payloadsCleaned++;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Skip individual file errors
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (logsCleaned > 0 || payloadsCleaned > 0) {
|
|
139
|
+
process.stderr.write(`[govyn] Cleaned up ${logsCleaned} expired log files, ${payloadsCleaned} expired payload files\n`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
144
|
+
process.stderr.write(`[govyn] Cleanup error: ${message}\n`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Stop the periodic cleanup interval.
|
|
149
|
+
*/
|
|
150
|
+
stop() {
|
|
151
|
+
if (this.cleanupInterval !== null) {
|
|
152
|
+
clearInterval(this.cleanupInterval);
|
|
153
|
+
this.cleanupInterval = null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=log-rotator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-rotator.js","sourceRoot":"","sources":["../src/log-rotator.ts"],"names":[],"mappings":"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"}
|