/******************************************************************************** * Copyright (C) 2017 Ericsson and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. * * This Source Code may also be made available under the following Secondary * Licenses when the conditions for such availability set forth in the Eclipse * Public License v. 2.0 are satisfied: GNU General Public License, version 2 * with the GNU Classpath Exception which is available at * https://www.gnu.org/software/classpath/license.html. * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { inject, injectable } from 'inversify'; import { LoggerWatcher } from './logger-watcher'; import { Logger, Log, Loggable } from '@gedit/utils'; import { ConsoleLogger, LoggerServer, LogLevel, rootLoggerName } from './logger-protocol'; export const LoggerFactory = Symbol('LoggerFactory'); export type LoggerFactory = (name: string) => Logger; export const LoggerName = Symbol('LoggerName'); /* eslint-disable @typescript-eslint/no-explicit-any */ export { LogLevel, rootLoggerName }; /* This is to be initialized from container composition root. It can be used outside of the inversify context. */ let logger: Logger; /** * Counterpart of the `#setRootLogger(ILogger)`. Restores the `console.xxx` bindings to the original one. * Invoking has no side-effect if `setRootLogger` was not called before. Multiple function invocation has * no side-effect either. */ export function unsetRootLogger(): void { if (logger !== undefined) { ConsoleLogger.reset(); (logger) = undefined; } } export function setRootLogger(aLogger: Logger): void { logger = aLogger; const log = (logLevel: number, message?: any, ...optionalParams: any[]) => logger.log(logLevel, message, ...optionalParams); console.error = log.bind(undefined, LogLevel.ERROR); console.warn = log.bind(undefined, LogLevel.WARN); console.info = log.bind(undefined, LogLevel.INFO); console.debug = log.bind(undefined, LogLevel.DEBUG); console.trace = log.bind(undefined, LogLevel.TRACE); console.log = log.bind(undefined, LogLevel.INFO); } @injectable() export class LoggerImpl implements Logger { /* Log level for the logger. */ protected _logLevel: Promise; /* A promise resolved when the logger has been created by the backend. */ protected created: Promise; /** * Build a new Logger. */ constructor( @inject(LoggerServer) protected readonly server: LoggerServer, @inject(LoggerWatcher) protected readonly loggerWatcher: LoggerWatcher, @inject(LoggerFactory) protected readonly factory: LoggerFactory, @inject(LoggerName) protected name: string) { if (name !== rootLoggerName) { /* Creating a child logger. */ this.created = server.child(name); } else { /* Creating the root logger (it already exists at startup). */ this.created = Promise.resolve(); } /* Fetch the log level so it's cached in the frontend. */ this._logLevel = this.created.then(_ => this.server.getLogLevel(name)); /* Update the log level if it changes in the backend. */ loggerWatcher.onLogLevelChanged(event => { this.created.then(() => { if (event.loggerName === name) { this._logLevel = Promise.resolve(event.newLogLevel); } }); }); } setLogLevel(logLevel: number): Promise { return new Promise(resolve => { this.created.then(() => { this._logLevel.then(oldLevel => { this.server.setLogLevel(this.name, logLevel).then(() => { this._logLevel = Promise.resolve(logLevel); resolve(); }); }); }); }); } getLogLevel(): Promise { return this._logLevel; } isEnabled(logLevel: number): Promise { return this._logLevel.then(level => logLevel >= level ); } ifEnabled(logLevel: number): Promise { return new Promise(resolve => this.isEnabled(logLevel).then(enabled => { if (enabled) { resolve(); } }) ); } log(logLevel: number, arg2: any | Loggable, ...params: any[]): Promise { return this.getLog(logLevel).then(log => { if (typeof arg2 === 'function') { const loggable = arg2; loggable(log); } else if (arg2) { log(arg2, ...params); } }); } isTrace(): Promise { return this.isEnabled(LogLevel.TRACE); } ifTrace(): Promise { return this.ifEnabled(LogLevel.TRACE); } trace(arg: any | Loggable, ...params: any[]): Promise { return this.log(LogLevel.TRACE, arg, ...params); } isDebug(): Promise { return this.isEnabled(LogLevel.DEBUG); } ifDebug(): Promise { return this.ifEnabled(LogLevel.DEBUG); } debug(arg: any | Loggable, ...params: any[]): Promise { return this.log(LogLevel.DEBUG, arg, ...params); } isInfo(): Promise { return this.isEnabled(LogLevel.INFO); } ifInfo(): Promise { return this.ifEnabled(LogLevel.INFO); } info(arg: any | Loggable, ...params: any[]): Promise { return this.log(LogLevel.INFO, arg, ...params); } isWarn(): Promise { return this.isEnabled(LogLevel.WARN); } ifWarn(): Promise { return this.ifEnabled(LogLevel.WARN); } warn(arg: any | Loggable, ...params: any[]): Promise { return this.log(LogLevel.WARN, arg, ...params); } isError(): Promise { return this.isEnabled(LogLevel.ERROR); } ifError(): Promise { return this.ifEnabled(LogLevel.ERROR); } error(arg: any | Loggable, ...params: any[]): Promise { return this.log(LogLevel.ERROR, arg, ...params); } isFatal(): Promise { return this.isEnabled(LogLevel.FATAL); } ifFatal(): Promise { return this.ifEnabled(LogLevel.FATAL); } fatal(arg: any | Loggable, ...params: any[]): Promise { return this.log(LogLevel.FATAL, arg, ...params); } child(name: string): Logger { return this.factory(name); } protected getLog(logLevel: number): Promise { return this.ifEnabled(logLevel).then(() => this.created.then(() => (message: any, ...params: any[]) => this.server.log(this.name, logLevel, this.format(message), params.map(p => this.format(p))) ) ); } protected format(value: any): any { if (value instanceof Error) { return value.stack || value.toString(); } return value; } }