refactor: separate logger for web env

This commit is contained in:
ppedziwiatr
2021-08-25 16:18:45 +02:00
parent 27866702c5
commit d12468cb7b
6 changed files with 222 additions and 112 deletions

View File

@@ -0,0 +1,28 @@
import { Logger, LogLevel } from '@smartweave';
import { LoggerOptions } from 'winston';
import { ConsoleLogger } from './web/ConsoleLogger';
export class ConsoleLoggerFactory {
constructor() {
this.setOptions = this.setOptions.bind(this);
this.getOptions = this.getOptions.bind(this);
this.create = this.create.bind(this);
this.logLevel = this.logLevel.bind(this);
}
setOptions(newOptions: LoggerOptions, moduleName?: string): void {
// noop
}
getOptions(moduleName?: string): LoggerOptions {
return {};
}
logLevel(level: LogLevel, moduleName?: string) {
// noop
}
create(moduleName = 'SWC'): Logger {
return new ConsoleLogger();
}
}

19
src/logging/Logger.ts Normal file
View File

@@ -0,0 +1,19 @@
export type LogLevel = 'error' | 'warn' | 'info' | 'http' | 'verbose' | 'debug' | 'silly';
export interface Logger {
profile(id: any);
error(message?: any, ...optionalParams: any[]);
warn(message?: any, ...optionalParams: any[]);
info(message?: any, ...optionalParams: any[]);
verbose(message?: any, ...optionalParams: any[]);
debug(message?: any, ...optionalParams: any[]);
silly(message?: any, ...optionalParams: any[]);
log(message?: any, ...optionalParams: any[]);
}

View File

@@ -1,125 +1,25 @@
import winston, { createLogger, format, LogEntry, Logger, LoggerOptions, transports } from 'winston'; import { Logger, LogLevel } from '@smartweave';
import path from 'path'; import { LoggerOptions } from 'winston';
import { ConsoleLoggerFactory } from './ConsoleLoggerFactory';
import { WinstonLoggerFactory } from './node/WinstonLoggerFactory';
const { combine, errors, timestamp, colorize, printf } = format;
export const baseFormat = combine(
timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
errors({ stack: true }),
format.splat(),
format((info) => {
info.level = info.level.toUpperCase();
return info;
})()
);
winston.addColors({
error: 'bold redBG',
warn: 'bold magenta',
info: 'bold green',
http: 'bold magentaBG',
verbose: 'bold cyan',
debug: 'bold blue',
silly: 'grey'
});
export const prettyFormat = combine(
baseFormat,
colorize({ all: false }),
printf(({ timestamp, level, message, ...rest }) => {
let result = `[${timestamp}] [${rest.module || 'SWC'}] ${level}: ${message}`;
if (rest?.durationMs) {
result += ` - ${rest.durationMs}ms`;
}
return result;
})
);
export const defaultLoggerOptions = {
level: 'debug',
format: prettyFormat,
transports: [new transports.Console()],
exitOnError: false
};
export type LogLevel = 'error' | 'warn' | 'info' | 'http' | 'verbose' | 'debug' | 'silly';
/**
* A wrapper around "Winston" logging library that allows to change logging settings at runtime
* (for each registered module independently, or globally - for all loggers).
*/
export class LoggerFactory { export class LoggerFactory {
static readonly INST: LoggerFactory = new LoggerFactory(); static readonly INST: LoggerFactory =
typeof window === 'undefined' ? new WinstonLoggerFactory() : new ConsoleLoggerFactory();
private readonly registeredLoggers: { [moduleName: string]: Logger } = {}; setOptions(newOptions: LoggerOptions, moduleName: string): void {
private readonly registeredOptions: { [moduleName: string]: LoggerOptions } = {}; LoggerFactory.INST.setOptions(newOptions, moduleName);
private defaultOptions: LoggerOptions = { ...defaultLoggerOptions };
private constructor() {
// noop
}
setOptions(newOptions: LoggerOptions, moduleName?: string): void {
// if moduleName not specified
if (!moduleName) {
// update default options
LoggerFactory.INST.defaultOptions = newOptions;
// update options for all already registered loggers
Object.keys(this.registeredLoggers).forEach((key: string) => {
Object.assign(this.registeredLoggers[key], newOptions);
});
} else {
// if logger already registered
if (this.registeredLoggers[moduleName]) {
// update its options
Object.assign(this.registeredLoggers[moduleName], newOptions);
} else {
// if logger not yet registered - save options that will be used for its creation
this.registeredOptions[moduleName] = {
...this.defaultOptions,
...newOptions
};
}
}
} }
getOptions(moduleName?: string): LoggerOptions { getOptions(moduleName?: string): LoggerOptions {
if (!moduleName) { return LoggerFactory.INST.getOptions(moduleName);
return this.defaultOptions;
} else {
if (this.registeredLoggers[moduleName]) {
return this.registeredLoggers[moduleName];
} else if (this.registeredOptions[moduleName]) {
return this.registeredOptions[moduleName];
} else {
return this.defaultOptions;
}
}
} }
logLevel(level: LogLevel, moduleName?: string) { logLevel(level: LogLevel, moduleName?: string) {
this.setOptions({ level }, moduleName); LoggerFactory.INST.logLevel(level, moduleName);
} }
create(moduleName = 'SWC'): Logger { create(moduleName?: string): Logger {
// in case of passing '__dirname' as moduleName - leaves only the file name without extension. return LoggerFactory.INST.create(moduleName);
const normalizedModuleName = path.basename(moduleName, path.extname(moduleName));
if (!this.registeredLoggers[normalizedModuleName]) {
const logger = createLogger({
...this.getOptions(normalizedModuleName),
// note: profiler this not currently honor defaultMeta - https://github.com/winstonjs/winston/pull/1935
defaultMeta: { module: normalizedModuleName }
});
// note: winston by default logs profile message with info level (to high IMO),
// with no option to set different default - so we're forcing level by
// overwriting default function...
const originalProfile = logger.profile.bind(logger);
logger.profile = (id: string | number, meta?: LogEntry) => {
return originalProfile(id, meta || { message: '', level: 'debug' });
};
this.registeredLoggers[normalizedModuleName] = logger;
}
return this.registeredLoggers[normalizedModuleName];
} }
} }

View File

@@ -1 +1,5 @@
export * from './web/ConsoleLogger';
export * from './ConsoleLoggerFactory';
export * from './node/WinstonLoggerFactory';
export * from './Logger';
export * from './LoggerFactory'; export * from './LoggerFactory';

View File

@@ -0,0 +1,124 @@
import winston, { createLogger, format, LogEntry, LoggerOptions, transports } from 'winston';
import path from 'path';
import { Logger, LogLevel } from '@smartweave';
const { combine, errors, timestamp, colorize, printf } = format;
/**
* A wrapper around "Winston" logging library that allows to change logging settings at runtime
* (for each registered module independently, or globally - for all loggers).
*/
export class WinstonLoggerFactory {
public readonly baseFormat = combine(
timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
errors({ stack: true }),
format.splat(),
format((info) => {
info.level = info.level.toUpperCase();
return info;
})()
);
public readonly prettyFormat = combine(
this.baseFormat,
colorize({ all: false }),
printf(({ timestamp, level, message, ...rest }) => {
let result = `[${timestamp}] [${rest.module || 'SWC'}] ${level}: ${message}`;
if (rest?.durationMs) {
result += ` - ${rest.durationMs}ms`;
}
return result;
})
);
private readonly registeredLoggers: { [moduleName: string]: Logger } = {};
private readonly registeredOptions: { [moduleName: string]: LoggerOptions } = {};
private defaultOptions: LoggerOptions = {
level: 'debug',
format: this.prettyFormat,
transports: [new transports.Console()],
exitOnError: false
};
public constructor() {
winston.addColors({
error: 'bold redBG',
warn: 'bold magenta',
info: 'bold green',
http: 'bold magentaBG',
verbose: 'bold cyan',
debug: 'bold blue',
silly: 'grey'
});
this.setOptions = this.setOptions.bind(this);
this.getOptions = this.getOptions.bind(this);
this.create = this.create.bind(this);
this.logLevel = this.logLevel.bind(this);
}
setOptions(newOptions: LoggerOptions, moduleName?: string): void {
// if moduleName not specified
if (!moduleName) {
// update default options
this.defaultOptions = newOptions;
// update options for all already registered loggers
Object.keys(this.registeredLoggers).forEach((key: string) => {
Object.assign(this.registeredLoggers[key], newOptions);
});
} else {
// if logger already registered
if (this.registeredLoggers[moduleName]) {
// update its options
Object.assign(this.registeredLoggers[moduleName], newOptions);
} else {
// if logger not yet registered - save options that will be used for its creation
this.registeredOptions[moduleName] = {
...this.defaultOptions,
...newOptions
};
}
}
}
getOptions(moduleName?: string): LoggerOptions {
if (!moduleName) {
return this.defaultOptions;
} else {
if (this.registeredLoggers[moduleName]) {
// safe typecast in this case...
return this.registeredLoggers[moduleName] as LoggerOptions;
} else if (this.registeredOptions[moduleName]) {
return this.registeredOptions[moduleName];
} else {
return this.defaultOptions;
}
}
}
logLevel(level: LogLevel, moduleName?: string) {
this.setOptions({ level }, moduleName);
}
create(moduleName = 'SWC'): Logger {
// in case of passing '__dirname' as moduleName - leaves only the file name without extension.
const normalizedModuleName = path.basename(moduleName, path.extname(moduleName));
if (!this.registeredLoggers[normalizedModuleName]) {
const logger = createLogger({
...this.getOptions(normalizedModuleName),
// note: profiler this not currently honor defaultMeta - https://github.com/winstonjs/winston/pull/1935
defaultMeta: { module: normalizedModuleName }
});
// note: winston by default logs profile message with info level (to high IMO),
// with no option to set different default - so we're forcing level by
// overwriting default function...
const originalProfile = logger.profile.bind(logger);
logger.profile = (id: string | number, meta?: LogEntry) => {
return originalProfile(id, meta || { message: '', level: 'debug' });
};
this.registeredLoggers[normalizedModuleName] = logger;
}
return this.registeredLoggers[normalizedModuleName];
}
}

View File

@@ -0,0 +1,35 @@
import { Logger } from '@smartweave';
export class ConsoleLogger implements Logger {
debug(message?: any, ...optionalParams: any[]) {
console.debug(message, optionalParams);
}
error(message?: any, ...optionalParams: any[]) {
console.error(message, optionalParams);
}
info(message?: any, ...optionalParams: any[]) {
console.info(message, optionalParams);
}
profile(id: any) {
console.warn('Profile not implemented for this logger!');
}
silly(message?: any, ...optionalParams: any[]) {
console.debug();
}
verbose(message?: any, ...optionalParams: any[]) {
console.debug(message, optionalParams);
}
warn(message?: any, ...optionalParams: any[]) {
console.warn(message, optionalParams);
}
log(message?: any, ...optionalParams: any[]) {
console.info(message, optionalParams);
}
}