8

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

    }

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

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

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);
4

3 回答 3

23

记录器在哪里使用?

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

  • 侵入式记录:

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

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

  • 非侵入式日志记录:

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

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

实现侵入式记录器

为此,您有两种主要方法可用。您可以注入一个实现该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 .. );
        ....
    }
    ....
}

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

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

注意:
实际上,以这种方式编写时,类 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');时,工厂会查找实例是否已创建。如果搜索失败,它会创建一个新实例。

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

最后一步实际上是与提供者连接起来

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

实施非侵入式日志记录

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

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();

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

如果在某个时候您决定删除日志记录,那么它可以在不重写应用程序的其余部分的情况下完成。

于 2013-09-08T10:52:55.343 回答
1

Logger 的目标是保存调试信息。记录器必须是具有接口的类来存储消息和遇险级别。实施是次要的。今天你想要文件记录。明天你可能想把日志放到数据库中。所以这个逻辑必须写在记录器类端。已经有一个很好的记录器叫做 Monolog https://github.com/Seldaek/monolog

于 2013-09-07T14:56:13.183 回答
0

如果你想要一个完整的日志框架,支持日志到不同的输出,log4PHP 是一个开源解决方案。

如果您现在想要一个适合您需求的小型实现,那么应该这样做

class Logger
{
    const INFO = 'info';
    const ERROR = 'error';

    private static $instance;
    private $config = array();

    private function __construct()
    {
        $this->config = require "/path/to/config.php";
    }

    private static function getInstance()
    {
        if(!self::$instance)
        {
            self::$instance = new Logger();
        }
        return self::$instance;
    }

    private function writeToFile($message)
    {
        file_put_contents($this->config['log_file'], "$message\n", FILE_APPEND);
    }

    public static function log($message, $level = Logger::INFO)
    {
        $date = date('Y-m-d H:i:s');
        $severity = "[$level]";
        $message = "$date $severity ::$message";
        self::getInstance()->writeToFile($message);
    }
}

//config.php
return array(
    'log_file' => '/tmp/my_log.txt'
);

Logger::log($message);

未经测试,但应该可以工作。

于 2013-09-07T15:07:29.120 回答