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

第一次使用,將 slack APP 加至 slack workspace

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

取得 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 這收到類似下面的訊息~

這樣也就完成簡單的 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

可樂

Recent Posts

Drone CI/CD 配合 Github 使用 Rsync 進行 Deploy

jenkins、circleci、travis 或 Gitlab CI 皆為目前暫知名的 CI/CD 服務,各自缺點也不言而喻...過於肥大、收費略高(?)、速度不夠快執問題...此時使用 go language 開發的 Drone 就出現啦,完全 docker 容器化的運行方式讓整個 CI…

4 years ago

Nginx brotli 設定

網頁壓縮技術中 gzip 很好用,deflate 己經過時,但你聽過 brotli 嗎? 有著比 gzip 更好、更快的壓縮效率。看起來利大於弊有什麼不用他的理由嗎?簡單從優、缺點來看 brotli!到底 brotli 布羅特利是什麼、如何設定呢。 目前大多的 web server…

4 years ago

本機使用 Docker 容器內 PHP (wrapper/expose PHP)

為什麼要讓本機使用 Docker 內 PHP? 情境... docker 容器內用的是 PHP 7.4 但你的開發本機還在跑 PHP 5.6 或是更舊,因為 dockerize 的關係會將所有相關環境都轉移到…

4 years ago

為什麼你需要密碼管理工具

為什麼你需要密碼管理工具現代人一天下來需要輸入多少組密碼,工作與生活己經和密碼密不可分! 除了足夠全安的密碼,密碼記錄、儲存的方式又足夠安全嗎?密碼管理工具可以帶來什麼幫助呢? 為什麼你需要密碼管理工具 資安問題!!大多人說著沒做壞事不怕被偷資料、監聽。嚴重曝露出現代人的基本科技素養的低落和無知 🤯 密碼的使用無所不在!! 行動裝置的普及,APP 、手機遊戲、銀行帳戶所有和生活相關的東西都需要密碼!!facebook, line 只要打開 APP 也會輸入密碼只是他是自動輸入、一般情況不可視 (auth token) 一般人最常發生的密碼資安問題…

4 years ago

簡單使用 Mysql Partition 優化查詢

mysql 資料表分區 mysql table partition 從架構上調整 mysql 的查詢效率。mysql DB 的優化可以簡單也能複雜,除了調整設定值。也可以透過水平分割(Horizontal Partitioning)、垂直分割(Vertical Partitioning) 分庫或分表將資料分散儲存減少資料搜尋、group by 時的效能消耗。拆開批次處理,理論上效率都會變好,本文就水平分割的…

4 years ago

如何 Debug Node APP 配合 Docker 與 VsCode

透過 vscode Debug 利用中斷點 (breakpoints) 讓開發、偵錯更聰明。 加快除錯速度,而不是用傳統的 console log 方式查看變數、物件內容找問題。 本篇教你如何用 vscode + node +…

4 years ago