Files
jerapah-flow/packages/server/logger.js
T
nsrb 023cf83cfa feat: enhance project structure with server and workflow management
- Introduced a new server package with Fastify for handling API requests and workflows.
- Implemented user authentication and authorization with JWT and cookie management.
- Added endpoints for managing workflows, runs, and user accounts.
- Established a dashboard for monitoring workflow status and statistics.
- Included a script sandbox for executing user-defined scripts securely.
- Updated README with setup instructions and API documentation.
- Configured database migrations for user management.
- Enhanced logging capabilities for better traceability.
2026-08-14 14:25:37 +07:00

127 lines
2.7 KiB
JavaScript

import fs from "fs";
import path from "path";
import pino from "pino";
import * as store from "./store.js";
import { LOGS_DIR } from "./paths.js";
const LEVEL_TO_NUM = {
trace: 10,
debug: 20,
info: 30,
warn: 40,
error: 50,
fatal: 60,
};
const FLUSH_MS = 200;
/** @type {Array<{ runId: string, stepId?: string | null, ts: string, level: number, msg?: string | null, payload?: unknown }>} */
let buffer = [];
let flushChain = Promise.resolve();
let ready = false;
let timer = null;
/**
* @param {string} line
*/
function enqueueLine(line) {
let record;
try {
record = JSON.parse(line);
} catch {
return;
}
if (!record.runId) return;
const {
runId,
stepId = null,
time,
level,
msg = null,
...rest
} = record;
const ts =
typeof time === "number"
? new Date(time).toISOString()
: typeof time === "string"
? time
: new Date().toISOString();
const levelNum =
typeof level === "number"
? level
: LEVEL_TO_NUM[level] ?? LEVEL_TO_NUM.info;
const payload = Object.keys(rest).length ? rest : null;
buffer.push({ runId, stepId, ts, level: levelNum, msg, payload });
}
function scheduleFlush() {
if (timer) return;
timer = setTimeout(() => {
timer = null;
void flush();
}, FLUSH_MS);
timer.unref?.();
}
async function flush() {
if (!ready || buffer.length === 0) return;
const batch = buffer;
buffer = [];
flushChain = flushChain
.then(() => store.insertLogs(batch))
.catch((err) => {
// Re-queue failed batch so we don't lose run-scoped logs on transient errors.
buffer = batch.concat(buffer);
console.error("failed to flush logs to sqlite", err);
});
await flushChain;
}
const sqliteStream = {
write(line) {
enqueueLine(typeof line === "string" ? line : String(line));
scheduleFlush();
},
};
fs.mkdirSync(LOGS_DIR, { recursive: true });
const rollingFile = pino.transport({
target: "pino-roll",
options: {
file: path.join(LOGS_DIR, "scrunner.log"),
size: "10m",
mkdir: true,
limit: { count: 5 },
},
});
export const log = pino(
{ level: process.env.SCRUNNER_LOG_LEVEL ?? "debug" },
pino.multistream([
{ level: "debug", stream: process.stdout },
{ level: "debug", stream: rollingFile },
{ level: "debug", stream: sqliteStream },
]),
);
/** Allow the SQLite log writer to flush after migrations have completed. */
export function enableLogPersistence() {
ready = true;
void flush();
}
/** Flush remaining buffered log lines (call on shutdown). */
export async function flushLogs() {
if (timer) {
clearTimeout(timer);
timer = null;
}
await flush();
await flushChain;
}