本文大概是我在幾年前開(kāi)發(fā)個(gè)人項(xiàng)目時(shí)所做,如果有疏漏之處,請(qǐng)諸位補(bǔ)漏.
日志記錄對(duì)于應(yīng)用的維護(hù)特別是對(duì)于已部署到運(yùn)行環(huán)境之后的應(yīng)用調(diào)試都有著重要的意義乍丈。
對(duì)于一個(gè)應(yīng)用的日志系統(tǒng)而言,首先必須得有一個(gè)日志對(duì)象增蹭,該對(duì)象負(fù)責(zé)記錄日志信息启搂。同時(shí)該信息可以輸出到不同的位置渠驼,例如控制臺(tái)疫鹊,文件甚至網(wǎng)絡(luò)中杉武。對(duì)于信息的格式儡陨,則可以根據(jù)不同的需求褪子,可以輸出成普通文本,XML 或者 HTML 的格式骗村。同時(shí)還需要對(duì)日志信息進(jìn)行不同級(jí)別的分類嫌褪,這樣的好處是可以過(guò)濾冗余信息,只保留關(guān)鍵的日志胚股。對(duì)于一個(gè)日志框架而言笼痛,日志對(duì)象必須是可配置的,它可以按照配置來(lái)輸出到指定的目標(biāo)琅拌,同時(shí)按照配置來(lái)決定輸出的格式和決定何種級(jí)別以上的日志才能輸出缨伊。
在我成為PHP程序員后,我使用過(guò)許多的PHP框架,也使用過(guò)太多大同小易的日志類,抑或者自己實(shí)現(xiàn)一個(gè)日志類也是非常簡(jiǎn)單的,譬如
這個(gè) , 這個(gè) , 這個(gè)
這些看起來(lái)都能夠記錄日志,但是 這真的就是我們需要的日志功能么?
接著我去問(wèn)一個(gè)軟件測(cè)試人員(非程序員),你理解的日志功能到底有哪些?
他給我的答案,大致如下:
記錄信息: 能夠在一個(gè)地方查看輸出結(jié)果
分級(jí)輸出: 能夠過(guò)濾指定級(jí)別的日志記錄
格式輸出: 能夠以不同的形式來(lái)輸出,諸如 html,xml,txt等
報(bào)警提示: 錯(cuò)誤并不能每次都能檢測(cè)到,對(duì)于某些錯(cuò)誤應(yīng)該能夠提醒應(yīng)用維護(hù)人員
根據(jù)上述 4 條,其實(shí) 大部分框架中都基本實(shí)現(xiàn)了 1 - 3 這部分功能,比如
- 記錄信息到本地文件,SAE環(huán)境,等等
- 過(guò)濾特定級(jí)別信息
- 格式輸出,大部分使用場(chǎng)景都是 txt 格式的,擴(kuò)展其它樣式應(yīng)該也不難
對(duì)于 **報(bào)警 **這一項(xiàng)基本都沒(méi)有什么體現(xiàn),而這一點(diǎn)我思索之后覺(jué)得其實(shí)是很重要的一個(gè)環(huán)節(jié),拿我們?nèi)粘i_(kāi)發(fā)來(lái)講,假設(shè)此場(chǎng)景: 客服童鞋反映 一個(gè)線上bug突然出現(xiàn),請(qǐng)你趕緊解決.
我們的解決思路大概是這樣: 根據(jù)客服童鞋給的bug一些諸如截圖,訪問(wèn)地址之類的信息去重現(xiàn)這個(gè)bug,如果能夠重現(xiàn)成功,那么恭喜你;但是大部分線上bug很難重現(xiàn),或者說(shuō)是在某些特定環(huán)境下才能重現(xiàn);
此時(shí) 我們就會(huì)去查應(yīng)用日志(如果你沒(méi)有記錄,嘿嘿,那你...),我們要從龐大的日志文件中去定位記錄的信息(如果按大小進(jìn)行了分割的話就有"且眾多"), 看到這里你是否想到了"報(bào)警" 是個(gè)多么有用的手段啊,不管是發(fā)郵件還是sms,抑或微信等消息....雖然不是個(gè)銀彈,但是可以節(jié)省我們好多時(shí)間
在我的建議下 知名高性能日志組件Seaslog增加了報(bào)警機(jī)制.
此處給出我那時(shí)的實(shí)現(xiàn)代碼,此處功能即將會(huì)被整理到一個(gè)單獨(dú)的功能組件中實(shí)現(xiàn)ws-log
class Aert_Log
{
const TRACE = 1;
const DEBUG = 2;
const INFO = 3;
const WARN = 4;
const ERROR = 5;
const FATAL = 6;
private $enable = false;
private $level;
/**
* 日志存儲(chǔ)器
* @var Aert_LogAppender
*/
private $appender;
/**
* 日志存儲(chǔ)器
* @var Aert_LogAppender
*/
private $alert;
private $alertLevel;
private $enableAlert = false;
private static $levelNames = array(
1 => 'TRACE',
2 => 'DBEUG',
3 => 'INFO',
4 => 'WARN',
5 => 'ERROR',
6 => 'FATAL',
);
/**
* 返回指定的日志服務(wù)對(duì)象實(shí)例
*
* @param string $name
* @param array $config
*
* @return Aert_Log
*/
static function instance($name, array $config=array())
{
static $instances = array();
if (!isset($instances[$name]))
{
$instances[$name] = new self($config);
}
return $instances[$name];
}
private function __construct(array $config)
{
$this->level = intval(val($config, 'level', self::WARN));
$this->enable = (bool) val($config ,'enable' ,TRUE);
if ($this->enable)
{
do {
if ( empty($config['appender']) || empty($config['appender']['class']) )
{
$this->enable = false;
break;
}
$class = $config['appender']['class'];
$params = (array) val($config['appender'], 'config', NULL);
$this->appender = new $class($params);
if ( !empty($config['alert']) || !empty($config['alert']['class']) )
{
$this->enableAlert = TRUE;
$this->alertLevel = (int) val($config['alert'] ,'level' ,self::ERROR);
$class = $config['alert']['class'];
$params = (array) val($config['alert'], 'config', NULL);
$this->alert = new $class($params);
}
} while (false);
}
}
function log($level, $msg)
{
if (!$this->enable) return;
if ($this->enableAlert && ($level >= $this->alertLevel))
{
$this->alert($level, $msg);
}
if ($level < $this->level) return;
$this->appender->append(self::$levelNames[$level], $msg);
}
private function alert($level, $msg)
{
$this->alert->alert(self::$levelNames[$level], $msg);
}
}
/**
* 日志存儲(chǔ)器
*/
class Aert_LogAppender
{
function __construct(array $config)
{
$this->init($config);
}
protected function init(array $config)
{
}
function append($level, $msg)
{
}
}
/**
* 日志警報(bào)器
*/
class Aert_LogAlert
{
function __construct(array $config)
{
$this->init($config);
}
protected function init(array $config)
{
}
function alert($level, $msg)
{
}
}
將日志的存儲(chǔ)以及警報(bào)進(jìn)行了分離,可以大大簡(jiǎn)化自定義日志處理的復(fù)雜度以及增強(qiáng)處理的多樣性.比如可以單獨(dú)實(shí)現(xiàn)File存儲(chǔ),SAE存儲(chǔ)等,對(duì)報(bào)警器則可以實(shí)現(xiàn)控制臺(tái)(一般是瀏覽器)輸出,郵件,SMS,QQ,微信,SMS等多種.
以下給出2種實(shí)現(xiàn)形式,其它的就由大家自己去抽象
控制臺(tái)(一般是瀏覽器)輸出實(shí)現(xiàn)
<?php
/**
* 日志警報(bào)器 -- Console
*
* 監(jiān)聽(tīng)指定錯(cuò)誤級(jí)別,并直接打印到控制臺(tái)
*/
class LogAlert_Console extends Aert_LogAlert
{
function alert($level, $msg)
{
if (AERT_ISCLI)
{
fwrite(STDOUT, PHP_EOL . "[$level]: " . print_r($msg,true) . PHP_EOL);
}
else
{
if (is_string($msg))
{
echo "<BR />[$level]: " . print_r($msg,true);
}
else
{
dump($msg,"[{$level}]");
}
}
}
}
火狐插件FirePHP實(shí)現(xiàn)
<?php
#{{{
app_import_file('/Lib/FirePHP.class.php');
#}}}
/**
* 日志警報(bào)器 -- FirePHP
*
* 監(jiān)聽(tīng)指定錯(cuò)誤級(jí)別,并通過(guò)火狐擴(kuò)展 FirePHP 通知開(kāi)發(fā)人員
*/
class LogAlert_FirePHP extends Aert_LogAlert
{
function alert($level, $msg)
{
if (is_object($msg) && $msg instanceof TableRows)
{
/* @var $msg TableRows */
$caption = $msg->getCaption();
if (empty($caption)) $caption = $level;
FirePHP::getInstance(true)->table($caption, $msg->combingRows());
return;
}
switch ($level)
{
case 'TRACE':
FirePHP::getInstance(true)->log($msg,$level);
break;
case 'DBEUG':
FirePHP::getInstance(true)->log($msg,$level);
break;
case 'INFO':
FirePHP::getInstance(true)->info($msg);
break;
case 'WARN':
FirePHP::getInstance(true)->warn($msg);
break;
case 'ERROR':
FirePHP::getInstance(true)->error($msg);
break;
case 'FATAL':
FirePHP::getInstance(true)->error($msg,$level);
break;
}
}
}