@wdio/logger
Version:
A helper utility for logging of WebdriverIO packages
185 lines (184 loc) • 6.07 kB
JavaScript
import fs from 'node:fs';
import util from 'node:util';
import log from 'loglevel';
import chalk from 'chalk';
import prefix from 'loglevel-plugin-prefix';
import ansiStrip from 'strip-ansi';
prefix.reg(log);
const DEFAULT_LEVEL = process.env.WDIO_DEBUG
? 'trace'
: 'info';
const COLORS = {
error: 'red',
warn: 'yellow',
info: 'cyanBright',
debug: 'green',
trace: 'cyan',
progress: 'magenta'
};
const matches = {
COMMAND: 'COMMAND',
BIDICOMMAND: 'BIDI COMMAND',
DATA: 'DATA',
RESULT: 'RESULT',
BIDIRESULT: 'BIDI RESULT'
};
const SERIALIZERS = [{
/**
* display error stack
*/
matches: (err) => err instanceof Error,
serialize: (err) => err.stack
}, {
/**
* color commands blue
*/
matches: (log) => log === matches.COMMAND || log === matches.BIDICOMMAND,
serialize: (log) => chalk.magenta(log)
}, {
/**
* color data yellow
*/
matches: (log) => log === matches.DATA,
serialize: (log) => chalk.yellow(log)
}, {
/**
* color result cyan
*/
matches: (log) => log === matches.RESULT || log === matches.BIDIRESULT,
serialize: (log) => chalk.cyan(log)
}];
const loggers = log.getLoggers();
let logLevelsConfig = {};
const logCache = new Set();
let logFile;
const originalFactory = log.methodFactory;
const wdioLoggerMethodFactory = function (methodName, logLevel, loggerName) {
const rawMethod = originalFactory(methodName, logLevel, loggerName);
return (...args) => {
/**
* create logFile lazily
*/
if (!logFile && process.env.WDIO_LOG_PATH) {
logFile = fs.createWriteStream(process.env.WDIO_LOG_PATH);
}
/**
* split `prefixer: value` sting to `prefixer: ` and `value`
* so that SERIALIZERS can match certain string
*/
const match = Object.values(matches).filter(x => args[0].endsWith(`: ${x}`))[0];
if (match) {
const prefixStr = args.shift().slice(0, -match.length - 1);
args.unshift(prefixStr, match);
}
args = args.map((arg) => {
for (const s of SERIALIZERS) {
if (s.matches(arg)) {
return s.serialize(arg);
}
}
return arg;
});
const logText = ansiStrip(`${util.format.apply(this, args)}\n`);
if (logFile && logFile.writable) {
/**
* empty logging cache if stuff got logged before
*/
if (logCache.size) {
logCache.forEach((log) => {
if (logFile) {
logFile.write(log);
}
});
logCache.clear();
}
return logFile.write(logText);
}
logCache.add(logText);
rawMethod(...args);
};
};
const progress = function (data) {
if (process.stdout.isTTY && this.getLevel() <= log.levels.INFO) {
const level = 'progress';
const timestampFormatter = chalk.gray(new Date().toISOString());
const levelFormatter = chalk[COLORS[level]](level.toUpperCase());
const nameFormatter = chalk.whiteBright(this.name);
const _data = data.length > 0 ? `${timestampFormatter} ${levelFormatter} ${nameFormatter}: ${data}` : '\r\x1b[K';
process.stdout.write('\u001B[?25l'); // Disable cursor in terminal
process.stdout.write(`${_data}\r`);
}
};
export default function getLogger(name) {
/**
* check if logger was already initiated
*/
if (loggers[name]) {
return loggers[name];
}
let logLevel = (process.env.WDIO_LOG_LEVEL || DEFAULT_LEVEL);
const logLevelName = getLogLevelName(name);
if (logLevelsConfig[logLevelName]) {
logLevel = logLevelsConfig[logLevelName];
}
loggers[name] = log.getLogger(name);
loggers[name].setLevel(logLevel);
loggers[name].methodFactory = wdioLoggerMethodFactory;
loggers[name].progress = progress;
prefix.apply(loggers[name], {
template: '%t %l %n:',
timestampFormatter: (date) => chalk.gray(date.toISOString()),
levelFormatter: (level) => chalk[COLORS[level]](level.toUpperCase()),
nameFormatter: (name) => chalk.whiteBright(name)
});
return loggers[name];
}
/**
* Wait for writable stream to be flushed.
* Calling this prevents part of the logs in the very env to be lost.
*/
getLogger.waitForBuffer = async () => new Promise(resolve => {
// @ts-ignore
if (logFile && Array.isArray(logFile.writableBuffer) && logFile.writableBuffer.length !== 0) {
return setTimeout(async () => {
await getLogger.waitForBuffer();
resolve();
}, 20);
}
resolve();
});
getLogger.setLevel = (name, level) => loggers[name].setLevel(level);
getLogger.clearLogger = () => {
if (logFile) {
logFile.end();
}
logFile = null;
};
getLogger.setLogLevelsConfig = (logLevels = {}, wdioLogLevel = DEFAULT_LEVEL) => {
/**
* set log level
*/
if (process.env.WDIO_LOG_LEVEL === undefined) {
process.env.WDIO_LOG_LEVEL = wdioLogLevel;
}
logLevelsConfig = {};
/**
* build logLevelsConfig object
*/
Object.entries(logLevels).forEach(([logName, logLevel]) => {
const logLevelName = getLogLevelName(logName);
logLevelsConfig[logLevelName] = logLevel;
});
/**
* set log level for each logger
*/
Object.keys(loggers).forEach(logName => {
const logLevelName = getLogLevelName(logName);
/**
* either apply log level from logLevels object or use global logLevel
*/
const logLevel = typeof logLevelsConfig[logLevelName] !== 'undefined' ? logLevelsConfig[logLevelName] : process.env.WDIO_LOG_LEVEL;
loggers[logName].setLevel(logLevel);
});
};
const getLogLevelName = (logName) => logName.split(':').shift();