Add Core\Log

psr-3
This commit is contained in:
Visman 2021-01-17 21:01:47 +07:00
parent 56a872bbb1
commit 2891f8fc78

294
app/Core/Log.php Normal file
View 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);
}
}