📌 版本说明: 这是 JDK 21 版本(主仓库)。如果你的项目使用 JDK 8,请使用 support-jdk8 分支版本。
一个轻量级的 Java HTTP 请求/响应日志记录库,快照式捕获完整的 HTTP 交互。
同时支持 客户端(OkHttp)和 服务端(Spring MVC)场景。
- 🔄 双向支持 - 客户端发起请求、服务端处理请求,一套 API 全搞定
- ⏱️ 详细耗时 - DNS、连接、TTFB、下载...每个阶段耗时一目了然
- 📊 多种格式 - 文本格式便于调试,JSON 格式便于分析
- 🛡️ 脱敏支持 - Authorization、Cookie 等敏感头自动脱敏
- 🔗 URL 规范化 - 自动将路径中的数字 ID 替换为占位符,降低监控系统标签基数
- 🎯 零侵入 - 不改业务代码,Filter/EventListener 即插即用
- 🔧 可扩展 - 格式化器、输出目标、适配器全部可定制
- 🎛️ 灵活过滤 - 支持基于 URL + body/header 条件的智能排除规则,JSON 表达式过滤
- 📡 SSE 支持 - 针对 SSE 流式响应提供专门规则,只记录请求不记录响应体
<dependency>
<groupId>io.github.null-object-0000</groupId>
<artifactId>http-log-snap</artifactId>
<version>0.0.11</version>
</dependency><dependency>
<groupId>io.github.null-object-0000</groupId>
<artifactId>http-log-snap-jdk8</artifactId>
<version>0.0.11</version>
</dependency>💡 提示:
- 如果你的项目使用 JDK 21,请使用
http-log-snap依赖(主仓库版本)- 如果你的项目使用 JDK 8,请使用
http-log-snap-jdk8依赖(support-jdk8 分支)
// 1. 创建 OkHttpClient
OkHttpClient client = new OkHttpClient.Builder()
.eventListenerFactory(OkHttpLoggingEventListener.FACTORY)
.build();
// 2. 创建 Logger
HttpRequestLogger logger = HttpRequestLogger.forClient(HttpLogContext.of("创建订单"));
logger.putExtra("orderId", "ORD123");
// 3. 发起请求
String body = "{\"name\":\"test\"}";
logger.start(body);
Request request = new Request.Builder()
.url("https://api.example.com/orders")
.tag(HttpRequestLogger.class, logger)
.post(RequestBody.create(body, MediaType.parse("application/json")))
.build();
try (Response response = client.newCall(request).execute()) {
logger.end(response.body().string());
}
// 4. 输出日志
logger.log();# application.yml
newbie:
http:
logging:
enabled: true// 方式1:实现 HttpLogCustomizer 统一定制(推荐)
@Component
public class MyHttpLogCustomizer implements HttpLogCustomizer {
@Override
public void customize(HttpRequestLogger logger, HttpServletRequest request) {
// 添加 traceId
logger.putExtra("traceId", request.getHeader("X-Trace-Id"));
// 根据路径设置接口名称
if (request.getRequestURI().startsWith("/api/users")) {
logger.setInterfaceName("用户服务");
}
}
}
// 方式2:在 Controller 中按需设置
HttpRequestLogger logger = HttpRequestLoggerHolder.get();
if (logger != null) {
logger.putExtra("orderId", "ORD123");
}
// 方式3:在全局异常处理器中记录异常(如果有 GlobalExceptionHandler)
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public BaseResponse<?> errorHandler(Exception e) {
HttpLoggingExceptionHandler.record(e); // 添加这一行
// ... 原有逻辑
return BaseResponse.error();
}
}搞定! 服务端自动记录所有 HTTP 请求/响应。
15:42:31.123 --- LOG EXTRAS -------------------------------------------------------
{"userId":10086,"source":"app"}
15:42:31.123 --- START [NONE] 获取用户信息 (total: 245ms)
15:42:31.180 --> DNS LOOKUP (50ms)
15:42:31.210 --> CONNECTING (30ms) [192.168.1.100:54321 -> 203.0.113.50:443]
15:42:31.215 --> REQUEST START --------------------------------------------------->
GET https://api.example.com/users/123 HTTP/2
Authorization: ██
15:42:31.350 <-- RESPONSE START <--------------------------------------------------
HTTP/2 200 OK (130ms)
Content-Type: application/json
{"id":123,"name":"张三","email":"zhangsan@example.com"}
15:42:31.368 <-- END RESPONSE (18ms, 256-byte body)
15:42:31.100 --- LOG EXTRAS -------------------------------------------------------
{"orderId":"ORD123","channel":"web"}
15:42:31.100 --- START [SERVER] 用户登录 -> UserController.login (total: 156ms) [client: 192.168.1.50:52341]
15:42:31.102 --> REQUEST START --------------------------------------------------->
POST http://localhost:8080/api/login HTTP/1.1
Content-Type: application/json
{"username":"zhangsan","password":"****"}
15:42:31.245 <-- RESPONSE START <--------------------------------------------------
200 OK (handler: 135ms)
{"code":0,"message":"success","data":{"token":"eyJ..."}}
15:42:31.256 <-- END RESPONSE (11ms, 128-byte body)
{
"type": "HTTP_CLIENT",
"duration_ms": 245,
"network": {
"local_address": "192.168.1.100:54321",
"remote_address": "203.0.113.50:443"
},
"request": {
"method": "GET",
"url": "https://api.example.com/users/123"
},
"response": {
"code": 200,
"body": "{\"id\":123,\"name\":\"张三\"}"
},
"timing": {
"dns_lookup_ms": 50,
"connection_ms": 30,
"server_processing_ms": 130
}
}| 客户端指标 | 服务端指标 |
|---|---|
| DNS 解析时间 | 请求体读取时间 |
| TCP/TLS 连接时间 | Handler 执行时间 |
| 请求发送时间 | 响应构建时间 |
| TTFB(首字节时间) | 响应发送时间 |
| 内容下载时间 | 框架开销时间 |
| 配置项 | 默认值 | 说明 |
|---|---|---|
| 格式化器 | JsonHttpLogFormatter |
JSON 格式 |
| 输出目标 | Slf4jLogOutput |
输出到 SLF4J(INFO 级别) |
| 请求体/响应体最大长度 | 无限制 |
默认不限制,可通过配置设置限制(超过限制会被截断) |
-1)。如果设置了限制值,超过此长度的内容会被截断,并在日志中显示 ... [truncated N bytes] 提示。建议根据实际日志系统的限制来设置合适的限制值。
切换格式化器:
// 使用 JSON 格式
HttpRequestLogger.setDefaultFormatter(new JsonHttpLogFormatter());
// 使用美化的 JSON 格式
HttpRequestLogger.setDefaultFormatter(new JsonHttpLogFormatter(true));
// 使用 JSON 格式 + 记录完整事件序列(用于分析调用链路)
HttpRequestLogger.setDefaultFormatter(new JsonHttpLogFormatter()
.setPrettyPrint(true)
.setIncludeEvents(true));切换输出目标:
// 输出到控制台
HttpRequestLogger.setDefaultOutput(new ConsoleLogOutput());
// 输出到 SLF4J DEBUG 级别
HttpRequestLogger.setDefaultOutput(new Slf4jLogOutput(Slf4jLogOutput.LogLevel.DEBUG));
// 同时输出到多个目标
HttpRequestLogger.setDefaultOutput(CompositeLogOutput.of(
new Slf4jLogOutput(),
new ConsoleLogOutput()
));配置请求体/响应体最大长度:
# Spring Boot 自动配置(application.yml)
newbie:
http:
logging:
max-payload-length: 512000 # 512KB(单位:字节,-1 表示无限制)// 代码配置
HttpLoggingFilter filter = new HttpLoggingFilter();
filter.setMaxPayloadLength(512 * 1024); // 512KB条件排除规则(高级功能):
支持基于 URL + body/header 条件的灵活排除规则,包括 JSON 表达式过滤:
newbie:
http:
logging:
enabled: true
exclude-patterns: # 简单排除:直接排除 URL 模式
- /actuator/**
- /health
exclude-rules: # 条件排除:URL 匹配 + body/header 条件
- url-pattern: /api/webhook
body-json-expression: "stream == true" # JSON 表达式过滤
- url-pattern: /api/test
header-name: X-Skip-Log
header-value: "true"
sse-rules: # SSE 响应规则:只记录请求,不记录响应
- url-pattern: /api/events/**
- url-pattern: /api/stream/**
- url-pattern: /api/notifications
header-name: Accept
header-value-contains: "text/event-stream"💡 提示: 更多条件排除规则和 SSE 响应规则配置,请查看 高级用法文档
URL 规范化(降低监控标签基数):
// 客户端:在 HttpLogContext 中配置占位符
HttpRequestLogger logger = HttpRequestLogger.forClient(
HttpLogContext.builder()
.interfaceName("获取场次信息")
.urlPlaceholders("{showId}", "{ticketId}") // 自定义占位符
.build()
);
// 服务端:在 HttpLogCustomizer 中配置
@Component
public class MyHttpLogCustomizer implements HttpLogCustomizer {
@Override
public void customize(HttpRequestLogger logger, HttpServletRequest request) {
if (request.getRequestURI().startsWith("/api/info/")) {
logger.getContext().setUrlPlaceholders(new String[]{"{showId}", "{ticketId}"});
}
}
}URL /api/info/2434420/1459635/seats 会被规范化为 /api/info/{showId}/{ticketId}/seats,便于在监控系统中聚合统计。
- 📖 使用指南 - 完整的使用教程
- 🔧 高级用法 - 自定义格式化器、输出目标、适配器
- 📋 版本记录 - 查看所有版本的更新内容
- 🔄 开发与发布流程 - 开发规范、约束和发布流程
- 💡 接入示例 - 在 IDE 中直接运行:
- OkHttp 客户端:
src/test/java/.../demo/OkHttpClientDemo.java - Spring Boot 服务端:
src/test/java/.../demo/SpringBootServerDemo.java
- OkHttp 客户端:
欢迎提交 Issue 和 Pull Request!
MIT License