【问题标题】:PHP - Implement logging mechanism to file in several classesPHP - 在多个类中实现日志记录机制
【发布时间】:2013-09-11 12:11:47
【问题描述】:

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

【问题讨论】:

  • 如果您发现以下答案之一有帮助,您应该点赞并接受它

标签: php oop logging


【解决方案1】:

记录器在哪里使用?

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

  • 侵入式记录:

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

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

  • 非侵入式日志记录:

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

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

实施侵入式记录器

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

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

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

【讨论】:

  • 以前从未听说过或见过非侵入式日志记录……但我喜欢这种抽象。可以记录 Foobar 的方式,但在任何情况下都可以使用任何记录代码!太棒了!
  • 简单用例:读取 config.ini,有些值超出范围,对于它们,我将退回到默认值,但我仍然想记录无效值。这种“调试”在现实生活中是需要的。这怎么能以非侵入性的方式完成?
  • PSR-3 不仅没有意义和误导,而且与这个答案不兼容
  • @Ziumin .. 好吧......我想我必须详细说明。 PSR(PHP Specification Request)用词不当,因为它是由 PHP-FIG(Framework Interoperability Group)生产的。他们的目标是为属于该组的框架创建通用实践(应用程序结构、接口、编码标准)。 PSR-3 本身是不一致的:它将动词(debug、log)、形容词(critical)和名词(emergency、alert、notice)定义为方法名称。基本上,整个事情看起来很匆忙,可能会有更新(就像 PSR-0 发生的那样,他们试图将其推入 PHP5.5 核心)。
  • @user3225313 感谢您提供这么棒的意见,但您要么不知道 DI 是什么,要么不了解 java 中的记录器是如何工作的。只有不使用上述两种方法之一的 java 记录器才使用静态类。但是话又说回来......你可能甚至不明白为什么使用静态类是一件坏事。
【解决方案2】:

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

【讨论】:

  • 我会这样做 - 请参阅我的问题的编辑。我认为它将来会是可重用和可扩展的,而且你的编写方式很重要。
【解决方案3】:

如果您想要一个完整的日志框架,支持日志到不同的输出,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);

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

【讨论】:

  • 那么,您更喜欢全局函数吗?或者每次你想使用它时都用配置实例化记录器类?
  • 你听说过依赖注入吗?
  • 我在考虑。我会这样做 - 请参阅我的问题的编辑。 To tereško:我的解决方案使用依赖注入。谢谢。
  • ibm.com/developerworks/library/co-single 我不确定 PHP 世界中的谁,尽管 DI 是做事的唯一方法,但它非常短视。记录器是使用单例来减少复杂代码并使您的应用程序更高效的最佳场所的完美示例。难怪我能够如此轻松地提高 PHP 应用程序的速度,每个人都被困在只有一种做事方式的无知中。

    猜猜PHP的创建者说框架不好,但他一定是个白痴吧?

  • 对不起,之前的帖子太丑了,我正在试图找出网站上的 cmets。
猜你喜欢
  • 1970-01-01
  • 2019-03-25
  • 1970-01-01
  • 1970-01-01
  • 2020-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多