@hasna/logs 0.3.7 → 0.3.9
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/cli/index.js +25 -0
- package/dist/count-x3n7qg3c.js +9 -0
- package/dist/index-edn08m6f.js +51 -0
- package/dist/mcp/index.js +38 -5
- package/dist/server/index.js +5 -5
- package/package.json +1 -1
- package/src/cli/index.ts +33 -0
- package/src/lib/count.ts +10 -0
- package/src/mcp/index.ts +26 -0
package/dist/cli/index.js
CHANGED
|
@@ -2379,6 +2379,31 @@ Errors: ${errorCount} Warnings: ${warnCount}`);
|
|
|
2379
2379
|
process.exit(0);
|
|
2380
2380
|
});
|
|
2381
2381
|
});
|
|
2382
|
+
program2.command("count").description("Count logs with optional breakdown by level or service").option("--project <name|id>", "Project name or ID").option("--service <name>", "Filter by service").option("--level <level>", "Filter by level").option("--since <time>", "Since (1h, 24h, 7d)").option("--until <time>", "Until").option("--group-by <field>", "Breakdown: level | service").action(async (opts) => {
|
|
2383
|
+
const { countLogs } = await import("../count-x3n7qg3c.js");
|
|
2384
|
+
const result = countLogs(getDb(), {
|
|
2385
|
+
project_id: resolveProject(opts.project),
|
|
2386
|
+
service: opts.service,
|
|
2387
|
+
level: opts.level,
|
|
2388
|
+
since: opts.since,
|
|
2389
|
+
until: opts.until,
|
|
2390
|
+
group_by: opts.groupBy
|
|
2391
|
+
});
|
|
2392
|
+
console.log(`Total: ${result.total} ${C.red}Errors: ${result.errors}${C.reset} ${C.yellow}Warns: ${result.warns}${C.reset} Fatals: ${result.fatals}`);
|
|
2393
|
+
if (result.by_service) {
|
|
2394
|
+
console.log(`
|
|
2395
|
+
By Service:`);
|
|
2396
|
+
for (const [svc, cnt] of Object.entries(result.by_service)) {
|
|
2397
|
+
console.log(` ${C.cyan}${pad(svc, 20)}${C.reset} ${cnt}`);
|
|
2398
|
+
}
|
|
2399
|
+
} else if (opts.groupBy === "level") {
|
|
2400
|
+
console.log(`
|
|
2401
|
+
By Level:`);
|
|
2402
|
+
for (const [lvl, cnt] of Object.entries(result.by_level)) {
|
|
2403
|
+
console.log(` ${colorLevel(lvl)} ${cnt}`);
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
});
|
|
2382
2407
|
program2.command("export").description("Export logs to JSON or CSV").option("--project <name|id>", "Project name or ID").option("--since <time>", "Relative time or ISO").option("--level <level>").option("--service <name>").option("--format <fmt>", "json or csv", "json").option("--output <file>", "Output file (default: stdout)").option("--limit <n>", "Max rows", "100000").action(async (opts) => {
|
|
2383
2408
|
const { exportToCsv, exportToJson } = await import("../export-c3eqjste.js");
|
|
2384
2409
|
const { createWriteStream } = await import("fs");
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
parseTime
|
|
4
|
+
} from "./index-997bkzr2.js";
|
|
5
|
+
|
|
6
|
+
// src/lib/count.ts
|
|
7
|
+
function countLogs(db, opts) {
|
|
8
|
+
const conditions = [];
|
|
9
|
+
const params = {};
|
|
10
|
+
if (opts.project_id) {
|
|
11
|
+
conditions.push("project_id = $p");
|
|
12
|
+
params.$p = opts.project_id;
|
|
13
|
+
}
|
|
14
|
+
if (opts.service) {
|
|
15
|
+
conditions.push("service = $service");
|
|
16
|
+
params.$service = opts.service;
|
|
17
|
+
}
|
|
18
|
+
if (opts.level) {
|
|
19
|
+
conditions.push("level = $level");
|
|
20
|
+
params.$level = opts.level;
|
|
21
|
+
}
|
|
22
|
+
const since = parseTime(opts.since);
|
|
23
|
+
const until = parseTime(opts.until);
|
|
24
|
+
if (since) {
|
|
25
|
+
conditions.push("timestamp >= $since");
|
|
26
|
+
params.$since = since;
|
|
27
|
+
}
|
|
28
|
+
if (until) {
|
|
29
|
+
conditions.push("timestamp <= $until");
|
|
30
|
+
params.$until = until;
|
|
31
|
+
}
|
|
32
|
+
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
33
|
+
const byLevel = db.prepare(`SELECT level, COUNT(*) as c FROM logs ${where} GROUP BY level`).all(params);
|
|
34
|
+
const by_level = Object.fromEntries(byLevel.map((r) => [r.level, r.c]));
|
|
35
|
+
const total = byLevel.reduce((s, r) => s + r.c, 0);
|
|
36
|
+
let by_service;
|
|
37
|
+
if (opts.group_by === "service") {
|
|
38
|
+
const bySvc = db.prepare(`SELECT COALESCE(service, '-') as service, COUNT(*) as c FROM logs ${where} GROUP BY service ORDER BY c DESC`).all(params);
|
|
39
|
+
by_service = Object.fromEntries(bySvc.map((r) => [r.service, r.c]));
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
total,
|
|
43
|
+
errors: by_level["error"] ?? 0,
|
|
44
|
+
warns: by_level["warn"] ?? 0,
|
|
45
|
+
fatals: by_level["fatal"] ?? 0,
|
|
46
|
+
by_level,
|
|
47
|
+
by_service
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { countLogs };
|
package/dist/mcp/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
} from "../index-
|
|
4
|
+
getHealth
|
|
5
|
+
} from "../index-xjn8gam3.js";
|
|
6
6
|
import {
|
|
7
7
|
createAlertRule,
|
|
8
8
|
createPage,
|
|
@@ -35,12 +35,16 @@ import {
|
|
|
35
35
|
searchLogs,
|
|
36
36
|
tailLogs
|
|
37
37
|
} from "../index-exeq2gs6.js";
|
|
38
|
+
import {
|
|
39
|
+
countLogs
|
|
40
|
+
} from "../index-edn08m6f.js";
|
|
38
41
|
import {
|
|
39
42
|
parseTime
|
|
40
43
|
} from "../index-997bkzr2.js";
|
|
41
44
|
import {
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
exportToCsv,
|
|
46
|
+
exportToJson
|
|
47
|
+
} from "../index-eh9bkbpa.js";
|
|
44
48
|
import {
|
|
45
49
|
__commonJS,
|
|
46
50
|
__export,
|
|
@@ -28618,7 +28622,8 @@ server.tool("log_count", {
|
|
|
28618
28622
|
service: exports_external.string().optional(),
|
|
28619
28623
|
level: exports_external.string().optional(),
|
|
28620
28624
|
since: exports_external.string().optional(),
|
|
28621
|
-
until: exports_external.string().optional()
|
|
28625
|
+
until: exports_external.string().optional(),
|
|
28626
|
+
group_by: exports_external.enum(["level", "service"]).optional().describe("Return breakdown by 'level' or 'service' in addition to totals")
|
|
28622
28627
|
}, (args) => ({
|
|
28623
28628
|
content: [{ type: "text", text: JSON.stringify(countLogs(db, { ...args, project_id: rp(args.project_id) })) }]
|
|
28624
28629
|
}));
|
|
@@ -28653,6 +28658,34 @@ server.tool("log_context_from_id", {
|
|
|
28653
28658
|
}, ({ log_id, brief }) => ({
|
|
28654
28659
|
content: [{ type: "text", text: JSON.stringify(applyBrief(getLogContextFromId(db, log_id), brief !== false)) }]
|
|
28655
28660
|
}));
|
|
28661
|
+
server.tool("log_export", {
|
|
28662
|
+
project_id: exports_external.string().optional().describe("Project name or ID"),
|
|
28663
|
+
format: exports_external.enum(["json", "csv"]).optional().default("json").describe("Output format"),
|
|
28664
|
+
since: exports_external.string().optional().describe("Since time (1h, 24h, 7d, ISO)"),
|
|
28665
|
+
until: exports_external.string().optional(),
|
|
28666
|
+
level: exports_external.array(exports_external.string()).optional().describe("Filter by levels"),
|
|
28667
|
+
service: exports_external.string().optional(),
|
|
28668
|
+
limit: exports_external.number().optional().default(1e5)
|
|
28669
|
+
}, (args) => {
|
|
28670
|
+
const chunks = [];
|
|
28671
|
+
const write = (s) => {
|
|
28672
|
+
chunks.push(s);
|
|
28673
|
+
return true;
|
|
28674
|
+
};
|
|
28675
|
+
const options = {
|
|
28676
|
+
project_id: rp(args.project_id),
|
|
28677
|
+
level: args.level,
|
|
28678
|
+
service: args.service,
|
|
28679
|
+
since: args.since,
|
|
28680
|
+
until: args.until,
|
|
28681
|
+
limit: args.limit ?? 1e5
|
|
28682
|
+
};
|
|
28683
|
+
if (args.format === "csv")
|
|
28684
|
+
exportToCsv(db, options, write);
|
|
28685
|
+
else
|
|
28686
|
+
exportToJson(db, options, write);
|
|
28687
|
+
return { content: [{ type: "text", text: chunks.join("") }] };
|
|
28688
|
+
});
|
|
28656
28689
|
server.tool("log_diagnose", {
|
|
28657
28690
|
project_id: exports_external.string(),
|
|
28658
28691
|
since: exports_external.string().optional(),
|
package/dist/server/index.js
CHANGED
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
startScheduler
|
|
9
9
|
} from "../index-7qhh666n.js";
|
|
10
10
|
import {
|
|
11
|
-
|
|
12
|
-
} from "../index-
|
|
11
|
+
getHealth
|
|
12
|
+
} from "../index-xjn8gam3.js";
|
|
13
13
|
import {
|
|
14
14
|
createAlertRule,
|
|
15
15
|
createPage,
|
|
@@ -43,6 +43,9 @@ import {
|
|
|
43
43
|
searchLogs,
|
|
44
44
|
tailLogs
|
|
45
45
|
} from "../index-exeq2gs6.js";
|
|
46
|
+
import {
|
|
47
|
+
countLogs
|
|
48
|
+
} from "../index-edn08m6f.js";
|
|
46
49
|
import {
|
|
47
50
|
parseTime
|
|
48
51
|
} from "../index-997bkzr2.js";
|
|
@@ -50,9 +53,6 @@ import {
|
|
|
50
53
|
exportToCsv,
|
|
51
54
|
exportToJson
|
|
52
55
|
} from "../index-eh9bkbpa.js";
|
|
53
|
-
import {
|
|
54
|
-
getHealth
|
|
55
|
-
} from "../index-xjn8gam3.js";
|
|
56
56
|
import"../index-re3ntm60.js";
|
|
57
57
|
|
|
58
58
|
// node_modules/hono/dist/compose.js
|
package/package.json
CHANGED
package/src/cli/index.ts
CHANGED
|
@@ -278,6 +278,39 @@ program.command("watch")
|
|
|
278
278
|
process.on("SIGINT", () => { clearInterval(interval); console.log(`\n\nErrors: ${errorCount} Warnings: ${warnCount}`); process.exit(0) })
|
|
279
279
|
})
|
|
280
280
|
|
|
281
|
+
// ── logs count ────────────────────────────────────────────
|
|
282
|
+
program.command("count")
|
|
283
|
+
.description("Count logs with optional breakdown by level or service")
|
|
284
|
+
.option("--project <name|id>", "Project name or ID")
|
|
285
|
+
.option("--service <name>", "Filter by service")
|
|
286
|
+
.option("--level <level>", "Filter by level")
|
|
287
|
+
.option("--since <time>", "Since (1h, 24h, 7d)")
|
|
288
|
+
.option("--until <time>", "Until")
|
|
289
|
+
.option("--group-by <field>", "Breakdown: level | service")
|
|
290
|
+
.action(async (opts) => {
|
|
291
|
+
const { countLogs } = await import("../lib/count.ts")
|
|
292
|
+
const result = countLogs(getDb(), {
|
|
293
|
+
project_id: resolveProject(opts.project),
|
|
294
|
+
service: opts.service,
|
|
295
|
+
level: opts.level,
|
|
296
|
+
since: opts.since,
|
|
297
|
+
until: opts.until,
|
|
298
|
+
group_by: opts.groupBy as "level" | "service" | undefined,
|
|
299
|
+
})
|
|
300
|
+
console.log(`Total: ${result.total} ${C.red}Errors: ${result.errors}${C.reset} ${C.yellow}Warns: ${result.warns}${C.reset} Fatals: ${result.fatals}`)
|
|
301
|
+
if (result.by_service) {
|
|
302
|
+
console.log(`\nBy Service:`)
|
|
303
|
+
for (const [svc, cnt] of Object.entries(result.by_service)) {
|
|
304
|
+
console.log(` ${C.cyan}${pad(svc, 20)}${C.reset} ${cnt}`)
|
|
305
|
+
}
|
|
306
|
+
} else if (opts.groupBy === "level") {
|
|
307
|
+
console.log(`\nBy Level:`)
|
|
308
|
+
for (const [lvl, cnt] of Object.entries(result.by_level)) {
|
|
309
|
+
console.log(` ${colorLevel(lvl)} ${cnt}`)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
|
|
281
314
|
// ── logs export ───────────────────────────────────────────
|
|
282
315
|
program.command("export")
|
|
283
316
|
.description("Export logs to JSON or CSV")
|
package/src/lib/count.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface LogCount {
|
|
|
7
7
|
warns: number
|
|
8
8
|
fatals: number
|
|
9
9
|
by_level: Record<string, number>
|
|
10
|
+
by_service?: Record<string, number>
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export function countLogs(db: Database, opts: {
|
|
@@ -15,6 +16,7 @@ export function countLogs(db: Database, opts: {
|
|
|
15
16
|
level?: string
|
|
16
17
|
since?: string
|
|
17
18
|
until?: string
|
|
19
|
+
group_by?: "level" | "service"
|
|
18
20
|
}): LogCount {
|
|
19
21
|
const conditions: string[] = []
|
|
20
22
|
const params: Record<string, unknown> = {}
|
|
@@ -35,11 +37,19 @@ export function countLogs(db: Database, opts: {
|
|
|
35
37
|
const by_level = Object.fromEntries(byLevel.map(r => [r.level, r.c]))
|
|
36
38
|
const total = byLevel.reduce((s, r) => s + r.c, 0)
|
|
37
39
|
|
|
40
|
+
let by_service: Record<string, number> | undefined
|
|
41
|
+
if (opts.group_by === "service") {
|
|
42
|
+
const bySvc = db.prepare(`SELECT COALESCE(service, '-') as service, COUNT(*) as c FROM logs ${where} GROUP BY service ORDER BY c DESC`)
|
|
43
|
+
.all(params) as { service: string; c: number }[]
|
|
44
|
+
by_service = Object.fromEntries(bySvc.map(r => [r.service, r.c]))
|
|
45
|
+
}
|
|
46
|
+
|
|
38
47
|
return {
|
|
39
48
|
total,
|
|
40
49
|
errors: by_level["error"] ?? 0,
|
|
41
50
|
warns: by_level["warn"] ?? 0,
|
|
42
51
|
fatals: by_level["fatal"] ?? 0,
|
|
43
52
|
by_level,
|
|
53
|
+
by_service,
|
|
44
54
|
}
|
|
45
55
|
}
|
package/src/mcp/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { getLatestSnapshot, getPerfTrend, scoreLabel } from "../lib/perf.ts"
|
|
|
13
13
|
import { createAlertRule, deleteAlertRule, listAlertRules } from "../lib/alerts.ts"
|
|
14
14
|
import { listIssues, updateIssueStatus } from "../lib/issues.ts"
|
|
15
15
|
import { diagnose } from "../lib/diagnose.ts"
|
|
16
|
+
import { exportToJson, exportToCsv } from "../lib/export.ts"
|
|
16
17
|
import { compare } from "../lib/compare.ts"
|
|
17
18
|
import { getHealth } from "../lib/health.ts"
|
|
18
19
|
import { getSessionContext } from "../lib/session-context.ts"
|
|
@@ -154,6 +155,7 @@ server.tool("log_tail", {
|
|
|
154
155
|
server.tool("log_count", {
|
|
155
156
|
project_id: z.string().optional(), service: z.string().optional(),
|
|
156
157
|
level: z.string().optional(), since: z.string().optional(), until: z.string().optional(),
|
|
158
|
+
group_by: z.enum(["level", "service"]).optional().describe("Return breakdown by 'level' or 'service' in addition to totals"),
|
|
157
159
|
}, (args) => ({
|
|
158
160
|
content: [{ type: "text", text: JSON.stringify(countLogs(db, { ...args, project_id: rp(args.project_id) })) }]
|
|
159
161
|
}))
|
|
@@ -188,6 +190,30 @@ server.tool("log_context_from_id", {
|
|
|
188
190
|
content: [{ type: "text", text: JSON.stringify(applyBrief(getLogContextFromId(db, log_id), brief !== false)) }]
|
|
189
191
|
}))
|
|
190
192
|
|
|
193
|
+
server.tool("log_export", {
|
|
194
|
+
project_id: z.string().optional().describe("Project name or ID"),
|
|
195
|
+
format: z.enum(["json", "csv"]).optional().default("json").describe("Output format"),
|
|
196
|
+
since: z.string().optional().describe("Since time (1h, 24h, 7d, ISO)"),
|
|
197
|
+
until: z.string().optional(),
|
|
198
|
+
level: z.array(z.string()).optional().describe("Filter by levels"),
|
|
199
|
+
service: z.string().optional(),
|
|
200
|
+
limit: z.number().optional().default(100000),
|
|
201
|
+
}, (args) => {
|
|
202
|
+
const chunks: string[] = []
|
|
203
|
+
const write = (s: string) => { chunks.push(s); return true }
|
|
204
|
+
const options = {
|
|
205
|
+
project_id: rp(args.project_id),
|
|
206
|
+
level: args.level as never,
|
|
207
|
+
service: args.service,
|
|
208
|
+
since: args.since,
|
|
209
|
+
until: args.until,
|
|
210
|
+
limit: args.limit ?? 100000,
|
|
211
|
+
}
|
|
212
|
+
if (args.format === "csv") exportToCsv(db, options, write)
|
|
213
|
+
else exportToJson(db, options, write)
|
|
214
|
+
return { content: [{ type: "text" as const, text: chunks.join("") }] }
|
|
215
|
+
})
|
|
216
|
+
|
|
191
217
|
server.tool("log_diagnose", {
|
|
192
218
|
project_id: z.string(),
|
|
193
219
|
since: z.string().optional(),
|