feat(logs): add Logs Dashboard with real-time Console Viewer

- Consolidated 4-tab Logs page: Request Logs, Proxy Logs, Audit Logs, Console
- Terminal-style Console Log Viewer with level filter, search, auto-scroll
- Console interceptor captures all console.log/warn/error to JSON log file
- Integrated in Next.js instrumentation.ts for both dev and prod
- Log rotation by size + retention-based cleanup
- Fixed pino logger file transport (pino/file targets only)
- Moved initAuditLog() to instrumentation.ts for proper initialization
- Bumped version to 1.0.3
- Updated CHANGELOG, all 8 README translations, and .env.example
This commit is contained in:
diegosouzapw
2026-02-19 04:38:04 -03:00
parent 2ddf63fde1
commit 1e332babd6
27 changed files with 1244 additions and 62 deletions

View File

@@ -9,25 +9,99 @@
*
* In development, output is pretty-printed via pino-pretty.
* In production, output is structured JSON for log aggregation.
*
* When LOG_TO_FILE is enabled (default: true), logs are also written
* as JSON lines to the file specified by LOG_FILE_PATH.
*/
import pino from "pino";
import { resolve } from "path";
import { getLogConfig, initLogRotation } from "@/lib/logRotation";
const isDev = process.env.NODE_ENV !== "production";
const baseConfig = {
const baseConfig: pino.LoggerOptions = {
level: process.env.LOG_LEVEL || (isDev ? "debug" : "info"),
base: { service: "omniroute" },
timestamp: pino.stdTimeFunctions.isoTime,
formatters: {
level(label) {
level(label: string) {
return { level: label };
},
},
};
// In development, use pino-pretty for human-readable output
const devTransport = isDev
? {
/**
* Build the logger with optional file transport.
* Uses pino transport targets for all destinations.
*/
function buildLogger(): pino.Logger {
const logConfig = getLogConfig();
const logLevel = (baseConfig.level as string) || "info";
// If file logging is enabled, set up dual transport (stdout + file)
if (logConfig.logToFile) {
try {
// Initialize log directory and rotation
initLogRotation();
// Resolve to absolute path for pino worker threads
const absLogPath = resolve(logConfig.logFilePath);
if (isDev) {
// Dev: pino-pretty → stdout, JSON → file
return pino({
...baseConfig,
transport: {
targets: [
{
target: "pino-pretty",
options: {
colorize: true,
translateTime: "HH:MM:ss.l",
ignore: "pid,hostname,service",
messageFormat: "[{module}] {msg}",
destination: 1,
},
level: logLevel,
},
{
target: "pino/file",
options: { destination: absLogPath, mkdir: true },
level: logLevel,
},
],
},
});
}
// Production: JSON → stdout + JSON → file
return pino({
...baseConfig,
transport: {
targets: [
{
target: "pino/file",
options: { destination: 1 }, // stdout
level: logLevel,
},
{
target: "pino/file",
options: { destination: absLogPath, mkdir: true },
level: logLevel,
},
],
},
});
} catch {
// If file setup fails, fall back to console-only logging
console.warn("[logger] Failed to set up file transport, falling back to console only");
}
}
// Console-only (no file logging)
if (isDev) {
return pino({
...baseConfig,
transport: {
target: "pino-pretty",
options: {
@@ -37,17 +111,20 @@ const devTransport = isDev
messageFormat: "[{module}] {msg}",
},
},
}
: {};
});
}
export const logger = pino({ ...baseConfig, ...devTransport });
return pino(baseConfig);
}
export const logger = buildLogger();
/**
* Create a child logger with a module tag.
* @param {string} module - Module name for log context (e.g., "proxy", "db", "sse")
* @returns {pino.Logger}
*/
export function createLogger(module) {
export function createLogger(module: string) {
return logger.child({ module });
}