無論你對產品工程師有沒有興趣,只要你做的產品真的有人使用,大概都遇過使用者或其他同事跑來回報:
「這裡好像壞掉了。」
有些問題很單純,光看錯誤訊息就知道發生在哪裡,也能很快進行處理。但更多時候,拿到的資訊可能只有「按了沒反應」、「剛才不能用」甚至「好像怪怪的」。如果程式碼又經過編譯與打包,想單靠錯誤訊息裡的 Stack Trace 找到原始碼位置,就更加困難。
當然,也可以回頭詢問使用者是怎麼觸發的,但如果是一個面向公眾的產品,每次出問題都要請使用者協助工程師重現,多少還是會損害使用者對產品的信任。
因此,收集真實使用者環境中的錯誤,一直是軟體工程中很重要的一環,也有很多產品專門處理這件事情。
我自己最早接觸的是 Sentry。如果單獨比較 Error Tracking,我不認為 PostHog 有非常顯著的功能優勢,甚至在不少工程與 Observability 能力上,Sentry 都更加完整。
但即便如此,我現在還是會優先考慮 PostHog Error Tracking。
原因很簡單:
免費額度真的太香了。
Sentry 的 Developer 免費方案只允許一名使用者,每月包含 5,000 筆 Error;PostHog 不限制團隊成員數,Error Tracking 的免費額度則直接給到每月 100,000 Exceptions。
Session Replay 的差距更明顯:Sentry 免費方案每月包含 50 個 Replay,而 PostHog 則是 5,000 個。
當然,我並不是說 PostHog 可以完全取代 Sentry。單看錯誤追蹤與系統監控,Sentry 的功能仍然更完整,支援的語言與框架也更多。如果你的團隊已經大量使用 Sentry,而且現有流程運作得很好,那就不見得有必要只是為了免費額度而更換。
但如果還沒引進錯誤追蹤系統,或者是不排斥更換,我認為 PostHog Error Tracking 相當值得嘗試。
如果前一篇已經使用 PostHog Wizard 完成安裝,它可能已經順便幫你設定好 Error Tracking。以 Next.js 來說,可以先在專案中搜尋 captureException,確認前後端的 Exception 是否都有被捕捉。PostHog 的 Next.js 安裝流程本身也會要求確認 Exception 已經成功送進 Activity Feed。
例如可以在 Error Boundary 中額外捕捉錯誤:
componentDidCatch(error: globalThis.Error, errorInfo: ErrorInfo) {
// ...
posthog.captureException(error, {
component: this.props.componentName ?? "ErrorBoundary",
component_name: this.props.componentName ?? "ErrorBoundary",
componentStack: errorInfo.componentStack,
screen: `${window.screen.width}x${window.screen.height}`,
screen_height: window.screen.height,
screen_width: window.screen.width,
url: window.location.href,
viewport: `${window.innerWidth}x${window.innerHeight}`,
viewport_height: window.innerHeight,
viewport_width: window.innerWidth,
$current_url: window.location.href,
});
}
除了 Exception 本身之外,也可以附加更多與當下操作有關的資訊。這些 Properties 往往比單純的錯誤訊息更有用,例如交易失敗時,可以一起留下金額、付款管道、訂單類型等資訊。
另一個非常重要的設定是 Source Map。
正式環境中的 JavaScript 通常經過編譯、壓縮與切割,如果沒有 Source Map,即使拿到了 Stack Trace,也可能只看到壓縮後的檔案位置。PostHog 的 Next.js Integration 可以在 Build 時自動產生並上傳 Source Map,再用它還原實際的程式碼位置。
在 Next.js Config 中大致會是:
module.exports = withPostHogConfig(config, {
personalApiKey: process.env.POSTHOG_CLI_API_KEY,
projectId: process.env.POSTHOG_CLI_PROJECT_ID,
host:
process.env.POSTHOG_CLI_HOST ||
process.env.NEXT_PUBLIC_POSTHOG_HOST ||
"https://us.posthog.com",
sourcemaps: {
releaseName: process.env.POSTHOG_SOURCEMAP_RELEASE_NAME,
releaseVersion: process.env.POSTHOG_SOURCEMAP_RELEASE_VERSION,
deleteAfterUpload: true,
},
});
releaseName 與 releaseVersion 也可以協助區分不同應用程式與不同版本,例如前端、後端,或不同的 Deployment。
完成之後,就可以故意觸發一個錯誤,再到 PostHog 的 Error Tracking 頁面看看實際效果。

(Error Tracking 應該預設會開啟。如果沒有,可以在 MY TOOLS 旁邊的齒輪把它啟用。剛開始其實可以全部打開,之後再依照使用情況把不需要的功能關掉。)

如果有在 captureException 中附帶額外資訊,就可以在 Properties Tab 中查看。例如我們在交易相關的錯誤裡,會另外記錄金額、付款管道等資訊。

Session Tab 則可以把錯誤放回使用者當時的操作脈絡中,看到自訂事件、頁面瀏覽、Console Log,以及最後發生的 Exception。
這也是我覺得 PostHog Error Tracking 很方便的地方。除了錯誤本身之外,你還能往前看到使用者在出錯之前做了哪些操作,而不是只拿著一段 Stack Trace 猜測問題是怎麼發生的。

由於 Console Log 很容易包含 Token、使用者資料或其他敏感資訊,因此 PostHog 預設不會捕捉。如果確定內容適合上傳,可以在 posthog.init 中加入:
enable_recording_console_log: true
也可以直接從 Session Replay 的設定中開啟。

比較美中不足的是,目前 Error Tracking 裡的 Session Timeline 沒有顯示 Autocapture 事件。
這代表如果某個錯誤是由 UI 操作觸發,例如使用者點了某個按鈕、展開某個選單之後發生 Exception,光看這個 Timeline 有時候還是不容易知道完整的觸發流程。
目前我找到的一個替代方式,是先透過使用者來定位。
首先從 Error Tracking 的錯誤資訊進入對應的使用者頁面:

接著到 Events Tab 找到同一時間附近的 Exception,再直接觀察它前後出現了哪些 Autocapture Event。

這時我們終於有機會回答那個最麻煩的問題:
「使用者到底做了什麼,才把它弄壞的?」
知道之後,就可以重現、定位,接著安排修復。
不過,到這裡其實還只解決了一半的問題。
Error Tracking 能幫我們知道使用者有沒有因為程式錯誤而失敗,但一個完全沒有 Exception 的產品,也不代表使用者真的完成了他想做的事情。
使用者順利打開頁面、點擊按鈕、API 全部回傳 200,最後卻沒有完成他原本想完成的事——從系統角度來看一切正常,從使用者角度來看,產品仍然可能是失敗的。
所以接下來,我們要開始處理另一個問題:
使用者來到我們的產品,到底是想完成什麼?
如果你願意花 30 秒留下回饋,我會用這些意見來調整後續文章:分享你的意見
