<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Customer-Support on Tarragon</title><link>https://tarrragon.github.io/blog/tags/customer-support/</link><description>Recent content in Customer-Support on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 14 Oct 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/customer-support/index.xml" rel="self" type="application/rss+xml"/><item><title>11.16 面向終端使用者的錯誤碼與請求編號：錯誤畫面的欄位、請求編號的產生與傳遞、錯誤碼對照表、客服收件欄位、客服查詢工具的權限與 log 保留期</title><link>https://tarrragon.github.io/blog/backend/11-api-design/end-user-error-code-and-reference-id/</link><pubDate>Wed, 14 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/end-user-error-code-and-reference-id/</guid><description>&lt;p>這一篇處理終端使用者遇到錯誤之後，從他看到的畫面、到客服收件、再到工程查到那一次請求，系統在每一段要提供什麼。API 呼叫方（另一個系統）收到錯誤之後怎麼回報給 API 提供者，是同一套機制在系統之間的版本，見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/error-feedback-loop/" data-link-title="錯誤回報的回饋迴路：request-id、trace 與呈現回報分工" data-link-desc="consumer 收到錯誤之後怎麼跟 provider 溝通：error 要帶什麼定位鉤子、同一個錯誤怎麼分別投影給使用者與回報、持續錯誤什麼時候該升級">錯誤回報的回饋迴路：request-id、trace 與呈現回報分工&lt;/a>；錯誤要怎麼分類、錯誤回應的格式怎麼定，見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/error-model-design/" data-link-title="11.4 錯誤模型設計" data-link-desc="錯誤該分幾類、格式怎麼定才有演化空間、機器判讀跟人類訊息怎麼分工 — 錯誤作為契約一級公民的設計判斷標準">11.4 錯誤模型設計&lt;/a>。客服與工程之間的流程（什麼條件轉給值班、工程怎麼回覆）在 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/support-escalation-and-engineering-reply/" data-link-title="8.24 客服回報的處理：客服當場答覆時用的資訊、轉給值班的條件、工程查證後給客服的回覆、查證結果寫回知識文章、告警與查詢工具，以及流程失效時的徵兆" data-link-desc="客服接到使用者回報之後，哪些資訊讓客服當場答覆（進行中的事故、已知錯誤清單、維護與變更公告、錯誤碼對照表、帳號查詢工具），哪些條件要立即呼叫值班、哪些開單在上班時間處理，工程查證之後回覆給客服的內容，同一類回報重複出現時怎麼把查證結果寫回知識文章、告警規則或查詢工具，以及從客服單與事故紀錄看出流程在哪一段失效">8.24 客服回報的處理&lt;/a>，這一篇寫那個流程用到的系統設計。&lt;/p>
&lt;p>這一篇的例子是一個短網址服務（需求規格見 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/url-shortener-requirements/" data-link-title="0.23 短網址服務的需求定義：自建或購買、保存期限、目的地修改與濫用處理的規格" data-link-desc="短網址服務交給工程之前要定案的需求決定：自建或購買、點擊資料的地位、連結的保存承諾、目的地能否修改、建立者的範圍、短碼的可猜測性、網域策略與可用性分級；多數答案由商業模型推出，目的地修改、短碼可猜測性與可用性分級由使用情境決定">0.23 短網址服務的需求定義&lt;/a>）：入口 Nginx 把請求轉給後端程式，登入的使用者建立短網址、看點擊統計，訪客點短網址時由服務轉址。&lt;/p>
&lt;h2 id="錯誤碼與請求編號">錯誤碼與請求編號&lt;/h2>
&lt;p>使用者回報錯誤時，工程要從那一句「我剛剛建立短網址失敗」找到兩件事：這是哪一種錯誤，以及是哪一次請求。這兩件事由錯誤畫面上的兩個欄位分別提供。&lt;/p>
&lt;p>&lt;strong>錯誤碼&lt;/strong>代表一類錯誤，同一種錯誤每次出現都一樣，例如 &lt;code>LINK_EXPIRED&lt;/code>（短網址已過期）、&lt;code>QUOTA_EXCEEDED&lt;/code>（本月建立額度用完）。客服拿它去查錯誤碼對照表，就知道這一類錯誤的意思與答覆方式，不必知道是哪一次請求。畫面上的錯誤碼可以直接沿用錯誤回應裡給程式判讀的 code 欄位（見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/error-model-design/" data-link-title="11.4 錯誤模型設計" data-link-desc="錯誤該分幾類、格式怎麼定才有演化空間、機器判讀跟人類訊息怎麼分工 — 錯誤作為契約一級公民的設計判斷標準">11.4 錯誤模型設計&lt;/a>），客服、工程與程式用同一個字串，對照表不必另外維護一份對應；API 的 code 分得比客服需要的細時，由前端對應成較粗的類別再顯示。不論哪一種，對照表的鍵是畫面上顯示的那個字串，而畫面上只放錯誤的類別，不放服務名稱或內部細節。&lt;/p>
&lt;p>&lt;strong>請求編號&lt;/strong>代表單一次請求，每次都不同，例如 &lt;code>9c8561d5272320ba8aafd57145eb9894&lt;/code>。它是那一次請求的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/request-id/" data-link-title="Request ID" data-link-desc="說明單次 request 的識別碼如何支援 log 搜尋與問題定位">Request ID&lt;/a>，工程拿它去 log 與 trace 裡找到那一次請求的完整紀錄。畫面上的欄位名稱也寫「請求編號」，與系統內部用同一個名字，客服與工程說的是同一件事。&lt;/p>
&lt;p>錯誤碼與請求編號缺一個，查證就少一段。只有錯誤碼，客服知道是額度用完，而使用者說「我明明還有額度」時，工程查不出那一次請求實際讀到的額度是多少；只有請求編號，客服手上是一串看不出意思的字元，每一則回報都要轉給工程才知道是什麼錯誤。Microsoft Entra 的登入錯誤頁同時給兩者：畫面上有 &lt;code>AADSTS&lt;/code> 開頭的錯誤碼，展開的疑難排解資訊列出 Request Id、Correlation Id（同一次登入的多個請求共用的編號）與時間戳記，管理員在登入紀錄裡以這些值找到使用者遇到的那一次失敗。系統之間的 API 也是同一個做法：Stripe 在每個回應的 &lt;code>Request-Id&lt;/code> header 帶一個 &lt;code>req_&lt;/code> 開頭的編號，並請使用者聯絡客服時附上它。&lt;/p>
&lt;h2 id="錯誤畫面上的欄位">錯誤畫面上的欄位&lt;/h2>
&lt;p>錯誤畫面要同時給使用者與客服用，所以畫面上放四個欄位：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>給使用者看的訊息&lt;/strong>：發生了什麼、他現在能做什麼，不含內部的服務名稱、資料表或堆疊追蹤（stack trace）。錯誤訊息透露內部結構會成為攻擊者的偵察材料，見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/errorchain-owasp-error-handling/" data-link-title="11.C77 OWASP error handling：錯誤訊息是攻擊者的偵察面" data-link-desc="非預期錯誤回 generic response、細節只留 server side log — provider 少暴露的安全端論證、跟 AIP-193 的機器可讀路線形成張力">OWASP error handling：錯誤訊息是攻擊者的偵察面&lt;/a>；訊息怎麼分成標題、說明與行動，見 &lt;a href="https://tarrragon.github.io/blog/ux-design/04-error-recovery/error-message-principles/" data-link-title="錯誤訊息撰寫原則" data-link-desc="錯誤訊息的兩個職責：使用者能讀懂發生什麼、使用者能決定下一步做什麼">錯誤訊息撰寫原則&lt;/a>，錯誤碼、請求編號與發生時間這三個欄位，放在那篇說的次要位置（折疊區塊或複製按鈕）。&lt;/li>
&lt;li>&lt;strong>錯誤碼&lt;/strong>。&lt;/li>
&lt;li>&lt;strong>請求編號&lt;/strong>，旁邊放一個複製按鈕。&lt;/li>
&lt;li>&lt;strong>發生時間&lt;/strong>，標明時區。&lt;/li>
&lt;/ul>
&lt;p>訊息與錯誤碼對應的是錯誤的類別，可以在程式裡逐一寫好；請求編號與時間是每一次請求才有的值，要由產生錯誤的那一段程式帶到畫面上。&lt;/p>
&lt;p>請求編號的長度要和回報的管道一起考慮。網頁與 App 上，使用者按複製再貼進客服表單，32 個字元不成問題；電話客服要使用者唸出來，32 個十六進位字元太長。做法之一是畫面顯示完整的編號、電話上只請使用者唸前 8 碼，工程以前 8 碼加上帳號與時間範圍篩選 log。8 碼十六進位有 16⁸ 種、約 43 億種組合。設工程篩選的那個帳號與時間範圍裡共有 n 次請求，除了使用者要找的那一次，其餘請求之中至少有一次的前 8 碼也相同的機率約為 n ÷ 43 億：一小時一千次請求時約是四百萬分之一，帳號的請求量大時把 n 換成自己的數字重算。&lt;/p>
&lt;p>請求編號要是隨機產生的值，不能由帳號編號、遞增序號或時間組成；要讓使用者在電話上只唸前 8 碼時，也不帶固定的前綴。由帳號編號組成的編號讓別人從編號推出帳號；遞增序號讓別人推算服務的請求量，也讓別人猜出其他使用者的編號；以時間開頭的編號前幾碼是時間戳記，UUIDv7（開頭 48 位元是毫秒時間戳記的 UUID）的前 8 碼約每 65 秒才變一次，ULID（開頭是時間戳記、可依時間排序的編號格式）的前 8 碼約每秒變一次，同一段時間內的請求前 8 碼相同；固定前綴（例如 &lt;code>app-&lt;/code>）佔掉前幾碼，前 8 碼裡的隨機字元也少了；Stripe 的 &lt;code>req_&lt;/code> 這類前綴不影響複製貼上，只影響唸前幾碼的做法。這幾種情形下，電話上只唸前 8 碼就分不出是哪一次請求。&lt;/p>
&lt;h2 id="請求編號的產生與傳遞nginx-入口後端程式前端與-app">請求編號的產生與傳遞：Nginx 入口、後端程式、前端與 App&lt;/h2>
&lt;p>請求編號要在 log 裡查得到，前提是同一個值同時出現在三個地方：回給使用者的錯誤畫面、入口的 access log，以及後端程式處理這個請求時寫的每一行 log。最直接的做法是由請求最先抵達的入口產生請求編號，再把它傳下去。&lt;/p>
&lt;p>Nginx 從 1.11.0 起提供 &lt;code>$request_id&lt;/code> 變數，每個請求以 16 個隨機位元組產生，寫成 32 個十六進位字元。以下設定讓 Nginx 把它寫進 access log、加在回應的 header 上，並以 &lt;code>X-Request-Id&lt;/code> header 傳給後端程式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-nginx" data-lang="nginx">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 以下兩行寫在 http 區塊（server 之外）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">log_format&lt;/span> &lt;span class="s">withid&lt;/span> &lt;span class="s">&amp;#39;&lt;/span>&lt;span class="nv">$remote_addr&lt;/span> &lt;span class="s">[&lt;/span>&lt;span class="nv">$time_iso8601]&lt;/span> &lt;span class="s">&amp;#34;&lt;/span>&lt;span class="nv">$request&amp;#34;&lt;/span> &lt;span class="nv">$status&lt;/span> &lt;span class="s">rid=&lt;/span>&lt;span class="nv">$request_id&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">access_log&lt;/span> &lt;span class="s">/var/log/nginx/access.log&lt;/span> &lt;span class="s">withid&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">server&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="kn">listen&lt;/span> &lt;span class="mi">80&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 每一個回應（包括錯誤回應）都帶上同一個編號
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kn">add_header&lt;/span> &lt;span class="s">X-Request-Id&lt;/span> &lt;span class="nv">$request_id&lt;/span> &lt;span class="s">always&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="kn">location&lt;/span> &lt;span class="s">/&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 把編號傳給後端；後端從這個 header 讀出編號寫進自己的 log
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kn">proxy_set_header&lt;/span> &lt;span class="s">X-Request-Id&lt;/span> &lt;span class="nv">$request_id&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="c1"># app:8080 換成自己後端程式的位址
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kn">proxy_pass&lt;/span> &lt;span class="s">http://app:8080&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這段設定要拆開併進既有的設定檔，各行的位置與作用：&lt;/p></description><content:encoded><![CDATA[<p>這一篇處理終端使用者遇到錯誤之後，從他看到的畫面、到客服收件、再到工程查到那一次請求，系統在每一段要提供什麼。API 呼叫方（另一個系統）收到錯誤之後怎麼回報給 API 提供者，是同一套機制在系統之間的版本，見 <a href="/blog/backend/11-api-design/error-feedback-loop/" data-link-title="錯誤回報的回饋迴路：request-id、trace 與呈現回報分工" data-link-desc="consumer 收到錯誤之後怎麼跟 provider 溝通：error 要帶什麼定位鉤子、同一個錯誤怎麼分別投影給使用者與回報、持續錯誤什麼時候該升級">錯誤回報的回饋迴路：request-id、trace 與呈現回報分工</a>；錯誤要怎麼分類、錯誤回應的格式怎麼定，見 <a href="/blog/backend/11-api-design/error-model-design/" data-link-title="11.4 錯誤模型設計" data-link-desc="錯誤該分幾類、格式怎麼定才有演化空間、機器判讀跟人類訊息怎麼分工 — 錯誤作為契約一級公民的設計判斷標準">11.4 錯誤模型設計</a>。客服與工程之間的流程（什麼條件轉給值班、工程怎麼回覆）在 <a href="/blog/backend/08-incident-response/support-escalation-and-engineering-reply/" data-link-title="8.24 客服回報的處理：客服當場答覆時用的資訊、轉給值班的條件、工程查證後給客服的回覆、查證結果寫回知識文章、告警與查詢工具，以及流程失效時的徵兆" data-link-desc="客服接到使用者回報之後，哪些資訊讓客服當場答覆（進行中的事故、已知錯誤清單、維護與變更公告、錯誤碼對照表、帳號查詢工具），哪些條件要立即呼叫值班、哪些開單在上班時間處理，工程查證之後回覆給客服的內容，同一類回報重複出現時怎麼把查證結果寫回知識文章、告警規則或查詢工具，以及從客服單與事故紀錄看出流程在哪一段失效">8.24 客服回報的處理</a>，這一篇寫那個流程用到的系統設計。</p>
<p>這一篇的例子是一個短網址服務（需求規格見 <a href="/blog/backend/00-service-selection/url-shortener-requirements/" data-link-title="0.23 短網址服務的需求定義：自建或購買、保存期限、目的地修改與濫用處理的規格" data-link-desc="短網址服務交給工程之前要定案的需求決定：自建或購買、點擊資料的地位、連結的保存承諾、目的地能否修改、建立者的範圍、短碼的可猜測性、網域策略與可用性分級；多數答案由商業模型推出，目的地修改、短碼可猜測性與可用性分級由使用情境決定">0.23 短網址服務的需求定義</a>）：入口 Nginx 把請求轉給後端程式，登入的使用者建立短網址、看點擊統計，訪客點短網址時由服務轉址。</p>
<h2 id="錯誤碼與請求編號">錯誤碼與請求編號</h2>
<p>使用者回報錯誤時，工程要從那一句「我剛剛建立短網址失敗」找到兩件事：這是哪一種錯誤，以及是哪一次請求。這兩件事由錯誤畫面上的兩個欄位分別提供。</p>
<p><strong>錯誤碼</strong>代表一類錯誤，同一種錯誤每次出現都一樣，例如 <code>LINK_EXPIRED</code>（短網址已過期）、<code>QUOTA_EXCEEDED</code>（本月建立額度用完）。客服拿它去查錯誤碼對照表，就知道這一類錯誤的意思與答覆方式，不必知道是哪一次請求。畫面上的錯誤碼可以直接沿用錯誤回應裡給程式判讀的 code 欄位（見 <a href="/blog/backend/11-api-design/error-model-design/" data-link-title="11.4 錯誤模型設計" data-link-desc="錯誤該分幾類、格式怎麼定才有演化空間、機器判讀跟人類訊息怎麼分工 — 錯誤作為契約一級公民的設計判斷標準">11.4 錯誤模型設計</a>），客服、工程與程式用同一個字串，對照表不必另外維護一份對應；API 的 code 分得比客服需要的細時，由前端對應成較粗的類別再顯示。不論哪一種，對照表的鍵是畫面上顯示的那個字串，而畫面上只放錯誤的類別，不放服務名稱或內部細節。</p>
<p><strong>請求編號</strong>代表單一次請求，每次都不同，例如 <code>9c8561d5272320ba8aafd57145eb9894</code>。它是那一次請求的 <a href="/blog/backend/knowledge-cards/request-id/" data-link-title="Request ID" data-link-desc="說明單次 request 的識別碼如何支援 log 搜尋與問題定位">Request ID</a>，工程拿它去 log 與 trace 裡找到那一次請求的完整紀錄。畫面上的欄位名稱也寫「請求編號」，與系統內部用同一個名字，客服與工程說的是同一件事。</p>
<p>錯誤碼與請求編號缺一個，查證就少一段。只有錯誤碼，客服知道是額度用完，而使用者說「我明明還有額度」時，工程查不出那一次請求實際讀到的額度是多少；只有請求編號，客服手上是一串看不出意思的字元，每一則回報都要轉給工程才知道是什麼錯誤。Microsoft Entra 的登入錯誤頁同時給兩者：畫面上有 <code>AADSTS</code> 開頭的錯誤碼，展開的疑難排解資訊列出 Request Id、Correlation Id（同一次登入的多個請求共用的編號）與時間戳記，管理員在登入紀錄裡以這些值找到使用者遇到的那一次失敗。系統之間的 API 也是同一個做法：Stripe 在每個回應的 <code>Request-Id</code> header 帶一個 <code>req_</code> 開頭的編號，並請使用者聯絡客服時附上它。</p>
<h2 id="錯誤畫面上的欄位">錯誤畫面上的欄位</h2>
<p>錯誤畫面要同時給使用者與客服用，所以畫面上放四個欄位：</p>
<ul>
<li><strong>給使用者看的訊息</strong>：發生了什麼、他現在能做什麼，不含內部的服務名稱、資料表或堆疊追蹤（stack trace）。錯誤訊息透露內部結構會成為攻擊者的偵察材料，見 <a href="/blog/backend/11-api-design/cases/errorchain-owasp-error-handling/" data-link-title="11.C77 OWASP error handling：錯誤訊息是攻擊者的偵察面" data-link-desc="非預期錯誤回 generic response、細節只留 server side log — provider 少暴露的安全端論證、跟 AIP-193 的機器可讀路線形成張力">OWASP error handling：錯誤訊息是攻擊者的偵察面</a>；訊息怎麼分成標題、說明與行動，見 <a href="/blog/ux-design/04-error-recovery/error-message-principles/" data-link-title="錯誤訊息撰寫原則" data-link-desc="錯誤訊息的兩個職責：使用者能讀懂發生什麼、使用者能決定下一步做什麼">錯誤訊息撰寫原則</a>，錯誤碼、請求編號與發生時間這三個欄位，放在那篇說的次要位置（折疊區塊或複製按鈕）。</li>
<li><strong>錯誤碼</strong>。</li>
<li><strong>請求編號</strong>，旁邊放一個複製按鈕。</li>
<li><strong>發生時間</strong>，標明時區。</li>
</ul>
<p>訊息與錯誤碼對應的是錯誤的類別，可以在程式裡逐一寫好；請求編號與時間是每一次請求才有的值，要由產生錯誤的那一段程式帶到畫面上。</p>
<p>請求編號的長度要和回報的管道一起考慮。網頁與 App 上，使用者按複製再貼進客服表單，32 個字元不成問題；電話客服要使用者唸出來，32 個十六進位字元太長。做法之一是畫面顯示完整的編號、電話上只請使用者唸前 8 碼，工程以前 8 碼加上帳號與時間範圍篩選 log。8 碼十六進位有 16⁸ 種、約 43 億種組合。設工程篩選的那個帳號與時間範圍裡共有 n 次請求，除了使用者要找的那一次，其餘請求之中至少有一次的前 8 碼也相同的機率約為 n ÷ 43 億：一小時一千次請求時約是四百萬分之一，帳號的請求量大時把 n 換成自己的數字重算。</p>
<p>請求編號要是隨機產生的值，不能由帳號編號、遞增序號或時間組成；要讓使用者在電話上只唸前 8 碼時，也不帶固定的前綴。由帳號編號組成的編號讓別人從編號推出帳號；遞增序號讓別人推算服務的請求量，也讓別人猜出其他使用者的編號；以時間開頭的編號前幾碼是時間戳記，UUIDv7（開頭 48 位元是毫秒時間戳記的 UUID）的前 8 碼約每 65 秒才變一次，ULID（開頭是時間戳記、可依時間排序的編號格式）的前 8 碼約每秒變一次，同一段時間內的請求前 8 碼相同；固定前綴（例如 <code>app-</code>）佔掉前幾碼，前 8 碼裡的隨機字元也少了；Stripe 的 <code>req_</code> 這類前綴不影響複製貼上，只影響唸前幾碼的做法。這幾種情形下，電話上只唸前 8 碼就分不出是哪一次請求。</p>
<h2 id="請求編號的產生與傳遞nginx-入口後端程式前端與-app">請求編號的產生與傳遞：Nginx 入口、後端程式、前端與 App</h2>
<p>請求編號要在 log 裡查得到，前提是同一個值同時出現在三個地方：回給使用者的錯誤畫面、入口的 access log，以及後端程式處理這個請求時寫的每一行 log。最直接的做法是由請求最先抵達的入口產生請求編號，再把它傳下去。</p>
<p>Nginx 從 1.11.0 起提供 <code>$request_id</code> 變數，每個請求以 16 個隨機位元組產生，寫成 32 個十六進位字元。以下設定讓 Nginx 把它寫進 access log、加在回應的 header 上，並以 <code>X-Request-Id</code> header 傳給後端程式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 以下兩行寫在 http 區塊（server 之外）
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="k">log_format</span> <span class="s">withid</span> <span class="s">&#39;</span><span class="nv">$remote_addr</span> <span class="s">[</span><span class="nv">$time_iso8601]</span> <span class="s">&#34;</span><span class="nv">$request&#34;</span> <span class="nv">$status</span> <span class="s">rid=</span><span class="nv">$request_id&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">access_log</span> <span class="s">/var/log/nginx/access.log</span> <span class="s">withid</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># 每一個回應（包括錯誤回應）都帶上同一個編號
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span>    <span class="kn">add_header</span> <span class="s">X-Request-Id</span> <span class="nv">$request_id</span> <span class="s">always</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="kn">location</span> <span class="s">/</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="c1"># 把編號傳給後端；後端從這個 header 讀出編號寫進自己的 log
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span>        <span class="kn">proxy_set_header</span> <span class="s">X-Request-Id</span> <span class="nv">$request_id</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="c1"># app:8080 換成自己後端程式的位址
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span>        <span class="kn">proxy_pass</span> <span class="s">http://app:8080</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>這段設定要拆開併進既有的設定檔，各行的位置與作用：</p>
<ul>
<li><code>log_format</code> 只能寫在 <code>http</code> 區塊，寫進 <code>server</code> 時 <code>nginx -t</code> 會回報 <code>&quot;log_format&quot; directive is not allowed here</code>。官方映像與多數套件的主設定檔在 <code>http</code> 區塊裡以 <code>include</code> 引入 <code>conf.d/*.conf</code>，所以寫在 <code>conf.d</code> 底下某個檔案的最外層、不包在任何 <code>server</code> 裡的指令，就屬於 <code>http</code> 區塊。名字 <code>withid</code> 與欄位標記 <code>rid=</code> 都是自取的，<code>access_log</code> 那一行以同一個名字引用它；log 檔的路徑換成自己主機上的位置。</li>
<li>套件預設的 <code>nginx.conf</code> 在 <code>http</code> 層已經有一行 <code>access_log … main;</code>。這時把那一行的格式名改成 <code>withid</code>，或在原本的 <code>main</code> 格式後面加上 <code>rid=$request_id</code>；另加一行 <code>access_log</code> 的話，每個請求會以兩種格式各寫一次。讀這份 access log 的工具照原本的格式解析，格式一改，結果依工具而不同：GoAccess 回報 <code>Format Errors</code> 並停止，ngxtop 不報錯而讀到 0 筆（見 <a href="/blog/linux/tools/cli/web-server-log-monitoring/" data-link-title="終端機看 nginx 請求：GoAccess、ngxtop 與何時該用 pipeline 而非 TUI" data-link-desc="要在終端機即時看 nginx／web 伺服器請求、在 GoAccess 與 ngxtop 之間選、或判斷該用當下排查的 TUI 還是持續監控的 metrics pipeline 時回來讀">終端機看 nginx 請求</a>）。既有格式有工具在讀時，採用在 <code>main</code> 格式最後加 <code>rid=$request_id</code> 的做法：GoAccess 的 COMBINED 格式照樣解析得到，ngxtop 與 log 收集器的解析規則要把新欄位加進它們的格式定義。</li>
<li><code>add_header</code> 併進既有的 <code>server</code>，<code>proxy_set_header</code> 併進每一個有 <code>proxy_pass</code> 的 <code>location</code>。這兩個指令都只在當層沒有同名指令時才繼承上一層的設定，所以某個 <code>location</code> 自己寫了其他 <code>add_header</code> 或 <code>proxy_set_header</code> 時，要在那個 <code>location</code> 裡再寫一次這一行。</li>
<li><code>add_header</code> 後面的 <code>always</code> 讓 Nginx 在 4xx 與 5xx 的回應上也加這個 header。少了它，Nginx 只在 200、201、204、206、301、302、303、304、307、308 這幾種回應加 header，而使用者回報的正好是錯誤回應。</li>
</ul>
<p>以 Nginx 1.27.5 實測這段設定：一次請求的回應 header、後端收到的 <code>X-Request-Id</code> 與 access log 的 <code>rid=</code> 欄位是同一個值。用戶端自己在請求裡帶了 <code>X-Request-Id</code> 時，Nginx 產生 <code>$request_id</code> 不讀這個 header，仍然產生新的值，後端收到的是 Nginx 產生的那一個；入口因此不必信任外部送進來的編號，與 <a href="/blog/backend/knowledge-cards/trace-context/" data-link-title="Trace Context" data-link-desc="說明跨服務 request 如何用 trace context 串起路徑與耗時">W3C Trace Context</a> 允許在信任邊界重新開始 trace 是同一個考量（見 <a href="/blog/backend/11-api-design/error-feedback-loop/#%e5%ae%9a%e4%bd%8d%e9%89%a4%e5%ad%90request-id-%e8%88%87-trace-%e7%9a%84%e5%a5%91%e7%b4%84" data-link-title="錯誤回報的回饋迴路：request-id、trace 與呈現回報分工" data-link-desc="consumer 收到錯誤之後怎麼跟 provider 溝通：error 要帶什麼定位鉤子、同一個錯誤怎麼分別投影給使用者與回報、持續錯誤什麼時候該升級">錯誤回報的回饋迴路：request-id、trace 與呈現回報分工</a> 的〈定位鉤子：request-id 與 trace 的契約〉）。</p>
<p>Nginx 前面還有 CDN 或雲端負載平衡器時，連不上 Nginx 的錯誤頁由那一層產生，不會有 <code>$request_id</code>。有的那一層會在錯誤頁顯示自己的編號（CDN 服務 Cloudflare 的錯誤頁顯示 Ray ID），這時 Nginx 的 access log 也要記下那一層傳給 Nginx 的編號 header（Cloudflare 是 <code>CF-Ray</code>），兩個編號才接得起來；Cloudflare 的文件說明 Ray ID 不保證每個請求唯一，以它查 log 時另外加上時間範圍，那一層的編號也同樣要確認不是以時間開頭。有的那一層只能回一個靜態頁（AWS 的 CDN 服務 CloudFront 的自訂錯誤頁是存放在某個路徑下的固定物件），放不了每次請求的編號，這時改請使用者回報發生時間，工程以時間範圍查那一層的 log。</p>
<p>後端程式要做兩件事。第一件是從 <code>X-Request-Id</code> header 讀出編號，放進這個請求的 log 欄位，讓這個請求寫的每一行 log 都帶著它；多數 logging 函式庫把「這個請求期間每一行 log 都自動帶上的欄位」稱為 context 或 MDC（Mapped Diagnostic Context），log 欄位怎麼命名、跨服務怎麼保持一致，見 <a href="/blog/backend/04-observability/log-schema/" data-link-title="4.1 log schema 與搜尋規劃" data-link-desc="整理 log 欄位、索引與搜尋策略">4.1 log schema 與搜尋規劃</a>。第二件是回應錯誤時把它放進錯誤回應的 body，讓前端顯示在畫面上。</p>
<p>錯誤回應採用 <a href="/blog/backend/knowledge-cards/problem-details/" data-link-title="Problem Details（RFC 9457）" data-link-desc="錯誤格式的現成標準給了什麼——URI 命名空間與未知欄位忽略條款，這兩件事自建格式要自己補">RFC 9457</a>（HTTP API 錯誤回應的 Problem Details 格式）時，編號可以放在 <code>instance</code> 成員，它要寫成 URI，RFC 建議帶完整路徑，例如 <code>/requests/9c8561d5272320ba8aafd57145eb9894</code>；也可以放在自訂的欄位。</p>
<p>後端程式不能用自己產生的編號取代入口的編號：錯誤畫面與 access log 上寫的是入口的編號，後端的 log 只寫自己的編號時，拿使用者給的編號就找不到後端那一段紀錄。後端需要自己的編號時，兩個一起寫進 log。請求在後端又呼叫其他服務時，跨服務的關聯改由 trace 承擔，log 同時寫入 <a href="/blog/backend/knowledge-cards/trace-id/" data-link-title="Trace ID" data-link-desc="說明分散式追蹤中同一條呼叫路徑的識別碼">trace-id</a>，工程從請求編號找到 log，再從 trace-id 找到整條呼叫鏈。</p>
<p>入口自己產生的錯誤也要帶編號。後端程式停掉時，Nginx 回 502，錯誤頁是 Nginx 產生的，不經過後端；以 <code>error_page</code> 把這些錯誤導到一個以 <code>return</code> 回應的具名 location，錯誤頁就能顯示同一個編號：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">location</span> <span class="s">/</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="kn">proxy_set_header</span> <span class="s">X-Request-Id</span> <span class="nv">$request_id</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="kn">proxy_pass</span> <span class="s">http://app:8080</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="c1"># 後端連不上或逾時時，改由下面的 location 產生錯誤頁
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span>    <span class="kn">error_page</span> <span class="mi">502</span> <span class="mi">503</span> <span class="mi">504</span> <span class="p">=</span> <span class="s">@upstream_error</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># @upstream_error 是自取的名字
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span><span class="k">location</span> <span class="s">@upstream_error</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="kn">default_type</span> <span class="s">text/plain</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="kn">return</span> <span class="mi">502</span> <span class="s">&#34;服務暫時無法使用。錯誤碼</span> <span class="s">UPSTREAM_UNAVAILABLE，請求編號</span> <span class="nv">$request_id\n&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>error_page</code> 寫了 <code>=</code> 而沒有接回應碼時，Nginx 回給用戶端的回應碼改用 <code>@upstream_error</code> 回的值；上面的 <code>@upstream_error</code> 寫的是 <code>return 502</code>，所以 502、503、504 三種錯誤送到用戶端時都變成 502。要保留 503 與 504 的區分，就為它們各寫一個具名 location，各自 <code>return</code> 自己的回應碼。錯誤頁要用 HTML 時，把 <code>default_type</code> 改成 <code>text/html</code>，內文換成 HTML。這裡的 <code>UPSTREAM_UNAVAILABLE</code> 也會顯示在使用者畫面上，同樣要在錯誤碼對照表上有一列。實測時把 <code>proxy_pass</code> 指向一個沒有程式在聽的埠，回應是 502，內文的請求編號、header 的 <code>X-Request-Id</code> 與 access log 的 <code>rid=</code> 三者相同。</p>
<p>入口自己產生的錯誤不經過後端，只記在 Nginx 自己的 log 裡：access log 帶請求編號，error log 記下連不上後端的原因而不帶編號。上面的 <code>withid</code> 格式沒有帳號欄位，這一類錯誤只能以時間範圍、請求路徑與來源位址篩選，電話上唸前 8 碼時比對的範圍也因此是那段時間的全部請求。</p>
<p>前端或 App 自己畫錯誤畫面時，請求編號有兩個來源：後端錯誤回應 body 裡的欄位，以及每一個回應都帶的 <code>X-Request-Id</code> header。入口自己回的 502 是 <code>@upstream_error</code> 回的那段純文字，不是後端的錯誤格式，前端從 body 解析不出欄位，這時改讀 header。網頁前端與 API 不同源時，瀏覽器依 CORS（跨來源資源共用）的規則只讓前端程式讀到伺服器明確公開的 header，入口要在 <code>add_header X-Request-Id … always;</code> 那一行所在的區塊再加一行 <code>add_header Access-Control-Expose-Headers X-Request-Id always;</code>；實測 502 回應同時帶著 <code>X-Request-Id</code> 與 <code>Access-Control-Expose-Headers</code> 兩個 header。</p>
<p>行動 App 的錯誤可能根本沒有到達伺服器（沒有網路、DNS 解析失敗、連線逾時），這時沒有入口產生的編號。App 可以在每次送出請求時自己產生一個隨機編號，在畫面上顯示 App 自己的錯誤碼（例如 <code>NETWORK_UNREACHABLE</code>）與這個編號，並把它與錯誤一起寫進 App 自己送出的 log 或當機回報；請求沒有到達伺服器時，伺服器上沒有任何紀錄，工程只能在 App 送出的 log 或當機回報裡查到這個編號。這個編號要放在另一個 header 送出，例如 <code>X-Client-Request-Id</code>，因為入口會以 <code>$request_id</code> 覆寫 <code>X-Request-Id</code>。請求到達了伺服器、只是回應沒有回到 App 時，要讓工程在伺服器的 access log 查得到這個編號，把前面寫在 <code>http</code> 區塊、只記 <code>rid=$request_id</code> 的那一行 <code>log_format withid</code> 換成下面這一行（同名的 <code>log_format</code> 只能定義一次，另加一行同名的，<code>nginx -t</code> 會回報 <code>duplicate &quot;log_format&quot; name</code>）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 取代前面寫在 http 區塊的 log_format withid：在 rid= 之後多記 App 送來的編號；沒有帶這個 header 的請求記成 -
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">log_format</span> <span class="s">withid</span> <span class="s">&#39;</span><span class="nv">$remote_addr</span> <span class="s">[</span><span class="nv">$time_iso8601]</span> <span class="s">&#34;</span><span class="nv">$request&#34;</span> <span class="nv">$status</span> <span class="s">rid=</span><span class="nv">$request_id</span> <span class="s">crid=</span><span class="nv">$http_x_client_request_id&#39;</span><span class="p">;</span></span></span></code></pre></div><p><code>$http_x_client_request_id</code> 是 Nginx 讀請求 header 的變數，名字由 <code>$http_</code> 加上 header 名稱的小寫、連字號換成底線組成。以 Nginx 1.27.5 實測，帶了 <code>X-Client-Request-Id: 7f3a9c0e</code> 的請求在 access log 記成 <code>crid=7f3a9c0e</code>，沒帶的記成 <code>crid=-</code>；Nginx 預設把請求裡的其他 header 原樣轉給後端（header 名稱本身含底線的會被丟棄），後端不必另外設定就收得到它。App 若已經依 <a href="/blog/backend/04-observability/client-server-trace-integration/" data-link-title="4.24 Client-to-Server 端到端觀測串接" data-link-desc="用一個結帳場景走完 browser click → trace context → server span → 統一 waterfall 的完整實作鏈路">4.24 Client-to-Server 端到端觀測串接</a> 送出 <code>traceparent</code>（W3C Trace Context 規定的 trace 傳遞 header），可以不另外產生編號，直接以其中的 trace-id 當作 App 端的編號，用戶端與伺服器端的紀錄由同一個 trace 串起來。拿 trace-id 當編號、又要在電話上唸前 8 碼時，trace-id 的開頭要是隨機產生的：AWS 的分散式追蹤服務 X-Ray 的 trace-id 把請求時間的 Unix 秒數放在開頭 8 個十六進位字元，用這種 trace-id 時，電話上改唸最後 8 碼（隨機產生的那一段）。</p>
<h2 id="錯誤碼對照表">錯誤碼對照表</h2>
<p>錯誤碼對照表讓客服不經過工程就答覆使用者。每一個會顯示在使用者畫面上的錯誤碼佔一列，欄位如下：</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>內容</th>
          <th>短網址服務的例子（<code>LINK_EXPIRED</code>）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>錯誤碼</td>
          <td>畫面上顯示的字串</td>
          <td><code>LINK_EXPIRED</code></td>
      </tr>
      <tr>
          <td>意思</td>
          <td>用客服聽得懂的話說明</td>
          <td>這個短網址超過有效期限，服務已停止轉址</td>
      </tr>
      <tr>
          <td>使用者能做的事</td>
          <td>使用者自己可以採取的動作，沒有就寫沒有</td>
          <td>擁有者登入後可以延長期限；訪客請聯絡分享網址的人</td>
      </tr>
      <tr>
          <td>客服的答覆</td>
          <td>客服可以直接用的一段話</td>
          <td>「這個短網址已在 ○ 月 ○ 日到期，擁有者可在管理頁延長期限。」</td>
      </tr>
      <tr>
          <td>轉給工程的條件</td>
          <td>什麼情形下這個錯誤碼不是使用者端的狀況</td>
          <td>擁有者表示期限未到卻收到這個錯誤</td>
      </tr>
      <tr>
          <td>處理時查的工具</td>
          <td>客服查證時要開的頁面</td>
          <td>客服查詢工具的短碼頁，看到期日</td>
      </tr>
      <tr>
          <td>負責的團隊</td>
          <td>這一列由誰維護</td>
          <td>短網址服務團隊</td>
      </tr>
  </tbody>
</table>
<p>同一個錯誤碼多數時候代表使用者端的狀況，少數時候是程式錯誤：<code>LINK_EXPIRED</code> 出現在一個期限未到的短網址上，代表到期判斷寫錯了。只寫錯誤說明的文件分不出這兩種情形，客服只能每一則都照「已過期」答覆；對照表多了「轉給工程的條件」這一欄，客服才分得出來，使用者端的狀況自己答覆，程式錯誤轉給工程。</p>
<p>錯誤碼與它的使用者訊息一旦出現在使用者畫面上，就是對客服與使用者的承諾：重新命名一個錯誤碼，客服的對照表與知識文章就對不上，使用者手上的舊截圖也查不到。錯誤碼的變更紀律因此與 API 欄位相同，見 <a href="/blog/backend/11-api-design/backward-compatibility-discipline/" data-link-title="11.6 向後相容的變更紀律" data-link-desc="哪些變更算 breaking、相容性檢查放人工還是 CI、檢查粒度怎麼選 — 讓介面變更可審可擋的日常紀律">11.6 向後相容的變更紀律</a>；命名用可讀的字串而不是連續數字，理由見 <a href="/blog/backend/11-api-design/error-model-design/#%e5%b8%b8%e8%a6%8b%e8%a8%ad%e8%a8%88%e9%8c%af%e8%aa%a4" data-link-title="11.4 錯誤模型設計" data-link-desc="錯誤該分幾類、格式怎麼定才有演化空間、機器判讀跟人類訊息怎麼分工 — 錯誤作為契約一級公民的設計判斷標準">11.4 錯誤模型設計</a> 的〈常見設計錯誤〉。</p>
<p>對照表最常見的失效是程式新增了錯誤碼而對照表沒有跟上，客服遇到一個查不到的錯誤碼，只能把整張單轉給工程。一種做法是讓對照表從程式裡的錯誤定義產生：每一個錯誤碼在定義的地方同時寫上客服用的欄位，文件由建置流程從那裡產生，CI（每次提交時自動執行的建置與檢查）在客服欄位空白時讓建置失敗。新增錯誤碼的那一次變更因此必須同時寫好客服的答覆。不從程式產生時，至少把負責的團隊寫在每一列上，並在每週營運會議裡比對客服單出現過的錯誤碼與對照表的列（見 <a href="/blog/backend/04-observability/on-call-routine-checks/#%e6%af%8f%e9%80%b1%e7%87%9f%e9%81%8b%e6%9c%83%e8%ad%b0%e8%ae%80%e7%9a%84%e8%a8%8a%e8%99%9f" data-link-title="4.29 值班的例行檢查：接班檢查與每週營運會議讀的訊號、交班紀錄的內容，以及例行檢查項目改寫成告警規則的條件" data-link-desc="告警刻意不呼叫值班的訊號由值班定時讀：接班時讀上一班的交班紀錄、生效中的告警靜音、只送通知的 warning、error budget 的走向、排程工作與心跳檢查項目、到期事件、容量走向、近期變更與客服單量，各自防的是哪一種情形；交班紀錄要寫的內容；每週營運會議讀的告警雜訊、值班負擔與重複出現的客服回報；以及一個檢查項目什麼時候該改寫成告警規則">4.29 值班的例行檢查</a> 的〈每週營運會議讀的訊號〉）。</p>
<h2 id="客服收件的欄位">客服收件的欄位</h2>
<p>客服收到回報時要收齊的欄位，每一個都對應工程查證的一個步驟：</p>
<ul>
<li><strong>帳號</strong>（或使用者自己看得到的使用者編號）：沒有請求編號時，以帳號篩選 log；也用來查這個帳號的資料狀態。</li>
<li><strong>發生時間與時區</strong>：縮小 log 的查詢範圍。使用者說的「下午三點」沒有時區時，工程可能在差八小時的位置找。</li>
<li><strong>錯誤碼</strong>：查對照表，客服自己就可能答覆。</li>
<li><strong>請求編號</strong>：直接找到那一次請求。</li>
<li><strong>功能</strong>：從固定的清單選一項（短網址服務是轉址、建立短網址、點擊統計、付款與帳號）。每週的計數與排名用它分類，沒有錯誤碼的回報也只能靠它歸類。</li>
<li><strong>裝置、作業系統與 App 版本或瀏覽器</strong>：判斷是否只發生在某個版本；只發生在低於最低支援版本的 App 上時，處理方式是請使用者更新（見 <a href="/blog/backend/knowledge-cards/minimum-supported-version/" data-link-title="Minimum Supported Version（最低支援版本）" data-link-desc="要讓舊版行動 App 停止使用時：服務方保存的版本下限、客戶端啟動檢查與伺服器端拒絕兩層各自涵蓋哪些版本，以及提高它之前要查的使用者數與作業系統下限">Minimum Supported Version</a>）。</li>
<li><strong>操作步驟與螢幕截圖</strong>：重現問題，也從截圖補回使用者沒抄下的錯誤碼與請求編號。</li>
</ul>
<p>錯誤碼與功能要存成客服單上可以篩選的欄位，客服系統才算得出件數；從截圖補回的錯誤碼，由客服填進欄位。</p>
<p>使用者沒有抄下請求編號時，工程以帳號加上前後幾分鐘的時間範圍查 log，再以錯誤碼與請求的路徑縮小到那一次請求。這條退路成立的前提是 log 有帳號與錯誤碼欄位可以篩選，而帳號屬於個人資料，寫進 log 時要依 <a href="/blog/backend/knowledge-cards/pii/" data-link-title="PII" data-link-desc="說明可識別個人的資料如何影響權限、遮罩、保留與稽核">PII</a> 的規則處理：寫內部的使用者編號，不寫電子郵件或姓名。</p>
<h2 id="客服查詢工具的權限">客服查詢工具的權限</h2>
<p>客服查詢工具（<a href="/blog/backend/knowledge-cards/internal-admin-tool/" data-link-title="Internal Admin Tool（客服後台）" data-link-desc="設計讓客服以帳號或訂單編號查使用者資料的內部頁面、決定它顯示哪些欄位與留下什麼紀錄，或讀到 support tooling、admin tool、back office 這些名稱要確認它們是否指同一種工具時，查客服後台的讀寫範圍、權限與稽核責任，以及它與管理端點、代登入的分界">Internal Admin Tool（客服後台）</a>）讓客服以帳號或短碼查到資料的狀態，取代請工程查資料庫。工具的設計重點在客服看得到什麼、以及每一次查詢留下什麼紀錄：</p>
<ul>
<li><strong>只讀</strong>：工具只顯示狀態，延長期限、退款、解除停用這類會改資料的動作另外走有核准流程的後台。</li>
<li><strong>欄位層級的遮罩</strong>：客服看得到短碼的狀態、原始網址、到期日、建立時間與檢舉紀錄，以及帳號的方案、本月剩餘額度與本期的扣款紀錄；電子郵件、姓名這類個人資料不顯示，付款資訊只顯示卡號末四碼。遮罩的做法見 <a href="/blog/backend/knowledge-cards/data-masking/" data-link-title="Data Masking" data-link-desc="說明敏感資料如何在顯示、匯出、log 與測試資料中降低暴露">Data Masking</a>。</li>
<li><strong>查詢要附還在處理中的單號</strong>：客服以帳號查詢時要填這次查詢對應的客服單號，工具只在那張單還在處理中時顯示資料，結案的單號解不開查詢。這讓每一次查詢都有理由與有效期間，與 <a href="/blog/backend/07-security-data-protection/identity-access-boundary/#%e4%bb%a3%e7%90%86%e6%93%8d%e4%bd%9c%e7%9a%84%e6%8e%88%e6%ac%8a%e9%82%8a%e7%95%8c" data-link-title="7.2 身分與授權邊界" data-link-desc="以問題驅動方式整理身分、授權、會話與供應商身分鏈">7.2 身分與授權邊界</a> 的〈代理操作的授權邊界〉要求的發起理由與有效時窗是同一件事。來電者自稱帳號本人時，客服先依 <a href="/blog/backend/07-security-data-protection/single-account-takeover-response/" data-link-title="7.41 單一帳號被接管：求助的人是誰本身待證" data-link-desc="有人說自己的帳號被盜、或系統偵測到單一帳號異常時，用來排處置順序、定核身要求，以及找出攻擊者留下的持久存取">7.41 單一帳號被接管</a> 的做法確認身分，再查那個帳號。</li>
<li><strong>每一次查詢都寫進稽核紀錄</strong>：誰、什麼時間、以哪一張單、查了哪個帳號（見 <a href="/blog/backend/knowledge-cards/audit-log/" data-link-title="Audit Log" data-link-desc="說明高風險操作如何留下可追溯、可稽核的紀錄">Audit Log</a>）。各客服帳號的查詢量寫成一條告警：某個帳號在一小時內的查詢量超過它平常的最高值時，通知資安的負責人，查詢量突然升高的帳號可能已經被盜用。客服帳號被盜用時，攻擊者用的就是這個工具，稽核紀錄是事後查出影響範圍的依據；客服支援系統被入侵而外洩客戶資料的實例見 <a href="/blog/backend/07-security-data-protection/cases/okta-support-system-incident-2023/" data-link-title="7.C5 Okta：2023 Support System 事件" data-link-desc="支援系統存取憑證風險如何擴散到客戶租戶的案例。">Okta 支援系統事件</a>。</li>
</ul>
<p>一線客服不直接查 log。log 裡有其他使用者的資料與內部細節，而且 log 系統的權限多半以整個資料集為單位，給了一線客服就等於給了全部；一線客服需要的狀態，由查詢工具挑出來顯示。技術支援是分層支援裡處理需要讀系統資料的那一層（見 <a href="/blog/backend/knowledge-cards/tiered-support/" data-link-title="Tiered Support（分層支援）" data-link-desc="要決定使用者回報由誰處理、什麼時候往上轉，或客服一再把同一類問題轉給工程時，查分層支援怎麼切分各層、決定一則回報停在哪一層的因素，以及它與值班升級策略的差別">Tiered Support</a>），它要讀 log 時，權限縮到它負責的服務與遮罩過的欄位。權限的整體設計原則見 <a href="/blog/backend/knowledge-cards/least-privilege/" data-link-title="Least Privilege" data-link-desc="說明身份、服務與人員只應取得完成工作所需的最小權限">Least Privilege</a>。</p>
<h2 id="log-保留期從使用者遇到錯誤到工程查-log-的時間">log 保留期：從使用者遇到錯誤到工程查 log 的時間</h2>
<p>請求編號在 log 裡查得到的前提，是那一行 log 在工程查詢時還在，而且仍是逐筆的完整紀錄。從使用者遇到錯誤到工程以請求編號查 log，下文稱為<strong>錯誤到查證的時間</strong>，由幾段相加：使用者隔幾天才回報、客服單在佇列裡等、客服處理後轉給工程、工程排到這張單並開始查。開單在上班時間處理的回報，這段時間常以天甚至週計算。<a href="/blog/backend/knowledge-cards/retention/" data-link-title="Retention" data-link-desc="說明資料或事件保留多久，以及保留期限如何影響重放與成本">Retention（保留期）</a> 比錯誤到查證的時間短的 log，工程拿著正確的請求編號也查不到，看起來像使用者抄錯了編號。</p>
<p>所以可以逐筆以請求編號查詢的那一層 log（完整粒度、沒有聚合），保留期至少要涵蓋錯誤到查證的時間的上限，再加上餘裕。例如使用者一週後才回報（7 天）、客服兩天內轉單、工程在 <a href="/blog/backend/08-incident-response/support-escalation-and-engineering-reply/" data-link-title="8.24 客服回報的處理：客服當場答覆時用的資訊、轉給值班的條件、工程查證後給客服的回覆、查證結果寫回知識文章、告警與查詢工具，以及流程失效時的徵兆" data-link-desc="客服接到使用者回報之後，哪些資訊讓客服當場答覆（進行中的事故、已知錯誤清單、維護與變更公告、錯誤碼對照表、帳號查詢工具），哪些條件要立即呼叫值班、哪些開單在上班時間處理，工程查證之後回覆給客服的內容，同一類回報重複出現時怎麼把查證結果寫回知識文章、告警規則或查詢工具，以及從客服單與事故紀錄看出流程在哪一段失效">8.24 客服回報的處理</a> 舉例的兩個上班日內第一次回覆（這兩段加起來四個上班日，最多跨一個週末，約 6 天）、工程排到這張單實際查 log 再隔兩週（14 天），加起來約二十七天，再加上餘裕，請求層級的 log 保留四十五天；這幾段時間換成自己服務的數字重算。</p>
<p>錯誤到查證的時間給出的是保留期的一個下限。合約與法規可能給出更長的下限：服務水準協議的補償申請有期限（例如 AWS 要求在事件發生後第二個帳單週期結束前申請），證據要留到那時；處理信用卡資料的系統適用 PCI DSS（支付卡產業資料安全標準）4.0.1，要求稽核紀錄保留 12 個月、其中最近 3 個月能立即取用。這兩個下限多半落在聚合的可用性指標與稽核紀錄這兩層，不必延長完整粒度的請求 log。個資法規則可能給出上限。每一層的保留期取它適用的下限中最長的那一個，並且不超過適用的上限；下限長過上限時，個資欄位先遮罩或改存內部編號，再保留到下限。</p>
<p>access log 的保留期另外確認，它不一定在 log 平台上。Debian 與 Ubuntu 套件版 Nginx 附的 logrotate（Linux 上定期輪替與刪除舊 log 的工具）設定每天輪替、保留 14 份；官方 Docker 映像把 <code>access.log</code> 連到標準輸出，保留期由 Docker 收集容器輸出的 log driver 決定，預設的 json-file driver 在刪除重建容器時一起刪掉。入口自己回的 502 只記在 Nginx 的 log 裡，所以 access log 要送進和後端 log 同一個 log 平台，或把輪替份數調到涵蓋錯誤到查證的時間。</p>
<p>保留期越長，儲存成本與個人資料外洩的影響範圍越大，所以常見的做法是分層：請求層級的 log 以完整粒度保留到涵蓋錯誤到查證的時間為止，其中的個人資料欄位遮罩或只存內部編號，更長期的統計改用聚合過的指標。<a href="/blog/backend/04-observability/cardinality-cost-governance/#%e6%8e%a7%e5%88%b6%e9%9d%a2%e8%88%87%e4%bf%9d%e7%95%99%e9%9a%8e%e6%a2%af" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7 Cardinality 治理與成本邊界</a> 的保留階梯以最近 7 天的熱層保留完整粒度、7 到 30 天聚合為例，用在請求 log 時要把完整粒度那一層延長到錯誤到查證的時間；營運 log（operational log）的保留期與它的漂移見 <a href="/blog/backend/04-observability/audit-log-governance/#retention-%e8%88%87%e4%bf%9d%e7%95%99%e7%ad%96%e7%95%a5%e6%bc%82%e7%a7%bb" data-link-title="4.12 Audit Log 邊界與 PII 治理" data-link-desc="把稽核訊號從 operational log 拆出、按法規與不變性治理">4.12 Audit Log 邊界與 PII 治理</a> 的〈Retention 與保留策略漂移〉。</p>
<h2 id="延伸閱讀客服流程系統之間的錯誤回報與錯誤分類">延伸閱讀：客服流程、系統之間的錯誤回報與錯誤分類</h2>
<ul>
<li>客服當場答覆時用的資訊、轉給值班的條件、工程查證後給客服的回覆：<a href="/blog/backend/08-incident-response/support-escalation-and-engineering-reply/" data-link-title="8.24 客服回報的處理：客服當場答覆時用的資訊、轉給值班的條件、工程查證後給客服的回覆、查證結果寫回知識文章、告警與查詢工具，以及流程失效時的徵兆" data-link-desc="客服接到使用者回報之後，哪些資訊讓客服當場答覆（進行中的事故、已知錯誤清單、維護與變更公告、錯誤碼對照表、帳號查詢工具），哪些條件要立即呼叫值班、哪些開單在上班時間處理，工程查證之後回覆給客服的內容，同一類回報重複出現時怎麼把查證結果寫回知識文章、告警規則或查詢工具，以及從客服單與事故紀錄看出流程在哪一段失效">8.24 客服回報的處理</a></li>
<li>API 呼叫方與提供者之間以 request-id 與 trace 回報錯誤、錯誤內容分成給使用者的呈現與給工程的回報：<a href="/blog/backend/11-api-design/error-feedback-loop/" data-link-title="錯誤回報的回饋迴路：request-id、trace 與呈現回報分工" data-link-desc="consumer 收到錯誤之後怎麼跟 provider 溝通：error 要帶什麼定位鉤子、同一個錯誤怎麼分別投影給使用者與回報、持續錯誤什麼時候該升級">錯誤回報的回饋迴路：request-id、trace 與呈現回報分工</a></li>
<li>錯誤分成可重試與終態、錯誤回應的格式與演化條款：<a href="/blog/backend/11-api-design/error-model-design/" data-link-title="11.4 錯誤模型設計" data-link-desc="錯誤該分幾類、格式怎麼定才有演化空間、機器判讀跟人類訊息怎麼分工 — 錯誤作為契約一級公民的設計判斷標準">11.4 錯誤模型設計</a></li>
<li>API、非同步工作與外部呼叫的錯誤要帶哪些診斷欄位：<a href="/blog/backend/04-observability/debuggability-by-design/" data-link-title="4.19 Debuggability by Design" data-link-desc="把可診斷性前移到 API、async workflow、dependency call 與錯誤模型設計">4.19 Debuggability by Design</a></li>
</ul>
]]></content:encoded></item><item><title>8.24 客服回報的處理：客服當場答覆時用的資訊、轉給值班的條件、工程查證後給客服的回覆、查證結果寫回知識文章、告警與查詢工具，以及流程失效時的徵兆</title><link>https://tarrragon.github.io/blog/backend/08-incident-response/support-escalation-and-engineering-reply/</link><pubDate>Wed, 14 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/backend/08-incident-response/support-escalation-and-engineering-reply/</guid><description>&lt;p>這一篇整理客服與工程之間處理使用者回報的流程，範圍從客服接到一則回報開始，到它由客服當場答覆、併進一個事故，或由工程查證後回覆為止。一則回報確定是事故之後的分級、指揮與對外通訊在 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-severity-trigger/" data-link-title="8.1 事故分級與啟動條件" data-link-desc="建立統一分級標準與事故啟動門檻">8.1 事故分級與啟動條件&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-command-roles/" data-link-title="8.2 事故指揮與角色分工" data-link-desc="定義 incident commander 與跨角色協作責任">8.2 事故指揮與角色分工&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/stakeholder-communication/" data-link-title="8.10 Stakeholder 通訊與外部狀態頁" data-link-desc="把 impact scope、status page、補償政策串成節奏">8.10 Stakeholder 通訊與外部狀態頁&lt;/a>；客服回報、告警與第三方狀態怎麼以同一組欄位進入事故候選，在 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-intake-evidence-triage/" data-link-title="8.18 Incident Intake &amp;amp; Evidence Triage" data-link-desc="把告警、客訴、支援回報與第三方狀態轉成同一個 intake / evidence 判讀流程">8.18 Incident Intake &amp;amp; Evidence Triage&lt;/a>。使用者畫面上的錯誤碼與請求編號怎麼設計，在 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/end-user-error-code-and-reference-id/" data-link-title="11.16 面向終端使用者的錯誤碼與請求編號：錯誤畫面的欄位、請求編號的產生與傳遞、錯誤碼對照表、客服收件欄位、客服查詢工具的權限與 log 保留期" data-link-desc="使用者經由客服回報錯誤、再由工程查到那一次請求，系統在每一段要提供什麼：錯誤碼與請求編號各自交給誰用、請求編號為什麼要隨機以及電話上怎麼唸，入口以 Nginx 產生編號並傳進後端與 log、入口自己的 502 與前端怎麼讀到編號（實測），錯誤碼對照表怎麼跟著程式維護，客服查詢工具的唯讀、遮罩與稽核，以及 log 保留期的下限從哪些時間與規定來">11.16 面向終端使用者的錯誤碼與請求編號&lt;/a>。回報者是 API 的呼叫方（另一個系統的工程師）時，一則回報代表那個系統背後的所有使用者，這一篇以回報件數計算的門檻不適用，改以 log 裡那個呼叫方的錯誤率判斷；呼叫方帶來的請求編號怎麼查，見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/error-feedback-loop/" data-link-title="錯誤回報的回饋迴路：request-id、trace 與呈現回報分工" data-link-desc="consumer 收到錯誤之後怎麼跟 provider 溝通：error 要帶什麼定位鉤子、同一個錯誤怎麼分別投影給使用者與回報、持續錯誤什麼時候該升級">錯誤回報的回饋迴路：request-id、trace 與呈現回報分工&lt;/a>。&lt;/p>
&lt;p>這一篇的例子是一個短網址服務（需求規格見 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/url-shortener-requirements/" data-link-title="0.23 短網址服務的需求定義：自建或購買、保存期限、目的地修改與濫用處理的規格" data-link-desc="短網址服務交給工程之前要定案的需求決定：自建或購買、點擊資料的地位、連結的保存承諾、目的地能否修改、建立者的範圍、短碼的可猜測性、網域策略與可用性分級；多數答案由商業模型推出，目的地修改、短碼可猜測性與可用性分級由使用情境決定">0.23 短網址服務的需求定義&lt;/a>）：登入的使用者建立短網址、看點擊統計，付費方案每月扣款並提高建立額度，訪客點短網址時由服務轉址到原本的網址。&lt;/p>
&lt;h2 id="分層支援一則回報停在哪一層">分層支援：一則回報停在哪一層&lt;/h2>
&lt;p>客服組織常把處理回報的人分成幾層，業界叫分層支援（&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/tiered-support/" data-link-title="Tiered Support（分層支援）" data-link-desc="要決定使用者回報由誰處理、什麼時候往上轉，或客服一再把同一類問題轉給工程時，查分層支援怎麼切分各層、決定一則回報停在哪一層的因素，以及它與值班升級策略的差別">Tiered Support&lt;/a>）：一線客服接所有回報，技術支援處理需要讀系統資料的回報，工程處理需要改程式、或讀完整 log 與資料庫的回報。小團隊多半只有兩層，客服直接轉給工程。&lt;/p>
&lt;p>一則回報停在哪一層，由那一層手上的資訊與工具決定，不由回報者的身分或語氣決定。客服手上有進行中的事故清單與錯誤碼對照表，「短網址打不開」這類回報多數在一線就有答案；客服手上只有一個轉單按鈕，同一則回報就會一路轉到工程，工程的時間花在查一個客服本來查得到的狀態。客服手上的資訊答不了的回報，才依影響範圍與損害能否補回，決定呼叫值班或開單。呼叫值班（page）指由值班通知系統打電話或推播給排班表上這一班的&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">值班&lt;/a>工程師，要他立即處理，半夜也會把人叫醒。有些回報即使客服答得出來也要立即呼叫值班，例如重複扣款，因為損害在等待的時間裡擴大。有技術支援這一層的組織，開單的回報先到技術支援，技術支援讀得到的系統資料比客服查詢工具顯示的欄位多，查不出來才轉給工程。&lt;/p>
&lt;h2 id="客服當場答覆時用的資訊">客服當場答覆時用的資訊&lt;/h2>
&lt;p>客服當場答覆需要五種資訊：進行中的事故、已知錯誤清單、排定的維護與近期變更、錯誤碼對照表，以及帳號層級的查詢工具。前四種是要有人更新的內容，下面各自寫了由誰更新；查詢工具直接讀線上的資料，要維護的是它顯示哪些欄位。&lt;/p>
&lt;p>&lt;strong>進行中的事故&lt;/strong>：一份內部版的狀態頁，列出目前確認中的事故、受影響的功能、開始時間、對客戶的說法與下次更新的時間。它比對外的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/status-page/" data-link-title="Status Page" data-link-desc="說明事故期間對外狀態頁如何承接可用性承諾">Status Page&lt;/a> 多一欄「對客戶的說法」，因為客服要逐一回覆，而對外狀態頁寫的是給所有人看的一段公告。這一份由事故中的對外聯絡人（liaison，見 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-command-roles/" data-link-title="8.2 事故指揮與角色分工" data-link-desc="定義 incident commander 與跨角色協作責任">8.2 事故指揮與角色分工&lt;/a>；小團隊由值班兼任）更新。PagerDuty 的事故流程把這個角色叫 Customer Liaison，職責是把確認過的狀態送給客戶，並把客服收到的回報彙整給事故指揮，溝通紀律是不說謊也不猜測（見 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cases/pagerduty-customer-liaison/" data-link-title="4.C24 PagerDuty Customer Liaison：客訴的雙向 out-of-band 管道" data-link-desc="專責角色把客戶回報聚合成 IC 可用的 scope 訊號、把確認過的狀態往外送；never lie never guess 在盲飛時尤其關鍵">PagerDuty Customer Liaison&lt;/a>）。內部狀態頁上的說法因此只寫確認過的事，不寫還在推測的原因與修復時間。&lt;/p>
&lt;p>&lt;strong>已知錯誤清單&lt;/strong>：已經確認、還沒修好、而影響小到不必開事故的問題。每一條寫症狀、log 裡對應的內部錯誤類別、替代做法與追蹤單號（追蹤單上看得到修正的進度）。ITIL 問題管理把這類紀錄稱為 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/known-error/" data-link-title="Known Error（已知錯誤）" data-link-desc="已經確認、還沒修好、影響小到不必開事故的問題要讓客服與值班知道時，查 ITIL 的已知錯誤與已知錯誤資料庫（KEDB）記錄什麼、它與事故在 ITIL 裡的關係、本站流程裡清單與事故的分流，以及清單沒有維護時的失效">Known Error（已知錯誤）&lt;/a>，存放它們的地方叫已知錯誤資料庫（KEDB）。一個問題要列進已知錯誤清單還是開事故，在這套流程裡看影響是否會擴大：「某一款舊版瀏覽器開啟統計頁時圖表空白，換瀏覽器可以看」影響固定、有替代做法，列進已知錯誤清單；「所有人的統計頁都空白」開事故。清單由確認問題的工程寫入，由發布修正的人在修正上線時移除；修好的條目沒有移除，客服會繼續用一個已經不需要的替代做法回覆。&lt;/p>
&lt;p>&lt;strong>排定的維護與近期變更&lt;/strong>：維護時段與最近上線的變更（例如短網址的有效期限從永久改成兩年）。客服接到「我的短網址突然失效」時，第一個要對照的是最近是否改過規則。這一份由發布變更的團隊在上線前送給客服，對外公告與客服用的版本同時發出。&lt;/p>
&lt;p>&lt;strong>錯誤碼對照表&lt;/strong>：使用者畫面上顯示的每一個錯誤碼，寫明它代表什麼、使用者能做什麼、客服可以怎麼答覆，以及什麼條件下要轉給工程。對照表由擁有那個錯誤碼的服務團隊維護，欄位與維護方式見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/end-user-error-code-and-reference-id/#%e9%8c%af%e8%aa%a4%e7%a2%bc%e5%b0%8d%e7%85%a7%e8%a1%a8" data-link-title="11.16 面向終端使用者的錯誤碼與請求編號：錯誤畫面的欄位、請求編號的產生與傳遞、錯誤碼對照表、客服收件欄位、客服查詢工具的權限與 log 保留期" data-link-desc="使用者經由客服回報錯誤、再由工程查到那一次請求，系統在每一段要提供什麼：錯誤碼與請求編號各自交給誰用、請求編號為什麼要隨機以及電話上怎麼唸，入口以 Nginx 產生編號並傳進後端與 log、入口自己的 502 與前端怎麼讀到編號（實測），錯誤碼對照表怎麼跟著程式維護，客服查詢工具的唯讀、遮罩與稽核，以及 log 保留期的下限從哪些時間與規定來">11.16 面向終端使用者的錯誤碼與請求編號&lt;/a> 的〈錯誤碼對照表〉。&lt;/p>
&lt;p>&lt;strong>帳號層級的查詢工具&lt;/strong>（&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/internal-admin-tool/" data-link-title="Internal Admin Tool（客服後台）" data-link-desc="設計讓客服以帳號或訂單編號查使用者資料的內部頁面、決定它顯示哪些欄位與留下什麼紀錄，或讀到 support tooling、admin tool、back office 這些名稱要確認它們是否指同一種工具時，查客服後台的讀寫範圍、權限與稽核責任，以及它與管理端點、代登入的分界">客服後台&lt;/a>）：一個只讀的頁面，讓客服以帳號或短碼查到這一筆資料的狀態，例如短碼存在、已過期、被檢舉停用，帳號的方案、本月剩餘額度與是否簽了服務水準協議，以及本期的扣款紀錄（時間、金額、狀態）。它取代「請工程幫我查一下資料庫」，由工程維護它顯示的欄位，客服缺欄位時提出需求；設計時要決定客服看得到哪些欄位、每一次查詢是否留下稽核紀錄，見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/end-user-error-code-and-reference-id/#%e5%ae%a2%e6%9c%8d%e6%9f%a5%e8%a9%a2%e5%b7%a5%e5%85%b7%e7%9a%84%e6%ac%8a%e9%99%90" data-link-title="11.16 面向終端使用者的錯誤碼與請求編號：錯誤畫面的欄位、請求編號的產生與傳遞、錯誤碼對照表、客服收件欄位、客服查詢工具的權限與 log 保留期" data-link-desc="使用者經由客服回報錯誤、再由工程查到那一次請求，系統在每一段要提供什麼：錯誤碼與請求編號各自交給誰用、請求編號為什麼要隨機以及電話上怎麼唸，入口以 Nginx 產生編號並傳進後端與 log、入口自己的 502 與前端怎麼讀到編號（實測），錯誤碼對照表怎麼跟著程式維護，客服查詢工具的唯讀、遮罩與稽核，以及 log 保留期的下限從哪些時間與規定來">11.16 面向終端使用者的錯誤碼與請求編號&lt;/a> 的〈客服查詢工具的權限〉。&lt;/p>
&lt;p>以「短網址打不開」為例，客服先以查詢工具查那個短碼，因為查詢工具讀的是這一筆資料本身：短碼不存在、已過期或被停用，都能當場答覆並說明原因。短碼狀態正常，客服用自己的瀏覽器開一次那個短網址。開得起來，問題多半出在使用者那一端的網路或裝置。開不起來時看瀏覽器停在哪裡：已經轉到原本的網址、而那個網站打不開，問題在原本的網址那一端，由客服答覆；停在短網址服務的網域上（錯誤頁或逾時），問題出在多人共用的元件上，這時對照內部狀態頁有沒有轉址相關的事故，有的話把這則回報附到那個事故上。沒有的話，轉址是短網址服務的核心功能，客服又在自己的瀏覽器重現出來了，照〈轉給值班的條件與轉交方式〉裡「客服照使用者的步驟操作一次就重現得出來」那一條立即呼叫值班。&lt;/p>
&lt;h2 id="轉給值班的條件與轉交方式">轉給值班的條件與轉交方式&lt;/h2>
&lt;p>客服判斷一則回報要怎麼處理時，有三種去處：立即呼叫值班、開單在上班時間處理、由客服答覆。這三種去處就是客服這一端的分級。《&lt;a href="https://sre.google/workbook/on-call/">The Site Reliability Workbook&lt;/a>》的值班章收錄的 Evernote 案例把事件分成三級：P1 立即處理並呼叫值班，P2 下一個上班日處理、寄信給團隊並發到事件頻道，P3 只是資訊。立即呼叫值班對應 P1，開單對應 P2；由客服答覆的回報不需要工程處理，接近 P3，其中附到進行中事故上的那些則是那個事故影響範圍的證據。&lt;/p>
&lt;p>&lt;strong>立即呼叫值班&lt;/strong>的回報，共同點是影響正在擴大，或者損害事後補不回來：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>客服照使用者的步驟操作一次就重現得出來，不是單一帳號的資料造成的，而且受影響的是作業規則列出的核心功能（短網址服務是轉址與建立短網址）。&lt;/strong> 重現得出來代表問題不在使用者自己的網路或裝置；夜間只有少數使用者的服務，等到回報件數夠多可能已經天亮，所以這一條不必等件數。核心功能以外、重現得出來的錯誤（例如某一款舊版瀏覽器的統計頁圖表空白）開單處理。&lt;/li>
&lt;li>&lt;strong>多位使用者在短時間內回報同一個症狀或錯誤碼，而內部狀態頁沒有對應的事故。&lt;/strong> 單一使用者的回報多半出在他自己的資料、網路或裝置；不同使用者回報同一個症狀，指向的是他們共用的元件，而內部狀態頁上沒有事故，代表監控沒有發現它。客服有好幾位時，每一位只看得到自己手上的單，件數要由客服系統計算：收件時把錯誤碼與功能填進單子的欄位，客服系統統計最近十五分鐘同一個錯誤碼（沒有錯誤碼的以功能）的件數，越過門檻時直接在值班通知系統建立事件；已有進行中的事故時，值班通知系統把新事件併進那個事故，不再重複呼叫。這是 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/on-call-routine-checks/" data-link-title="4.29 值班的例行檢查：接班檢查與每週營運會議讀的訊號、交班紀錄的內容，以及例行檢查項目改寫成告警規則的條件" data-link-desc="告警刻意不呼叫值班的訊號由值班定時讀：接班時讀上一班的交班紀錄、生效中的告警靜音、只送通知的 warning、error budget 的走向、排程工作與心跳檢查項目、到期事件、容量走向、近期變更與客服單量，各自防的是哪一種情形；交班紀錄要寫的內容；每週營運會議讀的告警雜訊、值班負擔與重複出現的客服回報；以及一個檢查項目什麼時候該改寫成告警規則">4.29 值班的例行檢查&lt;/a> 那條原則（一個訊號能寫成越過某個門檻就送出的規則，就寫成告警）用在客服單上。門檻數字的依據是平常的基線：取過去幾週同一個錯誤碼在每一個十五分鐘內的件數，先排除有事故的時段，以及沒有宣告成事故、但件數異常的時段（留在基線裡會把門檻抬到那次的件數，同樣規模的故障再發生時就越不過），門檻設在剩下的最高值之上，而且至少兩件，平常不會越過、共用的元件出錯時才越過。基線取幾週、門檻設多高由團隊依自己的件數定。&lt;/li>
&lt;li>&lt;strong>金流、資料遺失或資料外洩。&lt;/strong> 重複扣款、短網址的資料整批不見、使用者在自己的統計頁看到別人的短網址，這三種即使只有一位使用者回報也立即呼叫值班。重複扣款先以查詢工具看這個帳號本期的扣款紀錄：同一期有兩筆成功的扣款，立即呼叫值班，由值班確認重複扣款的來源（自己的扣款批次、或對金流商重送了同一筆扣款）是否已經停止，來源還在執行時，每多處理一批就多一批使用者被重複扣款；只有一筆成功、另一筆是信用卡的預授權暫扣，由客服答覆。資料遺失只能在備份與交易日誌的保留範圍內還原，拖得越久，能還原的時間點越少；資料外洩要在證據還在的時候保全。看到別人資料、帳號被盜這一類屬於資安事件，處理路線與一般事故不同，見 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/security-vs-operational-incident/" data-link-title="8.17 Security Incident vs Operational Incident 分流" data-link-desc="把資安事故跟可用性事故的 IR 流程分支點明確化">8.17 Security Incident vs Operational Incident 分流&lt;/a>；外洩是從服務本身、還是從客戶持有的帳號或客戶自己的系統流出，怎麼判斷、判斷完成前後各通知誰，見 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/security-incident-customer-notification/" data-link-title="8.26 資料外洩時對商家的通知：外洩來源的歸屬判定、風險預警、確認與結案通知、調查報告" data-link-desc="替商家保管會員資料的平台在可能的資料外洩發生時對商家發出的通知：外洩來源歸屬判定的比對方法與已排除、未排除、指向平台各自的證據標準、子處理者與平台自己發現暴露時的判定路徑、歸屬未定時風險預警的觸發條件與發送範圍、確認通知與結案通知交付各商家的事實、調查報告的時間軸與分層根因，以及事中要留給事後檢驗的決策紀錄">8.26 資料外洩時對商家的通知&lt;/a>；使用者自稱帳號被盜時，客服要先確認來電者是帳號本人，見 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/single-account-takeover-response/" data-link-title="7.41 單一帳號被接管：求助的人是誰本身待證" data-link-desc="有人說自己的帳號被盜、或系統偵測到單一帳號異常時，用來排處置順序、定核身要求，以及找出攻擊者留下的持久存取">7.41 單一帳號被接管&lt;/a>。&lt;/li>
&lt;li>&lt;strong>簽了服務水準協議（SLA）的客戶，合約定義的核心功能無法使用。&lt;/strong> 合約上的可用性承諾以一段期間內的停機時間或錯誤率計算（例如 Amazon S3 以每五分鐘的錯誤率取整月平均），故障拖到上班時間才處理，中間的每一段時間都會算進去。這一條要客服判斷得了，查詢工具要看得到帳號是否簽了服務水準協議，作業規則要列出合約定義的核心功能（短網址服務是轉址與建立短網址）以及合約怎麼算「無法使用」：多數合約以錯誤率計算，只有變慢不一定算。&lt;/li>
&lt;/ul>
&lt;p>客服回報沒有寫成呼叫值班的條件時會發生什麼，Google SRE Workbook 記錄的 Google Home 案例是一個實例：裝置對喚醒詞回應錯誤訊息，客服陸續接到大量使用者回報，這些回報卻沒有觸發升級，過了約四天問題才被提到最高優先度，團隊始終沒有宣告事故（見 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cases/google-home-customer-report-signal/" data-link-title="4.C23 Google Home：客訴是監控失效時唯一生效的訊號源" data-link-desc="內部監控沒有發現的部分失效、客訴累積約四天才把 bug 優先度推到最高、團隊始終沒有宣告事故；consumer 回報作為 telemetry 替代品的正反面實證，以及客訴沒有寫成升級條件的代價">Google Home 客訴訊號&lt;/a>）。&lt;/p></description><content:encoded><![CDATA[<p>這一篇整理客服與工程之間處理使用者回報的流程，範圍從客服接到一則回報開始，到它由客服當場答覆、併進一個事故，或由工程查證後回覆為止。一則回報確定是事故之後的分級、指揮與對外通訊在 <a href="/blog/backend/08-incident-response/incident-severity-trigger/" data-link-title="8.1 事故分級與啟動條件" data-link-desc="建立統一分級標準與事故啟動門檻">8.1 事故分級與啟動條件</a>、<a href="/blog/backend/08-incident-response/incident-command-roles/" data-link-title="8.2 事故指揮與角色分工" data-link-desc="定義 incident commander 與跨角色協作責任">8.2 事故指揮與角色分工</a> 與 <a href="/blog/backend/08-incident-response/stakeholder-communication/" data-link-title="8.10 Stakeholder 通訊與外部狀態頁" data-link-desc="把 impact scope、status page、補償政策串成節奏">8.10 Stakeholder 通訊與外部狀態頁</a>；客服回報、告警與第三方狀態怎麼以同一組欄位進入事故候選，在 <a href="/blog/backend/08-incident-response/incident-intake-evidence-triage/" data-link-title="8.18 Incident Intake &amp; Evidence Triage" data-link-desc="把告警、客訴、支援回報與第三方狀態轉成同一個 intake / evidence 判讀流程">8.18 Incident Intake &amp; Evidence Triage</a>。使用者畫面上的錯誤碼與請求編號怎麼設計，在 <a href="/blog/backend/11-api-design/end-user-error-code-and-reference-id/" data-link-title="11.16 面向終端使用者的錯誤碼與請求編號：錯誤畫面的欄位、請求編號的產生與傳遞、錯誤碼對照表、客服收件欄位、客服查詢工具的權限與 log 保留期" data-link-desc="使用者經由客服回報錯誤、再由工程查到那一次請求，系統在每一段要提供什麼：錯誤碼與請求編號各自交給誰用、請求編號為什麼要隨機以及電話上怎麼唸，入口以 Nginx 產生編號並傳進後端與 log、入口自己的 502 與前端怎麼讀到編號（實測），錯誤碼對照表怎麼跟著程式維護，客服查詢工具的唯讀、遮罩與稽核，以及 log 保留期的下限從哪些時間與規定來">11.16 面向終端使用者的錯誤碼與請求編號</a>。回報者是 API 的呼叫方（另一個系統的工程師）時，一則回報代表那個系統背後的所有使用者，這一篇以回報件數計算的門檻不適用，改以 log 裡那個呼叫方的錯誤率判斷；呼叫方帶來的請求編號怎麼查，見 <a href="/blog/backend/11-api-design/error-feedback-loop/" data-link-title="錯誤回報的回饋迴路：request-id、trace 與呈現回報分工" data-link-desc="consumer 收到錯誤之後怎麼跟 provider 溝通：error 要帶什麼定位鉤子、同一個錯誤怎麼分別投影給使用者與回報、持續錯誤什麼時候該升級">錯誤回報的回饋迴路：request-id、trace 與呈現回報分工</a>。</p>
<p>這一篇的例子是一個短網址服務（需求規格見 <a href="/blog/backend/00-service-selection/url-shortener-requirements/" data-link-title="0.23 短網址服務的需求定義：自建或購買、保存期限、目的地修改與濫用處理的規格" data-link-desc="短網址服務交給工程之前要定案的需求決定：自建或購買、點擊資料的地位、連結的保存承諾、目的地能否修改、建立者的範圍、短碼的可猜測性、網域策略與可用性分級；多數答案由商業模型推出，目的地修改、短碼可猜測性與可用性分級由使用情境決定">0.23 短網址服務的需求定義</a>）：登入的使用者建立短網址、看點擊統計，付費方案每月扣款並提高建立額度，訪客點短網址時由服務轉址到原本的網址。</p>
<h2 id="分層支援一則回報停在哪一層">分層支援：一則回報停在哪一層</h2>
<p>客服組織常把處理回報的人分成幾層，業界叫分層支援（<a href="/blog/backend/knowledge-cards/tiered-support/" data-link-title="Tiered Support（分層支援）" data-link-desc="要決定使用者回報由誰處理、什麼時候往上轉，或客服一再把同一類問題轉給工程時，查分層支援怎麼切分各層、決定一則回報停在哪一層的因素，以及它與值班升級策略的差別">Tiered Support</a>）：一線客服接所有回報，技術支援處理需要讀系統資料的回報，工程處理需要改程式、或讀完整 log 與資料庫的回報。小團隊多半只有兩層，客服直接轉給工程。</p>
<p>一則回報停在哪一層，由那一層手上的資訊與工具決定，不由回報者的身分或語氣決定。客服手上有進行中的事故清單與錯誤碼對照表，「短網址打不開」這類回報多數在一線就有答案；客服手上只有一個轉單按鈕，同一則回報就會一路轉到工程，工程的時間花在查一個客服本來查得到的狀態。客服手上的資訊答不了的回報，才依影響範圍與損害能否補回，決定呼叫值班或開單。呼叫值班（page）指由值班通知系統打電話或推播給排班表上這一班的<a href="/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">值班</a>工程師，要他立即處理，半夜也會把人叫醒。有些回報即使客服答得出來也要立即呼叫值班，例如重複扣款，因為損害在等待的時間裡擴大。有技術支援這一層的組織，開單的回報先到技術支援，技術支援讀得到的系統資料比客服查詢工具顯示的欄位多，查不出來才轉給工程。</p>
<h2 id="客服當場答覆時用的資訊">客服當場答覆時用的資訊</h2>
<p>客服當場答覆需要五種資訊：進行中的事故、已知錯誤清單、排定的維護與近期變更、錯誤碼對照表，以及帳號層級的查詢工具。前四種是要有人更新的內容，下面各自寫了由誰更新；查詢工具直接讀線上的資料，要維護的是它顯示哪些欄位。</p>
<p><strong>進行中的事故</strong>：一份內部版的狀態頁，列出目前確認中的事故、受影響的功能、開始時間、對客戶的說法與下次更新的時間。它比對外的 <a href="/blog/backend/knowledge-cards/status-page/" data-link-title="Status Page" data-link-desc="說明事故期間對外狀態頁如何承接可用性承諾">Status Page</a> 多一欄「對客戶的說法」，因為客服要逐一回覆，而對外狀態頁寫的是給所有人看的一段公告。這一份由事故中的對外聯絡人（liaison，見 <a href="/blog/backend/08-incident-response/incident-command-roles/" data-link-title="8.2 事故指揮與角色分工" data-link-desc="定義 incident commander 與跨角色協作責任">8.2 事故指揮與角色分工</a>；小團隊由值班兼任）更新。PagerDuty 的事故流程把這個角色叫 Customer Liaison，職責是把確認過的狀態送給客戶，並把客服收到的回報彙整給事故指揮，溝通紀律是不說謊也不猜測（見 <a href="/blog/backend/04-observability/cases/pagerduty-customer-liaison/" data-link-title="4.C24 PagerDuty Customer Liaison：客訴的雙向 out-of-band 管道" data-link-desc="專責角色把客戶回報聚合成 IC 可用的 scope 訊號、把確認過的狀態往外送；never lie never guess 在盲飛時尤其關鍵">PagerDuty Customer Liaison</a>）。內部狀態頁上的說法因此只寫確認過的事，不寫還在推測的原因與修復時間。</p>
<p><strong>已知錯誤清單</strong>：已經確認、還沒修好、而影響小到不必開事故的問題。每一條寫症狀、log 裡對應的內部錯誤類別、替代做法與追蹤單號（追蹤單上看得到修正的進度）。ITIL 問題管理把這類紀錄稱為 <a href="/blog/backend/knowledge-cards/known-error/" data-link-title="Known Error（已知錯誤）" data-link-desc="已經確認、還沒修好、影響小到不必開事故的問題要讓客服與值班知道時，查 ITIL 的已知錯誤與已知錯誤資料庫（KEDB）記錄什麼、它與事故在 ITIL 裡的關係、本站流程裡清單與事故的分流，以及清單沒有維護時的失效">Known Error（已知錯誤）</a>，存放它們的地方叫已知錯誤資料庫（KEDB）。一個問題要列進已知錯誤清單還是開事故，在這套流程裡看影響是否會擴大：「某一款舊版瀏覽器開啟統計頁時圖表空白，換瀏覽器可以看」影響固定、有替代做法，列進已知錯誤清單；「所有人的統計頁都空白」開事故。清單由確認問題的工程寫入，由發布修正的人在修正上線時移除；修好的條目沒有移除，客服會繼續用一個已經不需要的替代做法回覆。</p>
<p><strong>排定的維護與近期變更</strong>：維護時段與最近上線的變更（例如短網址的有效期限從永久改成兩年）。客服接到「我的短網址突然失效」時，第一個要對照的是最近是否改過規則。這一份由發布變更的團隊在上線前送給客服，對外公告與客服用的版本同時發出。</p>
<p><strong>錯誤碼對照表</strong>：使用者畫面上顯示的每一個錯誤碼，寫明它代表什麼、使用者能做什麼、客服可以怎麼答覆，以及什麼條件下要轉給工程。對照表由擁有那個錯誤碼的服務團隊維護，欄位與維護方式見 <a href="/blog/backend/11-api-design/end-user-error-code-and-reference-id/#%e9%8c%af%e8%aa%a4%e7%a2%bc%e5%b0%8d%e7%85%a7%e8%a1%a8" data-link-title="11.16 面向終端使用者的錯誤碼與請求編號：錯誤畫面的欄位、請求編號的產生與傳遞、錯誤碼對照表、客服收件欄位、客服查詢工具的權限與 log 保留期" data-link-desc="使用者經由客服回報錯誤、再由工程查到那一次請求，系統在每一段要提供什麼：錯誤碼與請求編號各自交給誰用、請求編號為什麼要隨機以及電話上怎麼唸，入口以 Nginx 產生編號並傳進後端與 log、入口自己的 502 與前端怎麼讀到編號（實測），錯誤碼對照表怎麼跟著程式維護，客服查詢工具的唯讀、遮罩與稽核，以及 log 保留期的下限從哪些時間與規定來">11.16 面向終端使用者的錯誤碼與請求編號</a> 的〈錯誤碼對照表〉。</p>
<p><strong>帳號層級的查詢工具</strong>（<a href="/blog/backend/knowledge-cards/internal-admin-tool/" data-link-title="Internal Admin Tool（客服後台）" data-link-desc="設計讓客服以帳號或訂單編號查使用者資料的內部頁面、決定它顯示哪些欄位與留下什麼紀錄，或讀到 support tooling、admin tool、back office 這些名稱要確認它們是否指同一種工具時，查客服後台的讀寫範圍、權限與稽核責任，以及它與管理端點、代登入的分界">客服後台</a>）：一個只讀的頁面，讓客服以帳號或短碼查到這一筆資料的狀態，例如短碼存在、已過期、被檢舉停用，帳號的方案、本月剩餘額度與是否簽了服務水準協議，以及本期的扣款紀錄（時間、金額、狀態）。它取代「請工程幫我查一下資料庫」，由工程維護它顯示的欄位，客服缺欄位時提出需求；設計時要決定客服看得到哪些欄位、每一次查詢是否留下稽核紀錄，見 <a href="/blog/backend/11-api-design/end-user-error-code-and-reference-id/#%e5%ae%a2%e6%9c%8d%e6%9f%a5%e8%a9%a2%e5%b7%a5%e5%85%b7%e7%9a%84%e6%ac%8a%e9%99%90" data-link-title="11.16 面向終端使用者的錯誤碼與請求編號：錯誤畫面的欄位、請求編號的產生與傳遞、錯誤碼對照表、客服收件欄位、客服查詢工具的權限與 log 保留期" data-link-desc="使用者經由客服回報錯誤、再由工程查到那一次請求，系統在每一段要提供什麼：錯誤碼與請求編號各自交給誰用、請求編號為什麼要隨機以及電話上怎麼唸，入口以 Nginx 產生編號並傳進後端與 log、入口自己的 502 與前端怎麼讀到編號（實測），錯誤碼對照表怎麼跟著程式維護，客服查詢工具的唯讀、遮罩與稽核，以及 log 保留期的下限從哪些時間與規定來">11.16 面向終端使用者的錯誤碼與請求編號</a> 的〈客服查詢工具的權限〉。</p>
<p>以「短網址打不開」為例，客服先以查詢工具查那個短碼，因為查詢工具讀的是這一筆資料本身：短碼不存在、已過期或被停用，都能當場答覆並說明原因。短碼狀態正常，客服用自己的瀏覽器開一次那個短網址。開得起來，問題多半出在使用者那一端的網路或裝置。開不起來時看瀏覽器停在哪裡：已經轉到原本的網址、而那個網站打不開，問題在原本的網址那一端，由客服答覆；停在短網址服務的網域上（錯誤頁或逾時），問題出在多人共用的元件上，這時對照內部狀態頁有沒有轉址相關的事故，有的話把這則回報附到那個事故上。沒有的話，轉址是短網址服務的核心功能，客服又在自己的瀏覽器重現出來了，照〈轉給值班的條件與轉交方式〉裡「客服照使用者的步驟操作一次就重現得出來」那一條立即呼叫值班。</p>
<h2 id="轉給值班的條件與轉交方式">轉給值班的條件與轉交方式</h2>
<p>客服判斷一則回報要怎麼處理時，有三種去處：立即呼叫值班、開單在上班時間處理、由客服答覆。這三種去處就是客服這一端的分級。《<a href="https://sre.google/workbook/on-call/">The Site Reliability Workbook</a>》的值班章收錄的 Evernote 案例把事件分成三級：P1 立即處理並呼叫值班，P2 下一個上班日處理、寄信給團隊並發到事件頻道，P3 只是資訊。立即呼叫值班對應 P1，開單對應 P2；由客服答覆的回報不需要工程處理，接近 P3，其中附到進行中事故上的那些則是那個事故影響範圍的證據。</p>
<p><strong>立即呼叫值班</strong>的回報，共同點是影響正在擴大，或者損害事後補不回來：</p>
<ul>
<li><strong>客服照使用者的步驟操作一次就重現得出來，不是單一帳號的資料造成的，而且受影響的是作業規則列出的核心功能（短網址服務是轉址與建立短網址）。</strong> 重現得出來代表問題不在使用者自己的網路或裝置；夜間只有少數使用者的服務，等到回報件數夠多可能已經天亮，所以這一條不必等件數。核心功能以外、重現得出來的錯誤（例如某一款舊版瀏覽器的統計頁圖表空白）開單處理。</li>
<li><strong>多位使用者在短時間內回報同一個症狀或錯誤碼，而內部狀態頁沒有對應的事故。</strong> 單一使用者的回報多半出在他自己的資料、網路或裝置；不同使用者回報同一個症狀，指向的是他們共用的元件，而內部狀態頁上沒有事故，代表監控沒有發現它。客服有好幾位時，每一位只看得到自己手上的單，件數要由客服系統計算：收件時把錯誤碼與功能填進單子的欄位，客服系統統計最近十五分鐘同一個錯誤碼（沒有錯誤碼的以功能）的件數，越過門檻時直接在值班通知系統建立事件；已有進行中的事故時，值班通知系統把新事件併進那個事故，不再重複呼叫。這是 <a href="/blog/backend/04-observability/on-call-routine-checks/" data-link-title="4.29 值班的例行檢查：接班檢查與每週營運會議讀的訊號、交班紀錄的內容，以及例行檢查項目改寫成告警規則的條件" data-link-desc="告警刻意不呼叫值班的訊號由值班定時讀：接班時讀上一班的交班紀錄、生效中的告警靜音、只送通知的 warning、error budget 的走向、排程工作與心跳檢查項目、到期事件、容量走向、近期變更與客服單量，各自防的是哪一種情形；交班紀錄要寫的內容；每週營運會議讀的告警雜訊、值班負擔與重複出現的客服回報；以及一個檢查項目什麼時候該改寫成告警規則">4.29 值班的例行檢查</a> 那條原則（一個訊號能寫成越過某個門檻就送出的規則，就寫成告警）用在客服單上。門檻數字的依據是平常的基線：取過去幾週同一個錯誤碼在每一個十五分鐘內的件數，先排除有事故的時段，以及沒有宣告成事故、但件數異常的時段（留在基線裡會把門檻抬到那次的件數，同樣規模的故障再發生時就越不過），門檻設在剩下的最高值之上，而且至少兩件，平常不會越過、共用的元件出錯時才越過。基線取幾週、門檻設多高由團隊依自己的件數定。</li>
<li><strong>金流、資料遺失或資料外洩。</strong> 重複扣款、短網址的資料整批不見、使用者在自己的統計頁看到別人的短網址，這三種即使只有一位使用者回報也立即呼叫值班。重複扣款先以查詢工具看這個帳號本期的扣款紀錄：同一期有兩筆成功的扣款，立即呼叫值班，由值班確認重複扣款的來源（自己的扣款批次、或對金流商重送了同一筆扣款）是否已經停止，來源還在執行時，每多處理一批就多一批使用者被重複扣款；只有一筆成功、另一筆是信用卡的預授權暫扣，由客服答覆。資料遺失只能在備份與交易日誌的保留範圍內還原，拖得越久，能還原的時間點越少；資料外洩要在證據還在的時候保全。看到別人資料、帳號被盜這一類屬於資安事件，處理路線與一般事故不同，見 <a href="/blog/backend/08-incident-response/security-vs-operational-incident/" data-link-title="8.17 Security Incident vs Operational Incident 分流" data-link-desc="把資安事故跟可用性事故的 IR 流程分支點明確化">8.17 Security Incident vs Operational Incident 分流</a>；外洩是從服務本身、還是從客戶持有的帳號或客戶自己的系統流出，怎麼判斷、判斷完成前後各通知誰，見 <a href="/blog/backend/08-incident-response/security-incident-customer-notification/" data-link-title="8.26 資料外洩時對商家的通知：外洩來源的歸屬判定、風險預警、確認與結案通知、調查報告" data-link-desc="替商家保管會員資料的平台在可能的資料外洩發生時對商家發出的通知：外洩來源歸屬判定的比對方法與已排除、未排除、指向平台各自的證據標準、子處理者與平台自己發現暴露時的判定路徑、歸屬未定時風險預警的觸發條件與發送範圍、確認通知與結案通知交付各商家的事實、調查報告的時間軸與分層根因，以及事中要留給事後檢驗的決策紀錄">8.26 資料外洩時對商家的通知</a>；使用者自稱帳號被盜時，客服要先確認來電者是帳號本人，見 <a href="/blog/backend/07-security-data-protection/single-account-takeover-response/" data-link-title="7.41 單一帳號被接管：求助的人是誰本身待證" data-link-desc="有人說自己的帳號被盜、或系統偵測到單一帳號異常時，用來排處置順序、定核身要求，以及找出攻擊者留下的持久存取">7.41 單一帳號被接管</a>。</li>
<li><strong>簽了服務水準協議（SLA）的客戶，合約定義的核心功能無法使用。</strong> 合約上的可用性承諾以一段期間內的停機時間或錯誤率計算（例如 Amazon S3 以每五分鐘的錯誤率取整月平均），故障拖到上班時間才處理，中間的每一段時間都會算進去。這一條要客服判斷得了，查詢工具要看得到帳號是否簽了服務水準協議，作業規則要列出合約定義的核心功能（短網址服務是轉址與建立短網址）以及合約怎麼算「無法使用」：多數合約以錯誤率計算，只有變慢不一定算。</li>
</ul>
<p>客服回報沒有寫成呼叫值班的條件時會發生什麼，Google SRE Workbook 記錄的 Google Home 案例是一個實例：裝置對喚醒詞回應錯誤訊息，客服陸續接到大量使用者回報，這些回報卻沒有觸發升級，過了約四天問題才被提到最高優先度，團隊始終沒有宣告事故（見 <a href="/blog/backend/04-observability/cases/google-home-customer-report-signal/" data-link-title="4.C23 Google Home：客訴是監控失效時唯一生效的訊號源" data-link-desc="內部監控沒有發現的部分失效、客訴累積約四天才把 bug 優先度推到最高、團隊始終沒有宣告事故；consumer 回報作為 telemetry 替代品的正反面實證，以及客訴沒有寫成升級條件的代價">Google Home 客訴訊號</a>）。</p>
<p><strong>開單、上班時間處理</strong>的回報，是不符合立即呼叫值班的條件、而客服手上的資訊答不了的回報，例如某一個帳號的點擊統計少算了一天。單一使用者、影響沒有擴大到其他使用者，是這一類的典型樣子；工程重現不了、或沒有替代做法，都不改變它開單的去處，客服在單子上註明，工程排序時看得到。單子上要帶齊客服收件欄位（見〈工程查證的順序與給客服的回覆〉），工程才不必回頭向客服要資料；開單之後工程第一次回覆的期限也寫在那一節。</p>
<p><strong>由客服答覆</strong>的回報是錯誤碼屬於使用者自己能處理的那一類（密碼錯誤、輸入的網址格式不對、額度用完），或是已知錯誤清單、內部狀態頁上已經有的問題。已經有事故的回報不另開單，而是附到那個事故上：每一則附上去的回報都是受影響範圍的一筆證據，事故的影響評估會用到它們（見 <a href="/blog/backend/08-incident-response/customer-impact-assessment/" data-link-title="8.20 Customer Impact Assessment" data-link-desc="把受影響用戶、功能、區域、金額、SLO 與補償判斷串成影響評估模型">8.20 Customer Impact Assessment</a>）。</p>
<p>立即呼叫值班要走和告警同一套值班通知系統：客服在 PagerDuty、Jira Service Management 或 Grafana Cloud IRM 這類值班通知服務手動建立一個事件，帶上客服收件欄位、目前收到幾件與第一件的時間，由排班表決定通知誰，沒有回應時照 <a href="/blog/backend/knowledge-cards/escalation-policy/" data-link-title="Escalation Policy" data-link-desc="說明事故升級鏈與值班轉接規則">Escalation Policy</a> 往下一層轉。客服因此要有在值班通知系統建立事件的入口（帳號、表單或整合的信箱位址）。只有一兩個人、沒有值班通知系統的服務，收信的人就是值班自己，改由信箱規則依使用者在表單選的類別（例如「被重複扣款」「網站完全打不開」）自動推播到手機。客服在聊天群組裡標記某一位工程師，那則訊息只送到一個人，他不在值班、在睡覺或在開會時，回報就停在那裡，也沒有任何紀錄可以事後查出它停了多久。</p>
<p>客服外包給另一家公司時，這些入口都跨了組織：值班通知系統的事件入口、內部狀態頁與查詢工具要開給外包人員的個人帳號，查詢工具的稽核紀錄才記得到是誰查的；外包的客服系統若與自己的工單系統分開，兩邊的單要彙總到同一個系統計數，件數門檻才算得到全部的回報；查詢工具驗證單號時也要讀得到外包的客服系統。</p>
<p>值班接手之後，以 <a href="/blog/backend/08-incident-response/incident-intake-evidence-triage/#%e5%85%a5%e5%8f%a3%e4%be%86%e6%ba%90" data-link-title="8.18 Incident Intake &amp; Evidence Triage" data-link-desc="把告警、客訴、支援回報與第三方狀態轉成同一個 intake / evidence 判讀流程">8.18 Incident Intake &amp; Evidence Triage</a> 的事故候選欄位建立一筆事故候選（還沒確認是事故、等待查證的紀錄）：來源欄標為客訴（customer ticket），可信度欄記錄這筆紀錄的證據來自哪裡，在找到內部證據之前標為只有外部來源（external-only）。</p>
<p>客服呼叫值班之後，資訊也要從值班回到客服：值班確認是事故之後，由對外聯絡人（或兼任的值班）在內部狀態頁寫上對客戶的說法，客服才知道接下來同類回報要怎麼答覆；少了這一步，客服會把每一則同類回報再轉上來一次。</p>
<p>上面這些判斷要客服照著做，得寫成一份客服的作業規則，內容是：同一個錯誤碼的件數門檻與時間窗、簽約客戶的核心功能與「無法使用」的定義、在值班通知系統建立事件的入口、開單之後工程第一次回覆的期限，以及逾期時往誰追。</p>
<h2 id="工程查證的順序與給客服的回覆">工程查證的順序與給客服的回覆</h2>
<p>工程收到客服轉來的回報時，查證從回報裡的識別資訊開始。客服收件欄位是帳號、發生時間與時區、畫面上的錯誤碼與請求編號、功能、裝置與作業系統、App 版本或瀏覽器，以及操作步驟與螢幕截圖；這些欄位各自用在哪一步查證、使用者沒抄下請求編號時怎麼退回以帳號與時間查，見 <a href="/blog/backend/11-api-design/end-user-error-code-and-reference-id/#%e5%ae%a2%e6%9c%8d%e6%94%b6%e4%bb%b6%e7%9a%84%e6%ac%84%e4%bd%8d" data-link-title="11.16 面向終端使用者的錯誤碼與請求編號：錯誤畫面的欄位、請求編號的產生與傳遞、錯誤碼對照表、客服收件欄位、客服查詢工具的權限與 log 保留期" data-link-desc="使用者經由客服回報錯誤、再由工程查到那一次請求，系統在每一段要提供什麼：錯誤碼與請求編號各自交給誰用、請求編號為什麼要隨機以及電話上怎麼唸，入口以 Nginx 產生編號並傳進後端與 log、入口自己的 502 與前端怎麼讀到編號（實測），錯誤碼對照表怎麼跟著程式維護，客服查詢工具的唯讀、遮罩與稽核，以及 log 保留期的下限從哪些時間與規定來">11.16 面向終端使用者的錯誤碼與請求編號</a> 的〈客服收件的欄位〉。</p>
<p>查證從最精確的證據開始，再往外擴。請求編號只對應一次請求，所以先以它查 log，確定錯誤發生在哪一次請求，以及 log 記下的內部錯誤類別是什麼（畫面上的錯誤碼可能由前端對應成較粗的類別，log 裡的比它細）；請求跨了好幾個服務時，以 log 裡的 <a href="/blog/backend/knowledge-cards/trace-id/" data-link-title="Trace ID" data-link-desc="說明分散式追蹤中同一條呼叫路徑的識別碼">trace-id</a> 查 trace，找到出錯的那一個服務。有了內部錯誤類別，工程才拿它和進行中的事故與已知錯誤清單比對：客服先前比對的是使用者描述的症狀，工程這一次比對的是 log 裡的內部錯誤類別，所以工程分辨得出兩則症狀相同而原因不同的回報。最後判斷影響範圍：只有這個帳號的資料出了問題，就以這個帳號處理；同一段時間別的帳號的 log 也出現同一個內部錯誤類別，就要回頭檢查它是不是一個還沒被發現的事故，必要時由工程自己呼叫值班。請求編號在 log 裡查不到時，先確認 log 的保留期是否比錯誤到查證的時間（從使用者遇到錯誤到工程查 log，見 <a href="/blog/backend/11-api-design/end-user-error-code-and-reference-id/#log-%e4%bf%9d%e7%95%99%e6%9c%9f%e5%be%9e%e4%bd%bf%e7%94%a8%e8%80%85%e9%81%87%e5%88%b0%e9%8c%af%e8%aa%a4%e5%88%b0%e5%b7%a5%e7%a8%8b%e6%9f%a5-log-%e7%9a%84%e6%99%82%e9%96%93" data-link-title="11.16 面向終端使用者的錯誤碼與請求編號：錯誤畫面的欄位、請求編號的產生與傳遞、錯誤碼對照表、客服收件欄位、客服查詢工具的權限與 log 保留期" data-link-desc="使用者經由客服回報錯誤、再由工程查到那一次請求，系統在每一段要提供什麼：錯誤碼與請求編號各自交給誰用、請求編號為什麼要隨機以及電話上怎麼唸，入口以 Nginx 產生編號並傳進後端與 log、入口自己的 502 與前端怎麼讀到編號（實測），錯誤碼對照表怎麼跟著程式維護，客服查詢工具的唯讀、遮罩與稽核，以及 log 保留期的下限從哪些時間與規定來">11.16 面向終端使用者的錯誤碼與請求編號</a>）短，這種情形下問題出在保留期，不在使用者抄錯。</p>
<p>給客服的回覆要讓客服能直接轉述給使用者，所以包含：</p>
<ul>
<li><strong>原因</strong>：以使用者聽得懂的話說明，例如「這個短網址在九月因為收到檢舉被停用」，不寫內部的表名或錯誤類別。</li>
<li><strong>對使用者的說法</strong>：客服可以直接用的一段話。</li>
<li><strong>使用者要做的事</strong>：需要重新登入、換瀏覽器、等修復上線，或什麼都不用做。</li>
<li><strong>修復狀態</strong>：已修好、預計何時修好，或不會修以及理由；預計時間只寫工程有把握的時間，因為客服會把它轉述成對使用者的承諾。</li>
<li><strong>追蹤單號</strong>：之後同一個使用者再來問，客服查得到進度。</li>
<li><strong>是否列進已知錯誤清單</strong>：還沒修好、會再有別人遇到的問題，由工程在回覆的同時寫進清單；使用者操作或設定造成、本來就不需要修正的，寫成知識文章或錯誤碼對照表的一列（見〈重複出現的回報：查證結果寫回知識文章、告警規則與查詢工具〉）。寫成對照表的一列時，回覆裡的原因、對使用者的說法與使用者要做的事，分別填進對照表的意思、客服的答覆與使用者能做的事，另外補上轉給工程的條件、處理時查的工具與負責的團隊。</li>
</ul>
<p>開單的回報也要有回覆期限，例如工程在兩個上班日內第一次回覆。期限寫進客服的作業規則，客服才能告訴使用者什麼時候會有消息；工程沒在期限內回覆時，客服依作業規則往工程團隊的負責人或當週的值班追。</p>
<h2 id="重複出現的回報查證結果寫回知識文章告警規則與查詢工具">重複出現的回報：查證結果寫回知識文章、告警規則與查詢工具</h2>
<p>一則回報查完之後，查證的結果要寫回成下一次不必再查的形式，否則同一類回報每出現一次，工程就重做一次同樣的查證。</p>
<p>客服知識管理有一套方法叫 <a href="/blog/backend/knowledge-cards/knowledge-centered-service/" data-link-title="Knowledge-Centered Service（KCS）" data-link-desc="客服或工程查完一則回報之後答案沒有留下、同一類問題每次都重查，或要決定知識文章由誰在什麼時候寫時，查 KCS 在處理回報的當下寫入與修正知識的做法">Knowledge-Centered Service（KCS）</a>，由 Consortium for Service Innovation 維護。它的做法是在處理回報的當下就把解法寫成知識文章，用提問者自己的說法描述問題，讓下一個人搜尋得到；之後每一次有人用到那篇文章，就順便確認內容是否仍符合現況，不符合的當場修正或標記。KCS 的重點在時機：寫在處理當下，而不是事後找一段時間整理，因為過了那個時間點，處理的人已經不記得細節，也就沒有人會寫。</p>
<p>同一類回報重複出現的原因有三種，每一種的查證結果寫回不同的地方；一則回報可以同時屬於兩種（例如監控沒有發現，而工程查證時要下資料庫查詢），兩處都要寫回：</p>
<ul>
<li><strong>工程查證時用到的資訊，客服手上的工具也查得到</strong>：代表客服本來可以自己答，寫回客服的資訊。有對應錯誤碼的寫成錯誤碼對照表的一列，沒有錯誤碼的症狀寫成一篇知識文章，還沒修好的程式錯誤寫進已知錯誤清單。</li>
<li><strong>監控沒有發現</strong>：使用者比告警先發現的故障，寫回一條告警規則、一條<a href="/blog/backend/knowledge-cards/synthetic-monitoring/" data-link-title="Synthetic Monitoring（合成監控）" data-link-desc="要知道使用者從外面連不連得到服務、或分不清某個「外部探測」從哪裡發出時，查合成監控以排程的請求模擬使用者的做法、探測端的位置怎麼決定它量得到什麼，以及它與真實使用者監測的差別">合成監控（synthetic monitoring）</a>的探測，或一項用戶端監控（在使用者的瀏覽器或 App 裡收集錯誤與效能，見 <a href="/blog/backend/04-observability/client-side-monitoring/" data-link-title="4.10 Client-side / Synthetic / RUM" data-link-desc="補 server-side 看不到的 user perceived 訊號">4.10 Client-side / Synthetic / RUM</a>）。這一類代表監控沒有在檢查這種故障，各層監控各自檢查什麼、怎麼找出這種缺口，見 <a href="/blog/backend/04-observability/monitoring-layers/" data-link-title="4.0 服務端監控的分層：容器與程序的自檢、服務網路內的探測、元件指標、應用層訊號、告警規則與分送、告警鏈的監看與使用者路徑的合成監控" data-link-desc="以每一層監控檢查的對象、看不到的範圍與接手的那一層，把服務端監控串成一條監控鏈，以及拿它盤點一個服務時怎麼判讀缺口的性質（檢查範圍、位置、讀取與訊號失真），涵蓋自架主機、Kubernetes 與雲端託管的情形；使用者路徑那一層另寫合成監控與真實使用者監測的差別，以及盤點時各種缺口的補法">4.0 服務端監控的分層</a>，業務停了而程式回應正常這類每一層都偵測不到的故障，見 <a href="/blog/backend/04-observability/monitoring-outside-the-chain/" data-link-title="4.0.1 監控鏈事前偵測不到的異常：業務結果、資料正確性、到期事件、排程與背景工作、備份、安全、第三方依賴與成本的監看" data-link-desc="監控鏈各層都正常時仍會發生的幾類異常：業務停了而程式回應正常、資料寫錯、TLS 憑證與網域到期、排程工作與佇列消費端沒跑、備份還原不回來、以帳號正常操作進行的攻擊、第三方業者停掉與帳單失控，各自要先定義的預期、檢查它的規則與承接的專章">4.0.1 監控鏈事前偵測不到的異常</a>；從事故寫回規則的流程見 <a href="/blog/backend/04-observability/signal-governance-loop/" data-link-title="4.8 訊號治理閉環" data-link-desc="把 postmortem 揭露的偵測缺口回寫成新訊號、讓觀測能力隨事故學習成長">4.8 訊號治理閉環</a>，事故證據寫回監控與 runbook 的做法見 <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back</a>。</li>
<li><strong>工程查證用到的資訊，客服手上的工具查不到</strong>：工程每次都要連進資料庫下同一段查詢，寫回客服查詢工具的一個欄位，或一個給工程用的查詢頁面。重複、手動、可以自動化的維運工作叫 <a href="/blog/backend/knowledge-cards/toil/" data-link-title="Toil" data-link-desc="說明重複、手動、無永久價值的工作如何成為工程治理對象">Toil</a>，治理方式見 <a href="/blog/backend/08-incident-response/repeated-incident-toil/" data-link-title="8.13 Repeated Incident 與 Toil 治理" data-link-desc="把同型事故反覆發生與重複手動修復作為工程化治理對象">8.13 Repeated Incident 與 Toil 治理</a>。</li>
</ul>
<p>同一個程式錯誤造成的重複回報，最後的解法是修掉那個錯誤；寫回是修好之前讓客服與工程不必重複處理的做法。要知道哪一類回報在重複，客服單要以錯誤碼與功能分類計數（兩者都是客服收件欄位），每週由工程與客服一起看排名前幾名的類別（例如前五名，數字由團隊定），這份計數是 <a href="/blog/backend/04-observability/on-call-routine-checks/#%e6%af%8f%e9%80%b1%e7%87%9f%e9%81%8b%e6%9c%83%e8%ad%b0%e8%ae%80%e7%9a%84%e8%a8%8a%e8%99%9f" data-link-title="4.29 值班的例行檢查：接班檢查與每週營運會議讀的訊號、交班紀錄的內容，以及例行檢查項目改寫成告警規則的條件" data-link-desc="告警刻意不呼叫值班的訊號由值班定時讀：接班時讀上一班的交班紀錄、生效中的告警靜音、只送通知的 warning、error budget 的走向、排程工作與心跳檢查項目、到期事件、容量走向、近期變更與客服單量，各自防的是哪一種情形；交班紀錄要寫的內容；每週營運會議讀的告警雜訊、值班負擔與重複出現的客服回報；以及一個檢查項目什麼時候該改寫成告警規則">4.29 值班的例行檢查</a> 在〈每週營運會議讀的訊號〉裡讀的項目之一。</p>
<h2 id="客服與工程之間的回報流程失效時的徵兆">客服與工程之間的回報流程失效時的徵兆</h2>
<p>這套流程失效在哪一段，會在客服單與事故紀錄上留下不同的徵兆：</p>
<ul>
<li><strong>事故時間線上，客訴比告警早出現，而且不只一次</strong>：監控沒有在檢查使用者先發現的那一類故障，前幾次的查證結果也沒有寫回告警規則或合成監控。</li>
<li><strong>事故時間線上，第一則客訴到值班被呼叫之間隔了很久</strong>：客服呼叫值班的條件沒有寫成數字，或件數仍由客服各自計算，要等到有人覺得件數夠多才轉。</li>
<li><strong>同一類回報反覆轉到工程</strong>：先查這一類在錯誤碼對照表與已知錯誤清單有沒有條目。沒有條目，是查證結果沒有寫回客服的資訊；有條目而內容過期，是清單或對照表沒有人更新。</li>
<li><strong>客服把工程的回覆再轉回來問「那我要怎麼跟使用者說」</strong>：回覆缺了對使用者的說法與使用者要做的事。</li>
<li><strong>事故進行中，客服仍然逐一轉單</strong>：先問客服是否知道內部狀態頁。知道而頁面上沒有這個事故，是值班確認事故之後沒有更新；不知道，是內部狀態頁沒有放進客服的工具。</li>
<li><strong>工程以請求編號查不到 log</strong>：先查同一時段其他請求的 log 是否還在。都不在，是 log 的保留期比錯誤到查證的時間短；其他請求在、這一筆不在，是請求編號沒有寫進 log。見 <a href="/blog/backend/11-api-design/end-user-error-code-and-reference-id/#log-%e4%bf%9d%e7%95%99%e6%9c%9f%e5%be%9e%e4%bd%bf%e7%94%a8%e8%80%85%e9%81%87%e5%88%b0%e9%8c%af%e8%aa%a4%e5%88%b0%e5%b7%a5%e7%a8%8b%e6%9f%a5-log-%e7%9a%84%e6%99%82%e9%96%93" data-link-title="11.16 面向終端使用者的錯誤碼與請求編號：錯誤畫面的欄位、請求編號的產生與傳遞、錯誤碼對照表、客服收件欄位、客服查詢工具的權限與 log 保留期" data-link-desc="使用者經由客服回報錯誤、再由工程查到那一次請求，系統在每一段要提供什麼：錯誤碼與請求編號各自交給誰用、請求編號為什麼要隨機以及電話上怎麼唸，入口以 Nginx 產生編號並傳進後端與 log、入口自己的 502 與前端怎麼讀到編號（實測），錯誤碼對照表怎麼跟著程式維護，客服查詢工具的唯讀、遮罩與稽核，以及 log 保留期的下限從哪些時間與規定來">11.16 面向終端使用者的錯誤碼與請求編號</a> 的〈log 保留期：從使用者遇到錯誤到工程查 log 的時間〉。</li>
</ul>
<h2 id="延伸閱讀事故流程錯誤碼設計與值班的例行檢查">延伸閱讀：事故流程、錯誤碼設計與值班的例行檢查</h2>
<ul>
<li>客服回報、告警與第三方狀態怎麼以同一組欄位進入事故候選，以及事故候選要填的欄位：<a href="/blog/backend/08-incident-response/incident-intake-evidence-triage/" data-link-title="8.18 Incident Intake &amp; Evidence Triage" data-link-desc="把告警、客訴、支援回報與第三方狀態轉成同一個 intake / evidence 判讀流程">8.18 Incident Intake &amp; Evidence Triage</a></li>
<li>事故中對內、對客戶與對外的訊息分層與更新節奏，對外聯絡人寫進內部狀態頁的說法從這裡來：<a href="/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">8.4 事故通訊與狀態更新</a></li>
<li>使用者畫面上的錯誤碼與請求編號、錯誤碼對照表的欄位、客服查詢工具的權限與 log 保留期：<a href="/blog/backend/11-api-design/end-user-error-code-and-reference-id/" data-link-title="11.16 面向終端使用者的錯誤碼與請求編號：錯誤畫面的欄位、請求編號的產生與傳遞、錯誤碼對照表、客服收件欄位、客服查詢工具的權限與 log 保留期" data-link-desc="使用者經由客服回報錯誤、再由工程查到那一次請求，系統在每一段要提供什麼：錯誤碼與請求編號各自交給誰用、請求編號為什麼要隨機以及電話上怎麼唸，入口以 Nginx 產生編號並傳進後端與 log、入口自己的 502 與前端怎麼讀到編號（實測），錯誤碼對照表怎麼跟著程式維護，客服查詢工具的唯讀、遮罩與稽核，以及 log 保留期的下限從哪些時間與規定來">11.16 面向終端使用者的錯誤碼與請求編號</a></li>
<li>值班接班時、交班時與每週要看的訊號，包括客服單量與排名前幾名的錯誤碼：<a href="/blog/backend/04-observability/on-call-routine-checks/" data-link-title="4.29 值班的例行檢查：接班檢查與每週營運會議讀的訊號、交班紀錄的內容，以及例行檢查項目改寫成告警規則的條件" data-link-desc="告警刻意不呼叫值班的訊號由值班定時讀：接班時讀上一班的交班紀錄、生效中的告警靜音、只送通知的 warning、error budget 的走向、排程工作與心跳檢查項目、到期事件、容量走向、近期變更與客服單量，各自防的是哪一種情形；交班紀錄要寫的內容；每週營運會議讀的告警雜訊、值班負擔與重複出現的客服回報；以及一個檢查項目什麼時候該改寫成告警規則">4.29 值班的例行檢查</a></li>
</ul>
]]></content:encoded></item><item><title>Tiered Support（分層支援）</title><link>https://tarrragon.github.io/blog/backend/knowledge-cards/tiered-support/</link><pubDate>Wed, 14 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/backend/knowledge-cards/tiered-support/</guid><description>&lt;p>Tiered support（分層支援）是把處理使用者回報的人分成幾層的組織方式：一線客服（Tier 1）接所有回報，能以既有資訊答覆的當場答覆；技術支援（Tier 2）處理需要讀系統資料的回報；工程（Tier 3）處理需要改程式、或讀完整 log 與資料庫的回報。小團隊多半只有兩層，客服直接轉給工程。一線客服答覆時用的資訊之一是 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/known-error/" data-link-title="Known Error（已知錯誤）" data-link-desc="已經確認、還沒修好、影響小到不必開事故的問題要讓客服與值班知道時，查 ITIL 的已知錯誤與已知錯誤資料庫（KEDB）記錄什麼、它與事故在 ITIL 裡的關係、本站流程裡清單與事故的分流，以及清單沒有維護時的失效">Known Error（已知錯誤）&lt;/a> 清單，各層之間累積答案的方法見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/knowledge-centered-service/" data-link-title="Knowledge-Centered Service（KCS）" data-link-desc="客服或工程查完一則回報之後答案沒有留下、同一類問題每次都重查，或要決定知識文章由誰在什麼時候寫時，查 KCS 在處理回報的當下寫入與修正知識的做法">Knowledge-Centered Service（KCS）&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>分層支援處理的是使用者回報，&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/escalation-policy/" data-link-title="Escalation Policy" data-link-desc="說明事故升級鏈與值班轉接規則">Escalation Policy&lt;/a> 處理的是告警與事故：分層支援決定一則回報由哪一層的人回覆，升級策略決定一個沒有回應或沒有進展的事故轉給下一個值班的人。兩者在一個地方交會：客服判斷一則回報要立即呼叫值班時，它就從分層支援進入事故流程，由值班通知系統與升級策略接手。&lt;/p>
&lt;p>往上轉在 ITIL 的事件管理裡分成兩種：functional escalation 是把回報轉給專業能力更深的一層（一線客服轉技術支援、技術支援轉工程），hierarchical escalation 是往有權調動資源或做決定的人升（主管、事故指揮），通常發生在處理逾期或影響擴大時。同一則回報可能兩種都經過：客服把重複扣款的回報立即呼叫值班，是 functional escalation；開單之後工程逾期沒有回覆、客服往工程主管追，是 hierarchical escalation。&lt;/p>
&lt;p>分層之外另有一種做法叫 swarming（Consortium for Service Innovation 稱為 Intelligent Swarming）：回報不逐層轉，而是由能處理它的人直接協作處理。它省掉逐層轉手的等待，代價是每一則回報都要找到合適的人，適合問題種類多、單一層的資訊不夠用的組織。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;p>一則回報停在哪一層，由那一層手上的資訊與工具決定。短網址服務的客服手上有查詢工具（&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/internal-admin-tool/" data-link-title="Internal Admin Tool（客服後台）" data-link-desc="設計讓客服以帳號或訂單編號查使用者資料的內部頁面、決定它顯示哪些欄位與留下什麼紀錄，或讀到 support tooling、admin tool、back office 這些名稱要確認它們是否指同一種工具時，查客服後台的讀寫範圍、權限與稽核責任，以及它與管理端點、代登入的分界">客服後台&lt;/a>），查得到短碼存在、過期或被停用，「短網址打不開」多數在一線就能答覆；客服手上沒有這個工具，同一類回報就一路轉到工程。分層運作不良的訊號是同一類回報反覆轉到上一層、而上一層每次查的都是下一層本來查得到的資料。&lt;/p>
&lt;h2 id="判讀方式">判讀方式&lt;/h2>
&lt;p>看到回報大量往上轉時，先問上一層這次用了什麼資訊才答出來，再問這份資訊能不能交到下一層手上：能的話補成下一層的工具或知識文章，不能的話（需要改程式、需要讀含個人資料的 log）才是該轉的回報。客服與工程之間的完整流程見 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/support-escalation-and-engineering-reply/" data-link-title="8.24 客服回報的處理：客服當場答覆時用的資訊、轉給值班的條件、工程查證後給客服的回覆、查證結果寫回知識文章、告警與查詢工具，以及流程失效時的徵兆" data-link-desc="客服接到使用者回報之後，哪些資訊讓客服當場答覆（進行中的事故、已知錯誤清單、維護與變更公告、錯誤碼對照表、帳號查詢工具），哪些條件要立即呼叫值班、哪些開單在上班時間處理，工程查證之後回覆給客服的內容，同一類回報重複出現時怎麼把查證結果寫回知識文章、告警規則或查詢工具，以及從客服單與事故紀錄看出流程在哪一段失效">8.24 客服回報的處理&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Tiered support（分層支援）是把處理使用者回報的人分成幾層的組織方式：一線客服（Tier 1）接所有回報，能以既有資訊答覆的當場答覆；技術支援（Tier 2）處理需要讀系統資料的回報；工程（Tier 3）處理需要改程式、或讀完整 log 與資料庫的回報。小團隊多半只有兩層，客服直接轉給工程。一線客服答覆時用的資訊之一是 <a href="/blog/backend/knowledge-cards/known-error/" data-link-title="Known Error（已知錯誤）" data-link-desc="已經確認、還沒修好、影響小到不必開事故的問題要讓客服與值班知道時，查 ITIL 的已知錯誤與已知錯誤資料庫（KEDB）記錄什麼、它與事故在 ITIL 裡的關係、本站流程裡清單與事故的分流，以及清單沒有維護時的失效">Known Error（已知錯誤）</a> 清單，各層之間累積答案的方法見 <a href="/blog/backend/knowledge-cards/knowledge-centered-service/" data-link-title="Knowledge-Centered Service（KCS）" data-link-desc="客服或工程查完一則回報之後答案沒有留下、同一類問題每次都重查，或要決定知識文章由誰在什麼時候寫時，查 KCS 在處理回報的當下寫入與修正知識的做法">Knowledge-Centered Service（KCS）</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>分層支援處理的是使用者回報，<a href="/blog/backend/knowledge-cards/escalation-policy/" data-link-title="Escalation Policy" data-link-desc="說明事故升級鏈與值班轉接規則">Escalation Policy</a> 處理的是告警與事故：分層支援決定一則回報由哪一層的人回覆，升級策略決定一個沒有回應或沒有進展的事故轉給下一個值班的人。兩者在一個地方交會：客服判斷一則回報要立即呼叫值班時，它就從分層支援進入事故流程，由值班通知系統與升級策略接手。</p>
<p>往上轉在 ITIL 的事件管理裡分成兩種：functional escalation 是把回報轉給專業能力更深的一層（一線客服轉技術支援、技術支援轉工程），hierarchical escalation 是往有權調動資源或做決定的人升（主管、事故指揮），通常發生在處理逾期或影響擴大時。同一則回報可能兩種都經過：客服把重複扣款的回報立即呼叫值班，是 functional escalation；開單之後工程逾期沒有回覆、客服往工程主管追，是 hierarchical escalation。</p>
<p>分層之外另有一種做法叫 swarming（Consortium for Service Innovation 稱為 Intelligent Swarming）：回報不逐層轉，而是由能處理它的人直接協作處理。它省掉逐層轉手的等待，代價是每一則回報都要找到合適的人，適合問題種類多、單一層的資訊不夠用的組織。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<p>一則回報停在哪一層，由那一層手上的資訊與工具決定。短網址服務的客服手上有查詢工具（<a href="/blog/backend/knowledge-cards/internal-admin-tool/" data-link-title="Internal Admin Tool（客服後台）" data-link-desc="設計讓客服以帳號或訂單編號查使用者資料的內部頁面、決定它顯示哪些欄位與留下什麼紀錄，或讀到 support tooling、admin tool、back office 這些名稱要確認它們是否指同一種工具時，查客服後台的讀寫範圍、權限與稽核責任，以及它與管理端點、代登入的分界">客服後台</a>），查得到短碼存在、過期或被停用，「短網址打不開」多數在一線就能答覆；客服手上沒有這個工具，同一類回報就一路轉到工程。分層運作不良的訊號是同一類回報反覆轉到上一層、而上一層每次查的都是下一層本來查得到的資料。</p>
<h2 id="判讀方式">判讀方式</h2>
<p>看到回報大量往上轉時，先問上一層這次用了什麼資訊才答出來，再問這份資訊能不能交到下一層手上：能的話補成下一層的工具或知識文章，不能的話（需要改程式、需要讀含個人資料的 log）才是該轉的回報。客服與工程之間的完整流程見 <a href="/blog/backend/08-incident-response/support-escalation-and-engineering-reply/" data-link-title="8.24 客服回報的處理：客服當場答覆時用的資訊、轉給值班的條件、工程查證後給客服的回覆、查證結果寫回知識文章、告警與查詢工具，以及流程失效時的徵兆" data-link-desc="客服接到使用者回報之後，哪些資訊讓客服當場答覆（進行中的事故、已知錯誤清單、維護與變更公告、錯誤碼對照表、帳號查詢工具），哪些條件要立即呼叫值班、哪些開單在上班時間處理，工程查證之後回覆給客服的內容，同一類回報重複出現時怎麼把查證結果寫回知識文章、告警規則或查詢工具，以及從客服單與事故紀錄看出流程在哪一段失效">8.24 客服回報的處理</a>。</p>
]]></content:encoded></item><item><title>Known Error（已知錯誤）</title><link>https://tarrragon.github.io/blog/backend/knowledge-cards/known-error/</link><pubDate>Wed, 14 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/backend/knowledge-cards/known-error/</guid><description>&lt;p>Known error（已知錯誤）是 ITIL 問題管理的用語。ITIL v3 的定義是根因已經記錄、而且有替代做法（workaround）的問題；ITIL 4 放寬成已經分析過、還沒解決的問題。存放這些紀錄的地方叫已知錯誤資料庫（KEDB，Known Error Database），內容是症狀、根因、替代做法與修正的進度。客服以它當場答覆使用者，是 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/tiered-support/" data-link-title="Tiered Support（分層支援）" data-link-desc="要決定使用者回報由誰處理、什麼時候往上轉，或客服一再把同一類問題轉給工程時，查分層支援怎麼切分各層、決定一則回報停在哪一層的因素，以及它與值班升級策略的差別">Tiered Support（分層支援）&lt;/a> 一線答得出來的資訊之一；它的條目常由 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/knowledge-centered-service/" data-link-title="Knowledge-Centered Service（KCS）" data-link-desc="客服或工程查完一則回報之後答案沒有留下、同一類問題每次都重查，或要決定知識文章由誰在什麼時候寫時，查 KCS 在處理回報的當下寫入與修正知識的做法">Knowledge-Centered Service（KCS）&lt;/a> 的流程在處理回報時寫下。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>ITIL 把三種紀錄分開：事故（incident）是一次服務中斷或品質下降，包括單一使用者遇到的；問題（problem）是一個或多個事故的原因或可能的原因，建立問題紀錄時根因通常還不知道；問題分析過、還沒解決的，就是已知錯誤。在 ITIL 裡事故是事件、已知錯誤是原因，兩者會同時存在：每一位撞上同一個已知錯誤的使用者回報，都是一筆連到它的事故，以已知錯誤裡的替代做法處理。&lt;/p>
&lt;p>本站的事故流程另有一條分界，用的「事故」是需要指揮處理、影響可能擴大的那一類（接近 ITIL 的 major incident）：影響固定、有替代做法的問題列進已知錯誤清單，由客服與值班照著處理；影響正在擴大、需要有人指揮的，走 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-severity/" data-link-title="Incident Severity" data-link-desc="說明事故分級如何把產品影響轉成對應處置節奏">Incident Severity&lt;/a> 與事故流程。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;p>短網址服務的統計頁在某一款舊版瀏覽器上圖表空白，換瀏覽器就看得到：影響只限那一款瀏覽器的使用者，不會擴大，有替代做法，修正排在下一次發布。這一則列進已知錯誤清單，寫明症狀、受影響的瀏覽器版本、客服給使用者的替代做法與追蹤單號。客服接到「統計頁空白」時先查清單，符合的當場答覆，不轉給工程。&lt;/p>
&lt;h2 id="判讀方式">判讀方式&lt;/h2>
&lt;p>清單的價值取決於它是否跟上現況。修好上線的條目沒有移除，客服會繼續給使用者一個已經不需要的替代做法；新確認的問題沒有寫進去，同一類回報就繼續轉給工程。維護責任因此要指定給確認問題與發布修正的人：確認時寫入、修正上線時移除。客服怎麼用這份清單、工程什麼時候寫入，見 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/support-escalation-and-engineering-reply/" data-link-title="8.24 客服回報的處理：客服當場答覆時用的資訊、轉給值班的條件、工程查證後給客服的回覆、查證結果寫回知識文章、告警與查詢工具，以及流程失效時的徵兆" data-link-desc="客服接到使用者回報之後，哪些資訊讓客服當場答覆（進行中的事故、已知錯誤清單、維護與變更公告、錯誤碼對照表、帳號查詢工具），哪些條件要立即呼叫值班、哪些開單在上班時間處理，工程查證之後回覆給客服的內容，同一類回報重複出現時怎麼把查證結果寫回知識文章、告警規則或查詢工具，以及從客服單與事故紀錄看出流程在哪一段失效">8.24 客服回報的處理&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Known error（已知錯誤）是 ITIL 問題管理的用語。ITIL v3 的定義是根因已經記錄、而且有替代做法（workaround）的問題；ITIL 4 放寬成已經分析過、還沒解決的問題。存放這些紀錄的地方叫已知錯誤資料庫（KEDB，Known Error Database），內容是症狀、根因、替代做法與修正的進度。客服以它當場答覆使用者，是 <a href="/blog/backend/knowledge-cards/tiered-support/" data-link-title="Tiered Support（分層支援）" data-link-desc="要決定使用者回報由誰處理、什麼時候往上轉，或客服一再把同一類問題轉給工程時，查分層支援怎麼切分各層、決定一則回報停在哪一層的因素，以及它與值班升級策略的差別">Tiered Support（分層支援）</a> 一線答得出來的資訊之一；它的條目常由 <a href="/blog/backend/knowledge-cards/knowledge-centered-service/" data-link-title="Knowledge-Centered Service（KCS）" data-link-desc="客服或工程查完一則回報之後答案沒有留下、同一類問題每次都重查，或要決定知識文章由誰在什麼時候寫時，查 KCS 在處理回報的當下寫入與修正知識的做法">Knowledge-Centered Service（KCS）</a> 的流程在處理回報時寫下。</p>
<h2 id="概念位置">概念位置</h2>
<p>ITIL 把三種紀錄分開：事故（incident）是一次服務中斷或品質下降，包括單一使用者遇到的；問題（problem）是一個或多個事故的原因或可能的原因，建立問題紀錄時根因通常還不知道；問題分析過、還沒解決的，就是已知錯誤。在 ITIL 裡事故是事件、已知錯誤是原因，兩者會同時存在：每一位撞上同一個已知錯誤的使用者回報，都是一筆連到它的事故，以已知錯誤裡的替代做法處理。</p>
<p>本站的事故流程另有一條分界，用的「事故」是需要指揮處理、影響可能擴大的那一類（接近 ITIL 的 major incident）：影響固定、有替代做法的問題列進已知錯誤清單，由客服與值班照著處理；影響正在擴大、需要有人指揮的，走 <a href="/blog/backend/knowledge-cards/incident-severity/" data-link-title="Incident Severity" data-link-desc="說明事故分級如何把產品影響轉成對應處置節奏">Incident Severity</a> 與事故流程。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<p>短網址服務的統計頁在某一款舊版瀏覽器上圖表空白，換瀏覽器就看得到：影響只限那一款瀏覽器的使用者，不會擴大，有替代做法，修正排在下一次發布。這一則列進已知錯誤清單，寫明症狀、受影響的瀏覽器版本、客服給使用者的替代做法與追蹤單號。客服接到「統計頁空白」時先查清單，符合的當場答覆，不轉給工程。</p>
<h2 id="判讀方式">判讀方式</h2>
<p>清單的價值取決於它是否跟上現況。修好上線的條目沒有移除，客服會繼續給使用者一個已經不需要的替代做法；新確認的問題沒有寫進去，同一類回報就繼續轉給工程。維護責任因此要指定給確認問題與發布修正的人：確認時寫入、修正上線時移除。客服怎麼用這份清單、工程什麼時候寫入，見 <a href="/blog/backend/08-incident-response/support-escalation-and-engineering-reply/" data-link-title="8.24 客服回報的處理：客服當場答覆時用的資訊、轉給值班的條件、工程查證後給客服的回覆、查證結果寫回知識文章、告警與查詢工具，以及流程失效時的徵兆" data-link-desc="客服接到使用者回報之後，哪些資訊讓客服當場答覆（進行中的事故、已知錯誤清單、維護與變更公告、錯誤碼對照表、帳號查詢工具），哪些條件要立即呼叫值班、哪些開單在上班時間處理，工程查證之後回覆給客服的內容，同一類回報重複出現時怎麼把查證結果寫回知識文章、告警規則或查詢工具，以及從客服單與事故紀錄看出流程在哪一段失效">8.24 客服回報的處理</a>。</p>
]]></content:encoded></item><item><title>Knowledge-Centered Service（KCS）</title><link>https://tarrragon.github.io/blog/backend/knowledge-cards/knowledge-centered-service/</link><pubDate>Wed, 14 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/backend/knowledge-cards/knowledge-centered-service/</guid><description>&lt;p>Knowledge-Centered Service（KCS）是由 Consortium for Service Innovation 維護的知識管理方法，做法是把寫知識文章放進處理回報的工作本身：處理回報時先搜尋既有的文章，找到就用並順便確認它還正確，找不到就在解決之後當場寫一篇，用提問者自己的說法描述問題，讓下一個人搜尋得到。它是 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/tiered-support/" data-link-title="Tiered Support（分層支援）" data-link-desc="要決定使用者回報由誰處理、什麼時候往上轉，或客服一再把同一類問題轉給工程時，查分層支援怎麼切分各層、決定一則回報停在哪一層的因素，以及它與值班升級策略的差別">Tiered Support（分層支援）&lt;/a> 各層之間累積答案的方式，&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/known-error/" data-link-title="Known Error（已知錯誤）" data-link-desc="已經確認、還沒修好、影響小到不必開事故的問題要讓客服與值班知道時，查 ITIL 的已知錯誤與已知錯誤資料庫（KEDB）記錄什麼、它與事故在 ITIL 裡的關係、本站流程裡清單與事故的分流，以及清單沒有維護時的失效">Known Error（已知錯誤）&lt;/a> 的條目也常經由它寫下。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>KCS 把處理回報的流程稱為 Solve Loop，Practices Guide 列出四個動作：在當下記錄（capture）新的知識、把它整理（structure）成人與機器都能使用的形式、重用（reuse）既有的知識避免重做、在使用時改進（improve）它，讓它保持正確。另有兩個原則：「Reuse is Review」指每一次使用一篇文章就是在檢查它；「Flag It or Fix It」指使用文章時發現要改的地方，有把握而且有修改權限的人當場改掉，否則標記出來交給有權限的人。寫下的文章與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/known-error/" data-link-title="Known Error（已知錯誤）" data-link-desc="已經確認、還沒修好、影響小到不必開事故的問題要讓客服與值班知道時，查 ITIL 的已知錯誤與已知錯誤資料庫（KEDB）記錄什麼、它與事故在 ITIL 裡的關係、本站流程裡清單與事故的分流，以及清單沒有維護時的失效">Known Error（已知錯誤）&lt;/a> 清單的差別在範圍：已知錯誤只收還沒永久修正的問題，KCS 的文章也收使用者操作與設定這類本來就不需要修正的問題。它與事後集中寫文件的差別在時機：處理的當下，處理的人還記得細節，提問者的說法也還在手上；事後整理時兩者都已經不在。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;p>短網址服務的工程查完一則「短網址突然失效」，原因是那一批網址在服務改了有效期限之後到期。依 KCS 的做法，工程在回覆客服的同時寫一篇文章，標題用使用者的說法「短網址突然打不開」，內容寫判斷方式（查到期日）與答覆；下一則同樣的回報，客服搜尋得到這篇，當場答覆。&lt;/p>
&lt;h2 id="判讀方式">判讀方式&lt;/h2>
&lt;p>判斷一個組織的知識是否在累積，看同一類回報轉到工程的次數是否下降。次數不降時，先查答案有沒有被寫下（處理流程裡沒有寫文章這一步），再查寫下的文章能否以使用者的說法搜尋到（標題用了內部術語時搜尋不到）。客服與工程之間把查證結果寫回的流程，見 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/support-escalation-and-engineering-reply/#%e9%87%8d%e8%a4%87%e5%87%ba%e7%8f%be%e7%9a%84%e5%9b%9e%e5%a0%b1%e6%9f%a5%e8%ad%89%e7%b5%90%e6%9e%9c%e5%af%ab%e5%9b%9e%e7%9f%a5%e8%ad%98%e6%96%87%e7%ab%a0%e5%91%8a%e8%ad%a6%e8%a6%8f%e5%89%87%e8%88%87%e6%9f%a5%e8%a9%a2%e5%b7%a5%e5%85%b7" data-link-title="8.24 客服回報的處理：客服當場答覆時用的資訊、轉給值班的條件、工程查證後給客服的回覆、查證結果寫回知識文章、告警與查詢工具，以及流程失效時的徵兆" data-link-desc="客服接到使用者回報之後，哪些資訊讓客服當場答覆（進行中的事故、已知錯誤清單、維護與變更公告、錯誤碼對照表、帳號查詢工具），哪些條件要立即呼叫值班、哪些開單在上班時間處理，工程查證之後回覆給客服的內容，同一類回報重複出現時怎麼把查證結果寫回知識文章、告警規則或查詢工具，以及從客服單與事故紀錄看出流程在哪一段失效">8.24 客服回報的處理&lt;/a> 的〈重複出現的回報：查證結果寫回知識文章、告警規則與查詢工具〉。&lt;/p></description><content:encoded><![CDATA[<p>Knowledge-Centered Service（KCS）是由 Consortium for Service Innovation 維護的知識管理方法，做法是把寫知識文章放進處理回報的工作本身：處理回報時先搜尋既有的文章，找到就用並順便確認它還正確，找不到就在解決之後當場寫一篇，用提問者自己的說法描述問題，讓下一個人搜尋得到。它是 <a href="/blog/backend/knowledge-cards/tiered-support/" data-link-title="Tiered Support（分層支援）" data-link-desc="要決定使用者回報由誰處理、什麼時候往上轉，或客服一再把同一類問題轉給工程時，查分層支援怎麼切分各層、決定一則回報停在哪一層的因素，以及它與值班升級策略的差別">Tiered Support（分層支援）</a> 各層之間累積答案的方式，<a href="/blog/backend/knowledge-cards/known-error/" data-link-title="Known Error（已知錯誤）" data-link-desc="已經確認、還沒修好、影響小到不必開事故的問題要讓客服與值班知道時，查 ITIL 的已知錯誤與已知錯誤資料庫（KEDB）記錄什麼、它與事故在 ITIL 裡的關係、本站流程裡清單與事故的分流，以及清單沒有維護時的失效">Known Error（已知錯誤）</a> 的條目也常經由它寫下。</p>
<h2 id="概念位置">概念位置</h2>
<p>KCS 把處理回報的流程稱為 Solve Loop，Practices Guide 列出四個動作：在當下記錄（capture）新的知識、把它整理（structure）成人與機器都能使用的形式、重用（reuse）既有的知識避免重做、在使用時改進（improve）它，讓它保持正確。另有兩個原則：「Reuse is Review」指每一次使用一篇文章就是在檢查它；「Flag It or Fix It」指使用文章時發現要改的地方，有把握而且有修改權限的人當場改掉，否則標記出來交給有權限的人。寫下的文章與 <a href="/blog/backend/knowledge-cards/known-error/" data-link-title="Known Error（已知錯誤）" data-link-desc="已經確認、還沒修好、影響小到不必開事故的問題要讓客服與值班知道時，查 ITIL 的已知錯誤與已知錯誤資料庫（KEDB）記錄什麼、它與事故在 ITIL 裡的關係、本站流程裡清單與事故的分流，以及清單沒有維護時的失效">Known Error（已知錯誤）</a> 清單的差別在範圍：已知錯誤只收還沒永久修正的問題，KCS 的文章也收使用者操作與設定這類本來就不需要修正的問題。它與事後集中寫文件的差別在時機：處理的當下，處理的人還記得細節，提問者的說法也還在手上；事後整理時兩者都已經不在。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<p>短網址服務的工程查完一則「短網址突然失效」，原因是那一批網址在服務改了有效期限之後到期。依 KCS 的做法，工程在回覆客服的同時寫一篇文章，標題用使用者的說法「短網址突然打不開」，內容寫判斷方式（查到期日）與答覆；下一則同樣的回報，客服搜尋得到這篇，當場答覆。</p>
<h2 id="判讀方式">判讀方式</h2>
<p>判斷一個組織的知識是否在累積，看同一類回報轉到工程的次數是否下降。次數不降時，先查答案有沒有被寫下（處理流程裡沒有寫文章這一步），再查寫下的文章能否以使用者的說法搜尋到（標題用了內部術語時搜尋不到）。客服與工程之間把查證結果寫回的流程，見 <a href="/blog/backend/08-incident-response/support-escalation-and-engineering-reply/#%e9%87%8d%e8%a4%87%e5%87%ba%e7%8f%be%e7%9a%84%e5%9b%9e%e5%a0%b1%e6%9f%a5%e8%ad%89%e7%b5%90%e6%9e%9c%e5%af%ab%e5%9b%9e%e7%9f%a5%e8%ad%98%e6%96%87%e7%ab%a0%e5%91%8a%e8%ad%a6%e8%a6%8f%e5%89%87%e8%88%87%e6%9f%a5%e8%a9%a2%e5%b7%a5%e5%85%b7" data-link-title="8.24 客服回報的處理：客服當場答覆時用的資訊、轉給值班的條件、工程查證後給客服的回覆、查證結果寫回知識文章、告警與查詢工具，以及流程失效時的徵兆" data-link-desc="客服接到使用者回報之後，哪些資訊讓客服當場答覆（進行中的事故、已知錯誤清單、維護與變更公告、錯誤碼對照表、帳號查詢工具），哪些條件要立即呼叫值班、哪些開單在上班時間處理，工程查證之後回覆給客服的內容，同一類回報重複出現時怎麼把查證結果寫回知識文章、告警規則或查詢工具，以及從客服單與事故紀錄看出流程在哪一段失效">8.24 客服回報的處理</a> 的〈重複出現的回報：查證結果寫回知識文章、告警規則與查詢工具〉。</p>
]]></content:encoded></item></channel></rss>