Yii 提供了一個強大的日志框架,這個框架具有高度的可定制性和可擴展性。使用這個框架,你可以輕松地記錄各種類型的消息,過濾它們,并且將它們收集到不同的目標,諸如文件,數(shù)據(jù)庫,郵件。
使用 Yii 日志框架涉及下面的幾個步驟:
在這部分,我們主要描述前兩個步驟。
記錄日志消息就跟調(diào)用下面的日志方法一樣簡單:
這些日志記錄方法針對 嚴重程度 和 類別 來記錄日志消息。它們共享相同的函數(shù)簽名 function ($message, $category = 'application')
,$message
代表要被記錄的日志消息,而 $category
是日志消息的類別。在下面的示例代碼中,在默認的類別 application
下記錄了一條跟蹤消息:
Yii::trace('start calculating average revenue');
信息:日志消息可以是字符串,也可以是復雜的數(shù)據(jù),諸如數(shù)組或者對象。log targets 的義務是正確處理日志消息。默認情況下,假如一條日志消息不是一個字符串,它將被導出為一個字符串,通過調(diào)用 [[yii\helpers\VarDumper::export()]]。
為了更好地組織和過濾日志消息,我們建議您為每個日志消息指定一個適當?shù)念悇e。您可以為類別選擇一個分層命名方案,這將使得 log targets 在基于它們的分類來過濾消息變得更加容易。一個簡單而高效的命名方案是使用 PHP 魔術(shù)常量 __METHOD__
作為分類名稱。這種方式也在 Yii 框架的核心代碼中得到應用,例如,
Yii::trace('start calculating average revenue', __METHOD__);
__METHOD__
常量計算值作為該常量出現(xiàn)的地方的方法名(完全限定的類名前綴)。例如,假如上面那行代碼在這個方法內(nèi)被調(diào)用,則它將等于字符串'app\controllers\RevenueController::calculate'
。
信息:上面所描述的日志方法實際上是 [[yii\log\Logger|logger object]] 對象(一個通過表達式
Yii::getLogger()
可訪問的單例)的方法 [[yii\log\Logger::log()|log()]] 的一個快捷方式。當足夠的消息被記錄或者當應用結(jié)束時,日志對象將會調(diào)用一個 [[yii\log\Dispatcher|message dispatcher]]調(diào)度對象將已經(jīng)記錄的日志消息發(fā)送到已注冊的 log targets 目標中。
一個日志目標是一個 [[yii\log\Target]] 類或者它的子類的實例。它將通過他們的嚴重層級和類別來過濾日志消息,然后將它們導出到一些媒介中。例如,一個 [[yii\log\DbTarget|database target]] 目標導出已經(jīng)過濾的日志消息到一個數(shù)據(jù)的表里面,而一個 [[yii\log\EmailTarget|email target]]目標將日志消息導出到指定的郵箱地址里。
在一個應用里,通過配置在應用配置里的 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]] 屬性里有兩個日志目標被注冊:
yii\db\
開頭的分類下,并且在一個郵件里將它們發(fā)送到 admin@example.com
和 developer@example.com
。Yii 配備了以下的內(nèi)建日志目標。請參考關(guān)于這些類的 API 文檔,并且學習怎樣配置和使用他們。
syslog()
將日志消息保存到系統(tǒng)日志里。下面,我們將描述所有日志目標的公共特性。
對于每一個日志目標,你可以配置它的 [[yii\log\Target::levels|levels]] 和 [[yii\log\Target::categories|categories]] 屬性來指定哪個消息的嚴重程度和分類目標應該處理。
[[yii\log\Target::levels|levels]] 屬性是由一個或者若干個以下值組成的數(shù)組:
error
:相應的消息通過 [[Yii::error()]] 被記錄。warning
:相應的消息通過 [[Yii::warning()]] 被記錄。info
:相應的消息通過 [[Yii::info()]] 被記錄。trace
:相應的消息通過 [[Yii::trace()]] 被記錄。profile
:相應的消息通過 [[Yii::beginProfile()]] 和 [[Yii::endProfile()]] 被記錄。更多細節(jié)將在Profiling 分段解釋。如果你沒有指定 [[yii\log\Target::levels|levels]] 的屬性,那就意味著目標將處理 任何 嚴重程度的消息。
[[yii\log\Target::categories|categories]] 屬性是一個包含消息分類名稱或者模式的數(shù)組。
一個目標將只處理那些在這個數(shù)組中能夠找到對應的分類或者其中一個相匹配的模式的消息。一個分類模式是一個以星號 *
結(jié)尾的分類名前綴。假如一個分類名與分類模式具有相同的前綴,那么該分類名將和分類模式相匹配。例如,yii\db\Command::execute
和 yii\db\Command::query
都是作為分類名稱運用在 [[yii\db\Command]] 類來記錄日志消息的。它們都是匹配模式 yii\db\*
。
假如你沒有指定 [[yii\log\Target::categories|categories]] 屬性,這意味著目標將會處理 任何 分類的消息。
除了通過 [[yii\log\Target::categories|categories]] 屬性設(shè)置白名單分類,你也可以通過 [[yii\log\Target::except|except]] 屬性來設(shè)置某些分類作為黑名單。假如一條消息的分類在這個屬性中被發(fā)現(xiàn)或者是匹配其中一個,那么它將不會在目標中被處理。
在下面的目標配置中指明了目標應該只處理錯誤和警告消息,當分類的名稱匹配 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
的錯誤消息。
日志目標以某種格式導出過濾過的日志消息。例如,假如你安裝一個 [[yii\log\FileTarget]] 類的日志目標,你應該能找出一個日志消息類似下面的 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]] 屬性適應這個行為,這個屬性是你想要通過日志目標包含的全局變量名稱。舉個例子,下面的日志目標配置指明了只有 $_SERVER
變量的值將被追加到日志消息中。
[
'class' => 'yii\log\FileTarget',
'logVars' => ['_SERVER'],
]
你可以將 logVars
配置成一個空數(shù)組來完全禁止上下文信息包含?;蛘呒偃缒阆胍獙崿F(xiàn)你自己提供上下文信息的方式,你可以重寫 [[yii\log\Target::getContextMessage()]] 方法。
在開發(fā)的時候,通常希望看到每個日志消息來自哪里。這個是能夠被實現(xiàn)的,通過配置 log
組件的 [[yii\log\Dispatcher::traceLevel|traceLevel]] 屬性,就像下面這樣:
return [
'bootstrap' => ['log'],
'components' => [
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [...],
],
],
];
上面的應用配置設(shè)置了 [[yii\log\Dispatcher::traceLevel|traceLevel]] 的層級,假如 YII_DEBUG
開啟則是 3,否則是 0。這意味著,假如 YII_DEBUG
開啟,每個日志消息在日志消息被記錄的時候,將被追加最多 3 個調(diào)用堆棧層級;假如 YII_DEBUG
關(guān)閉,那么將沒有調(diào)用堆棧信息被包含。
信息:獲得調(diào)用堆棧信息并不是不重要。因此,你應該只在開發(fā)或者調(diào)試一個應用的時候使用這個特性。
如上所述,通過 [[yii\log\Logger|logger object]] 對象,日志消息被保存在一個數(shù)組里。為了這個數(shù)組的內(nèi)存消耗,當數(shù)組積累了一定數(shù)量的日志消息,日志對象每次都將刷新被記錄的消息到 log targets 中。你可以通過配置 log
組件的 [[yii\log\Dispatcher::flushInterval|flushInterval]] 屬性來自定義數(shù)量:
return [
'bootstrap' => ['log'],
'components' => [
'log' => [
'flushInterval' => 100, // default is 1000
'targets' => [...],
],
],
];
信息:當應用結(jié)束的時候,消息刷新也會發(fā)生,這樣才能確保日志目標能夠接收完整的日志消息。
當 [[yii\log\Logger|logger object]] 對象刷新日志消息到 log targets 的時候,它們并不能立即獲取導出的消息。相反,消息導出僅僅在一個日志目標累積了一定數(shù)量的過濾消息的時候才會發(fā)生。你可以通過配置個別的 log targets 的 [[yii\log\Target::exportInterval|exportInterval]] 屬性來自定義這個數(shù)量,就像下面這樣:
[
'class' => 'yii\log\FileTarget',
'exportInterval' => 100, // default is 1000
]
因為刷新和導出層級的設(shè)置,默認情況下,當你調(diào)用 Yii::trace()
或者任何其他的記錄方法,你將不能在日志目標中立即看到日志消息。這對于一些長期運行的控制臺應用來說可能是一個問題。為了讓每個日志消息在日志目標中能夠立即出現(xiàn),你應該設(shè)置 [[yii\log\Dispatcher::flushInterval|flushInterval]]和 [[yii\log\Target::exportInterval|exportInterval]] 都為 1,就像下面這樣:
return [
'bootstrap' => ['log'],
'components' => [
'log' => [
'flushInterval' => 1,
'targets' => [
[
'class' => 'yii\log\FileTarget',
'exportInterval' => 1,
],
],
],
],
];
注意:頻繁的消息刷新和導出將降低你到應用性能。
你可以通過配置 [[yii\log\Target::enabled|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)建一個新的日志目標類非常地簡單。你主要需要實現(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é)果的時候,你將使用這個標記來定位對應的代碼塊所花費的時間。
對于確保 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 去收集這些消息,并且導出他們。Yii debugger有一個內(nèi)建的性能分析面板能夠展示分析結(jié)果。