PHP, Uncategorized, 工具

plain PHP 搭配 Slack 進行錯誤追蹤、回報(Error Tracking、Error Handling)

錯誤追蹤、回報非常重要,看到的錯誤才知道怎麼修。

現今 PHP 流行的 Laravel 有很好的 Error Tracking, Error Handling。但 plain PHP 怎麼辦呢?
在 production 為了安全考量會設定 error_reporting(0); 🙈 隱藏錯誤訊息,但…眼不見為淨了嗎?

silent error” 是非常可怕的!! 能正確的記錄錯誤訊息才能有效 debug。
使用 PHP 原生方式將 PHP 錯誤訊息傳送至 Slack 進行通知與記錄。


為什麼需要 Error Tracking、Error Handling

線上服務常常因為快速開發、快速上線或安全考量,進而關閉錯誤訊息。
但是… 壞掉的時候怎麼辦? 怎麼修? 是誰壞了?

誰知道呢?
發生錯誤時…
除了通靈沒人知道壞了什麼


然而~
只要做好錯誤處理(Error Tracking、Error Handling) 就算把錯誤訊息關閉
也不要緊~

error_reporting(0);

透過下面兩個的方式,依然可以截取 PHP 錯誤訊息。

set_error_handler() 自定錯誤處理

PHP 原生函式
https://www.php.net/manual/en/function.set-error-handler.php

設定一個 function 負責接收處理錯誤內容(error_handler)
透過這個 function 分別可以取得相關訊息為…

  • 錯誤編號
  • 錯誤訊息
  • 發生錯誤的檔案
  • 發生錯誤的檔案行數
  • 所有 PHP runtime 環境變數

example code: set_error_handler()


register_shutdown_function()

為 PHP 原生函式
https://www.php.net/manual/en/function.register-shutdown-function.php
同上,設定一個 function 負責處理

這個 function 會在以下幾種情況被呼叫:

  • 程式結束時
  • exit() 或 die() 執行時(同上)
  • 發生異常時, notice, warning, error 或 exception

再配合 error_get_last(),就能取得當下的錯誤、異常訊息~

example code: register_shutdown_function()


實例

說那麼多,直接上 code!!!

Slack Webhook

開始前~ 先申請 slack webhook API,取得 api url(token)

先準備好,一個 slack workspace,然後透過 incoming webhook 處理訊息
https://api.slack.com/incoming-webhooks
https://slack.com/apps/A0F7XDUAZ-incoming-webhooks

add incoming webhook to slack
第一次使用,將 slack APP 加至 slack workspace

進行一些簡單設定後
.
.
.
可以選擇將訊息傳送至個人或 channel

get slack webhook url
取得 webhook url

PHP error handler

準備一支 php file
(詳細內容,再依自己需求做調整,當然比較建議封裝成 class~)

common.php
<?php
// 全開或全關 php error
// error_reporting(E_ALL);
error_reporting(0);

// 取代為你自己的 slack webhook url
define('SLACK_WEBHOOK', 'https://hooks.slack.com/services/Txxxx5/B0xxxxxxxxxxxxxxxxx87s');



set_error_handler('common_error_handler');
register_shutdown_function("shutdown_error_handler");

function FriendlyErrorType($type)
{
    switch ($type) {
        case E_ERROR: // 1 //
            return 'E_ERROR';
        case E_WARNING: // 2 //
            return 'E_WARNING';
        case E_PARSE: // 4 //
            return 'E_PARSE';
        case E_NOTICE: // 8 //
            return 'E_NOTICE';
        case E_CORE_ERROR: // 16 //
            return 'E_CORE_ERROR';
        case E_CORE_WARNING: // 32 //
            return 'E_CORE_WARNING';
        case E_COMPILE_ERROR: // 64 //
            return 'E_COMPILE_ERROR';
        case E_COMPILE_WARNING: // 128 //
            return 'E_COMPILE_WARNING';
        case E_USER_ERROR: // 256 //
            return 'E_USER_ERROR';
        case E_USER_WARNING: // 512 //
            return 'E_USER_WARNING';
        case E_USER_NOTICE: // 1024 //
            return 'E_USER_NOTICE';
        case E_STRICT: // 2048 //
            return 'E_STRICT';
        case E_RECOVERABLE_ERROR: // 4096 //
            return 'E_RECOVERABLE_ERROR';
        case E_DEPRECATED: // 8192 //
            return 'E_DEPRECATED';
        case E_USER_DEPRECATED: // 16384 //
            return 'E_USER_DEPRECATED';
    }
    return "";
}

function shutdown_error_handler()
{
    $lasterror = error_get_last();
    $type = $lasterror['type'];
    $typeName = FriendlyErrorType($type);
    $message = $lasterror['message'];
    $file = $lasterror['file'];
    $line = $lasterror['line'];
    if (isset($lasterror) && in_array($typeName, ['E_ERROR', 'E_PARSE', 'E_COMPILE_ERROR', 'E_USER_ERROR'])) {
        $payload = [
            'text' => "
Error ({$typeName}) | PHP Stopped \n
File ({$file}:{$line}) \n
Message ({$message}) \n",
        ];
        $ch = curl_init(SLACK_WEBHOOK);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            'Content-Type: application/json',
        ));
        $result = curl_exec($ch);
        curl_close($ch);
        if ($result === false) {
            die('Curl error: ' . curl_error($ch));
        }
    }
}

function common_error_handler($number, $message, $file, $line, $vars)
{
    $message = '{"text": "('
        . $message
        . '). An error (' . $number . ') occurred on line ' . $line . ' and in the file: ' . $file . '."}';

    $message = array('payload' => $message);
    $c = curl_init(SLACK_WEBHOOK);
    curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($c, CURLOPT_POST, true);
    curl_setopt($c, CURLOPT_POSTFIELDS, $message);
    $r = curl_exec($c);
    curl_close($c);

    if (($number !== E_NOTICE) && ($number < 2048)) {
        die("There was an error. Please try again later.");
    }
}


接著
建立一支專門用來發生錯誤的 php file(?!)

index.php
<?php

require_once 'common.php';

echo '@test-error.php';

先確認 index.php 運作正常後,再開始讓他發生錯誤 XD

再次修改 index.php,故意讓他出錯

<?php

require_once 'common.php';

echo '@test-error.php';




throw new Exception("my Exception!", 500);

如果 common.php 裡啟用 error_reporting(0); 那畫面上不會有任何錯誤訊息喔~


Slack 接收錯誤訊息

只要 slack webhook url 和 curl 都正常,就能在 slack 這收到類似下面的訊息~

slack receive message
這樣也就完成簡單的 plain PHP 錯誤追蹤、錯誤回報(Error Tracking、Error Handling)
更進階的動作就讓大家自由發揮~

其它 Error Tracking 工具

當然~ 高科技(?) 的工具、服務也是有~

以下三個服務是我比較常用,也有免費方案
(一點點個人感想)

Ref

『Slack Bot To Handle PHP Errors Real Time』
https://medium.com/@rajeshjnair/slack-bot-to-handle-php-errors-real-time-d25b3b401b93

Leave a Reply