@hasna/logs 0.3.23 → 0.3.24
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/LICENSE +1 -2
- package/README.md +3 -22
- package/bun.lock +7 -14
- package/dist/cli/index.js +2 -2
- package/dist/{index-t97ttm0a.js → index-75dwghvv.js} +88 -6
- package/dist/{index-1f2ghyhm.js → index-g8f8kep6.js} +93 -8
- package/dist/{index-zmayq5kj.js → index-pf8hpweg.js} +2 -2
- package/dist/{index-2sbhn1ye.js → index-w24zm361.js} +2 -2
- package/dist/mcp/index.js +23801 -8266
- package/dist/server/index.js +2 -2
- package/package.json +4 -3
- package/sdk/package.json +4 -9
- package/src/cli/entrypoints.test.ts +1 -1
- package/src/db/index.ts +1 -1
- package/src/lib/cloud-sync.ts +167 -0
- package/src/lib/ingest.ts +3 -3
- package/src/lib/remote-storage.ts +45 -0
- package/src/mcp/index.ts +30 -34
- package/dist/export-yjaar93b.js +0 -10
- package/dist/health-9792c1rc.js +0 -8
- package/dist/health-egdb00st.js +0 -8
- package/dist/http-0wsh40x1.js +0 -1240
- package/dist/index-14dvwcf1.js +0 -45
- package/dist/index-4ba0sabv.js +0 -1241
- package/dist/index-4hj4sakk.js +0 -1241
- package/dist/index-5cj74qka.js +0 -10803
- package/dist/index-5qwba140.js +0 -1241
- package/dist/index-5tvnhvgr.js +0 -536
- package/dist/index-6y8pmes4.js +0 -45
- package/dist/index-6zrkek5y.js +0 -9454
- package/dist/index-7qhh666n.js +0 -1241
- package/dist/index-86j0hn03.js +0 -540
- package/dist/index-exeq2gs6.js +0 -79
- package/dist/index-fzmz9aqs.js +0 -1241
- package/dist/index-g8dczzvv.js +0 -30
- package/dist/index-hjzbctgt.js +0 -5868
- package/dist/index-rbrsvsyh.js +0 -88
- package/dist/index-vmr85wa1.js +0 -9579
- package/dist/index-wbsq8qjd.js +0 -1241
- package/dist/index-xjn8gam3.js +0 -39
- package/dist/index-yb8yd4j6.js +0 -39
- package/dist/jobs-02z4fzsn.js +0 -22
- package/dist/query-6s5gqkck.js +0 -15
- package/dist/query-shjjj67k.js +0 -14
- package/dist/query-tcg3bm9s.js +0 -14
- package/src/mcp/http.test.ts +0 -92
- package/src/mcp/http.ts +0 -135
package/LICENSE
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
Apache License
|
|
3
2
|
Version 2.0, January 2004
|
|
4
3
|
http://www.apache.org/licenses/
|
|
@@ -176,7 +175,7 @@
|
|
|
176
175
|
|
|
177
176
|
END OF TERMS AND CONDITIONS
|
|
178
177
|
|
|
179
|
-
Copyright 2026
|
|
178
|
+
Copyright 2026 hasna
|
|
180
179
|
|
|
181
180
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
182
181
|
you may not use this file except in compliance with the License.
|
package/README.md
CHANGED
|
@@ -32,36 +32,17 @@ logs-mcp
|
|
|
32
32
|
|
|
33
33
|
6 tools available.
|
|
34
34
|
|
|
35
|
-
## HTTP mode
|
|
36
|
-
|
|
37
|
-
Run a shared Streamable HTTP MCP server (127.0.0.1 only):
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
logs-mcp --http # default port 8820
|
|
41
|
-
logs-mcp --http --port 8820
|
|
42
|
-
MCP_HTTP=1 logs-mcp
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
- Health: `GET http://127.0.0.1:8820/health`
|
|
46
|
-
- MCP: `POST http://127.0.0.1:8820/mcp`
|
|
47
|
-
|
|
48
|
-
Stdio remains the default when no `--http` flag is passed.
|
|
49
|
-
|
|
50
35
|
## REST API
|
|
51
36
|
|
|
52
37
|
```bash
|
|
53
38
|
logs-serve
|
|
54
39
|
```
|
|
55
40
|
|
|
56
|
-
##
|
|
41
|
+
## Remote Sync
|
|
57
42
|
|
|
58
|
-
|
|
43
|
+
Logs stores data locally in SQLite and can optionally push/pull service-owned tables to PostgreSQL, including AWS RDS:
|
|
59
44
|
|
|
60
|
-
|
|
61
|
-
cloud setup
|
|
62
|
-
cloud sync push --service logs
|
|
63
|
-
cloud sync pull --service logs
|
|
64
|
-
```
|
|
45
|
+
Configure `HASNA_LOGS_CLOUD_DATABASE_URL` or `LOGS_CLOUD_DATABASE_URL`, then use the MCP tools `cloud_status`, `cloud_push`, `cloud_pull`, and `cloud_sync`.
|
|
65
46
|
|
|
66
47
|
## Data Directory
|
|
67
48
|
|
package/bun.lock
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
"": {
|
|
6
6
|
"name": "open-logs",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@
|
|
9
|
-
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
8
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
10
9
|
"commander": "^14.0.0",
|
|
11
10
|
"hono": "^4.7.11",
|
|
12
11
|
"ink": "^5.1.0",
|
|
13
12
|
"node-cron": "^3.0.3",
|
|
13
|
+
"pg": "^8.20.0",
|
|
14
14
|
"playwright": "^1.52.0",
|
|
15
15
|
"react": "^19.1.0",
|
|
16
16
|
},
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"@biomejs/biome": "^1.9.4",
|
|
19
19
|
"@types/bun": "latest",
|
|
20
20
|
"@types/node-cron": "^3.0.11",
|
|
21
|
+
"@types/pg": "^8.20.0",
|
|
21
22
|
"@types/react": "^19.1.4",
|
|
22
23
|
"typescript": "^5.9.3",
|
|
23
24
|
},
|
|
@@ -44,11 +45,9 @@
|
|
|
44
45
|
|
|
45
46
|
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="],
|
|
46
47
|
|
|
47
|
-
"@hasna/cloud": ["@hasna/cloud@0.1.30", "", { "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", "commander": "^13.1.0", "pg": "^8.13.3", "zod": "^3.24.2" }, "bin": { "cloud": "dist/cli/index.js", "cloud-mcp": "dist/mcp/index.js" } }, "sha512-qpDiJNsU/K1cFDk1l+xUA1xEEq8+Q37OdAz/ec0LwrQiA1HyWukzR8Lh7igkOqBT2foa46cxhB7yGnfiT2cWrA=="],
|
|
48
|
-
|
|
49
48
|
"@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="],
|
|
50
49
|
|
|
51
|
-
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.
|
|
50
|
+
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.27.1", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA=="],
|
|
52
51
|
|
|
53
52
|
"@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="],
|
|
54
53
|
|
|
@@ -56,6 +55,8 @@
|
|
|
56
55
|
|
|
57
56
|
"@types/node-cron": ["@types/node-cron@3.0.11", "", {}, "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg=="],
|
|
58
57
|
|
|
58
|
+
"@types/pg": ["@types/pg@8.20.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow=="],
|
|
59
|
+
|
|
59
60
|
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
|
60
61
|
|
|
61
62
|
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
|
@@ -356,20 +357,12 @@
|
|
|
356
357
|
|
|
357
358
|
"yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
|
|
358
359
|
|
|
359
|
-
"zod": ["zod@3.
|
|
360
|
+
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
|
360
361
|
|
|
361
362
|
"zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
|
|
362
363
|
|
|
363
|
-
"@hasna/cloud/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.27.1", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA=="],
|
|
364
|
-
|
|
365
|
-
"@hasna/cloud/commander": ["commander@13.1.0", "", {}, "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw=="],
|
|
366
|
-
|
|
367
|
-
"@modelcontextprotocol/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
|
368
|
-
|
|
369
364
|
"cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="],
|
|
370
365
|
|
|
371
366
|
"slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
|
|
372
|
-
|
|
373
|
-
"@hasna/cloud/@modelcontextprotocol/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
|
374
367
|
}
|
|
375
368
|
}
|
package/dist/cli/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
import {
|
|
4
4
|
runJob
|
|
5
|
-
} from "../index-
|
|
5
|
+
} from "../index-pf8hpweg.js";
|
|
6
6
|
import {
|
|
7
7
|
PACKAGE_VERSION,
|
|
8
8
|
createPage,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
listProjects,
|
|
14
14
|
resolveProjectId,
|
|
15
15
|
summarizeLogs
|
|
16
|
-
} from "../index-
|
|
16
|
+
} from "../index-g8f8kep6.js";
|
|
17
17
|
import {
|
|
18
18
|
createJob,
|
|
19
19
|
listJobs
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
// src/db/index.ts
|
|
7
7
|
import { Database } from "bun:sqlite";
|
|
8
8
|
import { join } from "path";
|
|
9
|
-
import { existsSync, mkdirSync } from "fs";
|
|
9
|
+
import { existsSync, mkdirSync, cpSync } from "fs";
|
|
10
10
|
|
|
11
11
|
// src/db/migrations/001_alert_rules.ts
|
|
12
12
|
function migrateAlertRules(db) {
|
|
@@ -80,8 +80,21 @@ function migratePageAuth(db) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
// src/db/index.ts
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
function resolveDataDir() {
|
|
84
|
+
const explicit = process.env.HASNA_LOGS_DATA_DIR ?? process.env.LOGS_DATA_DIR;
|
|
85
|
+
if (explicit)
|
|
86
|
+
return explicit;
|
|
87
|
+
const home = process.env.HOME ?? "~";
|
|
88
|
+
const newDir = join(home, ".hasna", "logs");
|
|
89
|
+
const oldDir = join(home, ".logs");
|
|
90
|
+
if (!existsSync(newDir) && existsSync(oldDir)) {
|
|
91
|
+
mkdirSync(join(home, ".hasna"), { recursive: true });
|
|
92
|
+
cpSync(oldDir, newDir, { recursive: true });
|
|
93
|
+
}
|
|
94
|
+
return newDir;
|
|
95
|
+
}
|
|
96
|
+
var DATA_DIR = resolveDataDir();
|
|
97
|
+
var DB_PATH = process.env.HASNA_LOGS_DB_PATH ?? process.env.LOGS_DB_PATH ?? join(DATA_DIR, "logs.db");
|
|
85
98
|
var _db = null;
|
|
86
99
|
function getDb() {
|
|
87
100
|
if (_db)
|
|
@@ -92,6 +105,15 @@ function getDb() {
|
|
|
92
105
|
_db.run("PRAGMA journal_mode=WAL");
|
|
93
106
|
_db.run("PRAGMA foreign_keys=ON");
|
|
94
107
|
migrate(_db);
|
|
108
|
+
_db.run(`CREATE TABLE IF NOT EXISTS feedback (
|
|
109
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
110
|
+
message TEXT NOT NULL,
|
|
111
|
+
email TEXT,
|
|
112
|
+
category TEXT DEFAULT 'general',
|
|
113
|
+
version TEXT,
|
|
114
|
+
machine_id TEXT,
|
|
115
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
116
|
+
)`);
|
|
95
117
|
return _db;
|
|
96
118
|
}
|
|
97
119
|
function migrate(db) {
|
|
@@ -383,7 +405,7 @@ function ingestBatch(db, entries, sharedTraceId) {
|
|
|
383
405
|
VALUES ($project_id, $page_id, $level, $source, $service, $message, $trace_id, $session_id, $agent, $url, $stack_trace, $metadata)
|
|
384
406
|
RETURNING *
|
|
385
407
|
`);
|
|
386
|
-
const
|
|
408
|
+
const rows = db.transaction(() => entries.map((entry) => insert.get({
|
|
387
409
|
$project_id: entry.project_id ?? null,
|
|
388
410
|
$page_id: entry.page_id ?? null,
|
|
389
411
|
$level: entry.level,
|
|
@@ -397,7 +419,6 @@ function ingestBatch(db, entries, sharedTraceId) {
|
|
|
397
419
|
$stack_trace: entry.stack_trace ?? null,
|
|
398
420
|
$metadata: entry.metadata ? JSON.stringify(entry.metadata) : null
|
|
399
421
|
})));
|
|
400
|
-
const rows = tx(entries);
|
|
401
422
|
for (const entry of entries) {
|
|
402
423
|
if (ERROR_LEVELS.has(entry.level) && entry.project_id) {
|
|
403
424
|
upsertIssue(db, { project_id: entry.project_id, level: entry.level, service: entry.service, message: entry.message, stack_trace: entry.stack_trace });
|
|
@@ -406,6 +427,67 @@ function ingestBatch(db, entries, sharedTraceId) {
|
|
|
406
427
|
return rows;
|
|
407
428
|
}
|
|
408
429
|
|
|
430
|
+
// src/lib/package-meta.ts
|
|
431
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
432
|
+
var PACKAGE_JSON_CANDIDATES = [
|
|
433
|
+
"../../package.json",
|
|
434
|
+
"../package.json",
|
|
435
|
+
"./package.json"
|
|
436
|
+
];
|
|
437
|
+
function readPackageJson(baseUrl = import.meta.url) {
|
|
438
|
+
for (const relativePath of PACKAGE_JSON_CANDIDATES) {
|
|
439
|
+
const candidate = new URL(relativePath, baseUrl);
|
|
440
|
+
if (!existsSync2(candidate))
|
|
441
|
+
continue;
|
|
442
|
+
return JSON.parse(readFileSync(candidate, "utf8"));
|
|
443
|
+
}
|
|
444
|
+
throw new Error(`Unable to locate package.json from ${String(baseUrl)}`);
|
|
445
|
+
}
|
|
446
|
+
function readPackageVersion(baseUrl = import.meta.url) {
|
|
447
|
+
return readPackageJson(baseUrl).version ?? "0.0.0";
|
|
448
|
+
}
|
|
449
|
+
var PACKAGE_VERSION = readPackageVersion();
|
|
450
|
+
function exitIfMetadataRequest(spec, argv = process.argv.slice(2)) {
|
|
451
|
+
if (argv.includes("--version") || argv.includes("-V")) {
|
|
452
|
+
console.log(PACKAGE_VERSION);
|
|
453
|
+
process.exit(0);
|
|
454
|
+
}
|
|
455
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
456
|
+
const options = spec.options ?? [];
|
|
457
|
+
const renderedOptions = [
|
|
458
|
+
" -V, --version output the version number",
|
|
459
|
+
" -h, --help display help for command",
|
|
460
|
+
...options
|
|
461
|
+
];
|
|
462
|
+
console.log([
|
|
463
|
+
`Usage: ${spec.name} [options]`,
|
|
464
|
+
"",
|
|
465
|
+
spec.description,
|
|
466
|
+
"",
|
|
467
|
+
"Options:",
|
|
468
|
+
...renderedOptions
|
|
469
|
+
].join(`
|
|
470
|
+
`));
|
|
471
|
+
process.exit(0);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function readOptionValue(names, argv = process.argv.slice(2)) {
|
|
475
|
+
for (let index = 0;index < argv.length; index += 1) {
|
|
476
|
+
const arg = argv[index];
|
|
477
|
+
if (!arg)
|
|
478
|
+
continue;
|
|
479
|
+
const inline = names.find((name) => arg.startsWith(`${name}=`));
|
|
480
|
+
if (inline)
|
|
481
|
+
return arg.slice(inline.length + 1);
|
|
482
|
+
if (names.includes(arg)) {
|
|
483
|
+
const next = argv[index + 1];
|
|
484
|
+
if (next && !next.startsWith("-"))
|
|
485
|
+
return next;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
409
491
|
// src/lib/summarize.ts
|
|
410
492
|
function summarizeLogs(db, projectId, since, until) {
|
|
411
493
|
const conditions = ["level IN ('warn','error','fatal')"];
|
|
@@ -540,4 +622,4 @@ function scoreLabel(score) {
|
|
|
540
622
|
return "red";
|
|
541
623
|
}
|
|
542
624
|
|
|
543
|
-
export { getDb, listIssues, getIssue, updateIssueStatus, createAlertRule, listAlertRules, updateAlertRule, deleteAlertRule, ingestLog, ingestBatch, summarizeLogs, createProject, listProjects, getProject, updateProject, createPage, listPages, getPage, resolveProjectId, touchPage, saveSnapshot, getLatestSnapshot, getPerfTrend, scoreLabel };
|
|
625
|
+
export { getDb, listIssues, getIssue, updateIssueStatus, createAlertRule, listAlertRules, updateAlertRule, deleteAlertRule, ingestLog, ingestBatch, PACKAGE_VERSION, exitIfMetadataRequest, readOptionValue, summarizeLogs, createProject, listProjects, getProject, updateProject, createPage, listPages, getPage, resolveProjectId, touchPage, saveSnapshot, getLatestSnapshot, getPerfTrend, scoreLabel };
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
// src/db/index.ts
|
|
7
7
|
import { Database } from "bun:sqlite";
|
|
8
8
|
import { join } from "path";
|
|
9
|
-
import { existsSync, mkdirSync } from "fs";
|
|
9
|
+
import { existsSync, mkdirSync, cpSync } from "fs";
|
|
10
10
|
|
|
11
11
|
// src/db/migrations/001_alert_rules.ts
|
|
12
12
|
function migrateAlertRules(db) {
|
|
@@ -80,8 +80,21 @@ function migratePageAuth(db) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
// src/db/index.ts
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
function resolveDataDir() {
|
|
84
|
+
const explicit = process.env.HASNA_LOGS_DATA_DIR ?? process.env.LOGS_DATA_DIR;
|
|
85
|
+
if (explicit)
|
|
86
|
+
return explicit;
|
|
87
|
+
const home = process.env.HOME ?? "~";
|
|
88
|
+
const newDir = join(home, ".hasna", "logs");
|
|
89
|
+
const oldDir = join(home, ".logs");
|
|
90
|
+
if (!existsSync(newDir) && existsSync(oldDir)) {
|
|
91
|
+
mkdirSync(join(home, ".hasna"), { recursive: true });
|
|
92
|
+
cpSync(oldDir, newDir, { recursive: true });
|
|
93
|
+
}
|
|
94
|
+
return newDir;
|
|
95
|
+
}
|
|
96
|
+
var DATA_DIR = resolveDataDir();
|
|
97
|
+
var DB_PATH = process.env.HASNA_LOGS_DB_PATH ?? process.env.LOGS_DB_PATH ?? join(DATA_DIR, "logs.db");
|
|
85
98
|
var _db = null;
|
|
86
99
|
function getDb() {
|
|
87
100
|
if (_db)
|
|
@@ -92,6 +105,15 @@ function getDb() {
|
|
|
92
105
|
_db.run("PRAGMA journal_mode=WAL");
|
|
93
106
|
_db.run("PRAGMA foreign_keys=ON");
|
|
94
107
|
migrate(_db);
|
|
108
|
+
_db.run(`CREATE TABLE IF NOT EXISTS feedback (
|
|
109
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
110
|
+
message TEXT NOT NULL,
|
|
111
|
+
email TEXT,
|
|
112
|
+
category TEXT DEFAULT 'general',
|
|
113
|
+
version TEXT,
|
|
114
|
+
machine_id TEXT,
|
|
115
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
116
|
+
)`);
|
|
95
117
|
return _db;
|
|
96
118
|
}
|
|
97
119
|
function migrate(db) {
|
|
@@ -374,13 +396,16 @@ function ingestLog(db, entry) {
|
|
|
374
396
|
}
|
|
375
397
|
return row;
|
|
376
398
|
}
|
|
377
|
-
function ingestBatch(db, entries) {
|
|
399
|
+
function ingestBatch(db, entries, sharedTraceId) {
|
|
400
|
+
if (sharedTraceId) {
|
|
401
|
+
entries = entries.map((e) => e.trace_id ? e : { ...e, trace_id: sharedTraceId });
|
|
402
|
+
}
|
|
378
403
|
const insert = db.prepare(`
|
|
379
404
|
INSERT INTO logs (project_id, page_id, level, source, service, message, trace_id, session_id, agent, url, stack_trace, metadata)
|
|
380
405
|
VALUES ($project_id, $page_id, $level, $source, $service, $message, $trace_id, $session_id, $agent, $url, $stack_trace, $metadata)
|
|
381
406
|
RETURNING *
|
|
382
407
|
`);
|
|
383
|
-
const
|
|
408
|
+
const rows = db.transaction(() => entries.map((entry) => insert.get({
|
|
384
409
|
$project_id: entry.project_id ?? null,
|
|
385
410
|
$page_id: entry.page_id ?? null,
|
|
386
411
|
$level: entry.level,
|
|
@@ -393,8 +418,7 @@ function ingestBatch(db, entries) {
|
|
|
393
418
|
$url: entry.url ?? null,
|
|
394
419
|
$stack_trace: entry.stack_trace ?? null,
|
|
395
420
|
$metadata: entry.metadata ? JSON.stringify(entry.metadata) : null
|
|
396
|
-
})));
|
|
397
|
-
const rows = tx(entries);
|
|
421
|
+
})))();
|
|
398
422
|
for (const entry of entries) {
|
|
399
423
|
if (ERROR_LEVELS.has(entry.level) && entry.project_id) {
|
|
400
424
|
upsertIssue(db, { project_id: entry.project_id, level: entry.level, service: entry.service, message: entry.message, stack_trace: entry.stack_trace });
|
|
@@ -403,6 +427,67 @@ function ingestBatch(db, entries) {
|
|
|
403
427
|
return rows;
|
|
404
428
|
}
|
|
405
429
|
|
|
430
|
+
// src/lib/package-meta.ts
|
|
431
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
432
|
+
var PACKAGE_JSON_CANDIDATES = [
|
|
433
|
+
"../../package.json",
|
|
434
|
+
"../package.json",
|
|
435
|
+
"./package.json"
|
|
436
|
+
];
|
|
437
|
+
function readPackageJson(baseUrl = import.meta.url) {
|
|
438
|
+
for (const relativePath of PACKAGE_JSON_CANDIDATES) {
|
|
439
|
+
const candidate = new URL(relativePath, baseUrl);
|
|
440
|
+
if (!existsSync2(candidate))
|
|
441
|
+
continue;
|
|
442
|
+
return JSON.parse(readFileSync(candidate, "utf8"));
|
|
443
|
+
}
|
|
444
|
+
throw new Error(`Unable to locate package.json from ${String(baseUrl)}`);
|
|
445
|
+
}
|
|
446
|
+
function readPackageVersion(baseUrl = import.meta.url) {
|
|
447
|
+
return readPackageJson(baseUrl).version ?? "0.0.0";
|
|
448
|
+
}
|
|
449
|
+
var PACKAGE_VERSION = readPackageVersion();
|
|
450
|
+
function exitIfMetadataRequest(spec, argv = process.argv.slice(2)) {
|
|
451
|
+
if (argv.includes("--version") || argv.includes("-V")) {
|
|
452
|
+
console.log(PACKAGE_VERSION);
|
|
453
|
+
process.exit(0);
|
|
454
|
+
}
|
|
455
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
456
|
+
const options = spec.options ?? [];
|
|
457
|
+
const renderedOptions = [
|
|
458
|
+
" -V, --version output the version number",
|
|
459
|
+
" -h, --help display help for command",
|
|
460
|
+
...options
|
|
461
|
+
];
|
|
462
|
+
console.log([
|
|
463
|
+
`Usage: ${spec.name} [options]`,
|
|
464
|
+
"",
|
|
465
|
+
spec.description,
|
|
466
|
+
"",
|
|
467
|
+
"Options:",
|
|
468
|
+
...renderedOptions
|
|
469
|
+
].join(`
|
|
470
|
+
`));
|
|
471
|
+
process.exit(0);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function readOptionValue(names, argv = process.argv.slice(2)) {
|
|
475
|
+
for (let index = 0;index < argv.length; index += 1) {
|
|
476
|
+
const arg = argv[index];
|
|
477
|
+
if (!arg)
|
|
478
|
+
continue;
|
|
479
|
+
const inline = names.find((name) => arg.startsWith(`${name}=`));
|
|
480
|
+
if (inline)
|
|
481
|
+
return arg.slice(inline.length + 1);
|
|
482
|
+
if (names.includes(arg)) {
|
|
483
|
+
const next = argv[index + 1];
|
|
484
|
+
if (next && !next.startsWith("-"))
|
|
485
|
+
return next;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
406
491
|
// src/lib/summarize.ts
|
|
407
492
|
function summarizeLogs(db, projectId, since, until) {
|
|
408
493
|
const conditions = ["level IN ('warn','error','fatal')"];
|
|
@@ -537,4 +622,4 @@ function scoreLabel(score) {
|
|
|
537
622
|
return "red";
|
|
538
623
|
}
|
|
539
624
|
|
|
540
|
-
export { getDb, listIssues, getIssue, updateIssueStatus, createAlertRule, listAlertRules, updateAlertRule, deleteAlertRule, ingestLog, ingestBatch, summarizeLogs, createProject, listProjects, getProject, updateProject, createPage, listPages, getPage, resolveProjectId, touchPage, saveSnapshot, getLatestSnapshot, getPerfTrend, scoreLabel };
|
|
625
|
+
export { getDb, listIssues, getIssue, updateIssueStatus, createAlertRule, listAlertRules, updateAlertRule, deleteAlertRule, ingestLog, ingestBatch, PACKAGE_VERSION, exitIfMetadataRequest, readOptionValue, summarizeLogs, createProject, listProjects, getProject, updateProject, createPage, listPages, getPage, resolveProjectId, touchPage, saveSnapshot, getLatestSnapshot, getPerfTrend, scoreLabel };
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
listPages,
|
|
6
6
|
saveSnapshot,
|
|
7
7
|
touchPage
|
|
8
|
-
} from "./index-
|
|
8
|
+
} from "./index-g8f8kep6.js";
|
|
9
9
|
import {
|
|
10
10
|
createScanRun,
|
|
11
11
|
finishScanRun,
|
|
@@ -894,7 +894,7 @@ var require_scheduled_task = __commonJS((exports, module) => {
|
|
|
894
894
|
|
|
895
895
|
// node_modules/node-cron/src/background-scheduled-task/index.js
|
|
896
896
|
var require_background_scheduled_task = __commonJS((exports, module) => {
|
|
897
|
-
var __dirname = "/
|
|
897
|
+
var __dirname = "/home/hasna/workspace/hasna/opensource/open-logs/node_modules/node-cron/src/background-scheduled-task";
|
|
898
898
|
var EventEmitter = __require("events");
|
|
899
899
|
var path = __require("path");
|
|
900
900
|
var { fork } = __require("child_process");
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
listPages,
|
|
6
6
|
saveSnapshot,
|
|
7
7
|
touchPage
|
|
8
|
-
} from "./index-
|
|
8
|
+
} from "./index-75dwghvv.js";
|
|
9
9
|
import {
|
|
10
10
|
createScanRun,
|
|
11
11
|
finishScanRun,
|
|
@@ -894,7 +894,7 @@ var require_scheduled_task = __commonJS((exports, module) => {
|
|
|
894
894
|
|
|
895
895
|
// node_modules/node-cron/src/background-scheduled-task/index.js
|
|
896
896
|
var require_background_scheduled_task = __commonJS((exports, module) => {
|
|
897
|
-
var __dirname = "/
|
|
897
|
+
var __dirname = "/home/hasna/workspace/hasna/opensource/open-logs/node_modules/node-cron/src/background-scheduled-task";
|
|
898
898
|
var EventEmitter = __require("events");
|
|
899
899
|
var path = __require("path");
|
|
900
900
|
var { fork } = __require("child_process");
|