2012-02-16 20 views
9

Estoy creando una aplicación usando Zend Framework. La aplicación requiere un registro intensivo para cada acción o función en el código.Patrón de diseño de registro en una aplicación Zend Framework

por lo que mi código de la mayor parte del tiempo se parece a esto:

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.') 
} 

A veces incluso tengo que entrar 2 mesas, por lo que la mayor parte del código de registro se duplica y las funciones comienzan a complicarse y largo.

Uso Zend framework's Zend_Log para el registro, así que mi problema no es la clase de registro en sí, sino cómo separar el código de registro de la funcionalidad del código y mantener la separación de las preocupaciones.

Algunas personas sugirieron la Programación Orientada a Aspectos (AOP), pero desafortunadamente AOP para PHP no es aceptable para mi cliente, por lo que estoy buscando una solución Orientada a Objetos o la mejor práctica.

Nota:

Sólo para dejar las cosas claras mi problema no es cómo utilizar Zend_Log, pero la forma de añadir el registro a mi código de la aplicación en general.

+0

¿Qué le parece el código debe ser similar? ¿Hay algún codigo deseable? – akond

+0

@akond bien Estaba pensando en "adjuntar" funciones de registro a ciertas funciones en los Modelos o Controladores. algo así como registrar funciones para ejecutar en ciertos puntos en el código. Si algo de esto es incluso aplicable en PHP, por supuesto. – Songo

+0

Buena pregunta y creo que ZF2 manejará este tipo de problema mucho más elegantemente con [EventManager] (http://mwop.net/blog/266-Using-the-ZF2-EventManager#toc_1.6). –

Respuesta

5

A veces incluso tengo que iniciar sesión en 2 tablas, por lo que la mayor parte del código de registro se duplica y las funciones comienzan a ser complicadas y largas.

Va a ser largo. Si su código registra mucho, será largo, ya que tendrá que iniciar sesión, y cada acción de línea que registre significará que hay una línea en su código.Sin embargo, no debería ser complicado en ningún caso, ya que el registro es una de las cosas más sencillas que puede hacer. Lo que me preocupa es que menciones "a veces incluso tengo que iniciar sesión en 2 tablas". En mi libro, una, dos, cinco, sesenta o mil tablas se realiza por una línea. El código no se duplica para cada registrador. Si está copiando y pegando una línea y cambiando $log a $log2, claramente lo está haciendo mal (tm).

Algunas personas sugirieron Programación Orientada a Aspectos (AOP), pero, por desgracia AOP para PHP no es aceptable para mi cliente, así que estoy buscando una solución orientada a objetos o de las mejores prácticas.

Es agradable, AOP. Sin embargo, tiene inconvenientes; como con el enfoque debug_backtrace, hay un gran golpe de rendimiento. Eso, además de que el código se vuelve cada vez más "mágico", ya que hace cosas que no están claras cuando estás mirando el código en sí. Eso aumenta el tiempo de depuración de su aplicación.

My $ 0.02? En primer lugar, no se repita: una entrada de registro por acción debería ser suficiente. Use registradores flexibles que se pueden unir a ciertas clases en tiempo de ejecución. Decida si en realidad registra el mensaje en el registrador, según "gravedad" o "tipo". Con todo, simplemente implementar el patrón Observador:

<?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(); 
+0

+1 para el código de trabajo y una gran explicación.Sin embargo, me preguntaba, ¿por qué hiciste que la clase abstracta implementara la interfaz en 'clase abstracta Logger implementa Observes'? – Songo

+0

@Songo Principalmente porque ya escribí la interfaz, luego encontré que ambos tienen un comportamiento común que podría resumirse fácilmente en la clase principal. Pero, si alguna vez escribe un Observer que no registra cosas, puede encontrar la interfaz útil de todos modos. –

1

que saber que registro Zend puede tener más de un escritor http://framework.zend.com/manual/en/zend.log.writers.html#zend.log.writers.compositing

su simple como crear una clase única función

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

} 

por supuesto, puede modificar este ejemplo para ser más rápido con muchas maneras, pero debería explicar la idea

+0

En realidad, mi problema no es utilizar la clase 'Zend_Log'. Se trata de mantener mi código limpio y mantener la separación de preocupaciones en las clases utilizadas. – Songo

+0

+1 por recordarme que podría adjuntar más de un escritor al registro. :) – Songo

+0

Noté que ha instanciado el registrador dentro de una función. Intenté hacer esto en el alcance de clase y falló. ¿Esto es normal? ¿Inventario para cada función un nuevo registrador + escritor? – Erik

4

Si no desea utilizar ninguna herramienta externa, puede escribir una especie de envoltura de observador alrededor de debug_backtrace que recorre la traza inversa y compara todas las llamadas de función a un arra y el mapa provisto por el contenedor y si es un golpe, escribe el mensaje de registro respectivo con su texto de mensaje personalizado. Esta sería una separación completa del código donde solo necesitarías ejecutar esta clase de observadores al final de cada script.

Como un ejemplo, creo que todo lo que necesita es en los ejemplos del manual de PHP. Aún así, he aquí algo de pseudo-código para iluminar lo que quiero decir:

//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 
    } 
} 

El eventmap ya debería contener el mensaje que desea iniciar sesión para cada evento.

Si no te importa utilizar una herramienta externa (que creo que es la mejor opción) entonces puedes trabajar con xdebug y WebGrind o similar.

Btw: Puede que le interese monitorix, que es una extensión de Zend_Log con un montón de buen registro automatizado en una tabla db (como registro lento de consultas, error de php y registro de excepciones, registro de errores de JavaScript).

+0

¿Podría explicarme más sobre la primera parte en su respuesta ?. ¿Podría publicar algunos ejemplos de código sobre el uso de 'debug_bactrace', por favor? – Songo

+0

+1 para monitorix, parece ser bueno – tawfekov

+1

He actualizado la respuesta para darle un ejemplo de lo que quiero decir. – markus

2

tengo a mi servicio de registro Zend2 con la ayuda de mi biblioteca Go! AOP PHP. Es lo suficientemente rápido y me permite depurar el código fuente original con XDebug en modo de desarrollo. Sin embargo, es solo beta, ¡ten en cuenta!

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

`` `

+0

+1 que parece muy prometedor. – Songo

+0

Enlace al sitio oficial: http://go.aopphp.com/ – lisachenko

Cuestiones relacionadas