Zend框架应用程序中的日志记录设计模式


Logging design pattern in a Zend framework application

我正在使用Zend Framework构建一个应用程序。应用程序需要对代码中的每个操作或函数进行密集的日志记录。

所以我的代码大多数时候看起来是这样的:

function SendMailsAction(){
      $logger->log('Started sending mails.')
...
...
...Some Code...
...
...
foreach ($mails as $mail){
  try{      
       $logger->log('Trying to send')
       $mail->send()
       $logger->log('Mail sent successfully.')
  }catch(Exception $e){
       $logger->log('Failed to send mail.')
      }      
 }
...
...
...Some Code...
...
...
       $logger->log('Finished sending mails.')
}

有时我甚至必须登录2个表,所以大多数日志记录代码都加倍了,函数开始变得复杂和冗长。

我使用Zend框架的Zend_Log进行日志记录,所以我的问题不是日志记录类本身,而是如何将日志记录代码与代码功能本身分离,并保持关注点的分离。

有些人建议使用面向方面编程(AOP),但不幸的是,AOP for PHP对我的客户来说是不可接受的,所以我正在寻找一个面向对象的解决方案或最佳实践。

注:

为了说明问题,我的问题不是如何使用Zend_Log,而是如何将日志记录添加到我的应用程序代码中。

有时我甚至必须登录2个表,所以大多数日志记录代码都加倍了,函数开始变得复杂和冗长。

时间会很长。如果你的代码进行了大量的日志记录,它将很长,因为它必须进行日志记录,并且它记录的每一行操作都意味着你的代码中有一行。然而,在任何情况下,它都不应该很复杂,因为登录是你能做的最简单的事情之一。让我担心的是,你提到"有时我甚至必须登录两个表"。在我的书中,一张、两张、五张、六万张或一千张表由一行执行。每个记录器的代码不会加倍。如果您正在复制粘贴一行,并将$log更改为$log2,那么您显然做错了(tm)。

有些人建议使用面向方面编程(AOP),但不幸的是,AOP for PHP对我的客户来说是不可接受的,所以我正在寻找一个面向对象的解决方案或最佳实践。

这很好,AOP。不过,它也有缺点;与debugbacktrace方法一样,性能会受到很大的影响。此外,代码变得越来越"神奇",因为当你查看代码本身时,它会做一些不清楚的事情。这会增加调试应用程序的时间。

我的0.02美元?首先,不要重复:每个操作一个日志条目就足够了。使用可以在运行时附加到某些类的灵活记录器。根据"严重性"或"类型",决定是否实际在记录器中记录消息。总而言之,只需实现观察者模式:

<?php
namespace Foo;
class MailService {
    public function attach( Observes $observer ) {
        $this->observers[] = $observer;
    }
    public function notify( $message, $type = 'notice' ) {
        foreach( $this->observers as $observer ) {
            $observer->notify( $message, $type );
        }
    }
    public function sendMail( ) {
        $this->notify( 'Started sending mails', 'debug' );
        $mails = array( );
        foreach( $mails as $mail ) {
            try {
                $this->notify( 'Trying to send', 'debug' );
                $mail->send( );
                $this->notify( 'Mail sent succesfully', 'debug' );
            }
            catch( Exception $e ) {
                $this->notify( 'Failed to send mail', 'notice' );
            }
        }
        $this->notify( 'Finished sending mail', 'debug' );
    }
}
interface Observes {
    public function notify( $message, $type = 'notice' );
}
abstract class Logger implements Observes {
    protected $types = array(
        'debug' => 0,
        'notice' => 1,
        'warning' => 2,
        'error' => 3
    );
    protected function code( $type ) {
        return isset( $this->types[$type] ) ? $this->types[$type] : 0;
    }
}
class FileLogger extends Logger implements Observes {
    public function __construct( $filename ) {
        $this->filename = $filename;
    }
    /**
     * @todo replace the method body with a call to, say, file_put_contents.
     */
    public function notify( $message, $type = 'notice' ) {
        if( $this->code( $type ) > $this->code( 'notice' ) ) { // only for warning and error.
            echo $message . "'n";
        }
    }

}
class DebugLogger extends Logger implements Observes {
    public function notify( $message, $type = 'notice' ) {
        if( $this->code( $type ) === $this->code( 'debug' ) ) { // only show "debug" notices.
            echo $message . "'n";
        }
    }
}

$service = new MailService( );
$service->attach( new FileLogger( 'yourlog.txt' ) );
$service->attach( new DebugLogger( ) );
$service->sendMail( );

如果您不想使用任何外部工具,可以围绕debug_backtrace编写某种观测器包装器,该包装器在回溯中循环,并将所有函数调用与包装器提供的数组映射进行比较,如果命中,则使用自定义消息文本编写相应的日志消息。这将是一个完全的代码分离,您只需要在每个脚本的末尾运行这个观察者类。

作为一个例子,我认为你所需要的只是PHP手册的例子。尽管如此,这里还是有一些伪代码来说明我的意思:

//use register_shutdown_function to register your logger function
function scanTrace(Zend_Log $logger, array $eventMap)
{
    $trace = array_reverse(debug_backtrace());
    foreach ($trace as $step)
    {
        //1. extract the needed info
        //2. check if the event is in your eventMap
        //3. if yes, log it
    }
}

eventMap应该已经包含要为每个事件记录的消息。

如果你不介意使用外部工具(我认为这是更好的选择),那么你可以使用xdebug和WebGrind或类似的工具。

顺便说一句:你可能对monitorix感兴趣,它是Zend_Log的扩展,在数据库表中有很多不错的自动日志记录(比如慢速查询日志记录、php错误和异常日志记录、javascript错误日志记录)。

我在Go的帮助下登录了我的Zend2服务!AOP PHP库。它足够快,并允许我在开发模式下使用XDebug调试原始源代码。然而,这只是测试版,请注意!

use Go'Aop'Aspect;
use Go'Aop'Intercept'MethodInvocation;
use Go'Lang'Annotation'After;
use Go'Lang'Annotation'AfterThrowing;
use Go'Lang'Annotation'Before;
use Go'Lang'Annotation'Around;
/**
 * Logging aspect
 */
class LoggingAspect implements Aspect
{
    /**
     * @var Zend'Log'Logger
     */
    protected $logger = null;
    /**
     * Constructs a logging aspect
     */
    public function __construct()
    {
        $logger = new Zend'Log'Logger;
        $writer = new Zend'Log'Writer'Stream('php://output');
        $logger->addWriter($writer);
        $this->logger = $logger;
    }
    /**
     * Method that will be called before real method
     *
     * @param MethodInvocation $invocation Invocation
     * @Before("execution(public ClassName->*(*))")
     */
    public function beforeMethodExecution(MethodInvocation $invocation)
    {
        $msg = 'Before: '. $this->formatMessage($invocation);
        $this->logger->log(Zend'Log'Logger::INFO, $msg);
    }
    /**
     * Method that will be called after throwing an exception in the real method
     *
     * @param MethodInvocation $invocation Invocation
     * @AfterThrowing("execution(public ClassName->*(*))")
     */
    public function afterThrowingMethodExecution(MethodInvocation $invocation)
    {
        $msg = 'After throwing: '. $this->formatMessage($invocation);
        $this->logger->log(Zend'Log'Logger::ERR, $msg);
    }
    /**
     * Format a message from invocation
     *
     * @param MethodInvocation $invocation
     * @return string
     */
    protected function formatMessage(MethodInvocation $invocation)
    {
        $obj = $invocation->getThis();
        return is_object($obj) ? get_class($obj) : $obj .
            $invocation->getMethod()->isStatic() ? '::' : '->' .
            $invocation->getMethod()->getName() .
            '()' .
            ' with arguments: ' .
            json_encode($invocation->getArguments()) .
            PHP_EOL;
    }
}

```

您知道zend日志可以有多个编写器http://framework.zend.com/manual/en/zend.log.writers.html#zend.log.writers.compositing

创建一个类单功能很简单

class logger {
  public function log ($value , $type  , $bothlogger = false){
      $writer1 = new Zend_Log_Writer_Stream('/path/to/first/logfile');
      $writer2 = new Zend_Log_Writer_Stream('/path/to/second/logfile');
      $logger = new Zend_Log();
      $logger->addWriter($writer1);
      if($bothlogger === true){
        $logger->addWriter($writer2);
      }
      // goes to both writers
       $logger->info('Informational message');
       return true;
  }
}

当然,您可以通过多种方式将此示例修改得更快,但它应该解释

的想法