Add a more verbose example of customizing log output
I've been playing around with 5.0.0-beta.25 and found very little about customizing the output in this version. After going through the source, I have created a more verbose example to help others.
The only strange thing is that your toJSON() method in electron-log/src/main/transforms/object.js returns an object, rather than JSON. It removes cyclic references so that JSON.stringify() can safely handle them, but the method name should probably be changed.
Renderer
This is my logger.js file. It initializes the log and keeps the format simple. Most importantly with this setup, the output from console.log(), console.error() etc. in the renderer process appear in both DevTools and the terminal output:
import log from 'electron-log/renderer';
log.transports.console.format = '{text}';
// Echo console.log() etc. to the terminal using electron logger.
Object.assign(console, log.functions);
export { log };
Main Process
The log customization in the main process is more verbose, but simple to customize.
global.logHome and global.logLevel need to be set first:
const chalk = require('chalk');
const log = require('electron-log/main');
const { maxDepth, toJSON } = require('electron-log/src/main/transforms/object');
async function initLogs() {
const color = {
error: chalk.bgRed.white.bold,
warn: chalk.yellow.bold,
info: chalk.blue.bold,
verbose: (t) => t,
debug: (t) => t,
silly: (t) => t,
};
log.transports.file.resolvePathFn = () => path.join(global.logHome, 'debug_last.log');
log.transports.file.level = global.logLevel;
// Change the console output to just the text we create in our hook.
log.transports.console.format = '{text}';
log.hooks.push((message, transport) => {
let text = null;
if (transport !== log.transports.console) {
return message;
}
// Clone message and data because they are shared by the different
// transports.
const newMessage = Object.assign({}, message);
const { data, date, level } = newMessage;
const dataClone = [...data];
if (typeof dataClone[0] === 'string') {
text = dataClone[0];
} else {
// Deal with objects, arrays etc.
// Step 1: Ensure the object is not deeper the 6 levels.
let safeObj = maxDepth({ data: dataClone[0] });
// Step 2: This 'toJSON' method actually removes cyclic references so that
// JSON.stringify() can safely handle them.
safeObj = toJSON({ data: safeObj });
// Step 3: JSON.stringify() the safe object
text = JSON.stringify(toJSON({ data: safeObj }));
}
// Personal tweak to highlight messages starting with 'XXXXX'
if (text.startsWith('XXXXX')) {
text = chalk.bold(text);
}
// Build strings ready for output
const colorize = color[level];
const lvl = ('[' + level + ']').padStart(9, ' ');
const formattedTime = date.toTimeString().substring(0, 8);
// Tag entries with their process type:
// - M: main
// - R: renderer
const processType = newMessage.variables.processType === 'main' ? 'M' : 'R';
// Colorize the beginning of the output
const prefix = colorize(`${formattedTime} ${processType} ${lvl}`);
// Add the final string back to the clone of the data array and save it to
// newMessage.data
dataClone[0] = `${prefix} ${text}`;
newMessage.data = dataClone;
// Return the newly constructed message
return newMessage;
});
log.initialize({ preload: true });
Object.assign(console, log.functions);
}
I agree, it makes sense to make an example of output processing. In v5 a new property transport.transforms was introduced for that. I have added it to type definitions. But I want to play a bit more with it and add the corresponding documentation when I'm sure the public interface is good enough.
As for toJSON function, it just follows the standard convention when it returns a JSON representation of some object, it doesn't have to be a string.
It really helped me. Sometimes, it may be necessary to add chalk.level = 3 to make colorization work fine.