日志(Logging)

2018-02-24 15:40 更新

日志

Yii提供了一個強大的日志框架,這個框架具有高度的可定制性和可擴展性。使用這個框架,你可以輕松地記錄各種類型的消息,過濾它們, 并且將它們收集到不同的目標,諸如文件,數(shù)據(jù)庫,郵件。

使用Yii日志框架涉及下面的幾個步驟:

  • 在你代碼里的各個地方記錄?log messages;
  • 在應(yīng)用配置里通過配置?log targets?來過濾和導(dǎo)出日志消息;
  • 檢查由不同的目標導(dǎo)出的已過濾的日志消息(例如:Yii debugger)。

在這部分,我們主要描述前兩個步驟。

日志消息

記錄日志消息就跟調(diào)用下面的日志方法一樣簡單:

  • Yii::trace():記錄一條消息去跟蹤一段代碼是怎樣運行的。這主要在開發(fā)的時候使用。
  • Yii::info():記錄一條消息來傳達一些有用的信息。
  • Yii::warning():記錄一個警告消息用來指示一些已經(jīng)發(fā)生的意外。
  • Yii::error():記錄一個致命的錯誤,這個錯誤應(yīng)該盡快被檢查。

這些日志記錄方法針對?嚴重程度?和?類別?來記錄日志消息。 它們共享相同的函數(shù)簽名?function ($message, $category = 'application'),$message代表要被 記錄的日志消息,而?$category?是日志消息的類別。在下面的示例代碼中,在默認的類別application?下 記錄了一條跟蹤消息:

Yii::trace('start calculating average revenue');

信息:日志消息可以是字符串,也可以是復(fù)雜的數(shù)據(jù),諸如數(shù)組或者對象。log targets?的義務(wù)是正確處理日志消息。 默認情況下,假如一條日志消息不是一個字符串,它將被導(dǎo)出為一個字符串,通過調(diào)用 yii\helpers\VarDumper::export()。

為了更好地組織和過濾日志消息,我們建議您為每個日志消息指定一個適當?shù)念悇e。 您可以為類別選擇一個分層命名方案,這將使得log targets?在基于它們的分類來過濾消息變得更加容易。 一個簡單而高效的命名方案是使用PHP魔術(shù)常量?__METHOD__?作為分類名稱。這種方式也在Yii框架的核心代碼中得到應(yīng)用, 例如,

Yii::trace('start calculating average revenue', __METHOD__);

__METHOD__?常量計算值作為該常量出現(xiàn)的地方的方法名(完全限定的類名前綴)。例如,假如上面那行代碼在這個方法內(nèi)被調(diào)用,則它將等于字符串?'app\controllers\RevenueController::calculate'。

信息:上面所描述的日志方法實際上是 yii\log\Logger 對象(一個通過表達式?Yii::getLogger()?可訪問的單例) 的方法 yii\log\Logger::log() 的一個快捷方式。當足夠的消息被記錄或者當應(yīng)用結(jié)束時,日志對象將會調(diào)用一個 yii\log\Dispatcher 調(diào)度對象將已經(jīng)記錄的日志消息發(fā)送到已注冊的?log targets?目標中。

日志目標

一個日志目標是一個 yii\log\Target 類或者它的子類的實例。它將通過他們的嚴重層級和類別來過濾日志消息,然后將它們導(dǎo)出到一些媒介中。 例如,一個 yii\log\DbTarget 目標導(dǎo)出已經(jīng)過濾的日志消息到一個數(shù)據(jù)的表里面,而一個 yii\log\EmailTarget 目標將日志消息導(dǎo)出到指定的郵箱地址里。

在一個應(yīng)用里,通過配置在應(yīng)用配置里的?log?application component?,你可以注冊多個日志目標。 就像下面這樣:

return [
    // the "log" component must be loaded during bootstrapping time
    'bootstrap' => ['log'],

    'components' => [
        'log' => [
            'targets' => [
                [
                    'class' => 'yii\log\DbTarget',
                    'levels' => ['error', 'warning'],
                ],
                [
                    'class' => 'yii\log\EmailTarget',
                    'levels' => ['error'],
                    'categories' => ['yii\db\*'],
                    'message' => [
                       'from' => ['log@example.com'],
                       'to' => ['admin@example.com', 'developer@example.com'],
                       'subject' => 'Database errors at example.com',
                    ],
                ],
            ],
        ],
    ],
];

注意:log?組件必須在?bootstrapping?期間就被加載,以便于它能夠及時調(diào)度日志消息到目標里。 這是為什么在上面的代碼中,它被列在?bootstrap?數(shù)組中的原因。

在上面的代碼中,在 yii\log\Dispatcher::targets 屬性里有兩個日志目標被注冊:

  • 第一個目標選擇的是錯誤和警告層級的消息,并且在數(shù)據(jù)庫表里保存他們;
  • 第二個目標選擇的是錯誤層級的消息并且是在以?yii\db\?開頭的分類下,并且在一個郵件里將它們發(fā)送到?admin@example.com和?developer@example.com

Yii配備了以下的內(nèi)建日志目標。請參考關(guān)于這些類的API文檔, 并且學(xué)習(xí)怎樣配置和使用他們。

  • yii\log\DbTarget:在數(shù)據(jù)庫表里存儲日志消息。
  • yii\log\EmailTarget:發(fā)送日志消息到預(yù)先指定的郵箱地址。
  • yii\log\FileTarget:保存日志消息到文件中.
  • yii\log\SyslogTarget:通過調(diào)用PHP函數(shù)?syslog()?將日志消息保存到系統(tǒng)日志里。

下面,我們將描述所有日志目標的公共特性。

消息過濾

對于每一個日志目標,你可以配置它的 yii\log\Target::levels 和 yii\log\Target::categories 屬性來指定哪個消息的嚴重程度和分類目標應(yīng)該處理。

yii\log\Target::levels 屬性是由一個或者若干個以下值組成的數(shù)組:

  • error:相應(yīng)的消息通過 Yii::error() 被記錄。
  • warning:相應(yīng)的消息通過 Yii::warning() 被記錄。
  • info:相應(yīng)的消息通過 Yii::info() 被記錄。
  • trace:相應(yīng)的消息通過 Yii::trace() 被記錄。
  • profile:相應(yīng)的消息通過 Yii::beginProfile() 和 Yii::endProfile() 被記錄。更多細節(jié)將在?Profiling?分段解釋。

如果你沒有指定 yii\log\Target::levels 的屬性, 那就意味著目標將處理?任何?嚴重程度的消息。

yii\log\Target::categories 屬性是一個包含消息分類名稱或者模式的數(shù)組。 一個目標將只處理那些在這個數(shù)組中能夠找到對應(yīng)的分類或者其中一個相匹配的模式的消息。 一個分類模式是一個以星號?*?結(jié)尾的分類名前綴。假如一個分類名與分類模式具有相同的前綴,那么該分類名將和分類模式相匹配。 例如,yii\db\Command::execute?和?yii\db\Command::query?都是作為分類名稱運用在 yii\db\Command 類來記錄日志消息的。 它們都是匹配模式?yii\db\*

假如你沒有指定 yii\log\Target::categories 屬性,這意味著目標將會處理?任何?分類的消息。

除了通過 yii\log\Target::categories 屬性設(shè)置白名單分類,你也可以通過 yii\log\Target::except 屬性來設(shè)置某些分類作為黑名單。假如一條消息的分類在這個屬性中被發(fā)現(xiàn)或者是匹配其中一個,那么它將不會在目標中被處理。

在下面的目標配置中指明了目標應(yīng)該只處理錯誤和警告消息,當分類的名稱匹配?yii\db\*?或者是?yii\web\HttpException:*?的時候, 但是除了?yii\web\HttpException:404

[
    'class' => 'yii\log\FileTarget',
    'levels' => ['error', 'warning'],
    'categories' => [
        'yii\db\*',
        'yii\web\HttpException:*',
    ],
    'except' => [
        'yii\web\HttpException:404',
    ],
]

信息:當一個HTTP異常通過?error handler?被捕獲的時候,一個錯誤消息將以?yii\web\HttpException:ErrorCode?這樣的格式的分類名被記錄下來。例如,yii\web\NotFoundHttpException 將會引發(fā)一個分類是?yii\web\HttpException:404?的 錯誤消息。

消息格式化

日志目標以某種格式導(dǎo)出過濾過的日志消息。例如,假如你安裝一個 yii\log\FileTarget 類的日志目標, 你應(yīng)該能找出一個日志消息類似下面的?runtime/log/app.log?文件:

2014-10-04 18:10:15 [::1][][-][trace][yii\base\Module::getModule] Loading module: debug

默認情況下,日志消息將被格式化,格式化的方式遵循 yii\log\Target::formatMessage():

Timestamp [IP address][User ID][Session ID][Severity Level][Category] Message Text

你可以通過配置 yii\log\Target::prefix 的屬性來自定義格式,這個屬性是一個PHP可調(diào)用體返回的自定義消息前綴。 例如,下面的代碼配置了一個日志目標的前綴是每個日志消息中當前用戶的ID(IP地址和Session ID被刪除是由于隱私的原因)。

[
    'class' => 'yii\log\FileTarget',
    'prefix' => function ($message) {
        $user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null;
        $userID = $user ? $user->getId(false) : '-';
        return "[$userID]";
    }
]

除了消息前綴以外,日志目標也可以追加一些上下文信息到每組日志消息中。 默認情況下,這些全局的PHP變量的值被包含在:$_GET,?$_POST,?$_FILES,?$_COOKIE,$_SESSION?和?$_SERVER?中。 你可以通過配置 yii\log\Target::logVars 屬性適應(yīng)這個行為,這個屬性是你想要通過日志目標包含的全局變量名稱。 舉個例子,下面的日志目標配置指明了只有?$_SERVER?變量的值將被追加到日志消息中。

[
    'class' => 'yii\log\FileTarget',
    'logVars' => ['_SERVER'],
]

你可以將?logVars?配置成一個空數(shù)組來完全禁止上下文信息包含。或者假如你想要實現(xiàn)你自己提供上下文信息的方式, 你可以重寫 yii\log\Target::getContextMessage() 方法。

消息跟蹤級別

在開發(fā)的時候,通常希望看到每個日志消息來自哪里。這個是能夠被實現(xiàn)的,通過配置?log?組件的 yii\log\Dispatcher::traceLevel 屬性, 就像下面這樣:

return [
    'bootstrap' => ['log'],
    'components' => [
        'log' => [
            'traceLevel' => YII_DEBUG ? 3 : 0,
            'targets' => [...],
        ],
    ],
];

上面的應(yīng)用配置設(shè)置了 yii\log\Dispatcher::traceLevel 的層級,假如?YII_DEBUG?開啟則是3,否則是0。 這意味著,假如?YII_DEBUG開啟,每個日志消息在日志消息被記錄的時候,將被追加最多3個調(diào)用堆棧層級;假如?YII_DEBUG?關(guān)閉, 那么將沒有調(diào)用堆棧信息被包含。

信息:獲得調(diào)用堆棧信息并不是不重要。因此,你應(yīng)該只在開發(fā)或者調(diào)試一個應(yīng)用的時候使用這個特性。

消息刷新和導(dǎo)出

如上所述,通過 yii\log\Logger 對象,日志消息被保存在一個數(shù)組里。為了這個數(shù)組的內(nèi)存消耗, 當數(shù)組積累了一定數(shù)量的日志消息,日志對象每次都將刷新被記錄的消息到?log targets?中。 你可以通過配置?log?組件的 yii\log\Dispatcher::flushInterval 屬性來自定義數(shù)量:

return [
    'bootstrap' => ['log'],
    'components' => [
        'log' => [
            'flushInterval' => 100,   // default is 1000
            'targets' => [...],
        ],
    ],
];

信息:當應(yīng)用結(jié)束的時候,消息刷新也會發(fā)生,這樣才能確保日志目標能夠接收完整的日志消息。

當 yii\log\Logger 對象刷新日志消息到?log targets?的時候,它們并 不能立即獲取導(dǎo)出的消息。相反,消息導(dǎo)出僅僅在一個日志目標累積了一定數(shù)量的過濾消息的時候才會發(fā)生。你可以通過配置 個別的?log targets?的 yii\log\Target::exportInterval 屬性來 自定義這個數(shù)量,就像下面這樣:

[
    'class' => 'yii\log\FileTarget',
    'exportInterval' => 100,  // default is 1000
]

因為刷新和導(dǎo)出層級的設(shè)置,默認情況下,當你調(diào)用?Yii::trace()?或者任何其他的記錄方法,你將不能在日志目標中立即看到日志消息。 這對于一些長期運行的控制臺應(yīng)用來說可能是一個問題。為了讓每個日志消息在日志目標中能夠立即出現(xiàn),你應(yīng)該設(shè)置 yii\log\Dispatcher::flushInterval 和 yii\log\Target::exportInterval 都為1, 就像下面這樣:

return [
    'bootstrap' => ['log'],
    'components' => [
        'log' => [
            'flushInterval' => 1,
            'targets' => [
                [
                    'class' => 'yii\log\FileTarget',
                    'exportInterval' => 1,
                ],
            ],
        ],
    ],
];

注意:頻繁的消息刷新和導(dǎo)出將降低你到應(yīng)用性能。

切換日志目標

你可以通過配置 yii\log\Target::enabled 屬性來開啟或者禁用日志目標。 你可以通過日志目標配置去做,或者是在你的代碼中放入下面的PHP申明:

Yii::$app->log->targets['file']->enabled = false;

上面的代碼要求您將目標命名為?file,像下面展示的那樣,在?targets?數(shù)組中使用使用字符串鍵:

return [
    'bootstrap' => ['log'],
    'components' => [
        'log' => [
            'targets' => [
                'file' => [
                    'class' => 'yii\log\FileTarget',
                ],
                'db' => [
                    'class' => 'yii\log\DbTarget',
                ],
            ],
        ],
    ],
];

創(chuàng)建新的目標

創(chuàng)建一個新的日志目標類非常地簡單。你主要需要實現(xiàn) yii\log\Target::export() 方法來發(fā)送 yii\log\Target::messages 數(shù)組的 內(nèi)容到一個指定的媒體中。你可以調(diào)用 yii\log\Target::formatMessage() 方法去格式化每個消息。更多細節(jié),你可以參考任何一個包含在Yii 發(fā)布版中的日志目標類。

性能分析

性能分析是一個特殊的消息記錄類型,它通常用在測量某段代碼塊的時間,并且找出性能瓶頸是什么。舉個例子,yii\db\Command 類 使用性能分析找出每個數(shù)據(jù)庫查詢的時間。

為了使用性能分析,首先確定需要進行分析的代碼塊。然后像下面這樣圍住每個代碼塊:

\Yii::beginProfile('myBenchmark');

...code block being profiled...

\Yii::endProfile('myBenchmark');

這里的?myBenchmark?代表一個唯一標記來標識一個代碼塊。之后當你檢查分析結(jié)果的時候, 你將使用這個標記來定位對應(yīng)的代碼塊所花費的時間。

對于確保?beginProfile?和?endProfile?對能夠正確地嵌套,這是很重要的。 例如,

\Yii::beginProfile('block1');

    // some code to be profiled

    \Yii::beginProfile('block2');
        // some other code to be profiled
    \Yii::endProfile('block2');

\Yii::endProfile('block1');

假如你漏掉?\Yii::endProfile('block1')?或者切換了?\Yii::endProfile('block1')?和?\Yii::endProfile('block2')?的 順序,那么性能分析將不會工作。

對于每個被分析的代碼塊,一個帶有嚴重程度?profile?的日志消息被記錄。你可以配置一個?log target?去收集這些 消息,并且導(dǎo)出他們。Yii debugger?有一個內(nèi)建的性能分析面板能夠展示分析結(jié)果。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號