5分钟给cpp-httplib接上请求追踪:ID、耗时、状态码一条日志全带出来
【免费下载链接】cpp-httplibA C++ header-only HTTP/HTTPS server and client library项目地址: https://gitcode.com/GitHub_Trending/cp/cpp-httplib
cpp-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++ -std=c++17 -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::atomic<unsigned 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.get<std::chrono::steady_clock::time_point>("start"); auto ms = std::chrono::duration_cast<std::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-ID:
127.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.get<T>("start")取回。官方 cookbook 里就叫它"handlers 之间的数据交接袋",参考 S12. Pass Data between Handlers with res.user_data。set_logger()是结账柜台。客人离桌后(响应生成完毕、即将发往客户端)统一结算:号牌上的进门时间还在,减去现在就是耗时,状态码也已经写在res.status里了。
对应的真实定义都在httplib.h这一个文件里,关键就四行:
using Logger = std::function<void(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和get<T>用同一个精确类型,取到后先判空再解引用。
2. 想在 pre-routing 里打印状态码,结果全是 0现象:埋点位置打印res.status输出 0 或 200 的固定值。 原因:pre-routing 跑在业务 handler 之前,响应根本没生成,此时打印状态码没有意义。 修复:状态码相关的日志一律挪到set_logger()里,埋点钩子只负责"登记"。
3. 高并发下日志行互相串现象:QPS 上去后,一条日志被拆成两行、和别的请求的输出交错。 原因:cpp-httplib 每个 worker 线程同步执行 logger,多线程同时写std::cout没有任何锁。 修复:logger 里加std::lock_guard<std::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),仅供参考