포스트

2023-01-10-TIL

2023-01-10-TIL

Today I Learned

요청 로깅과 클라이언트 정보 추출을 중심으로 Servlet API, Logback, Spring AOP를 정리했다. 그 밖에 M1 Mac의 Netty DNS 경고, Spotify 아키텍처 사례, ERD 도구를 찾아봤다.


Get Empty String When Null

1
2
3
Objects.toString(id, "");
Optional.ofNullable(id).orElse("");
StringUtils.defaultString(str).replaceAll("^(?i)null$", "");

Java 8 이상에서는 Optional을 사용해서 null 처리를 하는게 깔끔한 것 같다.

세 방법은 동작이 조금씩 다르다. Objects.toString(o, "")는 JDK 7부터 있는 표준 메서드로, o가 null이면 기본값을, 아니면 o.toString()을 돌려준다. Optional.ofNullable(id).orElse("")는 값의 타입이 이미 String일 때 같은 일을 한다. Commons Lang의 StringUtils.defaultString()은 null만 빈 문자열로 바꾸며, 위 예시는 여기에 더해 "null"이라는 문자열 자체까지 정규식으로 지운다. Optional은 원래 메서드 반환 타입에서 “값이 없을 수 있음”을 표현하려고 만든 것이라, 단순한 기본값 처리라면 Objects.toString도 충분히 깔끔하다.

  • https://stackoverflow.com/questions/21936503/get-empty-string-when-null

Servlet, HttpServletRequest

HttpServletRequest는 서블릿 컨테이너(Tomcat 등)가 HTTP 요청을 파싱해 만든 객체로, 요청 메서드와 URI, 헤더, 파라미터, 바디 스트림, 세션, 원격 주소 같은 정보를 제공한다. 응답은 HttpServletResponse로 쓴다. 컨테이너는 요청마다 이 객체를 새로 만들어 서블릿(Spring MVC에서는 DispatcherServlet)에 넘기고, 요청 범위의 데이터를 다음 처리 단계에 넘길 때는 request.setAttribute()를 쓴다.

  • https://tragramming.tistory.com/99
  • https://velog.io/@oliviarla/HttpServletRequest-HttpServletResponse-%EA%B0%9D%EC%B2%B4%EB%9E%80
  • https://kgvovc.tistory.com/41#:~:text=HttpServletRequest%20%EA%B0%9D%EC%B2%B4%EB%A5%BC%20%ED%86%B5%ED%95%9C%20%EC%A0%95%EB%B3%B4,%EC%9A%94%EC%B2%AD%20%EC%9E%AC%EC%A7%80%EC%A0%95’%EC%9D%B4%EB%9D%BC%EA%B3%A0%20%ED%95%A9%EB%8B%88%EB%8B%A4.

Logback

Logback 문서는 Logback이 Logger, Appender, Layout 세 클래스를 중심으로 만들어졌다고 설명한다. Logger는 이름 계층(보통 패키지 구조)과 레벨을 가지고 로그 이벤트를 만들고, Appender는 이벤트를 콘솔, 파일, 소켓 같은 목적지로 보내며, Layout이 이벤트를 문자열로 바꾼다. 설정 파일을 찾지 못하면 루트 로거에 ConsoleAppender를 붙이는 기본 설정으로 동작한다.

  • https://logback.qos.ch/manual/introduction.html
  • https://www.baeldung.com/logback
  • https://ckddn9496.tistory.com/86
  • https://kimpaper.github.io/2015/10/30/logback-config/

Get Client IP From HttpServletRequest

1
2
3
4
String ipAddress = request.getHeader("X-FORWARDED-FOR");  
if (ipAddress == null) {  
    ipAddress = request.getRemoteAddr();  
}

HttpServletRequest로부터 클라이언트 IP를 위와 같이 얻어올 수 있는데, FE를 통해서 들어온 IP는 FE의 IP가 찍힐 수 있다. 따라서 실제 사용자의 IP를 가져오려면 클라이언트인 FE서버가 X-FORWARDED-FOR 헤더에 실제 사용자의 IP를 실어서 보내주어야 한다.

MDN 문서에 따르면 X-Forwarded-For는 프록시를 거칠 때마다 IP가 뒤에 덧붙는 형식이라, 가장 왼쪽이 원래 클라이언트, 가장 오른쪽이 가장 최근 프록시다. 값이 client, proxy1, proxy2처럼 여러 개일 수 있으므로 위 코드처럼 헤더 값을 그대로 쓰지 말고 쉼표로 나눠 처리해야 한다. 또 이 헤더는 클라이언트가 임의로 넣을 수 있어서, 신뢰할 수 있는 프록시가 추가한 부분만 믿어야 한다. 서버가 인터넷에서 직접 접근 가능하면 헤더 전체가 위조될 수 있다.

  • http://jmlim.github.io/java/2018/12/14/java-client-ip/
  • https://stackoverflow.com/questions/29910074/how-to-get-client-ip-address-in-java-httpservletrequest

Rolling Logs Every 15 Minutes With Logback

Logback의 TimeBasedRollingPolicy는 fileNamePattern의 날짜 형식에서 가장 작은 단위를 주기로 파일을 나눈다. %d{yyyy-MM-dd_HH-mm}이면 매분, %d{yyyy-MM-dd_HH}이면 매시간이다. “15분마다” 같은 임의 주기는 패턴만으로 표현할 수 없어서, 링크된 글은 RollingFileAppender를 상속해 마지막 롤오버 이후 15분이 지나면 rollover()를 호출하는 커스텀 Appender를 만든다.

  • https://ronanquillevere.github.io/2015/08/04/rolling-log-15-min.html#.Y7uniuxBzgF
  • https://reference-m1.tistory.com/349

Spring Logging

요청 정보를 남기는 가장 간단한 방법은 Spring이 제공하는 CommonsRequestLoggingFilter를 빈으로 등록하는 것이다. 쿼리 스트링, 헤더, 페이로드를 포함할지 옵션으로 정하고 해당 로거 레벨을 DEBUG로 맞추면 요청 전후로 로그가 남는다.

  • https://www.baeldung.com/spring-http-logging

Spring Boot AOP Logging Request Body

컨트롤러에 공통 로깅을 넣는 방법은 필터, 인터셉터, AOP 세 가지다. AOP는 @Around 어드바이스에서 JoinPoint.getArgs()로 이미 @RequestBody로 변환된 객체를 받을 수 있어서 바디 스트림을 다시 읽을 필요가 없다. 대신 컨트롤러 메서드에 도달하지 못한 요청(필터에서 거부된 요청, 바인딩 실패)은 기록되지 않는다.

  • https://www.javaguides.net/2019/05/spring-boot-spring-aop-logging-example-tutorial.html
  • https://threepotatoteers.medium.com/use-spring-aop-to-log-post-requests-6abee5753464
  • https://www.springcloud.io/post/2022-03/record-request-and-response-bodies/#gsc.tab=0
  • https://meetup.nhncloud.com/posts/44
  • https://dylee.tistory.com/6
  • https://twofootdog.github.io/Spring-POST%EB%B0%A9%EC%8B%9D%EC%9C%BC%EB%A1%9C-%EC%A0%84%EB%8B%AC%EB%90%9C-JSON-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0/
  • https://hirlawldo.tistory.com/42
  • https://taetaetae.github.io/2019/06/30/controller-common-logging/
  • https://stackoverflow.com/questions/63008446/get-requestbody-using-aspectj

Spring AOP

Aspect는 공통 관심사를 모은 모듈, Advice는 실제로 실행할 코드(@Before, @After, @Around 등), Pointcut은 어드바이스를 적용할 위치를 고르는 표현식, JoinPoint는 어드바이스가 끼어드는 실행 지점(Spring AOP에서는 메서드 실행)이다. Spring AOP는 프록시 기반이라 같은 클래스 안에서 자기 메서드를 호출하면 어드바이스가 적용되지 않는다.

  • https://www.digitalocean.com/community/tutorials/spring-aop-example-tutorial-aspect-advice-pointcut-joinpoint-annotations
  • https://www.linkedin.com/pulse/logging-spring-requests-using-aspect-abd-alrhman-alkraien?trk=pulse-article_more-articles_related-content-card
  • https://pamyferret.tistory.com/51

Get Request Body With Spring Interceptor

요청 바디는 InputStream이라 한 번 읽으면 다시 읽을 수 없다. 인터셉터에서 바디를 읽어 버리면 이후 @RequestBody 변환이 실패한다. Spring의 ContentCachingRequestWrapper로 요청을 감싸면 읽힌 내용을 캐시해 두므로, 필터에서 래핑하고 처리가 끝난 뒤 getContentAsByteArray()로 바디를 꺼내 로그에 남기는 방식을 많이 쓴다.

  • https://xzio.tistory.com/1999

Unable to load io.netty.resolver.dns.macos.MacOSDnsServerAddressStreamProvider

M1에서 해당 에러가 발생하면 스프링 의존을 추가해주면 된다.

정확히는 Netty의 macOS용 네이티브 DNS 리졸버(netty-resolver-dns-native-macos)를 로드하지 못할 때 나오는 메시지다. 이슈 본문의 스택 트레이스에는 osx-x86_64 분류자 jar가 보이는데, Apple Silicon에서는 osx-aarch_64 분류자를 가진 의존성을 런타임에 추가하면 해결된다.

1
runtimeOnly("io.netty:netty-resolver-dns-native-macos::osx-aarch_64")
  • https://github.com/netty/netty/issues/11020

Spotify Architecture

대규모 스트리밍 서비스의 구조를 참고하려고 모은 글이다. Spotify 엔지니어링 블로그의 2013년 글은 수많은 스쿼드가 운영 부서를 거치지 않고 직접 서비스를 개발하고 운영할 수 있는 셀프 서비스 백엔드 인프라를 소개하고, 스토리지로 Cassandra, PostgreSQL, memcached를 제공한다고 설명한다. InfoQ 기사는 Spotify가 자체 시스템 모델을 정의하고 C4 모델을 응용해 아키텍처 다이어그램을 표준화한 사례를 다룬다. 나머지 서드파티 글들은 공개 자료를 바탕으로 구조를 재구성한 것이라 공식 자료와 구분해서 읽어야 한다.

  • https://www.techaheadcorp.com/blog/decoding-software-architecture-of-spotify-how-microservices-empowers-spotify/
  • https://medium.com/geekculture/spotify-system-architecture-6bb418db6084
  • https://engineering.atspotify.com/2016/08/commodity-music-ml-services/
  • https://engineering.atspotify.com/2013/03/backend-infrastructure-at-spotify/
  • https://www.infoq.com/news/2022/07/spotify-system-model-c4/
  • https://www.linkedin.com/pulse/spotify-system-architecture-omar-ismail?trk=pulse-article

ERD Tools

ERD 도구는 웹 기반 협업 도구(Visual Paradigm Online, GitMind 등)와 DB에 직접 연결해 기존 스키마에서 다이어그램을 뽑아내는 도구로 나뉜다. 고를 때는 DB 리버스 엔지니어링, DDL 내보내기, 팀 공유, 무료 사용 범위를 기준으로 보면 된다.

  • https://gitmind.com/kr/er-diagram-tool.html
  • https://online.visual-paradigm.com/diagrams/features/erd-tool/
  • https://trevor.io/blog/top-7-entity-relationship-diagram-tools
  • https://www.comparitech.com/net-admin/er-diagram-tools/
  • https://www.guru99.com/free-database-diagram-design-tools.html
  • https://www.holistics.io/blog/top-5-free-database-diagram-design-tools/
  • http://www.uniondata.co.kr/product/da-shop/
이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

댓글

아직 댓글이 없습니다