Backend Development 7 min read

How to Log HTTP Requests & Responses with Spring Boot Actuator (Full Code Guide)

This article explains how to use Spring Boot Actuator's HTTP trace feature to record request and response metadata, shows the required dependencies and configuration, demonstrates a custom Redis repository, and provides complete code examples for testing the logging functionality.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
How to Log HTTP Requests & Responses with Spring Boot Actuator (Full Code Guide)

Introduction

In a microservice architecture, logging API request and response data is essential for system monitoring, debugging, and security auditing. Spring Boot Actuator offers a built‑in filter that can easily record request/response information.

The /actuator/httptrace endpoint records the last 100 HTTP requests' metadata (method, URI, status code, duration, etc.), but it does not include request or response bodies by default.

Practical Example

1. Add Dependency

<code>&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  &lt;artifactId&gt;spring-boot-starter-actuator&lt;/artifactId&gt;
&lt;/dependency&gt;</code>

2. Enable Recording

<code>management:
  endpoints:
    web:
      base-path: /ac
  httpexchanges:
    recording:
      enabled: true</code>

3. Configure Repository

After enabling the feature, an HttpExchangeRepository must be provided. Spring Boot supplies InMemoryHttpExchangeRepository which stores up to 100 entries in memory.

4. Custom Repository (Redis)

<code>@Component
public class RedisHttpExchangeRepository implements HttpExchangeRepository {
    private final StringRedisTemplate stringRedisTemplate;
    private final ObjectMapper objectMapper;

    public RedisHttpExchangeRepository(StringRedisTemplate stringRedisTemplate, ObjectMapper objectMapper) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.objectMapper = objectMapper;
    }

    @Override
    public List<HttpExchange> findAll() {
        List<String> list = stringRedisTemplate.opsForList().range("http:request:list", 0, -1);
        return list.stream().map(str -> {
            try {
                HttpLog log = objectMapper.readValue(str, HttpLog.class);
                HttpLog.Request req = log.getRequest();
                HttpExchange.Request request = new HttpExchange.Request(req.getUri(), req.getRemoteAddress(), req.getMethod(), req.getHeaders());
                HttpLog.Response resp = log.getResponse();
                HttpExchange.Response response = new HttpExchange.Response(resp.getStatus(), resp.getHeaders());
                return new HttpExchange(log.getTimestamp().atZone(ZoneId.systemDefault()).toInstant(), request, response, log.getPrincipal(), null, log.getTimeTaken());
            } catch (JsonProcessingException e) {
                return null;
            }
        }).filter(Objects::nonNull).toList();
    }

    @Override
    public void add(HttpExchange httpExchange) {
        try {
            HttpLog log = new HttpLog();
            log.setPrincipal(httpExchange.getPrincipal());
            log.setTimestamp(httpExchange.getTimestamp().atZone(ZoneId.systemDefault()).toLocalDateTime());
            log.setTimeTaken(httpExchange.getTimeTaken());
            HttpLog.Request request = new HttpLog.Request();
            BeanUtils.copyProperties(httpExchange.getRequest(), request);
            HttpLog.Response response = new HttpLog.Response();
            BeanUtils.copyProperties(httpExchange.getResponse(), response);
            log.setRequest(request);
            log.setResponse(response);
            stringRedisTemplate.opsForList().leftPush("http:request:list", objectMapper.writeValueAsString(log));
        } catch (JsonProcessingException e) {
            // handle exception
        }
    }
}</code>

5. Test API

<code>@RestController
@RequestMapping("/api")
public class ApiController {
    @GetMapping("/{id}")
    public ResponseEntity<User> query(@PathVariable Long id) {
        return ResponseEntity.ok(new User(id, "Name - " + id));
    }

    @GetMapping("")
    public ResponseEntity<List<User>> list(String name) {
        return ResponseEntity.ok(List.of(new User(1L, name + " - 1"), new User(2L, name + " - 2")));
    }

    @GetMapping("/s/{type}")
    public ResponseEntity<String> s(@PathVariable String type, String name) {
        return ResponseEntity.ok(String.format("type: %s, name: %s", type, name));
    }
}</code>

After invoking the three sample endpoints, accessing /ac/httptrace displays the recorded traces (see image).

HTTP trace example
HTTP trace example

For production environments, replace the default in‑memory repository with a persistent implementation such as the Redis repository shown above.

Javabackend developmentloggingSpring BootActuatorhttptrace
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.