import { listenerHandler , RecordPlugin , IWindow } from '../../../types' ; import { patch } from '../../../utils' ; import { ErrorStackParser , StackFrame } from './error-stack-parser' ; import { stringify } from './stringify' ; export type StringifyOptions = { stringLengthLimit ? : number ; /**
     * limit of number of keys in an object
     * if an object contains more keys than this limit, we would call its toString function directly
     */ numOfKeysLimit : number ; /**
     * limit number of depth in an object
     * if an object is too deep, toString process may cause browser OOM
     */ depthOfLimit : number ; } ; type LogRecordOptions = { level ? : LogLevel [ ] ; lengthThreshold ? : number ; stringifyOptions ? : StringifyOptions ; logger ? : Logger | 'console' ; } ; const defaultLogOptions = { level : [ 'assert' , 'clear' , 'count' , 'countReset' , 'debug' , 'dir' , 'dirxml' , 'error' , 'group' , 'groupCollapsed' , 'groupEnd' , 'info' , 'log' , 'table' , 'time' , 'timeEnd' , 'timeLog' , 'trace' , 'warn' , ] , lengthThreshold : 1000 , logger : 'console' , } ; export type LogData = { level : LogLevel ; trace : string [ ] ; payload : string [ ] ; } ; type logCallback = ( p : LogData ) => void ; export type LogLevel = 'assert' | 'clear' | 'count' | 'countReset' | 'debug' | 'dir' | 'dirxml' | 'error' | 'group' | 'groupCollapsed' | 'groupEnd' | 'info' | 'log' | 'table' | 'time' | 'timeEnd' | 'timeLog' | 'trace' | 'warn' ; export type Logger = { assert ? : typeof console . assert ; clear ? : typeof console . clear ; count ? : typeof console . count ; countReset ? : typeof console . countReset ; debug ? : typeof console . debug ; dir ? : typeof console . dir ; dirxml ? : typeof console . dirxml ; error ? : typeof console . error ; group ? : typeof console . group ; groupCollapsed ? : typeof console . groupCollapsed ; groupEnd ? : ( ) => void ; info ? : typeof console . info ; log ? : typeof console . log ; table ? : typeof console . table ; time ? : typeof console . time ; timeEnd ? : typeof console . timeEnd ; timeLog ? : typeof console . timeLog ; trace ? : typeof console . trace ; warn ? : typeof console . warn ; } ; function initLogObserver ( cb , win , logOptions ) { const loggerType = logOptions . logger ; if ( ! loggerType ) { return ( ) => { } ; } let logger ; if ( typeof loggerType === 'string' ) { logger = win [ loggerType ] ; } else { logger = loggerType ; } let logCount = 0 ; const cancelHandlers = [ ] ; if ( logOptions . level ! . includes ( 'error' ) ) { if ( window ) { const errorHandler = ( event ) => { const { message , error } = event ; const trace = ErrorStackParser . parse ( error ) . map ( ( stackFrame ) => stackFrame . toString ( ) ) ; const payload = [ stringify ( message , logOptions . stringifyOptions ) ] ; cb ( { level : 'error' , trace , payload , } ) ; } ; window . addEventListener ( 'error' , errorHandler ) ; cancelHandlers . push ( ( ) => { if ( window ) window . removeEventListener ( 'error' , errorHandler ) ; } ) ; } } for ( const levelType of logOptions . level ! ) { cancelHandlers . push ( replace ( logger , levelType ) ) ; } return ( ) => { cancelHandlers . forEach ( ( h ) => h ( ) ) ; } ; param logger param level function replace ( _logger , level ) { if ( ! _logger [ level ] ) { return ( ) => { } ; } return patch ( _logger , level , ( original ) => { return ( ... args ) => { original . apply ( this , args ) ; try { const trace = ErrorStackParser . parse ( new Error ( ) ) . map ( ( stackFrame ) => stackFrame . toString ( ) ) . splice ( 1 ) ; const payload = args . map ( ( s ) => stringify ( s , logOptions . stringifyOptions ) ) ; logCount ++ ; if ( logCount < logOptions . lengthThreshold ! ) { cb ( { level , trace , payload , } ) ; } else if ( logCount === logOptions . lengthThreshold ) { cb ( { level : 'warn' , trace : [ ] , payload : [ stringify ( 'The number of log records reached the threshold.' ) , ] , } ) ; } } catch ( error ) { original ( 'rrweb logger error:' , error , ... args ) ; } } ; } ) ; } } export const PLUGIN_NAME = 'rrweb/console@1' ; export const getRecordConsolePlugin = ( options ) => ( { name : PLUGIN_NAME , observer : initLogObserver , options : options ? Object . assign ( { } , defaultLogOptions , options ) : defaultLogOptions , } ) ;