...
 
Commits (2)
<?php
/**
* Logger
*
* @author edgebal
*/
namespace Minds\Core\Log;
use Exception;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\ChromePHPHandler;
use Monolog\Handler\ErrorLogHandler;
use Monolog\Handler\FirePHPHandler;
use Monolog\Handler\PHPConsoleHandler;
use Monolog\Logger as MonologLogger;
use Sentry\Monolog\Handler as SentryHandler;
use Sentry\SentrySdk;
/**
* A PSR-3 logger tailored for Minds. Based off Monolog.
*
* @package Minds\Core\Log
* @see \Monolog\Logger
* @see \Psr\Log\LoggerInterface
*/
class Logger extends MonologLogger
{
/**
* Logger constructor.
* @param string $channel
* @param array $options
*/
public function __construct(string $channel = 'Minds', array $options = [])
{
$options = array_merge([
'isProduction' => true,
'devToolsLogger' => '',
], $options);
$handlers = [];
$errorLogHandler = new ErrorLogHandler(
ErrorLogHandler::OPERATING_SYSTEM,
$options['isProduction'] ? MonologLogger::INFO : MonologLogger::DEBUG,
true,
true
);
$errorLogHandler
->setFormatter(new LineFormatter(
"%channel%.%level_name%: %message% %context% %extra%\n",
'c',
!$options['isProduction'], // Allow newlines on dev mode
true
));
$handlers[] = $errorLogHandler;
if ($options['isProduction']) {
$handlers[] = new SentryHandler(SentrySdk::getCurrentHub());
} else {
// Extra handlers for Development Mode
switch ($options['devToolsLogger']) {
case 'firephp':
$handlers[] = new FirePHPHandler();
break;
case 'chromelogger':
$handlers[] = new ChromePHPHandler();
break;
case 'phpconsole':
try {
$handlers[] = new PHPConsoleHandler();
} catch (Exception $exception) {
// If the server-side vendor package is not installed, ignore any warnings.
}
break;
}
}
// Create Monolog instance
parent::__construct($channel, $handlers);
}
}
<?php
/**
* Module
*
* @author edgebal
*/
namespace Minds\Core\Log;
use Minds\Interfaces\ModuleInterface;
/**
* Module class
*
* @package Minds\Core\Log
*/
class Module implements ModuleInterface
{
/**
* @inheritDoc
*/
public function onInit(): void
{
(new Provider())->register();
}
}
<?php
/**
* Provider
*
* @author edgebal
*/
namespace Minds\Core\Log;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Minds\Core\Di\Provider as DiProvider;
/**
* DI Provider bindings
*
* @package Minds\Core\Log
*/
class Provider extends DiProvider
{
public function register()
{
$this->di->bind('Logger', function ($di) {
/** @var Di $di */
/** @var Config|false $config */
$config = $di->get('Config');
$options = [
'isProduction' => $config ? !$config->get('development_mode') : true,
'devToolsLogger' => $config ? $config->get('devtools_logger') : '',
];
return new Logger('Minds', $options);
}, [ 'useFactory' => false ]);
$this->di->bind('Logger\Singleton', function ($di) {
/** @var Di $di */
return $di->get('Logger');
}, [ 'useFactory' => true ]);
}
}
......@@ -16,6 +16,7 @@ class Minds extends base
public static $booted = false;
private $modules = [
Log\Module::class,
Events\Module::class,
SSO\Module::class,
Email\Module::class,
......
......@@ -7,6 +7,8 @@
namespace Minds\Core\Router\Middleware\Kernel;
use Exception;
use Minds\Core\Di\Di;
use Minds\Core\Log\Logger;
use Minds\Core\Router\Exceptions\ForbiddenException;
use Minds\Core\Router\Exceptions\UnauthorizedException;
use Psr\Http\Message\ResponseInterface;
......@@ -15,21 +17,20 @@ use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\JsonResponse;
use function Sentry\captureException;
class ErrorHandlerMiddleware implements MiddlewareInterface
{
/** @var bool */
protected $sentryEnabled = true;
/** @var Logger */
protected $logger;
/**
* @param bool $sentryEnabled
* @return ErrorHandlerMiddleware
* ErrorHandlerMiddleware constructor.
* @param Logger $logger
*/
public function setSentryEnabled(bool $sentryEnabled): ErrorHandlerMiddleware
{
$this->sentryEnabled = $sentryEnabled;
return $this;
public function __construct(
$logger = null
) {
$this->logger = $logger ?: Di::_()->get('Logger');
}
/**
......@@ -58,15 +59,7 @@ class ErrorHandlerMiddleware implements MiddlewareInterface
$status = 403;
} catch (Exception $e) {
// Log
// TODO: Monolog
error_log((string) $e);
// Sentry
if ($this->sentryEnabled) {
captureException($e);
}
$this->logger->critical($e, ['exception' => $e]);
}
switch ($request->getAttribute('accept')) {
......
<?php
/**
* Log
*
* @author edgebal
*/
namespace Minds\Helpers;
use Minds\Core\Di\Di;
use Psr\Log\InvalidArgumentException;
/**
* Static helper class for easy access to Logger singleton
* logging methods.
*
* @package Minds\Helpers
* @see \Minds\Core\Log\Logger
*/
class Log
{
/**
* System is unusable.
*
* @param string $message
* @param array $context
*
* @return void
*/
public static function emergency($message, array $context = [])
{
Di::_()->get('Logger\Singleton')->emergency($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 static function alert($message, array $context = [])
{
Di::_()->get('Logger\Singleton')->alert($message, $context);
}
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
*
* @return void
*/
public static function critical($message, array $context = [])
{
Di::_()->get('Logger\Singleton')->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 static function error($message, array $context = [])
{
Di::_()->get('Logger\Singleton')->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 static function warning($message, array $context = [])
{
Di::_()->get('Logger\Singleton')->warning($message, $context);
}
/**
* Normal but significant events.
*
* @param string $message
* @param array $context
*
* @return void
*/
public static function notice($message, array $context = [])
{
Di::_()->get('Logger\Singleton')->notice($message, $context);
}
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
*
* @return void
*/
public static function info($message, array $context = [])
{
Di::_()->get('Logger\Singleton')->info($message, $context);
}
/**
* Detailed debug information.
*
* @param string $message
* @param array $context
*
* @return void
*/
public static function debug($message, array $context = [])
{
Di::_()->get('Logger\Singleton')->debug($message, $context);
}
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
*
* @return void
*
* @throws InvalidArgumentException
*/
public static function log($level, $message, array $context = [])
{
Di::_()->get('Logger\Singleton')->log($level, $message, $context);
}
}
......@@ -9,11 +9,23 @@ use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\JsonResponse;
class ErrorHandlerMiddlewareSpec extends ObjectBehavior
{
/** @var LoggerInterface */
protected $logger;
public function let(
LoggerInterface $logger
) {
$this->logger = $logger;
$this->beConstructedWith($logger);
}
public function it_is_initializable()
{
$this->shouldHaveType(ErrorHandlerMiddleware::class);
......@@ -29,7 +41,6 @@ class ErrorHandlerMiddlewareSpec extends ObjectBehavior
->willReturn($response);
$this
->setSentryEnabled(false)
->process($request, $handler)
->shouldReturn($response);
}
......@@ -39,16 +50,21 @@ class ErrorHandlerMiddlewareSpec extends ObjectBehavior
RequestHandlerInterface $handler,
ResponseInterface $response
) {
$exception = new Exception('PHPSpec');
$handler->handle($request)
->shouldBeCalled()
->willThrow(new Exception('PHPSpec'));
->willThrow($exception);
$this->logger->critical($exception, ['exception' => $exception])
->shouldBeCalled()
->willReturn(null);
$request->getAttribute('accept')
->shouldBeCalled()
->willReturn('html');
$this
->setSentryEnabled(false)
->process($request, $handler)
->shouldBeAnInstanceOf(HtmlResponse::class);
}
......@@ -58,16 +74,21 @@ class ErrorHandlerMiddlewareSpec extends ObjectBehavior
RequestHandlerInterface $handler,
ResponseInterface $response
) {
$exception = new Exception('PHPSpec');
$handler->handle($request)
->shouldBeCalled()
->willThrow(new Exception('PHPSpec'));
->willThrow($exception);
$this->logger->critical($exception, ['exception' => $exception])
->shouldBeCalled()
->willReturn(null);
$request->getAttribute('accept')
->shouldBeCalled()
->willReturn('json');
$this
->setSentryEnabled(false)
->process($request, $handler)
->shouldBeAnInstanceOf(JsonResponse::class);
}
......
......@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "6c23473ac0e2036a05166b78c51a6080",
"content-hash": "017bf76d590078e33e984171db408c4a",
"packages": [
{
"name": "Minds/Surge",
......@@ -1903,16 +1903,16 @@
},
{
"name": "monolog/monolog",
"version": "1.25.1",
"version": "1.25.2",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "70e65a5470a42cfec1a7da00d30edb6e617e8dcf"
"reference": "d5e2fb341cb44f7e2ab639d12a1e5901091ec287"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/70e65a5470a42cfec1a7da00d30edb6e617e8dcf",
"reference": "70e65a5470a42cfec1a7da00d30edb6e617e8dcf",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/d5e2fb341cb44f7e2ab639d12a1e5901091ec287",
"reference": "d5e2fb341cb44f7e2ab639d12a1e5901091ec287",
"shasum": ""
},
"require": {
......@@ -1977,7 +1977,7 @@
"logging",
"psr-3"
],
"time": "2019-09-06T13:49:17+00:00"
"time": "2019-11-13T10:00:05+00:00"
},
{
"name": "mtdowling/jmespath.php",
......
......@@ -839,8 +839,14 @@ function elgg_trigger_plugin_hook($hook, $type, $params = null, $returnvalue = n
* @access private
*/
function _elgg_php_exception_handler($exception) {
$timestamp = time();
error_log("Exception #$timestamp: $exception");
try {
\Minds\Helpers\Log::critical($exception, [ 'exception' => $exception ]);
} catch (Exception $loggerException) {
$timestamp = time();
error_log("Exception #$timestamp: $exception");
Sentry\captureException($exception);
}
// Wipe any existing output buffer
ob_end_clean();
......@@ -850,8 +856,6 @@ function _elgg_php_exception_handler($exception) {
header("Cache-Control: no-cache, must-revalidate", true);
header('Expires: Fri, 05 Feb 1982 00:00:00 -0500', true);
// @note Do not send a 500 header because it is not a server error
Sentry\captureException($exception);
}
/**
......