signal-desktop/js/logging.js

145 lines
3.4 KiB
JavaScript

/* eslint-env node */
/* eslint strict: ['error', 'never'] */
const electron = require('electron');
const bunyan = require('bunyan');
const _ = require('lodash');
const debuglogs = require('./modules/debuglogs');
const Privacy = require('./modules/privacy');
const ipc = electron.ipcRenderer;
// Default Bunyan levels: https://github.com/trentm/node-bunyan#levels
// To make it easier to visually scan logs, we make all levels the same length
const BLANK_LEVEL = ' ';
const LEVELS = {
60: 'fatal',
50: 'error',
40: 'warn ',
30: 'info ',
20: 'debug',
10: 'trace',
};
// Backwards-compatible logging, simple strings and no level (defaulted to INFO)
function now() {
const date = new Date();
return date.toJSON();
}
function log(...args) {
const consoleArgs = ['INFO ', now()].concat(args);
console._log(...consoleArgs);
// To avoid [Object object] in our log since console.log handles non-strings smoothly
const str = args.map(item => {
if (typeof item !== 'string') {
try {
return JSON.stringify(item);
} catch (error) {
return item;
}
}
return item;
});
const logText = Privacy.redactAll(str.join(' '));
ipc.send('log-info', logText);
}
if (window.console) {
console._log = console.log;
console.log = log;
}
// The mechanics of preparing a log for publish
function getHeader() {
let header = window.navigator.userAgent;
header += ` node/${window.getNodeVersion()}`;
header += ` env/${window.getEnvironment()}`;
return header;
}
function getLevel(level) {
const text = LEVELS[level];
if (!text) {
return BLANK_LEVEL;
}
return text.toUpperCase();
}
function formatLine(entry) {
return `${getLevel(entry.level)} ${entry.time} ${entry.msg}`;
}
function format(entries) {
return Privacy.redactAll(entries.map(formatLine).join('\n'));
}
function fetch() {
return new Promise(resolve => {
ipc.send('fetch-log');
ipc.on('fetched-log', (event, text) => {
const result = `${getHeader()}\n${format(text)}`;
resolve(result);
});
});
}
const publish = debuglogs.upload;
// A modern logging interface for the browser
// We create our own stream because we don't want to output JSON to the devtools console.
// Anyway, the default process.stdout stream goes to the command-line, not the devtools.
const logger = bunyan.createLogger({
name: 'log',
streams: [
{
level: 'debug',
stream: {
write(entry) {
console._log(formatLine(JSON.parse(entry)));
},
},
},
],
});
// The Bunyan API: https://github.com/trentm/node-bunyan#log-method-api
function logAtLevel(level, ...args) {
const ipcArgs = [`log-${level}`].concat(args);
ipc.send(...ipcArgs);
logger[level](...args);
}
window.log = {
fatal: _.partial(logAtLevel, 'fatal'),
error: _.partial(logAtLevel, 'error'),
warn: _.partial(logAtLevel, 'warn'),
info: _.partial(logAtLevel, 'info'),
debug: _.partial(logAtLevel, 'debug'),
trace: _.partial(logAtLevel, 'trace'),
fetch,
publish,
};
window.onerror = (message, script, line, col, error) => {
const errorInfo = error && error.stack ? error.stack : JSON.stringify(error);
window.log.error(`Top-level unhandled error: ${errorInfo}`);
};
window.addEventListener('unhandledrejection', rejectionEvent => {
window.log.error(
`Top-level unhandled promise rejection: ${rejectionEvent.reason}`
);
});