zhamao-logger/src/ZM/Logger/ConsoleLogger.php

266 lines
6.4 KiB
PHP
Raw Normal View History

2022-05-12 20:27:01 +08:00
<?php
declare(strict_types=1);
namespace ZM\Logger;
use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;
class ConsoleLogger extends AbstractLogger
{
public const VERSION = '1.0.0-alpha';
2022-05-13 02:36:05 +08:00
/**
* 日志输出格式
*
* @var string
*/
2022-05-13 18:06:03 +08:00
public static $format = '[%date%] [%level%] %body%';
2022-05-12 20:27:01 +08:00
2022-05-13 02:36:05 +08:00
/**
* 日志输出日期格式
*
* @var string
*/
2022-05-12 20:27:01 +08:00
public static $date_format = 'Y-m-d H:i:s';
/**
2022-05-13 02:36:05 +08:00
* 颜色表
*
2022-05-13 18:38:23 +08:00
* @var string[][]
2022-05-12 20:27:01 +08:00
*/
protected static $styles = [
2022-05-13 02:36:05 +08:00
['blink', 'white', 'bg_bright_red'], // emergency
['white', 'bg_bright_red'], // alert
['underline', 'red'], // critical
['red'], // error
['bright_yellow'], // warning
['cyan'], // notice
['green'], // info
['gray'], // debug
2022-05-12 20:27:01 +08:00
];
2022-05-13 02:36:05 +08:00
/**
* 等级表
*
2022-05-13 18:38:23 +08:00
* @var string[]
2022-05-13 02:36:05 +08:00
*/
2022-05-12 20:27:01 +08:00
protected static $levels = [
LogLevel::EMERGENCY, // 0
LogLevel::ALERT, // 1
LogLevel::CRITICAL, // 2
LogLevel::ERROR, // 3
LogLevel::WARNING, // 4
LogLevel::NOTICE, // 5
LogLevel::INFO, // 6
LogLevel::DEBUG, // 7
];
2022-05-13 02:36:05 +08:00
/**
* 当前日志等级
*
* @var int
*/
protected static $log_level;
2022-05-12 20:27:01 +08:00
2022-05-13 18:33:57 +08:00
/**
* 静态上下文
*
* @var array
*/
protected $static_context = [];
2022-05-13 19:43:52 +08:00
/**
* 日志记录回调
*
* @var callable[]
*/
protected $log_callbacks = [];
2022-05-13 02:36:05 +08:00
/**
* 创建一个 ConsoleLogger 实例
*
* @param string $level 日志等级
*/
public function __construct(string $level = LogLevel::INFO)
2022-05-12 20:27:01 +08:00
{
2022-05-13 02:36:05 +08:00
self::$log_level = $this->castLogLevel($level);
2022-05-12 20:27:01 +08:00
}
/**
2022-05-13 02:36:05 +08:00
* 获取当前样式表
*
2022-05-13 18:38:23 +08:00
* @return string[][]
2022-05-12 20:27:01 +08:00
*/
public static function getStyles(): array
{
return self::$styles;
}
2022-05-13 02:36:05 +08:00
/**
* 获取版本号
*/
public static function getVersion(): string
2022-05-12 20:27:01 +08:00
{
2022-05-13 02:36:05 +08:00
return self::VERSION;
2022-05-12 20:27:01 +08:00
}
2022-05-13 19:43:52 +08:00
/**
* 添加静态上下文
*/
public function addStaticContext(array $context): void
{
$this->static_context = array_merge($this->static_context, $context);
}
/**
* 添加日志记录回调
*/
public function addLogCallback(callable $callback): void
{
$this->log_callbacks[] = $callback;
}
2022-05-13 02:36:05 +08:00
/**
* 打印执行栈
*/
2022-05-12 20:27:01 +08:00
public function trace(): void
{
$log = "Stack trace:\n";
$trace = debug_backtrace();
//array_shift($trace);
foreach ($trace as $i => $t) {
if (!isset($t['file'])) {
$t['file'] = 'unknown';
}
if (!isset($t['line'])) {
$t['line'] = 0;
}
$log .= "#{$i} {$t['file']}({$t['line']}): ";
if (isset($t['object']) && is_object($t['object'])) {
$log .= get_class($t['object']) . '->';
}
$log .= "{$t['function']}()\n";
}
2022-05-13 02:36:05 +08:00
$log = $this->colorize($log, $this->castLogLevel(LogLevel::DEBUG));
2022-05-12 20:27:01 +08:00
echo $log;
}
2022-05-13 02:36:05 +08:00
/**
* 根据日志等级将样式应用至指定字符串
*
* @param mixed $string 日志内容
* @param int $level 日志等级
*/
public function colorize($string, int $level): string
2022-05-12 20:27:01 +08:00
{
2022-05-13 02:36:05 +08:00
$string = $this->stringify($string);
$styles = self::$styles[$level] ?? [];
return ConsoleColor::apply($styles, $string)->__toString();
2022-05-12 20:27:01 +08:00
}
2022-05-13 02:36:05 +08:00
/**
* {@inheritDoc}
*/
2022-05-12 20:27:01 +08:00
public function log($level, $message, array $context = []): void
{
2022-05-13 02:36:05 +08:00
$level = $this->castLogLevel($level);
2022-05-12 20:27:01 +08:00
2022-05-13 02:36:05 +08:00
if (!$this->shouldLog($level)) {
2022-05-12 20:27:01 +08:00
return;
}
$output = str_replace(
['%date%', '%level%', '%body%'],
2022-05-13 02:36:05 +08:00
[date(self::$date_format), strtoupper(substr(self::$levels[$level], 0, 4)), $message],
2022-05-12 20:27:01 +08:00
self::$format
);
2022-05-13 18:33:57 +08:00
$output = $this->interpolate($output, array_merge($this->static_context, $context));
2022-05-12 20:27:01 +08:00
2022-05-13 19:43:52 +08:00
foreach ($this->log_callbacks as $callback) {
if ($callback($level, $output, $message, $context) === false) {
return;
}
}
echo $this->colorize($output, $level) . "\n";
2022-05-13 18:33:57 +08:00
}
2022-05-13 02:36:05 +08:00
/**
* 转换日志等级
*/
private function castLogLevel(string $level): int
2022-05-12 20:27:01 +08:00
{
2022-05-13 02:36:05 +08:00
if (in_array($level, self::$levels, true)) {
return array_flip(self::$levels)[$level];
}
throw new InvalidArgumentException('无效的日志等级');
2022-05-12 20:27:01 +08:00
}
2022-05-13 02:36:05 +08:00
/**
* 将日志内容转换为字符串
*
* @param mixed $item 日志内容
*/
private function stringify($item): string
2022-05-12 20:27:01 +08:00
{
2022-05-13 02:36:05 +08:00
switch (true) {
case is_callable($item):
if (is_array($item)) {
if (is_object($item[0])) {
return get_class($item[0]) . '@' . $item[1];
}
return $item[0] . '::' . $item[1];
}
return 'closure';
case is_string($item):
return $item;
case is_array($item):
2022-05-14 23:36:57 +08:00
if (extension_loaded('json')) {
return 'array' . json_encode($item, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_LINE_TERMINATORS);
}
return var_export($item, true);
2022-05-13 02:36:05 +08:00
case is_object($item):
return get_class($item);
case is_resource($item):
return 'resource(' . get_resource_type($item) . ')';
case is_null($item):
return 'null';
case is_bool($item):
return $item ? 'true' : 'false';
case is_float($item):
case is_int($item):
return (string) $item;
default:
return 'unknown';
2022-05-12 20:27:01 +08:00
}
}
2022-05-13 02:36:05 +08:00
/**
* 判断是否应该记录该等级日志
*/
private function shouldLog(int $level): bool
2022-05-12 20:27:01 +08:00
{
2022-05-13 02:36:05 +08:00
return $level <= self::$log_level;
}
2022-05-12 20:27:01 +08:00
2022-05-13 02:36:05 +08:00
/**
* 插入变量到日志内容中
*
* @param string $message 日志内容
* @param array $context 变量列表
*/
private function interpolate(string $message, array $context = []): string
{
2022-05-12 20:27:01 +08:00
$replace = [];
foreach ($context as $key => $value) {
$replace['{' . $key . '}'] = $this->stringify($value);
}
return strtr($message, $replace);
}
}