Add Core\Log
psr-3
This commit is contained in:
parent
56a872bbb1
commit
2891f8fc78
1 changed files with 294 additions and 0 deletions
294
app/Core/Log.php
Normal file
294
app/Core/Log.php
Normal file
|
@ -0,0 +1,294 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of the ForkBB <https://github.com/forkbb>.
|
||||
*
|
||||
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
|
||||
* @license The MIT License (MIT)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ForkBB\Core;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
use Psr\Log\InvalidArgumentException;
|
||||
use DateTimeZone;
|
||||
use DateTime;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
class Log implements LoggerInterface
|
||||
{
|
||||
const JSON_OPTIONS = \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR;
|
||||
|
||||
protected $path;
|
||||
protected $lineFormat;
|
||||
protected $timeFormat;
|
||||
protected $resource;
|
||||
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->path = $config['path'] ?? __DIR__ . '/../log/{Y-m-d}.log';
|
||||
$this->lineFormat = $config['lineFormat'] ?? "%datetime% [%level_name%] %message%\t%context%\n";
|
||||
$this->timeFormat = $config['timeFormat'] ?? 'Y-m-d H:i:s';
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (\is_resource($this->resource)) {
|
||||
\fclose($this->resource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs with an arbitrary level.
|
||||
*
|
||||
* @param mixed $level
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \Psr\Log\InvalidArgumentException
|
||||
*/
|
||||
public function log($level, $message, array $context = [])
|
||||
{
|
||||
if (! \is_string($message)) {
|
||||
throw new InvalidArgumentException('Expected string in message');
|
||||
}
|
||||
if (! \is_string($level)) {
|
||||
throw new InvalidArgumentException('Expected string in level');
|
||||
}
|
||||
switch ($level) {
|
||||
case LogLevel::EMERGENCY:
|
||||
case LogLevel::ALERT:
|
||||
case LogLevel::CRITICAL:
|
||||
case LogLevel::ERROR:
|
||||
case LogLevel::WARNING:
|
||||
case LogLevel::NOTICE:
|
||||
case LogLevel::INFO:
|
||||
case LogLevel::DEBUG:
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException('Invalid level value');
|
||||
}
|
||||
|
||||
$line = $this->generateLine($level, $message, $context);
|
||||
|
||||
if (! \is_resource($this->resource)) {
|
||||
$this->initResource();
|
||||
}
|
||||
|
||||
\flock($this->resource, \LOCK_EX);
|
||||
\fwrite($this->resource, $line);
|
||||
\flock($this->resource, \LOCK_UN);
|
||||
}
|
||||
|
||||
protected function initResource(): void
|
||||
{
|
||||
$dt = new DateTime('now', new DateTimeZone('UTC'));
|
||||
$path = \preg_replace_callback(
|
||||
'%{([^{}]+)}%',
|
||||
function ($matches) use ($dt) {
|
||||
$result = $dt->format($matches[1]);
|
||||
|
||||
return $result ?: 'bad_format';
|
||||
},
|
||||
$this->path
|
||||
);
|
||||
|
||||
if (! \is_writable($path)) {
|
||||
$dir = \pathinfo($path, \PATHINFO_DIRNAME);
|
||||
|
||||
if (
|
||||
! \is_dir($dir)
|
||||
&& ! \mkdir($dir, 0755, true)
|
||||
) {
|
||||
throw new RuntimeException("Unable to create '{$dir}' directory");
|
||||
}
|
||||
|
||||
if (! \chmod($dir, 0755)) {
|
||||
throw new RuntimeException("Error changing the access mode to '{$dir}' directory");
|
||||
}
|
||||
|
||||
if (
|
||||
\is_file($path)
|
||||
&& ! \chmod($dir, 0755)
|
||||
) {
|
||||
throw new RuntimeException("Error changing the access mode to '{$path}' file");
|
||||
}
|
||||
}
|
||||
|
||||
$this->resource = \fopen($path, 'a');
|
||||
|
||||
if (! \is_resource($this->resource)) {
|
||||
throw new RuntimeException("Could not get access to '{$path}' resource");
|
||||
}
|
||||
}
|
||||
|
||||
protected function generateLine(string $level, string $message, array $context): string
|
||||
{
|
||||
if (
|
||||
false !== \strpos($message, '{')
|
||||
&& false !== \strpos($message, '}')
|
||||
) {
|
||||
$message = $this->interpolate($message, $context);
|
||||
}
|
||||
|
||||
if (
|
||||
isset($context['exception'])
|
||||
&& $context['exception'] instanceof Throwable
|
||||
) {
|
||||
$context['exception'] = (string) $context['exception']; // ????
|
||||
}
|
||||
|
||||
$dt = new DateTime('now', new DateTimeZone('UTC'));
|
||||
$result = [
|
||||
'%datetime%' => $dt->format($this->timeFormat),
|
||||
'%level_name%' => $level,
|
||||
'%message%' => $message,
|
||||
'%context%' => \json_encode($context, self::JSON_OPTIONS),
|
||||
];
|
||||
|
||||
return \strtr($this->lineFormat, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates context values into the message placeholders.
|
||||
*/
|
||||
protected function interpolate(string $message, array $context): string
|
||||
{
|
||||
$replace = [];
|
||||
foreach ($context as $key => $val) {
|
||||
// check that the value can be cast to string
|
||||
if (
|
||||
! \is_array($val)
|
||||
&& (
|
||||
! \is_object($val)
|
||||
|| \method_exists($val, '__toString')
|
||||
)
|
||||
) {
|
||||
$replace['{' . $key . '}'] = (string) $val;
|
||||
}
|
||||
}
|
||||
|
||||
// interpolate replacement values into the message and return
|
||||
return \strtr($message, $replace);
|
||||
}
|
||||
|
||||
/**
|
||||
* System is unusable.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function emergency($message, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::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 function alert($message, array $context = [])
|
||||
{
|
||||
$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, array $context = [])
|
||||
{
|
||||
$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, array $context = [])
|
||||
{
|
||||
$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, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::WARNING, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normal but significant events.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function notice($message, array $context = [])
|
||||
{
|
||||
$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, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::INFO, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detailed debug information.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function debug($message, array $context = [])
|
||||
{
|
||||
$this->log(LogLevel::DEBUG, $message, $context);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue