. * @since 1.0.0 */ namespace Decalog\Plugin\Feature; use Decalog\Plugin\Feature\HandlerTypes; use DLMonolog\Logger; use Decalog\System\Environment; use Decalog\System\Option; use Decalog\System\Timezone; use Decalog\Plugin\Feature\LoggerFactory; use Decalog\Plugin\Feature\ClassTypes; use Decalog\Plugin\Feature\ChannelTypes; use Decalog\Plugin\Feature\HandlerDiagnosis; use Decalog\System\UUID; use Decalog\Plugin\Feature\DMonitor; /** * Main DecaLog logger class. * * This class defines all code necessary to log events with DecaLog. * * @package Features * @author Pierre Lannoy . * @since 1.0.0 */ class DLogger { /** * The class of the component. * * @since 1.0.0 * @var string $class Maintains the class of the component. */ protected $class = 'unknwon'; /** * The name of the component. * * @since 1.0.0 * @var string $class Maintains the name of the component. */ protected $name = 'unknown'; /** * The version of the component. * * @since 1.0.0 * @var string $version Maintains the version of the component. */ protected $version = '-'; /** * The monolog logger. * * @since 1.0.0 * @var object $logger Maintains the logger. */ protected $logger = null; /** * Is the logger in test. * * @since 1.2.1 * @var boolean $in_test Maintains the test status of the logger. */ protected $in_test = false; /** * Is this listener a PSR-3 logger. * * @since 1.3.0 * @var boolean $psr3 Maintains the psr3 status of the logger. */ private $psr3 = false; /** * Is logger allowed to run. * * @since 1.3.0 * @var boolean $allowed Maintains the allowed status of the logger. */ private $allowed = true; /** * Messages counter. * * @since 1.3.0 * @var array $counter Maintains the messages counter. */ private static $counter = []; /** * Initialize the class and set its properties. * * @param string $class The class identifier, must be in self::$classes. * @param string $name Optional. The name of the component. * @param string $version Optional. The version of the component. * @param string $test Optional. The handler to create if specified. * @param boolean $psr3 Optional. True if this logger is a PSR-3 logger. * @since 1.0.0 */ public function __construct( $class, $name = null, $version = null, $test = null, $psr3 = false ) { if ( ! defined( 'DECALOG_TRACEID' ) ) { define( 'DECALOG_TRACEID', UUID::generate_unique_id( 32 ) ); } if ( in_array( $class, ClassTypes::$classes, true ) ) { $this->class = $class; } if ( $name && is_string( $name ) ) { $this->name = $name; } if ( $version && is_string( $version ) ) { $this->version = $version; } $this->psr3 = $psr3; $this->init( $test ); $this->debug( 'A new instance of DecaLog logger is initialized and operational.' ); } /** * Init the logger. * * @param string $test Optional. The handler to init if specified. * @since 1.0.0 */ private function init( $test = null ) { if ( $this->psr3 ) { if ( ! Option::network_get( 'autolisteners' ) ) { $this->allowed = in_array( 'psr3', Option::network_get( 'listeners' ), true ); } } $this->in_test = isset( $test ); $factory = new LoggerFactory(); $this->logger = new Logger( $this->current_channel_tag(), [], [], Timezone::network_get() ); $handlers = new HandlerTypes(); $diagnosis = new HandlerDiagnosis(); $unloadable = []; $skipped = []; foreach ( $this->loggers_check() as $key => $logger ) { if ( $this->in_test && $key !== $test ) { continue; } if ( '' === $key ) { continue; } if ( ! is_array( $logger ) || ( is_array( $logger ) && !array_key_exists('handler', $logger) ) ) { continue; } $handler_def = $handlers->get( $logger['handler'] ); $logger['uuid'] = $key; if ( $handler_def && $diagnosis->check( $handler_def['id'] ) ) { $handler = $factory->create_logger( $logger ); if ( $handler instanceof \DLMonolog\Handler\HandlerInterface ) { $this->logger->pushHandler( $handler ); } elseif ( $logger['running'] ) { $skipped[] = sprintf( 'Skipping loading of a %s logger.', $handler_def['name'] ); } } else { if ( $handler_def ) { $unloadable[] = sprintf( 'Unable to load a %s logger. %s', $handler_def['name'], $diagnosis->error_string( $handler_def['id'] ) ); } else { $unloadable[] = 'Unable to load a logger.'; } } } if ( count( $unloadable ) > 0 ) { foreach ( $unloadable as $item ) { $this->error( $item, 666 ); } } if ( count( $skipped ) > 0 ) { foreach ( $skipped as $item ) { $this->debug( $item ); } } } /** * Check the loggers. * * @return array The logger list. * @since 2.0.0 */ private function loggers_check() { $loggers = Option::network_get( 'loggers', [] ); $persist_logger_update = false; // Verify data structure and fix if required if ( ! is_array( $loggers ) ) { $persist_logger_update = true; $loggers = []; } // Verify shared memory logger if ( ! array_key_exists( DECALOG_SHM_ID, $loggers ) ) { $persist_logger_update = true; $shm = []; $shm['name'] =decalog__( 'System events-logger', 'decalog' ); $shm['handler'] = 'SharedMemoryHandler'; $shm['running'] = Option::network_get( 'livelog' ); $shm['level'] = Logger::INFO; $shm['privacy'] = [ 'obfuscation' => 0, 'pseudonymization' => 0, ]; $shm['processors'] = [ 'WordpressProcessor', 'IntrospectionProcessor', 'WWWProcessor' ]; $loggers[ DECALOG_SHM_ID ] = $shm; } // Setup loggers from wp-config if ( defined( 'DECALOG_DEFAULT_LOGGERS' ) && is_array( DECALOG_DEFAULT_LOGGERS ) ) { foreach ( DECALOG_DEFAULT_LOGGERS as $default_logger_id => $default_logger ) { if ( ! array_key_exists( $default_logger_id, $loggers ) ) { $persist_logger_update = true; $loggers[ $default_logger_id ] = $default_logger; } } } if ( $persist_logger_update ) { Option::network_set( 'loggers', $loggers ); } return $loggers; } /** * Get the current channel tag. * * @return string The current channel tag. * @since 1.0.0 */ private function current_channel_tag() { return $this->channel_tag( Environment::exec_mode() ); } /** * Get the channel tag. * * @param integer $id Optional. The channel id (execution mode). * @return string The channel tag. * @since 1.0.0 */ public function channel_tag( $id = 0 ) { if ( $id >= count( ChannelTypes::$channels ) ) { $id = 0; } return ChannelTypes::$channels[ $id ]; } /** * Verify if DEBUG is allowed. * * @return boolean True if DEBUG message are allowed, false otherwise. * @since 1.0.0 */ private function is_debug_allowed() { if ( ! Option::network_get( 'respect_wp_debug' ) ) { return true; } if ( defined( 'WP_DEBUG' ) ) { return WP_DEBUG; } return true; } /** * Normalize a string. * * @param string $string The string. * @return string The normalized string. * @since 1.10.0+ */ private function normalize_string( $string ) { $string = str_replace( '"', '“', $string ); $string = str_replace( '\'', '`', $string ); $string = str_replace( '>=', '≥', $string ); $string = str_replace( '<=', '≤', $string ); return decalog_filter_string( $string ); } /** * Normalize an array. * * @param mixed $array The array. * @return mixed The normalized array. * @since 1.10.0+ */ private function normalize_array( $array ) { array_walk_recursive( $array, function ( &$item, $key ) { if ( is_string( $item ) ) { $item = $this->normalize_string( $item ); } } ); return $array; } /** * Normalize an array. * * @param mixed $level The log level. * @return integer The counter for this level. * @since 1.10.0+ */ public static function count( $level ) { if ( is_string( $level ) ) { $level = EventTypes::$levels[ strtolower( $level ) ]; } if ( ! array_key_exists( $level, self::$counter ) ) { return 0; } return self::$counter[ $level ]; } /** * Adds a log record at a specific level. * * @param mixed $level The log level. * @param string $message The log message. * @param integer $code Optional. The log code. * @param string $phase Optional. The log phase. * @param boolean $signal Optional. Add . * @return boolean True if message was logged, false otherwise. * @since 1.0.0 */ public function log( $level, $message, $code = 0, $phase = '', $signal = true ) { if ( is_string( $level ) ) { $level = EventTypes::$levels[ strtolower( $level ) ]; } if ( ! array_key_exists( $level, self::$counter ) ) { self::$counter[ $level ] = 0; } self::$counter[ $level ]++; if ( ! $this->allowed ) { return false; } $debug = ( Logger::DEBUG === $level || 100 === $level || 'DEBUG' === strtoupper( (string) $level ) ); if ( $debug && ! $this->is_debug_allowed() ) { return false; } try { $context = [ 'class' => (string) $this->class, 'component' => (string) $this->name, 'version' => (string) $this->version, 'phase' => (string) $phase, 'code' => (int) $code, 'environment' => (string) Environment::stage(), 'traceID' => (string) DECALOG_TRACEID, 'instance' => (string) DECALOG_INSTANCE_NAME, ]; $channel = $this->current_channel_tag(); if ( $this->logger->getName() !== $channel ) { $this->logger = $this->logger->withName( $channel ); } $result = true; // phpcs:ignore set_error_handler( function () use (&$result) {$result = false;} ); $this->logger->log( $level, $this->normalize_string( $message ), $this->normalize_array( $context ) ); // phpcs:ignore restore_error_handler(); } catch ( \Throwable $t ) { $result = false; } finally { if ( $signal && ! $result && ! $debug ) { //$this->log( Logger::ALERT, 'error', 666, '', false ); } return $result; } } /** * Adds a log record at the DEBUG level. * * @param string $message The log message. * @param integer $code Optional. The log code. * @return boolean True if message was logged, false otherwise. * @since 1.0.0 */ public function debug( $message, $code = 0 ) { return $this->log( Logger::DEBUG, $message, $code ); } /** * Adds a log record at the INFO level. * * @param string $message The log message. * @param integer $code Optional. The log code. * @return boolean True if message was logged, false otherwise. * @since 1.0.0 */ public function info( $message, $code = 0 ) { return $this->log( Logger::INFO, $message, $code ); } /** * Adds a log record at the NOTICE level. * * @param string $message The log message. * @param integer $code Optional. The log code. * @return boolean True if message was logged, false otherwise. * @since 1.0.0 */ public function notice( $message, $code = 0 ) { return $this->log( Logger::NOTICE, $message, $code ); } /** * Adds a log record at the WARNING level. * * @param string $message The log message. * @param integer $code Optional. The log code. * @return boolean True if message was logged, false otherwise. * @since 1.0.0 */ public function warning( $message, $code = 0 ) { return $this->log( Logger::WARNING, $message, $code ); } /** * Adds a log record at the ERROR level. * * @param string $message The log message. * @param integer $code Optional. The log code. * @return boolean True if message was logged, false otherwise. * @since 1.0.0 */ public function error( $message, $code = 0 ) { return $this->log( Logger::ERROR, $message, $code ); } /** * Adds a log record at the CRITICAL level. * * @param string $message The log message. * @param integer $code Optional. The log code. * @return boolean True if message was logged, false otherwise. * @since 1.0.0 */ public function critical( $message, $code = 0 ) { return $this->log( Logger::CRITICAL, $message, $code ); } /** * Adds a log record at the ALERT level. * * @param string $message The log message. * @param integer $code Optional. The log code. * @return boolean True if message was logged, false otherwise. * @since 1.0.0 */ public function alert( $message, $code = 0 ) { return $this->log( Logger::ALERT, $message, $code ); } /** * Adds a log record at the EMERGENCY level. * * @param string $message The log message. * @param integer $code Optional. The log code. * @return boolean True if message was logged, false otherwise. * @since 1.0.0 */ public function emergency( $message, $code = 0 ) { return $this->log( Logger::EMERGENCY, $message, $code ); } }