terrazza/logger

1.0.0 2022-05-23 20:22 UTC

This package is auto-updated.

Last update: 2024-09-24 02:06:31 UTC


README

此组件是PSR/Log标准的实现,并添加了一些扩展。

结构

  1. Logger组件
    需要用0-n个ChannelHandler初始化
    并提供了常见的方法
    • 警告
    • 错误
    • 通知
    • ...
  2. ChannelHandler组件
    负责确定
    1. 目标/写入者(必需)
    2. 记录格式化器(必需)
    3. 通道过滤器(可选)
    4. 此通道的所有相关LogHandler
  3. LogHandler组件
    确定
    • 日志级别
    • 格式(可选,默认:从ChannelHandler->recordFormatter获取)

与常见的PSR/Log实现相比,Terrazza/Logger组件在处理"格式"方面有所不同。Writer组件处理多个"行",并将它们合并。在这个差异中,可以转发转换后的格式并保持每行的键。
例如:将消息/格式写入JSON对象或数据库。

对象/类

  1. Logger
    1. 方法:registerChannelHandler
    2. 方法:registerExceptionHandler
    3. 方法:registerErrorHandler
    4. 方法:registerFatalHandler
    5. 方法:setExceptionFileName
    6. 构造函数:context (数组)
  2. 处理程序
    1. ChannelHandler
    2. LogHandler
  3. LogRecord
    1. LogRecordTrace
  4. LogRecordFormatter
  5. LogHandlerFilter
  6. 转换器
  7. 安装
  8. 需求
  9. 示例

Logger

Logger对象/方法与常见的PSR实现相似,但!
Logger是用channelHandler(s)初始化的,而不是Handler

每个channelHandler只执行一个logHandler

方法:registerChannelHandler

向logger添加channelHandler(非不可变)。

方法:registerExceptionHandler

为PHP异常处理程序注册回调。

开发良好的项目应该自己处理/覆盖所有异常,但;-)

方法:registerErrorHandler

为PHP错误处理程序注册回调。

有时没有这种解决方案就无法捕获此类错误

方法:registerFatalHandler

为PHP关闭注册回调。

有时没有这种解决方案就无法捕获此类错误

方法:setExceptionFileName

方法:addMessage本身被try/catch覆盖。
catch处理程序将Exception.Message写入文件,该文件可以用setExceptionFileName方法设置。

通知
默认:php://stderr

构造函数:context (数组)

除了名称外,logger还可以用初始化的上下文初始化。
可以单独访问此上下文。

使用示例
类注入了组件,并在构造函数中

$logger = new Logger("name", ["user" => "Value"]);
$logger->notice("hello", ["my" => "value"]);
$format = ["{Context.my} {iContext.user}"];

处理程序

ChannelHandler

ChannelHandler将LogHandler收集到相同的通道,并提供

  • 相同的写入者
  • 相同的格式化器
    为每个LogHandler。

ChannelHandler可以通过Logger中的方法注册

  • 方法:registerChannelHandler
  • 构造函数(第三个参数为可变参数)
方法:getWriter
方法:getFormatter
方法:getFilter
方法:getLogHandler (LogHandlerInterface[])
方法:pushLogHandler

添加新LogHandler的方法。
日志处理器的数组将被按键排序,以防止针对不同日志级别的多个写入事务。

方法:getEffectedHandler

返回给定日志记录的匹配LogHandler

方法:writeRecord

对于传入的LogHandler,记录将被

  • 格式化
  • 并写入Writer

LogHandler

SingleHandler提供了为Logger创建处理器的常用方式。与常规实现相比,唯一的不同之处在于

  • 而不是logLevel
  • SingleHandler需要在Channel中注入

LogRecord

与常规PSR实现相比,我们的组件处理对象而不是数组。
日志记录属性

  • logDate (\Datetime)
  • loggerName (字符串)
  • logLevel (整数)
  • logMessage (字符串)
  • memUsed (整数)
  • memAllocated (整数)
  • LogRecordTrace
  • context (数组)
  • initContext (数组)

此外,该对象还提供

  • logLevelName (字符串)

方法/静态 createRecord

此方法在Logger内部用于创建新的LogRecord对象。

方法:getToken()

此方法在LogRecordFormatterInterface中用于获取日志记录的"编码"。每个元素都可以通过其"格式"访问,例如{Level}{LevelName}{Context.name}

return [
  'Date'         => $this->getLogDate(),
  'Level'        => $this->getLogLevel(),
  'LevelName'    => $this->getLogLevelName(),
  'LoggerName'   => $this->getLoggerName(),
  'MemUsed'      => $this->getMemUsed(),
  'MemAllocated' => $this->getMemAllocated(),
  'Message'      => $this->getLogMessage(),
  'Context'      => $this->getContext(),
  'iContext'     => $this->getInitContext(),
  'Trace'        => [
    "Namespace"    => $this->getTrace()->getNamespace(),
    "Line"         => $this->getTrace()->getLine(),
    "Classname"    => $this->getTrace()->getClassname(),
    "Function"     => $this->getTrace()->getFunction(),
    "Method"       => $this->getTrace()->getClassname()."::".$this->getTrace()->getFunction(),
    "sMethod"      => basename($this->getTrace()->getClassname())."::".$this->getTrace()->getFunction(),  
  ]
]

LogRecordTrace

在创建LogRecord时,Logger会生成LogRecordTrace对象。
基于debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS),每个记录都会获得额外的属性

  • 命名空间
  • 类名
  • 函数
  • 行号

LogRecordFormatter

日志记录格式化器将记录转换为数组
初始化属性

  • NonScalarConverterInterface
  • 格式 (数组)
  • valueConverter (数组,可选)

NonScalarConverter (NonScalarConverterInterface)

NonScalarConverter将非标量值(例如从Context)转换为字符串。
实际上,提供的类NonScalarJsonEncode使用json_encode,并在前面添加属性名。

当格式化器行包含非标量和标量内容时,将使用NonScalarConverter。

使用示例:

use Terrazza\Component\Logger\Converter\NonScalar\NonScalarJsonConverter;

$record = ["message" => "myMessage", "key" => ["value1", "value2"]];
echo (new NonScalarJsonEncode())->getValue($context["key"]); // key:{"value1", "value2"}

// in context of the formatter it will be
$format = ["{Message}:{Context.key}"];                 // ... myMessage:key:{"value1", "value2"}
$format = ["Context" => "{Message}:{Context.key}"];    // ... myMessage:key:{"value1", "value2"}

可以使用ValueConverter (LogRecordValueConverterInterface)根据其键转换特殊值。

方法:pushConverter

为了根据键映射/转换特殊值,推送特殊转换器。
此转换器必须满足LogRecordValueConverterInterface。

方法:formatRecord

将记录映射到$format,并返回映射后的数组。
未知模式(例如{undefined})将从响应中删除。
空行也将被删除。

示例

此示例使用一个额外的ValueConverter来转换记录值"Date"。

use DateTime;
use Terrazza\Component\Logger\LogRecordValueConverterInterface;
use Terrazza\Component\Logger\LOgRecord;
use Terrazza\Component\Logger\Formatter\LogRecordFormatter;
use Terrazza\Component\Logger\Converter\NonScalar\NonScalarJsonEncode;

class RecordTokenValueDate implements LogRecordValueConverterInterface {
    private string $dateFormat;
    public function __construct(string $dateFormat="Y-m-d H:i:s.u") {
        $this->dateFormat                           = $dateFormat;
    }
    public function getValue($value) {
        return $value->format($this->dateFormat);
    }
}

$formatter = new LogRecordFormatter(
   new NonScalarJsonEncode,
   ["Date", "Message"]
);
$formatter->pushConverter("Date", new RecordTokenValueDate);   

$record  = LogRecord::create("LoggerName", 100, "myMessage");
var_dump($formatter->formatRecord($record)); 
/*
[
   "Date" => 2022-12-31 23:59:01,
   "Message" => "myMessage"
]
*/

LogHandlerFilter

LogHandler可以有一个LogHandlerFilter。
属性

  • include (数组,可选)
  • exclude (数组,可选)
  • start (数组,可选)

方法:isHandling (string $callerNamespace) : bool

include
使用include模式与callerNamespace进行preg_match比较
exclude
使用exclude模式与callerNamespace进行preg_match比较
start
使用start模式与callerNamespace进行preg_match比较
如果preg_match为true,则后续的isHandling都将为true。 (exclude过滤器覆盖start)

转换器

首先,将LogRecordRecordFormatter进行转换/映射
之后,根据目标/写入器的不同,数组需要再次进行格式化。

我们实际上包含了/提供了两个转换器

  • 转换为字符串,json类型
  • 转换为字符串,例如用于控制台日志。在任何情况下,转换器都会注入到Writer中。

FormattedRecordFlat

将映射的LogRecord转换为每行带有分隔符的字符串。
对于非标量值,我们使用json_encode进行值转换。

方法:setNonScalarPrefix(string $delimiter)

使用此方法,非标量值将被dataKey和分隔符前缀。
参数

  • 分隔符(字符串,必需)
  • 编码标志(整数,可选)

使用示例:

use Terrazza\Component\Logger\Converter\FormattedRecord\FormattedRecordFlat;

$formatter = new FormattedRecordFlat("|",0);
echo $formatter->convert(["message" => "myMessage", "context" => ["k" => "v"]); 
//myMessage|{"k":"v"}

$formatter->setNonScalarPrefix(":");
echo $formatter->convert(["message" => "myMessage", "context" => ["k" => "v"]); 
//myMessage|context:{"k":"v"}

FormattedRecordJson

通过使用json_encode将映射的LogRecord转换为字符串。
参数

  • 编码标志(整数,可选)

使用示例:

use Terrazza\Component\Logger\Converter\FormattedRecord\FormattedRecordJson;

$formatter = new FormattedRecordJson(0);
echo $formatter->convert(["message" => "myMessage", "context" => ["k" => "v"]); 
//{"message" : "myMessage", "context": {"k":"v"}}

Writer

StreamFile

将转换后的记录保存到文件中。
参数

  • converter (IFormattedRecordConverter,必需)
  • filename (字符串,必需)
  • flags (整数,可选,默认:0)

转换器应将格式化的LogRecord转换为字符串。

use Terrazza\Component\Logger\Writer\StreamFile;
use Terrazza\Component\Logger\Converter\FormattedRecord\FormattedRecordFlat;

$logFile    = "log.txt";
@unlink($logFile);
$converter  = new FormattedRecordFlat("|",0);
$writer     = new StreamFile($converter, $logFile);
$writer->write(["message" => "myMessage", "context" => ["k" => "v"]);

$logContent = file_get_contents($logFile);
echo $logContent;
//{"message" : "myMessage", "context": {"k":"v"}}

如何安装

通过composer安装

composer require terrazza/logger

需求

  • php >= 7.4

composer包

  • psr/log

示例

1. 创建一个ChannelHandler

use Terrazza\Component\Logger\Converter\FormattedRecord\FormattedRecordFlat;
use Terrazza\Component\Logger\Writer\StreamFile;
use Terrazza\Component\Logger\Formatter\RecordFormatter;
use Terrazza\Component\Logger\Converter\NonScalar\NonScalarJsonEncode;
use Terrazza\Component\Logger\Handler\ChannelHandler;

$writeConverter   = new FormattedRecordFlat("|",0); 
$writer           = new StreamFile($writeConverter, "test.log");
$formatter        = new RecordFormatter(new NonScalarJsonEncode(), [
   "Message" => "{Level}-{Message}-{Context.pid}"
]); 
$channelHandler   = new ChannelHandler($writer, $formatter);

2. 创建一个LogHandler

通知
下一行代码取决于前面的示例...(创建ChannelHandler)

use Terrazza\Component\Logger\Handler\LogHandler;
use Terrazza\Component\Logger\Logger;

$handler          = new LogHandler(Logger::WARNING);
// push handler into previouse create channelHandler
$channelHandler->pushLogHandler($handler);

3. 在处理器中创建

通知
下一行代码取决于前面的示例...(创建LogHandler)

use Terrazza\Component\Logger\Logger;

// additinal we initialize the Context with pid = getmypid
// the formatter uses {Context.pid} and will print it

$logger           = new Logger("loggerName", ["pid" => "myPID"], $channelHandler);
$logger->error($message = "message");

创建并注册ChannelHandler

use Terrazza\Component\Logger\Logger;

$logger           = new Logger("loggerName", ["pid" => getmypid()]);
$logger           = $logger->registerChannelHandler($channelHandler);
$logger->error($message = "message");

Logger的使用

产生一个NOTICE

$logger->notice("myMessage");
// by using our examples above this message will not be printed
// ...cause the logLevel for the Handler is WARNING

产生一个ERROR

$logger->error("myMessage"); 
// output to file will be: 400-myMessage-myPID