@linkedmink/node-route53-dynamic-dns
Version:
Background process that updates AWS Route 53 DNS address records whenever the public IP of the hosting environment changes
143 lines (123 loc) • 4.76 kB
text/typescript
import path from "node:path";
import { fileURLToPath } from "node:url";
import * as winston from "winston";
import TransportStream from "winston-transport";
import { ConfigKey } from "../constants/config.mjs";
import { DEFAULT_LOGGER_LABEL, DEFAULT_LOG_LEVEL, LogLevel } from "../constants/logging.mjs";
import { getEnumKeys } from "../functions/convert.mjs";
import { formatError } from "../functions/format.mjs";
import { EnvironmentConfig } from "./environment-config.mjs";
const getDefaultFormat = (label: string): winston.Logform.Format => {
return winston.format.combine(
winston.format.errors({ stack: true }),
winston.format.colorize(),
winston.format.label({ label, message: false }),
winston.format.timestamp(),
winston.format.printf(info => {
const label = typeof info.label === "string" ? ` ${info.label}` : "";
const message = typeof info.stack === "string" ? info.stack : (info.message as string);
return `${info.timestamp as string} ${info.level}${label}: ${message}`;
})
);
};
const getDefaultOptions = (label: string): winston.LoggerOptions => {
const options: winston.LoggerOptions = {
levels: winston.config.npm.levels,
level: DEFAULT_LOG_LEVEL,
transports: [
new winston.transports.Console({
format: getDefaultFormat(label),
}),
],
};
return options;
};
/**
* Change the options before constructing a logger. A logger will use the options
* set at the time the first get() is called for a specific label
*/
let loggerOptionsFunc = getDefaultOptions;
/**
* Wrap the Winston logger container, so we can get the same logger for each module.
* @param label The label of the module we're logging
* @return An instance of the logger
*/
export const loggerForLabel = (label = DEFAULT_LOGGER_LABEL): winston.Logger => {
if (!winston.loggers.has(label)) {
return winston.loggers.add(label, loggerOptionsFunc(label));
}
return winston.loggers.get(label);
};
/**
* @param moduleUrl Use {@link ImportMeta.url import.meta.url} for ESM modules to generate label
* @return An instance of the logger
*/
export const loggerForModuleUrl = (moduleUrl: string): winston.Logger => {
const moduleFilename = path.basename(fileURLToPath(moduleUrl));
const moduleName = moduleFilename.substring(
0,
moduleFilename.length - path.extname(moduleFilename).length
);
return loggerForLabel(moduleName);
};
/**
* Wrap expensive operations, so we don't perform them for logging unless logging for the level is enabled
* @param logger
* @param level
* @param messageFunc
*/
export const logWhenEnabled = (
logger: winston.Logger,
level: LogLevel,
messageFunc: (loggerLevel: LogLevel) => string
): void => {
const loggerLevel = LogLevel[logger.level as keyof typeof LogLevel];
if (level <= loggerLevel) {
logger.log(LogLevel[level], messageFunc(loggerLevel));
}
};
/**
* Sets the options to construct winston loggers and configures logging for unhandled errors
* @param config Configuration determines the output transport streams
* @returns The default global logger
*/
export const initializeLogging = (config: EnvironmentConfig): winston.Logger => {
const getLoggerOptions = (label: string): winston.LoggerOptions => {
const format = getDefaultFormat(label);
const outputs: TransportStream[] = [new winston.transports.Console({ format })];
const logFilePath = config.getStringOrNull(ConfigKey.LogFile);
if (logFilePath && !config.isEnvironmentContainerized) {
outputs.push(
new winston.transports.File({
filename: logFilePath,
format,
})
);
}
const logLevel = config.getString(ConfigKey.LogLevel);
const numericLogLevel = LogLevel[logLevel as keyof typeof LogLevel];
if (numericLogLevel === undefined || isNaN(numericLogLevel)) {
const allowed = getEnumKeys(LogLevel).toString();
throw new Error(`Log level is invalid: value=${logLevel}, allowed=${allowed}`);
}
const options: winston.LoggerOptions = {
level: logLevel,
transports: outputs,
};
return options;
};
loggerOptionsFunc = getLoggerOptions;
const logger = loggerForLabel();
if (process.listenerCount("uncaughtException") <= 0) {
process.on("uncaughtException", (error: Error, _origin: NodeJS.UncaughtExceptionOrigin) => {
logger.error(`uncaughtException: ${formatError(error)}`);
});
}
if (process.listenerCount("unhandledRejection") <= 0) {
process.on("unhandledRejection", (error: unknown, _rejected: Promise<unknown>) => {
logger.error(`unhandledRejection: ${formatError(error)}`);
});
}
logger.verbose(`${initializeLogging.name}: Logger Initialized`);
return logger;
};