I did not end up finding a package that suited me, so I put something together and have opted to share it. It is basic, but does the job I needed.
The source code. The config is a JSON structure, with an entry for the logging configuration.
const loggingConfig = config.logging;
if (!loggingConfig) {
throw new Error('No logging configuration was found');
}
const formatter = printf(({ level, message, label, timestamp, stack }) => {
if (!stack) {
return `${timestamp} [${label}] ${level}: ${message}`;
} else {
return `${timestamp} [${label}] ${level}: ${message}\n ${stack}`;
}
});
const logLabel = 'main';
const formats = {
colorized: combine(
colorize(),
errors({ stack: true }),
label({ label: logLabel }),
timestamp(),
formatter
),
'default': combine(
errors({ stack: true }),
label({ label: logLabel }),
timestamp(),
formatter
)
};
const transports = Object.values(loggingConfig.outputs).map((transportConfig: any) => {
const { type, formatName, ...other } = transportConfig;
let selectedFormat = formats[formatName];
if (!selectedFormat) {
selectedFormat = formats['default'];
}
if (type === 'console') {
return new winston.transports.Console(Object.assign(other, { format: selectedFormat }));
} else if (type === 'file') {
return new winston.transports.File(Object.assign(other, { format: selectedFormat }));
} else {
throw new Error(`Invalid transport configuation in ${JSON.stringify(transportConfig)}`);
}
});
Then the JSON structure looks as follows:
{
"logging": {
"outputs": {
"console": {
"type": "console",
"level": "debug",
"formatName": "colorized"
},
"main": {
"type": "file",
"level": "debug",
"filename": "logs/main.log",
"formatName": "default"
}
}
}
}
This approach allows me to have a different log configuration in development, on the continuous integration machine and in production.
This works for me with Winston 3.