紀錄的問題和解法(Logging problems and solutions)

===============================

在Yii裡面,紀錄(log)是有很大彈性的。基本的紀錄很簡單,但是有時需要花很多時間設定,來得到想要的紀錄方式。
以下是一些需求的解法,希望讀者能找到適合自己需要的。

1. 404 錯誤只寫檔紀錄,其他錯誤寄 email 提醒

遇到錯誤我們有時會希望有 email 提醒,但是 404 錯誤太常發生,跟其他錯誤不同,不值得寄 email 提醒,只值得紀錄進log檔裡面。
我們來實做看看這樣的需求:

'components' => [
    'log' => [
        'targets' => [
            'file' => [
                'class' => 'yii\log\FileTarget',
                'categories' => ['yii\web\HttpException:404'],
                'levels' => ['error', 'warning'],
                'logFile' => '@runtime/logs/404.log',
            ],
            'email' => [
                'class' => 'yii\log\EmailTarget',
                'except' => ['yii\web\HttpException:404'],
                'levels' => ['error', 'warning'],
                'message' => ['from' => '[email protected]', 'to' => '[email protected]'],
            ],
        ],
    ],
],

當遇到沒有處理的例外(exception )狀況時,Yii 會進行紀錄,並顯示錯誤訊息或者客製化錯誤頁面給使用者。 這邊實際寫進紀錄的是例外訊息,而例外類別名稱則是當我們在設定錯誤訊息目標時,可以用來作為篩選訊息的分類。

404 錯誤可能是被yii\web\NotFoundHttpException觸發或者自己產生。兩種狀況下,處理該例外的類別都相同,並且繼承於 \yii\web\HttpException類別。另外 Yii 在這類錯誤紀錄的分類裡,使用:加上 HTTP 狀態碼做結尾,這其實在錯誤紀錄上是比較特殊的 。所以上面我們可以用這種方式,搭配使用categories來包括,使用except來排除 404 錯誤資訊,而不會影響其他的錯誤訊息。

2. 即時紀錄

Yii 預設會等到程式運行結束,或者等紀錄程式累積到足夠的紀錄數量──預設是一千筆──才會把紀錄寫進檔案內。不過我們有可能希望能夠即時紀錄。比方說,當我們正在做重要的測試,而且是一邊做一邊監控 log 檔。這種狀況之下,我們要透過修改 config 檔來改變設定:

'components' => [
    'log' => [
        'flushInterval' => 1, // <-- 改這裡
        'targets' => [
            'file' => [
                'class' => 'yii\log\FileTarget',
                'levels' => ['error', 'warning'],
                'exportInterval' => 1, // <-- 以及這裡
            ],
        ],
    ]
]

3. 在不同檔案裡面紀錄不同狀況

程式有許許多多不同的功能,我們通常會希望藉由紀錄來監控這些功能的運作。可是,如果所有的東西都紀錄在同一個檔案裡面,這個檔案會變得太大、太難以維護了。比較好的作法是將不同的功能,紀錄在個別的紀錄檔內。

舉例來說,假設我們的網站有兩個功能:商品目錄以及購物籃。我們希望將紀錄分別寫進 catalog.log 和 basket.log。這時我們需要為紀錄資訊建立種類。將 config 檔修改如下,以連接不同種類的紀錄資訊與紀錄檔:

'components' => [
    'log' => [
        'targets' => [
                [
                    'class' => 'yii\log\FileTarget',
                    'categories' => ['catalog'],
                    'logFile' => '@app/runtime/logs/catalog.log',
                ],
                [
                    'class' => 'yii\log\FileTarget',
                    'categories' => ['basket'],
                    'logFile' => '@app/runtime/logs/basket.log',
                ],
        ],
    ]
]

之後,藉由在紀錄函式的第二個參數裡加進種類名稱,我們就可以將錯誤寫進不同的紀錄檔。
範例:

\Yii::info('catalog info', 'catalog');
\Yii::error('basket error', 'basket');
\Yii::beginProfile('add to basket', 'basket');
\Yii::endProfile('add to basket', 'basket');

4. 在紀錄中忽略自己的操作

4.1. 問題

當有使用者註冊時,我們希望可以收到 email。但是不要在自己測試註冊的時候,還寄信提醒。

4.2. 解法

At首先,在config.php設定紀錄目標:

'log' => [
    // ...
    'targets' => [
            // 「email」是我們紀錄目標對應的關鍵字
            'email' => [  
                'class' => 'yii\log\EmailTarget',
                'levels' => ['info'],
                'message' => ['from' => '[email protected]', 'to' => '[email protected]'],
            ],
    // ...

然後,像是在ControllerbeforeAction,讀者可以建立一個狀況:

    public function beforeAction($action)
    {
        // '127.0.0.1' - 或換成讀者自己的 IP
        if (in_array(@$_SERVER['REMOTE_ADDR'], ['127.0.0.1'])) {
            Yii::$app->log->targets['email']->enabled = false; // 這裡取消我們的target
        }
        return parent::beforeAction($action);
    }

5. 留下完整紀錄但是顯示不同錯誤訊息

5.1. 問題

我們想紀錄具體的錯誤,但是只對使用者顯示粗略的解釋。

5.2. 解法

如果你將例外成功的 catch ,那麼 log target 就不會生效。假設我們原本設定的紀錄目標如下:

'components' => [
    'log' => [
        'targets' => [
            'file' => [
                'class' => 'yii\log\FileTarget',
                'levels' => ['error', 'warning'],
                'logFile' => '@runtime/logs/error.log',
            ],
        ],
    ],
],

作為示範,我們修改actionIndex如下:

    public function actionIndex()
    {
        throw new ServerErrorHttpException('程式有問題喔!');
        // ...

這個例外沒有被 catch,進入index頁面,你會看到錯誤信息,並且error.log檔也會有一樣的紀錄。

現在我們修改actionIndex()

    public function actionIndex()
    {
        try {
            throw new ServerErrorHttpException('程式有問題喔!'); // 這邊是原本的程式
        }
        catch(ServerErrorHttpException $ex) {
            Yii::error($ex->getMessage()); // 給我們自己看的具體訊息
            throw new ServerErrorHttpException('網頁問題,抱歉'); // 給使用者看的粗略訊息
        }
    // ..

這樣的話,我們在瀏覽器上會看到網頁問題,抱歉,但是在error.log裡面,我們會同時看到兩個錯誤訊息。這邊的第二個錯誤訊息只是給使用者看,照理是不需要被紀錄的。

我們加上category來排除不必要的紀錄。

config

'file' => [
    'class' => 'yii\log\FileTarget',
    'levels' => ['error', 'warning'],
    'categories' => ['serverError'], // 加入種類
    'logFile' => '@runtime/logs/error.log',
],

actionIndex()

catch(ServerErrorHttpException $ex) {
    Yii::error($ex->getMessage(), 'serverError'); // 加入種類
    throw new ServerErrorHttpException('網頁問題,抱歉');
}

這樣error.log裡面,我們只會看到程式有問題喔!的錯誤紀錄。

5.3. 這樣做的好處

如果今天是遇到 bad request 例外,我們可能希望能夠照 Yii 預設的,紀錄並顯示相同的錯誤訊息給使用者。
用上面的方法很容易達成這個目的,因為我們在 catch 這一段,只有處理到ServerErrorHttpException這一類例外 。

所以,如果我們丟出的例外是:

throw new BadRequestHttpException('提供的信箱不可用');

那麼使用者就能如我們希望的看到該訊息。

6. 其他資料

results matching ""

    No results matching ""