@xopcai/xopc 0.0.92 → 0.0.94
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/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/agents-OqhbJkMf.js +222 -0
- package/dist/gateway/static/root/assets/apps-page-OHXW9XP8.js +1 -0
- package/dist/gateway/static/root/assets/channels-settings-4N2R-jof.js +1 -0
- package/dist/gateway/static/root/assets/{channels-status-swr-XzddfJW2.js → channels-status-swr-Bv6f9kDq.js} +1 -1
- package/dist/gateway/static/root/assets/{cron-api--I8LJ44S.js → cron-api-BtaQaHJq.js} +1 -1
- package/dist/gateway/static/root/assets/cron-page-Dah32HJK.js +1 -0
- package/dist/gateway/static/root/assets/{dist-CYgHMQO0.js → dist-BJfD9Qvs.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-6cRP0nA9.js → extension-debug-page-DnYuMzmH.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-DpwIkspI.js → extension-page-CJfc-6XV.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-DYbnQUxH.js → extension-settings-page-BxdfYQMG.js} +1 -1
- package/dist/gateway/static/root/assets/{fetch-DTN0w7rV.js → fetch-B0aeeY0q.js} +1 -1
- package/dist/gateway/static/root/assets/{field-primitives-CslW6HwD.js → field-primitives-DOLHwowi.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-config-api-2UiKevxG.js → heartbeat-config-api-Bj2INAf5.js} +1 -1
- package/dist/gateway/static/root/assets/index-Bj_l8QDp.css +1 -0
- package/dist/gateway/static/root/assets/{index-DnevRVa6.js → index-DuQ1XPoA.js} +99 -98
- package/dist/gateway/static/root/assets/logs-page-AsOgLNJE.js +2 -0
- package/dist/gateway/static/root/assets/{note-detail-page-DvW2qg4i.js → note-detail-page-24J4mVP-.js} +53 -53
- package/dist/gateway/static/root/assets/{note-time-BEiibLJv.js → note-time-JBszYV3s.js} +1 -1
- package/dist/gateway/static/root/assets/notes-page-BApAirFB.js +1 -0
- package/dist/gateway/static/root/assets/sessions-page-DX9huWsA.js +1 -0
- package/dist/gateway/static/root/assets/{settings-advanced-gate-BctKqHcf.js → settings-advanced-gate-DWvhsTuz.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-QJh5ruel.js → settings-form-section-CxMjaMiy.js} +1 -1
- package/dist/gateway/static/root/assets/settings-page-4VmUTzQs.js +3 -0
- package/dist/gateway/static/root/assets/{share-preview-page-DBsvvbmD.js → share-preview-page-IX0TJvRd.js} +1 -1
- package/dist/gateway/static/root/assets/skills-page-CGKGKfwe.js +2 -0
- package/dist/gateway/static/root/assets/{theme-store-ht5iswWS.js → theme-store-Cg_SuBw0.js} +1 -1
- package/dist/gateway/static/root/assets/url-BHHmdJYc.js +3 -0
- package/dist/gateway/static/root/assets/{utils-DhPv9xoB.js → utils-BmlcxR2j.js} +1 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-DaGm2N4J.js +1 -0
- package/dist/gateway/static/root/assets/{workflow-page.utils-CJqnPWkW.js → workflow-page.utils-D0vsIGHD.js} +1 -1
- package/dist/gateway/static/root/assets/workflows-page-BFCrD3nw.js +27 -0
- package/dist/gateway/static/root/index.html +5 -5
- package/dist/package.js +1 -1
- package/dist/src/agent/inbound/turn-dispatcher.d.ts +1 -0
- package/dist/src/agent/inbound/turn-dispatcher.js +3 -0
- package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
- package/dist/src/agent/lifecycle/handlers/compaction.js +1 -1
- package/dist/src/agent/lifecycle/handlers/compaction.js.map +1 -1
- package/dist/src/agent/mcp/bundle-mcp-materialize.js +1 -1
- package/dist/src/agent/mcp/bundle-mcp-materialize.js.map +1 -1
- package/dist/src/agent/mcp/bundle-mcp-runtime.js +17 -4
- package/dist/src/agent/mcp/bundle-mcp-runtime.js.map +1 -1
- package/dist/src/agent/mcp/mcp-transport-config.js +10 -3
- package/dist/src/agent/mcp/mcp-transport-config.js.map +1 -1
- package/dist/src/agent/mcp/mcp-transport.js +1 -1
- package/dist/src/agent/mcp/mcp-transport.js.map +1 -1
- package/dist/src/agent/service/process-direct-streaming.d.ts +1 -0
- package/dist/src/agent/service/process-direct-streaming.js +15 -12
- package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
- package/dist/src/agent/service.d.ts +4 -2
- package/dist/src/agent/service.js +20 -4
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/service.types.d.ts +3 -1
- package/dist/src/agent/tools/browser/tool/browser-use-tool.js +1 -1
- package/dist/src/agent/tools/browser/tool/browser-use-tool.js.map +1 -1
- package/dist/src/agent/tools/search/registry.js +1 -1
- package/dist/src/agent/tools/search/registry.js.map +1 -1
- package/dist/src/agent/tools/session-search-tool.js +1 -1
- package/dist/src/agent/tools/session-search-tool.js.map +1 -1
- package/dist/src/agent/tools/workflow-tool.js +1 -1
- package/dist/src/agent/tools/workflow-tool.js.map +1 -1
- package/dist/src/agent/workflow/progress-broker.js +1 -1
- package/dist/src/agent/workflow/progress-broker.js.map +1 -1
- package/dist/src/agent/workflow/subagent-runner.js +1 -1
- package/dist/src/agent/workflow/subagent-runner.js.map +1 -1
- package/dist/src/channels/pipeline.js +3 -2
- package/dist/src/channels/pipeline.js.map +1 -1
- package/dist/src/cli/cli-log-level-preset.d.ts +1 -1
- package/dist/src/cli/cli-log-level-preset.js +2 -2
- package/dist/src/cli/cli-log-level-preset.js.map +1 -1
- package/dist/src/cli/commands/logs.js +3 -3
- package/dist/src/cli/commands/logs.js.map +1 -1
- package/dist/src/cron/executor.js +7 -4
- package/dist/src/cron/executor.js.map +1 -1
- package/dist/src/gateway/hono/app.js +4 -1
- package/dist/src/gateway/hono/app.js.map +1 -1
- package/dist/src/gateway/hono/lib/route-logger.d.ts +6 -0
- package/dist/src/gateway/hono/lib/route-logger.js +31 -0
- package/dist/src/gateway/hono/lib/route-logger.js.map +1 -0
- package/dist/src/gateway/hono/middleware/auth.js +16 -3
- package/dist/src/gateway/hono/middleware/auth.js.map +1 -1
- package/dist/src/gateway/hono/middleware/logger.js +1 -1
- package/dist/src/gateway/hono/middleware/logger.js.map +1 -1
- package/dist/src/gateway/hono/middleware/route-errors.d.ts +5 -0
- package/dist/src/gateway/hono/middleware/route-errors.js +27 -0
- package/dist/src/gateway/hono/middleware/route-errors.js.map +1 -0
- package/dist/src/gateway/hono/routes/agent-stream.js +6 -0
- package/dist/src/gateway/hono/routes/agent-stream.js.map +1 -1
- package/dist/src/gateway/hono/routes/browser-install.js +2 -4
- package/dist/src/gateway/hono/routes/browser-install.js.map +1 -1
- package/dist/src/gateway/hono/routes/config.js +25 -11
- package/dist/src/gateway/hono/routes/config.js.map +1 -1
- package/dist/src/gateway/hono/routes/cron.js +5 -0
- package/dist/src/gateway/hono/routes/cron.js.map +1 -1
- package/dist/src/gateway/hono/routes/host-fs.js +2 -4
- package/dist/src/gateway/hono/routes/host-fs.js.map +1 -1
- package/dist/src/gateway/hono/routes/lazy-bundles.js +14 -1
- package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
- package/dist/src/gateway/hono/routes/lazy-fallback.js +3 -0
- package/dist/src/gateway/hono/routes/lazy-fallback.js.map +1 -1
- package/dist/src/gateway/hono/routes/logs.js +39 -7
- package/dist/src/gateway/hono/routes/logs.js.map +1 -1
- package/dist/src/gateway/hono/routes/mcp.d.ts +3 -0
- package/dist/src/gateway/hono/routes/mcp.js +107 -0
- package/dist/src/gateway/hono/routes/mcp.js.map +1 -0
- package/dist/src/gateway/hono/routes/notes.js +105 -1
- package/dist/src/gateway/hono/routes/notes.js.map +1 -1
- package/dist/src/gateway/hono/routes/sessions.js +6 -0
- package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
- package/dist/src/gateway/hono/routes/update.js +2 -4
- package/dist/src/gateway/hono/routes/update.js.map +1 -1
- package/dist/src/gateway/hono/routes/voice.js +2 -4
- package/dist/src/gateway/hono/routes/voice.js.map +1 -1
- package/dist/src/gateway/hono/routes/workspace.js +2 -4
- package/dist/src/gateway/hono/routes/workspace.js.map +1 -1
- package/dist/src/gateway/hono/sse.js +9 -2
- package/dist/src/gateway/hono/sse.js.map +1 -1
- package/dist/src/gateway/host.d.ts +2 -0
- package/dist/src/gateway/host.js +6 -3
- package/dist/src/gateway/host.js.map +1 -1
- package/dist/src/gateway/service/agent-runner.js +1 -1
- package/dist/src/gateway/service/agent-runner.js.map +1 -1
- package/dist/src/gateway/service/config-coordinator.js +14 -6
- package/dist/src/gateway/service/config-coordinator.js.map +1 -1
- package/dist/src/gateway/service/marketplace-service.js +1 -1
- package/dist/src/gateway/service/marketplace-service.js.map +1 -1
- package/dist/src/gateway/service/run-gateway-agent.js +22 -5
- package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
- package/dist/src/gateway/service/sse-hub.js +1 -1
- package/dist/src/gateway/service/sse-hub.js.map +1 -1
- package/dist/src/gateway/service.js +12 -5
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/mcp/channel-bridge.js +26 -2
- package/dist/src/mcp/channel-bridge.js.map +1 -1
- package/dist/src/mcp/gateway-http-client.js +24 -2
- package/dist/src/mcp/gateway-http-client.js.map +1 -1
- package/dist/src/notes/service.d.ts +13 -1
- package/dist/src/notes/service.js +237 -0
- package/dist/src/notes/service.js.map +1 -1
- package/dist/src/notes/store.d.ts +3 -0
- package/dist/src/notes/store.js +6 -2
- package/dist/src/notes/store.js.map +1 -1
- package/dist/src/notes/types.d.ts +31 -0
- package/dist/src/session/config-store.js +10 -4
- package/dist/src/session/config-store.js.map +1 -1
- package/dist/src/session/index.d.ts +1 -1
- package/dist/src/session/index.js +2 -2
- package/dist/src/session/manager.js +8 -1
- package/dist/src/session/manager.js.map +1 -1
- package/dist/src/session/session-title.d.ts +19 -3
- package/dist/src/session/session-title.js +82 -7
- package/dist/src/session/session-title.js.map +1 -1
- package/dist/src/utils/index.js +4 -4
- package/dist/src/utils/logger/config.js +2 -6
- package/dist/src/utils/logger/config.js.map +1 -1
- package/dist/src/utils/logger/context.d.ts +3 -22
- package/dist/src/utils/logger/context.js +4 -32
- package/dist/src/utils/logger/context.js.map +1 -1
- package/dist/src/utils/logger/index.d.ts +4 -7
- package/dist/src/utils/logger/index.js +9 -28
- package/dist/src/utils/logger/index.js.map +1 -1
- package/dist/src/utils/logger/log-store.d.ts +14 -32
- package/dist/src/utils/logger/log-store.js +67 -118
- package/dist/src/utils/logger/log-store.js.map +1 -1
- package/dist/src/utils/logger/log-stream.d.ts +5 -70
- package/dist/src/utils/logger/log-stream.js +67 -178
- package/dist/src/utils/logger/log-stream.js.map +1 -1
- package/dist/src/utils/logger/pino-record.d.ts +8 -0
- package/dist/src/utils/logger/pino-record.js +83 -0
- package/dist/src/utils/logger/pino-record.js.map +1 -0
- package/dist/src/utils/logger/stats.d.ts +1 -1
- package/dist/src/utils/logger/stats.js +2 -2
- package/dist/src/utils/logger/stats.js.map +1 -1
- package/dist/src/utils/logger/streams.js +18 -0
- package/dist/src/utils/logger/streams.js.map +1 -1
- package/dist/src/utils/logger/types.d.ts +0 -9
- package/dist/src/utils/logger/types.js.map +1 -1
- package/dist/src/utils/logger.js +4 -4
- package/package.json +6 -1
- package/dist/gateway/static/root/assets/agents-uwPn7ZW9.js +0 -222
- package/dist/gateway/static/root/assets/apps-page-CWKdhSPU.js +0 -1
- package/dist/gateway/static/root/assets/channels-settings-hEhW7Mbk.js +0 -1
- package/dist/gateway/static/root/assets/cron-page-B0kvgZGR.js +0 -1
- package/dist/gateway/static/root/assets/index-BUKUv7QW.css +0 -1
- package/dist/gateway/static/root/assets/logs-page-sOP4TXJ4.js +0 -1
- package/dist/gateway/static/root/assets/notes-page-BFQaquHU.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-CptjDKAX.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-V3p-hISB.js +0 -2
- package/dist/gateway/static/root/assets/skills-page-q2zPUJAR.js +0 -2
- package/dist/gateway/static/root/assets/url-CWWpfkq1.js +0 -3
- package/dist/gateway/static/root/assets/voice-api-key-field-DLSKUipa.js +0 -1
- package/dist/gateway/static/root/assets/workflows-page-DRRQ1A0l.js +0 -27
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { init_pino_record, logEntrySearchText, pinoRecordToLogEntry } from "./pino-record.js";
|
|
1
2
|
import { basename, join } from "path";
|
|
2
|
-
import { createReadStream, existsSync, mkdirSync, readdirSync, statSync
|
|
3
|
+
import { createReadStream, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
4
|
+
import { Readable } from "stream";
|
|
3
5
|
import { readFile } from "fs/promises";
|
|
4
6
|
import { gunzip } from "zlib";
|
|
5
7
|
import { promisify } from "util";
|
|
6
8
|
import { createInterface } from "readline";
|
|
7
|
-
import { Readable } from "stream";
|
|
8
9
|
//#region src/utils/logger/log-store.ts
|
|
9
10
|
/**
|
|
10
11
|
* Log Store - Enhanced File-based Log Storage
|
|
@@ -16,6 +17,7 @@ import { Readable } from "stream";
|
|
|
16
17
|
* - Statistics and analytics
|
|
17
18
|
* - Safe log cleanup with actual deletion
|
|
18
19
|
*/
|
|
20
|
+
init_pino_record();
|
|
19
21
|
const gunzipAsync = promisify(gunzip);
|
|
20
22
|
const LOG_DIR = process.env.XOPC_LOG_DIR || join(process.env.HOME || ".", ".xopc", "logs");
|
|
21
23
|
function ensureLogDir() {
|
|
@@ -69,45 +71,12 @@ function parseLogLine(line, source, lineNumber) {
|
|
|
69
71
|
const trimmed = line.trim();
|
|
70
72
|
if (!trimmed) return null;
|
|
71
73
|
try {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
level: {
|
|
77
|
-
10: "trace",
|
|
78
|
-
20: "debug",
|
|
79
|
-
30: "info",
|
|
80
|
-
40: "warn",
|
|
81
|
-
50: "error",
|
|
82
|
-
60: "fatal"
|
|
83
|
-
}[levelNum] || (parsed.level?.toString() || "info").toLowerCase(),
|
|
84
|
-
message: parsed.msg || parsed.message || "",
|
|
85
|
-
module: parsed.module,
|
|
86
|
-
prefix: parsed.prefix,
|
|
87
|
-
service: parsed.service,
|
|
88
|
-
extension: parsed.extension,
|
|
89
|
-
requestId: parsed.requestId,
|
|
90
|
-
sessionId: parsed.sessionId,
|
|
91
|
-
_source: source,
|
|
92
|
-
_lineNumber: lineNumber,
|
|
93
|
-
...parsed
|
|
94
|
-
};
|
|
74
|
+
const entry = pinoRecordToLogEntry(JSON.parse(trimmed));
|
|
75
|
+
entry._source = source;
|
|
76
|
+
entry._lineNumber = lineNumber;
|
|
77
|
+
return entry;
|
|
95
78
|
} catch {
|
|
96
|
-
|
|
97
|
-
if (match) return {
|
|
98
|
-
timestamp: match[1],
|
|
99
|
-
level: match[2].toLowerCase(),
|
|
100
|
-
message: match[3],
|
|
101
|
-
_source: source,
|
|
102
|
-
_lineNumber: lineNumber
|
|
103
|
-
};
|
|
104
|
-
return {
|
|
105
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
106
|
-
level: "info",
|
|
107
|
-
message: trimmed,
|
|
108
|
-
_source: source,
|
|
109
|
-
_lineNumber: lineNumber
|
|
110
|
-
};
|
|
79
|
+
return null;
|
|
111
80
|
}
|
|
112
81
|
}
|
|
113
82
|
/**
|
|
@@ -164,15 +133,7 @@ function matchesQuery(entry, query) {
|
|
|
164
133
|
}
|
|
165
134
|
if (query.q) {
|
|
166
135
|
const keyword = query.q.toLowerCase();
|
|
167
|
-
if (!
|
|
168
|
-
entry.message,
|
|
169
|
-
entry.module,
|
|
170
|
-
entry.prefix,
|
|
171
|
-
entry.service,
|
|
172
|
-
entry.extension,
|
|
173
|
-
entry.requestId,
|
|
174
|
-
entry.sessionId
|
|
175
|
-
].filter(Boolean).map(String).join(" ").toLowerCase().includes(keyword)) return false;
|
|
136
|
+
if (!logEntrySearchText(entry).includes(keyword)) return false;
|
|
176
137
|
}
|
|
177
138
|
if (query.module && entry.module !== query.module) return false;
|
|
178
139
|
if (query.extension && entry.extension !== query.extension) return false;
|
|
@@ -207,62 +168,16 @@ async function queryLogs(query = {}) {
|
|
|
207
168
|
const limit = query.limit || 100;
|
|
208
169
|
return results.slice(offset, offset + limit).map(({ _source, _lineNumber, ...rest }) => rest);
|
|
209
170
|
}
|
|
210
|
-
/**
|
|
211
|
-
* Get recent logs (convenience method)
|
|
212
|
-
*/
|
|
213
|
-
async function getRecentLogs(options) {
|
|
214
|
-
return queryLogs({
|
|
215
|
-
levels: options?.level ? [options.level] : void 0,
|
|
216
|
-
limit: options?.limit || 50,
|
|
217
|
-
module: options?.module,
|
|
218
|
-
order: "desc"
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Search logs by keyword
|
|
223
|
-
*/
|
|
224
|
-
async function searchLogs(keyword, options) {
|
|
225
|
-
return queryLogs({
|
|
226
|
-
q: keyword,
|
|
227
|
-
...options,
|
|
228
|
-
limit: options?.limit || 100
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* Get logs for a specific request/session
|
|
233
|
-
*/
|
|
234
|
-
async function getLogsByContext(contextType, contextValue, limit = 100) {
|
|
235
|
-
return queryLogs({
|
|
236
|
-
[contextType]: contextValue,
|
|
237
|
-
limit,
|
|
238
|
-
order: "asc"
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Get available log levels from actual log data
|
|
243
|
-
*/
|
|
244
|
-
async function getLogLevelsFromData() {
|
|
245
|
-
const levels = /* @__PURE__ */ new Set();
|
|
246
|
-
const files = getLogFiles().slice(0, 3);
|
|
247
|
-
for (const file of files) for await (const entry of streamLogFile(file.path, { limit: 500 })) levels.add(entry.level);
|
|
248
|
-
return Array.from(levels).sort();
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Get available modules from log data
|
|
252
|
-
*/
|
|
253
171
|
async function getLogModules() {
|
|
254
172
|
const modules = /* @__PURE__ */ new Set();
|
|
255
173
|
const files = getLogFiles().slice(0, 7);
|
|
256
|
-
for (const file of files) for await (const entry of streamLogFile(file.path, { limit: 1e3 }))
|
|
257
|
-
if (entry.module) modules.add(entry.module);
|
|
258
|
-
if (entry.prefix) modules.add(entry.prefix);
|
|
259
|
-
}
|
|
174
|
+
for (const file of files) for await (const entry of streamLogFile(file.path, { limit: 1e3 })) if (entry.module) modules.add(entry.module);
|
|
260
175
|
return Array.from(modules).filter(Boolean).sort();
|
|
261
176
|
}
|
|
262
177
|
/**
|
|
263
178
|
* Get log statistics by level (sampled from recent files)
|
|
264
179
|
*/
|
|
265
|
-
async function
|
|
180
|
+
async function getFileLogStats() {
|
|
266
181
|
const files = getLogFiles();
|
|
267
182
|
const byLevel = {
|
|
268
183
|
trace: 0,
|
|
@@ -276,29 +191,63 @@ async function getLogStats() {
|
|
|
276
191
|
for (const file of files.slice(0, 7)) for await (const entry of streamLogFile(file.path, { limit: 1e3 })) if (entry.level in byLevel) byLevel[entry.level]++;
|
|
277
192
|
return { byLevel };
|
|
278
193
|
}
|
|
194
|
+
function entryMeta(entry) {
|
|
195
|
+
if (!entry.meta || typeof entry.meta !== "object") return void 0;
|
|
196
|
+
return entry.meta;
|
|
197
|
+
}
|
|
198
|
+
function extractErrName(entry) {
|
|
199
|
+
const meta = entryMeta(entry);
|
|
200
|
+
const raw = entry.err ?? meta?.err;
|
|
201
|
+
if (raw && typeof raw === "object" && raw !== null) {
|
|
202
|
+
const name = raw.name;
|
|
203
|
+
if (typeof name === "string" && name.trim()) return name;
|
|
204
|
+
const message = raw.message;
|
|
205
|
+
if (typeof message === "string" && message.trim()) return message.slice(0, 120);
|
|
206
|
+
}
|
|
207
|
+
if (typeof entry.errorMessage === "string" && entry.errorMessage.trim()) return entry.errorMessage.slice(0, 120);
|
|
208
|
+
return entry.message?.slice(0, 120) || "Error";
|
|
209
|
+
}
|
|
210
|
+
function summaryKey(entry) {
|
|
211
|
+
const errName = extractErrName(entry);
|
|
212
|
+
const meta = entryMeta(entry);
|
|
213
|
+
return `${errName}::${typeof entry.phase === "string" ? entry.phase : typeof meta?.phase === "string" ? meta.phase : ""}::${entry.module || ""}`;
|
|
214
|
+
}
|
|
279
215
|
/**
|
|
280
|
-
*
|
|
216
|
+
* Aggregate recent error/fatal logs by err name + phase + module.
|
|
281
217
|
*/
|
|
282
|
-
function
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
218
|
+
async function getLogErrorSummary(options) {
|
|
219
|
+
const limit = options?.limit ?? 20;
|
|
220
|
+
const groups = /* @__PURE__ */ new Map();
|
|
221
|
+
const entries = await queryLogs({
|
|
222
|
+
levels: ["error", "fatal"],
|
|
223
|
+
from: options?.from,
|
|
224
|
+
to: options?.to,
|
|
225
|
+
limit: 5e3,
|
|
226
|
+
order: "desc"
|
|
227
|
+
});
|
|
228
|
+
for (const entry of entries) {
|
|
229
|
+
const key = summaryKey(entry);
|
|
230
|
+
const parsed = entry;
|
|
231
|
+
const existing = groups.get(key);
|
|
232
|
+
if (existing) {
|
|
233
|
+
existing.count += 1;
|
|
234
|
+
if (entry.timestamp > existing.lastSeen) {
|
|
235
|
+
existing.lastSeen = entry.timestamp;
|
|
236
|
+
existing.sampleMessage = entry.message;
|
|
237
|
+
}
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
groups.set(key, {
|
|
241
|
+
key,
|
|
242
|
+
errName: extractErrName(parsed),
|
|
243
|
+
phase: typeof parsed.phase === "string" ? parsed.phase : typeof entryMeta(parsed)?.phase === "string" ? String(entryMeta(parsed)?.phase) : void 0,
|
|
244
|
+
module: parsed.module,
|
|
245
|
+
count: 1,
|
|
246
|
+
lastSeen: entry.timestamp,
|
|
247
|
+
sampleMessage: entry.message
|
|
248
|
+
});
|
|
296
249
|
}
|
|
297
|
-
return
|
|
298
|
-
deleted,
|
|
299
|
-
freedBytes,
|
|
300
|
-
errors
|
|
301
|
-
};
|
|
250
|
+
return Array.from(groups.values()).sort((a, b) => b.count - a.count || b.lastSeen.localeCompare(a.lastSeen)).slice(0, limit);
|
|
302
251
|
}
|
|
303
252
|
const LOG_LEVELS = [
|
|
304
253
|
"trace",
|
|
@@ -315,6 +264,6 @@ function getLogLevels() {
|
|
|
315
264
|
return [...LOG_LEVELS];
|
|
316
265
|
}
|
|
317
266
|
//#endregion
|
|
318
|
-
export { LOG_DIR,
|
|
267
|
+
export { LOG_DIR, getFileLogStats, getLogErrorSummary, getLogFiles, getLogLevels, getLogModules, getLogPath, queryLogs };
|
|
319
268
|
|
|
320
269
|
//# sourceMappingURL=log-store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log-store.js","names":[],"sources":["../../../../src/utils/logger/log-store.ts"],"sourcesContent":["/**\n * Log Store - Enhanced File-based Log Storage\n * \n * Features:\n * - Query logs across multiple files with filtering\n * - Support for compressed (.gz) log files\n * - Pagination and sorting\n * - Statistics and analytics\n * - Safe log cleanup with actual deletion\n */\n\nimport { \n existsSync, \n mkdirSync, \n readdirSync, \n statSync, \n createReadStream,\n unlinkSync,\n} from 'fs';\nimport { readFile } from 'fs/promises';\nimport { join, basename } from 'path';\nimport { createInterface } from 'readline';\nimport { gunzip } from 'zlib';\nimport { promisify } from 'util';\nimport { Readable } from 'stream';\nimport type { LogLevel, LogFileMeta, LogQuery, LogStats, LogEntry } from './types.js';\n\nconst gunzipAsync = promisify(gunzip);\n\n// ============================================\n// Types\n// ============================================\n\ninterface ParsedLogEntry extends LogEntry {\n _source?: string;\n _lineNumber?: number;\n}\n\n// ============================================\n// Configuration\n// ============================================\n\nconst LOG_DIR = process.env.XOPC_LOG_DIR || join(process.env.HOME || '.', '.xopc', 'logs');\n\nfunction ensureLogDir(): void {\n if (!existsSync(LOG_DIR)) {\n mkdirSync(LOG_DIR, { recursive: true });\n }\n}\n\n// ============================================\n// File Management\n// ============================================\n\n/**\n * Get all log files (including compressed)\n */\nexport function getLogFiles(): LogFileMeta[] {\n ensureLogDir();\n\n const files = readdirSync(LOG_DIR)\n .filter(f => f.endsWith('.log') || f.endsWith('.log.gz'))\n .map(f => {\n const filePath = join(LOG_DIR, f);\n const stats = statSync(filePath);\n \n let type: LogFileMeta['type'] = 'app';\n if (f.includes('error')) type = 'error';\n else if (f.includes('audit')) type = 'audit';\n else if (f.includes('access')) type = 'access';\n\n return {\n name: f,\n path: filePath,\n size: stats.size,\n created: stats.birthtime.toISOString(),\n modified: stats.mtime.toISOString(),\n type,\n };\n })\n .sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());\n\n return files;\n}\n\n/**\n * Get log file path for a specific date and type\n */\nexport function getLogPath(\n date: Date = new Date(), \n type: 'app' | 'error' | 'audit' | 'access' = 'app'\n): string {\n ensureLogDir();\n const dateStr = date.toISOString().split('T')[0];\n return join(LOG_DIR, `${type}-${dateStr}.log`);\n}\n\n/**\n * Get available log files for a date range\n */\nfunction getLogFilesForRange(from: Date, to: Date): LogFileMeta[] {\n const allFiles = getLogFiles();\n \n return allFiles.filter(f => {\n // Extract date from filename (e.g., app-2024-01-01.log)\n const match = f.name.match(/(\\d{4}-\\d{2}-\\d{2})/);\n if (!match) return false;\n \n const fileDate = new Date(match[1]);\n return fileDate >= from && fileDate <= to;\n });\n}\n\n// ============================================\n// Log Parsing\n// ============================================\n\n/**\n * Parse a single log line (JSON format from pino)\n */\nfunction parseLogLine(line: string, source?: string, lineNumber?: number): ParsedLogEntry | null {\n const trimmed = line.trim();\n if (!trimmed) return null;\n\n try {\n // Try JSON format (pino default)\n const parsed = JSON.parse(trimmed);\n \n // Convert pino numeric level to string (10=trace, 20=debug, 30=info, 40=warn, 50=error, 60=fatal)\n const levelNum = typeof parsed.level === 'number' ? parsed.level : 30;\n const levelMap: Record<number, string> = {\n 10: 'trace',\n 20: 'debug',\n 30: 'info',\n 40: 'warn',\n 50: 'error',\n 60: 'fatal',\n };\n\n return {\n timestamp: parsed.time || parsed.timestamp || '',\n level: levelMap[levelNum] || (parsed.level?.toString() || 'info').toLowerCase(),\n message: parsed.msg || parsed.message || '',\n module: parsed.module,\n prefix: parsed.prefix,\n service: parsed.service,\n extension: parsed.extension,\n requestId: parsed.requestId,\n sessionId: parsed.sessionId,\n _source: source,\n _lineNumber: lineNumber,\n ...parsed,\n };\n } catch {\n // Fallback: try to parse pino's text format\n const match = trimmed.match(/^\\[([^\\]]+)\\]\\s+(\\w+):\\s+(.+)$/);\n if (match) {\n return {\n timestamp: match[1],\n level: match[2].toLowerCase() as LogLevel,\n message: match[3],\n _source: source,\n _lineNumber: lineNumber,\n };\n }\n \n // Plain text fallback\n return {\n timestamp: new Date().toISOString(),\n level: 'info',\n message: trimmed,\n _source: source,\n _lineNumber: lineNumber,\n };\n }\n}\n\n// ============================================\n// Streaming\n// ============================================\n\n/**\n * Create a readable stream for a log file (handles .gz files)\n */\nasync function createLogFileStream(filePath: string): Promise<Readable> {\n if (filePath.endsWith('.gz')) {\n const compressed = await readFile(filePath);\n const decompressed = await gunzipAsync(compressed);\n return Readable.from(decompressed.toString('utf-8').split('\\n'));\n }\n \n return createReadStream(filePath, { encoding: 'utf-8' });\n}\n\n/**\n * Stream and filter log entries from a file\n */\nasync function* streamLogFile(\n filePath: string,\n query: LogQuery = {}\n): AsyncGenerator<ParsedLogEntry> {\n if (!existsSync(filePath)) return;\n\n const fileName = basename(filePath);\n let stream: Readable;\n \n try {\n stream = await createLogFileStream(filePath);\n } catch {\n return;\n }\n\n const rl = createInterface({ \n input: stream,\n crlfDelay: Infinity \n });\n\n let lineNumber = 0;\n\n try {\n for await (const line of rl) {\n lineNumber++;\n const entry = parseLogLine(line, fileName, lineNumber);\n if (!entry) continue;\n\n // Apply filters\n if (!matchesQuery(entry, query)) continue;\n\n yield entry;\n }\n } finally {\n rl.close();\n }\n}\n\n/**\n * Check if a log entry matches the query filters\n */\nfunction matchesQuery(entry: ParsedLogEntry, query: LogQuery): boolean {\n // Filter by levels\n if (query.levels?.length && !query.levels.includes(entry.level as LogLevel)) {\n return false;\n }\n\n // Filter by time range\n if (query.from) {\n const fromDate = new Date(query.from);\n const entryDate = new Date(entry.timestamp);\n if (entryDate < fromDate) return false;\n }\n if (query.to) {\n const toDate = new Date(query.to);\n const entryDate = new Date(entry.timestamp);\n if (entryDate > toDate) return false;\n }\n\n // Filter by keyword (search in message and context fields)\n if (query.q) {\n const keyword = query.q.toLowerCase();\n const searchable = [\n entry.message,\n entry.module,\n entry.prefix,\n entry.service,\n entry.extension,\n entry.requestId,\n entry.sessionId,\n ]\n .filter(Boolean)\n .map(String)\n .join(' ')\n .toLowerCase();\n \n if (!searchable.includes(keyword)) return false;\n }\n\n // Filter by specific fields\n if (query.module && entry.module !== query.module) return false;\n if (query.extension && entry.extension !== query.extension) return false;\n if (query.service && entry.service !== query.service) return false;\n if (query.requestId && entry.requestId !== query.requestId) return false;\n if (query.sessionId && entry.sessionId !== query.sessionId) return false;\n\n return true;\n}\n\n// ============================================\n// Query API\n// ============================================\n\n/** Cap matched rows before sort/slice so query stays bounded on huge log dirs. */\nconst QUERY_LOGS_MAX_MATCHED = 25_000;\n\n/**\n * Query logs across multiple files\n */\nexport async function queryLogs(query: LogQuery = {}): Promise<LogEntry[]> {\n ensureLogDir();\n\n const results: ParsedLogEntry[] = [];\n const files = getLogFiles();\n\n // Filter files by date range if specified\n let relevantFiles = files;\n if (query.from || query.to) {\n const fromDate = query.from ? new Date(query.from) : new Date(0);\n const toDate = query.to ? new Date(query.to) : new Date();\n relevantFiles = getLogFilesForRange(fromDate, toDate);\n }\n\n // Collect all matches (files are newest-first; lines in each file are time-asc).\n // Do not stop at `limit` while streaming — that only captured the oldest tail of\n // early files and made \"newest first\" wrong after sort.\n for (const file of relevantFiles) {\n if (results.length >= QUERY_LOGS_MAX_MATCHED) break;\n for await (const entry of streamLogFile(file.path, query)) {\n results.push(entry);\n if (results.length >= QUERY_LOGS_MAX_MATCHED) break;\n }\n }\n\n // Sort by timestamp\n const order = query.order || 'desc';\n results.sort((a, b) => {\n const diff = new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();\n return order === 'desc' ? diff : -diff;\n });\n\n // Apply offset/limit\n const offset = query.offset || 0;\n const limit = query.limit || 100;\n return results.slice(offset, offset + limit).map(({ _source, _lineNumber, ...rest }) => rest);\n}\n\n/**\n * Get recent logs (convenience method)\n */\nexport async function getRecentLogs(options?: {\n level?: LogLevel;\n limit?: number;\n module?: string;\n}): Promise<LogEntry[]> {\n return queryLogs({\n levels: options?.level ? [options.level] : undefined,\n limit: options?.limit || 50,\n module: options?.module,\n order: 'desc',\n });\n}\n\n/**\n * Search logs by keyword\n */\nexport async function searchLogs(\n keyword: string,\n options?: {\n from?: string;\n to?: string;\n limit?: number;\n }\n): Promise<LogEntry[]> {\n return queryLogs({\n q: keyword,\n ...options,\n limit: options?.limit || 100,\n });\n}\n\n/**\n * Get logs for a specific request/session\n */\nexport async function getLogsByContext(\n contextType: 'requestId' | 'sessionId',\n contextValue: string,\n limit: number = 100\n): Promise<LogEntry[]> {\n return queryLogs({\n [contextType]: contextValue,\n limit,\n order: 'asc',\n });\n}\n\n// ============================================\n// Statistics\n// ============================================\n\n/**\n * Get available log levels from actual log data\n */\nexport async function getLogLevelsFromData(): Promise<LogLevel[]> {\n const levels = new Set<LogLevel>();\n const files = getLogFiles().slice(0, 3); // Check last 3 files\n\n for (const file of files) {\n for await (const entry of streamLogFile(file.path, { limit: 500 })) {\n levels.add(entry.level as LogLevel);\n }\n }\n\n return Array.from(levels).sort();\n}\n\n/**\n * Get available modules from log data\n */\nexport async function getLogModules(): Promise<string[]> {\n const modules = new Set<string>();\n const files = getLogFiles().slice(0, 7);\n\n for (const file of files) {\n for await (const entry of streamLogFile(file.path, { limit: 1000 })) {\n if (entry.module) modules.add(entry.module);\n if (entry.prefix) modules.add(entry.prefix);\n }\n }\n\n return Array.from(modules).filter(Boolean).sort();\n}\n\n/**\n * Get log statistics by level (sampled from recent files)\n */\nexport async function getLogStats(): Promise<LogStats> {\n const files = getLogFiles();\n\n // Count by level (sample from recent files)\n const byLevel: Record<LogLevel, number> = {\n trace: 0,\n debug: 0,\n info: 0,\n warn: 0,\n error: 0,\n fatal: 0,\n silent: 0,\n };\n\n for (const file of files.slice(0, 7)) {\n for await (const entry of streamLogFile(file.path, { limit: 1000 })) {\n if (entry.level in byLevel) {\n byLevel[entry.level as LogLevel]++;\n }\n }\n }\n\n return { byLevel };\n}\n\n// ============================================\n// Cleanup\n// ============================================\n\n/**\n * Clean old logs (actually deletes files)\n */\nexport function cleanOldLogs(keepDays: number = 7): {\n deleted: number;\n freedBytes: number;\n errors: string[];\n} {\n const cutoff = new Date();\n cutoff.setDate(cutoff.getDate() - keepDays);\n\n let deleted = 0;\n let freedBytes = 0;\n const errors: string[] = [];\n\n const files = getLogFiles();\n for (const file of files) {\n const fileDate = new Date(file.modified);\n if (fileDate < cutoff) {\n try {\n const size = file.size;\n unlinkSync(file.path);\n deleted++;\n freedBytes += size;\n } catch (err) {\n errors.push(`Failed to delete ${file.name}: ${err}`);\n }\n }\n }\n\n return { deleted, freedBytes, errors };\n}\n\n/**\n * Clean logs by size (keep total under limit)\n */\nfunction _cleanBySize(maxTotalMB: number = 500): {\n deleted: number;\n freedBytes: number;\n errors: string[];\n} {\n const maxBytes = maxTotalMB * 1024 * 1024;\n const files = getLogFiles();\n \n let totalSize = files.reduce((sum, f) => sum + f.size, 0);\n let deleted = 0;\n let freedBytes = 0;\n const errors: string[] = [];\n\n // Delete oldest files until under limit\n for (const file of files.slice().reverse()) {\n if (totalSize <= maxBytes) break;\n\n try {\n unlinkSync(file.path);\n freedBytes += file.size;\n totalSize -= file.size;\n deleted++;\n } catch (err) {\n errors.push(`Failed to delete ${file.name}: ${err}`);\n }\n }\n\n return { deleted, freedBytes, errors };\n}\n\n// ============================================\n// Log Levels\n// ============================================\n\nconst LOG_LEVELS: LogLevel[] = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];\n\n/**\n * Get available log levels\n */\nexport function getLogLevels(): LogLevel[] {\n return [...LOG_LEVELS];\n}\n\n// ============================================\n// Exports\n// ============================================\n\nexport { LOG_DIR };\nexport type { LogEntry, LogQuery, LogFileMeta, LogStats } from './types.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;AA2BA,MAAM,cAAc,UAAU,OAAO;AAerC,MAAM,UAAU,QAAQ,IAAI,gBAAgB,KAAK,QAAQ,IAAI,QAAQ,KAAK,SAAS,OAAO;AAE1F,SAAS,eAAqB;AAC5B,KAAI,CAAC,WAAW,QAAQ,CACtB,WAAU,SAAS,EAAE,WAAW,MAAM,CAAC;;;;;AAW3C,SAAgB,cAA6B;AAC3C,eAAc;AAwBd,QAtBc,YAAY,QAAQ,CAC/B,QAAO,MAAK,EAAE,SAAS,OAAO,IAAI,EAAE,SAAS,UAAU,CAAC,CACxD,KAAI,MAAK;EACR,MAAM,WAAW,KAAK,SAAS,EAAE;EACjC,MAAM,QAAQ,SAAS,SAAS;EAEhC,IAAI,OAA4B;AAChC,MAAI,EAAE,SAAS,QAAQ,CAAE,QAAO;WACvB,EAAE,SAAS,QAAQ,CAAE,QAAO;WAC5B,EAAE,SAAS,SAAS,CAAE,QAAO;AAEtC,SAAO;GACL,MAAM;GACN,MAAM;GACN,MAAM,MAAM;GACZ,SAAS,MAAM,UAAU,aAAa;GACtC,UAAU,MAAM,MAAM,aAAa;GACnC;GACD;GACD,CACD,MAAM,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,SAAS,CAAC,SAAS,CAErE;;;;;AAMd,SAAgB,WACd,uBAAa,IAAI,MAAM,EACvB,OAA6C,OACrC;AACR,eAAc;CACd,MAAM,UAAU,KAAK,aAAa,CAAC,MAAM,IAAI,CAAC;AAC9C,QAAO,KAAK,SAAS,GAAG,KAAK,GAAG,QAAQ,MAAM;;;;;AAMhD,SAAS,oBAAoB,MAAY,IAAyB;AAGhE,QAFiB,aAEF,CAAC,QAAO,MAAK;EAE1B,MAAM,QAAQ,EAAE,KAAK,MAAM,sBAAsB;AACjD,MAAI,CAAC,MAAO,QAAO;EAEnB,MAAM,WAAW,IAAI,KAAK,MAAM,GAAG;AACnC,SAAO,YAAY,QAAQ,YAAY;GACvC;;;;;AAUJ,SAAS,aAAa,MAAc,QAAiB,YAA4C;CAC/F,MAAM,UAAU,KAAK,MAAM;AAC3B,KAAI,CAAC,QAAS,QAAO;AAErB,KAAI;EAEF,MAAM,SAAS,KAAK,MAAM,QAAQ;EAGlC,MAAM,WAAW,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ;AAUnE,SAAO;GACL,WAAW,OAAO,QAAQ,OAAO,aAAa;GAC9C,OAAO;IAVP,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IAKW,CAAC,cAAc,OAAO,OAAO,UAAU,IAAI,QAAQ,aAAa;GAC/E,SAAS,OAAO,OAAO,OAAO,WAAW;GACzC,QAAQ,OAAO;GACf,QAAQ,OAAO;GACf,SAAS,OAAO;GAChB,WAAW,OAAO;GAClB,WAAW,OAAO;GAClB,WAAW,OAAO;GAClB,SAAS;GACT,aAAa;GACb,GAAG;GACJ;SACK;EAEN,MAAM,QAAQ,QAAQ,MAAM,iCAAiC;AAC7D,MAAI,MACF,QAAO;GACL,WAAW,MAAM;GACjB,OAAO,MAAM,GAAG,aAAa;GAC7B,SAAS,MAAM;GACf,SAAS;GACT,aAAa;GACd;AAIH,SAAO;GACL,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC,OAAO;GACP,SAAS;GACT,SAAS;GACT,aAAa;GACd;;;;;;AAWL,eAAe,oBAAoB,UAAqC;AACtE,KAAI,SAAS,SAAS,MAAM,EAAE;EAE5B,MAAM,eAAe,MAAM,YAAY,MADd,SAAS,SAAS,CACO;AAClD,SAAO,SAAS,KAAK,aAAa,SAAS,QAAQ,CAAC,MAAM,KAAK,CAAC;;AAGlE,QAAO,iBAAiB,UAAU,EAAE,UAAU,SAAS,CAAC;;;;;AAM1D,gBAAgB,cACd,UACA,QAAkB,EAAE,EACY;AAChC,KAAI,CAAC,WAAW,SAAS,CAAE;CAE3B,MAAM,WAAW,SAAS,SAAS;CACnC,IAAI;AAEJ,KAAI;AACF,WAAS,MAAM,oBAAoB,SAAS;SACtC;AACN;;CAGF,MAAM,KAAK,gBAAgB;EACzB,OAAO;EACP,WAAW;EACZ,CAAC;CAEF,IAAI,aAAa;AAEjB,KAAI;AACF,aAAW,MAAM,QAAQ,IAAI;AAC3B;GACA,MAAM,QAAQ,aAAa,MAAM,UAAU,WAAW;AACtD,OAAI,CAAC,MAAO;AAGZ,OAAI,CAAC,aAAa,OAAO,MAAM,CAAE;AAEjC,SAAM;;WAEA;AACR,KAAG,OAAO;;;;;;AAOd,SAAS,aAAa,OAAuB,OAA0B;AAErE,KAAI,MAAM,QAAQ,UAAU,CAAC,MAAM,OAAO,SAAS,MAAM,MAAkB,CACzE,QAAO;AAIT,KAAI,MAAM,MAAM;EACd,MAAM,WAAW,IAAI,KAAK,MAAM,KAAK;AAErC,MAAI,IADkB,KAAK,MAAM,UACpB,GAAG,SAAU,QAAO;;AAEnC,KAAI,MAAM,IAAI;EACZ,MAAM,SAAS,IAAI,KAAK,MAAM,GAAG;AAEjC,MAAI,IADkB,KAAK,MAAM,UACpB,GAAG,OAAQ,QAAO;;AAIjC,KAAI,MAAM,GAAG;EACX,MAAM,UAAU,MAAM,EAAE,aAAa;AAerC,MAAI,CAde;GACjB,MAAM;GACN,MAAM;GACN,MAAM;GACN,MAAM;GACN,MAAM;GACN,MAAM;GACN,MAAM;GACP,CACE,OAAO,QAAQ,CACf,IAAI,OAAO,CACX,KAAK,IAAI,CACT,aAEY,CAAC,SAAS,QAAQ,CAAE,QAAO;;AAI5C,KAAI,MAAM,UAAU,MAAM,WAAW,MAAM,OAAQ,QAAO;AAC1D,KAAI,MAAM,aAAa,MAAM,cAAc,MAAM,UAAW,QAAO;AACnE,KAAI,MAAM,WAAW,MAAM,YAAY,MAAM,QAAS,QAAO;AAC7D,KAAI,MAAM,aAAa,MAAM,cAAc,MAAM,UAAW,QAAO;AACnE,KAAI,MAAM,aAAa,MAAM,cAAc,MAAM,UAAW,QAAO;AAEnE,QAAO;;;AAQT,MAAM,yBAAyB;;;;AAK/B,eAAsB,UAAU,QAAkB,EAAE,EAAuB;AACzE,eAAc;CAEd,MAAM,UAA4B,EAAE;CAIpC,IAAI,gBAHU,aAGW;AACzB,KAAI,MAAM,QAAQ,MAAM,GAGtB,iBAAgB,oBAFC,MAAM,OAAO,IAAI,KAAK,MAAM,KAAK,mBAAG,IAAI,KAAK,EAAE,EACjD,MAAM,KAAK,IAAI,KAAK,MAAM,GAAG,mBAAG,IAAI,MAAM,CACJ;AAMvD,MAAK,MAAM,QAAQ,eAAe;AAChC,MAAI,QAAQ,UAAU,uBAAwB;AAC9C,aAAW,MAAM,SAAS,cAAc,KAAK,MAAM,MAAM,EAAE;AACzD,WAAQ,KAAK,MAAM;AACnB,OAAI,QAAQ,UAAU,uBAAwB;;;CAKlD,MAAM,QAAQ,MAAM,SAAS;AAC7B,SAAQ,MAAM,GAAG,MAAM;EACrB,MAAM,OAAO,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS;AAC9E,SAAO,UAAU,SAAS,OAAO,CAAC;GAClC;CAGF,MAAM,SAAS,MAAM,UAAU;CAC/B,MAAM,QAAQ,MAAM,SAAS;AAC7B,QAAO,QAAQ,MAAM,QAAQ,SAAS,MAAM,CAAC,KAAK,EAAE,SAAS,aAAa,GAAG,WAAW,KAAK;;;;;AAM/F,eAAsB,cAAc,SAIZ;AACtB,QAAO,UAAU;EACf,QAAQ,SAAS,QAAQ,CAAC,QAAQ,MAAM,GAAG,KAAA;EAC3C,OAAO,SAAS,SAAS;EACzB,QAAQ,SAAS;EACjB,OAAO;EACR,CAAC;;;;;AAMJ,eAAsB,WACpB,SACA,SAKqB;AACrB,QAAO,UAAU;EACf,GAAG;EACH,GAAG;EACH,OAAO,SAAS,SAAS;EAC1B,CAAC;;;;;AAMJ,eAAsB,iBACpB,aACA,cACA,QAAgB,KACK;AACrB,QAAO,UAAU;GACd,cAAc;EACf;EACA,OAAO;EACR,CAAC;;;;;AAUJ,eAAsB,uBAA4C;CAChE,MAAM,yBAAS,IAAI,KAAe;CAClC,MAAM,QAAQ,aAAa,CAAC,MAAM,GAAG,EAAE;AAEvC,MAAK,MAAM,QAAQ,MACjB,YAAW,MAAM,SAAS,cAAc,KAAK,MAAM,EAAE,OAAO,KAAK,CAAC,CAChE,QAAO,IAAI,MAAM,MAAkB;AAIvC,QAAO,MAAM,KAAK,OAAO,CAAC,MAAM;;;;;AAMlC,eAAsB,gBAAmC;CACvD,MAAM,0BAAU,IAAI,KAAa;CACjC,MAAM,QAAQ,aAAa,CAAC,MAAM,GAAG,EAAE;AAEvC,MAAK,MAAM,QAAQ,MACjB,YAAW,MAAM,SAAS,cAAc,KAAK,MAAM,EAAE,OAAO,KAAM,CAAC,EAAE;AACnE,MAAI,MAAM,OAAQ,SAAQ,IAAI,MAAM,OAAO;AAC3C,MAAI,MAAM,OAAQ,SAAQ,IAAI,MAAM,OAAO;;AAI/C,QAAO,MAAM,KAAK,QAAQ,CAAC,OAAO,QAAQ,CAAC,MAAM;;;;;AAMnD,eAAsB,cAAiC;CACrD,MAAM,QAAQ,aAAa;CAG3B,MAAM,UAAoC;EACxC,OAAO;EACP,OAAO;EACP,MAAM;EACN,MAAM;EACN,OAAO;EACP,OAAO;EACP,QAAQ;EACT;AAED,MAAK,MAAM,QAAQ,MAAM,MAAM,GAAG,EAAE,CAClC,YAAW,MAAM,SAAS,cAAc,KAAK,MAAM,EAAE,OAAO,KAAM,CAAC,CACjE,KAAI,MAAM,SAAS,QACjB,SAAQ,MAAM;AAKpB,QAAO,EAAE,SAAS;;;;;AAUpB,SAAgB,aAAa,WAAmB,GAI9C;CACA,MAAM,yBAAS,IAAI,MAAM;AACzB,QAAO,QAAQ,OAAO,SAAS,GAAG,SAAS;CAE3C,IAAI,UAAU;CACd,IAAI,aAAa;CACjB,MAAM,SAAmB,EAAE;CAE3B,MAAM,QAAQ,aAAa;AAC3B,MAAK,MAAM,QAAQ,MAEjB,KAAI,IADiB,KAAK,KAAK,SACnB,GAAG,OACb,KAAI;EACF,MAAM,OAAO,KAAK;AAClB,aAAW,KAAK,KAAK;AACrB;AACA,gBAAc;UACP,KAAK;AACZ,SAAO,KAAK,oBAAoB,KAAK,KAAK,IAAI,MAAM;;AAK1D,QAAO;EAAE;EAAS;EAAY;EAAQ;;AAwCxC,MAAM,aAAyB;CAAC;CAAS;CAAS;CAAQ;CAAQ;CAAS;CAAQ;;;;AAKnF,SAAgB,eAA2B;AACzC,QAAO,CAAC,GAAG,WAAW"}
|
|
1
|
+
{"version":3,"file":"log-store.js","names":[],"sources":["../../../../src/utils/logger/log-store.ts"],"sourcesContent":["/**\n * Log Store - Enhanced File-based Log Storage\n * \n * Features:\n * - Query logs across multiple files with filtering\n * - Support for compressed (.gz) log files\n * - Pagination and sorting\n * - Statistics and analytics\n * - Safe log cleanup with actual deletion\n */\n\nimport { \n existsSync, \n mkdirSync, \n readdirSync, \n statSync, \n createReadStream,\n} from 'fs';\nimport { readFile } from 'fs/promises';\nimport { join, basename } from 'path';\nimport { createInterface } from 'readline';\nimport { gunzip } from 'zlib';\nimport { promisify } from 'util';\nimport { Readable } from 'stream';\nimport type { LogLevel, LogFileMeta, LogQuery, LogStats, LogEntry } from './types.js';\nimport { logEntrySearchText, pinoRecordToLogEntry } from './pino-record.js';\n\nconst gunzipAsync = promisify(gunzip);\n\n// ============================================\n// Types\n// ============================================\n\ninterface ParsedLogEntry extends LogEntry {\n _source?: string;\n _lineNumber?: number;\n}\n\n// ============================================\n// Configuration\n// ============================================\n\nconst LOG_DIR = process.env.XOPC_LOG_DIR || join(process.env.HOME || '.', '.xopc', 'logs');\n\nfunction ensureLogDir(): void {\n if (!existsSync(LOG_DIR)) {\n mkdirSync(LOG_DIR, { recursive: true });\n }\n}\n\n// ============================================\n// File Management\n// ============================================\n\n/**\n * Get all log files (including compressed)\n */\nexport function getLogFiles(): LogFileMeta[] {\n ensureLogDir();\n\n const files = readdirSync(LOG_DIR)\n .filter(f => f.endsWith('.log') || f.endsWith('.log.gz'))\n .map(f => {\n const filePath = join(LOG_DIR, f);\n const stats = statSync(filePath);\n \n let type: LogFileMeta['type'] = 'app';\n if (f.includes('error')) type = 'error';\n else if (f.includes('audit')) type = 'audit';\n else if (f.includes('access')) type = 'access';\n\n return {\n name: f,\n path: filePath,\n size: stats.size,\n created: stats.birthtime.toISOString(),\n modified: stats.mtime.toISOString(),\n type,\n };\n })\n .sort((a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime());\n\n return files;\n}\n\n/**\n * Get log file path for a specific date and type\n */\nexport function getLogPath(\n date: Date = new Date(), \n type: 'app' | 'error' | 'audit' | 'access' = 'app'\n): string {\n ensureLogDir();\n const dateStr = date.toISOString().split('T')[0];\n return join(LOG_DIR, `${type}-${dateStr}.log`);\n}\n\n/**\n * Get available log files for a date range\n */\nfunction getLogFilesForRange(from: Date, to: Date): LogFileMeta[] {\n const allFiles = getLogFiles();\n \n return allFiles.filter(f => {\n // Extract date from filename (e.g., app-2024-01-01.log)\n const match = f.name.match(/(\\d{4}-\\d{2}-\\d{2})/);\n if (!match) return false;\n \n const fileDate = new Date(match[1]);\n return fileDate >= from && fileDate <= to;\n });\n}\n\n// ============================================\n// Log Parsing\n// ============================================\n\n/**\n * Parse a single log line (JSON format from pino)\n */\nfunction parseLogLine(line: string, source?: string, lineNumber?: number): ParsedLogEntry | null {\n const trimmed = line.trim();\n if (!trimmed) return null;\n\n try {\n const parsed = JSON.parse(trimmed) as Record<string, unknown>;\n const entry = pinoRecordToLogEntry(parsed) as ParsedLogEntry;\n entry._source = source;\n entry._lineNumber = lineNumber;\n return entry;\n } catch {\n return null;\n }\n}\n\n// ============================================\n// Streaming\n// ============================================\n\n/**\n * Create a readable stream for a log file (handles .gz files)\n */\nasync function createLogFileStream(filePath: string): Promise<Readable> {\n if (filePath.endsWith('.gz')) {\n const compressed = await readFile(filePath);\n const decompressed = await gunzipAsync(compressed);\n return Readable.from(decompressed.toString('utf-8').split('\\n'));\n }\n \n return createReadStream(filePath, { encoding: 'utf-8' });\n}\n\n/**\n * Stream and filter log entries from a file\n */\nasync function* streamLogFile(\n filePath: string,\n query: LogQuery = {}\n): AsyncGenerator<ParsedLogEntry> {\n if (!existsSync(filePath)) return;\n\n const fileName = basename(filePath);\n let stream: Readable;\n \n try {\n stream = await createLogFileStream(filePath);\n } catch {\n return;\n }\n\n const rl = createInterface({ \n input: stream,\n crlfDelay: Infinity \n });\n\n let lineNumber = 0;\n\n try {\n for await (const line of rl) {\n lineNumber++;\n const entry = parseLogLine(line, fileName, lineNumber);\n if (!entry) continue;\n\n // Apply filters\n if (!matchesQuery(entry, query)) continue;\n\n yield entry;\n }\n } finally {\n rl.close();\n }\n}\n\n/**\n * Check if a log entry matches the query filters\n */\nfunction matchesQuery(entry: ParsedLogEntry, query: LogQuery): boolean {\n // Filter by levels\n if (query.levels?.length && !query.levels.includes(entry.level as LogLevel)) {\n return false;\n }\n\n // Filter by time range\n if (query.from) {\n const fromDate = new Date(query.from);\n const entryDate = new Date(entry.timestamp);\n if (entryDate < fromDate) return false;\n }\n if (query.to) {\n const toDate = new Date(query.to);\n const entryDate = new Date(entry.timestamp);\n if (entryDate > toDate) return false;\n }\n\n // Filter by keyword (search message, module, phase, err, structured fields)\n if (query.q) {\n const keyword = query.q.toLowerCase();\n if (!logEntrySearchText(entry).includes(keyword)) return false;\n }\n\n if (query.module && entry.module !== query.module) return false;\n if (query.extension && entry.extension !== query.extension) return false;\n if (query.service && entry.service !== query.service) return false;\n if (query.requestId && entry.requestId !== query.requestId) return false;\n if (query.sessionId && entry.sessionId !== query.sessionId) return false;\n\n return true;\n}\n\n// ============================================\n// Query API\n// ============================================\n\n/** Cap matched rows before sort/slice so query stays bounded on huge log dirs. */\nconst QUERY_LOGS_MAX_MATCHED = 25_000;\n\n/**\n * Query logs across multiple files\n */\nexport async function queryLogs(query: LogQuery = {}): Promise<LogEntry[]> {\n ensureLogDir();\n\n const results: ParsedLogEntry[] = [];\n const files = getLogFiles();\n\n // Filter files by date range if specified\n let relevantFiles = files;\n if (query.from || query.to) {\n const fromDate = query.from ? new Date(query.from) : new Date(0);\n const toDate = query.to ? new Date(query.to) : new Date();\n relevantFiles = getLogFilesForRange(fromDate, toDate);\n }\n\n // Collect all matches (files are newest-first; lines in each file are time-asc).\n // Do not stop at `limit` while streaming — that only captured the oldest tail of\n // early files and made \"newest first\" wrong after sort.\n for (const file of relevantFiles) {\n if (results.length >= QUERY_LOGS_MAX_MATCHED) break;\n for await (const entry of streamLogFile(file.path, query)) {\n results.push(entry);\n if (results.length >= QUERY_LOGS_MAX_MATCHED) break;\n }\n }\n\n // Sort by timestamp\n const order = query.order || 'desc';\n results.sort((a, b) => {\n const diff = new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();\n return order === 'desc' ? diff : -diff;\n });\n\n // Apply offset/limit\n const offset = query.offset || 0;\n const limit = query.limit || 100;\n return results.slice(offset, offset + limit).map(({ _source, _lineNumber, ...rest }) => rest);\n}\n\n// ============================================\n// Statistics\n// ============================================\nexport async function getLogModules(): Promise<string[]> {\n const modules = new Set<string>();\n const files = getLogFiles().slice(0, 7);\n\n for (const file of files) {\n for await (const entry of streamLogFile(file.path, { limit: 1000 })) {\n if (entry.module) modules.add(entry.module);\n }\n }\n\n return Array.from(modules).filter(Boolean).sort();\n}\n\n/**\n * Get log statistics by level (sampled from recent files)\n */\nexport async function getFileLogStats(): Promise<LogStats> {\n const files = getLogFiles();\n\n // Count by level (sample from recent files)\n const byLevel: Record<LogLevel, number> = {\n trace: 0,\n debug: 0,\n info: 0,\n warn: 0,\n error: 0,\n fatal: 0,\n silent: 0,\n };\n\n for (const file of files.slice(0, 7)) {\n for await (const entry of streamLogFile(file.path, { limit: 1000 })) {\n if (entry.level in byLevel) {\n byLevel[entry.level as LogLevel]++;\n }\n }\n }\n\n return { byLevel };\n}\n\n// ============================================\n// Error aggregation\n// ============================================\n\nexport interface LogErrorSummaryItem {\n key: string;\n errName: string;\n phase?: string;\n module?: string;\n count: number;\n lastSeen: string;\n sampleMessage: string;\n}\n\nfunction entryMeta(entry: ParsedLogEntry): Record<string, unknown> | undefined {\n if (!entry.meta || typeof entry.meta !== 'object') return undefined;\n return entry.meta as Record<string, unknown>;\n}\n\nfunction extractErrName(entry: ParsedLogEntry): string {\n const meta = entryMeta(entry);\n const raw = entry.err ?? meta?.err;\n if (raw && typeof raw === 'object' && raw !== null) {\n const name = (raw as Record<string, unknown>).name;\n if (typeof name === 'string' && name.trim()) return name;\n const message = (raw as Record<string, unknown>).message;\n if (typeof message === 'string' && message.trim()) return message.slice(0, 120);\n }\n if (typeof entry.errorMessage === 'string' && entry.errorMessage.trim()) {\n return entry.errorMessage.slice(0, 120);\n }\n return entry.message?.slice(0, 120) || 'Error';\n}\n\nfunction summaryKey(entry: ParsedLogEntry): string {\n const errName = extractErrName(entry);\n const meta = entryMeta(entry);\n const phase =\n typeof entry.phase === 'string'\n ? entry.phase\n : typeof meta?.phase === 'string'\n ? meta.phase\n : '';\n const module = entry.module || '';\n return `${errName}::${phase}::${module}`;\n}\n\n/**\n * Aggregate recent error/fatal logs by err name + phase + module.\n */\nexport async function getLogErrorSummary(options?: {\n from?: string;\n to?: string;\n limit?: number;\n}): Promise<LogErrorSummaryItem[]> {\n const limit = options?.limit ?? 20;\n const groups = new Map<string, LogErrorSummaryItem>();\n\n const entries = await queryLogs({\n levels: ['error', 'fatal'],\n from: options?.from,\n to: options?.to,\n limit: 5000,\n order: 'desc',\n });\n\n for (const entry of entries) {\n const key = summaryKey(entry as ParsedLogEntry);\n const parsed = entry as ParsedLogEntry;\n const existing = groups.get(key);\n if (existing) {\n existing.count += 1;\n if (entry.timestamp > existing.lastSeen) {\n existing.lastSeen = entry.timestamp;\n existing.sampleMessage = entry.message;\n }\n continue;\n }\n groups.set(key, {\n key,\n errName: extractErrName(parsed),\n phase:\n typeof parsed.phase === 'string'\n ? parsed.phase\n : typeof entryMeta(parsed)?.phase === 'string'\n ? String(entryMeta(parsed)?.phase)\n : undefined,\n module: parsed.module,\n count: 1,\n lastSeen: entry.timestamp,\n sampleMessage: entry.message,\n });\n }\n\n return Array.from(groups.values())\n .sort((a, b) => b.count - a.count || b.lastSeen.localeCompare(a.lastSeen))\n .slice(0, limit);\n}\n\n// ============================================\n// Log Levels\n// ============================================\n\nconst LOG_LEVELS: LogLevel[] = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];\n\n/**\n * Get available log levels\n */\nexport function getLogLevels(): LogLevel[] {\n return [...LOG_LEVELS];\n}\n\n// ============================================\n// Exports\n// ============================================\n\nexport { LOG_DIR };\nexport type { LogEntry, LogQuery, LogFileMeta, LogStats } from './types.js';\n"],"mappings":"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"}
|
|
@@ -1,80 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Log Streaming Module - SSE
|
|
3
|
-
*
|
|
4
|
-
* Provides Server-Sent Events (SSE) for real-time log streaming:
|
|
5
|
-
* - Subscribe to log events
|
|
6
|
-
* - Filter by level and module
|
|
7
|
-
* - Automatic cleanup on disconnect
|
|
2
|
+
* Log Streaming Module - SSE real-time log delivery
|
|
8
3
|
*/
|
|
9
4
|
import type { LogLevel, LogEntry } from './types.js';
|
|
10
5
|
export type { LogLevel, LogEntry };
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
type LogSubscriber = (entry: LogEntry) => void;
|
|
17
|
-
/**
|
|
18
|
-
* Subscribe to log events
|
|
19
|
-
*
|
|
20
|
-
* @param subscriber - Callback function to receive log entries
|
|
21
|
-
* @returns Unsubscribe function
|
|
22
|
-
*/
|
|
23
|
-
declare function subscribeToLogs(subscriber: LogSubscriber): () => void;
|
|
24
|
-
/**
|
|
25
|
-
* Get number of active subscribers
|
|
26
|
-
*/
|
|
27
|
-
declare function getSubscriberCount(): number;
|
|
28
|
-
/**
|
|
29
|
-
* Check if any subscribers are active
|
|
30
|
-
*/
|
|
31
|
-
declare function hasSubscribers(): boolean;
|
|
32
|
-
/**
|
|
33
|
-
* Emit a log entry to all subscribers
|
|
34
|
-
*
|
|
35
|
-
* @internal - Called by the logger integration
|
|
36
|
-
*/
|
|
37
|
-
declare function emitLogEntry(entry: LogEntry): void;
|
|
38
|
-
/**
|
|
39
|
-
* Create a log entry from raw data
|
|
40
|
-
*
|
|
41
|
-
* @internal
|
|
42
|
-
*/
|
|
43
|
-
declare function createLogEntry(level: LogLevel, message: string, context?: Record<string, unknown>): LogEntry;
|
|
44
|
-
/**
|
|
45
|
-
* Create an SSE handler for log streaming
|
|
46
|
-
*
|
|
47
|
-
* Usage:
|
|
48
|
-
* ```typescript
|
|
49
|
-
* import { createLogStreamHandler } from './log-stream.js';
|
|
50
|
-
*
|
|
51
|
-
* app.get('/api/logs/stream', createLogStreamHandler());
|
|
52
|
-
* ```
|
|
53
|
-
*/
|
|
54
|
-
declare function createLogStreamHandler(): (req: Request) => Response;
|
|
55
|
-
/**
|
|
56
|
-
* Create a Hono-compatible SSE handler for log streaming
|
|
57
|
-
*
|
|
58
|
-
* Usage:
|
|
59
|
-
* ```typescript
|
|
60
|
-
* import { createLogSSEHandler } from './log-stream.js';
|
|
61
|
-
*
|
|
62
|
-
* app.get('/api/logs/stream', createLogSSEHandler());
|
|
63
|
-
* ```
|
|
64
|
-
*/
|
|
6
|
+
export declare function hasSubscribers(): boolean;
|
|
7
|
+
/** @internal Called from Pino live emit stream */
|
|
8
|
+
export declare function emitLogEntry(entry: LogEntry): void;
|
|
9
|
+
/** Hono handler for `GET /api/logs/stream`. */
|
|
65
10
|
export declare function createLogSSEHandler(): (c: {
|
|
66
11
|
req: {
|
|
67
12
|
raw: Request;
|
|
68
13
|
url: string;
|
|
69
14
|
};
|
|
70
15
|
}) => Promise<Response>;
|
|
71
|
-
declare const _default: {
|
|
72
|
-
subscribeToLogs: typeof subscribeToLogs;
|
|
73
|
-
getSubscriberCount: typeof getSubscriberCount;
|
|
74
|
-
hasSubscribers: typeof hasSubscribers;
|
|
75
|
-
emitLogEntry: typeof emitLogEntry;
|
|
76
|
-
createLogEntry: typeof createLogEntry;
|
|
77
|
-
createLogStreamHandler: typeof createLogStreamHandler;
|
|
78
|
-
createLogSSEHandler: typeof createLogSSEHandler;
|
|
79
|
-
};
|
|
80
|
-
export default _default;
|