vega-frontend-monorepo/libs/react-helpers/src/lib/local-logger.ts
2023-01-25 09:43:11 +01:00

164 lines
4.3 KiB
TypeScript

import * as Sentry from '@sentry/browser';
import type { Scope } from '@sentry/browser';
import type { Severity, Breadcrumb, Primitive } from '@sentry/types';
const LogLevels = [
'fatal',
'error',
'warning',
'log',
'info',
'debug',
'critical',
'silent',
];
type LogLevelsType = typeof LogLevels[number];
type ConsoleArg = string | number | boolean | bigint | symbol | object;
type ConsoleMethod = {
[K in keyof Console]: Console[K] extends (...args: ConsoleArg[]) => unknown
? K
: never;
}[keyof Console] &
string;
interface LoggerConf {
application?: string;
tags?: string[];
logLevel?: LogLevelsType;
}
const isPrimitive = (arg: ConsoleArg | undefined | null): arg is Primitive => {
return ['string', 'number', 'boolean', 'bigint', 'symbol'].includes(
typeof arg
);
};
export class LocalLogger {
static levelLogMap: Record<LogLevelsType, number> = {
debug: 10,
info: 20,
log: 30,
warning: 40,
error: 50,
critical: 60,
fatal: 70,
silent: 80,
};
private _logLevel: LogLevelsType = 'info';
private get numberLogLevel() {
return LocalLogger.levelLogMap[this._logLevel];
}
private tags: string[] = [];
private application = 'trading';
constructor(conf: LoggerConf) {
if (conf.application) {
this.application = conf.application;
}
this.tags = [...(conf.tags || [])];
this._logLevel = conf.logLevel || this._logLevel;
}
public debug(...args: ConsoleArg[]) {
this._log('debug', 'debug', args);
}
public info(...args: ConsoleArg[]) {
this._log('info', 'info', args);
}
public log(...args: ConsoleArg[]) {
this._log('log', 'log', args);
}
public warn(...args: ConsoleArg[]) {
this._log('warning', 'warn', args);
}
public error(...args: ConsoleArg[]) {
this._log('error', 'error', args);
}
public critical(...args: ConsoleArg[]) {
this._log('critical', 'error', args);
}
public fatal(...args: ConsoleArg[]) {
this._log('fatal', 'error', args);
}
private _log(
level: LogLevelsType,
logMethod: ConsoleMethod,
args: ConsoleArg[]
) {
if (this.numberLogLevel <= LocalLogger.levelLogMap[level]) {
console[logMethod].apply(console, [
`${this.application}:${level}: `,
...args,
]);
}
this._transmit(level, args);
}
private _extractArgs(
level: LogLevelsType,
args: ConsoleArg[]
): [string, Error, Scope] {
const arg = args.shift();
const error = arg instanceof Error ? arg : null;
const msg = error ? error.message : String(arg);
const scope = new Sentry.Scope();
scope.setLevel(level as Severity);
let logArgs: Record<string, unknown>;
try {
logArgs = { args: JSON.stringify(args) };
} catch (e) {
logArgs = { args };
}
scope.setContext('event-record', logArgs);
if (this.tags.length) {
this.tags.forEach((tag) => {
const found = args.reduce((aggr, arg) => {
if (typeof arg === 'object' && tag in arg) {
// @ts-ignore change object to record
aggr = arg[tag] as unknown as Primitive | object;
}
return aggr;
}, null as Primitive | object);
if (isPrimitive(found)) {
scope.setTag(tag, found);
}
});
}
return [msg, error || new Error(msg), scope];
}
private _transmit(level: LogLevelsType, args: ConsoleArg[]) {
const [msg, error, logEvent] = this._extractArgs(level, args);
switch (level) {
case 'debug':
case 'info':
case 'log':
case 'warning':
Sentry.captureMessage(msg, logEvent);
return;
case 'error':
case 'critical':
case 'fatal':
Sentry.captureException(error, logEvent);
return;
}
}
public addSentryBreadcrumb(breadcrumb: Breadcrumb) {
Sentry.addBreadcrumb(breadcrumb);
}
public setLogLevel(logLevel: LogLevelsType) {
this._logLevel = logLevel;
}
public get logLevel() {
return this._logLevel;
}
}
let singleLoggerInstance: LocalLogger;
export const localLoggerFactory = (conf: LoggerConf) => {
if (!singleLoggerInstance) {
singleLoggerInstance = new LocalLogger(conf);
}
if (conf.logLevel && singleLoggerInstance.logLevel !== conf.logLevel) {
singleLoggerInstance.setLogLevel(conf.logLevel);
}
return singleLoggerInstance;
};