iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0
Claude AI

用 AI Agent 重構一套無框架的 legacy PHP 系統系列 第 27 篇

Day 27:案例——從 error_log/trigger_error 遷移到結構化 logger

  • 分享至 

  • xImage
  •  

前言:這兩種寫法看起來都是「記個錯誤」,行為卻完全不同

「這段程式碼用 error_log() 記了個錯誤,那段用 trigger_error(),反正都是記錄錯誤訊息,遷移的時候應該可以用同一套邏輯處理吧?」

這是我在推動這個遷移時,一開始也有的想法——直到真的去查證這兩個函式的行為,才發現它們的差異遠比「用哪個函式」更關鍵,直接影響遷移時該怎麼設計轉換規則。

今日目標

  • 認識 error_log() 跟 trigger_error() 在攔截行為上的關鍵差異
  • 理解為什麼把兩者當成同一類問題處理,會漏掉真正的風險
  • 看清楚「結構化 context」在遷移到 PSR-3 logger 時解決了什麼問題
  • 建立遷移 legacy 錯誤處理程式碼前,先查證行為差異的習慣

兩個函式,兩種完全不同的攔截行為

error_log() 完全沒有被任何全域機制攔截——它就是單純把訊息寫到設定好的地方(可能是檔案、可能是系統 log),沒有任何攔截點可以在它執行的當下插入額外的處理邏輯,例如加上結構化的 context、或者觸發告警。

trigger_error() 則不一樣——它會觸發 PHP 的錯誤層級機制,如果系統裡有註冊全域錯誤處理器(set_error_handler()),這個處理器可以攔截到這次呼叫,做一些額外的事情。攔截點會自動拿到 PHP 內建附帶的資訊(錯誤訊息、發生的檔案、行號),但即使被攔截到,除了這些 PHP 自動帶的欄位之外,攔截點完全拿不到任何業務層的結構化資訊——呼叫的人是誰、當下的請求是誰觸發的、相關的業務欄位是什麼,這些資訊如果沒有手動塞進那串訊息文字裡,攔截點一樣拿不到。

為什麼把兩者當同一類問題處理會出問題

如果遷移時把 error_log() 跟 trigger_error() 都當成「反正都是舊式錯誤記錄,統一轉成 logger()->error(...) 就好」,看起來邏輯一致,但會漏掉一個關鍵事實:這兩種呼叫在遷移前的實際運作方式不一樣,遷移後如果沒有針對這個差異做對應處理,可能會意外改變系統行為,而不是單純换一種寫法記錄同一件事。

具體來說:如果系統裡原本就有全域 handler 攔截 trigger_error(),並且這個 handler 做了某些事情(例如把特定層級的錯誤轉發到某個告警管道),直接把這段呼叫換成 logger()->error(...) 而沒有確認新的 logger 設定是否涵蓋了原本 handler 做的事,可能會讓原本「靜默運作」的告警機制在遷移後消失,卻沒有任何錯誤訊息告訴你這件事——因為表面上「這段程式碼還是在記錄錯誤」,測試也可能還是綠燈,只是原本會觸發的下游動作不見了。

用一組對照來看這個差異:

❌ 把兩者當同一類問題,統一轉換:
「這段用 error_log(),那段用 trigger_error(),
 反正都是記錄錯誤,全部改成 logger()->error() 就好。」
→ 沒有查證 trigger_error() 原本有沒有被全域 handler 攔截、
  攔截後做了什麼,遷移後這些下游行為可能悄悄消失

✅ 先查證各自原本的攔截行為,再決定遷移方式:
「這段 error_log() 沒有被任何機制攔截,直接轉換風險低;
 那段 trigger_error() 有被全域 handler 攔截,
 攔截後會做 XX 事情,遷移時要確認新的 logger 設定
 是否需要額外設定才能涵蓋這個行為,不能只是換個函式名稱。」
→ 遷移前先分清楚兩種呼叫原本的實際行為,
  才能判斷遷移會不會意外改變系統行為

PSR-3 結構化 logger 解決了什麼

遷移到結構化的 PSR-3 logger(例如 logger()->error($message, $context))之後,真正的改善不只是「換了個函式名稱」,而是把原本只能塞在一串文字裡的資訊,拆成明確的結構化欄位——哪個 channel、哪個請求觸發的、相關的業務欄位是什麼,這些都可以獨立當成 context 欄位傳入,而不是全部擠壓成一段給人讀的文字訊息。

這個改變對下游處理很關鍵:結構化的 context 可以被下游系統(例如監控平台)直接查詢、過濾、統計,而不需要對著一串自然語言文字做字串比對來猜測「這個錯誤是不是我在意的那一類」。這正是「記錄一件事」跟「記錄一件事、並且讓這件事可以被後續系統理解跟利用」之間的差距。

今日思考題

如果你手上的 legacy 系統也有 error_log/trigger_error 這類舊式錯誤處理程式碼:你知道有哪些呼叫背後其實有全域 handler 在攔截並做額外處理嗎?如果不確定,遷移前你會怎麼查證?

今日重點回顧

  • error_log() 完全不會被任何全域機制攔截,trigger_error() 可能被攔截,但攔截點拿不到結構化 context
  • 把兩者當成同一類問題統一轉換,可能漏掉原本被攔截後觸發的下游行為
  • 遷移前要先查證每一種呼叫原本的實際攔截行為,不能只看「這是不是在記錄錯誤」這個表面共通點
  • PSR-3 結構化 logger 的真正價值是讓 context 可以被下游系統理解跟利用,不只是換了函式名稱

明日預告

明天要換一個角度:像這樣一個涉及全站數百支檔案的長期遷移計畫,該怎麼追蹤進度——尤其是進度清單本身也會過期這件事,該怎麼處理。


上一篇
Day 26:案例——日誌去重分組鍵設計錯誤,正式環境資料才踩到的坑
下一篇
Day 28:重構進度怎麼追蹤——一個尚未完成的長期遷移計畫管理法
系列文
用 AI Agent 重構一套無框架的 legacy PHP 系統 共 28 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言