import { LogLevel } from './LogLevel' ; interface BindTarget { [ key ] : Function ; } export interface UnknownFactoryLevels extends Record < Uppercase < string > , number > { SILENT : number ; } export interface Factory { [ key ] : any ; bindMethod : ( obj : BindTarget , methodName : string ) => any ; distillLevel : ( level : number | string ) => any ; levelValid : ( level : number | string ) => boolean ; levels : UnknownFactoryLevels ; logger : LogLevel ; make : ( methodName : string ) => Function ; methods ? : string [ ] ; replaceMethods : ( logLevel : number | string ) => void ; } const noop = ( ) => { } ; const levels = Symbol ( 'log-levels' ) ; const instance = Symbol ( 'log-instance' ) ; const BaseLevels = { TRACE : 0 , DEBUG : 1 , INFO : 2 , WARN : 3 , ERROR : 4 , SILENT : 5 } ; export type MethodFactoryLevels = Lowercase < keyof typeof BaseLevels > ; export class MethodFactory implements Factory { constructor ( logger ? ) { ( this as any ) [ instance ] = logger ; ( this as any ) [ levels ] = BaseLevels ; } get levels ( ) : typeof BaseLevels { return ( this as any ) [ levels ] ; } get logger ( ) : LogLevel { return ( this as any ) [ instance ] ; } set logger ( logger ) { ( this as any ) [ instance ] = logger ; } get methods ( ) { return Object . keys ( this . levels ) . map ( ( key ) => key . toLowerCase ( ) ) . filter ( ( key ) => key !== 'silent' ) ; } bindMethod ( obj , methodName ) { const method = obj [ methodName ] ; if ( typeof method . bind === 'function' ) { return method . bind ( obj ) ; } try { return Function . prototype . bind . call ( method , obj ) ; } catch ( e ) { return function result ( ) { return Function . prototype . apply . apply ( method , [ obj , arguments ] ) ; } ; } } distillLevel ( level ) { let result = level ; if ( typeof result === 'string' && typeof ( this . levels as UnknownFactoryLevels ) [ result . toUpperCase ( ) ] !== 'undefined' ) { result = ( this . levels as UnknownFactoryLevels ) [ result . toUpperCase ( ) ] ; } if ( this . levelValid ( result ) ) { return result ; } return null ; } levelValid ( level ) { if ( typeof level === 'number' && level >= 0 && level <= this . levels . SILENT ) { return true ; } return false ; } /**
     * Build the best logging method possible for this env
     * Wherever possible we want to bind, not wrap, to preserve stack traces.
     * Since we're targeting modern browsers, there's no need to wait for the
     * console to become available.
     */ make ( methodName ) { const target = console as unknown as BindTarget ; if ( typeof target [ methodName ] !== 'undefined' ) { return this . bindMethod ( target , methodName ) ; } else if ( typeof console . log !== 'undefined' ) { return this . bindMethod ( target , 'log' ) ; } return noop ; } replaceMethods ( logLevel ) { const level = this . distillLevel ( logLevel ) ; if ( level === null ) { throw new Error ( `loglevelnext: replaceMethods() called with invalid level: ${ logLevel }` ) ; } if ( ! this . logger || this . logger . type !== 'LogLevel' ) { throw new TypeError ( 'loglevelnext: Logger is undefined or invalid. Please specify a valid Logger instance.' ) ; } this . methods . forEach ( ( methodName ) => { const { [ methodName . toUpperCase ( ) ] : methodLevel } = this . levels ; this . logger [ methodName ] = methodLevel < level ? noop : this . make ( methodName ) ; } ) ; this . logger . log = this . logger . debug ; } }