@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 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,9 @@
1
+ // @bun
2
+ import {
3
+ countLogs
4
+ } from "./index-edn08m6f.js";
5
+ import"./index-997bkzr2.js";
6
+ import"./index-re3ntm60.js";
7
+ export {
8
+ countLogs
9
+ };
@@ -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
- countLogs
5
- } from "../index-14dvwcf1.js";
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
  }));
@@ -8,8 +8,8 @@ import {
8
8
  startScheduler
9
9
  } from "../index-7qhh666n.js";
10
10
  import {
11
- countLogs
12
- } from "../index-14dvwcf1.js";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/logs",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "Log aggregation + browser script + headless page scanner + performance monitoring for AI agents",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
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
  }))