設(shè)計(jì)一個(gè)精簡(jiǎn)易用的日志

本文大概是我在幾年前開(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 這部分功能,比如

  1. 記錄信息到本地文件,SAE環(huán)境,等等
  2. 過(guò)濾特定級(jí)別信息
  3. 格式輸出,大部分使用場(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;
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市进宝,隨后出現(xiàn)的幾起案子刻坊,更是在濱河造成了極大的恐慌,老刑警劉巖党晋,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谭胚,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡隶校,警方通過(guò)查閱死者的電腦和手機(jī)漏益,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)深胳,“玉大人,你說(shuō)我怎么就攤上這事铜犬∥柚眨” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵癣猾,是天一觀的道長(zhǎng)敛劝。 經(jīng)常有香客問(wèn)我,道長(zhǎng)纷宇,這世上最難降的妖魔是什么夸盟? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮像捶,結(jié)果婚禮上上陕,老公的妹妹穿的比我還像新娘桩砰。我一直安慰自己,他們只是感情好释簿,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布亚隅。 她就那樣靜靜地躺著,像睡著了一般庶溶。 火紅的嫁衣襯著肌膚如雪煮纵。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天偏螺,我揣著相機(jī)與錄音行疏,去河邊找鬼。 笑死套像,一個(gè)胖子當(dāng)著我的面吹牛酿联,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播凉夯,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼货葬,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了劲够?” 一聲冷哼從身側(cè)響起震桶,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎征绎,沒(méi)想到半個(gè)月后蹲姐,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡人柿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年柴墩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凫岖。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡江咳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出哥放,到底是詐尸還是另有隱情歼指,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布甥雕,位于F島的核電站踩身,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏社露。R本人自食惡果不足惜挟阻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧附鸽,春花似錦脱拼、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至击你,卻和暖如春玉组,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背丁侄。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工惯雳, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鸿摇。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓石景,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親拙吉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子潮孽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容