170 lines
4.3 KiB
TypeScript
170 lines
4.3 KiB
TypeScript
// Copyright 2021 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
import pino from 'pino';
|
|
|
|
import type { LoggerType } from '../types/Logging';
|
|
import { Environment, getEnvironment } from '../environment';
|
|
import { reallyJsonStringify } from '../util/reallyJsonStringify';
|
|
import { getLogLevelString } from './shared';
|
|
|
|
// This file is imported by some components so we can't import `ts/util/privacy`
|
|
let redactAll = (value: string) => value;
|
|
|
|
let destination: pino.DestinationStream | undefined;
|
|
let buffer = new Array<string>();
|
|
|
|
const COLORS = [
|
|
'#2c6bed',
|
|
'#cf163e',
|
|
'#c73f0a',
|
|
'#6f6a58',
|
|
'#3b7845',
|
|
'#1d8663',
|
|
'#077d92',
|
|
'#336ba3',
|
|
'#6058ca',
|
|
'#9932c8',
|
|
'#aa377a',
|
|
'#8f616a',
|
|
'#71717f',
|
|
'#ebeae8',
|
|
'#506ecd',
|
|
'#ff9500',
|
|
];
|
|
|
|
const SUBSYSTEM_COLORS = new Map<string, string>();
|
|
|
|
// Only for unpackaged app
|
|
function getSubsystemColor(name: string): string {
|
|
const cached = SUBSYSTEM_COLORS.get(name);
|
|
if (cached != null) {
|
|
return cached;
|
|
}
|
|
|
|
// Jenkins hash
|
|
let hash = 0;
|
|
|
|
/* eslint-disable no-bitwise */
|
|
for (let i = 0; i < name.length; i += 1) {
|
|
hash += name.charCodeAt(i) & 0xff;
|
|
hash += hash << 10;
|
|
hash ^= hash >>> 6;
|
|
}
|
|
hash += hash << 3;
|
|
hash ^= hash >>> 11;
|
|
hash += hash << 15;
|
|
hash >>>= 0;
|
|
/* eslint-enable no-bitwise */
|
|
|
|
const result = COLORS[hash % COLORS.length];
|
|
SUBSYSTEM_COLORS.set(name, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
const pinoInstance = pino(
|
|
{
|
|
formatters: {
|
|
// No point in saving pid or hostname
|
|
bindings: () => ({}),
|
|
},
|
|
hooks: {
|
|
logMethod(args, method, level) {
|
|
if (getEnvironment() !== Environment.PackagedApp) {
|
|
const consoleMethod = getLogLevelString(level);
|
|
|
|
const { msgPrefixSym } = pino.symbols as unknown as {
|
|
readonly msgPrefixSym: unique symbol;
|
|
};
|
|
const msgPrefix = (
|
|
this as unknown as Record<symbol, string | undefined>
|
|
)[msgPrefixSym];
|
|
|
|
const [message, ...extra] = args;
|
|
|
|
const color = getSubsystemColor(msgPrefix ?? '');
|
|
|
|
// `fatal` has no respective analog in `console`
|
|
// eslint-disable-next-line no-console
|
|
console[consoleMethod === 'fatal' ? 'error' : consoleMethod](
|
|
`%c${msgPrefix ?? ''}%c${message}`,
|
|
`color: ${color}; font-weight: bold`,
|
|
'color: inherit; font-weight: inherit',
|
|
...extra
|
|
);
|
|
}
|
|
|
|
// Always call original method, but with stringified arguments for
|
|
// compatibility with existing logging.
|
|
//
|
|
// (Since pino >= 6 extra arguments that don't correspond to %d/%s/%j
|
|
// templates in the `message` are ignored)
|
|
const line = args
|
|
.map(item =>
|
|
typeof item === 'string' ? item : reallyJsonStringify(item)
|
|
)
|
|
.join(' ');
|
|
return method.call(this, line);
|
|
},
|
|
},
|
|
timestamp: pino.stdTimeFunctions.isoTime,
|
|
redact: {
|
|
paths: ['*'],
|
|
censor: item => redactAll(item),
|
|
},
|
|
},
|
|
{
|
|
write(msg) {
|
|
if (destination == null) {
|
|
buffer.push(msg);
|
|
} else {
|
|
destination.write(msg);
|
|
}
|
|
},
|
|
}
|
|
);
|
|
|
|
export const log: LoggerType = {
|
|
fatal: pinoInstance.fatal.bind(pinoInstance),
|
|
error: pinoInstance.error.bind(pinoInstance),
|
|
warn: pinoInstance.error.bind(pinoInstance),
|
|
info: pinoInstance.info.bind(pinoInstance),
|
|
debug: pinoInstance.debug.bind(pinoInstance),
|
|
trace: pinoInstance.trace.bind(pinoInstance),
|
|
child: child.bind(pinoInstance),
|
|
};
|
|
|
|
function child(this: typeof pinoInstance, name: string): LoggerType {
|
|
const instance = this.child({}, { msgPrefix: `[${name}] ` });
|
|
|
|
return {
|
|
fatal: instance.fatal.bind(instance),
|
|
error: instance.error.bind(instance),
|
|
warn: instance.warn.bind(instance),
|
|
info: instance.info.bind(instance),
|
|
debug: instance.debug.bind(instance),
|
|
trace: instance.trace.bind(instance),
|
|
child: child.bind(instance),
|
|
};
|
|
}
|
|
|
|
export const createLogger = log.child;
|
|
|
|
/**
|
|
* Sets the low-level logging interface. Should be called early in a process's
|
|
* life.
|
|
*/
|
|
export function setPinoDestination(
|
|
newDestination: pino.DestinationStream,
|
|
newRedactAll: typeof redactAll
|
|
): void {
|
|
destination = newDestination;
|
|
redactAll = newRedactAll;
|
|
const queued = buffer;
|
|
buffer = [];
|
|
for (const msg of queued) {
|
|
destination.write(msg);
|
|
}
|
|
}
|