如何将Rest-Assured、Log4j及System.out日志输出至Log4j文件并添加归属信息
我来帮你搞定这个日志整合和归属标记的问题!下面是一步步的解决方案,结合代码示例,保证能让你的日志清晰区分测试类和用例,同时把三种日志统一输出到Log4j文件里~
解决方案:统一日志输出并添加测试归属信息
1. 先搞定全渠道日志统一输出
首先要把System.out.println、Log4j的log.info和Rest-Assured的日志都统一到Log4j文件中:
- 重定向
System.out到Log4j:通过自定义输出流,把控制台打印的内容转成Log4j的info级别日志 - 接管Rest-Assured日志:替换默认的控制台输出,改成用Log4j打印请求/响应日志
2. 用MDC传递测试上下文
Log4j的**MDC(映射诊断上下文)**是线程级的上下文存储工具,我们可以在测试用例执行前后,把测试类名、用例名存入MDC,这样所有日志都会自动带上这些归属信息,不用手动拼接。
3. 自定义Rest-Assured日志过滤器
Rest-Assured支持自定义过滤器,我们可以用这个特性给请求/响应日志加上测试类、用例标记,甚至添加自定义注释,把日志按测试用例划分开。
完整代码示例
第一步:Log4j2配置文件(log4j2.xml)
先配置Log4j,让日志输出到文件,并且格式中包含MDC的测试类和用例信息:
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <Appenders> <File name="TestLogFile" fileName="test-execution.log"> <!-- 日志格式:时间 | 线程 | 级别 | 测试类 | 测试用例 | 日志内容 --> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%X{testClass}] [%X{testCase}] - %msg%n"/> </File> </Appenders> <Loggers> <!-- 根日志,处理Log4j的log.info和System.out转来的日志 --> <Root level="info"> <AppenderRef ref="TestLogFile"/> </Root> <!-- 专门处理Rest-Assured的日志,避免重复输出 --> <Logger name="rest-assured-logger" level="info" additivity="false"> <AppenderRef ref="TestLogFile"/> </Logger> </Loggers> </Configuration>
第二步:System.out转Log4j工具类
package com.students.loggingexamples; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.OutputStream; import java.io.PrintStream; public class SystemOutToLog4j { private static final Logger systemOutLogger = LogManager.getLogger("system-out"); public static void redirect() { System.setOut(new PrintStream(new OutputStream() { private final StringBuilder logBuilder = new StringBuilder(); @Override public void write(int b) { if (b == '\n') { // 换行时把缓存的内容输出到Log4j systemOutLogger.info(logBuilder.toString().trim()); logBuilder.setLength(0); } else { logBuilder.append((char) b); } } }, true)); } }
第三步:自定义Rest-Assured日志过滤器
package com.students.loggingexamples; import io.restassured.filter.Filter; import io.restassured.filter.FilterContext; import io.restassured.response.Response; import io.restassured.specification.FilterableRequestSpecification; import io.restassured.specification.FilterableResponseSpecification; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.ThreadContext; public class TestContextLogFilter implements Filter { private static final Logger restLogger = LogManager.getLogger("rest-assured-logger"); @Override public Response filter(FilterableRequestSpecification requestSpec, FilterableResponseSpecification responseSpec, FilterContext ctx) { // 从MDC获取测试类和用例信息 String testClass = ThreadContext.get("testClass"); String testCase = ThreadContext.get("testCase"); // 输出自定义请求开始标记 restLogger.info("=== [{}] 测试用例: {} | 请求开始 ===", testClass, testCase); restLogger.info("请求URL: {}", requestSpec.getURI()); restLogger.info("请求方法: {}", requestSpec.getMethod()); if (requestSpec.getBody() != null) { restLogger.info("请求体: {}", requestSpec.getBody().toString()); } // 执行请求获取响应 Response response = ctx.next(requestSpec, responseSpec); // 输出响应日志和结束标记 restLogger.info("=== [{}] 测试用例: {} | 响应结果 ===", testClass, testCase); restLogger.info("响应状态码: {}", response.getStatusCode()); restLogger.info("响应体: {}", response.getBody().asString()); restLogger.info("=== [{}] 测试用例: {} | 请求结束 ===", testClass, testCase); return response; } }
第四步:测试类示例
package com.students.loggingexamples; import com.student.base.TestBase; import io.restassured.RestAssured; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.ThreadContext; import org.junit.After; import org.junit.Before; import org.junit.Test; public class StudentApiTest extends TestBase { private static final Logger testLogger = LogManager.getLogger(StudentApiTest.class); @Before public void globalSetup() { // 继承父类的Rest-Assured基础配置 super.setup(); // 重定向System.out到Log4j SystemOutToLog4j.redirect(); // 添加自定义日志过滤器到Rest-Assured RestAssured.filters(new TestContextLogFilter()); } private void setupTestContext(String testCaseName) { // 把测试类和用例名存入MDC ThreadContext.put("testClass", this.getClass().getSimpleName()); ThreadContext.put("testCase", testCaseName); // 输出测试用例开始的分隔线 testLogger.info("============================================="); testLogger.info("开始执行测试用例: {}", testCaseName); testLogger.info("============================================="); } @After public void teardownTestContext() { // 清除MDC上下文,避免线程复用导致的信息污染 ThreadContext.clearAll(); // 输出测试用例结束的分隔线 testLogger.info("============================================="); testLogger.info("测试用例执行完成"); testLogger.info("============================================="); } @Test public void testGetStudentById() { setupTestContext("testGetStudentById"); // Log4j日志 testLogger.info("准备发送GET请求,获取ID为1001的学生信息"); // System.out日志 System.out.println("当前请求的学生ID参数:1001"); // Rest-Assured请求(替换为你的实际接口) RestAssured.given() .pathParam("studentId", 1001) .when() .get("/students/{studentId}"); } @Test public void testCreateNewStudent() { setupTestContext("testCreateNewStudent"); testLogger.info("准备发送POST请求,创建新学生"); System.out.println("新学生的姓名:李四,年龄:22"); RestAssured.given() .contentType("application/json") .body("{\"name\":\"李四\",\"age\":22,\"grade\":\"大三\"}") .when() .post("/students"); } }
最终效果
所有日志(Log4j的log.info、System.out.println、Rest-Assured的请求响应)都会输出到test-execution.log文件,并且每条日志都清晰标记了所属的测试类和用例,格式示例:
2024-05-20 15:30:00.123 [main] INFO [StudentApiTest] [] - ============================================= 2024-05-20 15:30:00.124 [main] INFO [StudentApiTest] [] - 开始执行测试用例: testGetStudentById 2024-05-20 15:30:00.125 [main] INFO [StudentApiTest] [testGetStudentById] - 准备发送GET请求,获取ID为1001的学生信息 2024-05-20 15:30:00.126 [main] INFO [system-out] [StudentApiTest] [testGetStudentById] - 当前请求的学生ID参数:1001 2024-05-20 15:30:00.567 [main] INFO [rest-assured-logger] [StudentApiTest] [testGetStudentById] - === [StudentApiTest] 测试用例: testGetStudentById | 请求开始 === 2024-05-20 15:30:00.568 [main] INFO [rest-assured-logger] [StudentApiTest] [testGetStudentById] - 请求URL: http://your-api-host/students/1001 2024-05-20 15:30:00.569 [main] INFO [rest-assured-logger] [StudentApiTest] [testGetStudentById] - 请求方法: GET
这样就能完美区分每个测试用例的日志,可读性拉满!
内容的提问来源于stack exchange,提问作者seleniumappiumnewbie
相关产品推荐
相关产品推荐

