System.out.println() 의 문제점
- 동기화(Synchronization):
System.out.println()
시 호출되는newline()
에는synchronized
키워드를 통해 동기화를 진행하고 있다. - Blocking I/O:
synchronized
영역의 코드를 진행할 때, multi-thread 간의 Blocking 작업이 일어난다. - 로그 저장소:
System.out
은 콘솔에 출력되기 때문에 별도로 파일로 저장하여 관리하기가 힘들다.
Logger
spring boot 의 Logger는 Log4j -> Logback -> Log4j2 로 발전되어 왔다.
기본적으로 spring boot starter 에 설정되어 있는 logger 는 Logback 이다.
Log Level은 다음과 같은 계층을 가진다.
Trace < Debug < Info < Warning < Error < Fatal
로그의 수준이 INFO
라면 INFO
수준을 포함하여 그보다 중요도가 같거나 높은 로그들만 찍히게 된다.
이외에도 모든 로그를 찍겠다는 ALL
설정과 모든 로그를 찍지 않겠다는 OFF
설정이 있고, ALL
은 Trace
와 같은 설정으로 보면 된다.
Log4j2 란
spring 에서는 자체적으로 Logback
이라는 로깅 라이브러리를 사용하고 있다. Log4j2
는 Logback
에 비해 어느 상황에서나 더 빠른 속도를 가지고 있으며 자바의 람다식을 활용할 수 있는 메소드도 정의되어 있다.
때문에, 기존의 Logback
을 Log4j2
로 바꾸는 것이 좋다.
Log4j2 설정
1. 의존성 추가
dependencies {
implementation("org.springframework.boot:spring-boot-starter-log4j2")
}
2. build.gradle
에서 Logback 제거하기
Spring에서는 기본적으로 Logback
을 이용해서 로깅을 하기 때문에
다른 로깅 라이브러리인 Log4j2
를 그냥 추가하게 되면, 로깅 라이브러리끼리 충돌이 발생한다.
때문에 Log4j2
를 적용하기 위해서는 Logback
라이브러리를 제거해야 한다.
아래 내용을 build.gradle
에 추가하여, Logback
을 exclude 해주자.
configurations {
all {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
}
}
3. application 설정에 Logging 설정하기
로그 설정 파일은 application.yml
또는 application.properties
에 설정할 수 있다. XML
파일을 이용해서 설정을 분리하여 구성할 수도 있다.
분리하여 XML
파일을 만들 경우 파일의 classpath
를 지정해주어야 한다.
# log4j2.xml 파일 경로 지정
logging:
config: classpath:log4j2.xml
4. log4j2.xml 파일 설정
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="DEBUG">
<Properties>
<Property name="logPath">./logs</Property>
<Property name="logPattern">[%-5level] %d{yyyy-MM-dd HH:mm:ss} [%t] %c{1} - %msg%n</Property>
<Property name="serviceName">application</Property>
</Properties>
<Appenders>
<Console name="console">
<PatternLayout pattern="${logPattern}"/>
</Console>
<RollingFile
name="file"
append="true"
fileName="${logPath}/${serviceName}.log"
filePattern="${logPath}/${serviceName}.%d{yyyy-MM-dd}.%i.log.gz">
<PatternLayout pattern="${logPattern}"/>
<Policies>
<SizeBasedTriggeringPolicy size="5MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>
<DefaultRolloverStrategy>
<Delete basePath="${logPath}" maxDepth="1">
<IfFileName glob="${serviceName}.*.log"/>
<IfLastModified age="15d"/>
</Delete>
</DefaultRolloverStrategy>
</Appenders>
<Loggers>
<Logger name="consolelog" level="info" additivity="false">
<AppenderRef ref="console"/>
<AppenderRef ref="file"/>
</Logger>
</Loggers>
</Configuration>
- Property 를 설정하여, xml 파일 내부의 변수로 사용한다.
Logger 설정을 할 때 사용할,logPath
,logPattern
,serviceName
을 먼저 정의하였다. 변수는${variable}
형태로 사용할 수 있다.
<Properties>
<Property name="logPath">./logs</Property>
<Property name="logPattern">[%-5level] %d{yyyy-MM-dd HH:mm:ss} [%t] %c{1} - %msg%n</Property>
<Property name="serviceName">application</Property>
</Properties>
<Appenders>
: 로그 이벤트를 콘솔과 파일에 출력하는 데 사용되는 것으로, 다양한 appenders(추가자)를 내부에 정의한다.
차후 Logger 에서 이 appender 를 참조하여 사용한다.
<Appenders>
...
</Appenders>
<Console>
: 콘솔에 로그를 출력하는 appender를 정의한다.name="console"
: appender의 이름을 "console"로 지정한다.<PatternLayout>
: 출력되는 로그의 형식을 정의한다.
<Appenders>
<Console name="console">
<PatternLayout pattern="${logPattern}"/>
</Console>
</Appenders>
<RollingFile>
: 파일에 로그를 기록하는 appender를 정의한다. rolling file appender는 파일의 크기 또는 시간에 따라 새로운 파일을 생성한다.name="file"
: 이 appender의 이름을 "file"로 지정한다.append="true"
: 기존 파일에 로그를 추가할 것인지 여부를 나타낸다.fileName="${logPath}/${serviceName}.log"
: 로그 파일의 경로와 이름을 정의한다.filePattern="${logPath}/${serviceName}.%d{yyyy-MM-dd}.%i.log.gz"
: rolling file 패턴을 정의한다. 시간별로 로그를 분할하여 압축한 형태로 저장한다.<PatternLayout>
: 파일에 기록될 로그의 형식을 정의한다.
<Appenders>
<RollingFile
name="file"
append="true"
fileName="${logPath}/${serviceName}.log"
filePattern="${logPath}/${serviceName}.%d{yyyy-MM-dd}.%i.log.gz">
<PatternLayout pattern="${logPattern}"/>
</RollingFile>
</Appenders>
<Policies>
: rolling file의 행동을 결정하는 정책을 정의한다.<SizeBasedTriggeringPolicy size="5MB"/>
: 파일 크기가 5MB가 되면 새로운 파일로 롤오버된다.<TimeBasedTriggeringPolicy/>
: 시간 기반으로도 파일을 롤오버할 수 있도록 한다.
<Appenders>
<Policies>
<SizeBasedTriggeringPolicy size="5MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
</Appenders>
<DefaultRolloverStrategy>
: rolling file이 일어날 때 사용할 기본 롤오버 전략을 정의한다.<Delete basePath="${logPath}" maxDepth="1">
: 일정 조건에 따라 로그 파일을 삭제하는 전략을 설정한다.<IfFileName glob="${serviceName}.*.log"/>
: 특정 파일명 패턴에 따라 파일을 삭제한다.<IfLastModified age="15d"/>
: 파일의 마지막 수정일로부터 15일이 경과한 파일을 삭제한다.
<Appenders>
<DefaultRolloverStrategy>
<Delete basePath="${logPath}" maxDepth="1">
<IfFileName glob="${serviceName}.*.log"/>
<IfLastModified age="15d"/>
</Delete>
</DefaultRolloverStrategy>
</Appenders>
<Loggers>
: 로그 레벨과 로깅 이벤트를 특정 appender에 연결하는 것을 설정한다.name="consolelog"
: 로거가 적용되는 패키지 이름을 지정한다.level="info"
: 이 로거의 로그 레벨을 "info"로 설정한다.additivity="false"
: 부모 로거로 이벤트 전파를 중단한다.<AppenderRef ref="console"/>
: 이 로거에 콘솔 appender를 참조한다. 즉, 이 패키지의 로그는 콘솔에 출력된다.<AppenderRef ref="file"/>
: 이 로거에 파일 appender를 참조한다. 따라서 이 패키지의 로그는 파일에도 기록된다.
<Loggers>
<Logger name="consolelog" level="info" additivity="false">
<AppenderRef ref="console"/>
<AppenderRef ref="file"/>
</Logger>
</Loggers>
Log4j2 사용하기
spring boot project 내에서 다음과 같이 @Slf4j2
나 @Log4j2
어노테이션을 사용하여, logging 할 수 있다. 객체 log 가 자동 생성되어 별도의 선언 없이 logger를 사용할 수 있다.
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
protected ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
final ErrorCode errorCode = e.getErrorCode();
final ErrorResponse response = new ErrorResponse(errorCode);
// [ Error Level ]
// trace < debug < info < warn < error
// 현재 logging level 은 info 로 설정되어, info, warn, error 만 로그에 남는다
log.trace(e.toString());
log.debug(e.toString());
log.info(e.toString());
log.warn(e.toString());
log.error(e.toString());
return ResponseEntity.status(errorCode.getStatus()).body(response);
}
}
그리고 error 가 발생 시, 다음과 같이 지정 경로 폴더 밑에 log
가 생성되는 것을 확인 할 수 있다.
[ERROR] 2023-12-09 12:42:15 [http-nio-8080-exec-1] GlobalExceptionHandler - G001 : 예상치 못한 서버 내부 오류[consolelog.global.support.AuthInterceptor.preHandle(AuthInterceptor.java:45), org.springframework.web.servlet.HandlerExecutionChain.applyPreHandle(HandlerExecutionChain.java:146), org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1076), org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974), org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011), org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914), jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590), org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885), jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658), org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205), org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149), org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51), org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174), org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149), org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100), org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116), org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174), org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149), org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93), org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116), org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174), org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149), org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201), org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116), org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174), org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149), org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167), org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90), org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482), org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115), org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93), org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74), org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341), org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391), org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63), org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894), org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1740), org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52), org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191), org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659), org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61), java.base/java.lang.Thread.run(Thread.java:833)]
[ERROR] 2023-12-09 12:42:18 [http-nio-8080-exec-2] GlobalExceptionHandler - G001 : 예상치 못한 서버 내부 오류[consolelog.global.support.AuthInterceptor.preHandle(AuthInterceptor.java:45), org.springframework.web.servlet.HandlerExecutionChain.applyPreHandle(HandlerExecutionChain.java:146), org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1076), org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974), org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011), org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914), jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590), org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885), jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658), org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205), org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149), org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51), org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174), org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149), org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100), org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116), org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174), org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149), org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93), org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116), org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174), org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149), org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201), org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116), org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174), org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149), org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167), org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90), org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482), org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115), org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93), org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74), org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341), org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391), org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63), org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894), org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1740), org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52), org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191), org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659), org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61), java.base/java.lang.Thread.run(Thread.java:833)]
[ERROR] 2023-12-09 12:42:44 [http-nio-8080-exec-4] GlobalExceptionHandler - M005 : 비정상적인 회원가입 절차입니다.,[consolelog.member.service.MemberService.validateUniqueNickname(MemberService.java:78), consolelog.member.service.MemberService.validate(MemberService.java:61), consolelog.member.service.MemberService.signUp(MemberService.java:37), java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method), java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77), java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43), java.base/java.lang.reflect.Method.invoke(Method.java:568), org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343), org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196), org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163), org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:751), org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123), org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:391), org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119), org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184), org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:751), org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:703), consolelog.member.service.MemberService$$SpringCGLIB$$0.signUp(<generated>), consolelog.member.controller.MemberController.signUp(MemberController.java:35), java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method), java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77), java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43), java.base/java.lang.reflect.Method.invoke(Method.java:568), org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205), org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150), org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118), org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884), org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797), org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87), org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081), org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974), org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011), org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914), jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590), org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885), jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658), org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205), org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149), org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51), org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174), org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149), org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100), org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116), org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174), org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149), org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93), org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116), org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174), org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149), org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201), org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116), org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174), org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149), org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167), org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90), org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482), org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115), org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93), org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74), org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341), org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391), org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63), org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894), org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1740), org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52), org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191), org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659), org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61), java.base/java.lang.Thread.run(Thread.java:833)]
설정 과정에서 착각했던 점
log 파일은 직접 열어서 하나씩 확인하는 용도가 아니다. 별도의 모니터링 툴을 이용하여 log 를 분석하고, 어떤 오류가 발생했는 지 확인해야 한다.
그런데, logging 하는 과정에서 차후에 log 파일을 직접 열어 error를 확인 할 것이라 착각하고, 각 log에 ‘\n’
을 추가하여 보기 좋게 하는 작업을 시도 했었다. ‘\n’
이 들어가면 오히려 모니터링 툴에서는 하나의 log 가 여러개의 log 로 인식하여 문제가 발생한다. 때문에, 별도의 라인으로 분리할 필요가 없다.
또한, Exception 별로 log 파일을 분리하여 관리할까 생각도 했었는데, 어차피 툴을 이용하여 log 파일별로 filtering 해서 관리할 수 있다. 때문에, 파일을 분리하면 단점만 발생한다.