"use strict";
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2020 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.setUncaughtExceptionLogger = exports.newRequestLogger = exports.isVerbose = exports.configure = exports.getLogger = exports.logErr = exports.get = exports.simpleLogger = exports.formatterFn = void 0;
const winston_1 = __importStar(require("winston"));
require("winston-daily-rotate-file");
/* eslint-enable @typescript-eslint/no-explicit-any */
const UNCAUGHT_EXCEPTION_ERRCODE = 101;
let loggerConfig = {
    level: "debug",
    logfile: undefined,
    errfile: undefined,
    toConsole: true,
    maxFiles: 5,
    verbose: false,
    timestamp: true,
};
const loggers = new Map();
let loggerTransports; // from config
function formatterFn(forceTimestamp = false) {
    const layers = [
        winston_1.format.splat(),
        winston_1.format.printf((info) => {
            info.level = info.level.toUpperCase();
            info.loggerName = info.loggerName ? `${info.loggerName} ` : "";
            info.reqId = info.reqId ? `[${info.reqId}] ` : "";
            info.dir = info.dir ? `[${info.dir}] ` : "";
            info.timestamp = info.timestamp ? `${info.timestamp} ` : "";
            return "" +
                `${info.timestamp}${info.level}:${info.loggerName}${info.reqId}${info.dir}${info.message}`;
        }),
    ];
    if (forceTimestamp || loggerConfig.timestamp) {
        layers.unshift(winston_1.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }));
    }
    return winston_1.format.combine(...layers);
}
exports.formatterFn = formatterFn;
function simpleLogger(level = "info") {
    const l = winston_1.default.createLogger({
        level: level,
    });
    l.add(new (winston_1.default.transports.Console)({
        format: formatterFn(),
        level: level
    }));
    return l;
}
exports.simpleLogger = simpleLogger;
const makeTransports = function () {
    const transports = [];
    if (loggerConfig.toConsole) {
        transports.push(new (winston_1.default.transports.Console)({
            format: formatterFn(true),
            level: loggerConfig.level
        }));
    }
    if (loggerConfig.logfile) {
        transports.push(new (winston_1.default.transports.DailyRotateFile)({
            filename: loggerConfig.logfile,
            json: false,
            level: loggerConfig.level,
            format: formatterFn(),
            maxFiles: loggerConfig.maxFiles,
            datePattern: "YYYY-MM-DD",
            createSymlink: true, // Make it tailable.
        }));
    }
    if (loggerConfig.errfile) {
        transports.push(new (winston_1.default.transports.DailyRotateFile)({
            filename: loggerConfig.errfile,
            json: false,
            level: "error",
            format: formatterFn(),
            maxFiles: loggerConfig.maxFiles,
            datePattern: "YYYY-MM-DD",
            createSymlink: true, // Make it tailable.
        }));
    }
    // by default, EventEmitters will whine if you set more than 10 listeners on
    // them. The 'transport' is an emitter which the loggers listen for errors
    // from. Since we have > 10 files (each with their own logger), we get
    // warnings. Set the max listeners to unlimited to suppress the warning.
    for (const transport of transports) {
        transport.setMaxListeners(0);
    }
    return transports;
};
const createLogger = function (nameOfLogger) {
    // lazily load the transports if one wasn't set from configure()
    if (!loggerTransports) {
        loggerTransports = makeTransports();
    }
    return winston_1.default.createLogger({
        transports: loggerTransports,
        defaultMeta: {
            loggerName: nameOfLogger,
        }
    });
};
/**
 * Obtain a logger by name, creating one if necessary.
 */
function get(nameOfLogger) {
    let logger = loggers.get(nameOfLogger);
    if (!logger) {
        logger = createLogger(nameOfLogger);
        loggers.set(nameOfLogger, logger);
    }
    return logger;
}
exports.get = get;
function logErr(logger, e) {
    logger.error("Error: %s", JSON.stringify(e));
    if (e.stack) {
        logger.error(e.stack);
    }
}
exports.logErr = logErr;
exports.getLogger = get;
exports.default = exports.getLogger;
/**
 * Configure how loggers should be created.
 */
function configure(opts) {
    if (!opts) {
        return;
    }
    loggerConfig = opts;
    loggerTransports = makeTransports();
    // reconfigure any existing loggers. They may have been lazily loaded
    // with the default config, which is now being overwritten by this
    // configure() call.
    for (const existingLogger of loggers.values()) {
        for (const transport of existingLogger.transports) {
            existingLogger.remove(transport);
        }
        // apply the new transports
        for (const transport of loggerTransports) {
            existingLogger.add(transport);
        }
    }
}
exports.configure = configure;
function isVerbose() {
    return loggerConfig.verbose;
}
exports.isVerbose = isVerbose;
// We use any a lot here to avoid having to deal with IArguments inflexibity
/* eslint-disable @typescript-eslint/no-explicit-any */
function newRequestLogger(baseLogger, requestId, isFromIrc) {
    const decorate = function (fn, args) {
        const newArgs = [];
        // don't slice this; screws v8 optimisations apparently
        for (let i = 0; i < args.length; i++) {
            newArgs.push(args[i]);
        }
        // add a piece of metadata to the log line, with the request ID.
        newArgs[args.length] = {
            reqId: requestId,
            dir: (isFromIrc ? "[I->M]" : "[M->I]")
        };
        fn.apply(baseLogger, newArgs);
    };
    return {
        debug: (msg, ...meta) => { decorate(baseLogger.debug, [msg, ...meta]); },
        info: (msg, ...meta) => { decorate(baseLogger.info, [msg, ...meta]); },
        warn: (msg, ...meta) => { decorate(baseLogger.warn, [msg, ...meta]); },
        error: (msg, ...meta) => { decorate(baseLogger.error, [msg, ...meta]); },
    };
}
exports.newRequestLogger = newRequestLogger;
/* eslint-enable @typescript-eslint/no-explicit-any */
function setUncaughtExceptionLogger(exceptionLogger) {
    process.on("uncaughtException", function (e) {
        // Log to stderr first and foremost, to avoid any chance of us missing a flush.
        console.error("FATAL EXCEPTION");
        console.error(e && e.stack ? e.stack : String(e));
        // Log to winston handlers afterwards, if we can.
        exceptionLogger.error("FATAL EXCEPTION");
        if (e && e.stack) {
            exceptionLogger.error(e.stack);
        }
        else {
            exceptionLogger.error(e.name, e.message);
        }
        // We exit with UNCAUGHT_EXCEPTION_ERRCODE to ensure that the poor
        // developers debugging the bridge can identify where it exploded.
        // There have been issues where winston has failed to log the last
        // few lines before quitting, which I suspect is due to it not flushing.
        // Since we know we're going to die at this point, log something else
        // and forcibly flush all the transports before exiting.
        exceptionLogger.error("Terminating (exitcode=1)", function () {
            let numFlushes = 0;
            let numFlushed = 0;
            exceptionLogger.transports.forEach((stream) => {
                if (!stream) {
                    return;
                }
                numFlushes += 1;
                stream.once("finish", function () {
                    numFlushed += 1;
                    if (numFlushes === numFlushed) {
                        process.exit(UNCAUGHT_EXCEPTION_ERRCODE);
                    }
                });
                stream.on("error", function () {
                    // swallow
                });
                stream.end();
            });
            if (numFlushes === 0) {
                process.exit(UNCAUGHT_EXCEPTION_ERRCODE);
            }
        });
    });
}
exports.setUncaughtExceptionLogger = setUncaughtExceptionLogger;
//# sourceMappingURL=logging.js.map