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

时间:2013-09-07 13:43:48

标签: php oop logging

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

  1. 日志文件路径将在配置文件config.php
  2. 在几个类中我想将一些事件记录到日志文件中
  3. 例如:

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

3 个答案:

答案 0 :(得分:18)

使用记录器的位置是什么?

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

  • 侵入式日志记录:

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

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

  • 非侵入式伐木:

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

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

实施侵入式记录器

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

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

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

答案 1 :(得分:1)

Logger的目标是保存调试信息。 记录器必须是具有存储消息和遇险级别的接口的类。 实施是次要的。今天你想要文件记录。明天您可能希望将日志放入数据库。所以逻辑必须写在logger类的一面。 已有一个名为Monolog https://github.com/Seldaek/monolog

的漂亮记录器

答案 2 :(得分:1)

如果您需要完整的日志记录框架,并且支持记录到不同的输出,则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);

未经测试,但应该可以使用。