Skip to content
Snippets Groups Projects
Commit 87e00919 authored by Dyszczo's avatar Dyszczo
Browse files

Merge branch 'devel' into 'master'

basic idea

See merge request !1
parents e394a783 316945cb
No related branches found
No related tags found
1 merge request!1basic idea
Pipeline #5830 passed
......@@ -8,7 +8,8 @@
],
"require": {
"php": ">=5.6",
"psr/log": "^1.0.1"
"psr/log": "^1.0.1",
"monolog/monolog": "^1.23"
},
"require-dev": {
"phpunit/phpunit": "<7",
......
<?php
namespace WPDesk\Logger;
use Monolog\Handler\HandlerInterface;
use Monolog\Logger;
use Monolog\Registry;
/**
* Manages and facilitates creation of logger
*
* @package WPDesk\Logger
*/
class BasicLoggerFactory implements LoggerFactory
{
/** @var string Last created logger name/channel */
private static $lastLoggerChannel;
/**
* Creates logger for plugin
*
* @param string $channel The logging channel
* @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc.
* @param callable[] $processors Optional array of processors
* @return Logger
*/
public function createLogger($channel, $handlers = array(), array $processors = array())
{
if (Registry::hasLogger($channel)) {
return Registry::getInstance($channel);
}
self::$lastLoggerChannel = $channel;
$logger = new Logger($channel, $handlers, $processors);
Registry::addLogger($logger);
return $logger;
}
/**
* Returns created Logger
*
* @return Logger
*/
public function getLogger()
{
return Registry::getInstance(self::$lastLoggerChannel);
}
}
<?php
namespace WPDesk\Logger;
use Monolog\Logger;
use Psr\Log\LogLevel;
use WP_Error;
use Exception;
/**
* Facilitates creation of logger with default WPDesk settings
*
* @package WPDesk\Logger
*/
class LoggerFacade
{
const BACKTRACE_FILENAME_KEY = 'file';
/** @var LoggerFactory */
private static $factory;
/**
* @return Logger
*/
public static function getLogger()
{
if (self::$factory === null) {
self::$factory = new WPDeskLoggerFactory();
}
return self::$factory->createWPDeskLogger();
}
/**
* Snake case alias for getLogger
*
* @return Logger
*/
public static function get_logger()
{
return self::getLogger();
}
/**
* Log this exception into WPDesk logger
*
* @param WP_Error $e Error to log.
* @param array $backtrace Backtrace information with snapshot of error env.
* @param string $level Level of error.
*
* @see http://php.net/manual/en/function.debug-backtrace.php
*/
public static function log_wp_error(WP_Error $e, array $backtrace, $level = LogLevel::ERROR)
{
$message = 'Error: ' . get_class($e) . ' Code: ' . $e->get_error_code() . ' Message: ' . $e->get_error_message();
self::log_message_backtrace($message, $backtrace, $level);
}
/**
* Log this exception into WPDesk logger
*
* @param Exception $e Exception to log.
* @param string $level Level of error.
*/
public static function log_exception(Exception $e, $level = LogLevel::ERROR)
{
$message = 'Exception: ' . get_class($e) . ' Code: ' . $e->getCode() . ' Message: ' . $e->getMessage() . ' Stack: ' . $e->getTraceAsString();
self::log_message($message, ['exception' => $e], $e->getFile(), $level);
}
/**
* Log message into WPDesk logger
*
* @param string $message Message to log.
* @param array $context Context to log
* @param string $source Source of the message - can be file name, class name or whatever.
* @param string $level Level of error.
*/
public static function log_message(
$message,
array $context = array(),
$source = 'unknown',
$level = LogLevel::DEBUG
) {
$logger = self::getLogger();
$logger->log($level, $message, array_merge($context, ['source' => $source]));
}
/**
* Log message into WPDesk logger
*
* @param string $message Message to log.
* @param array $backtrace Backtrace information with snapshot of error env.
* @param string $level Level of error.
*/
public static function log_message_backtrace($message, array $backtrace, $level = LogLevel::DEBUG)
{
$message .= ' Backtrace: ' . json_encode($backtrace);
self::log_message($message, $backtrace[self::BACKTRACE_FILENAME_KEY], $level);
}
}
<?php
namespace WPDesk\Logger;
use Monolog\Logger;
/*
* @package WPDesk\Logger
*/
interface LoggerFactory
{
/**
* Returns created Logger
*
* @return Logger
*/
public function getLogger();
}
<?php
namespace WPDesk\Logger\WP\Exception;
class WCLoggerAlreadyCaptured extends \RuntimeException
{
}
\ No newline at end of file
<?php
namespace WPDesk\Logger\WP;
use Monolog\Logger;
use WPDesk\Logger\WP\Exception\WCLoggerAlreadyCaptured;
/**
* Can capture default WooCommerce logger
*
* @package WPDesk\Logger
*/
class WooCommerceCapture
{
const WOOCOMMERCE_LOGGER_FILTER = 'woocommerce_logging_class';
const WOOCOMMERCE_AFTER_IS_LOADED_ACTION = 'woocommerce_loaded';
/**
* Is logger filter captured by library.
*
* @var bool
*/
private static $WCLoggerCaptured = false;
/**
* Our monolog
*
* @var Logger
*/
private $monolog;
/**
* Original WC Logger
*
* @var \WC_Logger_Interface
*/
private $originalWCLogger;
/**
* WordPress hook function to return our logger
*
* @var ?callable
*/
private $captureHookFunction;
/**
* WordPress hook function to return original wc logger
*
* @var ?callable
*/
private $freeHookFunction;
public function __construct(Logger $monolog)
{
$this->monolog = $monolog;
}
/**
* Prepares callable property captureHookFunction.
* For it to work WC have to be loaded
*/
private function prepareCaptureHookCallable()
{
$monolog = $this->monolog;
if ($this->captureHookFunction === null) {
$this->captureHookFunction = function () use ($monolog) {
return new WooCommerceMonologPlugin($monolog);
};
$this->monolog->pushHandler(new WooCommerceHandler($this->originalWCLogger));
}
}
/**
* Capture WooCommerce logger and inject our decorated Logger
*/
public function captureWcLogger()
{
if (self::$WCLoggerCaptured) {
throw new WCLoggerAlreadyCaptured('Try to free wc logger first.');
}
if ($this->isWooCommerceLoggerAvailable()) {
$this->prepareFreeHookCallable();
$this->prepareCaptureHookCallable();
remove_filter(self::WOOCOMMERCE_LOGGER_FILTER, $this->freeHookFunction);
add_filter(self::WOOCOMMERCE_LOGGER_FILTER, $this->captureHookFunction);
self::$WCLoggerCaptured = true;
} elseif (function_exists('add_action')) {
add_action(self::WOOCOMMERCE_AFTER_IS_LOADED_ACTION, [$this, 'captureWcLogger']);
} else {
$this->monolog->alert('Cannot capture WC - WordPress is not available.');
}
}
/**
* Can i fetch WC Logger?
*
* @return bool
*/
private function isWooCommerceLoggerAvailable()
{
return function_exists('wc_get_logger');
}
/**
* Prepares callable property freeHookFunction.
* For it to work WC have to be loaded
*/
private function prepareFreeHookCallable()
{
if ($this->freeHookFunction === null) {
$this->originalWCLogger = $logger = wc_get_logger();
$this->freeHookFunction = function () use ($logger) {
return $logger;
};
}
}
/**
* Remove WooCommerce logger injection
*/
public function freeWcLogger()
{
if (self::$WCLoggerCaptured) {
remove_filter(self::WOOCOMMERCE_LOGGER_FILTER, $this->captureHookFunction);
add_filter(self::WOOCOMMERCE_LOGGER_FILTER, $this->freeHookFunction);
self::$WCLoggerCaptured = false;
}
}
}
<?php
namespace WPDesk\Logger\WP;
use Monolog\Handler\AbstractProcessingHandler;
/**
* Class WooCommerceFactory
*/
class WooCommerceHandler extends AbstractProcessingHandler {
const DEFAULT_WC_SOURCE = 'wpdesk-logger';
/** @var \WC_Logger_Interface */
private $wc_logger;
/**
* Writes the record down to the log of the implementing handler
*
* @param array $record
* @return void
*/
protected function write(array $record)
{
$context = array_merge([
'source' => self::DEFAULT_WC_SOURCE
], $record['extra'], $record['context']);
$this->wc_logger->log($record['level'], $record['message'], $context);
}
public function __construct(\WC_Logger_Interface $originalWcLogger) {
parent::__construct();
$this->wc_logger = $originalWcLogger;
}
}
<?php
namespace WPDesk\Logger\WP;
use Monolog\Logger;
use Psr\Log\LogLevel;
use WC_Log_Levels;
/**
* Can decorate monolog with WC_Logger_Interface
*
* @package WPDesk\Logger
*/
class WooCommerceMonologPlugin implements \WC_Logger_Interface {
/** @var Logger */
private $monolog;
public function __construct( Logger $monolog ) {
$this->monolog = $monolog;
}
/**
* Method for compatibility reason. Do not use.
*
* @param string $handle
* @param string $message
* @param string $level
* @return bool|void
*
* @deprecated
*/
public function add( $handle, $message, $level = WC_Log_Levels::NOTICE ) {
$this->log($message, $level);
}
/**
* System is unusable.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function emergency($message, $context = array())
{
$this->log(LogLevel::EMERGENCY, $message, $context);
}
public function log( $level, $message, $context = [] ) {
$this->monolog->log($level, $message, $context);
}
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function alert($message, $context = array())
{
$this->log(LogLevel::ALERT, $message, $context);
}
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function critical($message, $context = array())
{
$this->log(LogLevel::CRITICAL, $message, $context);
}
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function error($message, $context = array())
{
$this->log(LogLevel::ERROR, $message, $context);
}
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function warning($message, $context = array())
{
$this->log(LogLevel::WARNING, $message, $context);
}
/**
* Normal but significant events.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function notice($message, $context = array())
{
$this->log(LogLevel::NOTICE, $message, $context);
}
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function info($message, $context = array())
{
$this->log(LogLevel::INFO, $message, $context);
}
/**
* Detailed debug information.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function debug($message, $context = array())
{
$this->log(LogLevel::DEBUG, $message, $context);
}
}
<?php
namespace WPDesk\Logger;
use Monolog\Logger;
use Monolog\Registry;
use Monolog\ErrorHandler;
use Monolog\Handler\StreamHandler;
use Psr\Log\LogLevel;
use WPDesk\Logger\WP\WooCommerceCapture;
/**
* Manages and facilitates creation of logger
*
* @package WPDesk\Logger
*/
class WPDeskLoggerFactory extends BasicLoggerFactory
{
const WPDESK_LOGGER_CHANNEL_NAME = 'wpdesk';
/** @var string Log to file when level is */
const LEVEL_WPDESK_FILE = LogLevel::DEBUG;
/** @var string Log to wc logger when level is */
const LEVEL_WC = LogLevel::ERROR;
/** @var bool */
private static $isWpdeskLogWorking = false;
/**
* Creates default WPDesk logger.
*
* Requirements:
* - get_option, add/remove_action, add/remove filter and WP_CONTENT_DIR should be available for logger.
*
* Assumptions:
* - logger is actively working when 'wpdesk_helper_options' has 'debug_log' set to '1';
* - fatal errors, exception and standard errors are recorded but in a transparent way;
* - WooCommerce logger is captured and returns this logger;
* - logs are still correctly written to WooCommerce subsystem in a transparent way;
* - all recorded errors are written to WPDesk file.
*
* @return Logger
*/
public function createWPDeskLogger()
{
if (Registry::hasLogger(self::WPDESK_LOGGER_CHANNEL_NAME)) {
return Registry::getInstance(self::WPDESK_LOGGER_CHANNEL_NAME);
}
$logger = $this->createLogger(self::WPDESK_LOGGER_CHANNEL_NAME);
if ($this->shouldInitializeLoggerHandles()) {
$this->captureWooCommerce($logger);
$this->captureWordPressHandle($logger);
try {
$this->appendWPDeskHandle($logger);
$logger->debug('WPDesk handle is active');
self::$isWpdeskLogWorking = true;
} catch (\InvalidArgumentException $e) {
$logger->emergency('WPDesk log could not be created - invalid filename.');
} catch (\Exception $e) {
$logger->emergency('WPDesk log could not be written.');
}
}
return $logger;
}
/**
* is WPDesk file log is working(writable, exists, connected).
*
* @return bool
*/
public function isWPDeskLogWorking()
{
return self::$isWpdeskLogWorking;
}
/**
* Returns WPDesk filename with path.
*
* @return string
*/
public function getWPDeskFileName()
{
return WP_CONTENT_DIR . '/uploads/wpdesk-logs/wpdesk_debug.log';
}
/**
* According to WPDesk - use logger only when debug mode is enabled.
*
* @return bool
*/
private function shouldInitializeLoggerHandles()
{
if (function_exists('get_option')) {
$options = get_option('wpdesk_helper_options');
return is_array($options) && isset($options['debug_log']) && $options['debug_log'] === '1';
}
return false;
}
/**
* Capture WooCommerce and add handle
*
* @param Logger $logger
*/
private function captureWooCommerce(Logger $logger)
{
if (!defined('WC_LOG_THRESHOLD')) {
define('WC_LOG_THRESHOLD', self::LEVEL_WC);
}
$wcIntegration = new WooCommerceCapture($logger);
$wcIntegration->captureWcLogger();
}
/**
* Add WordPress(standard PHP) errors handle
*
* @param Logger $logger
*/
private function captureWordPressHandle(Logger $logger)
{
$errorHandler = new ErrorHandler($logger);
$errorHandler->registerErrorHandler();
$errorHandler->registerFatalHandler();
$errorHandler->registerExceptionHandler();
}
/**
* Add WPDesk log file handle
*
* @param Logger $logger
*
* @throws \Exception If a missing directory is not buildable
* @throws \InvalidArgumentException If stream is not a resource or string
*/
private function appendWPDeskHandle(Logger $logger)
{
$filename = $this->getWPDeskFileName();
$logger->pushHandler(new StreamHandler($filename, self::LEVEL_WPDESK_FILE));
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment