Log - woowacourse-teams/2020-zeze GitHub Wiki

log

๋กœ๊ทธ๋Š” ํ‘œ์‹œ๋ฅผ ์œ„ํ•ด ๋‚จ๊ธด๋‹ค. ํ”„๋กœ๊ทธ๋žจ ๊ฐœ๋ฐœ์ด๋‚˜ ์šด์˜ ์‹œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์ ์„ ์ถ”์  ํ•˜๊ฑฐ๋‚˜ ์šด์˜ ์ƒํƒœ๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋ง ํ•˜๋Š” ์ •๋ณด๋ฅผ ๊ธฐ๋กํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

  • ์ €ํฌ ํŒ€์€ access, api, db, error 4๊ฐœ์˜ ํŒŒ์ผ ํ˜•ํƒœ๋กœ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๊ธฐ๋กœ ํ–ˆ๊ณ , ์ถ”ํ›„์— ELK๋ฅผ ์ ์šฉํ•˜์—ฌ ๋กœ๊ทธ๋ฅผ ๊ฒ€์ƒ‰ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.
  • ํ˜„์žฌ๋Š” logback์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”๋ฐ ์ถ”ํ›„์— ์„ฑ๋Šฅ์ด ๋” ์ข‹์€ log4j2๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

logback

  • java spring ์ž์ฒด์—์„œ ์ œ๊ณตํ•˜๋Š” logback์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” logback-spring.xml๋กœ ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๋‹ค๋ฅธ ์„ค์ •์„ ์‚ฌ์šฉํ•˜์—ฌ logback.xml ์‚ฌ์šฉ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- 60์ดˆ๋งˆ๋‹ค ์„ค์ • ํŒŒ์ผ์˜ ๋ณ€๊ฒฝ์„ ํ™•์ธ ํ•˜์—ฌ ๋ณ€๊ฒฝ์‹œ ๊ฐฑ์‹  -->
<configuration scan="true" scanPeriod="60 seconds">
    <springProperty scope="context" name="LOG_LEVEL" source="${logging.level.root}"/>
    <!-- pattern ๊ฐ„๋žตํžˆ ๋ณด์—ฌ์คŒ -->
    <property name="LOG_PATTERN" value="%-5level %d{yy-MM-dd HH:mm:ss} - %msg%n"/>
    <!-- API -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- ํŒŒ์ผ๊ฒฝ๋กœ ์„ค์ • -->
        <file>${LOG_PATH}/api.log</file>
        <!-- ์ถœ๋ ฅํŒจํ„ด ์„ค์ •-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
        <!-- Rolling ์ •์ฑ… -->
        <rollingPolicy
                class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- .gz,.zip ๋“ฑ์„ ๋„ฃ์œผ๋ฉด ์ž๋™ ์ผ์ž๋ณ„ ๋กœ๊ทธํŒŒ์ผ ์••์ถ• -->
            <fileNamePattern>${LOG_PATH}/api.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- ํŒŒ์ผ๋‹น ์ตœ๊ณ  ์šฉ๋Ÿ‰ kb, mb, gb -->
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- ์ผ์ž๋ณ„ ๋กœ๊ทธํŒŒ์ผ ์ตœ๋Œ€ ๋ณด๊ด€์ฃผ๊ธฐ(~์ผ), ํ•ด๋‹น ์„ค์ •์ผ ์ด์ƒ๋œ ํŒŒ์ผ์€ ์ž๋™์œผ๋กœ ์ œ๊ฑฐ-->
            <maxHistory>30</maxHistory>
            <!--<MinIndex>1</MinIndex> <MaxIndex>10</MaxIndex>-->
        </rollingPolicy>
    </appender>
    <!-- ์—๋Ÿฌ์˜ ๊ฒฝ์šฐ ํŒŒ์ผ์— ๋กœ๊ทธ ์ฒ˜๋ฆฌ -->
    <appender name="Error" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>error</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <file>${LOG_PATH}/err.log</file>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
        <!-- Rolling ์ •์ฑ… -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- .gz,.zip ๋“ฑ์„ ๋„ฃ์œผ๋ฉด ์ž๋™ ์ผ์ž๋ณ„ ๋กœ๊ทธํŒŒ์ผ ์••์ถ• -->
            <fileNamePattern>${LOG_PATH}/err.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- ํŒŒ์ผ๋‹น ์ตœ๊ณ  ์šฉ๋Ÿ‰ kb, mb, gb -->
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- ์ผ์ž๋ณ„ ๋กœ๊ทธํŒŒ์ผ ์ตœ๋Œ€ ๋ณด๊ด€์ฃผ๊ธฐ(~์ผ), ํ•ด๋‹น ์„ค์ •์ผ ์ด์ƒ๋œ ํŒŒ์ผ์€ ์ž๋™์œผ๋กœ ์ œ๊ฑฐ -->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>
    <!-- DB -->
    <appender name="DB" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- ํŒŒ์ผ๊ฒฝ๋กœ ์„ค์ • -->
        <file>${LOG_PATH}/db.log</file>
        <!-- ์ถœ๋ ฅํŒจํ„ด ์„ค์ •-->
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
        <!-- Rolling ์ •์ฑ… -->
        <rollingPolicy
                class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- .gz,.zip ๋“ฑ์„ ๋„ฃ์œผ๋ฉด ์ž๋™ ์ผ์ž๋ณ„ ๋กœ๊ทธํŒŒ์ผ ์••์ถ• -->
            <fileNamePattern>${LOG_PATH}/db.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- ํŒŒ์ผ๋‹น ์ตœ๊ณ  ์šฉ๋Ÿ‰ kb, mb, gb -->
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- ์ผ์ž๋ณ„ ๋กœ๊ทธํŒŒ์ผ ์ตœ๋Œ€ ๋ณด๊ด€์ฃผ๊ธฐ(~์ผ), ํ•ด๋‹น ์„ค์ •์ผ ์ด์ƒ๋œ ํŒŒ์ผ์€ ์ž๋™์œผ๋กœ ์ œ๊ฑฐ-->
            <maxHistory>30</maxHistory>
            <!--<MinIndex>1</MinIndex> <MaxIndex>10</MaxIndex>-->
        </rollingPolicy>
    </appender>

    <!-- root๋ ˆ๋ฒจ ์„ค์ • -->
    <root level="${LOG_LEVEL}">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
        <appender-ref ref="Error"/>
    </root>

    <logger name="org.hibernate.SQL" level="DEBUG" additivity="false">
        <appender-ref ref="DB"/>
    </logger>
    <logger name="org.hibernate.type" level="TRACE" additivity="false">
        <appender-ref ref="DB"/>
    </logger>
</configuration>

tomcat์„ ์ด์šฉํ•œ logback

  • application.yml์— ์•„๋ž˜์™€ ๊ฐ™์€ ์„ค์ •์œผ๋กœ access.log๋ฅผ ๋‚จ๊ธธ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
server:
  tomcat:
    accesslog:
      directory: /[access log path]/
      enabled: true
      file-date-format: .yyyy-MM-dd
      max-days: 14
      pattern: '%h %l %u %t "%r" %s %b %D'
      prefix: test_access
      rotate: true
      suffix: .log
    basedir: /[tomcat base directory]/
  • ์ €ํฌ ํŒ€์˜ ๋กœ๊ทธ ์ „๋žต์— ๋งž๋Š” ํŒŒ์ผ ์ƒ์„ฑ๊ณผ ํŒจํ„ด์„ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด logback-access.xml์„ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
  • tomcat์„ ์ด์šฉํ•˜์—ฌ logback์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” tomcat์— logbackValve๋ฅผ ์—ฐ๊ฒฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • web.xml์„ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋Š”๋ฐ springboot์—์„œ๋Š” configuration์„ ์ด์šฉํ•˜์—ฌ web.xml์— ์„ค์ •์„ ์ถ”๊ฐ€ํ•ด์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— tomcat๋งŒ ์„ค์ •ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.
@Configuration
public class TomcatConfig implements WebServerFactoryCustomizer {
    @Override
    public void customize(WebServerFactory factory) {
        TomcatServletWebServerFactory containerFactory = (TomcatServletWebServerFactory)factory;

        LogbackValve logbackValve = new LogbackValve();
        logbackValve.setFilename("src/main/resources/logback/logback-access.xml");
        containerFactory.addContextValves(logbackValve);
    }
}

logback-access.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>

    <appender name="ACCESS_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/access.log</file>
        <encoder>
            <pattern>%fullRequest%fullResponse</pattern>
        </encoder>
        <rollingPolicy
                class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/access.%d{yyyy-MM-dd}_%i/.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>

    <appender-ref ref="ACCESS_FILE"/>
</configuration>

api.log ์ถœ๋ ฅ

  • ๋ชจ๋“  Controller์˜ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์„ log๋กœ ๋‚จ๊ธฐ๊ธฐ ์œ„ํ•ด interceptor๋ฅผ ์ด์šฉํ•˜์—ฌ log๋ฅผ ๋‚จ๊ฒผ์Šต๋‹ˆ๋‹ค.
  • interceptor์—์„œ prehandle์—์„œ ๋‚ด์šฉ์„ ์ถœ๋ ฅํ•˜๊ฒŒ ์‹œ๋„ํ•ด๋ณด์•˜๋Š”๋ฐ ๋˜์ง€ ์•Š์•˜๊ณ , afterComletion์— ๊ตฌํ˜„ํ•ด์•ผ ๋กœ๊ทธ๊ฐ€ ๋‚จ๊ฒจ์กŒ์Šต๋‹ˆ๋‹ค.
  • ๊ทธ ์š”์ฒญ์— ํ•ด๋‹นํ•˜๋Š” params, request body, response body๋ฅผ ์ถœ๋ ฅํ•˜๊ธฐ ์œ„ํ•ด filter๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
  • tomcat์—์„œ httpservletrequet, response์˜ ๊ฐ’์„ ํ•œ๋ฒˆ ์ฝ์–ด๋“ค์ด๋ฉด ๊ทธ ์ดํ›„๋ถ€ํ„ฐ๋Š” ๊ฐ’๋“ค์„ ์ฝ์–ด์˜ฌ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— filter์—์„œ ๊ฐ’์„ cacheํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
public class LogServletWrappingFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(
        HttpServletRequest request,
        HttpServletResponse response,
        FilterChain filterChain
    ) throws ServletException, IOException {
        ContentCachingRequestWrapper wrappingRequest = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper wrappingResponse = new ContentCachingResponseWrapper(response);
        filterChain.doFilter(wrappingRequest, wrappingResponse);
        wrappingResponse.copyBodyToResponse();
    }
}
@Override
    public void afterCompletion(
        HttpServletRequest request,
        HttpServletResponse response,
        Object handler,
        Exception exception
    ) throws Exception {
        ContentCachingRequestWrapper cachingRequest = (ContentCachingRequestWrapper)request;
        ContentCachingResponseWrapper cachingResponse = (ContentCachingResponseWrapper)response;

        String params = request.getParameterMap()
            .entrySet()
            .stream()
            .map(entry -> String.format("{\"%s\":\"%s\"}", entry.getKey(), Arrays.toString(entry.getValue())))
            .collect(Collectors.joining(","));
        log.warn("Params : {}", params);
        log.warn(
            "RequestBody : {} / ResponseBody : {}",
            objectMapper.readTree(cachingRequest.getContentAsByteArray()),
            objectMapper.readTree(cachingResponse.getContentAsByteArray())
        );
    }
โš ๏ธ **GitHub.com Fallback** โš ๏ธ