# 日志
Yii提供了一個強大的日志框架,這個框架具有高度的可定制性和可擴展性。使用這個框架,你可以輕松地記錄各種類型的消息,過濾它們, 并且將它們收集到不同的目標,諸如文件,數據庫,郵件。
使用Yii日志框架涉及下面的幾個步驟:
* 在你代碼里的各個地方記錄?[log messages](http://www.yiichina.com/doc/guide/2.0/runtime-logging#log-messages);
* 在應用配置里通過配置?[log targets](http://www.yiichina.com/doc/guide/2.0/runtime-logging#log-targets)?來過濾和導出日志消息;
* 檢查由不同的目標導出的已過濾的日志消息(例如:[Yii debugger](http://www.yiichina.com/doc/guide/2.0/tool-debugger))。
在這部分,我們主要描述前兩個步驟。
## 日志消息
記錄日志消息就跟調用下面的日志方法一樣簡單:
* Yii::trace():記錄一條消息去跟蹤一段代碼是怎樣運行的。這主要在開發的時候使用。
* Yii::info():記錄一條消息來傳達一些有用的信息。
* Yii::warning():記錄一個警告消息用來指示一些已經發生的意外。
* Yii::error():記錄一個致命的錯誤,這個錯誤應該盡快被檢查。
這些日志記錄方法針對?*嚴重程度*?和?*類別*?來記錄日志消息。 它們共享相同的函數簽名?`function ($message, $category = 'application')`,`$message`代表要被 記錄的日志消息,而?`$category`?是日志消息的類別。在下面的示例代碼中,在默認的類別`application`?下 記錄了一條跟蹤消息:
~~~
Yii::trace('start calculating average revenue');
~~~
> 信息:日志消息可以是字符串,也可以是復雜的數據,諸如數組或者對象。[log targets](http://www.yiichina.com/doc/guide/2.0/runtime-logging#log-targets)?的義務是正確處理日志消息。 默認情況下,假如一條日志消息不是一個字符串,它將被導出為一個字符串,通過調用 yii\helpers\VarDumper::export()。
為了更好地組織和過濾日志消息,我們建議您為每個日志消息指定一個適當的類別。 您可以為類別選擇一個分層命名方案,這將使得[log targets](http://www.yiichina.com/doc/guide/2.0/runtime-logging#log-targets)?在基于它們的分類來過濾消息變得更加容易。 一個簡單而高效的命名方案是使用PHP魔術常量?`__METHOD__`?作為分類名稱。這種方式也在Yii框架的核心代碼中得到應用, 例如,
~~~
Yii::trace('start calculating average revenue', __METHOD__);
~~~
`__METHOD__`?常量計算值作為該常量出現的地方的方法名(完全限定的類名前綴)。例如,假如上面那行代碼在這個方法內被調用,則它將等于字符串?`'app\controllers\RevenueController::calculate'`。
> 信息:上面所描述的日志方法實際上是 yii\log\Logger 對象(一個通過表達式?`Yii::getLogger()`?可訪問的單例) 的方法 yii\log\Logger::log() 的一個快捷方式。當足夠的消息被記錄或者當應用結束時,日志對象將會調用一個 yii\log\Dispatcher 調度對象將已經記錄的日志消息發送到已注冊的?[log targets](http://www.yiichina.com/doc/guide/2.0/runtime-logging#log-targets)?目標中。
## 日志目標
一個日志目標是一個 yii\log\Target 類或者它的子類的實例。它將通過他們的嚴重層級和類別來過濾日志消息,然后將它們導出到一些媒介中。 例如,一個 yii\log\DbTarget 目標導出已經過濾的日志消息到一個數據的表里面,而一個 yii\log\EmailTarget 目標將日志消息導出到指定的郵箱地址里。
在一個應用里,通過配置在應用配置里的?`log`?[application component](http://www.yiichina.com/doc/guide/2.0/structure-application-components)?,你可以注冊多個日志目標。 就像下面這樣:
~~~
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](http://www.yiichina.com/doc/guide/2.0/runtime-bootstrapping)?期間就被加載,以便于它能夠及時調度日志消息到目標里。 這是為什么在上面的代碼中,它被列在?`bootstrap`?數組中的原因。
在上面的代碼中,在 yii\log\Dispatcher::targets 屬性里有兩個日志目標被注冊:
* 第一個目標選擇的是錯誤和警告層級的消息,并且在數據庫表里保存他們;
* 第二個目標選擇的是錯誤層級的消息并且是在以?`yii\db\`?開頭的分類下,并且在一個郵件里將它們發送到?`admin@example.com`和?`developer@example.com`。
Yii配備了以下的內建日志目標。請參考關于這些類的API文檔, 并且學習怎樣配置和使用他們。
* yii\log\DbTarget:在數據庫表里存儲日志消息。
* yii\log\EmailTarget:發送日志消息到預先指定的郵箱地址。
* yii\log\FileTarget:保存日志消息到文件中.
* yii\log\SyslogTarget:通過調用PHP函數?`syslog()`?將日志消息保存到系統日志里。
下面,我們將描述所有日志目標的公共特性。
### 消息過濾
對于每一個日志目標,你可以配置它的 yii\log\Target::levels 和 yii\log\Target::categories 屬性來指定哪個消息的嚴重程度和分類目標應該處理。
yii\log\Target::levels 屬性是由一個或者若干個以下值組成的數組:
* `error`:相應的消息通過 Yii::error() 被記錄。
* `warning`:相應的消息通過 Yii::warning() 被記錄。
* `info`:相應的消息通過 Yii::info() 被記錄。
* `trace`:相應的消息通過 Yii::trace() 被記錄。
* `profile`:相應的消息通過 Yii::beginProfile() 和 Yii::endProfile() 被記錄。更多細節將在?[Profiling](http://www.yiichina.com/doc/guide/2.0/runtime-logging#performance-profiling)?分段解釋。
如果你沒有指定 yii\log\Target::levels 的屬性, 那就意味著目標將處理?*任何*?嚴重程度的消息。
yii\log\Target::categories 屬性是一個包含消息分類名稱或者模式的數組。 一個目標將只處理那些在這個數組中能夠找到對應的分類或者其中一個相匹配的模式的消息。 一個分類模式是一個以星號?`*`?結尾的分類名前綴。假如一個分類名與分類模式具有相同的前綴,那么該分類名將和分類模式相匹配。 例如,`yii\db\Command::execute`?和?`yii\db\Command::query`?都是作為分類名稱運用在 yii\db\Command 類來記錄日志消息的。 它們都是匹配模式?`yii\db\*`。
假如你沒有指定 yii\log\Target::categories 屬性,這意味著目標將會處理?*任何*?分類的消息。
除了通過 yii\log\Target::categories 屬性設置白名單分類,你也可以通過 yii\log\Target::except 屬性來設置某些分類作為黑名單。假如一條消息的分類在這個屬性中被發現或者是匹配其中一個,那么它將不會在目標中被處理。
在下面的目標配置中指明了目標應該只處理錯誤和警告消息,當分類的名稱匹配?`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](http://www.yiichina.com/doc/guide/2.0/runtime-handling-errors)?被捕獲的時候,一個錯誤消息將以?`yii\web\HttpException:ErrorCode`?這樣的格式的分類名被記錄下來。例如,yii\web\NotFoundHttpException 將會引發一個分類是?`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可調用體返回的自定義消息前綴。 例如,下面的代碼配置了一個日志目標的前綴是每個日志消息中當前用戶的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`?配置成一個空數組來完全禁止上下文信息包含。或者假如你想要實現你自己提供上下文信息的方式, 你可以重寫 yii\log\Target::getContextMessage() 方法。
### 消息跟蹤級別
在開發的時候,通常希望看到每個日志消息來自哪里。這個是能夠被實現的,通過配置?`log`?組件的 yii\log\Dispatcher::traceLevel 屬性, 就像下面這樣:
~~~
return [
'bootstrap' => ['log'],
'components' => [
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [...],
],
],
];
~~~
上面的應用配置設置了 yii\log\Dispatcher::traceLevel 的層級,假如?`YII_DEBUG`?開啟則是3,否則是0。 這意味著,假如?`YII_DEBUG`開啟,每個日志消息在日志消息被記錄的時候,將被追加最多3個調用堆棧層級;假如?`YII_DEBUG`?關閉, 那么將沒有調用堆棧信息被包含。
> 信息:獲得調用堆棧信息并不是不重要。因此,你應該只在開發或者調試一個應用的時候使用這個特性。
### 消息刷新和導出
如上所述,通過 yii\log\Logger 對象,日志消息被保存在一個數組里。為了這個數組的內存消耗, 當數組積累了一定數量的日志消息,日志對象每次都將刷新被記錄的消息到?[log targets](http://www.yiichina.com/doc/guide/2.0/runtime-logging#log-targets)?中。 你可以通過配置?`log`?組件的 yii\log\Dispatcher::flushInterval 屬性來自定義數量:
~~~
return [
'bootstrap' => ['log'],
'components' => [
'log' => [
'flushInterval' => 100, // default is 1000
'targets' => [...],
],
],
];
~~~
> 信息:當應用結束的時候,消息刷新也會發生,這樣才能確保日志目標能夠接收完整的日志消息。
當 yii\log\Logger 對象刷新日志消息到?[log targets](http://www.yiichina.com/doc/guide/2.0/runtime-logging#log-targets)?的時候,它們并 不能立即獲取導出的消息。相反,消息導出僅僅在一個日志目標累積了一定數量的過濾消息的時候才會發生。你可以通過配置 個別的?[log targets](http://www.yiichina.com/doc/guide/2.0/runtime-logging#log-targets)?的 yii\log\Target::exportInterval 屬性來 自定義這個數量,就像下面這樣:
~~~
[
'class' => 'yii\log\FileTarget',
'exportInterval' => 100, // default is 1000
]
~~~
因為刷新和導出層級的設置,默認情況下,當你調用?`Yii::trace()`?或者任何其他的記錄方法,你將不能在日志目標中立即看到日志消息。 這對于一些長期運行的控制臺應用來說可能是一個問題。為了讓每個日志消息在日志目標中能夠立即出現,你應該設置 yii\log\Dispatcher::flushInterval 和 yii\log\Target::exportInterval 都為1, 就像下面這樣:
~~~
return [
'bootstrap' => ['log'],
'components' => [
'log' => [
'flushInterval' => 1,
'targets' => [
[
'class' => 'yii\log\FileTarget',
'exportInterval' => 1,
],
],
],
],
];
~~~
> 注意:頻繁的消息刷新和導出將降低你到應用性能。
### 切換日志目標
你可以通過配置 yii\log\Target::enabled 屬性來開啟或者禁用日志目標。 你可以通過日志目標配置去做,或者是在你的代碼中放入下面的PHP申明:
~~~
Yii::$app->log->targets['file']->enabled = false;
~~~
上面的代碼要求您將目標命名為?`file`,像下面展示的那樣,在?`targets`?數組中使用使用字符串鍵:
~~~
return [
'bootstrap' => ['log'],
'components' => [
'log' => [
'targets' => [
'file' => [
'class' => 'yii\log\FileTarget',
],
'db' => [
'class' => 'yii\log\DbTarget',
],
],
],
],
];
~~~
### 創建新的目標
創建一個新的日志目標類非常地簡單。你主要需要實現 yii\log\Target::export() 方法來發送 yii\log\Target::messages 數組的 內容到一個指定的媒體中。你可以調用 yii\log\Target::formatMessage() 方法去格式化每個消息。更多細節,你可以參考任何一個包含在Yii 發布版中的日志目標類。
## 性能分析
性能分析是一個特殊的消息記錄類型,它通常用在測量某段代碼塊的時間,并且找出性能瓶頸是什么。舉個例子,yii\db\Command 類 使用性能分析找出每個數據庫查詢的時間。
為了使用性能分析,首先確定需要進行分析的代碼塊。然后像下面這樣圍住每個代碼塊:
~~~
\Yii::beginProfile('myBenchmark');
...code block being profiled...
\Yii::endProfile('myBenchmark');
~~~
這里的?`myBenchmark`?代表一個唯一標記來標識一個代碼塊。之后當你檢查分析結果的時候, 你將使用這個標記來定位對應的代碼塊所花費的時間。
對于確保?`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](http://www.yiichina.com/doc/guide/2.0/runtime-logging#log-targets)?去收集這些 消息,并且導出他們。[Yii debugger](http://www.yiichina.com/doc/guide/2.0/tool-debugger)?有一個內建的性能分析面板能夠展示分析結果。
- 介紹(Introduction)
- 關于 Yii(About Yii)
- 從 Yii 1.1 升級(Upgrading from Version 1.1)
- 入門(Getting Started)
- 安裝 Yii(Installing Yii)
- 運行應用(Running Applications)
- 第一次問候(Saying Hello)
- 使用 Forms(Working with Forms)
- 玩轉 Databases(Working with Databases)
- 用 Gii 生成代碼(Generating Code with Gii)
- 更上一層樓(Looking Ahead)
- 應用結構(Application Structure)
- 結構概述(Overview)
- 入口腳本(Entry Scripts)
- 應用(Applications)
- 應用組件(Application Components)
- 控制器(Controllers)
- 模型(Models)
- 視圖(Views)
- 模塊(Modules)
- 過濾器(Filters)
- 小部件(Widgets)
- 前端資源(Assets)
- 擴展(Extensions)
- 請求處理(Handling Requests)
- 運行概述(Overview)
- 引導(Bootstrapping)
- 路由引導與創建 URL(Routing and URL Creation)
- 請求(Requests)
- 響應(Responses)
- Sessions and Cookies
- 錯誤處理(Handling Errors)
- 日志(Logging)
- 關鍵概念(Key Concepts)
- 組件(Components)
- 屬性(Properties)
- 事件(Events)
- 行為(Behaviors)
- 配置(Configurations)
- 別名(Aliases)
- 類自動加載(Class Autoloading)
- 服務定位器(Service Locator)
- 依賴注入容器(Dependency Injection Container)
- 配合數據庫工作(Working with Databases)
- 數據庫訪問(Data Access Objects): 數據庫連接、基本查詢、事務和模式操作
- 查詢生成器(Query Builder): 使用簡單抽象層查詢數據庫
- 活動記錄(Active Record): 活動記錄對象關系映射(ORM),檢索和操作記錄、定義關聯關系
- 數據庫遷移(Migrations): 在團體開發中對你的數據庫使用版本控制
- Sphinx
- Redis
- MongoDB
- ElasticSearch
- 接收用戶數據(Getting Data from Users)
- 創建表單(Creating Forms)
- 輸入驗證(Validating Input)
- 文件上傳(Uploading Files)
- 收集列表輸入(Collecting Tabular Input)
- 多模型同時輸入(Getting Data for Multiple Models)
- 顯示數據(Displaying Data)
- 格式化輸出數據(Data Formatting)
- 分頁(Pagination)
- 排序(Sorting)
- 數據提供器(Data Providers)
- 數據小部件(Data Widgets)
- 操作客戶端腳本(Working with Client Scripts)
- 主題(Theming)
- 安全(Security)
- 認證(Authentication)
- 授權(Authorization)
- 處理密碼(Working with Passwords)
- 客戶端認證(Auth Clients)
- 安全領域的最佳實踐(Best Practices)
- 緩存(Caching)
- 概述(Overview)
- 數據緩存(Data Caching)
- 片段緩存(Fragment Caching)
- 分頁緩存(Page Caching)
- HTTP 緩存(HTTP Caching)
- RESTful Web 服務
- 快速入門(Quick Start)
- 資源(Resources)
- 控制器(Controllers)
- 路由(Routing)
- 格式化響應(Response Formatting)
- 授權驗證(Authentication)
- 速率限制(Rate Limiting)
- 版本化(Versioning)
- 錯誤處理(Error Handling)
- 開發工具(Development Tools)
- 調試工具欄和調試器(Debug Toolbar and Debugger)
- 使用 Gii 生成代碼(Generating Code using Gii)
- TBD 生成 API 文檔(Generating API Documentation)
- 測試(Testing)
- 概述(Overview)
- 搭建測試環境(Testing environment setup)
- 單元測試(Unit Tests)
- 功能測試(Functional Tests)
- 驗收測試(Acceptance Tests)
- 測試夾具(Fixtures)
- 高級專題(Special Topics)
- 高級應用模版(Advanced Project Template)
- 從頭構建自定義模版(Building Application from Scratch)
- 控制臺命令(Console Commands)
- 核心驗證器(Core Validators)
- 國際化(Internationalization)
- 收發郵件(Mailing)
- 性能優化(Performance Tuning)
- 共享主機環境(Shared Hosting Environment)
- 模板引擎(Template Engines)
- 集成第三方代碼(Working with Third-Party Code)
- 小部件(Widgets)
- Bootstrap 小部件(Bootstrap Widgets)
- jQuery UI 小部件(jQuery UI Widgets)
- 助手類(Helpers)
- 助手一覽(Overview)
- Array 助手(ArrayHelper)
- Html 助手(Html)
- Url 助手(Url)