You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:58:11