[NO-ISSUE] Initialize project
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package com.myoa.engineering.sample;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SpringBootRequestResponseLoggingExampleApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringBootRequestResponseLoggingExampleApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.myoa.engineering.sample.configuration;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
|
||||
@EnableJpaAuditing
|
||||
@Configuration
|
||||
public class DataSourceConfiguration {
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.myoa.engineering.sample.configuration;
|
||||
|
||||
import com.myoa.engineering.sample.configuration.logging.LoggingInterceptor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebConfiguration implements WebMvcConfigurer {
|
||||
private final LoggingInterceptor loggingInterceptor;
|
||||
|
||||
public WebConfiguration(LoggingInterceptor loggingInterceptor) {
|
||||
this.loggingInterceptor = loggingInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(loggingInterceptor).addPathPatterns("/api/**");
|
||||
WebMvcConfigurer.super.addInterceptors(registry);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.myoa.engineering.sample.configuration.logging;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class CustomServletWrappingFilter extends OncePerRequestFilter {
|
||||
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
String traceId = UUID.randomUUID().toString();
|
||||
MDC.put("traceId", traceId);
|
||||
RereadableRequestWrapper wrappedRequest = new RereadableRequestWrapper(request);
|
||||
ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);
|
||||
filterChain.doFilter(wrappedRequest, wrappedResponse);
|
||||
wrappedResponse.copyBodyToResponse();
|
||||
MDC.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.myoa.engineering.sample.configuration.logging;
|
||||
|
||||
import com.myoa.engineering.sample.entity.RequestLog;
|
||||
import com.myoa.engineering.sample.entity.ResponseLog;
|
||||
import com.myoa.engineering.sample.service.LogPersistenceService;
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class LoggingInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final LogPersistenceService logPersistenceService;
|
||||
|
||||
public LoggingInterceptor(LogPersistenceService logPersistenceService) {
|
||||
this.logPersistenceService = logPersistenceService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
if (isLoggable(request)) {
|
||||
RequestLog requestLog = RequestLog.builder()
|
||||
.httpMethod(request.getMethod())
|
||||
.uri(request.getRequestURI())
|
||||
.queryString(getQueryString(request))
|
||||
.contentType(request.getContentType())
|
||||
.body(getBodyIfAvailable(request))
|
||||
.requestedAt(Instant.now())
|
||||
.traceId(MDC.get("traceId"))
|
||||
.build();
|
||||
loggingRequest(requestLog);
|
||||
logPersistenceService.save(requestLog);
|
||||
}
|
||||
return HandlerInterceptor.super.preHandle(request, response, handler);
|
||||
}
|
||||
|
||||
private boolean isLoggable(HttpServletRequest request) {
|
||||
return request.getDispatcherType() != DispatcherType.ERROR;
|
||||
}
|
||||
|
||||
private String getBodyIfAvailable(HttpServletRequest request) throws IOException {
|
||||
if (request instanceof RereadableRequestWrapper wrappedRequest) {
|
||||
if (wrappedRequest.getContentType() != null && wrappedRequest.getContentType().contains(MimeTypeUtils.APPLICATION_JSON_VALUE)) {
|
||||
return new String(wrappedRequest.getContents());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void loggingRequest(RequestLog requestLog) {
|
||||
log.info("[REQUEST {}] {}{} ({}) [{}]\nbody: {}",
|
||||
requestLog.getHttpMethod(),
|
||||
requestLog.getUri(),
|
||||
requestLog.getQueryString(),
|
||||
requestLog.getContentType(),
|
||||
requestLog.getTraceId(),
|
||||
requestLog.getBody());
|
||||
}
|
||||
|
||||
private String getQueryString(HttpServletRequest request) {
|
||||
return request.getQueryString() == null || request.getQueryString().isEmpty()
|
||||
? ""
|
||||
: "?" + request.getQueryString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||
if (isLoggable()) {
|
||||
ResponseLog responseLog = ResponseLog.builder()
|
||||
.httpStatus(response.getStatus())
|
||||
.contentType(response.getContentType())
|
||||
.body(getBodyIfAvailable(response))
|
||||
.respondedAt(Instant.now())
|
||||
.traceId(MDC.get("traceId"))
|
||||
.build();
|
||||
loggingResponse(responseLog);
|
||||
logPersistenceService.save(responseLog);
|
||||
}
|
||||
|
||||
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
|
||||
}
|
||||
|
||||
private boolean isLoggable() {
|
||||
String traceId = MDC.get("traceId");
|
||||
return traceId != null && traceId.isEmpty() == false;
|
||||
}
|
||||
|
||||
private String getBodyIfAvailable(HttpServletResponse response) {
|
||||
if (response instanceof ContentCachingResponseWrapper wrappedResponse) {
|
||||
if (wrappedResponse.getContentType() != null && wrappedResponse.getContentType().contains(MimeTypeUtils.APPLICATION_JSON_VALUE)) {
|
||||
return new String(wrappedResponse.getContentAsByteArray());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void loggingResponse(ResponseLog responseLog) {
|
||||
log.info("[RESPONSE] {} ({}) [{}]\nbody: {}",
|
||||
responseLog.getHttpStatus(),
|
||||
responseLog.getContentType(),
|
||||
responseLog.getTraceId(),
|
||||
responseLog.getBody());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.myoa.engineering.sample.configuration.logging;
|
||||
|
||||
import jakarta.servlet.ReadListener;
|
||||
import jakarta.servlet.ServletInputStream;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class RereadableRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
private final ByteArrayOutputStream contents = new ByteArrayOutputStream();
|
||||
|
||||
public RereadableRequestWrapper(HttpServletRequest request) {
|
||||
super(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() throws IOException {
|
||||
super.getInputStream().transferTo(contents);
|
||||
return new RereadableServletInputStream(contents);
|
||||
}
|
||||
|
||||
public byte[] getContents() throws IOException {
|
||||
return this.getInputStream().readAllBytes();
|
||||
}
|
||||
|
||||
private static class RereadableServletInputStream extends ServletInputStream {
|
||||
|
||||
private final ByteArrayInputStream buffer;
|
||||
|
||||
public RereadableServletInputStream(final ByteArrayOutputStream contents) {
|
||||
buffer = new ByteArrayInputStream(contents.toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return buffer.available() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadListener(ReadListener listener) {
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return buffer.read();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.myoa.engineering.sample.controller;
|
||||
|
||||
import com.myoa.engineering.sample.dto.TestDataDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/loggingTest")
|
||||
public class LoggingTestAPIController {
|
||||
|
||||
@GetMapping("/get")
|
||||
public ResponseEntity<String> get() {
|
||||
// log.info("[API called] get");
|
||||
return ResponseEntity.ok("get");
|
||||
}
|
||||
|
||||
@GetMapping("/getWithBody")
|
||||
public ResponseEntity<TestDataDTO> getWithBody(@RequestBody TestDataDTO dto) {
|
||||
// log.info("[API called] getWithRequestBody");
|
||||
return ResponseEntity.ok(dto);
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/postWithBody")
|
||||
public ResponseEntity<TestDataDTO> postWithBody(@RequestBody TestDataDTO dto) {
|
||||
// log.info("[API called] postWithRequestBody");
|
||||
return ResponseEntity.ok(dto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.myoa.engineering.sample.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class TestDataDTO {
|
||||
private String value1;
|
||||
private Integer value2;
|
||||
private Boolean value3;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.myoa.engineering.sample.entity;
|
||||
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@MappedSuperclass
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@Getter
|
||||
@Setter
|
||||
public abstract class Auditable {
|
||||
|
||||
@CreatedDate
|
||||
private Instant createdAt;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.myoa.engineering.sample.entity;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@Entity
|
||||
public class RequestLog extends Auditable {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
private String httpMethod;
|
||||
private String uri;
|
||||
private String queryString;
|
||||
private String contentType;
|
||||
private String body;
|
||||
private Instant requestedAt;
|
||||
private String traceId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.myoa.engineering.sample.entity;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@Entity
|
||||
public class ResponseLog extends Auditable {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
private Integer httpStatus;
|
||||
private String contentType;
|
||||
private String body;
|
||||
private Instant respondedAt;
|
||||
private String traceId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.myoa.engineering.sample.infra.repository;
|
||||
|
||||
import com.myoa.engineering.sample.entity.RequestLog;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface RequestLogRepository extends JpaRepository<RequestLog, Long> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.myoa.engineering.sample.infra.repository;
|
||||
|
||||
import com.myoa.engineering.sample.entity.ResponseLog;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface ResponseLogRepository extends JpaRepository<ResponseLog, Long> {
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.myoa.engineering.sample.service;
|
||||
|
||||
import com.myoa.engineering.sample.entity.RequestLog;
|
||||
import com.myoa.engineering.sample.entity.ResponseLog;
|
||||
import com.myoa.engineering.sample.infra.repository.RequestLogRepository;
|
||||
import com.myoa.engineering.sample.infra.repository.ResponseLogRepository;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class LogPersistenceService {
|
||||
private final RequestLogRepository requestLogRepository;
|
||||
private final ResponseLogRepository responseLogRepository;
|
||||
|
||||
public LogPersistenceService(RequestLogRepository requestLogRepository,
|
||||
ResponseLogRepository responseLogRepository) {
|
||||
this.requestLogRepository = requestLogRepository;
|
||||
this.responseLogRepository = responseLogRepository;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void save(RequestLog requestLog) {
|
||||
requestLogRepository.save(requestLog);
|
||||
// log.info("requestLog saved. id: {}", requestLog.getId());
|
||||
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void save(ResponseLog responseLog) {
|
||||
responseLogRepository.save(responseLog);
|
||||
// log.info("responseLog saved. id: {}", responseLog.getId());
|
||||
}
|
||||
|
||||
}
|
||||
22
src/main/resources/application.yaml
Normal file
22
src/main/resources/application.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
server:
|
||||
port: 20090
|
||||
|
||||
spring:
|
||||
h2:
|
||||
console:
|
||||
enabled: true
|
||||
path: /h2
|
||||
datasource:
|
||||
url: jdbc:h2:mem:test
|
||||
username: sa
|
||||
password:
|
||||
driver-class-name: org.h2.Driver
|
||||
jpa:
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
|
||||
logging:
|
||||
level:
|
||||
org.hibernate.SQL: DEBUG
|
||||
org.hibernate.type.descriptor.sql: TRACE
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.myoa.engineering.sample;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class SpringBootRequestResponseLoggingExampleApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.myoa.engineering.sample.configuration;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
|
||||
class WebConfigurationTest {
|
||||
|
||||
private final AntPathMatcher sut = new AntPathMatcher();
|
||||
|
||||
@Test
|
||||
void test_ant_style_pattern_matching() {
|
||||
assertTrueAntPattern("/api/**", "/api/v1/loggingTest/get");
|
||||
assertTrueAntPattern("/api/**", "/api/v1/loggingTest/getWithBody");
|
||||
assertTrueAntPattern("/api/**", "/api/v1/loggingTest/postWithBody");
|
||||
}
|
||||
|
||||
public void assertTrueAntPattern(String pattern, String assertString) {
|
||||
Assertions.assertTrue(isMatchedAntStylePattern(pattern, assertString));
|
||||
}
|
||||
public void assertFalseAntPattern(String pattern, String assertString) {
|
||||
Assertions.assertFalse(isMatchedAntStylePattern(pattern, assertString));
|
||||
}
|
||||
|
||||
public boolean isMatchedAntStylePattern(String pattern, String assertString) {
|
||||
return sut.match(pattern, assertString);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user