현재 운영하는 서비스에 로그 설정이 제대로 되어있지 않아서 디버깅이 어려웠다.

(각 서비스들이 도커로 동작했는데, 기존에 되어있던 설정상 재배포하면 이전 로그가 사라졌음..)

 

그래서 logback을 사용해 커스텀 로그 규칙을 설정하고, 로그를 파일로 저장하기로 했다.

(이후 이 로그파일을 NCP Object Storage에 매일 주기적으로 업로드하도록 서버 스크립트를 작성했다)

 

추가적으로 수정하고 싶은 부분이 있었지만 우선 로그를 남기는 것이 중요했기 때문에

빠르게 설정할 수 있는 부분부터 적용하기로 했다

 


요구사항

  1. 쿼리문 결과가 출력되면 안됨 (민감 정보가 출력될 수 있음) -> 단순 결과 (몇 개가 조회되었는지)
  2. 각 요청별로 추적이 가능해야함
  3. 배포 환경별로 다른 설정이 적용되어야 함
  4. 로그파일이 저장되어야 함

 

설정

  • SQL 로그 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
    <!-- DB transaction -->
    <logger name="org.mybatis.spring.SqlSessionUtils" level="OFF"/>
 
    <!-- log4jdbc 옵션 설정 -->
    <logger name="jdbc" level="OFF"/>
 
    <springProfile name="prod">
        <!-- 운영 환경에서만 sql문 및 결과 출력하지 않음 -->
        <!-- SQL문과 소요된 시간 -->
        <logger name="jdbc.sqltiming" level="OFF"/>
 
        <!-- SQL 결과 조회된 데이터를 table 형식으로 출력 -->
        <logger name="jdbc.resultsettable" level="OFF"/>
    </springProfile>
 
    <springProfile name="local, dev">
        <!-- 로컬, 개발 환경에서는 sql문 및 결과 출력 -->
        <!-- SQL문과 소요된 시간 -->
        <logger name="jdbc.sqltiming" level="DEBUG"/>
 
        <!-- SQL 결과 조회된 데이터를 table 형식으로 출력 -->
        <logger name="jdbc.resultsettable" level="DEBUG"/>
    </springProfile>
 
    <!-- SQL문 출력 -->
    <logger name="jdbc.sqlonly" level="OFF"/>
 
    <!-- result를 포함한 모든 JDBC 호출 정보 -->
    <logger name="jdbc.resultset" level="OFF"/>
 
    <!-- DB connection open/close -->
    <logger name="jdbc.connection" level="OFF"/>
 
    <!-- resultset을 제외한 JDBC 호출 정보 -->
    <logger name="jdbc.audit" level="OFF"/>
 
    <!-- DB Connection Pool 관련 -->
    <logger name="com.zaxxer.hikari.pool.PoolBase" level="ERROR"/>
    <logger name="com.zaxxer.hikari.pool.HikariPool" level="ERROR"/>
    <logger name="com.zaxxer.hikari.HikariDataSource" level="ERROR"/>
cs

이렇게 설정하고 콘솔을 확인해보면,

Preparing: ... 이라고 출력되며 SQL문이 그대로 나오는 것을 확인할 수 있다.

기본 스프링 설정인거같은데, 이렇게 쿼리문을 로그에 그대로 출력하는 경우 보안상의 문제가 생길 수 있어서

실행하는 쿼리문의 이름만 출력하도록(ex. mybatis에서 설정한 쿼리 id) 하고자 했다.

 

그런데 이게 기본 설정이라서 그런지 이 로그를 출력하지 않는 옵션을 찾을 수가 없었고

logback 설정에서 제공하는 filter를 사용해서 Preparing 단어가 포함되는 로그는 출력하지 않도록 설정했다

1
2
3
4
5
6
7
8
9
10
    <!-- 기본 출력되는 SQL문 제거 -->
    <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
        <evaluator>
            <expression>
                return message.contains("Preparing");
            </expression>
        </evaluator>
        <OnMismatch>NEUTRAL</OnMismatch>
        <OnMatch>DENY</OnMatch>
    </filter>
cs

 

  • 각 요청별 ID를 발급해 추적할 수 있게 하기

디버깅 할 때 특정 사용자의 번호 말고도 요청별로 ID를 발급해 해당 ID로 따라가면

디버깅이 더 용이하지 않을까하는 생각이 들었고, 적용해보고자 했다.

 

3개의 클래스 (유저 정보를 가져와 반환하는 클래스), (각 요청마다 sessinID를 반환하는 클래스),

(log 패턴을 설정하는 클래스)를 만들어 logback에서 사용한다.

 

1. 유저 정보를 받아오는 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.Map;
 
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
 
import ch.qos.logback.classic.pattern.ClassicConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
 
// logback 에서 사용하기 위한 변수를 convert 해주는 class
// 해당 class는 user 정보에 대해 출력하기 위한 용도임
public class UserConverter extends ClassicConverter {
    @Override
    public String convert(ILoggingEvent event) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
 
        if (auth != null && auth.getPrincipal() instanceof Map) {
            Map<String, Object> authMap = (Map<String, Object>) auth.getPrincipal();
            String userNo = authMap.get("USER_NO").toString();
            return "userNo: " + userNo;
        }
 
       return "";
    }
}
cs

프로젝트 설정마다 다르겠지만, spring security에서 인증한 유저 정보를 가져온다.

 

2. 요청별 session id를 가져오는 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
 
import ch.qos.logback.classic.pattern.ClassicConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
 
// logback 에서 사용하기 위한 변수를 convert 해주는 class
// 해당 class는 request별 session을 출력하기 위한 용도임
public class SessionConverter extends ClassicConverter {
    @Override
    public String convert(ILoggingEvent event) {
        RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
        if (attrs != null) {
            return "session: " + attrs.getSessionId();
        }
        return "";
    }
}
cs

Spring에서 제공하는 session util을 사용해 sessionID를 가져온다.

 

3. log 패턴을 설정하는 클래스

1
2
3
4
5
6
7
8
9
10
import ch.qos.logback.classic.PatternLayout;
 
public class PatternLayoutWithUserContext extends PatternLayout {
    static {
        PatternLayout.defaultConverterMap.put(
            "user", UserConverter.class.getName());
        PatternLayout.defaultConverterMap.put(
            "session", SessionConverter.class.getName());
    }
}
cs

 

4. logback 적용

1
2
3
4
5
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="com.cmm.common.logging.PatternLayoutWithUserContext">
                <param name="Pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %session %-5level %logger{10} - %user %msg%n" />
            </layout>
        </encoder>
cs

년-월-일 시:분:초 형식으로 아까 설정했던 session id와 유저번호를 함께 출력한다.

 

  • 로그 파일로 저장
1
2
3
4
5
6
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>./logs/application-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>500MB</maxFileSize>
            <maxHistory>60</maxHistory>
            <totalSizeCap>10GB</totalSizeCap>
        </rollingPolicy>
cs

로그 파일은 한 파일당 최대 500메가, 60일까지, 총 10GB를 초과하지 않도록 설정했다.

이 설정 관련해서는 차후 서비스의 규모가 커지며 맞춰서 설정을 변경해줘야 한다.

 

만약 파일이 500메가를 넘어가는 경우,

application-2022-05-10.0.log, application-2022-05-10.1.log와 같은 형식으로 저장된다.

 

1
2
3
4
5
6
7
8
9
10
    <root level="debug">
        <springProfile name="dev, prod">
            <!-- 개발, 운영 환경에서만 로그 파일로 저장 -->
            <appender-ref ref="file" />
        </springProfile>
 
        <springProfile name="local, dev, prod">
            <appender-ref ref="console"/>
        </springProfile>
    </root>
cs

마지막으로 파일은 개발, 운영 환경에서만 저장되도록 했다.

 

 

 

+ Recent posts