فهرست منبع

Add Core\Log

psr-3
Visman 4 سال پیش
والد
کامیت
2891f8fc78
1فایلهای تغییر یافته به همراه294 افزوده شده و 0 حذف شده
  1. 294 0
      app/Core/Log.php

+ 294 - 0
app/Core/Log.php

@@ -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);
+    }
+}