@hasna/logs 0.2.0 → 0.3.1
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/bun.lock +336 -0
- package/dist/cli/index.js +8 -8
- package/dist/export-yjaar93b.js +10 -0
- package/dist/health-egdb00st.js +8 -0
- package/dist/index-3dr7d80h.js +57 -0
- package/dist/index-5tvnhvgr.js +536 -0
- package/dist/index-6y8pmes4.js +45 -0
- package/dist/index-eh9bkbpa.js +70 -0
- package/dist/index-g8dczzvv.js +30 -0
- package/dist/index-rbrsvsyh.js +88 -0
- package/dist/index-wbsq8qjd.js +1241 -0
- package/dist/index-yb8yd4j6.js +39 -0
- package/dist/jobs-02z4fzsn.js +22 -0
- package/dist/mcp/index.js +167 -74
- package/dist/query-tcg3bm9s.js +14 -0
- package/dist/server/index.js +33 -8
- package/package.json +1 -1
- package/src/lib/count.test.ts +44 -0
- package/src/lib/count.ts +45 -0
- package/src/lib/diagnose.ts +26 -11
- package/src/lib/parse-time.test.ts +37 -0
- package/src/lib/parse-time.ts +14 -0
- package/src/lib/projects.ts +10 -0
- package/src/lib/query.ts +10 -2
- package/src/lib/summarize.ts +2 -1
- package/src/mcp/index.ts +137 -68
- package/src/server/routes/logs.ts +28 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
__require
|
|
4
|
+
} from "./index-g8dczzvv.js";
|
|
5
|
+
|
|
6
|
+
// src/lib/health.ts
|
|
7
|
+
var startTime = Date.now();
|
|
8
|
+
function getHealth(db) {
|
|
9
|
+
const projects = db.prepare("SELECT COUNT(*) as c FROM projects").get().c;
|
|
10
|
+
const total_logs = db.prepare("SELECT COUNT(*) as c FROM logs").get().c;
|
|
11
|
+
const scheduler_jobs = db.prepare("SELECT COUNT(*) as c FROM scan_jobs WHERE enabled = 1").get().c;
|
|
12
|
+
const open_issues = db.prepare("SELECT COUNT(*) as c FROM issues WHERE status = 'open'").get().c;
|
|
13
|
+
const levelRows = db.prepare("SELECT level, COUNT(*) as c FROM logs GROUP BY level").all();
|
|
14
|
+
const logs_by_level = Object.fromEntries(levelRows.map((r) => [r.level, r.c]));
|
|
15
|
+
const oldest = db.prepare("SELECT MIN(timestamp) as t FROM logs").get();
|
|
16
|
+
const newest = db.prepare("SELECT MAX(timestamp) as t FROM logs").get();
|
|
17
|
+
let db_size_bytes = null;
|
|
18
|
+
try {
|
|
19
|
+
const dbPath = process.env.LOGS_DB_PATH;
|
|
20
|
+
if (dbPath) {
|
|
21
|
+
const { statSync } = __require("fs");
|
|
22
|
+
db_size_bytes = statSync(dbPath).size;
|
|
23
|
+
}
|
|
24
|
+
} catch {}
|
|
25
|
+
return {
|
|
26
|
+
status: "ok",
|
|
27
|
+
uptime_seconds: Math.floor((Date.now() - startTime) / 1000),
|
|
28
|
+
db_size_bytes,
|
|
29
|
+
projects,
|
|
30
|
+
total_logs,
|
|
31
|
+
logs_by_level,
|
|
32
|
+
oldest_log: oldest.t,
|
|
33
|
+
newest_log: newest.t,
|
|
34
|
+
scheduler_jobs,
|
|
35
|
+
open_issues
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { getHealth };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
createJob,
|
|
4
|
+
createScanRun,
|
|
5
|
+
deleteJob,
|
|
6
|
+
finishScanRun,
|
|
7
|
+
getJob,
|
|
8
|
+
listJobs,
|
|
9
|
+
listScanRuns,
|
|
10
|
+
updateJob
|
|
11
|
+
} from "./index-3dr7d80h.js";
|
|
12
|
+
import"./index-g8dczzvv.js";
|
|
13
|
+
export {
|
|
14
|
+
updateJob,
|
|
15
|
+
listScanRuns,
|
|
16
|
+
listJobs,
|
|
17
|
+
getJob,
|
|
18
|
+
finishScanRun,
|
|
19
|
+
deleteJob,
|
|
20
|
+
createScanRun,
|
|
21
|
+
createJob
|
|
22
|
+
};
|
package/dist/mcp/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
+
import {
|
|
4
|
+
countLogs
|
|
5
|
+
} from "../index-6y8pmes4.js";
|
|
3
6
|
import {
|
|
4
7
|
createAlertRule,
|
|
5
8
|
createPage,
|
|
@@ -8,27 +11,31 @@ import {
|
|
|
8
11
|
getDb,
|
|
9
12
|
getLatestSnapshot,
|
|
10
13
|
getPerfTrend,
|
|
14
|
+
ingestBatch,
|
|
11
15
|
ingestLog,
|
|
12
16
|
listAlertRules,
|
|
13
17
|
listIssues,
|
|
14
18
|
listPages,
|
|
15
19
|
listProjects,
|
|
20
|
+
resolveProjectId,
|
|
16
21
|
scoreLabel,
|
|
17
22
|
summarizeLogs,
|
|
18
23
|
updateIssueStatus
|
|
19
|
-
} from "../index-
|
|
24
|
+
} from "../index-5tvnhvgr.js";
|
|
20
25
|
import {
|
|
21
26
|
createJob,
|
|
22
27
|
listJobs
|
|
23
|
-
} from "../
|
|
28
|
+
} from "../index-3dr7d80h.js";
|
|
24
29
|
import {
|
|
25
30
|
getLogContext,
|
|
31
|
+
getLogContextFromId,
|
|
32
|
+
parseTime,
|
|
26
33
|
searchLogs,
|
|
27
34
|
tailLogs
|
|
28
|
-
} from "../
|
|
35
|
+
} from "../index-rbrsvsyh.js";
|
|
29
36
|
import {
|
|
30
37
|
getHealth
|
|
31
|
-
} from "../
|
|
38
|
+
} from "../index-yb8yd4j6.js";
|
|
32
39
|
import {
|
|
33
40
|
__commonJS,
|
|
34
41
|
__export,
|
|
@@ -6294,7 +6301,7 @@ var require_formats = __commonJS((exports) => {
|
|
|
6294
6301
|
}
|
|
6295
6302
|
var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
|
|
6296
6303
|
function getTime(strictTimeZone) {
|
|
6297
|
-
return function
|
|
6304
|
+
return function time3(str) {
|
|
6298
6305
|
const matches = TIME.exec(str);
|
|
6299
6306
|
if (!matches)
|
|
6300
6307
|
return false;
|
|
@@ -28381,17 +28388,19 @@ class StdioServerTransport {
|
|
|
28381
28388
|
}
|
|
28382
28389
|
|
|
28383
28390
|
// src/lib/diagnose.ts
|
|
28384
|
-
function diagnose(db, projectId, since) {
|
|
28385
|
-
const window = since ?? new Date(Date.now() - 24 * 3600 * 1000).toISOString();
|
|
28386
|
-
const
|
|
28391
|
+
function diagnose(db, projectId, since, include) {
|
|
28392
|
+
const window = parseTime(since) ?? since ?? new Date(Date.now() - 24 * 3600 * 1000).toISOString();
|
|
28393
|
+
const all = !include || include.length === 0;
|
|
28394
|
+
const want = (k) => all || include.includes(k);
|
|
28395
|
+
const top_errors = want("top_errors") ? db.prepare(`
|
|
28387
28396
|
SELECT message, COUNT(*) as count, service, MAX(timestamp) as last_seen
|
|
28388
28397
|
FROM logs
|
|
28389
28398
|
WHERE project_id = $p AND level IN ('error','fatal') AND timestamp >= $since
|
|
28390
28399
|
GROUP BY message, service
|
|
28391
28400
|
ORDER BY count DESC
|
|
28392
28401
|
LIMIT 10
|
|
28393
|
-
`).all({ $p: projectId, $since: window });
|
|
28394
|
-
const error_rate_by_service = db.prepare(`
|
|
28402
|
+
`).all({ $p: projectId, $since: window }) : [];
|
|
28403
|
+
const error_rate_by_service = want("error_rate") ? db.prepare(`
|
|
28395
28404
|
SELECT service,
|
|
28396
28405
|
SUM(CASE WHEN level IN ('error','fatal') THEN 1 ELSE 0 END) as errors,
|
|
28397
28406
|
SUM(CASE WHEN level = 'warn' THEN 1 ELSE 0 END) as warns,
|
|
@@ -28400,8 +28409,8 @@ function diagnose(db, projectId, since) {
|
|
|
28400
28409
|
WHERE project_id = $p AND timestamp >= $since
|
|
28401
28410
|
GROUP BY service
|
|
28402
28411
|
ORDER BY errors DESC
|
|
28403
|
-
`).all({ $p: projectId, $since: window });
|
|
28404
|
-
const failing_pages = db.prepare(`
|
|
28412
|
+
`).all({ $p: projectId, $since: window }) : [];
|
|
28413
|
+
const failing_pages = want("failing_pages") ? db.prepare(`
|
|
28405
28414
|
SELECT l.page_id, p.url, COUNT(*) as error_count
|
|
28406
28415
|
FROM logs l
|
|
28407
28416
|
JOIN pages p ON p.id = l.page_id
|
|
@@ -28409,8 +28418,8 @@ function diagnose(db, projectId, since) {
|
|
|
28409
28418
|
GROUP BY l.page_id, p.url
|
|
28410
28419
|
ORDER BY error_count DESC
|
|
28411
28420
|
LIMIT 10
|
|
28412
|
-
`).all({ $p: projectId, $since: window });
|
|
28413
|
-
const perf_regressions = db.prepare(`
|
|
28421
|
+
`).all({ $p: projectId, $since: window }) : [];
|
|
28422
|
+
const perf_regressions = want("perf") ? db.prepare(`
|
|
28414
28423
|
SELECT * FROM (
|
|
28415
28424
|
SELECT
|
|
28416
28425
|
cur.page_id,
|
|
@@ -28427,11 +28436,25 @@ function diagnose(db, projectId, since) {
|
|
|
28427
28436
|
) WHERE delta < -5 OR delta IS NULL
|
|
28428
28437
|
ORDER BY delta ASC
|
|
28429
28438
|
LIMIT 10
|
|
28430
|
-
`).all({ $p: projectId });
|
|
28439
|
+
`).all({ $p: projectId }) : [];
|
|
28431
28440
|
const totalErrors = top_errors.reduce((s, e) => s + e.count, 0);
|
|
28441
|
+
const totalWarns = error_rate_by_service.reduce((s, r) => s + r.warns, 0);
|
|
28432
28442
|
const topService = error_rate_by_service[0];
|
|
28443
|
+
const score = totalErrors === 0 ? "green" : totalErrors <= 10 ? "yellow" : "red";
|
|
28433
28444
|
const summary = totalErrors === 0 ? "No errors in this window. All looks good." : `${totalErrors} error(s) detected. Worst service: ${topService?.service ?? "unknown"} (${topService?.errors ?? 0} errors). ${failing_pages.length} page(s) with errors. ${perf_regressions.length} perf regression(s).`;
|
|
28434
|
-
return {
|
|
28445
|
+
return {
|
|
28446
|
+
project_id: projectId,
|
|
28447
|
+
window,
|
|
28448
|
+
score,
|
|
28449
|
+
error_count: totalErrors,
|
|
28450
|
+
warn_count: totalWarns,
|
|
28451
|
+
has_perf_regression: perf_regressions.length > 0,
|
|
28452
|
+
top_errors,
|
|
28453
|
+
error_rate_by_service,
|
|
28454
|
+
failing_pages,
|
|
28455
|
+
perf_regressions,
|
|
28456
|
+
summary
|
|
28457
|
+
};
|
|
28435
28458
|
}
|
|
28436
28459
|
|
|
28437
28460
|
// src/lib/compare.ts
|
|
@@ -28517,48 +28540,71 @@ async function getSessionContext(db, sessionId) {
|
|
|
28517
28540
|
|
|
28518
28541
|
// src/mcp/index.ts
|
|
28519
28542
|
var db = getDb();
|
|
28520
|
-
var server = new McpServer({ name: "logs", version: "0.
|
|
28543
|
+
var server = new McpServer({ name: "logs", version: "0.3.0" });
|
|
28521
28544
|
function applyBrief(rows, brief = true) {
|
|
28522
28545
|
if (!brief)
|
|
28523
28546
|
return rows;
|
|
28524
|
-
return rows.map((r) => ({
|
|
28547
|
+
return rows.map((r) => ({
|
|
28548
|
+
id: r.id,
|
|
28549
|
+
timestamp: r.timestamp,
|
|
28550
|
+
level: r.level,
|
|
28551
|
+
message: r.message,
|
|
28552
|
+
service: r.service,
|
|
28553
|
+
age_seconds: Math.floor((Date.now() - new Date(r.timestamp).getTime()) / 1000)
|
|
28554
|
+
}));
|
|
28555
|
+
}
|
|
28556
|
+
function rp(idOrName) {
|
|
28557
|
+
if (!idOrName)
|
|
28558
|
+
return;
|
|
28559
|
+
return resolveProjectId(db, idOrName) ?? idOrName;
|
|
28525
28560
|
}
|
|
28526
28561
|
var TOOLS = {
|
|
28527
|
-
register_project: "Register a project (name, github_repo?, base_url?, description?)",
|
|
28528
|
-
register_page: "Register a page URL (project_id, url, path?, name?)",
|
|
28529
|
-
create_scan_job: "Schedule page scans (project_id, schedule, page_id?)",
|
|
28530
|
-
|
|
28531
|
-
|
|
28532
|
-
|
|
28533
|
-
|
|
28534
|
-
|
|
28535
|
-
|
|
28536
|
-
|
|
28537
|
-
|
|
28538
|
-
|
|
28539
|
-
|
|
28540
|
-
|
|
28541
|
-
|
|
28542
|
-
|
|
28543
|
-
|
|
28544
|
-
|
|
28545
|
-
|
|
28546
|
-
|
|
28547
|
-
|
|
28548
|
-
|
|
28549
|
-
|
|
28550
|
-
|
|
28562
|
+
register_project: { desc: "Register a project", params: "(name, github_repo?, base_url?, description?)" },
|
|
28563
|
+
register_page: { desc: "Register a page URL to a project", params: "(project_id, url, path?, name?)" },
|
|
28564
|
+
create_scan_job: { desc: "Schedule headless page scans", params: "(project_id, schedule, page_id?)" },
|
|
28565
|
+
resolve_project: { desc: "Resolve project name to ID", params: "(name)" },
|
|
28566
|
+
log_push: { desc: "Push a single log entry", params: "(level, message, project_id?, service?, trace_id?, metadata?)" },
|
|
28567
|
+
log_push_batch: { desc: "Push multiple log entries in one call", params: "(entries: Array<{level, message, project_id?, service?, trace_id?}>)" },
|
|
28568
|
+
log_search: { desc: "Search logs", params: "(project_id?, level?, since?, until?, text?, service?, limit?=100, brief?=true)" },
|
|
28569
|
+
log_tail: { desc: "Get N most recent logs", params: "(project_id?, n?=50, brief?=true)" },
|
|
28570
|
+
log_count: { desc: "Count logs \u2014 zero token cost, pure signal", params: "(project_id?, service?, level?, since?, until?)" },
|
|
28571
|
+
log_recent_errors: { desc: "Shortcut: recent errors + fatals", params: "(project_id?, since?='1h', limit?=20)" },
|
|
28572
|
+
log_summary: { desc: "Error/warn counts by service", params: "(project_id?, since?)" },
|
|
28573
|
+
log_context: { desc: "All logs for a trace_id", params: "(trace_id, brief?=true)" },
|
|
28574
|
+
log_context_from_id: { desc: "Trace context from a log ID (no trace_id needed)", params: "(log_id, brief?=true)" },
|
|
28575
|
+
log_diagnose: { desc: "Full diagnosis: score, top errors, failing pages, perf regressions", params: "(project_id, since?='24h', include?=['top_errors','error_rate','failing_pages','perf'])" },
|
|
28576
|
+
log_compare: { desc: "Diff two time windows for new/resolved errors", params: "(project_id, a_since, a_until, b_since, b_until)" },
|
|
28577
|
+
log_session_context: { desc: "Logs + session metadata for a session_id", params: "(session_id, brief?=true)" },
|
|
28578
|
+
perf_snapshot: { desc: "Latest performance snapshot", params: "(project_id, page_id?)" },
|
|
28579
|
+
perf_trend: { desc: "Performance over time", params: "(project_id, page_id?, since?, limit?=50)" },
|
|
28580
|
+
scan_status: { desc: "Last scan jobs", params: "(project_id?)" },
|
|
28581
|
+
list_projects: { desc: "List all projects", params: "()" },
|
|
28582
|
+
list_pages: { desc: "List pages for a project", params: "(project_id)" },
|
|
28583
|
+
list_issues: { desc: "List grouped error issues", params: "(project_id?, status?, limit?=50)" },
|
|
28584
|
+
resolve_issue: { desc: "Update issue status", params: "(id, status: open|resolved|ignored)" },
|
|
28585
|
+
create_alert_rule: { desc: "Create alert rule", params: "(project_id, name, level?, threshold_count?, window_seconds?, webhook_url?)" },
|
|
28586
|
+
list_alert_rules: { desc: "List alert rules", params: "(project_id?)" },
|
|
28587
|
+
delete_alert_rule: { desc: "Delete alert rule", params: "(id)" },
|
|
28588
|
+
get_health: { desc: "Server health + DB stats", params: "()" },
|
|
28589
|
+
search_tools: { desc: "Search tools by keyword \u2014 returns names, descriptions, param signatures", params: "(query)" },
|
|
28590
|
+
describe_tools: { desc: "List all tools with descriptions and param signatures", params: "()" }
|
|
28551
28591
|
};
|
|
28552
28592
|
server.tool("search_tools", { query: exports_external.string() }, ({ query }) => {
|
|
28553
28593
|
const q = query.toLowerCase();
|
|
28554
|
-
const matches = Object.entries(TOOLS).filter(([k, v]) => k.includes(q) || v.toLowerCase().includes(q));
|
|
28555
|
-
|
|
28556
|
-
`) || "No matches"
|
|
28594
|
+
const matches = Object.entries(TOOLS).filter(([k, v]) => k.includes(q) || v.desc.toLowerCase().includes(q));
|
|
28595
|
+
const text = matches.map(([k, v]) => `${k}${v.params} \u2014 ${v.desc}`).join(`
|
|
28596
|
+
`) || "No matches";
|
|
28597
|
+
return { content: [{ type: "text", text }] };
|
|
28557
28598
|
});
|
|
28558
28599
|
server.tool("describe_tools", {}, () => ({
|
|
28559
|
-
content: [{ type: "text", text: Object.entries(TOOLS).map(([k, v]) => `${k}
|
|
28600
|
+
content: [{ type: "text", text: Object.entries(TOOLS).map(([k, v]) => `${k}${v.params} \u2014 ${v.desc}`).join(`
|
|
28560
28601
|
`) }]
|
|
28561
28602
|
}));
|
|
28603
|
+
server.tool("resolve_project", { name: exports_external.string() }, ({ name }) => {
|
|
28604
|
+
const id = resolveProjectId(db, name);
|
|
28605
|
+
const project = id ? db.prepare("SELECT * FROM projects WHERE id = $id").get({ $id: id }) : null;
|
|
28606
|
+
return { content: [{ type: "text", text: JSON.stringify(project ?? { error: `Project '${name}' not found` }) }] };
|
|
28607
|
+
});
|
|
28562
28608
|
server.tool("register_project", {
|
|
28563
28609
|
name: exports_external.string(),
|
|
28564
28610
|
github_repo: exports_external.string().optional(),
|
|
@@ -28570,12 +28616,12 @@ server.tool("register_page", {
|
|
|
28570
28616
|
url: exports_external.string(),
|
|
28571
28617
|
path: exports_external.string().optional(),
|
|
28572
28618
|
name: exports_external.string().optional()
|
|
28573
|
-
}, (args) => ({ content: [{ type: "text", text: JSON.stringify(createPage(db, args)) }] }));
|
|
28619
|
+
}, (args) => ({ content: [{ type: "text", text: JSON.stringify(createPage(db, { ...args, project_id: rp(args.project_id) ?? args.project_id })) }] }));
|
|
28574
28620
|
server.tool("create_scan_job", {
|
|
28575
28621
|
project_id: exports_external.string(),
|
|
28576
28622
|
schedule: exports_external.string(),
|
|
28577
28623
|
page_id: exports_external.string().optional()
|
|
28578
|
-
}, (args) => ({ content: [{ type: "text", text: JSON.stringify(createJob(db, args)) }] }));
|
|
28624
|
+
}, (args) => ({ content: [{ type: "text", text: JSON.stringify(createJob(db, { ...args, project_id: rp(args.project_id) ?? args.project_id })) }] }));
|
|
28579
28625
|
server.tool("log_push", {
|
|
28580
28626
|
level: exports_external.enum(["debug", "info", "warn", "error", "fatal"]),
|
|
28581
28627
|
message: exports_external.string(),
|
|
@@ -28587,9 +28633,22 @@ server.tool("log_push", {
|
|
|
28587
28633
|
url: exports_external.string().optional(),
|
|
28588
28634
|
metadata: exports_external.record(exports_external.unknown()).optional()
|
|
28589
28635
|
}, (args) => {
|
|
28590
|
-
const row = ingestLog(db, args);
|
|
28636
|
+
const row = ingestLog(db, { ...args, project_id: rp(args.project_id) });
|
|
28591
28637
|
return { content: [{ type: "text", text: `Logged: ${row.id}` }] };
|
|
28592
28638
|
});
|
|
28639
|
+
server.tool("log_push_batch", {
|
|
28640
|
+
entries: exports_external.array(exports_external.object({
|
|
28641
|
+
level: exports_external.enum(["debug", "info", "warn", "error", "fatal"]),
|
|
28642
|
+
message: exports_external.string(),
|
|
28643
|
+
project_id: exports_external.string().optional(),
|
|
28644
|
+
service: exports_external.string().optional(),
|
|
28645
|
+
trace_id: exports_external.string().optional(),
|
|
28646
|
+
metadata: exports_external.record(exports_external.unknown()).optional()
|
|
28647
|
+
}))
|
|
28648
|
+
}, ({ entries }) => {
|
|
28649
|
+
const rows = ingestBatch(db, entries.map((e) => ({ ...e, project_id: rp(e.project_id) })));
|
|
28650
|
+
return { content: [{ type: "text", text: `Logged ${rows.length} entries` }] };
|
|
28651
|
+
});
|
|
28593
28652
|
server.tool("log_search", {
|
|
28594
28653
|
project_id: exports_external.string().optional(),
|
|
28595
28654
|
page_id: exports_external.string().optional(),
|
|
@@ -28602,7 +28661,13 @@ server.tool("log_search", {
|
|
|
28602
28661
|
limit: exports_external.number().optional(),
|
|
28603
28662
|
brief: exports_external.boolean().optional()
|
|
28604
28663
|
}, (args) => {
|
|
28605
|
-
const rows = searchLogs(db, {
|
|
28664
|
+
const rows = searchLogs(db, {
|
|
28665
|
+
...args,
|
|
28666
|
+
project_id: rp(args.project_id),
|
|
28667
|
+
level: args.level ? args.level.split(",") : undefined,
|
|
28668
|
+
since: parseTime(args.since) ?? args.since,
|
|
28669
|
+
until: parseTime(args.until) ?? args.until
|
|
28670
|
+
});
|
|
28606
28671
|
return { content: [{ type: "text", text: JSON.stringify(applyBrief(rows, args.brief !== false)) }] };
|
|
28607
28672
|
});
|
|
28608
28673
|
server.tool("log_tail", {
|
|
@@ -28610,27 +28675,55 @@ server.tool("log_tail", {
|
|
|
28610
28675
|
n: exports_external.number().optional(),
|
|
28611
28676
|
brief: exports_external.boolean().optional()
|
|
28612
28677
|
}, ({ project_id, n, brief }) => {
|
|
28613
|
-
const rows = tailLogs(db, project_id, n ?? 50);
|
|
28678
|
+
const rows = tailLogs(db, rp(project_id), n ?? 50);
|
|
28614
28679
|
return { content: [{ type: "text", text: JSON.stringify(applyBrief(rows, brief !== false)) }] };
|
|
28615
28680
|
});
|
|
28681
|
+
server.tool("log_count", {
|
|
28682
|
+
project_id: exports_external.string().optional(),
|
|
28683
|
+
service: exports_external.string().optional(),
|
|
28684
|
+
level: exports_external.string().optional(),
|
|
28685
|
+
since: exports_external.string().optional(),
|
|
28686
|
+
until: exports_external.string().optional()
|
|
28687
|
+
}, (args) => ({
|
|
28688
|
+
content: [{ type: "text", text: JSON.stringify(countLogs(db, { ...args, project_id: rp(args.project_id) })) }]
|
|
28689
|
+
}));
|
|
28690
|
+
server.tool("log_recent_errors", {
|
|
28691
|
+
project_id: exports_external.string().optional(),
|
|
28692
|
+
since: exports_external.string().optional(),
|
|
28693
|
+
limit: exports_external.number().optional()
|
|
28694
|
+
}, ({ project_id, since, limit }) => {
|
|
28695
|
+
const rows = searchLogs(db, {
|
|
28696
|
+
project_id: rp(project_id),
|
|
28697
|
+
level: ["error", "fatal"],
|
|
28698
|
+
since: parseTime(since ?? "1h"),
|
|
28699
|
+
limit: limit ?? 20
|
|
28700
|
+
});
|
|
28701
|
+
return { content: [{ type: "text", text: JSON.stringify(applyBrief(rows, true)) }] };
|
|
28702
|
+
});
|
|
28616
28703
|
server.tool("log_summary", {
|
|
28617
28704
|
project_id: exports_external.string().optional(),
|
|
28618
28705
|
since: exports_external.string().optional()
|
|
28619
28706
|
}, ({ project_id, since }) => ({
|
|
28620
|
-
content: [{ type: "text", text: JSON.stringify(summarizeLogs(db, project_id, since)) }]
|
|
28707
|
+
content: [{ type: "text", text: JSON.stringify(summarizeLogs(db, rp(project_id), parseTime(since) ?? since)) }]
|
|
28621
28708
|
}));
|
|
28622
28709
|
server.tool("log_context", {
|
|
28623
28710
|
trace_id: exports_external.string(),
|
|
28624
28711
|
brief: exports_external.boolean().optional()
|
|
28625
|
-
}, ({ trace_id, brief }) => {
|
|
28626
|
-
|
|
28627
|
-
|
|
28628
|
-
|
|
28712
|
+
}, ({ trace_id, brief }) => ({
|
|
28713
|
+
content: [{ type: "text", text: JSON.stringify(applyBrief(getLogContext(db, trace_id), brief !== false)) }]
|
|
28714
|
+
}));
|
|
28715
|
+
server.tool("log_context_from_id", {
|
|
28716
|
+
log_id: exports_external.string(),
|
|
28717
|
+
brief: exports_external.boolean().optional()
|
|
28718
|
+
}, ({ log_id, brief }) => ({
|
|
28719
|
+
content: [{ type: "text", text: JSON.stringify(applyBrief(getLogContextFromId(db, log_id), brief !== false)) }]
|
|
28720
|
+
}));
|
|
28629
28721
|
server.tool("log_diagnose", {
|
|
28630
28722
|
project_id: exports_external.string(),
|
|
28631
|
-
since: exports_external.string().optional()
|
|
28632
|
-
|
|
28633
|
-
|
|
28723
|
+
since: exports_external.string().optional(),
|
|
28724
|
+
include: exports_external.array(exports_external.enum(["top_errors", "error_rate", "failing_pages", "perf"])).optional()
|
|
28725
|
+
}, ({ project_id, since, include }) => ({
|
|
28726
|
+
content: [{ type: "text", text: JSON.stringify(diagnose(db, rp(project_id) ?? project_id, since, include)) }]
|
|
28634
28727
|
}));
|
|
28635
28728
|
server.tool("log_compare", {
|
|
28636
28729
|
project_id: exports_external.string(),
|
|
@@ -28639,13 +28732,20 @@ server.tool("log_compare", {
|
|
|
28639
28732
|
b_since: exports_external.string(),
|
|
28640
28733
|
b_until: exports_external.string()
|
|
28641
28734
|
}, ({ project_id, a_since, a_until, b_since, b_until }) => ({
|
|
28642
|
-
content: [{ type: "text", text: JSON.stringify(compare(db, project_id, a_since, a_until, b_since, b_until)) }]
|
|
28735
|
+
content: [{ type: "text", text: JSON.stringify(compare(db, rp(project_id) ?? project_id, parseTime(a_since) ?? a_since, parseTime(a_until) ?? a_until, parseTime(b_since) ?? b_since, parseTime(b_until) ?? b_until)) }]
|
|
28643
28736
|
}));
|
|
28737
|
+
server.tool("log_session_context", {
|
|
28738
|
+
session_id: exports_external.string(),
|
|
28739
|
+
brief: exports_external.boolean().optional()
|
|
28740
|
+
}, async ({ session_id, brief }) => {
|
|
28741
|
+
const ctx = await getSessionContext(db, session_id);
|
|
28742
|
+
return { content: [{ type: "text", text: JSON.stringify({ ...ctx, logs: applyBrief(ctx.logs, brief !== false) }) }] };
|
|
28743
|
+
});
|
|
28644
28744
|
server.tool("perf_snapshot", {
|
|
28645
28745
|
project_id: exports_external.string(),
|
|
28646
28746
|
page_id: exports_external.string().optional()
|
|
28647
28747
|
}, ({ project_id, page_id }) => {
|
|
28648
|
-
const snap = getLatestSnapshot(db, project_id, page_id);
|
|
28748
|
+
const snap = getLatestSnapshot(db, rp(project_id) ?? project_id, page_id);
|
|
28649
28749
|
return { content: [{ type: "text", text: JSON.stringify(snap ? { ...snap, label: scoreLabel(snap.score) } : null) }] };
|
|
28650
28750
|
});
|
|
28651
28751
|
server.tool("perf_trend", {
|
|
@@ -28654,25 +28754,25 @@ server.tool("perf_trend", {
|
|
|
28654
28754
|
since: exports_external.string().optional(),
|
|
28655
28755
|
limit: exports_external.number().optional()
|
|
28656
28756
|
}, ({ project_id, page_id, since, limit }) => ({
|
|
28657
|
-
content: [{ type: "text", text: JSON.stringify(getPerfTrend(db, project_id, page_id, since, limit ?? 50)) }]
|
|
28757
|
+
content: [{ type: "text", text: JSON.stringify(getPerfTrend(db, rp(project_id) ?? project_id, page_id, parseTime(since) ?? since, limit ?? 50)) }]
|
|
28658
28758
|
}));
|
|
28659
28759
|
server.tool("scan_status", {
|
|
28660
28760
|
project_id: exports_external.string().optional()
|
|
28661
28761
|
}, ({ project_id }) => ({
|
|
28662
|
-
content: [{ type: "text", text: JSON.stringify(listJobs(db, project_id)) }]
|
|
28762
|
+
content: [{ type: "text", text: JSON.stringify(listJobs(db, rp(project_id))) }]
|
|
28663
28763
|
}));
|
|
28664
28764
|
server.tool("list_projects", {}, () => ({
|
|
28665
28765
|
content: [{ type: "text", text: JSON.stringify(listProjects(db)) }]
|
|
28666
28766
|
}));
|
|
28667
28767
|
server.tool("list_pages", { project_id: exports_external.string() }, ({ project_id }) => ({
|
|
28668
|
-
content: [{ type: "text", text: JSON.stringify(listPages(db, project_id)) }]
|
|
28768
|
+
content: [{ type: "text", text: JSON.stringify(listPages(db, rp(project_id) ?? project_id)) }]
|
|
28669
28769
|
}));
|
|
28670
28770
|
server.tool("list_issues", {
|
|
28671
28771
|
project_id: exports_external.string().optional(),
|
|
28672
28772
|
status: exports_external.string().optional(),
|
|
28673
28773
|
limit: exports_external.number().optional()
|
|
28674
28774
|
}, ({ project_id, status, limit }) => ({
|
|
28675
|
-
content: [{ type: "text", text: JSON.stringify(listIssues(db, project_id, status, limit ?? 50)) }]
|
|
28775
|
+
content: [{ type: "text", text: JSON.stringify(listIssues(db, rp(project_id), status, limit ?? 50)) }]
|
|
28676
28776
|
}));
|
|
28677
28777
|
server.tool("resolve_issue", {
|
|
28678
28778
|
id: exports_external.string(),
|
|
@@ -28689,23 +28789,16 @@ server.tool("create_alert_rule", {
|
|
|
28689
28789
|
window_seconds: exports_external.number().optional(),
|
|
28690
28790
|
action: exports_external.enum(["webhook", "log"]).optional(),
|
|
28691
28791
|
webhook_url: exports_external.string().optional()
|
|
28692
|
-
}, (args) => ({ content: [{ type: "text", text: JSON.stringify(createAlertRule(db, args)) }] }));
|
|
28792
|
+
}, (args) => ({ content: [{ type: "text", text: JSON.stringify(createAlertRule(db, { ...args, project_id: rp(args.project_id) ?? args.project_id })) }] }));
|
|
28693
28793
|
server.tool("list_alert_rules", {
|
|
28694
28794
|
project_id: exports_external.string().optional()
|
|
28695
28795
|
}, ({ project_id }) => ({
|
|
28696
|
-
content: [{ type: "text", text: JSON.stringify(listAlertRules(db, project_id)) }]
|
|
28796
|
+
content: [{ type: "text", text: JSON.stringify(listAlertRules(db, rp(project_id))) }]
|
|
28697
28797
|
}));
|
|
28698
28798
|
server.tool("delete_alert_rule", { id: exports_external.string() }, ({ id }) => {
|
|
28699
28799
|
deleteAlertRule(db, id);
|
|
28700
28800
|
return { content: [{ type: "text", text: "deleted" }] };
|
|
28701
28801
|
});
|
|
28702
|
-
server.tool("log_session_context", {
|
|
28703
|
-
session_id: exports_external.string(),
|
|
28704
|
-
brief: exports_external.boolean().optional()
|
|
28705
|
-
}, async ({ session_id, brief }) => {
|
|
28706
|
-
const ctx = await getSessionContext(db, session_id);
|
|
28707
|
-
return { content: [{ type: "text", text: JSON.stringify({ ...ctx, logs: applyBrief(ctx.logs, brief !== false) }) }] };
|
|
28708
|
-
});
|
|
28709
28802
|
server.tool("get_health", {}, () => ({
|
|
28710
28803
|
content: [{ type: "text", text: JSON.stringify(getHealth(db)) }]
|
|
28711
28804
|
}));
|
package/dist/server/index.js
CHANGED
|
@@ -6,7 +6,10 @@ import {
|
|
|
6
6
|
setPageAuth,
|
|
7
7
|
setRetentionPolicy,
|
|
8
8
|
startScheduler
|
|
9
|
-
} from "../index-
|
|
9
|
+
} from "../index-wbsq8qjd.js";
|
|
10
|
+
import {
|
|
11
|
+
countLogs
|
|
12
|
+
} from "../index-6y8pmes4.js";
|
|
10
13
|
import {
|
|
11
14
|
createAlertRule,
|
|
12
15
|
createPage,
|
|
@@ -23,29 +26,31 @@ import {
|
|
|
23
26
|
listIssues,
|
|
24
27
|
listPages,
|
|
25
28
|
listProjects,
|
|
29
|
+
resolveProjectId,
|
|
26
30
|
summarizeLogs,
|
|
27
31
|
updateAlertRule,
|
|
28
32
|
updateIssueStatus,
|
|
29
33
|
updateProject
|
|
30
|
-
} from "../index-
|
|
34
|
+
} from "../index-5tvnhvgr.js";
|
|
31
35
|
import {
|
|
32
36
|
createJob,
|
|
33
37
|
deleteJob,
|
|
34
38
|
listJobs,
|
|
35
39
|
updateJob
|
|
36
|
-
} from "../
|
|
40
|
+
} from "../index-3dr7d80h.js";
|
|
37
41
|
import {
|
|
38
42
|
getLogContext,
|
|
43
|
+
parseTime,
|
|
39
44
|
searchLogs,
|
|
40
45
|
tailLogs
|
|
41
|
-
} from "../
|
|
46
|
+
} from "../index-rbrsvsyh.js";
|
|
42
47
|
import {
|
|
43
48
|
exportToCsv,
|
|
44
49
|
exportToJson
|
|
45
|
-
} from "../
|
|
50
|
+
} from "../index-eh9bkbpa.js";
|
|
46
51
|
import {
|
|
47
52
|
getHealth
|
|
48
|
-
} from "../
|
|
53
|
+
} from "../index-yb8yd4j6.js";
|
|
49
54
|
import"../index-g8dczzvv.js";
|
|
50
55
|
|
|
51
56
|
// node_modules/hono/dist/compose.js
|
|
@@ -1835,7 +1840,7 @@ var serveStatic = (options) => {
|
|
|
1835
1840
|
|
|
1836
1841
|
// node_modules/hono/dist/adapter/bun/serve-static.js
|
|
1837
1842
|
var serveStatic2 = (options) => {
|
|
1838
|
-
return async function
|
|
1843
|
+
return async function serveStatic22(c, next) {
|
|
1839
1844
|
const getContent = async (path) => {
|
|
1840
1845
|
const file = Bun.file(path);
|
|
1841
1846
|
return await file.exists() ? file : null;
|
|
@@ -2097,9 +2102,29 @@ function logsRoutes(db) {
|
|
|
2097
2102
|
});
|
|
2098
2103
|
app.get("/summary", (c) => {
|
|
2099
2104
|
const { project_id, since } = c.req.query();
|
|
2100
|
-
const summary = summarizeLogs(db, project_id || undefined, since || undefined);
|
|
2105
|
+
const summary = summarizeLogs(db, resolveProjectId(db, project_id) || undefined, parseTime(since) || since || undefined);
|
|
2101
2106
|
return c.json(summary);
|
|
2102
2107
|
});
|
|
2108
|
+
app.get("/count", (c) => {
|
|
2109
|
+
const { project_id, service, level, since, until } = c.req.query();
|
|
2110
|
+
return c.json(countLogs(db, {
|
|
2111
|
+
project_id: resolveProjectId(db, project_id) || undefined,
|
|
2112
|
+
service: service || undefined,
|
|
2113
|
+
level: level || undefined,
|
|
2114
|
+
since: since || undefined,
|
|
2115
|
+
until: until || undefined
|
|
2116
|
+
}));
|
|
2117
|
+
});
|
|
2118
|
+
app.get("/recent-errors", (c) => {
|
|
2119
|
+
const { project_id, since, limit } = c.req.query();
|
|
2120
|
+
const rows = searchLogs(db, {
|
|
2121
|
+
project_id: resolveProjectId(db, project_id) || undefined,
|
|
2122
|
+
level: ["error", "fatal"],
|
|
2123
|
+
since: parseTime(since || "1h"),
|
|
2124
|
+
limit: limit ? Number(limit) : 20
|
|
2125
|
+
});
|
|
2126
|
+
return c.json(rows.map((r) => ({ id: r.id, timestamp: r.timestamp, level: r.level, message: r.message, service: r.service, age_seconds: Math.floor((Date.now() - new Date(r.timestamp).getTime()) / 1000) })));
|
|
2127
|
+
});
|
|
2103
2128
|
app.get("/:trace_id/context", (c) => {
|
|
2104
2129
|
const rows = getLogContext(db, c.req.param("trace_id"));
|
|
2105
2130
|
return c.json(rows);
|
package/package.json
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test"
|
|
2
|
+
import { createTestDb } from "../db/index.ts"
|
|
3
|
+
import { ingestBatch } from "./ingest.ts"
|
|
4
|
+
import { countLogs } from "./count.ts"
|
|
5
|
+
|
|
6
|
+
describe("countLogs", () => {
|
|
7
|
+
it("counts all logs", () => {
|
|
8
|
+
const db = createTestDb()
|
|
9
|
+
ingestBatch(db, [{ level: "error", message: "e" }, { level: "warn", message: "w" }, { level: "info", message: "i" }])
|
|
10
|
+
const c = countLogs(db, {})
|
|
11
|
+
expect(c.total).toBe(3)
|
|
12
|
+
expect(c.errors).toBe(1)
|
|
13
|
+
expect(c.warns).toBe(1)
|
|
14
|
+
expect(c.fatals).toBe(0)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it("filters by project", () => {
|
|
18
|
+
const db = createTestDb()
|
|
19
|
+
const p = db.prepare("INSERT INTO projects (name) VALUES ('app') RETURNING id").get() as { id: string }
|
|
20
|
+
ingestBatch(db, [{ level: "error", message: "e", project_id: p.id }, { level: "error", message: "e2" }])
|
|
21
|
+
const c = countLogs(db, { project_id: p.id })
|
|
22
|
+
expect(c.total).toBe(1)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it("filters by service", () => {
|
|
26
|
+
const db = createTestDb()
|
|
27
|
+
ingestBatch(db, [{ level: "error", message: "e", service: "api" }, { level: "error", message: "e2", service: "db" }])
|
|
28
|
+
expect(countLogs(db, { service: "api" }).total).toBe(1)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it("returns zero counts for empty db", () => {
|
|
32
|
+
const c = countLogs(createTestDb(), {})
|
|
33
|
+
expect(c.total).toBe(0)
|
|
34
|
+
expect(c.errors).toBe(0)
|
|
35
|
+
expect(c.by_level).toEqual({})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it("accepts relative since", () => {
|
|
39
|
+
const db = createTestDb()
|
|
40
|
+
ingestBatch(db, [{ level: "error", message: "recent" }])
|
|
41
|
+
const c = countLogs(db, { since: "1h" })
|
|
42
|
+
expect(c.total).toBe(1)
|
|
43
|
+
})
|
|
44
|
+
})
|