mod_utils_logger.js

/**
## logger 🪵
This module provides a logging utility for the xyz.
 * @module /utils/logger
 */

const crypto = require('crypto');

const logs = new Set(process.env.LOGS?.split(',') || []);

// Errors should always be logged.
logs.add('err');

const process_id = crypto.randomBytes(3).toString('hex');

const logout = {
  logflare,
  postgresql
};

// Required to initialse PostgreSQL logger.
const { Pool } = require('pg');

const logger = process.env.LOGGER
  && Object.hasOwn(logout, process.env.LOGGER.split(':')[0])
  && logout[process.env.LOGGER.split(':')[0]]();

/**
 * Logs a message to the configured logger or console.
 * @function logger
 * @param {string|Object} log - The message or object to log.
 * @param {string} [key='err'] - The log level or key.
 * @returns {void}
 */
module.exports = (log, key = 'err') => {
  // Check whether the log for the key should be logged.
  if (!logs.has(key)) return;

  // Write log to logger if configured.
  logger?.(log, key);

  if (key === 'err') {
    // Log errors as such.
    console.error(log);
    return;
  }

  // Log to stdout.
  console.log(log);
};

/**
 * Configures the Logflare logger.
 * @function logflare
 * @returns {Function} A function that logs messages to Logflare.
 */
function logflare() {
  const params = Object.fromEntries(new URLSearchParams(process.env.LOGGER.split(':')[1]).entries());

  return (log, key) => {
    fetch(`https://api.logflare.app/logs/json?source=${params.source}`, {
      method: 'post',
      headers: {
        'Content-Type': 'application/json',
        'X-API-KEY': params.apikey,
      },
      body: JSON.stringify({
        [process_id]: log,
        key
      })
    }).catch(err => {
      console.error(err);
    });
  };
}

/**
 * Configures the PostgreSQL logger.
 * @function postgresql
 * @returns {Function} A function that logs messages to a PostgreSQL database.
 */
function postgresql() {
  const params = Object.fromEntries(new URLSearchParams(process.env.LOGGER.split(':')[1]).entries());

  const connectionString = process.env[`DBS_${params.dbs}`];

  if (!connectionString) {
    console.warn(`Logger module unable to find dbs=${params.dbs}`);
    return;
  }

  const pool = new Pool({
    connectionString,
    statement_timeout: 3000
  });

  return async (log, key) => {
    //Sanitize the params.table to ensure no SQL injection
    const table = params.table.replace(/[^a-zA-Z0-9_.]/g, '');
    // Log messages can be string or objects
    // Objects must be parsed as string for the PostgreSQL log table schema.
    const logstring = typeof log === 'string' ? log : JSON.stringify(log);

    //This is to pull the short Error message from the stack
    const errorMessage = log.err?.toString().split('\n')[0];

    const client = await pool.connect();
    try {
      await client.query(
        `INSERT INTO ${table} (process, datetime, key, log, message) 
        VALUES ($1, $2, $3, $4, $5)`,
        [process_id, parseInt(Date.now() / 1000), key, logstring, errorMessage]
      );
    } catch (error) {
      console.error('Error while logging to database:', error);
    } finally {
      client.release();
    }
  };
}