Convert logging infrastructure to TypeScript
This commit is contained in:
parent
10ace53845
commit
a8787e7c9e
19 changed files with 595 additions and 331 deletions
150
ts/logging/set_up_renderer_logging.ts
Normal file
150
ts/logging/set_up_renderer_logging.ts
Normal file
|
@ -0,0 +1,150 @@
|
|||
// Copyright 2017-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* eslint-env node */
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { ipcRenderer as ipc } from 'electron';
|
||||
import _ from 'lodash';
|
||||
import { levelFromName } from 'bunyan';
|
||||
|
||||
import { uploadDebugLogs } from './debuglogs';
|
||||
import { redactAll } from '../../js/modules/privacy';
|
||||
import { createBatcher } from '../util/batcher';
|
||||
import {
|
||||
LogEntryType,
|
||||
LogLevel,
|
||||
cleanArgs,
|
||||
getLogLevelString,
|
||||
isLogEntry,
|
||||
} from './shared';
|
||||
import { reallyJsonStringify } from '../util/reallyJsonStringify';
|
||||
|
||||
// To make it easier to visually scan logs, we make all levels the same length
|
||||
const levelMaxLength: number = Object.keys(levelFromName).reduce(
|
||||
(maxLength, level) => Math.max(maxLength, level.length),
|
||||
0
|
||||
);
|
||||
|
||||
// Backwards-compatible logging, simple strings and no level (defaulted to INFO)
|
||||
function now() {
|
||||
const date = new Date();
|
||||
return date.toJSON();
|
||||
}
|
||||
|
||||
function log(...args: ReadonlyArray<unknown>) {
|
||||
logAtLevel(LogLevel.Info, ...args);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const getLevel = _.memoize((level: LogLevel): string => {
|
||||
const text = getLogLevelString(level);
|
||||
return text.toUpperCase().padEnd(levelMaxLength, ' ');
|
||||
});
|
||||
|
||||
function formatLine(mightBeEntry: Readonly<unknown>): string {
|
||||
const entry: LogEntryType = isLogEntry(mightBeEntry)
|
||||
? mightBeEntry
|
||||
: {
|
||||
level: LogLevel.Error,
|
||||
msg: `Invalid IPC data when fetching logs. Here's what we could recover: ${reallyJsonStringify(
|
||||
mightBeEntry
|
||||
)}`,
|
||||
time: new Date().toISOString(),
|
||||
};
|
||||
|
||||
return `${getLevel(entry.level)} ${entry.time} ${entry.msg}`;
|
||||
}
|
||||
|
||||
function fetch(): Promise<string> {
|
||||
return new Promise(resolve => {
|
||||
ipc.send('fetch-log');
|
||||
|
||||
ipc.on('fetched-log', (_event, logEntries: unknown) => {
|
||||
let body: string;
|
||||
if (Array.isArray(logEntries)) {
|
||||
body = logEntries.map(formatLine).join('\n');
|
||||
} else {
|
||||
const entry: LogEntryType = {
|
||||
level: LogLevel.Error,
|
||||
msg: 'Invalid IPC data when fetching logs; dropping all logs',
|
||||
time: new Date().toISOString(),
|
||||
};
|
||||
body = formatLine(entry);
|
||||
}
|
||||
|
||||
const result = `${getHeader()}\n${redactAll(body)}`;
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const publish = uploadDebugLogs;
|
||||
|
||||
// A modern logging interface for the browser
|
||||
|
||||
const env = window.getEnvironment();
|
||||
const IS_PRODUCTION = env === 'production';
|
||||
|
||||
const ipcBatcher = createBatcher({
|
||||
wait: 500,
|
||||
maxSize: 500,
|
||||
processBatch: (items: Array<LogEntryType>) => {
|
||||
ipc.send('batch-log', items);
|
||||
},
|
||||
});
|
||||
|
||||
// The Bunyan API: https://github.com/trentm/node-bunyan#log-method-api
|
||||
function logAtLevel(level: LogLevel, ...args: ReadonlyArray<unknown>): void {
|
||||
if (!IS_PRODUCTION) {
|
||||
const prefix = getLogLevelString(level)
|
||||
.toUpperCase()
|
||||
.padEnd(levelMaxLength, ' ');
|
||||
console._log(prefix, now(), ...args);
|
||||
}
|
||||
|
||||
ipcBatcher.add({
|
||||
level,
|
||||
msg: cleanArgs(args),
|
||||
time: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
window.log = {
|
||||
fatal: _.partial(logAtLevel, LogLevel.Fatal),
|
||||
error: _.partial(logAtLevel, LogLevel.Error),
|
||||
warn: _.partial(logAtLevel, LogLevel.Warn),
|
||||
info: _.partial(logAtLevel, LogLevel.Info),
|
||||
debug: _.partial(logAtLevel, LogLevel.Debug),
|
||||
trace: _.partial(logAtLevel, LogLevel.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 => {
|
||||
const error = rejectionEvent.reason;
|
||||
const errorString =
|
||||
error && error.stack ? error.stack : JSON.stringify(error);
|
||||
window.log.error(`Top-level unhandled promise rejection: ${errorString}`);
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue