mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-04-07 06:59:54 +00:00
refactor: move logger to monorepo
This commit is contained in:
348
packages/logger/src/logger.ts
Normal file
348
packages/logger/src/logger.ts
Normal file
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2020 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {stringify} from 'flatted';
|
||||
import {isNodeEnvironment, isProductiveEnvironment, isProductiveNodeEnvironment} from './common';
|
||||
import {Transformation} from './transformation';
|
||||
import {AddLogLevel} from './transformations/add-log-level';
|
||||
import {Transport} from './transport';
|
||||
|
||||
/**
|
||||
* Check if something has property STAPPS_LOG_LEVEL
|
||||
*
|
||||
* @param something Something to check
|
||||
*/
|
||||
// tslint:disable-next-line:completed-docs
|
||||
function hasStAppsLogLevel(something: object): something is {STAPPS_LOG_LEVEL: number} {
|
||||
return 'STAPPS_LOG_LEVEL' in something;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if something has property STAPPS_EXIT_LEVEL
|
||||
*
|
||||
* @param something Something to check
|
||||
*/
|
||||
// tslint:disable-next-line:completed-docs
|
||||
function hasStAppsExitLevel(something: object): something is {STAPPS_EXIT_LEVEL: number} {
|
||||
return 'STAPPS_EXIT_LEVEL' in something;
|
||||
}
|
||||
|
||||
/**
|
||||
* A level descriptor for either log or exit level
|
||||
*/
|
||||
export type Level = 'LOG' | 'EXIT';
|
||||
|
||||
/**
|
||||
* A log level
|
||||
*/
|
||||
export type LogLevel = 'INFO' | 'LOG' | 'WARN' | 'ERROR' | 'OK';
|
||||
|
||||
/**
|
||||
* A logger with transports and transformations
|
||||
*
|
||||
* Log level can be defined by setting the environment variable STAPPS_LOG_LEVEL to a valid log level. Log levels are
|
||||
* set in a binary way. For example STAPPS_LOG_LEVEL=12 does result in logs only for `Logger.warn` and `Logger.error`.
|
||||
*
|
||||
* Log levels in that order are:
|
||||
* ```
|
||||
* INFO: 1
|
||||
* LOG: 2
|
||||
* WARN: 4
|
||||
* ERROR: 8
|
||||
* OK: 16
|
||||
* ```
|
||||
*/
|
||||
export class Logger {
|
||||
/**
|
||||
* Base of binary system
|
||||
*/
|
||||
private static readonly binaryBase = 2;
|
||||
|
||||
/**
|
||||
* Log levels
|
||||
*/
|
||||
private static readonly logLevels: LogLevel[] = ['INFO', 'LOG', 'WARN', 'ERROR', 'OK'];
|
||||
|
||||
/**
|
||||
* Log level sum, equivalent to all log levels enabled
|
||||
*/
|
||||
private static readonly logLevelSum = Math.pow(Logger.binaryBase, Logger.logLevels.length) - 1;
|
||||
|
||||
/**
|
||||
* Transformers for log output
|
||||
*/
|
||||
private static transformations?: Transformation[] = [new AddLogLevel()];
|
||||
|
||||
/**
|
||||
* Transport for errors
|
||||
*/
|
||||
private static transport?: Transport;
|
||||
|
||||
/**
|
||||
* Apply transformations to an output
|
||||
* Will strip newlines in production environment
|
||||
*
|
||||
* @param logLevel Log level of the output
|
||||
* @param output Output to apply transformations to
|
||||
*/
|
||||
private static applyTransformers(logLevel: LogLevel, output: string): string {
|
||||
if (isProductiveEnvironment()) {
|
||||
output = output.replace(/[\n\r]/g, ' ');
|
||||
}
|
||||
|
||||
if (!Array.isArray(Logger.transformations) || Logger.transformations.length === 0) {
|
||||
return output;
|
||||
}
|
||||
|
||||
let transformedOutput = output;
|
||||
for (const transformation of Logger.transformations) {
|
||||
transformedOutput = transformation.transform(logLevel, transformedOutput);
|
||||
}
|
||||
|
||||
return transformedOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if intended exit level is allowed in environment exit level
|
||||
*
|
||||
* @param exitLevel Log level to check
|
||||
*/
|
||||
private static checkExitLevel(exitLevel: LogLevel): boolean {
|
||||
if (Logger.getLevel('EXIT') === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-bitwise
|
||||
return (Logger.getLevel('EXIT') & Logger.logLevelNumber(exitLevel)) === Logger.logLevelNumber(exitLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if intended log level is allowed in environment log level
|
||||
*
|
||||
* @param logLevel Log level to check
|
||||
*/
|
||||
private static checkLogLevel(logLevel: LogLevel): boolean {
|
||||
// tslint:disable-next-line:no-bitwise
|
||||
return (Logger.getLevel('LOG') & Logger.logLevelNumber(logLevel)) === Logger.logLevelNumber(logLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify about exit and end process
|
||||
*/
|
||||
private static exit(): void {
|
||||
if (isProductiveNodeEnvironment()) {
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
Logger.applyTransformers('ERROR', `exiting as of used exit level ${Logger.getLevel('EXIT')} !`),
|
||||
);
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return log level from environment
|
||||
*/
|
||||
private static getLevel(level: Level): number {
|
||||
if (typeof window !== 'undefined') {
|
||||
// browser environment exists
|
||||
if (hasStAppsLogLevel(window)) {
|
||||
return window.STAPPS_LOG_LEVEL;
|
||||
}
|
||||
|
||||
if (hasStAppsExitLevel(window)) {
|
||||
return window.STAPPS_EXIT_LEVEL;
|
||||
}
|
||||
}
|
||||
|
||||
const environmentLevel = level === 'LOG' ? process.env.STAPPS_LOG_LEVEL : process.env.STAPPS_EXIT_LEVEL;
|
||||
|
||||
if (isNodeEnvironment() && typeof environmentLevel !== 'undefined') {
|
||||
// Node.js environment exists
|
||||
return Number.parseInt(environmentLevel, 10);
|
||||
}
|
||||
|
||||
// Fallback to log everything, or not exiting
|
||||
switch (level) {
|
||||
case 'LOG':
|
||||
return Logger.logLevelSum;
|
||||
case 'EXIT':
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of specific log level
|
||||
*
|
||||
* @param logLevel Log level to check
|
||||
*/
|
||||
private static logLevelNumber(logLevel: LogLevel): number {
|
||||
return Math.pow(Logger.binaryBase, Logger.logLevels.indexOf(logLevel));
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an error
|
||||
*
|
||||
* @param arguments_ Arguments to log
|
||||
*/
|
||||
public static async error(...arguments_: unknown[]): Promise<string | void> {
|
||||
if (!Logger.checkLogLevel('ERROR')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(Logger.applyTransformers('ERROR', Logger.stringifyArguments(...arguments_)));
|
||||
|
||||
if (isProductiveNodeEnvironment()) {
|
||||
if (typeof Logger.transport !== 'undefined') {
|
||||
return Logger.transport.send('Error', Logger.stringifyArguments(...arguments_));
|
||||
}
|
||||
|
||||
if (process.env.ALLOW_NO_TRANSPORT !== 'true') {
|
||||
throw new Error(
|
||||
`Error couldn't be transported! Please set a transport or set ALLOW_NO_TRANSPORT='true'.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (Logger.checkExitLevel('ERROR')) {
|
||||
Logger.exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an information
|
||||
*
|
||||
* @param arguments_ Arguments to log
|
||||
*/
|
||||
public static info(...arguments_: unknown[]): void {
|
||||
if (!Logger.checkLogLevel('INFO')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(Logger.applyTransformers('INFO', Logger.stringifyArguments(...arguments_)));
|
||||
if (Logger.checkExitLevel('INFO')) {
|
||||
Logger.exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the logger is initialized correctly
|
||||
*/
|
||||
public static initialized(): void {
|
||||
if (isProductiveNodeEnvironment() && typeof Logger.transport === 'undefined') {
|
||||
if (process.env.ALLOW_NO_TRANSPORT !== 'true') {
|
||||
throw new Error(`Productive environment doesn't set a transport for error notifications.`);
|
||||
}
|
||||
|
||||
Logger.warn(`Productive environment doesn't set a transport for error notifications.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log something
|
||||
*
|
||||
* @param arguments_ Arguments to log
|
||||
*/
|
||||
public static log(...arguments_: unknown[]): void {
|
||||
if (!Logger.checkLogLevel('LOG')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(Logger.applyTransformers('LOG', Logger.stringifyArguments(...arguments_)));
|
||||
if (Logger.checkExitLevel('LOG')) {
|
||||
Logger.exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log something successful
|
||||
*
|
||||
* @param arguments_ Arguments to log
|
||||
*/
|
||||
public static ok(...arguments_: unknown[]): void {
|
||||
if (!Logger.checkLogLevel('OK')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(Logger.applyTransformers('OK', Logger.stringifyArguments(...arguments_)));
|
||||
if (Logger.checkExitLevel('OK')) {
|
||||
Logger.exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set transformations for log output
|
||||
*
|
||||
* @param transformations List of transformations
|
||||
*/
|
||||
public static setTransformations(transformations: Transformation[]) {
|
||||
const transforms = transformations.filter(transform =>
|
||||
isProductiveEnvironment() ? transform.useInProduction === true : true,
|
||||
);
|
||||
Logger.transformations = transforms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a transport
|
||||
*
|
||||
* @param transport Transport to set
|
||||
*/
|
||||
public static setTransport(transport?: Transport) {
|
||||
Logger.transport = transport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stringify a list of arguments
|
||||
*
|
||||
* @param arguments_ Arguments to stringify
|
||||
*/
|
||||
public static stringifyArguments(...arguments_: unknown[]): string {
|
||||
const result: string[] = [];
|
||||
|
||||
for (const argument of arguments_) {
|
||||
if (typeof argument === 'string' || typeof argument === 'number') {
|
||||
result.push(argument.toString());
|
||||
} else if (argument instanceof Error) {
|
||||
result.push(argument.message);
|
||||
if (typeof argument.stack !== 'undefined') {
|
||||
result.push(argument.stack);
|
||||
}
|
||||
} else {
|
||||
result.push(stringify(argument, undefined, 2));
|
||||
}
|
||||
}
|
||||
|
||||
return result.join(', ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a warning
|
||||
*
|
||||
* @param arguments_ Arguments to log
|
||||
*/
|
||||
public static warn(...arguments_: unknown[]): void {
|
||||
if (!Logger.checkLogLevel('WARN')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(Logger.applyTransformers('WARN', Logger.stringifyArguments(...arguments_)));
|
||||
if (Logger.checkExitLevel('WARN')) {
|
||||
Logger.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user