everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
267 lines (266 loc) • 6.51 kB
JavaScript
//#region src/dev-log-pipeline.ts
const ESC = "\x1B";
const BEL = "\x07";
const ANSI_RE = new RegExp(`${ESC}\\[[0-?]*[ -/]*[@-~]|${ESC}\\][^${BEL}]*${BEL}`, "g");
const stripAnsi = (input) => input.replace(ANSI_RE, "");
const LEVEL_RANK = {
error: 0,
warn: 1,
info: 2,
debug: 3
};
const LOG_LEVELS = [
"error",
"warn",
"info",
"debug"
];
function isDebug(value) {
return value === "true" || value === "1";
}
function resolveLogLevel(env = process.env, flag) {
if (flag && LOG_LEVELS.includes(flag)) return flag;
if (env.BOS_LOG_LEVEL && LOG_LEVELS.includes(env.BOS_LOG_LEVEL)) return env.BOS_LOG_LEVEL;
if (isDebug(env.DEBUG)) return "debug";
return "warn";
}
function toEffectLogLevel(level) {
switch (level) {
case "error": return "Error";
case "warn": return "Warn";
case "info": return "Info";
case "debug": return "Debug";
}
}
const STACK_FRAME_RE = /^\s+at\s/;
const countBraces = (line) => {
let count = 0;
for (const ch of line) if (ch === "{") count += 1;
else if (ch === "}") count -= 1;
return count;
};
const makeNormalizerState = () => ({
pending: [],
openBraces: 0
});
const pushLine = (state, line) => {
state.pending.push(line);
state.openBraces = Math.max(0, state.openBraces + countBraces(line));
};
const resetState = (state) => {
state.pending = [];
state.openBraces = 0;
};
const feedLine = (state, line, meta) => {
if (state.pending.length === 0) {
state.meta = meta;
pushLine(state, line);
return null;
}
if (state.openBraces > 0 || STACK_FRAME_RE.test(line) || /^\s*\{/.test(line)) {
const closeOnBalance = state.openBraces > 0;
pushLine(state, line);
if (closeOnBalance && state.openBraces === 0) {
const completed = {
text: state.pending.join("\n"),
meta: state.meta
};
resetState(state);
return completed;
}
return null;
}
const completed = {
text: state.pending.join("\n"),
meta: state.meta
};
resetState(state);
state.meta = meta;
pushLine(state, line);
return completed;
};
const drainState = (state) => {
if (state.pending.length === 0) return null;
const completed = {
text: state.pending.join("\n"),
meta: state.meta
};
resetState(state);
return completed;
};
const WARN_RE = /(?:^|\s)WARN\b|\[WARN\]|(?:^|\s)\w*Warning:/i;
const DEBUG_RE = /(?:^|\s)DEBUG\b|\[DEBUG\]/i;
const BUILD_DIAGNOSTIC_RE = /^(?:File: |\s*[×✖]\s)/;
const EXIT_LINE_RE = /^Process exited (?:after|before) ready \((?:exit code: (\d+)|signal: ([A-Z]+))\)/;
const CLEAN_SIGNALS = /* @__PURE__ */ new Set(["SIGTERM", "SIGINT"]);
const CATEGORY_PATTERNS = [
{
category: "shutdown",
patterns: [
EXIT_LINE_RE,
/\[SIGTERM\]/i,
/shutting down/i,
/graceful shutdown/i
]
},
{
category: "mf",
patterns: [
/\[ ?Federation Runtime ?\]/,
/\[MF\]/,
/Module Federation/i,
/Executing an Effect versioned/,
/you may want to dedupe the effect dependencies/,
/\[IntegrityMonitor\]/,
/\[Plugins\]/
]
},
{
category: "db",
patterns: [
/\[Database\]/i,
/\bpostgres\b/i,
/\bdrizzle\b/i,
/\bpglite\b/i
]
},
{
category: "banner",
patterns: [
/🚀|📡|📖|💚/,
/➜/,
/^[┌┐└┘│─┬┴├┤╔╗╚╝║═]/
]
},
{
category: "build",
patterns: [
/rspack\.config\.js not found/,
/rspack|rsbuild/i,
/compiled (?:success|with|in)/i,
/built in \d+/i,
/webpack/i,
/\bHMR\b/,
/building for production/i,
BUILD_DIAGNOSTIC_RE
]
},
{
category: "lifecycle",
patterns: [
/\bready in\b/i,
/listening (?:on|at)/i,
/server (?:is )?running/i
]
}
];
const classifyCategory = (text) => {
for (const { category, patterns } of CATEGORY_PATTERNS) if (patterns.some((pattern) => pattern.test(text))) return category;
return "other";
};
function classifyEvent(raw) {
const text = stripAnsi(raw.line);
const exitMatch = text.match(EXIT_LINE_RE);
const category = classifyCategory(text);
let level;
let isError = raw.isError === true;
if (exitMatch) {
if (exitMatch[2]) level = CLEAN_SIGNALS.has(exitMatch[2]) ? "info" : "error";
else level = exitMatch[1] === "0" ? "info" : "error";
isError = level === "error";
} else if (BUILD_DIAGNOSTIC_RE.test(text)) {
level = "error";
isError = true;
} else if (WARN_RE.test(text)) {
level = "warn";
isError = false;
} else if (DEBUG_RE.test(text)) {
level = "debug";
isError = false;
} else if (isError) level = "error";
else level = "info";
return {
timestamp: raw.timestamp ?? Date.now(),
source: raw.source,
line: text,
isError,
level,
category
};
}
function passesLevel(event, level) {
return LEVEL_RANK[event.level] <= LEVEL_RANK[level];
}
var DbCollapser = class {
runs = /* @__PURE__ */ new Set();
feed(event) {
if (event.category === "db" && event.level !== "error") {
if (!this.runs.has(event.source)) {
this.runs.add(event.source);
return {
...event,
line: "db ready",
isError: false,
level: "info",
collapsed: true
};
}
return null;
}
this.runs.delete(event.source);
return {
...event,
collapsed: false
};
}
};
function createLogPipeline(options) {
const level = options.level ?? resolveLogLevel();
const sinks = options.sinks;
const normalizers = /* @__PURE__ */ new Map();
const collapser = new DbCollapser();
const stateFor = (source) => {
let state = normalizers.get(source);
if (!state) {
state = makeNormalizerState();
normalizers.set(source, state);
}
return state;
};
const emit = (meta, text) => {
const event = classifyEvent({
source: meta.source,
line: text,
isError: meta.isError,
timestamp: meta.timestamp
});
sinks.file(event);
if (!passesLevel(event, level)) return;
const collapsed = collapser.feed(event);
if (!collapsed) return;
sinks.export(collapsed);
sinks.display(collapsed);
};
return {
ingest: (raw) => {
const clean = stripAnsi(raw.line);
if (!clean.trim()) return;
const state = stateFor(raw.source);
const completed = feedLine(state, clean, {
source: raw.source,
isError: raw.isError,
timestamp: raw.timestamp
});
if (completed !== null) emit(completed.meta, completed.text);
},
flush: () => {
for (const state of normalizers.values()) {
const completed = drainState(state);
if (completed !== null) emit(completed.meta, completed.text);
}
}
};
}
//#endregion
export { classifyEvent, createLogPipeline, isDebug, passesLevel, resolveLogLevel, stripAnsi, toEffectLogLevel };
//# sourceMappingURL=dev-log-pipeline.mjs.map