swf / slf4php
PHP的SWF日志门面 - 受slf4j / logback / log4j Java项目启发
Requires
- php: >= 5.3.0
- monolog/monolog: 1.0.*
- psr/log: 1.0.*
Requires (Dev)
- behat/behat: 3.0.*
- phpunit/phpunit: ~4.8
This package is not auto-updated.
Last update: 2024-09-13 16:16:02 UTC
README
这个项目是什么?
slf4php 类似于 slf4j(Java的简单日志门面)项目。
slf4php 不是一个日志实现!这是一个在现有日志框架(如 Monolog)之上的 抽象层,就像 slf4j 一样。
如果你没有添加(配置)任何日志实现,没问题!slf4php 默认使用 "无操作" 机制。在代码中使用门面时,没有任何变化。你可以在以后随时配置日志!
支持的日志实现
这个1.0版本通过提供 MonologProxyAppender
类,默认支持 Monolog 日志实现。
未来版本可能会支持更多。
概念 - 简而言之
我们有以下关键实体
-
追加器(Appenders)
您可以将Appender
视为一个输出通道。将某些内容以某种方式写入某处...(文件、数据库、电子邮件等)
这就是现有日志框架发挥作用的地方!本项目没有自己的Appender
实现。相反,它提供了一些 "连接器"(通常是代理/适配器设计模式),让您可以使用您喜欢的日志框架。 -
记录器(Loggers)
在您的代码中,您需要一个Logger
实例来记录消息。您可以在其上调用适当的日志方法(info()、debug()、warning() 等) - 如 PSR/log 中的LoggerInterface
定义。
记录器有 日志级别。并且...它们后面有 一个或多个追加器!当您使用记录器实例记录消息时,该消息将路由到记录器后面的所有追加器。因此,您的日志消息将被写入文件、数据库或通过电子邮件发送。或者全部! -
日志工厂(LoggerFactory)
我们有一个LoggerFactory
。您通过提供要记录的类的完全限定名称从工厂请求Logger
实例。您将得到一个Logger
实例 - 匹配您事先提供给LoggerFactory
的日志配置。
还有:配置
我们希望能够轻松地配置所有这些!为了实现这一点,最好的方式是拥有一个简单的(尽可能简单)配置文件。
快速看一下以下JSON配置,您将立即理解其概念
{ "appenders" : [ { "name" : "logFile", "builderClass" : "swf\\slf4php\\config\\builder\\monolog\\MonologProxyAppenderBuilder", "handlers" : [ { "builderClass" : "swf\\slf4php\\config\\builder\\monolog\\MonologStreamHandlerBuilder", "stream" : "${LOG_DIR}/application.log", "formatter": { "builderClass" : "swf\\slf4php\\config\\builder\\monolog\\MonologLineFormatterBuilder", "format" : "[%datetime%] %extra.loggerName%.%level_name%: %message% %context% %extra%\n\n" } } ] }, { "name" : "console", "builderClass" : "swf\\slf4php\\config\\builder\\monolog\\MonologProxyAppenderBuilder", "handlers" : [ { "builderClass" : "swf\\slf4php\\config\\builder\\monolog\\MonologStreamHandlerBuilder", "stream" : "php://stdout", "formatter": { "builderClass" : "swf\\slf4php\\config\\builder\\monolog\\MonologLineFormatterBuilder", "format" : "[%datetime%] %extra.loggerName%.%level_name%: %message% %context% %extra%\n\n" } } ] } ], "loggers" : [ { "name" : "your.namespace.A", "level" : "DEBUG", "appenders" : ["logFile", "console"] }, { "name" : "your.namespace.B", "level" : "INFO", "appenders" : "logFile" }, { "level" : "WARNING", "appenders" : "logFile" } ] }
使用上述简单配置,我们有
- 创建了两个追加器:一个日志文件和一个控制台。使用 Monolog 的
StreamHandler
- 为 "your.namespace.A" 创建了一个 DEBUG 级别的记录器,它使用日志文件追加器和控制台追加器
- 为 "your.namespace.B" 创建了一个 INFO 级别的记录器,它仅使用日志文件追加器
- 并且还使用日志文件追加器创建了一个警告级别的记录器。AND... 你可以注意到这个没有命名空间定义... 这意味着这是一个默认的记录器设置
访问我们的维基百科!有关配置日志的更多信息,请访问维基百科页面!
基本用法
假设你已经将上述配置保存在名为 log.config.json 的文件中,你可以这样配置LoggerFactory
<?php use swf\slf4php\LoggerFactory; use swf\slf4php\config\builder\LogConfigBuilder; use swf\util\JsonUtil; .... // you might use variables in the json file (unix like style) and we can pass an array to the json parsing mechanism // to let it resolve the vars $envVars = [ 'LOG_DIR' => '/var/log' ]; $loggerConfig = LogConfigBuilder::create()->initFromJson(JsonUtil::getJsonObjects("log.config.json"), $envVars)->build(); // and now we can init the factory LoggerFactory::init($loggerConfig);
一旦配置了LoggerFactory
,你就可以这样从工厂中获取Logger
实例
$myLogger = LoggerFactory::getLogger(YourClass::class); // the parameter is the fully qualified class name of YourClass $myLogger->info("This is a log message", []);
正如你可能已经猜到的,当你从工厂中获取记录器时,你会得到匹配的记录器!
在这种情况下
$logger = LoggerFactory::getLogger('\your\namespace\A\MyClass');
你会得到一个DEBUG级别的记录器实例,它后面有logFile和控制台追加器。
而在这个情况下
$logger = LoggerFactory::getLogger('JustAClass');
将是一个默认的记录器,匹配所以你会得到一个WARNING级别的记录器实例,后面跟着logFile追加器。
在日志消息中输出变量
我最喜欢的slf4j Java框架中的想法之一。
想象一下我们有一些复杂对象。在DEBUG级别,我们通常想要将这些对象输出到日志流中。但是当我们把日志级别改为更严格的级别时,如果我们仍然将这些对象输出到字符串(日志消息)中,然后因为日志级别而不扔掉它们... 好吧,这是一种浪费CPU功率和额外工作给垃圾收集器... 所以我们不应该这样做!
这里是一个不好的例子
// this might be a huge object: $jsonObj = json_decode($jsonString); ... // and this is a bad idea...: $myLogger->debug("Parsed json object is: " . print_r($jsonObj, true), []);
为什么这样做是个坏主意?因为无论如何都会构造一个String参数 - 使用内存和CPU功率。如果日志级别不是DEBUG,那么就毫无意义了...
slf4j为这个问题提供了一个优雅的解决方案 - 它使用Java中的"varargs"。这意味着一个方法可以获取任何数量的额外参数。实际上PHP也支持这个,所以我们也可以在这里使用它!
上面的例子中,这是推荐的做法
// this might be a huge object: $jsonObj = json_decode($jsonString); ... // do it this way instead: $myLogger->debug("Parsed json object is: {}", [], $jsonObj);
你可以在日志消息中添加{}
来定义占位符。然后你只需简单地将变量传递给日志方法调用。
你可以使用你想要的任何数量的占位符。传递给方法变量的数量应该与消息中使用的占位符数量相匹配!(当然,如果你传递的变量比占位符多或少,那不是什么大问题,但你的日志消息可能会看起来“奇怪”)
重要!你不应该在传递的对象上做任何转换!你应该以原样传递变量,并让slf4php在需要时进行字符串转换!
不要这样做
$jsonObj = json_decode($jsonString); ... // you shouldn't do string transformations like this $myLogger->debug("Parsed json object is: {}", [], print_r($jsonObj, true));
如果你不仅传递了简单类型(如布尔值、整数、字符串等),还传递了对象和/或数组,slf4php会以这种方式(在需要时)将它们转换为字符串
- NULL变量将作为字符串“NULL”
- 如果变量是数组,则将使用
print_r()
方法将其转换为字符串 - 如果变量是对象
- 如果它有一个
__toString()
方法,则将使用该方法 - 如果没有,则使用
print_r()
方法将对象转换为字符串
- 如果它有一个
这里还有一个例子
在类定义中推荐的使用方法
当你调用LoggerFactory::getLogger(<fully qualified class name>)
时,在幕后发生的事情是LoggerFactory
正在遍历配置的记录器,并尝试找到最佳匹配的记录器。如果你调用这个方法100次,它就会执行100次!所以你不应该这样做...
在Java中,典型的用法是以下代码(你可能可以读懂)
public class MyClass { // get the Logger instance only once / class private static final Logger LOG = LoggerFactory.getLogger(MyClass.class); ... public void aMethod() { LOG.info("a message..."); } }
很遗憾,我们无法在PHP中这样做,因为
- PHP不支持这种静态类变量的初始化方式.. :-(
- 而且,除此之外,在PHP中静态变量可能会在继承时让你感到困惑...
目前找到的最佳替代方案(到目前为止)如下
在你的类定义中
- 你声明一个受保护的静态变量 - 用于持有Logger实例的引用
- 你创建一个受保护的静态方法,例如命名为logger(),它从
LoggerFactory
获取并存储Logger实例 - 如果之前没有这样做。但请注意!你需要使用static::
关键字来引用受保护的静态变量,而不是self::
关键字!你可能需要PHP的“后期静态绑定”特性... - 当你想要记录一些内容时,通过调用(你第2步定义的)静态方法来获取Logger实例。当你扩展这个类时
- 确保在子类中重新定义受保护的静态变量 - 使用相同的名称。就像你在第1步中所做的那样。
以下代码示例展示了这一点
ClassA的定义
<?php namespace your\namespace; use swf\slf4php\LoggerFactory; class ClassA { protected static $_LOG; /** * @return \swf\slf4php\Logger */ protected static function logger() { if (is_null(static::$_LOG)) static::$_LOG = LoggerFactory::getLogger(static::class); return static::$_LOG; } public function logSomething() { self::logger()->info("a simple message...", []); } }
ClassA的子类定义
<?php // this can be on different namespace of course.. but this doesn't matter namespace different\namespace; use swf\slf4php\LoggerFactory; use your\namespace\ClassA; class ClassASubclass extends ClassA { // we need to override this! if we would not do this then we would inherit OR hijack the Logger instance // of our superclass... protected static $_LOG; public function anotherLog() { self::logger()->info("another message...", []); } }
说明
这将按预期工作!ClassASubclass
继承了名为logger()
的受保护的静态方法。但由于我们正在重写protected static $_LOG
字段,并且继承的logger()
方法使用了“后期静态绑定”,因此$_LOG
变量将在第一次在ClassASubclass
中使用logger()
方法时初始化。
访问我们的Wiki页面
有关更多信息/高级主题,请访问Wiki页面!