"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.censorLogs = exports.loggingContextMiddleware = exports.makeLogger = exports.LoggerFactoryProvider = exports.colorFactory = exports.COLORS = exports.asyncLocalStorage = void 0;
exports.censor = censor;
const crypto_1 = require("crypto");
const node_async_hooks_1 = require("node:async_hooks");
const pino_1 = __importDefault(require("pino"));
const pino_pretty_1 = __importDefault(require("pino-pretty"));
const config_1 = require("../config");
const censor_list_1 = __importDefault(require("./censor/censor-list"));
exports.asyncLocalStorage = new node_async_hooks_1.AsyncLocalStorage();
const stream = (0, pino_pretty_1.default)({
    levelFirst: true,
    levelLabel: 'level',
    ignore: 'layer,pid,hostname,correlationId,color',
    messageFormat: `\x1b[0m[{correlationId}] {color}[{layer}]\x1b[0m {msg}`,
    translateTime: 'yyyy-mm-dd HH:MM:ss.l',
});
// Base logger, shouldn't be used because we want layers to be specified
const baseLogger = (0, pino_1.default)({
    level: process.env['LOG_LEVEL']?.toLowerCase() || config_1.BaseSettingsDefinition.LOG_LEVEL.default,
    formatters: {
        level(label) {
            return { level: label };
        },
    },
    timestamp: () => {
        const now = new Date();
        return `,"time":${now.getTime()},"isoTime":"${now.toISOString()}"`;
    },
    hooks: {
        logMethod(inputArgs, method) {
            // Censor each argument of logger
            const censorList = censor_list_1.default.getAll();
            return method.apply(this, inputArgs.map((arg) => censor(arg, censorList)));
        },
    },
    mixin() {
        if (process.env['CORRELATION_ID_ENABLED'] !== 'false') {
            const store = exports.asyncLocalStorage.getStore();
            if (store) {
                return {
                    correlationId: store.correlationId,
                };
            }
        }
        return {};
    },
}, process.env['DEBUG'] === 'true' ? stream : undefined);
exports.COLORS = [
    '\u001b[31;1m',
    '\u001b[32m',
    '\u001b[33m',
    '\u001b[34m',
    '\u001b[35m',
    '\u001b[36m',
    '\u001b[38;2;121;85;72m',
    '\u001b[92m',
    '\u001b[93m',
    '\u001b[94m',
    '\u001b[95m',
    '\u001b[96m',
    '\u001b[38;5;202m',
    '\u001b[38;5;31m',
    '\u001b[38;5;130m',
    '\u001b[38;2;255;122;92m',
    '\u001b[38;2;46;125;50m',
    '\u001b[38;2;129;168;37m',
    '\u001b[38;2;175;180;43m',
    '\u001b[91m',
    '\u001b[38;2;63;81;181m',
];
const colorFactory = (colors) => {
    let index = -1;
    return () => {
        index++;
        if (index > colors.length - 1) {
            index = 0;
        }
        return colors[index];
    };
};
exports.colorFactory = colorFactory;
const getNextColor = (0, exports.colorFactory)(exports.COLORS);
const defaultLoggerFactory = baseLogger;
/**
 * Global class that will hold the logger factory.
 * This is easier than refactoring the entire framework to use a dependency injection framework,
 * and probably safer since all the popular ones use decorators that are still in the experimental stage.
 */
class LoggerFactoryProvider {
    static set(factory = defaultLoggerFactory) {
        // If the factory has already been set, we want to warn the user because
        // the code might be calling this twice
        if (this.factory) {
            factory
                .child({ layer: 'LoggerFactory' })
                .warn('The logger factory has been set twice in the provider, this will not affect existing global loggers');
        }
        this.factory = factory;
    }
    static get() {
        return this.factory;
    }
}
exports.LoggerFactoryProvider = LoggerFactoryProvider;
/**
 * Instance of a logger, that will defer the construction of its methods until the first time any of them are called.
 * This is done so we can freely create these instances before the adapter is initialized, and allow
 * the global logger factory to be injected if the user so desires.
 */
class PlaceholderLogger {
    constructor(layer) {
        this.levels = ['fatal', 'error', 'warn', 'info', 'debug', 'trace'];
        const color = process.env['DEBUG'] === 'true' ? getNextColor() : undefined;
        for (const level of this.levels) {
            this[level] = ((...args) => {
                // This is the first time any method is called for this logger.
                // We want to store the original arguments while we create the actual logger to call later.
                const firstExecutionArgs = args;
                // If a logger factory was not set in the provider, error out.
                const factory = LoggerFactoryProvider.get();
                if (!factory) {
                    throw new Error('The logger factory provider does not have a factory set, you need to set one before executing any logging methods.');
                }
                // We have a logger factory, so first we create a new child logger
                const logger = factory.child({
                    layer,
                    color,
                });
                // Replace all of this object's methods with the actual logger instance
                for (const _level of this.levels) {
                    this[_level] = logger[_level].bind(logger);
                }
                // Finally, call the original intended logging function
                this[level](...firstExecutionArgs);
            });
        }
    }
}
/**
 * Instead of using a global logger instance, we want to force using a child logger
 * with a specific layer set in it, so that we can filter logs by where they're output from.
 *
 * Details on what each log level represents:
 * "trace": Forensic debugging of issues on a local machine.
 * "debug": Detailed logging level to get more context from users on their environments.
 * "info": High-level informational messages, to describe at a glance the high level state of the system.
 * "warn": A mild error occurred that might require non-urgent action.
 * "error": An unexpected error occurred during the regular operation of a well-maintained EA.
 * "fatal": The EA encountered an unrecoverable problem and had to exit.
 *
 * Full reference this is based on can be found at
 * https://github.com/smartcontractkit/documentation/blob/main/docs/Node%20Operators/configuration-variables.md#log_level
 *
 * @param layer - the layer name to include in the logs (e.g. "SomeMiddleware", "RedisCache", etc.)
 * @returns a layer specific logger
 */
const makeLogger = (layer) => new PlaceholderLogger(layer);
exports.makeLogger = makeLogger;
const loggingContextMiddleware = (rawReq, res, done) => {
    const req = rawReq;
    const correlationId = req.headers['x-correlation-id'] || (0, crypto_1.randomUUID)();
    exports.asyncLocalStorage.run({ correlationId: correlationId }, () => {
        done();
    });
};
exports.loggingContextMiddleware = loggingContextMiddleware;
// Obj is typed as "any" because it could be a variety of structures in the logger
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function censor(obj, censorList, throwOnError = false) {
    let stringified = '';
    try {
        // JSON.stringify(obj) will fail if obj contains a circular reference.
        // If it fails, we fall back to replacing it with "[Unknown]".
        stringified = JSON.stringify(obj);
    }
    catch (e) {
        if (throwOnError) {
            throw e;
        }
        return '[Unknown]';
    }
    if (typeof stringified !== 'string') {
        return undefined;
    }
    let result = stringified;
    censorList.forEach((entry) => {
        result = result.replace(entry.value, `[${entry.key} REDACTED]`);
    });
    return JSON.parse(result);
}
const censorLogs = (logFunc) => {
    if (process.env['CENSOR_SENSITIVE_LOGS'] === 'true') {
        return;
    }
    logFunc();
};
exports.censorLogs = censorLogs;
//# sourceMappingURL=logger.js.map