get_levels() as $level ) { add_action( "qm/{$level}", array( $this, $level ), 10, 2 ); } add_action( 'qm/log', array( $this, 'log' ), 10, 3 ); } public function emergency( $message, array $context = array() ) { $this->store( self::EMERGENCY, $message, $context ); } public function alert( $message, array $context = array() ) { $this->store( self::ALERT, $message, $context ); } public function critical( $message, array $context = array() ) { $this->store( self::CRITICAL, $message, $context ); } public function error( $message, array $context = array() ) { $this->store( self::ERROR, $message, $context ); } public function warning( $message, array $context = array() ) { $this->store( self::WARNING, $message, $context ); } public function notice( $message, array $context = array() ) { $this->store( self::NOTICE, $message, $context ); } public function info( $message, array $context = array() ) { $this->store( self::INFO, $message, $context ); } public function debug( $message, array $context = array() ) { $this->store( self::DEBUG, $message, $context ); } public function log( $level, $message, array $context = array() ) { if ( ! in_array( $level, $this->get_levels(), true ) ) { throw new InvalidArgumentException( __( 'Unsupported log level', 'query-monitor' ) ); } $this->store( $level, $message, $context ); } protected function store( $level, $message, array $context = array() ) { $type = 'string'; $trace = new QM_Backtrace( array( 'ignore_frames' => 2, ) ); if ( is_wp_error( $message ) ) { $type = 'wp_error'; $message = sprintf( 'WP_Error: %s (%s)', $message->get_error_message(), $message->get_error_code() ); } if ( ( $message instanceof Exception ) || ( $message instanceof Throwable ) ) { $type = 'throwable'; $message = get_class( $message ) . ': ' . $message->getMessage(); } if ( ! QM_Util::is_stringy( $message ) ) { $type = 'dump'; $message = print_r( $message, true ); } $this->data['logs'][] = array( 'message' => self::interpolate( $message, $context ), 'context' => $context, 'trace' => $trace, 'level' => $level, 'type' => $type, ); } protected static function interpolate( $message, array $context = array() ) { // build a replacement array with braces around the context keys $replace = array(); foreach ( $context as $key => $val ) { // check that the value can be casted to string if ( is_bool( $val ) ) { $replace[ "{{$key}}" ] = ( $val ? 'true' : 'false' ); } elseif ( is_scalar( $val ) || QM_Util::is_stringy( $val ) ) { $replace[ "{{$key}}" ] = $val; } } // interpolate replacement values into the message and return return strtr( $message, $replace ); } public function process() { if ( empty( $this->data['logs'] ) ) { return; } $components = array(); foreach ( $this->data['logs'] as $row ) { $component = $row['trace']->get_component(); $components[ $component->name ] = $component->name; } $this->data['components'] = $components; } public function get_levels() { return array( self::EMERGENCY, self::ALERT, self::CRITICAL, self::ERROR, self::WARNING, self::NOTICE, self::INFO, self::DEBUG, ); } public function get_warning_levels() { return array( self::EMERGENCY, self::ALERT, self::CRITICAL, self::ERROR, self::WARNING, ); } } # Load early in case a plugin wants to log a message early in the bootstrap process QM_Collectors::add( new QM_Collector_Logger() );