Core points
log()
method that can receive any severity levels. Its design is to solve the problem of log implementation incompatibility. AbstractLogger
class provided in the Psr/Log library. In PHP development, logging is one of the most common tasks. We use logs to track error messages, log important events, and debug code issues. In any PHP project, the code may be filled with calls to log libraries that handle these operations for us. Unfortunately, calls to log libraries are scattered throughout the code, which makes the code depend on the availability of the library, which is clearly contrary to the principle of dependency inversion. Even if we use dependency injection to let our objects access the log library, the difference between log libraries means switching between them can be difficult and time-consuming, requiring a major refactoring of the entire code library. To improve compatibility between log libraries, the PHP-FIG team recently released PSR-3, a common log object interface. In this article, I will discuss how the PSR-3 defined log interface allows us to write reusable code that does not depend on any particular log implementation.
PSR-3 Quick Start
Before we understand how PSR-3 makes our code more reusable, it is necessary to understand what PSR-3 is. If you are already familiar with PSR-3, you can skip this section. The core of the specification is the interface to log objects. This interface discloses eight ways to handle messages of different severity levels, and a common log()
method that can accept any severity levels. The eight severity levels supported by PSR-3 are based on RFC 5424, as described below:
emergency
– The system cannot be usedalert
– Action is required critical
– Serious situation error
– Errors that do not need immediate attention but should be monitoredwarning
– An unusual or undesirable event, but not an errornotice
– Normal but important eventsinfo
– Interesting Eventsdebug
– Details for debuggingEach log method accepts a message that must be a string or an object with a __toString()
method. Additional parameters accept an array that can provide context information for log messages. A complete description of these methods and parameters can be found in the PSR-3 specification.
Get PSR-3 file
Getting the files you need to use PSR-3 is easy - you can find them in the Psr/Log GitHub repository. You can also use Composer to get these files from Packagist. Here is an example of a composer.json
file for retrieving Psr/Log files:
{ "require": { "psr/log": "dev-master" } }
How to limit code reuse of logging
PHP has many different log libraries, each with its own way of collecting and recording data. While they have some commonalities, each library has its own unique set of logging methods. This means switching between logs can be challenging, and often requires changing the code where you use logging. This runs contrary to the SOLID principle of code reuse and object-oriented design. The situation we face is that either declare dependencies on specific log libraries or avoid logging altogether. To illustrate this issue more clearly, a specific example is needed. Suppose we are creating a simple Mailer object to handle sending emails. We want Mailer to log a message every time we send an email, and we decided to use the excellent Monolog library to handle our logging needs.
<?php namespace Email; class Mailer { private $logger; public function __construct($logger) { $this->logger = $logger; } public function sendEmail($emailAddress) { // 发送电子邮件的代码... // 记录消息 $this->logger->addInfo("Email sent to $emailAddress"); } }
We can use this class with the following code:
<?php // 创建一个Monolog对象 $logger = new Monolog\Logger("Mail"); $logger->pushHandler(new Monolog\Handler\StreamHandler("mail.log")); // 创建邮件发送器并发送电子邮件 $mailer = new Email\Mailer($logger); $mailer->sendEmail("email@example.com");
Running this code will create a new entry in the mail.log
file, recording the email sent. At this point, we might think that we have written a reusable Mailer object. We use dependency injection to make the logger available for Mailer, so we can swap different logger configurations without touching our Mailer code. It looks like we have successfully followed the SOLID principle and avoided creating any hard dependencies. But suppose we want to reuse the Mailer class in different projects using Analog to handle logging interactions. Now we have a problem because Analog does not have a addInfo()
method. To record information-level messages using Analog, we call Analog::log($message, Analog::INFO)
. We can modify the Mailer class to use the Analog method as shown below.
<?php namespace Email; class Mailer { public function sendEmail($emailAddress) { // 发送电子邮件的代码... // 记录消息 Analog::log("Email sent to $emailAddress", Analog::INFO); } }
We can use the updated Mailer class with the following code:
{ "require": { "psr/log": "dev-master" } }
While this will work, it is far from ideal. We encountered Mailer's dependency on a specific logging implementation, which requires changing the class when introducing a new logger. This makes the class less reusable and forces us to choose between relying on the availability of a particular logger or abandoning logging in the class altogether.
Use PSR-3 to avoid logger dependencies
As Alejandro Gervasio explains in his excellent article on the topic, the principle of dependency inversion tells us that we should rely on abstraction rather than concrete implementations. In the case of logging, our current problem has been the lack of a suitable abstraction that can be relied on. This is where PSR-3 comes into play. PSR-3 is designed to overcome the incompatibility of logging implementation by providing a common interface for the logger (properly named LoggerInterface
). By providing an interface that is not bound to any specific implementation, PSR-3 allows us to avoid relying on a specific logger - we can instead type prompt for LoggerInterface
to get a PSR-3-compliant logger. I've updated the following Mailer class to demonstrate this:
<?php namespace Email; class Mailer { private $logger; public function __construct($logger) { $this->logger = $logger; } public function sendEmail($emailAddress) { // 发送电子邮件的代码... // 记录消息 $this->logger->addInfo("Email sent to $emailAddress"); } }
constructor has been modified to accept the LoggerInterface
implementer, and the sendEmail()
method now calls the info()
method specified in PSR-3. Monolog is already PSR-3 compliant, and Analog provides a wrapper object that implements LoggerInterface
, so we can now use these two loggers without modifying the Mailer class. Here is how to call this class using Monolog:
<?php // 创建一个Monolog对象 $logger = new Monolog\Logger("Mail"); $logger->pushHandler(new Monolog\Handler\StreamHandler("mail.log")); // 创建邮件发送器并发送电子邮件 $mailer = new Email\Mailer($logger); $mailer->sendEmail("email@example.com");
and use Analog:
<?php namespace Email; class Mailer { public function sendEmail($emailAddress) { // 发送电子邮件的代码... // 记录消息 Analog::log("Email sent to $emailAddress", Analog::INFO); } }
Now we are able to use our Mailer object with any library without editing the Mailer class or changing the way we use it.
Use adapter mode for loggers that do not support PSR-3
So far, we have successfully decoupled the Mailer object from any specific logging implementation through the implementer requesting LoggerInterface
. But what about those loggers that have not yet been added for PSR-3 support? For example, the popular KLogger library has not been updated for a while and is currently incompatible with PSR-3. Fortunately, we can easily map the methods exposed by KLogger to those defined in LoggerInterface
by leveraging the adapter pattern. Supported files in the Psr/Log repository enable us to easily create adapter classes by providing a AbstractLogger
class that we can extend. An abstract class simply forwards eight level-specific log methods defined in LoggerInterface
to a common log()
method. By extending the AbstractLogger
class and defining our own log()
method, we can easily create PSR-3-compliant adapters for loggers that do not natively support PSR-3. I'll demonstrate this below by creating a simple adapter for KLogger:
{ "require": { "psr/log": "dev-master" } }
log()
method simply maps the LoggerInterface
method to the respective KLogger method, and the KLogger handles the actual logging activity. By wrapping the KLogger class this way, we are able to use it without breaking the LoggerInterface
contract. We can now use the KLogger adapter with the Mailer class:
<?php namespace Email; class Mailer { private $logger; public function __construct($logger) { $this->logger = $logger; } public function sendEmail($emailAddress) { // 发送电子邮件的代码... // 记录消息 $this->logger->addInfo("Email sent to $emailAddress"); } }
With the adapter class, we are able to use KLogger without modifying the Mailer class and still adhere to LoggerInterface
. KLogger does not accept the second parameter of debug level messages, so it does not fully comply with PSR-3 even with an adapter. Extending KLogger to make it fully compatible with PSR-3 would be a trivial task, but that's beyond the scope of this article. However, it is safe to say that using our adapter class makes us very close to being fully PSR-3 compliant and allows us to use LoggerInterface
with the KLogger class.
Conclusion
In this article, we have learned how to use PSR-3 to help us write logger-free code that does not depend on a specific logging implementation. Many major PHP projects have added support for PSR-3, including Monolog, Symfony, and Mustache.php, as well as other well-known projects like Drupal are discussing how to best integrate it. Since PSR-3 reduces the barriers to code reuse, we should see more libraries and frameworks correctly use logging to provide useful information for developers. Will PSR-3 affect how you use logging in your application? Please let us know in the comments section below.
(Picture from Fotolia)
(The FAQ part of PSR-3 logging is omitted here due to space limitations. It can be added as needed.)
The above is the detailed content of PHP Master | Logging with PSR-3 to Improve Reusability. For more information, please follow other related articles on the PHP Chinese website!