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

标签 php oop logging

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

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

例如:

    Class A {

        public function f_A {
            log_to_file($message);
        }

    }

    Class B {

        public function f_B {
            log_to_file($message);
        }

    }

如果有任何提示,我将不胜感激。我想实现一些简单而优雅的解决方案。

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

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."\n", 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);

最佳答案

记录器用在什么地方?

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

  • 侵入式日志记录:

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

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

  • 非侵入式日志记录:

    使用此方法而不是更改您希望记录的类,您将现有实例包装在一个容器中,该容器允许您跟踪实例和应用程序其余部分之间的每次交换。

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

实现侵入式记录器

为此,您可以使用两种主要方法。您可以注入(inject)一个实现 Logger 接口(interface)的实例,或者为该类提供一个工厂,该工厂仅在必要时才初始化日志系统。

Note:
Since it seems that direct injection is not some hidden mystery for you, I will leave that part out... only I would urge you to avoid using constants outside of a file where they have been defined.

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

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

class Foobar 
{
    private $loggerFactory;

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

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

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

  • 它可以确保只创建一个实例而不需要全局状态
  • 提供编写单元测试时使用的接缝

Note:
Actually, when written this way the class Foobar only depends on an instance that implements the Creator interface. Usually you will inject either a builder (if you need to type of instance, probably with some setting) or a factory (if you want to create different instance with same interface).

下一步将是工厂的实现:

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'); 时,工厂会查找实例是否已创建。如果搜索失败,它会创建一个新实例。

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 中的工作原理,但无论如何您都必须学习它们。

实现非侵入式日志记录

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

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;
        }
    }
}

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

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

$instance = new Foobar;

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

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

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

关于PHP - 实现日志记录机制以在多个类中归档,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18673941/

相关文章:

php - 交响乐 4 : Var-dumper not working properly

c# - 如何访问单例类的静态方法?

iOS 8 在不安装 Xcode 的情况下从用户设备读取控制台日志

python - Django 设置默认日志记录

java - Log4J - JsonLayout 和 RollingFileAppender 生成无效的 JSON

带有字符列表的 PHP ltrim 行为

php - SQL查询插入两次

php - 使用 session 变量的 CodeIgniter 表单验证

php - 使用 PHP 维护不同页面的对象状态

java - 责任链模式——与后代紧密耦合