PHP - 在多个类中实现日志记录机制

时间:2023-01-21
本文介绍了PHP - 在多个类中实现日志记录机制的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在 PHP 中实现日志记录机制:

I would like to implement logging mechanism to file in PHP:

  1. 日志文件路径将在配置文件 config.php 中
  2. 在几个类中,我想将一些事件记录到日志文件中

例如:

    Class A {

        public function f_A {
            log_to_file($message);
        }

    }

    Class B {

        public function f_B {
            log_to_file($message);
        }

    }

我将非常感谢您提供任何提示.我想实现一些简单而优雅的解决方案.

I will be very grateful for any tips. I would like to implement some easy and elegant solution.

我正在考虑(谢谢你的回答),我想我会这样做(也许,有一些错误,我是从头开始写的):

I was thinking about it (thank you for your answers) and I think I will do it this way (maybe, there are some errors, I was writing it from scratch):

interface Logger {
    public function log_message($message);
}

class LoggerFile implements Logger {
    private $log_file;

public function __construct($log_file) {
    $this->log_file = $log_file;
}
public function log_message($message) {
        if (is_string($message)) {
            file_put_contents($this->log_file, date("Y-m-d H:i:s")." ".$message."
", FILE_APPEND);
        }
    }
}

//maybe in the future logging into database

class LoggerDb implements Logger {
    private $db;

    public function __construct($db) {
        //some code
    }
public function log_message($message) {
        //some code
    }
}

Class A {
    private $logger;

public function __construct(Logger $l) {
        $this->logger = $l;
    }


public function f_A {
    $this->logger->log_message($message);
}
}

Class B {
    private $logger;

public function __construct(Logger $l) {
        $this->logger = $l;
    }


public function f_B {
    $this->logger->log_message($message);
}
}

//usage:

//in config.php:

define("CONFIG_LOG_FILE", "log/app_log.log");

//in the index.php or some other files

$logger = new LoggerFile(CONFIG_LOG_FILE);

$instance_a = new A($logger);
$instance_b = new B($logger);

推荐答案

记录器在哪里使用?

一般来说,在代码中使用记录器有两个主要用例:

Where are loggers used?

In general there are two major use-cases for use of loggers within your code:

  • 侵入式日志记录:

  • invasive logging:

大多数人使用这种方法是因为它最容易理解.

For the most part people use this approach because it is the easiest to understand.

实际上,如果日志记录是域逻辑本身的一部分,您应该只使用侵入式日志记录.例如 - 在处理支付或敏感信息管理的课程中.

In reality you should only use invasive logging if logging is part of the domain logic itself. For example - in classes that deal with payments or management of sensitive information.

非侵入式日志记录:

使用此方法而不是更改您希望记录的类,您可以将现有实例包装在一个容器中,这样您就可以跟踪实例与应用程序其余部分之间的每次交换.

With this method instead of altering the class that you wish to log, you wrap an existing instance in a container that lets you track every exchange between instance and rest of application.

您还可以临时启用此类日志记录,同时在开发环境之外调试某些特定问题或对用户行为进行一些研究时.由于记录实例的类从未改变,与侵入式记录相比,破坏项目行为的风险要低得多.

You also gain the ability to enable such logging temporarily, while debugging some specific problem outside of the development environment or when you are conducting some research of user behaviour. Since the class of the logged instance is never altered, the risk of disrupting the project's behaviour is a lot lower when compared to invasive logging.

为此,您可以使用两种主要方法.您可以注入一个实现 Logger 接口的实例,也可以为类提供一个工厂,而工厂只会在必要时初始化日志系统.

To do this you have two main approaches available. You can either inject an instance that implements the Logger interface, or provide the class with a factory that in turn will initialize the logging system only when necessary.

注意:
由于似乎直接注入对您来说并不是什么隐藏的秘密,我将省略那部分...只是我会敦促您避免使用常量在已定义它们的文件之外.

现在.. 工厂和延迟加载的实现.

Now .. the implementation with factory and lazy loading.

您首先要定义您将使用的 API(在完美的世界中,您从单元测试开始).

You start by defining the API that you will use (in perfect world you start with unit-tests).

class Foobar 
{
    private $loggerFactory;

    public function __construct(Creator $loggerFactory, ....)
    {
        $this->loggerFactory = $loggerFactory;
        ....
    }
    .... 

    public function someLoggedMethod()
    {
        $logger = $this->loggerFactory->provide('simple');
        $logger->log( ... logged data .. );
        ....
    }
    ....
}

这个工厂将有两个额外的好处:

This factory will have two additional benefits:

  • 可以保证只创建一个实例,不需要全局状态
  • 在编写单元测试时提供一个接缝

注意:
实际上,当以这种方式编写时,类 Foobar 仅依赖于实现 Creator 接口的实例.通常你会注入一个构建器(如果你需要类型的实例,可能需要一些设置)或一个工厂(如果你想用相同的界面创建不同的实例).

下一步将是工厂的实施:

class LazyLoggerFactory implements Creator
{

    private $loggers = [];
    private $providers = [];

    public function addProvider($name, callable $provider)
    {
        $this->providers[$name] = $provider;
        return $this;
    }

    public function provide($name)
    {
        if (array_key_exists($name, $this->loggers) === false)
        {
            $this->loggers[$name] = call_user_func($this->providers[$name]);
        }
        return $this->loggers[$name];
    }

}

当您调用 $factory->provide('thing'); 时,工厂会查找实例是否已创建.如果搜索失败,它会创建一个新实例.

When you call $factory->provide('thing');, the factory looks up if the instance has already been created. If the search fails it creates a new instance.

注意:我实际上并不完全确定这是否可以称为工厂",因为实例化确实封装在匿名函数中.

Note: I am actually not entirely sure that this can be called "factory" since the instantiation is really encapsulated in the anonymous functions.

最后一步实际上是与供应商联系:

$config = include '/path/to/config/loggers.php';

$loggerFactory = new LazyLoggerFactory;
$loggerFactory->addProvider('simple', function() use ($config){
    $instance = new SimpleFileLogger($config['log_file']);
    return $instance;
});

/* 
$loggerFactory->addProvider('fake', function(){
    $instance = new NullLogger;
    return $instance;
});
*/

$test = new Foobar( $loggerFactory );

当然要完全理解这种方法,您必须知道闭包在 PHP 中的工作原理,但无论如何您都必须学习它们.

Of course to fully understand this approach you will have to know how closures work in PHP, but you will have to learn them anyway.

这种方法的核心思想是,不是注入记录器,而是将现有实例放入容器中,该容器充当所述实例和应用程序之间的隔膜.这种膜可以执行不同的任务,其中之一就是记录.

The core idea of this approach is that instead of injecting the logger, you put an existing instance in a container which acts as membrane between said instance and application. This membrane can then perform different tasks, one of those is logging.

class LogBrane
{
    protected $target = null;
    protected $logger = null;

    public function __construct( $target, Logger $logger )
    {
        $this->target = $target;
        $this->logger = $logger;
    }

    public function __call( $method, $arguments )
    {
        if ( method_exists( $this->target, $method ) === false )
        {
            // sometime you will want to log call of nonexistent method
        }

        try
        {
            $response = call_user_func_array( [$this->target, $method], 
                                              $arguments );

            // write log, if you want
            $this->logger->log(....);
        }
        catch (Exception $e)
        {
            // write log about exception 
            $this->logger->log(....);

            // and re-throw to not disrupt the behavior
            throw $e;
        }
    }
}

这个类也可以和上面描述的惰性工厂一起使用.

This class can also be used together with the above described lazy factory.

要使用此结构,您只需执行以下操作:

To use this structure, you simply do the following:

$instance = new Foobar;

$instance = new LogBrane( $instance, $logger );
$instance->someMethod();

此时包装实例的容器成为原始容器的全功能替代品.应用程序的其余部分可以像处理一个简单的对象一样处理它(传递、调用方法).并且包装的实例本身并不知道它正在被记录.

At this point the container which wraps the instance becomes a fully functional replacement of the original. The rest of your application can handle it as if it is a simple object (pass around, call methods upon). And the wrapped instance itself is not aware that it is being logged.

如果您决定在某个时候删除日志记录,则无需重写应用程序的其余部分即可完成.

And if at some point you decide to remove the logging then it can be done without rewriting the rest of your application.

这篇关于PHP - 在多个类中实现日志记录机制的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持html5模板网!

上一篇:在哪里可以找到 PHP 的错误日志文件? 下一篇:如何从 Laravel 控制器写入控制台?

相关文章

最新文章