Skip to content

Commit 4be4fe7

Browse files
edgebalxander-miller
authored andcommitted
PSR-3 Error logging (Monolog)
1 parent 3efa242 commit 4be4fe7

File tree

10 files changed

+362
-34
lines changed

10 files changed

+362
-34
lines changed

Core/Log/Logger.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
/**
3+
* Logger
4+
*
5+
* @author edgebal
6+
*/
7+
8+
namespace Minds\Core\Log;
9+
10+
use Exception;
11+
use Monolog\Formatter\LineFormatter;
12+
use Monolog\Handler\ChromePHPHandler;
13+
use Monolog\Handler\ErrorLogHandler;
14+
use Monolog\Handler\FirePHPHandler;
15+
use Monolog\Handler\PHPConsoleHandler;
16+
use Monolog\Logger as MonologLogger;
17+
use Sentry\Monolog\Handler as SentryHandler;
18+
use Sentry\SentrySdk;
19+
20+
/**
21+
* A PSR-3 logger tailored for Minds. Based off Monolog.
22+
*
23+
* @package Minds\Core\Log
24+
* @see \Monolog\Logger
25+
* @see \Psr\Log\LoggerInterface
26+
*/
27+
class Logger extends MonologLogger
28+
{
29+
/**
30+
* Logger constructor.
31+
* @param string $channel
32+
* @param array $options
33+
*/
34+
public function __construct(string $channel = 'Minds', array $options = [])
35+
{
36+
$options = array_merge([
37+
'isProduction' => true,
38+
'devToolsLogger' => '',
39+
], $options);
40+
41+
$handlers = [];
42+
43+
$errorLogHandler = new ErrorLogHandler(
44+
ErrorLogHandler::OPERATING_SYSTEM,
45+
$options['isProduction'] ? MonologLogger::INFO : MonologLogger::DEBUG,
46+
true,
47+
true
48+
);
49+
50+
$errorLogHandler
51+
->setFormatter(new LineFormatter(
52+
"%channel%.%level_name%: %message% %context% %extra%\n",
53+
'c',
54+
!$options['isProduction'], // Allow newlines on dev mode
55+
true
56+
));
57+
58+
$handlers[] = $errorLogHandler;
59+
60+
if ($options['isProduction']) {
61+
$handlers[] = new SentryHandler(SentrySdk::getCurrentHub());
62+
} else {
63+
// Extra handlers for Development Mode
64+
65+
switch ($options['devToolsLogger']) {
66+
case 'firephp':
67+
$handlers[] = new FirePHPHandler();
68+
break;
69+
70+
case 'chromelogger':
71+
$handlers[] = new ChromePHPHandler();
72+
break;
73+
74+
case 'phpconsole':
75+
try {
76+
$handlers[] = new PHPConsoleHandler();
77+
} catch (Exception $exception) {
78+
// If the server-side vendor package is not installed, ignore any warnings.
79+
}
80+
break;
81+
}
82+
}
83+
84+
// Create Monolog instance
85+
86+
parent::__construct($channel, $handlers);
87+
}
88+
}

Core/Log/Module.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
/**
3+
* Module
4+
*
5+
* @author edgebal
6+
*/
7+
8+
namespace Minds\Core\Log;
9+
10+
use Minds\Interfaces\ModuleInterface;
11+
12+
/**
13+
* Module class
14+
*
15+
* @package Minds\Core\Log
16+
*/
17+
class Module implements ModuleInterface
18+
{
19+
/**
20+
* @inheritDoc
21+
*/
22+
public function onInit(): void
23+
{
24+
(new Provider())->register();
25+
}
26+
}

Core/Log/Provider.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
/**
3+
* Provider
4+
*
5+
* @author edgebal
6+
*/
7+
8+
namespace Minds\Core\Log;
9+
10+
use Minds\Core\Config;
11+
use Minds\Core\Di\Di;
12+
use Minds\Core\Di\Provider as DiProvider;
13+
14+
/**
15+
* DI Provider bindings
16+
*
17+
* @package Minds\Core\Log
18+
*/
19+
class Provider extends DiProvider
20+
{
21+
public function register()
22+
{
23+
$this->di->bind('Logger', function ($di) {
24+
/** @var Di $di */
25+
26+
/** @var Config|false $config */
27+
$config = $di->get('Config');
28+
29+
$options = [
30+
'isProduction' => $config ? !$config->get('development_mode') : true,
31+
'devToolsLogger' => $config ? $config->get('devtools_logger') : '',
32+
];
33+
34+
return new Logger('Minds', $options);
35+
}, [ 'useFactory' => false ]);
36+
37+
$this->di->bind('Logger\Singleton', function ($di) {
38+
/** @var Di $di */
39+
return $di->get('Logger');
40+
}, [ 'useFactory' => true ]);
41+
}
42+
}

Core/Minds.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class Minds extends base
1616
public static $booted = false;
1717

1818
private $modules = [
19+
Log\Module::class,
1920
Events\Module::class,
2021
SSO\Module::class,
2122
Email\Module::class,

Core/Router/Middleware/Kernel/ErrorHandlerMiddleware.php

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
namespace Minds\Core\Router\Middleware\Kernel;
88

99
use Exception;
10+
use Minds\Core\Di\Di;
11+
use Minds\Core\Log\Logger;
1012
use Minds\Core\Router\Exceptions\ForbiddenException;
1113
use Minds\Core\Router\Exceptions\UnauthorizedException;
1214
use Psr\Http\Message\ResponseInterface;
@@ -15,21 +17,20 @@
1517
use Psr\Http\Server\RequestHandlerInterface;
1618
use Zend\Diactoros\Response\HtmlResponse;
1719
use Zend\Diactoros\Response\JsonResponse;
18-
use function Sentry\captureException;
1920

2021
class ErrorHandlerMiddleware implements MiddlewareInterface
2122
{
22-
/** @var bool */
23-
protected $sentryEnabled = true;
23+
/** @var Logger */
24+
protected $logger;
2425

2526
/**
26-
* @param bool $sentryEnabled
27-
* @return ErrorHandlerMiddleware
27+
* ErrorHandlerMiddleware constructor.
28+
* @param Logger $logger
2829
*/
29-
public function setSentryEnabled(bool $sentryEnabled): ErrorHandlerMiddleware
30-
{
31-
$this->sentryEnabled = $sentryEnabled;
32-
return $this;
30+
public function __construct(
31+
$logger = null
32+
) {
33+
$this->logger = $logger ?: Di::_()->get('Logger');
3334
}
3435

3536
/**
@@ -58,15 +59,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
5859
$status = 403;
5960
} catch (Exception $e) {
6061
// Log
61-
62-
// TODO: Monolog
63-
error_log((string) $e);
64-
65-
// Sentry
66-
67-
if ($this->sentryEnabled) {
68-
captureException($e);
69-
}
62+
$this->logger->critical($e, ['exception' => $e]);
7063
}
7164

7265
switch ($request->getAttribute('accept')) {

Helpers/Log.php

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
<?php
2+
/**
3+
* Log
4+
*
5+
* @author edgebal
6+
*/
7+
8+
namespace Minds\Helpers;
9+
10+
use Minds\Core\Di\Di;
11+
use Psr\Log\InvalidArgumentException;
12+
13+
/**
14+
* Static helper class for easy access to Logger singleton
15+
* logging methods.
16+
*
17+
* @package Minds\Helpers
18+
* @see \Minds\Core\Log\Logger
19+
*/
20+
class Log
21+
{
22+
/**
23+
* System is unusable.
24+
*
25+
* @param string $message
26+
* @param array $context
27+
*
28+
* @return void
29+
*/
30+
public static function emergency($message, array $context = [])
31+
{
32+
Di::_()->get('Logger\Singleton')->emergency($message, $context);
33+
}
34+
35+
/**
36+
* Action must be taken immediately.
37+
*
38+
* Example: Entire website down, database unavailable, etc. This should
39+
* trigger the SMS alerts and wake you up.
40+
*
41+
* @param string $message
42+
* @param array $context
43+
*
44+
* @return void
45+
*/
46+
public static function alert($message, array $context = [])
47+
{
48+
Di::_()->get('Logger\Singleton')->alert($message, $context);
49+
}
50+
51+
/**
52+
* Critical conditions.
53+
*
54+
* Example: Application component unavailable, unexpected exception.
55+
*
56+
* @param string $message
57+
* @param array $context
58+
*
59+
* @return void
60+
*/
61+
public static function critical($message, array $context = [])
62+
{
63+
Di::_()->get('Logger\Singleton')->critical($message, $context);
64+
}
65+
66+
/**
67+
* Runtime errors that do not require immediate action but should typically
68+
* be logged and monitored.
69+
*
70+
* @param string $message
71+
* @param array $context
72+
*
73+
* @return void
74+
*/
75+
public static function error($message, array $context = [])
76+
{
77+
Di::_()->get('Logger\Singleton')->error($message, $context);
78+
}
79+
80+
/**
81+
* Exceptional occurrences that are not errors.
82+
*
83+
* Example: Use of deprecated APIs, poor use of an API, undesirable things
84+
* that are not necessarily wrong.
85+
*
86+
* @param string $message
87+
* @param array $context
88+
*
89+
* @return void
90+
*/
91+
public static function warning($message, array $context = [])
92+
{
93+
Di::_()->get('Logger\Singleton')->warning($message, $context);
94+
}
95+
96+
/**
97+
* Normal but significant events.
98+
*
99+
* @param string $message
100+
* @param array $context
101+
*
102+
* @return void
103+
*/
104+
public static function notice($message, array $context = [])
105+
{
106+
Di::_()->get('Logger\Singleton')->notice($message, $context);
107+
}
108+
109+
/**
110+
* Interesting events.
111+
*
112+
* Example: User logs in, SQL logs.
113+
*
114+
* @param string $message
115+
* @param array $context
116+
*
117+
* @return void
118+
*/
119+
public static function info($message, array $context = [])
120+
{
121+
Di::_()->get('Logger\Singleton')->info($message, $context);
122+
}
123+
124+
/**
125+
* Detailed debug information.
126+
*
127+
* @param string $message
128+
* @param array $context
129+
*
130+
* @return void
131+
*/
132+
public static function debug($message, array $context = [])
133+
{
134+
Di::_()->get('Logger\Singleton')->debug($message, $context);
135+
}
136+
137+
/**
138+
* Logs with an arbitrary level.
139+
*
140+
* @param mixed $level
141+
* @param string $message
142+
* @param array $context
143+
*
144+
* @return void
145+
*
146+
* @throws InvalidArgumentException
147+
*/
148+
public static function log($level, $message, array $context = [])
149+
{
150+
Di::_()->get('Logger\Singleton')->log($level, $message, $context);
151+
}
152+
}

0 commit comments

Comments
 (0)