
5分鐘給cpp-httplib接上請求追蹤ID、耗時、狀態碼一條日志全帶出來【免費下載鏈接】cpp-httplibA C header-only HTTP/HTTPS server and client library項目地址: https://gitcode.com/GitHub_Trending/cp/cpp-httplibcpp-httplib 是一個 header-only 的 C HTTP 庫一個httplib.h就能跑起來。但服務一旦上線這個請求是誰發的、跑了多久、最后返回了什么就全靠自己猜。讀完這篇文章你只需要改不到 30 行代碼就能讓每個請求都帶著 request ID、耗時和狀態碼進日志排查時直接按 ID 過濾。5分鐘跑通一個最小追蹤例子思路很簡單請求進門時打一張時間戳和 ID 的標簽請求結束時統一結算。cpp-httplib 的四個鉤子剛好覆蓋這兩端——set_pre_routing_handler()在路由前對每個請求生效set_logger()在請求結束響應發出前時觸發中間用res.user_data傳數據。下面這份代碼可以直接編譯運行g -stdc17 -O2 trace_demo.cc -o trace_demo -lpthread把httplib.h放在同目錄#include httplib.h // header-only 庫單頭文件搞定 #include atomic #include chrono #include iostream int main() { httplib::Server svr; // 進門登記給每個請求生成 ID并把開始時間塞進 user_data svr.set_pre_routing_handler( [](const httplib::Request req, httplib::Response res) { static std::atomicunsigned long long seq{0}; res.set_header(X-Request-ID, std::to_string(seq)); // ID 同時回寫給客戶端 res.user_data.set(start, std::chrono::steady_clock::now()); return httplib::Server::HandlerResponse::Unhandled; // 放行繼續走業務路由 }); // 出門結算請求結束時 logger 觸發此時響應已經生成能算出真實耗時 svr.set_logger([](const httplib::Request req, const httplib::Response res) { auto *start res.user_data.getstd::chrono::steady_clock::time_point(start); auto ms std::chrono::duration_caststd::chrono::milliseconds( std::chrono::steady_clock::now() - *start).count(); std::cout req.remote_addr req.method req.path - res.status ms ms std::endl; }); svr.Get(/hello, [](const httplib::Request , httplib::Response res) { res.set_content(hello, text/plain); }); svr.listen(0.0.0.0, 8080); }啟動后訪問http://localhost:8080/hello控制臺會看到類似這樣的輸出用curl -i還能在響應頭里拿到X-Request-ID127.0.0.1 GET /hello - 200 1ms 127.0.0.1 GET /nope - 404 0ms注意第二條沒匹配到路由的 404 也會被記錄因為 pre-routing 鉤子攔的是所有請求不挑路由。原理速拆誰登記、誰結算把一次請求想象成餐廳流程set_pre_routing_handler()是門口領位員。客人請求還沒被分配到具體餐桌路由匹配之前領位員先做三件事發個號request ID、在號牌上寫下進門時間user_data.set(start, ...)、決定放不放行返回Handled直接拒絕返回Unhandled放行。res.user_data是號牌本身。它掛在Response上任意類型隨便塞后面的處理器都能通過res.user_data.getT(start)取回。官方 cookbook 里就叫它handlers 之間的數據交接袋參考 S12. Pass Data between Handlers with res.user_data。set_logger()是結賬柜臺。客人離桌后響應生成完畢、即將發往客戶端統一結算號牌上的進門時間還在減去現在就是耗時狀態碼也已經寫在res.status里了。對應的真實定義都在httplib.h這一個文件里關鍵就四行using Logger std::functionvoid(const Request , const Response ); // 請求結束時觸發 Server set_pre_routing_handler(HandlerWithResponse handler); // 路由前 Server set_post_routing_handler(Handler handler); // 路由后、發響應前 class UserData; // res.user_data任意類型的口袋為什么不能全塞進 pre-routing 里打印因為那時業務 handler 還沒跑res.status還是空的、耗時也算不出。登記和結算必須分開這正是埋點 結算兩步的結構。生產環境你會踩的坑1. 耗時永遠是 0或者get出來的指針是空的現象日志里耗時列全是 0加斷點發現user_data.get...(start)返回nullptr。 原因set和get的類型必須逐字符一致——存成std::chrono::steady_clock::time_point卻按const ...time_point取類型 ID 對不上直接返回空。 修復set和getT用同一個精確類型取到后先判空再解引用。2. 想在 pre-routing 里打印狀態碼結果全是 0現象埋點位置打印res.status輸出 0 或 200 的固定值。 原因pre-routing 跑在業務 handler 之前響應根本沒生成此時打印狀態碼沒有意義。 修復狀態碼相關的日志一律挪到set_logger()里埋點鉤子只負責登記。3. 高并發下日志行互相串現象QPS 上去后一條日志被拆成兩行、和別的請求的輸出交錯。 原因cpp-httplib 每個 worker 線程同步執行 logger多線程同時寫std::cout沒有任何鎖。 修復logger 里加std::lock_guardstd::mutex lk(log_mtx);或改用自帶線程安全的日志庫。4. 在 logger 里直接寫盤或遠程上報QPS 被拖垮現象接入追蹤后接口延遲整體上升壓測吞吐掉檔。 原因logger 運行在請求處理線程上是同步的磁盤 I/O 或網絡上報的耗時會原樣加到每個請求上。 修復logger 里只做queue.push(record);入隊由獨立后臺線程異步落盤或上報。自建埋點、set_logger 和 OpenTelemetry 的取舍方案適用場景取舍pre-routing user_data logger本文自己打點要 request ID、耗時、慢請求告警代碼最少、零依賴但跨服務傳播要自己拼請求頭只用set_logger()只需要 nginx 風格的 access log十幾行搞定但沒有請求級預處理無法注入 ID 或計時OpenTelemetry C SDK要和現有 APM 平臺、其他語言的分布式鏈路打通換來標準 traceparent 傳播和采樣代價是多一套 SDK 依賴和配置如果走分布式方案客戶端側的傳播在 cpp-httplib 里也很直接把 trace 上下文放進默認頭下游服務讀出來即可例如client.set_default_headers({{traceparent, ctx_string}});。到這里開頭的承諾已經兌現不到 30 行你的每個請求都有了可追蹤的 ID、真實耗時和狀態碼。下一步就做一件事把示例里的計數器換成 UUID然后curl -i http://localhost:8080/hello對著響應頭里的X-Request-ID去日志里 grep 一遍——能對上追蹤鏈路就算真正通了。【免費下載鏈接】cpp-httplibA C header-only HTTP/HTTPS server and client library項目地址: https://gitcode.com/GitHub_Trending/cp/cpp-httplib創作聲明:本文部分內容由AI輔助生成(AIGC),僅供參考