From 713ff0ab28ac55562facc897eddc22f66536c119 Mon Sep 17 00:00:00 2001 From: Alex Standiford Date: Thu, 5 Aug 2021 07:06:26 -0600 Subject: [PATCH] Release/2.0.0 (#6) * Adds support to append event type data to logged items. This makes it possible for site admins to better-grok their error logs. * Simpfies basic loger to use PHP's built in error logging by default. * Updates event types to support middleware, and multiple log writers. * Updates default logger to support middlewares * Adds default middleware and writer factory --- lib/abstracts/Event_Type.php | 202 +++++---- lib/abstracts/registries/Event_Registry.php | 271 ------------ lib/cron-jobs/Purge_Logs.php | 50 --- .../event-type-purge-frequency/Event_Type.php | 40 -- .../Event_Type_Purge_Frequency.php | 37 -- lib/factories/Basic_Logger.php | 179 +------- lib/factories/Basic_Logger_Middleware.php | 28 ++ .../Include_Backtrace_Middleware.php | 40 ++ lib/factories/Log_Item.php | 50 ++- lib/factories/Writer_Instance.php | 57 +++ lib/loaders/Logger.php | 412 ++++++++++++++---- lib/traits/With_Meta.php | 170 -------- logger.php | 32 +- 13 files changed, 609 insertions(+), 959 deletions(-) delete mode 100644 lib/abstracts/registries/Event_Registry.php delete mode 100644 lib/cron-jobs/Purge_Logs.php delete mode 100644 lib/decisions/event-type-purge-frequency/Event_Type.php delete mode 100644 lib/decisions/event-type-purge-frequency/Event_Type_Purge_Frequency.php create mode 100644 lib/factories/Basic_Logger_Middleware.php create mode 100644 lib/factories/Include_Backtrace_Middleware.php create mode 100644 lib/factories/Writer_Instance.php delete mode 100644 lib/traits/With_Meta.php diff --git a/lib/abstracts/Event_Type.php b/lib/abstracts/Event_Type.php index c9a70d0..1362671 100644 --- a/lib/abstracts/Event_Type.php +++ b/lib/abstracts/Event_Type.php @@ -12,7 +12,13 @@ use ArrayIterator; use Exception; +use Underpin\Abstracts\Underpin; +use Underpin\Factories\Registry; +use Underpin\Traits\Middleware; +use Underpin_Logger\Abstracts\Registries\Capability_Registry; +use Underpin_Logger\Abstracts\Registries\Writer_Registry; use Underpin_Logger\Factories\Log_Item; +use Underpin_Logger\Loaders\Logger; use WP_Error; use function Underpin\underpin; @@ -28,6 +34,8 @@ */ abstract class Event_Type extends ArrayIterator { + use Middleware; + /** * Event type * @@ -35,26 +43,7 @@ abstract class Event_Type extends ArrayIterator { * * @var string */ - public $type = ''; - - /** - * Writes this to the log. - * Set this to true to cause this event to get written to the log. - * - * @since 1.0.0 - * - * @var bool - */ - protected $write_to_log = false; - - /** - * Force logged events to include a backtrace. - * - * @since 1.0.0 - * - * @var bool - */ - protected $include_backtrace = false; + protected $type = ''; /** * List of capabilities. @@ -65,7 +54,7 @@ abstract class Event_Type extends ArrayIterator { * * @var array */ - protected $capabilities = []; + protected $capabilities; /** * The minimum volume to be able to see events of this type. @@ -108,7 +97,7 @@ abstract class Event_Type extends ArrayIterator { * * @var string */ - public $psr_level = ''; + protected $psr_level = ''; /** * The class to instantiate when writing to the error log. @@ -117,7 +106,7 @@ abstract class Event_Type extends ArrayIterator { * * @var string Namespaced instance of writer class. */ - public $writer_class = 'Underpin_Logger\Factories\Basic_Logger'; + protected $writers; /** * The class to instantiate when logging a new item. @@ -126,7 +115,7 @@ abstract class Event_Type extends ArrayIterator { * * @var string Namespaced instance of log item class. */ - public $log_item_class = 'Underpin_Logger\Factories\Log_Item'; + protected $log_item_class = 'Underpin_Logger\Factories\Log_Item'; /** * Determines how often this event type should be purged. @@ -146,56 +135,43 @@ public function __construct() { parent::__construct(); /** - * Filters the writer that is used when logging events. + * Makes it possible to modify the middleware for all logged events. * - * @since 1.0.0 - * @param string $writer_class The writer class to instantiate when a writer is created. - * @param string $type The current event type. - * @param string $log_item_class The writer class to instantiate when a writer is created. - */ - $this->writer_class = apply_filters( 'underpin/event_type/writer_class', $this->writer_class, $this->type, $this->log_item_class ); - - /** - * Filters the log item that is used when logging events. + * @since 2.0.0 * - * @since 1.0.0 - * @param string $log_item_class The writer class to instantiate when a writer is created. - * @param string $type The current event type. - * @param string $writer_class The writer class to instantiate when a writer is created. - */ - $this->log_item_class = apply_filters( 'underpin/event_type/log_item_class', $this->log_item_class, $this->type, $this->writer_class ); - - /** - * Filters the capabilities for event types. + * @param array $middlewares list of middlewares to add + * @param Event_Type $event_type The event type instance * - * @since 1.0.0 - * @param array $capabilities The list of capabilities to set for this event type. - * @param type $type The current event type - * @param type $group The current event group */ - $this->capabilities = apply_filters( 'underpin/event_type/capabilities', $this->capabilities, $this->type ); - $this->capabilities[] = 'administrator'; - - - /** - * Filters the group for event types. - * - * @since 1.0.0 - * @param array $capabilities The list of capabilities to set for this event type. - * @param type $type The current event type - * @param type $group The current event group - */ - $this->group = apply_filters( 'underpin/event_type/group', $this->group, $this->type ); - - /** - * Filters the volume for event types. - * - * @since 1.0.0 - * @param array $capabilities The list of capabilities to set for this event type. - * @param type $type The current event type - * @param type $group The current event group - */ - $this->volume = apply_filters( 'underpin/event_type/volume', $this->volume, $this->type ); + $this->middlewares = apply_filters( 'underpin\event_logs\middlewares', $this->middlewares, $this ); + + $this->writers = new Registry( [ + 'registry_id' => 'underpin_logger_' . $this->type, + 'skip_logging' => true, + 'validate_callback' => function ( $key, $value ) { + $abstraction_class = '\Underpin_Logger\Abstracts\Writer'; + if ( $value === $abstraction_class || is_subclass_of( $value, $abstraction_class ) || $value instanceof $abstraction_class ) { + return true; + } + + if ( is_array( $value ) ) { + if ( isset( $value['write_callback'] ) && isset( $value['clear_callback'] ) && isset( $value['purge_callback'] ) ) { + return true; + } + } + + return false; + }, + ] ); + + $this->capabilities = new Registry( [ + 'registry_id' => 'underpin_logger_capabilities_' . $this->type, + 'skip_logging' => true, + 'default_items' => [ 'administrator' ], + 'validate_callback' => function ( $key, $value ) { + return is_string( $value ); + }, + ] ); } /** @@ -209,49 +185,54 @@ public function do_actions() { * Log events to the logger. * * @since 1.0.0 + * @since 2.0.0 - Added support for multiple logger writers */ public function log_events() { - $writer = $this->writer(); - - if ( ! is_wp_error( $writer ) ) { - $writer->write_events(); - reset( $this ); - } - - do_action( 'logger/after_log_events', $this->writer_class, $this->type ); + $this->write_events( $this ); + reset( $this ); } + /** - * Fetch the log writer instance, if this class supports logging. + * Constructs a writer instance from the provided key and event type. * - * @since 1.0.0 + * @since 2.0.0 + * + * @param $key + * @param Event_Type $event_type * - * @return Writer|WP_Error The logger instance if this can write to log. WP_Error otherwise. + * @return Writer|WP_Error */ - public function writer() { - if ( true !== $this->write_to_log ) { - return new WP_Error( - 'event_type_does_not_write', - 'The specified event type does not write to the logger. To change this, set the write_to_log param to true.', - [ 'logger' => $this->type, 'write_to_log_value' => $this->write_to_log ] - ); - } elseif ( true === underpin()->logger()->is_muted() ) { - return new WP_Error( - 'events_are_muted', - 'The specified event type was not logged because events are muted.', - [ 'logger' => $this->type ] - ); + public function make_writer( $key, Event_Type $event_type ) { + $item = $this->writers->get( $key ); + + // If something went wrong, return the WP Error + if ( is_wp_error( $item ) ) { + return $item; } - if ( ! is_subclass_of( $this->writer_class, 'Underpin_Logger\Abstracts\Writer' ) ) { - return new WP_Error( - 'writer_class_invalid', - 'The writer class must be extend the Writer class.', - [ 'writer_class' => $this->writer_class ] - ); + // if this event was made using the array method, construct it with make class. + if ( is_array( $item ) ) { + $logger['event_type'] = $event_type; + return Underpin::make_class( $logger, '\Underpin_Logger\Factories\Event_Type_Instance' ); } - return new $this->writer_class( $this ); + // Return the event + return new $item( $event_type ); + } + + /** + * Write the events in this event type to each specified writer. + * + * @since 2.0.0 + */ + protected function write_events( Event_Type $event_type ) { + foreach ( (array) $this->writers as $key => $logger ) { + $writer = $this->make_writer( $key, $event_type ); + if ( ! is_wp_error( $logger ) ) { + $writer->write_events(); + } + } } @@ -267,11 +248,22 @@ public function writer() { */ public function log( $code, $message, $data = array() ) { - if ( true === $this->include_backtrace ) { - $data['backtrace'] = wp_debug_backtrace_summary( null, 3, false ); - } + /** + * Makes it possible to add additional data to logged events. + * + * @since 2.0.0 + * + * @param array $data list of data to add + * @param string $code event code + * @param string $message event message + * @param Event_Type $instance The current event instance + * + */ + $additional_logged_data = apply_filters( 'underpin/logger/additional_logged_data', [], $code, $message, $this ); - $item = new $this->log_item_class( $this->type, $code, $message, $data ); + // Add additional data, but original data should take priority. + $data = array_merge( (array) $additional_logged_data, $data ); + $item = new $this->log_item_class( $this, $code, $message, $data, ); if ( ! $item instanceof Log_Item ) { return new WP_Error( diff --git a/lib/abstracts/registries/Event_Registry.php b/lib/abstracts/registries/Event_Registry.php deleted file mode 100644 index 6c25f15..0000000 --- a/lib/abstracts/registries/Event_Registry.php +++ /dev/null @@ -1,271 +0,0 @@ -get( $key )->do_actions(); - } - - return $valid; - } - - /** - * Retrieves all events that have happened for this request. - * - * @since 1.0.0 - * - * @param bool $type The event type to retrieve. If false, this will get all events. - * @return array|WP_Error list of all events, or a WP_Error if something went wrong. - */ - public function get_request_events( $type = false ) { - if ( false !== $type ) { - $events = $this->get( $type ); - - // Return the error. - if ( is_wp_error( $events ) ) { - return $events; - } else { - return (array) $events; - } - - } else { - $result = []; - foreach ( $this as $type => $events ) { - $result[ $type ] = (array) $this->get( $type ); - } - - return $result; - } - } - - /** - * Enqueues an event to be logged in the system - * - * @since 1.0.0 - * - * @param string $type Event log type - * @param string $code The event code to use. - * @param string $message The message to log. - * @param array $data Arbitrary data associated with this event message. - * @return Log_Item|WP_Error Log item, with error message. WP_Error if something went wrong. - */ - public function log( $type, $code, $message, $data = array() ) { - $event_type = $this->get( $type ); - - if ( is_wp_error( $event_type ) ) { - return $event_type; - } - - return $event_type->log( $code, $message, $data ); - } - - /** - * Mutes the logger. - * - * @since 1.0.0 - */ - private function mute() { - $this->is_muted = true; - } - - /** - * Un-Mutes the logger. - * - * @since 1.0.0 - */ - private function unmute() { - $this->is_muted = false; - } - - /** - * Does a flareWP action, muting all events that would otherwise happen. - * - * @since 1.2.4 - * - * @param callable $action The muted action to call. - * @return mixed|WP_Error The action returned result, or WP_Error if something went wrong. - */ - public function do_muted_action( $action ) { - if ( is_callable( $action ) ) { - $this->mute(); - $result = $action(); - $this->unmute(); - } else { - $result = new WP_Error( - 'muted_action_not_callable', - 'The provided muted action is not callable', - [ 'ref' => $action, 'type' => 'function' ] - ); - } - - return $result; - } - - /** - * Fetches the mute status of the logger. - * - * @since 1.0.0 - */ - public function is_muted() { - return $this->is_muted; - } - - /** - * Enqueues an event to be logged in the system, and then returns a WP_Error object. - * - * @since 1.0.0 - * - * @param string $type Event log type - * @param string $code The event code to use. - * @param string $message The message to log. - * @param array $data Arbitrary data associated with this event message. - * @return WP_Error Log item, with error message. WP_Error if something went wrong. - */ - public function log_as_error( $type, $code, $message, $data = array() ) { - $item = $this->log( $type, $code, $message, $data ); - - if ( ! is_wp_error( $item ) ) { - $item = $item->error(); - } - - return $item; - } - - /** - * Logs an error using a WP Error object. - * - * @since 1.0.0 - * - * @param string $type Error log type - * @param WP_Error $wp_error Instance of WP_Error to use for log - * @param array $data Additional data to log - * @return Log_Item|WP_Error The logged item, if successful, otherwise WP_Error. - */ - public function log_wp_error( $type, WP_Error $wp_error, $data = [] ) { - $item = $this->get( $type ); - - if ( is_wp_error( $item ) ) { - return $item; - } - - $error = $item->log_wp_error( $wp_error, $data ); - - return $error; - } - - /** - * Logs an error from within an exception. - * - * @since 1.0.0 - * - * @param string $type Error log type - * @param Exception $exception Exception instance to log. - * @param array $data array Data associated with this error message - * @return Log_Item|WP_Error Log Item, with error message if successful, otherwise WP_Error. - */ - public function log_exception( $type, Exception $exception, $data = array() ) { - $item = $this->get( $type ); - - if ( is_wp_error( $type ) ) { - return $item; - } - - $error = $item->log_exception( $exception, $data ); - - return $error; - } - - /** - * @param string $key - * @return Event_Type|WP_Error Event type, if it exists. WP_Error, otherwise. - */ - public function get( $key ) { - $result = parent::get( $key ); - - // If the logged event could not be found, search event type keys. - if ( is_wp_error( $result ) ) { - $event_type = $this->find( [ - 'type' => $key, - ] ); - - // If that also comes up empty, return the error. - if ( is_wp_error( $event_type ) ) { - return $result; - } - - // Return the discovered event. - $result = $event_type; - } - - return $result; - } - - /** - * Purge old logged events. - * - * @since 1.0.0 - * - */ - public function cleanup() { - foreach ( $this as $key => $class ) { - $writer = $this->get( $key )->writer(); - - if ( ! is_wp_error( $writer ) ) { - $purged = $writer->cleanup(); - - if ( is_wp_error( $purged ) ) { - $this->log_wp_error( 'error', $purged ); - } - } - } - } - -} \ No newline at end of file diff --git a/lib/cron-jobs/Purge_Logs.php b/lib/cron-jobs/Purge_Logs.php deleted file mode 100644 index 3f63129..0000000 --- a/lib/cron-jobs/Purge_Logs.php +++ /dev/null @@ -1,50 +0,0 @@ -logger()->cleanup(); - } -} \ No newline at end of file diff --git a/lib/decisions/event-type-purge-frequency/Event_Type.php b/lib/decisions/event-type-purge-frequency/Event_Type.php deleted file mode 100644 index 303e8b1..0000000 --- a/lib/decisions/event-type-purge-frequency/Event_Type.php +++ /dev/null @@ -1,40 +0,0 @@ -purge_frequency; - } -} \ No newline at end of file diff --git a/lib/decisions/event-type-purge-frequency/Event_Type_Purge_Frequency.php b/lib/decisions/event-type-purge-frequency/Event_Type_Purge_Frequency.php deleted file mode 100644 index f1982b9..0000000 --- a/lib/decisions/event-type-purge-frequency/Event_Type_Purge_Frequency.php +++ /dev/null @@ -1,37 +0,0 @@ -add( 'event_type', 'Underpin_Logger\Decisions\Event_Type_Purge_Frequency\Event_Type' ); - } -} \ No newline at end of file diff --git a/lib/factories/Basic_Logger.php b/lib/factories/Basic_Logger.php index 54c95e8..93f3535 100644 --- a/lib/factories/Basic_Logger.php +++ b/lib/factories/Basic_Logger.php @@ -30,197 +30,24 @@ */ class Basic_Logger extends Writer { - /** - * Log directory. - * - * @since 1.0.0 - * - * @var string The log directory - */ - private $log_dir; - - public function __construct( Event_Type $event_type ) { - - // Construct the log dir - $upload_dir = wp_upload_dir( null, false ); - $this->log_dir = trailingslashit( trailingslashit( $upload_dir['basedir'] ) . 'underpin-event-logs/' ); - - // If the log directory does not exist, create it and set permissions. - if ( ! is_writeable( $this->log_dir ) ) { - @mkdir( $this->log_dir ); - @chmod( $this->log_dir, 0764 ); - } - - parent::__construct( $event_type ); - } - /** * @inheritDoc */ public function write( Log_Item $item ) { - $message = $item->format(); - $file = $this->file(); - - if ( ! is_wp_error( $file ) ) { - @file_put_contents( $file, "\n\n" . $message, FILE_APPEND ); - } + error_log( $item->format() ); } /** * @inheritDoc */ public function clear() { - $file = $this->file(); - - if ( is_wp_error( $file ) ) { - return $file; - } - - unlink( $file ); - - return true; - } - - - /** - * Gathers a list of log files. - * - * @since 1.0.0 - * - * @return array List of paths to log files. - */ - public function files() { - $files = glob( $this->log_dir . '*.log' ); - - if ( false === $files ) { - $files = array(); - } - - return $files; + // The basic writer simply uses PHP's error logger, so this isn't really do-able } /** * @inheritDoc */ public function purge( $max_file_age ) { - $files = $this->files(); - - // bail early if the max file age is less than zero. - if ( $max_file_age < 0 ) { - $error = new WP_Error( - 'invalid_max_age', - 'The provided max file age is less than zero. File age must be greater than zero.' - ); - - underpin()->logger()->log_wp_error( 'warning', $error ); - - return $error; - } - - $purged = []; - $oldest_date = date( 'U', strtotime( '-' . $max_file_age . ' days midnight' ) ); - - foreach ( $files as $file ) { - $file_info = $this->parse_file( $file ); - $file_date = date( 'U', strtotime( $file_info['date'] . ' midnight' ) ); - - if ( ! is_wp_error( $file_info ) && $file_date < $oldest_date ) { - $deleted = @unlink( $file_info['path'] ); - - if ( true === $deleted ) { - $purged[] = $file_info['path']; - } - } - } - - return $purged; - } - - - /** - * Attempt to retrieve log type and date from the provided file name. - * - * @since 1.0.0 - * - * @param string $path The path to the file, or the file name. - * @return array|WP_Error Parsed file, or \WP_Error object. - */ - public function parse_file( $path ) { - $file = basename( $path ); - $file_split = explode( '__', $file ); - $errors = new WP_Error(); - - if ( count( $file_split ) !== 2 ) { - $errors->add( - 'parse_file_malformed_file', - 'The file provided is malformed. The file must contain exactly one double __ between the type and date.', - compact( 'file', 'file_split' ) - ); - } - - if ( false === strpos( $file, '.log' ) ) { - $errors->add( - 'parse_file_type_is_not_log', - 'The provided file is not an error log file', - compact( 'file' ) - ); - } - - // Bail early if we have any errors. - if ( $errors->has_errors() ) { - underpin()->logger()->log( - 'warning', - 'errors_while_parsing_file', - 'A file failed to parse', - [ 'file' => $file, 'errors' => $errors ] - ); - - return $errors; - } - - // Remove file extension from date - $raw_date = str_replace( '.log', '', $file_split[1] ); - - // Set date - $date = date( 'M-d-Y', strtotime( $raw_date ) ); - - $path = $this->path( $date ); - - return compact( 'date', 'path' ); - } - - /** - * Gets the file name for the specified event type. - * This will automatically create the file if it does not exist. - * - * @since 1.0.0 - * - * @param string $date Optional. The log file date to retrieve. Default today. - * @return string|WP_Error - */ - public function file( $date = 'today' ) { - - $file = $this->path( $date ); - - if ( is_writeable( $this->log_dir ) && ! is_wp_error( $file ) && ! @file_exists( $file ) ) { - @fopen( $file, "w" ); - } - - return $file; - } - - /** - * Retrieves the path for the specified log type. - * - * @since 1.0.0 - * - * @param string $date Optional. The log file date to retrieve. Default today. - * @return string Path to the specified event log. - */ - public function path( $date = 'today' ) { - $date = strtolower( date( 'M-d-y', strtotime( $date ) ) ); - $type = str_replace( '__', '_', $this->event_type->type ); - - return $this->log_dir . $type . '__' . $date . '.log'; + // The basic writer simply uses PHP's error logger, so this isn't really do-able } } \ No newline at end of file diff --git a/lib/factories/Basic_Logger_Middleware.php b/lib/factories/Basic_Logger_Middleware.php new file mode 100644 index 0000000..e558790 --- /dev/null +++ b/lib/factories/Basic_Logger_Middleware.php @@ -0,0 +1,28 @@ +loader_item instanceof Event_Type ) { + $this->loader_item->writers->add( 'basic_logger', '\Underpin_Logger\Factories\Basic_Logger' ); + } else { + \Underpin\underpin()->logger()->log( + 'warning', + 'invalid_middleware_type', + 'Use_Basic_Logger was ran on middleware whose loader item is not an Event_Type instance', + [ 'loader_item' => get_class( $this->loader_item ), 'expects' => 'Event_Type' ] + ); + } + } + +} \ No newline at end of file diff --git a/lib/factories/Include_Backtrace_Middleware.php b/lib/factories/Include_Backtrace_Middleware.php new file mode 100644 index 0000000..e86b92b --- /dev/null +++ b/lib/factories/Include_Backtrace_Middleware.php @@ -0,0 +1,40 @@ +has_middleware( get_class( $this ) ) ) { + $data['backtrace'] = wp_debug_backtrace_summary( null, 3, false ); + } + return $data; + } + + public function do_actions() { + if ( $this->loader_item instanceof Event_Type ) { + if ( false === self::$filter_added ) { + add_filter( 'underpin/logger/additional_logged_data', [ $this, 'add_backtrace' ], 10, 4 ); + self::$filter_added = true; + } + } else { + \Underpin\underpin()->logger()->log( + 'warning', + 'invalid_middleware_type', + 'Include_Backtrace_Middleware was ran on middleware whose loader item is not an Event_Type instance', + [ 'loader_item' => get_class( $this->loader_item ), 'expects' => 'Event_Type' ] + ); + } + } + +} \ No newline at end of file diff --git a/lib/factories/Log_Item.php b/lib/factories/Log_Item.php index d5d173d..7c614b5 100644 --- a/lib/factories/Log_Item.php +++ b/lib/factories/Log_Item.php @@ -11,6 +11,7 @@ namespace Underpin_Logger\Factories; +use Underpin_Logger\Abstracts\Event_Type; use WP_Error; if ( ! defined( 'ABSPATH' ) ) { @@ -79,6 +80,16 @@ class Log_Item { */ public $type; + + /** + * Event type object. + * + * @since 1.0.0 + * + * @var Event_Type Event + */ + protected $event_type; + /** * Log Item Constructor. * @@ -90,10 +101,15 @@ class Log_Item { * @param array $data Arbitrary data associated with this event message. */ public function __construct( $type, $code, $message, $data = array() ) { - $this->type = $type; + if ( $type instanceof Event_Type ) { + $this->type = $type->type; + $this->event_type = $type; + } else { + $this->type = $type; + } $this->code = $code; $this->message = $message; - $this->data = $data; + $this->data = $data; if ( isset( $this->data['context'] ) ) { $this->context = $this->data['context']; @@ -115,21 +131,27 @@ public function __construct( $type, $code, $message, $data = array() ) { * @return string */ public function format() { + $additional_data = []; + + if ( ! empty( $this->ref ) ) { + $additional_data['ref'] = $this->ref; + $additional_data['context'] = $this->context; + } - $log_message = $this->code . ' - ' . $this->message; - - if(!empty($this->ref)) { - $data = array_merge( - [ - 'ref' => $this->ref, - 'context' => $this->context, - ], - $this->data - ); - } else{ - $data = $this->data; + if ( $this->event_type instanceof Event_Type ) { + $additional_data['group'] = $this->event_type->group; + $additional_data['volume'] = $this->event_type->volume; + $additional_data['psr_level'] = $this->event_type->psr_level; } + $log_message = 'Underpin ' . $this->type . ' event' . ': ' . $this->code . ' - ' . $this->message; + + + $data = array_merge( + $additional_data, + $this->data + ); + if ( ! empty( $data ) ) { $log_message .= "\n data:" . var_export( (object) $data, true ); } diff --git a/lib/factories/Writer_Instance.php b/lib/factories/Writer_Instance.php new file mode 100644 index 0000000..3d77151 --- /dev/null +++ b/lib/factories/Writer_Instance.php @@ -0,0 +1,57 @@ +set_values( $args ); + parent::__construct( $args['event_type'] ); + } + + public function write( Log_Item $item ) { + return $this->set_callable( $this->write_callback, $item ); + } + + public function clear() { + return $this->set_callable( $this->clear_callback ); + } + + public function purge( $max_file_age ) { + return $this->set_callable( $this->purge_callback, $max_file_age ); + } + +} \ No newline at end of file diff --git a/lib/loaders/Logger.php b/lib/loaders/Logger.php index e863f34..334e8a8 100644 --- a/lib/loaders/Logger.php +++ b/lib/loaders/Logger.php @@ -9,8 +9,10 @@ namespace Underpin_Logger\Loaders; +use Exception; +use Underpin\Abstracts\Registries\Loader_Registry; +use Underpin_Logger\Abstracts\Event_Type; use Underpin_Logger\Factories\Log_Item; -use Underpin_Logger\Abstracts\Registries\Event_Registry; use WP_Error; use function Underpin\underpin; @@ -25,93 +27,341 @@ * @since 1.0.0 * @package Underpin\Loaders */ -class Logger extends Event_Registry { +class Logger extends Loader_Registry { + + /** + * @inheritDoc + */ + protected $abstraction_class = 'Underpin_Logger\Abstracts\Event_Type'; + + protected $default_factory = 'Underpin_Logger\Factories\Event_Type_Instance'; + + /** + * Determines if the logger should log events, or not. Can be changed with mute, and unmute. + * + * @var bool + */ + protected static $is_muted = false; + + /** + * @inheritDoc + */ + public function add( $key, $value ) { + $valid = parent::add( $key, $value ); + + // If valid, set up actions. + if ( true === $valid ) { + $this->get( $key )->do_actions(); + } + + return $valid; + } + + /** + * Retrieves all events that have happened for this request. + * + * @since 1.0.0 + * + * @param bool $type The event type to retrieve. If false, this will get all events. + * + * @return array|WP_Error list of all events, or a WP_Error if something went wrong. + */ + public function get_request_events( $type = false ) { + if ( false !== $type ) { + $events = $this->get( $type ); + + // Return the error. + if ( is_wp_error( $events ) ) { + return $events; + } else { + return (array) $events; + } + + } else { + $result = []; + foreach ( $this as $type => $events ) { + $result[ $type ] = (array) $this->get( $type ); + } + + return $result; + } + } + + /** + * Enqueues an event to be logged in the system + * + * @since 1.0.0 + * + * @param string $type Event log type + * @param string $code The event code to use. + * @param string $message The message to log. + * @param array $data Arbitrary data associated with this event message. + * + * @return Log_Item|WP_Error Log item, with error message. WP_Error if something went wrong. + */ + public function log( $type, $code, $message, $data = array() ) { + if ( self::is_muted() ) { + return new WP_Error( + 'logger_is_muted', + 'Could not log event because this was ran in a muted action', + [ + 'type' => $type, + 'code' => $code, + 'message' => $message, + 'data' => $data, + ] + ); + } + + $event_type = $this->get( $type ); + + if ( is_wp_error( $event_type ) ) { + return $event_type; + } + + return $event_type->log( $code, $message, $data ); + } + + /** + * Mutes the logger. + * + * @since 1.0.0 + */ + private static function mute() { + self::$is_muted = true; + } + + /** + * Un-Mutes the logger. + * + * @since 1.0.0 + */ + private static function unmute() { + self::$is_muted = false; + } + + /** + * Does an action, muting all events that would otherwise happen. + * + * @since 1.2.4 + * + * @param callable $action The muted action to call. + * + * @return mixed|WP_Error The action returned result, or WP_Error if something went wrong. + */ + public static function do_muted_action( $action ) { + if ( is_callable( $action ) ) { + self::mute(); + $result = $action(); + self::unmute(); + } else { + $result = new WP_Error( + 'muted_action_not_callable', + 'The provided muted action is not callable', + [ 'ref' => $action, 'type' => 'function' ] + ); + } + + return $result; + } + + /** + * Fetches the mute status of the logger. + * + * @since 1.0.0 + */ + public static function is_muted() { + return self::$is_muted; + } + + /** + * Enqueues an event to be logged in the system, and then returns a WP_Error object. + * + * @since 1.0.0 + * + * @param string $type Event log type + * @param string $code The event code to use. + * @param string $message The message to log. + * @param array $data Arbitrary data associated with this event message. + * + * @return WP_Error Log item, with error message. WP_Error if something went wrong. + */ + public function log_as_error( $type, $code, $message, $data = array() ) { + $item = $this->log( $type, $code, $message, $data ); + + if ( ! is_wp_error( $item ) ) { + $item = $item->error(); + } + + return $item; + } + + /** + * Logs an error using a WP Error object. + * + * @since 1.0.0 + * + * @param string $type Error log type + * @param WP_Error $wp_error Instance of WP_Error to use for log + * @param array $data Additional data to log + * + * @return Log_Item|WP_Error The logged item, if successful, otherwise WP_Error. + */ + public function log_wp_error( $type, WP_Error $wp_error, $data = [] ) { + $item = $this->get( $type ); + + if ( is_wp_error( $item ) ) { + return $item; + } + + $error = $item->log_wp_error( $wp_error, $data ); + + return $error; + } + + /** + * Logs an error from within an exception. + * + * @since 1.0.0 + * + * @param string $type Error log type + * @param Exception $exception Exception instance to log. + * @param array $data array Data associated with this error message + * + * @return Log_Item|WP_Error Log Item, with error message if successful, otherwise WP_Error. + */ + public function log_exception( $type, Exception $exception, $data = array() ) { + $item = $this->get( $type ); + + if ( is_wp_error( $type ) ) { + return $item; + } + + $error = $item->log_exception( $exception, $data ); + + return $error; + } + + /** + * @param string $key + * + * @return Event_Type|WP_Error Event type, if it exists. WP_Error, otherwise. + */ + public function get( $key ) { + $result = parent::get( $key ); + + // If the logged event could not be found, search event type keys. + if ( is_wp_error( $result ) ) { + $event_type = $this->find( [ + 'type' => $key, + ] ); + + // If that also comes up empty, return the error. + if ( is_wp_error( $event_type ) ) { + return $result; + } + + // Return the discovered event. + $result = $event_type; + } + + return $result; + } + + /** + * Purge old logged events. + * + * @since 1.0.0 + * + */ + public function cleanup() { + foreach ( $this as $key => $class ) { + $writer = $this->get( $key )->writer(); + + if ( ! is_wp_error( $writer ) ) { + $purged = $writer->cleanup(); + + if ( is_wp_error( $purged ) ) { + $this->log_wp_error( 'error', $purged ); + } + } + } + } /** * @inheritDoc */ protected function set_default_items() { - $this->add( 'emergency', [ - 'type' => 'emergency', - 'write_to_log' => true, - 'group' => 'core', - 'description' => 'Intended to be used only for the most-severe events.', - 'name' => "Emergency", - 'psr_level' => 'emergency', - 'include_backtrace' => true, - 'purge_frequency' => 7, - ] ); - - $this->add( 'alert', [ - 'type' => 'alert', - 'write_to_log' => true, - 'group' => 'core', - 'description' => 'Intended to be used when someone should be notified about this problem.', - 'name' => "Alert", - 'psr_level' => 'alert', - 'include_backtrace' => true, - 'purge_frequency' => 7, - ] ); - - $this->add( 'critical', [ - 'type' => 'critical', - 'write_to_log' => true, - 'group' => 'core', - 'description' => 'Intended to log events when an error occurs that is potentially damaging.', - 'name' => "Critical Error", - 'psr_level' => 'critical', - 'include_backtrace' => true, - 'purge_frequency' => 7, - ] ); - - $this->add( 'error', [ - 'type' => 'error', - 'write_to_log' => true, - 'group' => 'core', - 'description' => 'Intended to log events when something goes wrong.', - 'name' => "Error", - 'psr_level' => 'error', - 'include_backtrace' => true, - 'purge_frequency' => 7, - ] ); + $defaults = [ + 'group' => 'core', + 'purge_frequency' => 7, + 'middlewares' => [ + 'Underpin_Logger\Factories\Basic_Logger_Middleware', + 'Underpin_Logger\Factories\Include_Backtrace_Middleware', + ], + ]; + + $this->add( 'emergency', array_merge( $defaults, [ + 'type' => 'emergency', + 'description' => 'Intended to be used only for the most-severe events.', + 'name' => "Emergency", + 'psr_level' => 'emergency', + ] ) ); + + $this->add( 'alert', array_merge( $defaults, [ + 'type' => 'alert', + 'description' => 'Intended to be used when someone should be notified about this problem.', + 'name' => "Alert", + 'psr_level' => 'alert', + ] ) ); + + $this->add( 'critical', array_merge( $defaults, [ + 'type' => 'critical', + 'description' => 'Intended to log events when an error occurs that is potentially damaging.', + 'name' => "Critical Error", + 'psr_level' => 'critical', + ] ) ); + + $this->add( 'error', array_merge( $defaults, [ + 'type' => 'error', + 'description' => 'Intended to log events when something goes wrong.', + 'name' => "Error", + 'psr_level' => 'error', + ] ) ); if ( underpin()->is_debug_mode_enabled() ) { - $this->add( 'warning', [ - 'type' => 'warning', - 'write_to_log' => false, - 'description' => 'Intended to log events when something seems wrong.', - 'name' => 'Warning', - 'psr_level' => 'warning', - 'group' => 'core', - ] ); + $this->add( 'warning', array_merge( [ + 'type' => 'warning', + 'description' => 'Intended to log events when something seems wrong.', + 'name' => 'Warning', + 'psr_level' => 'warning', + 'middlewares' => [], + ] ) ); - $this->add( 'notice', [ - 'type' => 'notice', - 'write_to_log' => false, - 'description' => 'Posts informative notices when something is neither good nor bad.', - 'name' => 'Notice', - 'group' => 'core', - 'psr_level' => 'notice', - ] ); + $this->add( 'notice', array_merge( [ + 'type' => 'notice', + 'description' => 'Posts informative notices when something is neither good nor bad.', + 'name' => 'Notice', + 'psr_level' => 'notice', + 'middlewares' => [], + ] ) ); - $this->add( 'info', [ - 'type' => 'info', - 'write_to_log' => false, - 'description' => 'Posts informative messages that something is most-likely going as-expected.', - 'name' => 'Info', - 'group' => 'core', - 'psr_level' => 'info', - ] ); + $this->add( 'info', array_merge( [ + 'type' => 'info', + 'description' => 'Posts informative messages that something is most-likely going as-expected.', + 'name' => 'Info', + 'psr_level' => 'info', + 'middlewares' => [], + ] ) ); - $this->add( 'debug', [ - 'type' => 'debug', - 'write_to_log' => false, - 'description' => 'A place to put information that is only useful in debugging context.', - 'name' => 'Debug', - 'group' => 'core', - 'psr_level' => 'debug', - ] ); + $this->add( 'debug', array_merge( [ + 'type' => 'debug', + 'description' => 'A place to put information that is only useful in debugging context.', + 'name' => 'Debug', + 'psr_level' => 'debug', + 'middlewares' => [], + ] ) ); } } @@ -179,4 +429,12 @@ public function capabilities() { return array_unique( $capabilities ); } + public function __get( $key ) { + if ( isset( $this->$key ) ) { + return $this->$key; + } else { + return new \WP_Error( 'logger_loader_param_not_set', 'The logger loader key ' . $key . ' could not be found.' ); + } + } + } \ No newline at end of file diff --git a/lib/traits/With_Meta.php b/lib/traits/With_Meta.php deleted file mode 100644 index 27e45b6..0000000 --- a/lib/traits/With_Meta.php +++ /dev/null @@ -1,170 +0,0 @@ -get_meta_table(); - - if ( is_wp_error( $table ) ) { - return $table; - } - - return str_replace( 'meta', '', $table->name ); - } - - /** - * Adds metadata to the meta table. See add_metadata - * - * @param int $object_id ID of the object metadata is for. - * @param string $meta_key Metadata key. - * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. - * @param bool $unique Optional. Whether the specified metadata key should be unique for the object. - * If true, and the object already has a value for the specified metadata key, - * no change will be made. Default false. - * @return int|WP_Error The meta ID on success, false on failure. - */ - public function add_meta( $object_id, $key, $value, $unique = false ) { - - $name = $this->get_meta_friendly_name(); - - if ( is_wp_error( $name ) ) { - return $name; - } - - if ( method_exists( $this, 'sanitize_item' ) ) { - $value = $this->sanitize_item( $key, $value ); - } - - $added = add_metadata( $name, $object_id, $key, $value, $unique ); - - if ( false === $added ) { - return new WP_Error( - 'add_meta_failed', - 'Add metadata failed to save meta.' - ); - } - - return $added; - } - - /** - * Updates metadata for the meta table. - * - * @param int $object_id ID of the object metadata is for. - * @param string $meta_key Metadata key. - * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. - * @param mixed $prev_value Optional. If specified, only update existing metadata entries - * with this value. Otherwise, update all entries. - * @return int|WP_Error The new meta field ID if a field with the given key didn't exist and was - * therefore added, true on successful update, false on failure. - */ - public function update_meta( $object_id, $meta_key, $meta_value, $prev_value = '' ) { - - $name = $this->get_meta_friendly_name(); - - if ( is_wp_error( $name ) ) { - return $name; - } - - $updated = update_metadata( $name, $object_id, $meta_key, $meta_value, $prev_value ); - - if ( false === $updated ) { - return new WP_Error( - 'update_meta_failed', - 'Update metadata failed to save meta.' - ); - } - - return $updated; - } - - /** - * Updates metadata for the meta table. - * - * @param int $object_id ID of the object metadata is for. - * @param string $meta_key Metadata key. - * @param mixed $meta_value Optional. Metadata value. Must be serializable if non-scalar. - * If specified, only delete metadata entries with this value. - * Otherwise, delete all entries with the specified meta_key. - * Pass `null`, `false`, or an empty string to skip this check. - * (For backward compatibility, it is not possible to pass an empty string - * to delete those entries with an empty string for a value.) - * @param bool $delete_all Optional. If true, delete matching metadata entries for all objects, - * ignoring the specified object_id. Otherwise, only delete - * matching metadata entries for the specified object_id. Default false. - * @return true|WP_Error True on successful delete, false on failure. - */ - public function delete_meta( $object_id, $meta_key, $meta_value = '', $delete_all = false ) { - - $name = $this->get_meta_friendly_name(); - - if ( is_wp_error( $name ) ) { - return $name; - } - - $deleted = delete_metadata( $name, $object_id, $meta_key, $meta_value, $delete_all ); - - if ( false === $deleted ) { - return new WP_Error( - 'delete_meta_failed', - 'Delete metadata failed to delete meta.' - ); - } - - return $deleted; - } - - /** - * Retrieves metadata for the specified object. - * - * @since 1.1.0 - * - * @param int $object_id ID of the object metadata is for. - * @param string $meta_key Optional. Metadata key. If not specified, retrieve all metadata for - * the specified object. Default empty. - * @param bool $single Optional. If true, return only the first value of the specified meta_key. - * This parameter has no effect if meta_key is not specified. Default false. - * @return mixed|WP_Error Single metadata value, or array of values, or error if something went wrong. - */ - public function get_meta( $object_id, $meta_key = '', $single = false ) { - - $name = $this->get_meta_friendly_name(); - - if ( is_wp_error( $name ) ) { - return $name; - } - - return get_metadata( $name, $object_id, $meta_key, $single ); - } -} \ No newline at end of file diff --git a/logger.php b/logger.php index 0dd5f3d..d1bb448 100644 --- a/logger.php +++ b/logger.php @@ -8,25 +8,19 @@ // Add this loader. add_action( 'underpin/before_setup', function ( $file, $class ) { - require_once( plugin_dir_path( __FILE__ ) . 'lib/abstracts/registries/Event_Registry.php' ); - require_once( plugin_dir_path( __FILE__ ) . 'lib/loaders/Logger.php' ); - require_once( plugin_dir_path( __FILE__ ) . 'lib/abstracts/Event_Type.php' ); - require_once( plugin_dir_path( __FILE__ ) . 'lib/factories/Event_Type_Instance.php' ); - require_once( plugin_dir_path( __FILE__ ) . 'lib/cron-jobs/Purge_Logs.php' ); - require_once( plugin_dir_path( __FILE__ ) . 'lib/abstracts/Writer.php' ); - require_once( plugin_dir_path( __FILE__ ) . 'lib/factories/Basic_Logger.php' ); - require_once( plugin_dir_path( __FILE__ ) . 'lib/factories/Log_Item.php' ); - require_once( plugin_dir_path( __FILE__ ) . 'lib/decisions/event-type-purge-frequency/Event_Type.php' ); - require_once( plugin_dir_path( __FILE__ ) . 'lib/decisions/event-type-purge-frequency/Event_Type_Purge_Frequency.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'lib/loaders/Logger.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'lib/abstracts/Event_Type.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'lib/factories/Event_Type_Instance.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'lib/factories/Basic_Logger_Middleware.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'lib/factories/Include_Backtrace_Middleware.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'lib/abstracts/Writer.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'lib/factories/Basic_Logger.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'lib/factories/Log_Item.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'lib/factories/Writer_Instance.php' ); - // Add the logger. - Underpin\underpin()->get( $file, $class )->loaders()->add( 'logger', [ - 'registry' => 'Underpin_Logger\Loaders\Logger', - ] ); + // Add the logger. + Underpin\underpin()->get( $file, $class )->loaders()->add( 'logger', [ + 'registry' => 'Underpin_Logger\Loaders\Logger', + ] ); - // Setup Cron jobs - Underpin\underpin()->get( $file, $class )->cron_jobs()->add( 'purge_logs', 'Underpin_Logger\Cron_Jobs\Purge_Logs' ); - - // Setup Decision Lists - Underpin\underpin()->get( $file, $class )->decision_lists()->add( 'event_type_purge_frequency', 'Underpin_Logger\Decisions\Event_Type_Purge_Frequency\Event_Type_Purge_Frequency' ); }, 5, 2 ); \ No newline at end of file