@hasna/logs 0.3.7 → 0.3.8
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 +7 -6
- 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 +1 -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,12 @@ 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
|
-
import {
|
|
42
|
-
getHealth
|
|
43
|
-
} from "../index-xjn8gam3.js";
|
|
44
44
|
import {
|
|
45
45
|
__commonJS,
|
|
46
46
|
__export,
|
|
@@ -28618,7 +28618,8 @@ server.tool("log_count", {
|
|
|
28618
28618
|
service: exports_external.string().optional(),
|
|
28619
28619
|
level: exports_external.string().optional(),
|
|
28620
28620
|
since: exports_external.string().optional(),
|
|
28621
|
-
until: exports_external.string().optional()
|
|
28621
|
+
until: exports_external.string().optional(),
|
|
28622
|
+
group_by: exports_external.enum(["level", "service"]).optional().describe("Return breakdown by 'level' or 'service' in addition to totals")
|
|
28622
28623
|
}, (args) => ({
|
|
28623
28624
|
content: [{ type: "text", text: JSON.stringify(countLogs(db, { ...args, project_id: rp(args.project_id) })) }]
|
|
28624
28625
|
}));
|
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
|
@@ -154,6 +154,7 @@ server.tool("log_tail", {
|
|
|
154
154
|
server.tool("log_count", {
|
|
155
155
|
project_id: z.string().optional(), service: z.string().optional(),
|
|
156
156
|
level: z.string().optional(), since: z.string().optional(), until: z.string().optional(),
|
|
157
|
+
group_by: z.enum(["level", "service"]).optional().describe("Return breakdown by 'level' or 'service' in addition to totals"),
|
|
157
158
|
}, (args) => ({
|
|
158
159
|
content: [{ type: "text", text: JSON.stringify(countLogs(db, { ...args, project_id: rp(args.project_id) })) }]
|
|
159
160
|
}))
|