我正在使用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 GoAopAspect;
use GoAopInterceptMethodInvocation;
use GoLangAnnotationAfter;
use GoLangAnnotationAfterThrowing;
use GoLangAnnotationBefore;
use GoLangAnnotationAround;
/**
* Logging aspect
*/
class LoggingAspect implements Aspect
{
/**
* @var ZendLogLogger
*/
protected $logger = null;
/**
* Constructs a logging aspect
*/
public function __construct()
{
$logger = new ZendLogLogger;
$writer = new ZendLogWriterStream('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(ZendLogLogger::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(ZendLogLogger::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;
}
}
当然,您可以通过多种方式将此示例修改得更快,但它应该解释