UNPKG

logsdx

Version:

log streaming with dx on the 🧠

361 lines (354 loc) • 10.5 kB
import { logger } from "./chunk-4MUAUYPX.js"; import { loadConfig, setTheme, styleManager } from "./chunk-T6FLUOTW.js"; // src/cli/index.ts import { Command } from "commander"; import fs2 from "fs"; // src/parsers/regex/line.ts function createRegexLineParser(rules) { return (line) => { let result; for (const rule of rules) { const match = line.match(rule.match); if (match) { const extracted = rule.extract?.(line, match) || {}; if (extracted.timestamp && extracted.level && extracted.message) { return extracted; } result = { ...extracted, ...result // This ensures earlier matches take precedence }; } } if (result) { if (!result.timestamp) { const timestampMatch = line.match( /(\d{4}-\d{2}-\d{2}(?:T|\s+)\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)/ ); if (timestampMatch) { result.timestamp = timestampMatch[1]; } } if (!result.message) { const messageMatch = line.match( /(?:\]|\d{2}(?:Z|[+-]\d{2}:?\d{2})?)\s+(?:\[\w+\])?\s+(.+)$/ ); if (messageMatch) { result.message = messageMatch[1]; } } return result; } return void 0; }; } // src/parsers/json/index.ts import fs from "fs"; import JSON5 from "json5"; var DEFAULT_JSON_RULES = [ { match: "\\{.*\\}", lang: "json", level: "info", meta: { service: "service", timestamp: "timestamp", message: "message", level: "level" } } ]; function mapLogLevel(level) { const levelMap = { // Standard levels debug: "debug", info: "info", warn: "warn", warning: "warn", error: "error", err: "error", success: "success", trace: "trace", // Common variations fatal: "error", critical: "error", notice: "info", verbose: "debug", silly: "debug", // Status-based levels fail: "error", failed: "error", failure: "error", pending: "warn", in_progress: "info", completed: "success" }; const normalizedLevel = level.toLowerCase().trim(); return levelMap[normalizedLevel] || "info"; } async function loadJsonRules(filePath) { let rules = DEFAULT_JSON_RULES; if (filePath) { try { const data = fs.readFileSync(filePath, "utf-8"); const customRules = JSON5.parse(data); if (Array.isArray(customRules) && customRules.length > 0) { rules = customRules; } } catch (error) { logger.warn( `Failed to load custom JSON rules: ${JSON5.stringify(error)}` ); logger.info("Using default JSON rules instead"); } } return (line) => { try { if (!line.trim().startsWith("{") || !line.trim().endsWith("}")) { return void 0; } const json = JSON5.parse(line); const result = { level: mapLogLevel( json.level || json.status || json.severity || "info" ), timestamp: json.timestamp || json.time || json.date || json["@timestamp"], message: json.message || json.msg || json.log || json.text, language: "json" }; for (const [key, value] of Object.entries(json)) { if (![ "level", "status", "severity", "timestamp", "time", "date", "@timestamp", "message", "msg", "log", "text" ].includes(key)) { result[key] = value; } } return result; } catch (error) { return void 0; } }; } // src/parsers/default.ts var defaultLineParser = (line) => { const result = {}; if (line.startsWith("[json]")) result.lang = "json"; if (line.startsWith("[sql]")) result.lang = "sql"; if (line.includes("ERROR")) result.level = "error"; if (line.includes("WARN")) result.level = "warn"; if (line.includes("INFO")) result.level = "info"; return result; }; // src/parsers/registry.ts var DEFAULT_CONFIG = { defaultRules: [ { match: /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z)\s+\[(\w+)\]\s+(.*)$/, extract: (line, match) => ({ timestamp: match[1], level: match[2].toLowerCase(), message: match[3] }) } ], levels: { debug: 0, info: 1, warn: 2, error: 3 } }; var parserRegistry = { // Default parser default: async () => defaultLineParser, // Built-in parsers regex: async () => { return createRegexLineParser(DEFAULT_CONFIG.defaultRules); }, json: async (options) => { return loadJsonRules(options?.rulesFile); } }; function registerParser(name, factory) { parserRegistry[name] = factory; } async function getParser(name, options) { if (!parserRegistry[name]) { throw new Error( `Parser '${name}' not found. Available parsers: ${Object.keys(parserRegistry).join(", ")}` ); } return parserRegistry[name](options); } function getRegisteredParsers() { return Object.keys(parserRegistry); } // src/cli/index.ts var program = new Command(); program.name("logsdx").description("A powerful log parsing and formatting tool").version("0.0.1"); program.option("-q, --quiet", "Suppress all output except errors").option("-d, --debug", "Enable debug mode").option("-l, --level <level>", "Minimum log level to display", "info").option("-p, --parser <parser>", "Parser to use for log parsing", "default").option("-r, --rules <file>", "Path to custom rules file").option("-o, --output <file>", "Path to output file").option("--list-parsers", "List available parsers").option( "-t, --theme <theme>", "Theme to use (default, dark, light, minimal, or custom theme name)" ).option("--list-themes", "List available themes").argument("[input]", "Input file to process (defaults to stdin)").action(async (input, options) => { try { if (options.listParsers) { const parsers = getRegisteredParsers(); logger.info("Available parsers:"); parsers.forEach((parser2) => logger.info(` - ${parser2}`)); process.exit(0); } if (options.listThemes) { const config2 = loadConfig(); const themes = config2 ? Object.keys(config2.customThemes || {}) : []; logger.info("Available themes:"); logger.info("Built-in themes:"); logger.info(" - default"); logger.info(" - dark"); logger.info(" - light"); logger.info(" - minimal"); if (themes.length > 0) { logger.info("Custom themes:"); themes.forEach((theme) => logger.info(` - ${theme}`)); } process.exit(0); } if (options.debug) { logger.withConfig({ level: "debug", prefix: "CLI" }).debug("Debug mode enabled"); } const config = loadConfig(); if (options.theme) { if (options.debug) { logger.withConfig({ level: "debug", prefix: "CLI" }).debug(`Using theme: ${options.theme}`); } setTheme(options.theme); } else if (config?.theme) { if (options.debug) { logger.withConfig({ level: "debug", prefix: "CLI" }).debug(`Using theme from config: ${config.theme}`); } setTheme(config.theme); } if (options.level && !["debug", "info", "warn", "error"].includes(options.level)) { throw new Error(`Invalid log level: ${options.level}`); } const inputStream = input ? fs2.createReadStream(input) : process.stdin; if (input && options.debug) { logger.withConfig({ level: "debug", prefix: "CLI" }).debug(`Input file set to: ${input}`); } const outputStream = options.output ? fs2.createWriteStream(options.output) : process.stdout; if (options.output && options.debug) { logger.withConfig({ level: "debug", prefix: "CLI" }).debug(`Output file set to: ${options.output}`); } const parser = await getParserForOptions(options); let buffer = ""; inputStream.on("data", (chunk) => { buffer += chunk.toString(); const lines = buffer.split("\n"); buffer = lines.pop() || ""; for (const line of lines) { if (!line.trim()) continue; const parsed = parser(line); const level = parsed?.level || "info"; if (shouldRender(level, options.level)) { const formattedLine = styleManager.styleLine( line, parsed, options.parser || "default" ); if (outputStream === process.stdout) { process.stdout.write(formattedLine + "\n"); } else { outputStream.write(formattedLine + "\n"); } } } }); inputStream.on("end", () => { if (buffer.trim()) { const parsed = parser(buffer); const level = parsed?.level || "info"; if (shouldRender(level, options.level)) { const formattedLine = styleManager.styleLine( buffer, parsed, options.parser || "default" ); if (outputStream === process.stdout) { process.stdout.write(formattedLine + "\n"); } else { outputStream.write(formattedLine + "\n"); } } } if (options.output) { outputStream.end(); } }); inputStream.on("error", (error) => { logger.error("Error reading input:", error); process.exit(1); }); } catch (error) { logger.error("Error:", error); process.exit(1); } }); program.parse(); async function getParserForOptions(options) { const parserName = options.parser || "default"; try { if (options.debug) { logger.withConfig({ level: "debug", prefix: "CLI" }).debug(`Using parser: ${parserName}`); } return await getParser(parserName, { rulesFile: options.rules }); } catch (error) { logger.error(`Error loading parser '${parserName}':`, error); logger.info(`Available parsers: ${getRegisteredParsers().join(", ")}`); process.exit(1); } } function shouldRender(level, minLevel) { if (!minLevel || !level) return true; const levelPriorities = { debug: 0, info: 1, warn: 2, error: 3, success: 1, trace: 0 }; const current = levelPriorities[level] ?? 0; const min = levelPriorities[minLevel] ?? 0; return current >= min; } export { createRegexLineParser, DEFAULT_JSON_RULES, loadJsonRules, defaultLineParser, registerParser, getParser, getRegisteredParsers, program, getParserForOptions, shouldRender }; //# sourceMappingURL=chunk-6XPS6F32.js.map