import angular from 'angular' ; import { Injectable } from 'angular-ts-decorators' ; import autobind from 'autobind-decorator' ; import stackTrace from 'stacktrace-js' ; import { BaseError } from '../errors/errors' ; import { StoreKey } from '../store/store.enum' ; import { TraceLogItem } from '../store/store.interface' ; import { StoreService } from '../store/store.service' ; import { LogLevel } from './log.enum' ; import { LogQueueItem } from './log.interface' ; @ autobind @ Injectable ( 'LogService' ) export class LogService { $injector : ng . auto . IInjectorService ; $log : ng . ILogService ; _$q : ng . IQService ; _storeSvc : StoreService ; currentLogQueueItem : LogQueueItem ; logItemQueue = [ ] ; static $inject = [ '$injector' , '$log' ] ; constructor ( $injector , $log ) { this . $injector = $injector ; this . $log = $log ; } get $q ( ) : ng . IQService { if ( angular . isUndefined ( this . _$q ) ) { this . _$q = this . $injector . get ( '$q' ) ; } return this . _$q ; } get storeSvc ( ) : StoreService { if ( angular . isUndefined ( this . _storeSvc ) ) { this . _storeSvc = this . $injector . get ( 'StoreService' ) ; } return this . _storeSvc ; } addLogItemToQueue ( logItem ) { this . logItemQueue . push ( logItem ) ; } getLogEntries ( ) { return this . storeSvc . get < TraceLogItem [ ] > ( StoreKey . TraceLog ) ; } clear ( ) { return this . storeSvc . remove ( StoreKey . TraceLog ) ; } logError ( error , message ? ) { if ( error . logged ) { return ; } error . logged = true ; this . logToConsole ( message , LogLevel . Error , error ) ; return angular . isUndefined ( error . stack ?? undefined ) ? this . $q . resolve ( ) : stackTrace . fromError ( error ) . then ( ( frames ) => { if ( frames ) { const stack = `${ error . name } (${ error . constructor . name }): ${ error . message }\n${ frames . map ( ( f ) => { return `\tat ${ f . functionName } (${ f . fileName }:${ f . lineNumber }:${ f . columnNumber })` ; } ) . join ( '\n' ) }` ; error . stack = stack ; } this . addLogItemToQueue ( { level : LogLevel . Error , message , error } ) ; this . processLogItemQueue ( ) ; } ) ; } logInfo ( message ) { if ( ! message ) { return ; } this . logToConsole ( message ) ; this . addLogItemToQueue ( { level : LogLevel . Trace , message } ) ; this . processLogItemQueue ( ) ; } logToConsole ( message , level = LogLevel . Trace , error ? ) { switch ( level ) { case LogLevel . Error : this . $log . warn ( error ?? message ) ; break ; case LogLevel . Warn : this . $log . warn ( message ) ; break ; case LogLevel . Trace : default : this . $log . info ( message ) ; } } logWarning ( message ) { if ( ! message ) { return ; } this . logToConsole ( message , LogLevel . Warn ) ; this . addLogItemToQueue ( { level : LogLevel . Warn , message } ) ; this . processLogItemQueue ( ) ; } processLogItemQueue ( ) { if ( this . currentLogQueueItem || this . logItemQueue . length === 0 ) { return this . $q . resolve ( ) ; } this . currentLogQueueItem = this . logItemQueue . shift ( ) ; let message = angular . isObject ( this . currentLogQueueItem . message ) ? JSON . stringify ( this . currentLogQueueItem . message ) : this . currentLogQueueItem . message ?? '' ; if ( this . currentLogQueueItem . error ) { message += `${ this . currentLogQueueItem . error . stack . replace ( /\s+/g , ' ' ) }` ; } const logItem = { level : this . currentLogQueueItem . level , message , timestamp : new Date ( ) . getTime ( ) } ; return this . storeSvc . set ( StoreKey . TraceLog , logItem ) . then ( ( ) => { this . currentLogQueueItem = undefined ; this . processLogItemQueue ( ) ; } ) ; } }