import { loggerContext } from "./platform/logger-context" ; import { errorToException } from "./util/error-util" ; import { app } from "./app" ; import { APIException , Exception , JavaScriptException , NetworkConnectionException } from "./Exception" ; interface Log { date : Date ; action : string ; result : "OK" | "WARN" | "ERROR" ; elapsedTime : number ; context : { [ key ] : string ; } ; info : { [ key ] : string ; } ; stats : { [ key ] : number ; } ; errorCode ? : string | undefined ; errorMessage ? : string | undefined ; } interface InfoLogEntry { action : string ; elapsedTime ? : number ; info ? : { [ key ] : string | undefined ; } ; stats ? : { [ key ] : number | undefined ; } ; } interface ErrorLogEntry extends InfoLogEntry { errorCode : string ; errorMessage : string ; } /**
 * If eventLogger config is provided in non-DEV environment.
 * All collected logs will automatically sent to {serverURL} in a regular basis.
 *
 * The request will be PUT to the server in the following format:
 *      {events: Log[]}
 */ export interface LoggerConfig { serverURL : string ; slowStartupThreshold ? : number ; maskedKeywords ? : RegExp [ ] ; } export interface Logger { addContext ( context ) : void ; removeContext ( key ) : void ; info ( entry ) : void ; warn ( data ) : void ; error ( data ) : void ; } export class LoggerImpl implements Logger { private contextMap : { [ key ] : string | ( ( ) => string ) ; } = { } ; private logQueue : Log [ ] = [ ] ; private collectPosition = 0 ; constructor ( ) { this . contextMap = loggerContext ; } addContext ( context ) { const newContextMap = { ... this . contextMap , ... context } ; const contextSize = Object . keys ( newContextMap ) . length ; if ( contextSize > 20 ) { console . warn ( `[framework] Logger context size ${ contextSize } is too large` ) ; } this . contextMap = newContextMap ; } removeContext ( key ) { if ( this . contextMap [ key ] !== undefined ) { delete this . contextMap [ key ] ; } else { console . warn ( `[framework] Logger context key ${ key } does not exist` ) ; } } info ( entry ) { this . createLog ( "OK" , entry ) ; } warn ( entry ) { this . createLog ( "WARN" , entry ) ; } error ( entry ) { this . createLog ( "ERROR" , entry ) ; } exception ( exception , extraInfo , action ) { let isWarning ; let errorCode ; const info = { ... extraInfo } ; if ( exception instanceof NetworkConnectionException ) { isWarning = true ; errorCode = "NETWORK_FAILURE" ; info [ "api_url" ] = exception . requestURL ; info [ "original_message" ] = exception . originalErrorMessage ; } else if ( exception instanceof APIException ) { if ( exception . statusCode === 400 && exception . errorCode === "VALIDATION_ERROR" ) { isWarning = false ; errorCode = "API_VALIDATION_ERROR" ; } else { isWarning = true ; errorCode = `API_ERROR_${ exception . statusCode }` ; } info [ "api_url" ] = exception . requestURL ; info [ "api_response" ] = JSON . stringify ( exception . responseData ) ; if ( exception . errorId ) { info [ "api_error_id" ] = exception . errorId ; } if ( exception . errorCode ) { info [ "api_error_code" ] = exception . errorCode ; } } else if ( exception instanceof JavaScriptException ) { isWarning = false ; errorCode = "JAVASCRIPT_ERROR" ; info [ "app_state" ] = JSON . stringify ( app . store . getState ( ) . app ) ; } else { console . warn ( "[framework] Exception class should not be extended, throw Error instead" ) ; isWarning = false ; errorCode = "JAVASCRIPT_ERROR" ; } this . createLog ( isWarning ? "WARN" : "ERROR" , { action , errorCode , errorMessage : exception . message , info , elapsedTime : 0 } ) ; } collect ( maxSize = 0 ) { const totalLength = this . logQueue . length ; if ( maxSize > 0 && maxSize < totalLength ) { this . collectPosition = maxSize ; return this . logQueue . slice ( 0 , maxSize ) ; } else { this . collectPosition = totalLength ; return this . logQueue ; } } emptyLastCollection ( ) { this . logQueue = this . logQueue . slice ( this . collectPosition ) ; } private createLog ( result , entry ) { const context = { } ; Object . entries ( this . contextMap ) . map ( ( [ key , value ] ) => { if ( typeof value === "string" ) { context [ key ] = value . substr ( 0 , 1000 ) ; } else { try { context [ key ] = value ( ) ; } catch ( e ) { const message = errorToException ( e ) . message ; context [ key ] = "ERR# " + message ; console . warn ( "[framework] Fail to execute logger context: " + message ) ; } } } ) ; const info = { } ; if ( entry . info ) { Object . entries ( entry . info ) . map ( ( [ key , value ] ) => { if ( value !== undefined ) { const isBuiltinInfo = [ "app_state" , "stacktrace" , "extra_stacktrace" ] . includes ( key ) ; info [ key ] = isBuiltinInfo ? value . substr ( 0 , 500000 ) : value . substr ( 0 , 500 ) ; } } ) ; } const stats = { } ; if ( entry . stats ) { Object . entries ( entry . stats ) . map ( ( [ key , value ] ) => { if ( value !== undefined ) { stats [ key ] = value ; } } ) ; } const event = { date : new Date ( ) , action : entry . action , elapsedTime : entry . elapsedTime || 0 , result , context , info , stats , errorCode : "errorCode" in entry ? entry . errorCode : undefined , errorMessage : "errorMessage" in entry ? entry . errorMessage . substr ( 0 , 1000 ) : undefined , } ; this . logQueue . push ( event ) ; } }