import toLower from '@tinkoff/utils/string/toLower' ; import eachObj from '@tinkoff/utils/object/each' ; import toArray from '@tinkoff/utils/array/toArray' ; import { LEVELS , LEVEL_NAMES } from './constants' ; import type { Logger as LoggerInterface , Reporter , Filter , Extension , Options , LogObj , LogFn , SaveState , LogLevel , LogArgs , } from './logger.h' ; const createRegexpFromNamespace = ( namespace ) => { return new RegExp ( `^${ namespace . replace ( /\*/g , '.*?' ) }$` ) ; } ; class Logger implements LoggerInterface { private static level : number = LEVELS . silent ; private static instances : Record < string , Logger > = { } ; private static enabledName : string [ ] = [ ] ; private static enabledLevel : number [ ] = [ ] ; private name : string ; private level ? : number ; private beforeReporters : Reporter [ ] ; private reporters : Reporter [ ] ; private filters : Filter [ ] ; private extensions : Extension [ ] ; private defaults : LogObj ; debug : LogFn ; error : LogFn ; fatal : LogFn ; info : LogFn ; trace : LogFn ; warn : LogFn ; private static onChange : ( state : SaveState ) => void ; constructor ( options = { name : '' } ) { this . name = toLower ( options . name ) ; if ( Logger . instances [ this . name ] ) { return Logger . instances [ this . name ] ; } this . beforeReporters = options . beforeReporters || [ ] ; this . reporters = options . reporters || [ ] ; this . filters = options . filters || [ ] ; this . extensions = options . extensions || [ ] ; this . defaults = options . defaults || { } ; if ( options . level ) { this . setLevel ( LEVELS [ options . level ] ) ; } if ( options . enabled ) { this . setLevel ( LEVELS . trace ) ; } this . checkEnabled ( ) ; Logger . instances [ this . name ] = this ; eachObj ( ( level , levelName ) => { this [ levelName ] = ( ... args ) => { return this . log ( level , args ) ; } ; } , LEVELS ) ; } private static save ( ) { Logger . onChange ?. ( { level : Logger . level , enabledName : this . enabledName , enabledLevel : this . enabledLevel , } ) ; } public static load ( state ) { Logger . level = state . level ; Logger . enabledName = state . enabledName ; Logger . enabledLevel = state . enabledLevel ; } static setLevel ( newLevel ) { Logger . level = LEVELS [ newLevel ] ?? Logger . level ; Logger . save ( ) ; } static setOnChange ( onChange ) { Logger . onChange = onChange ; } static enable ( level , namespace ? ) { const [ lvl , ns ] = namespace ? [ LEVELS [ level ] , namespace ] : [ LEVELS . trace , level ] ; Logger . enabledName . push ( ns ) ; Logger . enabledLevel . push ( lvl ) ; Logger . save ( ) ; const regexp = createRegexpFromNamespace ( ns ) ; eachObj ( ( instance , name ) => { if ( regexp . test ( name ) ) { instance . setLevel ( lvl ) ; } } , Logger . instances ) ; } static disable ( level , namespace ? ) { const [ lvl , ns ] = namespace ? [ LEVELS [ level ] , namespace ] : [ LEVELS . silent , level ] ; const index = Logger . enabledName . indexOf ( ns ) ; if ( index > - 1 ) { Logger . enabledName . splice ( index , 1 ) ; Logger . enabledLevel . splice ( index , 1 ) ; } else { Logger . enabledName . push ( ns ) ; Logger . enabledLevel . push ( lvl ) ; } Logger . save ( ) ; const regexp = createRegexpFromNamespace ( ns ) ; eachObj ( ( instance , name ) => { if ( regexp . test ( name ) ) { instance . setLevel ( lvl ) ; } } , Logger . instances ) ; } static clear ( ) { Logger . level = LEVELS . silent ; Logger . enabledName = [ ] ; Logger . enabledLevel = [ ] ; Logger . save ( ) ; eachObj ( ( instance , name ) => { instance . setLevel ( undefined ) ; } , Logger . instances ) ; } static setGlobalReporters ( reporters ) { const reportersArr = toArray ( reporters ) ; eachObj ( ( instance ) => { instance . reporters = reportersArr ; } , Logger . instances ) ; } child ( options ) { const opts = typeof options === 'string' ? { name : options } : options ; const name = this . name ? `${ this . name }.${ opts . name }` : opts . name ; return new Logger ( { beforeReporters : this . beforeReporters , reporters : this . reporters , filters : this . filters , extensions : this . extensions , defaults : this . defaults , ... opts , name , } ) ; } addBeforeReporter ( reporter ) { this . beforeReporters = this . beforeReporters . concat ( reporter ) ; } setBeforeReporters ( reporters ) { this . beforeReporters = toArray ( reporters ) ; } addReporter ( reporter ) { this . reporters = this . reporters . concat ( reporter ) ; } setReporters ( reporters ) { this . reporters = toArray ( reporters ) ; } addFilter ( filter ) { this . filters = this . filters . concat ( filter ) ; } setFilters ( filters ) { this . filters = toArray ( filters ) ; } addExtension ( extension ) { this . extensions = this . extensions . concat ( extension ) ; this . extensions . push ( extension ) ; } setExtensions ( extensions ) { this . extensions = toArray ( extensions ) ; } setLevel ( level ) { this . level = level ; } private checkEnabled ( ) { const len = Logger . enabledName . length ; for ( let i = len - 1 ; i >= 0 ; i -- ) { const namespace = Logger . enabledName [ i ] ; const regexp = createRegexpFromNamespace ( namespace ) ; if ( regexp . test ( this . name ) ) { this . setLevel ( Logger . enabledLevel [ i ] ) ; return ; } } } private createLogObj ( level , args ) { return { date : new Date ( ) , ... this . defaults , name : this . name , type : LEVEL_NAMES [ level ] , level , args , } ; } private log ( level , args ) { let logObj ; if ( this . beforeReporters . length ) { logObj = this . createLogObj ( level , args ) ; } for ( const reporter of this . beforeReporters ) { reporter . log ( logObj ) ; } if ( typeof this . level === 'undefined' ) { if ( level > Logger . level ) { return false ; } } else if ( level > this . level ) { return false ; } if ( ! logObj ) { logObj = this . createLogObj ( level , args ) ; } for ( const filter of this . filters ) { if ( filter . filter ( logObj ) === false ) { return false ; } } for ( const extension of this . extensions ) { logObj = extension . extend ( logObj ) ; } for ( const reporter of this . reporters ) { reporter . log ( logObj ) ; } return true ; } } export { Logger } ;