PHP - 将记录机制实现为几个类中的文件

teo*_*teo 6 php oop logging

我想在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);
        }

    }
Run Code Online (Sandbox Code Playgroud)

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

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

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);
Run Code Online (Sandbox Code Playgroud)

ter*_*ško 20

记录器在哪里使用?

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

  • 侵入式伐木:

    在大多数情况下,人们使用这种方法,因为它是最容易理解的.

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

  • 非侵入式伐木:

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

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

实施侵入式记录器

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

注意:
因为看起来直接注入对你来说不是一个隐藏的秘密,我会把那部分留下来...只有我会敦促你避免在已经定义它们的文件之外使用常量.

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

首先定义您将使用的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 .. );
        ....
    }
    ....
}
Run Code Online (Sandbox Code Playgroud)

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

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

注意:
实际上,当以这种方式编写时,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];
    }

}
Run Code Online (Sandbox Code Playgroud)

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

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

而最后一步实际上是接线这一切与提供:

$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 );
Run Code Online (Sandbox Code Playgroud)

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

实施非侵入式日志记录

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

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

该类也可以与上述惰性工厂一起使用.

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

$instance = new Foobar;

$instance = new LogBrane( $instance, $logger );
$instance->someMethod();
Run Code Online (Sandbox Code Playgroud)

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

如果在某些时候您决定删除日志记录,则可以在不重写其余应用程序的情况下完成.

  • 从未听过或看过非侵入式伐木......但我喜欢这种抽象.你可以记录Foobar的方式,但任何记录代码都在里面!辉煌! (5认同)
  • @ user3225313感谢这个伟大的输入,但你要么不知道DI是什么,或者你不明白java中的记录器实际上是如何工作的.只有java中没有使用上述两种方法之一的记录器才使用静态类.但话又说回来......你可能甚至不明白为什么使用静态类是一件坏事. (2认同)