除錯與工具

印出除錯、日誌記錄與日誌等級(printf-debugging / logging)

世上最古老、最簡單的除錯動作,就是在你的程式碼裡撒幾句「我到這裡了,而且 x 是 5」的訊息、看著它們在執行時飛過去。那就是印出除錯:你在可疑的地方插入印出敘述,揭露哪段程式碼跑了、那些值是什麼,從它留下的訊息軌跡重建一次執行的故事。它不需要特殊工具、到處都能用,而且常是測試一個快速假設的最快方式。

具體來說,你在有興趣的地方加上像 printf("reached parse, n=%d\n", n); 這樣的行,重新建構、再讀輸出。日誌記錄是同一個想法的成熟版:你不再用那些日後要刪掉的臨時印出,而是透過一套日誌設施寫出結構化的訊息,它會加上時間戳、原始碼位置與一個嚴重程度等級。日誌等級依重要性把訊息分級,慣例上從最嚴重到最不嚴重:ERROR(某件事失敗了)、WARN(某件事可疑)、INFO(正常的里程碑)、DEBUG(給開發者的詳細麵包屑)、TRACE(極其細緻)。你設一個門檻——「顯示 INFO 與以上」——這樣同一份程式碼在生產環境可以安靜、在你調查時可以囉嗦,而不必去改它。C 裡的一個重要細節:把除錯輸出送到標準錯誤(stderr)、而非標準輸出,這樣它才不會弄髒程式真正的輸出,並記得 stdout 是有緩衝的,所以一場當機可能吞掉一個從未被沖出(flush)的 printf。

為何重要:印出除錯與日誌記錄是普世的,並能在除錯器吃力的地方存活——跨機器、在生產環境、在暫停就會藏住臭蟲的時序敏感程式碼裡、以及在你不容易單步走過的程式碼裡。誠實的侷限是真實的:印出只顯示你想到要印的東西,所以它們證實猜想、卻很少用你沒料到的成因給你驚喜;加一個印出會改變時序與緩衝、可能讓一個 heisenbug 搬家或消失;太多日誌變成雜訊;而留在出貨程式碼裡的陳舊除錯印出是經典的爛攤子。用印出去測試特定的假設、在你需要檢查沒料到的狀態時動用除錯器,並讓真正的日誌保持結構化與分級,而不是到處亂撒 printf。

快速檢查: fprintf(stderr, "after open, fd=%d, errno=%d\n", fd, errno); 印到 stderr(而非 stdout)並可靠地沖出,揭露 fd 在 open() 之後緊接著就是 -1——所以開檔失敗了,於是你去調查路徑、而不是那段日後才當掉的解析。

印到 stderr 並沖出;open() 之後緊接著的 fd 值指向真正的失敗。

stdout 是有緩衝的,所以緊接在當機之前的 printf 可能根本不會出現,除非你沖出(fflush)或用 stderr——一個訊息沒出現並不代表那一行沒被走到。印出只揭露你預先想到要記錄的東西,而加一個印出可能挪動一個 heisenbug 的時序。

又稱
print debuggingtracing with printslogging levels印出式除錯日誌等級