@hasna/logs 0.3.26 → 0.3.28

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.
Files changed (130) hide show
  1. package/README.md +33 -10
  2. package/dashboard/dist/assets/index-C0wZYq1m.js +53 -0
  3. package/dashboard/dist/assets/index-DGNrK5qb.css +1 -0
  4. package/dashboard/dist/index.html +14 -0
  5. package/dist/cli/index.js +8511 -177
  6. package/dist/count-bmj4r2zb.js +10 -0
  7. package/dist/{diagnose-e0w5rwbc.js → diagnose-3q5cy9ra.js} +2 -2
  8. package/dist/{export-c3eqjste.js → export-cngdb9fh.js} +1 -1
  9. package/dist/{http-zm3ph78w.js → http-r0xc3d2s.js} +79 -8
  10. package/dist/index-931pbyn5.js +141 -0
  11. package/dist/index-b5c72f1p.js +7 -0
  12. package/dist/{index-7w7v7hnr.js → index-by1pdzbr.js} +14 -5
  13. package/dist/{index-3dr7d80h.js → index-e1930v9b.js} +12 -8
  14. package/dist/{index-eh9bkbpa.js → index-e72k53yq.js} +10 -2
  15. package/dist/{index-edn08m6f.js → index-gcd14q2f.js} +9 -6
  16. package/dist/index-hq6kzaah.js +26 -0
  17. package/dist/index-j34f36wy.js +5672 -0
  18. package/dist/{index-5qznfyah.js → index-q27bgpr1.js} +1086 -1646
  19. package/dist/index-qk8dbvbc.js +1859 -0
  20. package/dist/index-t3x838zw.js +2583 -0
  21. package/dist/{index-gc0zvs88.js → index-y2y0mdtd.js} +596 -37
  22. package/dist/{index-ww5ggfv3.js → index-zkb3z95a.js} +12 -9
  23. package/dist/index.js +2990 -22
  24. package/dist/{jobs-ypmmc2ma.js → jobs-hsgyhfvm.js} +2 -1
  25. package/dist/mcp/index.js +1473 -4286
  26. package/dist/{query-7jwj05er.js → query-c5a43zx3.js} +3 -2
  27. package/dist/server/index.js +2944 -417
  28. package/dist/storage.js +50 -0
  29. package/package.json +27 -8
  30. package/biome.json +0 -13
  31. package/bun.lock +0 -376
  32. package/dashboard/README.md +0 -73
  33. package/dashboard/bun.lock +0 -526
  34. package/dashboard/eslint.config.js +0 -23
  35. package/dashboard/index.html +0 -13
  36. package/dashboard/package.json +0 -32
  37. package/dashboard/src/App.css +0 -184
  38. package/dashboard/src/App.tsx +0 -49
  39. package/dashboard/src/api.ts +0 -33
  40. package/dashboard/src/assets/hero.png +0 -0
  41. package/dashboard/src/assets/react.svg +0 -1
  42. package/dashboard/src/assets/vite.svg +0 -1
  43. package/dashboard/src/index.css +0 -111
  44. package/dashboard/src/main.tsx +0 -10
  45. package/dashboard/src/pages/Alerts.tsx +0 -69
  46. package/dashboard/src/pages/Issues.tsx +0 -50
  47. package/dashboard/src/pages/Perf.tsx +0 -75
  48. package/dashboard/src/pages/Projects.tsx +0 -67
  49. package/dashboard/src/pages/Summary.tsx +0 -67
  50. package/dashboard/src/pages/Tail.tsx +0 -65
  51. package/dashboard/tsconfig.app.json +0 -28
  52. package/dashboard/tsconfig.json +0 -7
  53. package/dashboard/tsconfig.node.json +0 -26
  54. package/dashboard/vite.config.ts +0 -14
  55. package/dist/count-x3n7qg3c.js +0 -9
  56. package/dist/index-997bkzr2.js +0 -15
  57. package/dist/index-pen6t0yc.js +0 -10794
  58. package/sdk/package.json +0 -27
  59. package/sdk/src/index.ts +0 -143
  60. package/sdk/src/types.ts +0 -56
  61. package/src/cli/entrypoints.test.ts +0 -63
  62. package/src/cli/index.ts +0 -471
  63. package/src/db/index.test.ts +0 -33
  64. package/src/db/index.ts +0 -189
  65. package/src/db/migrations/001_alert_rules.ts +0 -21
  66. package/src/db/migrations/002_issues.ts +0 -21
  67. package/src/db/migrations/003_retention.ts +0 -15
  68. package/src/db/migrations/004_page_auth.ts +0 -13
  69. package/src/db/pg-migrations.ts +0 -167
  70. package/src/index.ts +0 -1
  71. package/src/lib/alerts.test.ts +0 -67
  72. package/src/lib/alerts.ts +0 -117
  73. package/src/lib/browser-script.test.ts +0 -35
  74. package/src/lib/browser-script.ts +0 -31
  75. package/src/lib/compare.test.ts +0 -52
  76. package/src/lib/compare.ts +0 -85
  77. package/src/lib/count.test.ts +0 -44
  78. package/src/lib/count.ts +0 -55
  79. package/src/lib/diagnose.test.ts +0 -55
  80. package/src/lib/diagnose.ts +0 -91
  81. package/src/lib/export.test.ts +0 -66
  82. package/src/lib/export.ts +0 -65
  83. package/src/lib/github.ts +0 -38
  84. package/src/lib/health.test.ts +0 -48
  85. package/src/lib/health.ts +0 -51
  86. package/src/lib/ingest.test.ts +0 -57
  87. package/src/lib/ingest.ts +0 -78
  88. package/src/lib/issues.test.ts +0 -79
  89. package/src/lib/issues.ts +0 -70
  90. package/src/lib/jobs.test.ts +0 -69
  91. package/src/lib/jobs.ts +0 -63
  92. package/src/lib/lighthouse.ts +0 -65
  93. package/src/lib/package-meta.test.ts +0 -43
  94. package/src/lib/package-meta.ts +0 -80
  95. package/src/lib/page-auth.test.ts +0 -54
  96. package/src/lib/page-auth.ts +0 -48
  97. package/src/lib/parse-time.test.ts +0 -37
  98. package/src/lib/parse-time.ts +0 -14
  99. package/src/lib/perf.test.ts +0 -45
  100. package/src/lib/perf.ts +0 -46
  101. package/src/lib/projects.test.ts +0 -73
  102. package/src/lib/projects.ts +0 -69
  103. package/src/lib/query.test.ts +0 -104
  104. package/src/lib/query.ts +0 -84
  105. package/src/lib/retention.test.ts +0 -42
  106. package/src/lib/retention.ts +0 -62
  107. package/src/lib/rotate.test.ts +0 -37
  108. package/src/lib/rotate.ts +0 -27
  109. package/src/lib/scanner.ts +0 -131
  110. package/src/lib/scheduler.ts +0 -63
  111. package/src/lib/session-context.ts +0 -28
  112. package/src/lib/summarize.test.ts +0 -38
  113. package/src/lib/summarize.ts +0 -23
  114. package/src/mcp/http.test.ts +0 -92
  115. package/src/mcp/http.ts +0 -135
  116. package/src/mcp/index.test.ts +0 -27
  117. package/src/mcp/index.ts +0 -444
  118. package/src/server/index.ts +0 -61
  119. package/src/server/routes/alerts.ts +0 -32
  120. package/src/server/routes/issues.ts +0 -43
  121. package/src/server/routes/jobs.ts +0 -32
  122. package/src/server/routes/logs.ts +0 -113
  123. package/src/server/routes/perf.ts +0 -23
  124. package/src/server/routes/projects.ts +0 -67
  125. package/src/server/routes/stream.ts +0 -43
  126. package/src/server/server.test.ts +0 -194
  127. package/src/types/index.ts +0 -119
  128. package/tsconfig.json +0 -22
  129. /package/dashboard/{public → dist}/favicon.svg +0 -0
  130. /package/dashboard/{public → dist}/icons.svg +0 -0
@@ -2,16 +2,25 @@
2
2
  import {
3
3
  getPage,
4
4
  ingestBatch,
5
+ ingestLog,
5
6
  listPages,
7
+ redactString,
8
+ redactValue,
6
9
  saveSnapshot,
7
10
  touchPage
8
- } from "./index-pen6t0yc.js";
11
+ } from "./index-qk8dbvbc.js";
12
+ import {
13
+ getEventStoreDataDir
14
+ } from "./index-t3x838zw.js";
9
15
  import {
10
16
  createScanRun,
11
17
  finishScanRun,
12
18
  listJobs,
13
19
  updateJob
14
- } from "./index-3dr7d80h.js";
20
+ } from "./index-e1930v9b.js";
21
+ import {
22
+ sqlBindings
23
+ } from "./index-b5c72f1p.js";
15
24
  import {
16
25
  __commonJS,
17
26
  __require,
@@ -894,7 +903,7 @@ var require_scheduled_task = __commonJS((exports, module) => {
894
903
 
895
904
  // node_modules/node-cron/src/background-scheduled-task/index.js
896
905
  var require_background_scheduled_task = __commonJS((exports, module) => {
897
- var __dirname = "/tmp/hasna-webhooks-release-1781607876875/open-logs/node_modules/node-cron/src/background-scheduled-task";
906
+ var __dirname = "/home/hasna/Workspace/hasna/opensource/open-logs/node_modules/node-cron/src/background-scheduled-task";
898
907
  var EventEmitter = __require("events");
899
908
  var path = __require("path");
900
909
  var { fork } = __require("child_process");
@@ -1018,23 +1027,23 @@ var TTL_BY_LEVEL = {
1018
1027
  fatal: "error_ttl_hours"
1019
1028
  };
1020
1029
  function runRetentionForProject(db, projectId) {
1021
- const project = db.prepare("SELECT * FROM projects WHERE id = $id").get({ $id: projectId });
1030
+ const project = db.prepare("SELECT * FROM projects WHERE id = $id").get(sqlBindings({ $id: projectId }));
1022
1031
  if (!project)
1023
1032
  return { deleted: 0 };
1024
1033
  let deleted = 0;
1025
1034
  for (const [level, configKey] of Object.entries(TTL_BY_LEVEL)) {
1026
1035
  const ttlHours = project[configKey];
1027
1036
  const cutoff = new Date(Date.now() - ttlHours * 3600 * 1000).toISOString();
1028
- const before = db.prepare("SELECT COUNT(*) as c FROM logs WHERE project_id = $p AND level = $level AND timestamp < $cutoff").get({ $p: projectId, $level: level, $cutoff: cutoff }).c;
1037
+ const before = db.prepare("SELECT COUNT(*) as c FROM logs WHERE project_id = $p AND level = $level AND timestamp < $cutoff").get(sqlBindings({ $p: projectId, $level: level, $cutoff: cutoff })).c;
1029
1038
  if (before > 0) {
1030
- db.prepare("DELETE FROM logs WHERE project_id = $p AND level = $level AND timestamp < $cutoff").run({ $p: projectId, $level: level, $cutoff: cutoff });
1039
+ db.prepare("DELETE FROM logs WHERE project_id = $p AND level = $level AND timestamp < $cutoff").run(sqlBindings({ $p: projectId, $level: level, $cutoff: cutoff }));
1031
1040
  deleted += before;
1032
1041
  }
1033
1042
  }
1034
- const total = db.prepare("SELECT COUNT(*) as c FROM logs WHERE project_id = $p").get({ $p: projectId }).c;
1043
+ const total = db.prepare("SELECT COUNT(*) as c FROM logs WHERE project_id = $p").get(sqlBindings({ $p: projectId })).c;
1035
1044
  if (total > project.max_rows) {
1036
1045
  const toDelete = total - project.max_rows;
1037
- db.prepare(`DELETE FROM logs WHERE id IN (SELECT id FROM logs WHERE project_id = $p ORDER BY timestamp ASC LIMIT ${toDelete})`).run({ $p: projectId });
1046
+ db.prepare("DELETE FROM logs WHERE id IN (SELECT id FROM logs WHERE project_id = $p ORDER BY timestamp ASC LIMIT $limit)").run(sqlBindings({ $p: projectId, $limit: toDelete }));
1038
1047
  deleted += toDelete;
1039
1048
  }
1040
1049
  return { deleted };
@@ -1053,44 +1062,128 @@ function setRetentionPolicy(db, projectId, config) {
1053
1062
  return;
1054
1063
  const params = Object.fromEntries(Object.entries(config).map(([k, v]) => [`$${k}`, v]));
1055
1064
  params.$id = projectId;
1056
- db.prepare(`UPDATE projects SET ${fields} WHERE id = $id`).run(params);
1065
+ db.prepare(`UPDATE projects SET ${fields} WHERE id = $id`).run(sqlBindings(params));
1057
1066
  }
1058
1067
 
1059
1068
  // src/lib/page-auth.ts
1060
- import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
1061
- var SECRET_KEY = Buffer.from((process.env.LOGS_SECRET_KEY ?? "open-logs-default-key-32bytesXXX").padEnd(32).slice(0, 32));
1062
- function encrypt(text) {
1063
- const iv = randomBytes(16);
1064
- const cipher = createCipheriv("aes-256-cbc", SECRET_KEY, iv);
1065
- const encrypted = Buffer.concat([cipher.update(text, "utf8"), cipher.final()]);
1066
- return iv.toString("hex") + ":" + encrypted.toString("hex");
1069
+ import {
1070
+ createCipheriv,
1071
+ createDecipheriv,
1072
+ createHash,
1073
+ randomBytes
1074
+ } from "crypto";
1075
+ import {
1076
+ chmodSync,
1077
+ existsSync,
1078
+ mkdirSync,
1079
+ readFileSync,
1080
+ writeFileSync
1081
+ } from "fs";
1082
+ import { join } from "path";
1083
+ var LOCAL_PAGE_AUTH_SECRET_FILE = "page-auth.key";
1084
+ function encrypt(db, text) {
1085
+ const iv = randomBytes(12);
1086
+ const cipher = createCipheriv("aes-256-gcm", pageAuthSecretKey(db), iv);
1087
+ const encrypted = Buffer.concat([
1088
+ cipher.update(text, "utf8"),
1089
+ cipher.final()
1090
+ ]);
1091
+ return [
1092
+ "v2",
1093
+ iv.toString("hex"),
1094
+ cipher.getAuthTag().toString("hex"),
1095
+ encrypted.toString("hex")
1096
+ ].join(":");
1067
1097
  }
1068
- function decrypt(text) {
1069
- const [ivHex, encHex] = text.split(":");
1098
+ function decrypt(db, text) {
1099
+ const parts = text.split(":");
1100
+ if (parts[0] === "v2") {
1101
+ const [, ivHex2, tagHex, encHex2] = parts;
1102
+ if (!ivHex2 || !tagHex || !encHex2)
1103
+ throw new Error("Invalid page auth secret format");
1104
+ const decipher2 = createDecipheriv("aes-256-gcm", pageAuthSecretKey(db), Buffer.from(ivHex2, "hex"));
1105
+ decipher2.setAuthTag(Buffer.from(tagHex, "hex"));
1106
+ return Buffer.concat([
1107
+ decipher2.update(Buffer.from(encHex2, "hex")),
1108
+ decipher2.final()
1109
+ ]).toString("utf8");
1110
+ }
1111
+ const [ivHex, encHex] = parts;
1070
1112
  if (!ivHex || !encHex)
1071
1113
  return text;
1072
- const iv = Buffer.from(ivHex, "hex");
1073
- const enc = Buffer.from(encHex, "hex");
1074
- const decipher = createDecipheriv("aes-256-cbc", SECRET_KEY, iv);
1075
- return Buffer.concat([decipher.update(enc), decipher.final()]).toString("utf8");
1114
+ const decipher = createDecipheriv("aes-256-cbc", legacyPageAuthSecretKey(db), Buffer.from(ivHex, "hex"));
1115
+ return Buffer.concat([
1116
+ decipher.update(Buffer.from(encHex, "hex")),
1117
+ decipher.final()
1118
+ ]).toString("utf8");
1076
1119
  }
1077
1120
  function setPageAuth(db, pageId, type, credentials) {
1078
- const encrypted = encrypt(credentials);
1121
+ const encrypted = encrypt(db, credentials);
1079
1122
  return db.prepare(`
1080
1123
  INSERT INTO page_auth (page_id, type, credentials)
1081
1124
  VALUES ($page_id, $type, $credentials)
1082
1125
  ON CONFLICT(page_id) DO UPDATE SET type = excluded.type, credentials = excluded.credentials
1083
1126
  RETURNING *
1084
- `).get({ $page_id: pageId, $type: type, $credentials: encrypted });
1127
+ `).get(sqlBindings({
1128
+ $page_id: pageId,
1129
+ $type: type,
1130
+ $credentials: encrypted
1131
+ }));
1085
1132
  }
1086
1133
  function getPageAuth(db, pageId) {
1087
- const row = db.prepare("SELECT * FROM page_auth WHERE page_id = $id").get({ $id: pageId });
1134
+ const row = db.prepare("SELECT * FROM page_auth WHERE page_id = $id").get(sqlBindings({ $id: pageId }));
1088
1135
  if (!row)
1089
1136
  return null;
1090
- return { type: row.type, credentials: decrypt(row.credentials) };
1137
+ return { type: row.type, credentials: decrypt(db, row.credentials) };
1091
1138
  }
1092
1139
  function deletePageAuth(db, pageId) {
1093
- db.run("DELETE FROM page_auth WHERE page_id = $id", { $id: pageId });
1140
+ db.prepare("DELETE FROM page_auth WHERE page_id = $id").run(sqlBindings({ $id: pageId }));
1141
+ }
1142
+ function pageAuthSecretKey(db) {
1143
+ const secret = pageAuthSecret(db);
1144
+ if (/^[a-f0-9]{64}$/i.test(secret))
1145
+ return Buffer.from(secret, "hex");
1146
+ return createHash("sha256").update(secret).digest();
1147
+ }
1148
+ function legacyPageAuthSecretKey(db) {
1149
+ return Buffer.from(pageAuthSecret(db).padEnd(32).slice(0, 32));
1150
+ }
1151
+ function pageAuthSecret(db) {
1152
+ const secret = process.env.HASNA_LOGS_SECRET_KEY?.trim() || process.env.LOGS_SECRET_KEY?.trim();
1153
+ if (!secret)
1154
+ return readOrCreateLocalPageAuthSecret(db);
1155
+ if (secret.length < 32) {
1156
+ throw new Error("Page auth secret must be at least 32 characters. Generate one with: openssl rand -hex 32");
1157
+ }
1158
+ return secret;
1159
+ }
1160
+ function readOrCreateLocalPageAuthSecret(db) {
1161
+ const dataDir = getEventStoreDataDir(db);
1162
+ const secretPath = join(dataDir, LOCAL_PAGE_AUTH_SECRET_FILE);
1163
+ mkdirSync(dataDir, { recursive: true });
1164
+ if (existsSync(secretPath))
1165
+ return readLocalPageAuthSecret(secretPath);
1166
+ const secret = randomBytes(32).toString("hex");
1167
+ try {
1168
+ writeFileSync(secretPath, `${secret}
1169
+ `, {
1170
+ encoding: "utf8",
1171
+ flag: "wx",
1172
+ mode: 384
1173
+ });
1174
+ } catch (error) {
1175
+ if (error.code !== "EEXIST")
1176
+ throw error;
1177
+ }
1178
+ chmodSync(secretPath, 384);
1179
+ return readLocalPageAuthSecret(secretPath);
1180
+ }
1181
+ function readLocalPageAuthSecret(secretPath) {
1182
+ const secret = readFileSync(secretPath, "utf8").trim();
1183
+ if (secret.length < 32) {
1184
+ throw new Error(`Page auth secret file ${secretPath} must contain at least 32 characters.`);
1185
+ }
1186
+ return secret;
1094
1187
  }
1095
1188
 
1096
1189
  // src/lib/scanner.ts
@@ -1111,12 +1204,20 @@ async function scanPage(db, projectId, pageId, urlOverride) {
1111
1204
  } catch {}
1112
1205
  } else if (auth?.type === "basic") {
1113
1206
  const [username, password] = auth.credentials.split(":");
1114
- contextOptions.httpCredentials = { username: username ?? "", password: password ?? "" };
1207
+ contextOptions.httpCredentials = {
1208
+ username: username ?? "",
1209
+ password: password ?? ""
1210
+ };
1115
1211
  }
1116
1212
  const context = await browser.newContext(contextOptions);
1117
1213
  if (auth?.type === "bearer") {
1118
1214
  await context.route("**/*", (route) => {
1119
- route.continue({ headers: { ...route.request().headers(), Authorization: `Bearer ${auth.credentials}` } });
1215
+ route.continue({
1216
+ headers: {
1217
+ ...route.request().headers(),
1218
+ Authorization: `Bearer ${auth.credentials}`
1219
+ }
1220
+ });
1120
1221
  });
1121
1222
  }
1122
1223
  const browserPage = await context.newPage();
@@ -1157,17 +1258,18 @@ async function scanPage(db, projectId, pageId, urlOverride) {
1157
1258
  url
1158
1259
  });
1159
1260
  });
1160
- let perfScore = null;
1261
+ const perfScore = null;
1161
1262
  try {
1162
1263
  await browserPage.goto(url, { waitUntil: "networkidle", timeout: 30000 });
1163
1264
  try {
1164
1265
  const metrics = await browserPage.evaluate(() => {
1165
- const nav = performance.getEntriesByType("navigation")[0];
1166
- const paint = performance.getEntriesByName("first-contentful-paint")[0];
1266
+ const perf = globalThis.performance;
1267
+ const nav = perf.getEntriesByType("navigation")[0];
1268
+ const paint = perf.getEntriesByName("first-contentful-paint")[0];
1167
1269
  return {
1168
- ttfb: nav ? nav.responseStart - nav.requestStart : null,
1270
+ ttfb: nav?.responseStart !== undefined && nav.requestStart !== undefined ? nav.responseStart - nav.requestStart : null,
1169
1271
  fcp: paint?.startTime ?? null,
1170
- domLoad: nav ? nav.domContentLoadedEventEnd - nav.startTime : null
1272
+ domLoad: nav?.domContentLoadedEventEnd !== undefined && nav.startTime !== undefined ? nav.domContentLoadedEventEnd - nav.startTime : null
1171
1273
  };
1172
1274
  });
1173
1275
  if (metrics.fcp !== null || metrics.ttfb !== null) {
@@ -1212,7 +1314,7 @@ function startScheduler(db) {
1212
1314
  }
1213
1315
  function scheduleJob(db, jobId, schedule, projectId, pageId) {
1214
1316
  if (tasks.has(jobId))
1215
- tasks.get(jobId).stop();
1317
+ tasks.get(jobId)?.stop();
1216
1318
  const task = import_node_cron.default.schedule(schedule, async () => {
1217
1319
  await runJob(db, jobId, projectId, pageId);
1218
1320
  });
@@ -1231,11 +1333,468 @@ async function runJob(db, jobId, projectId, pageId) {
1231
1333
  perf_score: result.perfScore ?? undefined
1232
1334
  });
1233
1335
  } catch (err) {
1234
- finishScanRun(db, run.id, { status: "failed", logs_collected: 0, errors_found: 0 });
1336
+ finishScanRun(db, run.id, {
1337
+ status: "failed",
1338
+ logs_collected: 0,
1339
+ errors_found: 0
1340
+ });
1235
1341
  console.error(`Scan failed for page ${page.id}:`, err);
1236
1342
  }
1237
1343
  }));
1238
1344
  updateJob(db, jobId, { last_run_at: new Date().toISOString() });
1239
1345
  }
1240
1346
 
1241
- export { runRetentionForProject, setRetentionPolicy, setPageAuth, deletePageAuth, startScheduler, runJob };
1347
+ // src/lib/structured-logs.ts
1348
+ import { createHash as createHash2 } from "crypto";
1349
+ var STRUCTURED_LOG_SOURCES = new Set([
1350
+ "sdk",
1351
+ "script",
1352
+ "scanner",
1353
+ "browser",
1354
+ "node",
1355
+ "bun",
1356
+ "next",
1357
+ "vite",
1358
+ "cli",
1359
+ "build",
1360
+ "test",
1361
+ "mcp",
1362
+ "agent",
1363
+ "otel",
1364
+ "system",
1365
+ "pino",
1366
+ "winston",
1367
+ "structured"
1368
+ ]);
1369
+ var PINO_LEVELS = new Map([
1370
+ [10, "debug"],
1371
+ [20, "debug"],
1372
+ [30, "info"],
1373
+ [40, "warn"],
1374
+ [50, "error"],
1375
+ [60, "fatal"]
1376
+ ]);
1377
+ var WINSTON_NUMERIC_LEVELS = new Map([
1378
+ [0, "error"],
1379
+ [1, "warn"],
1380
+ [2, "info"],
1381
+ [3, "info"],
1382
+ [4, "debug"],
1383
+ [5, "debug"],
1384
+ [6, "debug"]
1385
+ ]);
1386
+ var STRING_LEVELS = new Map([
1387
+ ["trace", "debug"],
1388
+ ["debug", "debug"],
1389
+ ["silly", "debug"],
1390
+ ["verbose", "debug"],
1391
+ ["http", "info"],
1392
+ ["info", "info"],
1393
+ ["notice", "info"],
1394
+ ["warn", "warn"],
1395
+ ["warning", "warn"],
1396
+ ["error", "error"],
1397
+ ["err", "error"],
1398
+ ["fatal", "fatal"],
1399
+ ["crit", "fatal"],
1400
+ ["critical", "fatal"],
1401
+ ["panic", "fatal"]
1402
+ ]);
1403
+ function validateStructuredLogReferences(db, entries) {
1404
+ for (let index = 0;index < entries.length; index += 1) {
1405
+ const entry = entries[index];
1406
+ if (!entry)
1407
+ continue;
1408
+ if (entry.project_id && !projectExists(db, entry.project_id)) {
1409
+ throw new Error(`entry[${index}].project_id does not exist`);
1410
+ }
1411
+ if (entry.page_id) {
1412
+ const page = pageProject(db, entry.page_id);
1413
+ if (!page)
1414
+ throw new Error(`entry[${index}].page_id does not exist`);
1415
+ if (entry.project_id && page.project_id !== entry.project_id) {
1416
+ throw new Error(`entry[${index}].page_id does not belong to entry[${index}].project_id`);
1417
+ }
1418
+ }
1419
+ }
1420
+ }
1421
+ function structuredLogPayloadToEntries(payload, options = {}) {
1422
+ if (Array.isArray(payload)) {
1423
+ return {
1424
+ entries: payload.map((record, index) => structuredLogToEntry(record, options, { index })),
1425
+ batch: true
1426
+ };
1427
+ }
1428
+ if (isRecord(payload) && Array.isArray(payload.logs)) {
1429
+ const envelope = payload;
1430
+ const logs = envelope.logs;
1431
+ const envelopeOptions = optionsFromEnvelope(envelope, options);
1432
+ return {
1433
+ entries: logs.map((record, index) => structuredLogToEntry(record, envelopeOptions, { index })),
1434
+ batch: true
1435
+ };
1436
+ }
1437
+ return {
1438
+ entries: [structuredLogToEntry(payload, options)],
1439
+ batch: false
1440
+ };
1441
+ }
1442
+ function parseStructuredJsonLines(input, options = {}, source = "jsonl") {
1443
+ const entries = [];
1444
+ const lines = input.split(/\r?\n/);
1445
+ for (let index = 0;index < lines.length; index += 1) {
1446
+ const line = lines[index]?.trim();
1447
+ if (!line)
1448
+ continue;
1449
+ let parsed;
1450
+ try {
1451
+ parsed = JSON.parse(line);
1452
+ } catch (error) {
1453
+ throw new Error(`line ${index + 1}: invalid JSON (${errorMessage(error)})`);
1454
+ }
1455
+ entries.push(structuredLogToEntry(parsed, options, {
1456
+ index: entries.length,
1457
+ line: index + 1,
1458
+ source
1459
+ }));
1460
+ }
1461
+ return entries;
1462
+ }
1463
+ function ingestStructuredJsonLines(db, input, options = {}, source = "jsonl") {
1464
+ const entries = parseStructuredJsonLines(input, options, source);
1465
+ validateStructuredLogReferences(db, entries);
1466
+ return entries.map((entry) => ingestLog(db, entry));
1467
+ }
1468
+ function structuredLogToEntry(value, options = {}, position = {}) {
1469
+ if (!isRecord(value)) {
1470
+ throw new Error("structured log record must be an object");
1471
+ }
1472
+ const format = detectFormat(value, normalizeFormat(options.format ?? "auto"));
1473
+ const message = messageFromRecord(value);
1474
+ const level = levelFromRecord(value, format);
1475
+ const eventTime = timestampFromRecord(value);
1476
+ const producerEventId = producerId(value);
1477
+ const idRecord = redactedRecordForId(value);
1478
+ const sourceEventId = structuredSourceEventId(idRecord, format, producerEventId, position, options.source_event_prefix);
1479
+ const source = options.source ?? defaultSource(format);
1480
+ assertSupportedSource(source);
1481
+ const service = firstString([
1482
+ options.service,
1483
+ stringValue(value.service),
1484
+ stringPath(value, ["service", "name"]),
1485
+ stringValue(value.name),
1486
+ stringValue(value.logger),
1487
+ stringPath(value, ["logger", "name"])
1488
+ ]);
1489
+ const traceId = firstString([
1490
+ options.trace_id,
1491
+ stringValue(value.trace_id),
1492
+ stringValue(value.traceId),
1493
+ stringPath(value, ["trace", "id"]),
1494
+ stringPath(value, ["otel", "trace_id"]),
1495
+ stringValue(value["dd.trace_id"])
1496
+ ]);
1497
+ const spanId = firstString([
1498
+ options.span_id,
1499
+ stringValue(value.span_id),
1500
+ stringValue(value.spanId),
1501
+ stringPath(value, ["span", "id"]),
1502
+ stringPath(value, ["otel", "span_id"]),
1503
+ stringValue(value["dd.span_id"])
1504
+ ]);
1505
+ const metadata = compactObject({
1506
+ ...options.metadata ?? {},
1507
+ structured_log: compactObject({
1508
+ format,
1509
+ source,
1510
+ producer_event_id: redactedProducerId(producerEventId),
1511
+ position: compactObject({ ...position }),
1512
+ level_raw: value.level ?? value.severity ?? value.severityText,
1513
+ time_raw: value.time ?? value.timestamp ?? value["@timestamp"] ?? value.datetime ?? value.date,
1514
+ logger: firstString([
1515
+ stringValue(value.name),
1516
+ stringValue(value.logger),
1517
+ stringPath(value, ["logger", "name"])
1518
+ ]),
1519
+ pid: numberOrString(value.pid) ?? numberOrString(isRecord(value.process) ? value.process.pid : undefined),
1520
+ hostname: stringValue(value.hostname) ?? stringPath(value, ["host", "name"]),
1521
+ original: value
1522
+ })
1523
+ });
1524
+ return compactObject({
1525
+ id: sourceEventId ? `log_struct_${stableHash(sourceEventId).slice(0, 32)}` : undefined,
1526
+ timestamp: eventTime,
1527
+ source_event_id: sourceEventId,
1528
+ project_id: options.project_id ?? stringValue(value.project_id),
1529
+ page_id: options.page_id ?? stringValue(value.page_id),
1530
+ level,
1531
+ source,
1532
+ service,
1533
+ message,
1534
+ privacy: options.privacy,
1535
+ machine_id: options.machine_id ?? stringValue(value.machine_id) ?? stringPath(value, ["host", "id"]) ?? stringValue(value.hostname) ?? stringPath(value, ["host", "name"]),
1536
+ repo_id: options.repo_id ?? stringValue(value.repo_id),
1537
+ app_id: options.app_id ?? stringValue(value.app_id) ?? stringPath(value, ["service", "name"]) ?? service,
1538
+ process_id: options.process_id ?? stringValue(value.process_id),
1539
+ run_id: options.run_id ?? stringValue(value.run_id),
1540
+ trace_id: traceId,
1541
+ span_id: spanId,
1542
+ parent_span_id: options.parent_span_id ?? stringValue(value.parent_span_id) ?? stringValue(value.parentSpanId),
1543
+ session_id: options.session_id ?? stringValue(value.session_id) ?? stringValue(value.sessionId),
1544
+ release_id: options.release_id ?? stringValue(value.release_id) ?? stringValue(value.release) ?? stringValue(value.version),
1545
+ environment: options.environment ?? stringValue(value.environment) ?? stringValue(value.env) ?? stringPath(value, ["deployment", "environment"]),
1546
+ agent: options.agent ?? stringValue(value.agent),
1547
+ url: options.url ?? stringValue(value.url) ?? stringPath(value, ["request", "url"]) ?? stringPath(value, ["req", "url"]),
1548
+ stack_trace: stringValue(value.stack) ?? stringPath(value, ["err", "stack"]) ?? stringPath(value, ["error", "stack"]) ?? stringPath(value, ["exception", "stacktrace"]),
1549
+ metadata
1550
+ });
1551
+ }
1552
+ function optionsFromEnvelope(envelope, defaults) {
1553
+ return compactObject({
1554
+ ...defaults,
1555
+ format: formatValue(envelope.format) ?? defaults.format,
1556
+ source: sourceValue(envelope.source) ?? defaults.source,
1557
+ service: stringValue(envelope.service) ?? defaults.service,
1558
+ project_id: stringValue(envelope.project_id) ?? defaults.project_id,
1559
+ page_id: stringValue(envelope.page_id) ?? defaults.page_id,
1560
+ machine_id: stringValue(envelope.machine_id) ?? defaults.machine_id,
1561
+ repo_id: stringValue(envelope.repo_id) ?? defaults.repo_id,
1562
+ app_id: stringValue(envelope.app_id) ?? defaults.app_id,
1563
+ process_id: stringValue(envelope.process_id) ?? defaults.process_id,
1564
+ run_id: stringValue(envelope.run_id) ?? defaults.run_id,
1565
+ trace_id: stringValue(envelope.trace_id) ?? defaults.trace_id,
1566
+ span_id: stringValue(envelope.span_id) ?? defaults.span_id,
1567
+ parent_span_id: stringValue(envelope.parent_span_id) ?? defaults.parent_span_id,
1568
+ session_id: stringValue(envelope.session_id) ?? defaults.session_id,
1569
+ release_id: stringValue(envelope.release_id) ?? defaults.release_id,
1570
+ environment: stringValue(envelope.environment) ?? defaults.environment,
1571
+ agent: stringValue(envelope.agent) ?? defaults.agent,
1572
+ url: stringValue(envelope.url) ?? defaults.url,
1573
+ source_event_prefix: stringValue(envelope.source_event_prefix) ?? defaults.source_event_prefix,
1574
+ metadata: metadataValue(envelope.metadata) ?? defaults.metadata
1575
+ });
1576
+ }
1577
+ function detectFormat(value, requested) {
1578
+ if (requested !== "auto")
1579
+ return requested;
1580
+ if (typeof value.level === "number" || "msg" in value)
1581
+ return "pino";
1582
+ if (typeof value.level === "string" && "message" in value)
1583
+ return "winston";
1584
+ return "json";
1585
+ }
1586
+ function normalizeFormat(value) {
1587
+ if (value === "auto" || value === "pino" || value === "winston" || value === "json") {
1588
+ return value;
1589
+ }
1590
+ throw new Error("format must be auto, pino, winston, or json");
1591
+ }
1592
+ function defaultSource(format) {
1593
+ if (format === "pino")
1594
+ return "pino";
1595
+ if (format === "winston")
1596
+ return "winston";
1597
+ return "structured";
1598
+ }
1599
+ function levelFromRecord(value, format) {
1600
+ const raw = value.level ?? value.severity ?? value.severityText;
1601
+ if (raw === undefined || raw === null || raw === "")
1602
+ return "info";
1603
+ if (typeof raw === "number") {
1604
+ const mapped = format === "winston" ? WINSTON_NUMERIC_LEVELS.get(raw) : PINO_LEVELS.get(raw) ?? WINSTON_NUMERIC_LEVELS.get(raw);
1605
+ if (mapped)
1606
+ return mapped;
1607
+ throw new Error(`unsupported numeric log level: ${raw}`);
1608
+ }
1609
+ if (typeof raw === "string") {
1610
+ const numeric = Number(raw);
1611
+ if (Number.isFinite(numeric) && raw.trim() !== "") {
1612
+ return levelFromRecord({ level: numeric }, format);
1613
+ }
1614
+ const mapped = STRING_LEVELS.get(raw.toLowerCase());
1615
+ if (mapped)
1616
+ return mapped;
1617
+ }
1618
+ throw new Error(`unsupported log level: ${String(raw)}`);
1619
+ }
1620
+ function messageFromRecord(value) {
1621
+ const raw = value.msg ?? value.message ?? value.body ?? stringPath(value, ["err", "message"]) ?? stringPath(value, ["error", "message"]);
1622
+ const message = messageValue(raw);
1623
+ if (!message)
1624
+ throw new Error("structured log record must include msg or message");
1625
+ return message;
1626
+ }
1627
+ function messageValue(value) {
1628
+ if (typeof value === "string") {
1629
+ const trimmed = value.trim();
1630
+ return trimmed.length > 0 ? value : null;
1631
+ }
1632
+ if (typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
1633
+ return String(value);
1634
+ }
1635
+ if (value && typeof value === "object") {
1636
+ return stableStringify(value);
1637
+ }
1638
+ return null;
1639
+ }
1640
+ function timestampFromRecord(value) {
1641
+ const raw = value.time ?? value.timestamp ?? value["@timestamp"] ?? value.datetime ?? value.date;
1642
+ if (raw === undefined || raw === null || raw === "")
1643
+ return;
1644
+ const timestamp = timestampValue(raw);
1645
+ if (!timestamp)
1646
+ throw new Error(`unsupported timestamp: ${String(raw)}`);
1647
+ return timestamp;
1648
+ }
1649
+ function timestampValue(value) {
1650
+ if (typeof value === "number")
1651
+ return epochNumberToIso(value);
1652
+ if (typeof value === "string") {
1653
+ const trimmed = value.trim();
1654
+ if (!trimmed)
1655
+ return null;
1656
+ const numeric = Number(trimmed);
1657
+ if (Number.isFinite(numeric))
1658
+ return epochNumberToIso(numeric);
1659
+ const parsed = new Date(trimmed);
1660
+ if (!Number.isNaN(parsed.getTime()))
1661
+ return parsed.toISOString();
1662
+ }
1663
+ return null;
1664
+ }
1665
+ function epochNumberToIso(value) {
1666
+ if (!Number.isFinite(value))
1667
+ return null;
1668
+ let milliseconds = value;
1669
+ if (value > 10000000000000000) {
1670
+ milliseconds = value / 1e6;
1671
+ } else if (value > 10000000000000) {
1672
+ milliseconds = value / 1000;
1673
+ } else if (value < 10000000000) {
1674
+ milliseconds = value * 1000;
1675
+ }
1676
+ const date = new Date(milliseconds);
1677
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
1678
+ }
1679
+ function producerId(value) {
1680
+ return firstString([
1681
+ stringValue(value.id),
1682
+ stringValue(value.event_id),
1683
+ stringValue(value.eventId),
1684
+ stringValue(value.source_event_id),
1685
+ stringValue(value.log_id),
1686
+ stringValue(value.logId),
1687
+ stringValue(value._open_logs_event_id)
1688
+ ]) ?? null;
1689
+ }
1690
+ function structuredSourceEventId(value, format, producerEventId, position, prefix = "structured") {
1691
+ if (producerEventId) {
1692
+ const redacted = redactString(producerEventId, "source_event_id");
1693
+ if (!redacted.report.applied) {
1694
+ return `${prefix}:${format}:producer:${stableHash(redacted.value)}`;
1695
+ }
1696
+ }
1697
+ if (!hasPosition(position))
1698
+ return;
1699
+ return `${prefix}:${format}:${stableHash({
1700
+ position,
1701
+ timestamp: value.time ?? value.timestamp ?? value["@timestamp"] ?? value.datetime ?? value.date,
1702
+ level: value.level ?? value.severity ?? value.severityText,
1703
+ message: value.msg ?? value.message ?? value.body,
1704
+ service: value.service ?? value.name ?? value.logger,
1705
+ original: value
1706
+ })}`;
1707
+ }
1708
+ function redactedProducerId(value) {
1709
+ return value ? redactString(value, "metadata.structured_log.producer_event_id").value : undefined;
1710
+ }
1711
+ function redactedRecordForId(value) {
1712
+ return redactValue(value, "structured_log_id").value;
1713
+ }
1714
+ function hasPosition(position) {
1715
+ return position.index !== undefined || position.line !== undefined || position.source !== undefined;
1716
+ }
1717
+ function assertSupportedSource(source) {
1718
+ if (!STRUCTURED_LOG_SOURCES.has(source)) {
1719
+ throw new Error(`unsupported structured log source: ${source}`);
1720
+ }
1721
+ }
1722
+ function formatValue(value) {
1723
+ if (value === undefined)
1724
+ return;
1725
+ return normalizeFormat(value);
1726
+ }
1727
+ function sourceValue(value) {
1728
+ if (value === undefined)
1729
+ return;
1730
+ const source = stringValue(value);
1731
+ if (!source)
1732
+ throw new Error("source must be a string");
1733
+ assertSupportedSource(source);
1734
+ return source;
1735
+ }
1736
+ function metadataValue(value) {
1737
+ if (value === undefined)
1738
+ return;
1739
+ if (!isRecord(value))
1740
+ throw new Error("metadata must be an object");
1741
+ return value;
1742
+ }
1743
+ function stringValue(value) {
1744
+ if (typeof value === "string" && value.length > 0)
1745
+ return value;
1746
+ if (typeof value === "number" || typeof value === "bigint")
1747
+ return String(value);
1748
+ return;
1749
+ }
1750
+ function numberOrString(value) {
1751
+ if (typeof value === "number" && Number.isFinite(value))
1752
+ return value;
1753
+ if (typeof value === "string" && value.length > 0)
1754
+ return value;
1755
+ return;
1756
+ }
1757
+ function stringPath(value, path) {
1758
+ let current = value;
1759
+ for (const part of path) {
1760
+ if (!isRecord(current))
1761
+ return;
1762
+ current = current[part];
1763
+ }
1764
+ return stringValue(current);
1765
+ }
1766
+ function firstString(values) {
1767
+ return values.find((value) => typeof value === "string" && value.length > 0);
1768
+ }
1769
+ function compactObject(value) {
1770
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
1771
+ }
1772
+ function stableHash(value) {
1773
+ return createHash2("sha256").update(stableStringify(value)).digest("hex");
1774
+ }
1775
+ function stableStringify(value) {
1776
+ if (value === undefined)
1777
+ return "undefined";
1778
+ if (typeof value === "bigint")
1779
+ return JSON.stringify(value.toString());
1780
+ if (value === null || typeof value !== "object")
1781
+ return JSON.stringify(value);
1782
+ if (Array.isArray(value))
1783
+ return `[${value.map(stableStringify).join(",")}]`;
1784
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`).join(",")}}`;
1785
+ }
1786
+ function isRecord(value) {
1787
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
1788
+ }
1789
+ function errorMessage(error) {
1790
+ return error instanceof Error ? error.message : String(error);
1791
+ }
1792
+ function projectExists(db, projectId) {
1793
+ const row = db.prepare("SELECT id FROM projects WHERE id = ?").get(projectId);
1794
+ return Boolean(row);
1795
+ }
1796
+ function pageProject(db, pageId) {
1797
+ return db.prepare("SELECT project_id FROM pages WHERE id = ?").get(pageId);
1798
+ }
1799
+
1800
+ export { runRetentionForProject, setRetentionPolicy, setPageAuth, deletePageAuth, startScheduler, runJob, validateStructuredLogReferences, structuredLogPayloadToEntries, ingestStructuredJsonLines, structuredLogToEntry };