Newer
Older
express-blog / src / utils / logging / consolePatch.js
// src/utils/logging/consolePatch.js
const util = require("util");

const { LOG_LEVEL, LOG_LEVELS } = require("../../config/logging");

function shouldLog(level) {
  return LOG_LEVELS[level.toLowerCase()] <= LOG_LEVELS[LOG_LEVEL];
}

const originalConsole = { ...console };

function patchConsole(logStreams, sessionTransport) {
  console.log = (...args) =>
    writeLog(
      "INFO",
      logStreams.info,
      sessionTransport,
      originalConsole.log,
      ...args,
    );
  console.error = (...args) =>
    writeLog(
      "ERROR",
      logStreams.error,
      sessionTransport,
      originalConsole.error,
      ...args,
    );
  console.warn = (...args) =>
    writeLog(
      "WARN",
      logStreams.warn,
      sessionTransport,
      originalConsole.warn,
      ...args,
    );
  console.info = (...args) =>
    writeLog(
      "INFO",
      logStreams.info,
      sessionTransport,
      originalConsole.info,
      ...args,
    );
  console.debug = (...args) =>
    writeLog(
      "DEBUG",
      logStreams.debug,
      sessionTransport,
      originalConsole.debug,
      ...args,
    );
  return originalConsole;
}

function unpatchConsole() {
  console.log = originalConsole.log;
  console.error = originalConsole.error;
  console.warn = originalConsole.warn;
  console.info = originalConsole.info;
  console.debug = originalConsole.debug;
}
function getCircularReplacer() {
  const seen = new WeakSet();
  return (key, value) => {
    if (typeof value === "object" && value !== null) {
      if (seen.has(value)) {
        return "[Circular]";
      }
      seen.add(value);
    }
    return value;
  };
}
function formatArg(arg) {
  if (arg instanceof Error) {
    return JSON.stringify(
      {
        name: arg.name,
        message: arg.message,
        stack: arg.stack,
      },
      null,
      2,
    );
  }

  if (arg instanceof RegExp) {
    return arg.toString();
  }

  if (typeof arg === "object" && arg !== null) {
    try {
      return JSON.stringify(arg, getCircularReplacer(), 2);
    } catch {
      return util.inspect(arg, { depth: null, colors: false });
    }
  }

  return String(arg);
}

function formatLog(level, ...args) {
  const timestamp = new Date().toISOString();
  const safeArgs = args.map(formatArg);
  const message = safeArgs.join(" ");
  const logLine = `[${timestamp}] [${level}] ${message}\n`;

  return { timestamp, safeArgs, message, logLine };
}

function writeLog(level, stream, sessionTransport, consoleFn, ...args) {
  if (!shouldLog(level)) return;

  const { timestamp, safeArgs, message, logLine } = formatLog(level, ...args);

  stream.write(logLine);
  if (!sessionTransport) {
    originalConsole.warn(
      `sessionTransport for log level '${level} is undefined`,
    );
  } else {
    sessionTransport.write({ level: level.toLowerCase(), message, timestamp });
  }
  if (consoleFn) {
    consoleFn(`[${timestamp}] [${level}]`, ...safeArgs);
  }
}

module.exports = {
  patchConsole,
  unpatchConsole,
  shouldLog,
  writeLog,
  formatLog,
};