terrazza / logger
Requires
- php: >=7.4
- psr/log: 1.1.*
Requires (Dev)
- phpunit/phpunit: ^9.3
- vimeo/psalm: 4.x-dev
README
此组件是PSR/Log标准的实现,并添加了一些扩展。
结构
- Logger组件
需要用0-n个ChannelHandler初始化
并提供了常见的方法- 警告
- 错误
- 通知
- ...
- ChannelHandler组件
负责确定- 目标/写入者(必需)
- 记录格式化器(必需)
- 通道过滤器(可选)
- 此通道的所有相关LogHandler
- LogHandler组件
确定- 日志级别
- 格式(可选,默认:从ChannelHandler->recordFormatter获取)
与常见的PSR/Log实现相比,Terrazza/Logger组件在处理"格式"方面有所不同。Writer组件处理多个"行",并将它们合并。在这个差异中,可以转发转换后的格式并保持每行的键。
例如:将消息/格式写入JSON对象或数据库。
对象/类
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)
转换器
首先,将LogRecord与RecordFormatter进行转换/映射
之后,根据目标/写入器的不同,数组需要再次进行格式化。
我们实际上包含了/提供了两个转换器
- 转换为字符串,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