PHP-实现日志记录机制以在多个类中进行归档 [英] PHP - Implement logging mechanism to file in several classes

查看:87
本文介绍了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."\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);

推荐答案

在哪里使用记录仪?

通常,在代码中有两个主要的记录器使用案例:

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-实现日志记录机制以在多个类中进行归档的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆