프로젝트 실습을 위해 다음과 같은 환경에서 새로운 스프링 프로젝트를 만들겠습니다.
Project : Gradle Project
Language : Java
Spring Boot : 2.4.x
Packaging : Jar
Dependencies : Spring Web, Thymeleaf, Lombok
1) Packaging의 경우 JSP를 사용하지 않기 때문에 Jar를 사용합니다.
내장 서버에서 스프링을 돌리게 되고 Web-app을 사용하지 않게 됩니다
War를 사용하면 내장 서버도 사용 가능하지만 주로 외부에 따로 톰캣을 설치하게 되는 경우에 사용합니다.
https://sunyoungj.tistory.com/109
Spring Boot log4j-to-slf4j 보안 취약점 대응
마인크래프트 서버를 대상으로 대규모 악성코드 공격이 감지 되었는데, log4j2의 취약점을 이용 하였답니다. 역사상 최악의 취약점 이라고 할 만큼, 많이 사용 하는 아파치재단의 오픈소스 라이
sunyoungj.tistory.com
스프링 웹 프레임워크를 사용할 때 System.Out.println으로 로그를 출력하게 되면, 로그 폭탄을 맞습니다.
println 내부에 synchronized 예약어가 사용되어서 락이 걸리는건 기본입니다.
slf4j를 사용하게 되면, 레벨별로 로그를 분류할 수 있고 따로 설정을 통해 개발 서버, 스테이지 서버, 로컬 서버, 테스트 서버에 따라 로그 출력을 달리할 수 있고 추가 기능을 사용하게 되면 해당 로그가 일정 크기를 넘어갔을 때 대응이나 로그들을 File로 extract할 수 있는 등 장점이 따릅니다.
과거 log4j에 보안취약점이 생겨서 난리가 났었는데 스프링 부트는 예외적인 경우에 해당한다고 하네요
읽어본 블로그 글 첨부합니다.
SLF4J는 로그를 제공하는 인터페이스고 그 구현체로 LogBack과 같은 로그 라이브러리가 존재합니다.
package hello.springmvc.basic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController // REST -> 문자 리턴시 @Controller, view 이름 반환하나
// REST의 경우 REST API로 문자 반환시 그대로 반환
public class LogTestController {
// 주의 import SLF4j
private final Logger log = LoggerFactory.getLogger(LogTestController.class);
@RequestMapping("/log-test")
public String logTest(){
String name = "Spring";
System.out.println("name = " + name);
log.trace(" info log={}", name);
log.debug(" debug log={}", name);
log.info(" info log={}", name);
log.warn(" warn log={}", name);
log.error(" error log={}", name);
// 또는 logging.leve.hello.springmvc = trace -> 난 로그 다볼거야
// 개발서버 = debug -> 디버그 인포 원 에러 다보겠다 trace 제외
// 로컬 = trace 레벨
// 운영서버 = info 레벨 (debug, trace 레벨은 안본다)
// sout -> 모든 경우에 뜬다, 로그 폭탄 맞는다....
// 즉 단계뼐로 필터링 가능, 배포할때 스프링 부트의 기능을 이용해 바꿀 수 있음
// 설정 정보 다르게 쓸 수 있다.
return "ok";
}
}
로깅을 사용하기 위한 방법은 다음과 같이 객체 인스턴스를 생성해주면 됩니다
private final Logger log = LoggerFactory.getLogger(LogTestController.class); // 또는
private final Logger log = LoggerFactory.getLogger(getClass()); // 또는
@Slf4j // 애노테이션 사용 (단, 인텔리제이에서 Annotation Processing과 Lombok 라이브러리 추가 필요
로그 레벨은 다음과 같습니다
: TRACE -> DEBUG -> INFO -> WARN -> ERROR
개발서버는 주로 Debug로 출력하고, 운영서버는 info만 출력하게 됩니다.
다음과 같은 방법으로 로그 레벨을 지정할 수 있습니다.
logging.level.root=info
logging.level.hello.springmvc=debug
log.trace(" info log={}", name);
log.debug(" debug log={}", name);
log.info(" info log={}", name);
log.warn(" warn log={}", name);
log.error(" error log={}", name);
로그를 출력할 때 특정 변수의 값을 확인하고 싶은 경우 꼭 , 을 이용하여 parameter로 넘겨주어야 합니다.
+로 지정하게 될 경우, 이 로그를 출력하지 않고 싶을때도 먼저 concatenation이 일어나버립니다 (불필요한 연산 발생)
한두건 발생하면 미미하겠지만 큰 서버에서 대규모로 발생하면 성능 저하의 위험이 큽니다.
, 을 사용하여 parameter로 넘겼을 때는 로그 출력이 불필요할 경우 예를 들어 .debug 를 사용했는데 설정이 info까지만 로그로 출력할 것으로 설정된 경우 아무 연산이 일어나지 않습니다.
로그 사용의 장점
- 쓰레드 정보, 클래스 이름과 같은 부가 정보를 함께 볼 수 있고 출력 모양을 조정할 수 있습니다.
- 로그 레벨에 따라 개발 서버에서는 모든 로그를 출력하고 운영서버에서는 출력하지 않는 등 상황에 맞게 조절할 수 있습니다.
- System.Out 콘솔에만 출력하는게 아니라 네트워크나 저장장치 등에 로그를 남길 수 있습니다.
-> 특히 파일로 남길때는 일별, 특정 용량에 따라 로그 분할도 가능합니다
System.Out.Println으로 로그를 출력할 경우 내부 버퍼링이나 멀티쓰레드 환경에서 락이 걸리는 경우가 있습니다.
실무에서는 꼭 로그를 사용할 것을 권장했습니다
이와 관련해서 찾은 자료를 남기겠습니다
In System.Out.println() what's the point of synchronizing this block?
From the PrintStream.class: public void println(Object x) { String s = String.valueOf(x); synchronized (this) { print(s); newLine(); } } What's the point of the synchr...
stackoverflow.com
synchronized prevents multiple threads from entering the method or block at the same time. print and println aren't thread safe methods, meaning if you have two threads call them at the same time, the output may get mixed.
e.g. thread 1 calls print('Hello World') right when thread 2 calls print('Goodbye World'). The output may look like HeGoodbyello W Woorldrld
The reason for using synchronized(this) here instead of putting synchronized on the method is because String.valueOf(x) does not need to block other threads, and is therefore placed outside the critical section.
Generally, you want to put as little as possible into the critical section to prevent threads from waiting around wasting time.
synchronized 예약어는 다수의 스레드가 메서드나 코드 블럭에 동시에 접근하는 것을 예방합니다.
print와 println은 thread-safe한 메서드가 아닙니다, 따라서 두 스레드를 동시에 호출하면 결과값이 섞일 수 있습니다.
1번 스레드가 print('Hello World')를 호출하고 2번 스레드가 print('Goodbye World')를 호출했다고 가정합시다.
출력은 HeGoodbyello W Woorldrld 처럼 Output이 섞이는 결과를 초래할 수 있습니다.
System.Out.Println이 synchronized(this)를 메서드 레벨 대신에 메서드 내부 함수에 사용한 이유는 String.valueOf(x)는 다른 스레드를 block 할 필요 없이 실행해도 되기 때문입니다, 그래서 이 statement는 critical section(임계영역) 외부에 위치해도 좋습니다.
상식적으로 생각해보면 스레드가 임계영역으로 부터 보호되는 영역을 최소화시키고 싶겠죠, 그럼으로써 대기하는데 버려지는 시간이 줄어들테니까요 (일부 의역)
결론 = 로깅을 씁시다!
'스프링 공부 (인프런 김영한 선생님) > 스프링 MVC 1편' 카테고리의 다른 글
[스프링 웹 MVC 1편] 21. 스프링 MVC - 기본기능 (기본/헤더/쿼리파라미터/ HTML Form 조회) (1) | 2023.06.03 |
---|---|
[스프링 웹 MVC 1편] 20. 스프링 MVC - 기본기능 (요청매핑) (0) | 2023.06.03 |
[스프링 웹 MVC 1편] 18. 스프링 MVC - 시작 (0) | 2023.05.30 |
[스프링 웹 MVC 1편] 17. 스프링 MVC - 핸들러 매핑과 어댑터 / 뷰 리졸버 (0) | 2023.05.29 |
[스프링 웹 MVC 1편] 16. 스프링 MVC - V1 ~ V5와 비교했을 때 (구조 이해) (0) | 2023.05.29 |