반응형

[Spring Boot 실습 #9] Logback 적용 - level 별 appender 분리

 

1. Logback 활용하여 appender 분리 및 level 별로 로그 파일 분리

2. 개발환경

    2.1. 개발환경

    2.2. 라이브러리

3. 구현

    3.1. 프로젝트 구조

    3.2. 구현 - 소스코드

4. 결과

    4.1. logback-file.log

    4.2. logback-accessLog.log

    4.3. logback-error.log

 

 

1. Logback 활용하여 appender 분리 및 level 별로 로그 파일 분리

 

2. 개발환경

 

2.1. 개발환경

MacOS M1 - macOS Monterey 12.0.1

IntelliJ IDEA 2021.2 (Community Edition)

 

2.2. 라이브러리

JDK 11

spring-boot-2.6.4

 

 

3. 구현

3.1. 프로젝트 구조

 

3.2. 구현 - 소스코드

 

[ pom.xml ]

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
41
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>io.home.test</groupId>
    <artifactId>spring-boot-accesslog</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-accesslog</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
 
</project>

 

[ logback.xml ]

  • fileLogback appender
    • logback-file.log 파일로 분리
    • ThresholdFilter 활용하여 log level을 INFO 설정
    • LevelFilter 활용하여 INFO 레벨의 로그만 기록하도록 설정
  •  errorLogback appender
    • logback-error.log 파일로 분리
    • ThresholdFilter 활용하여 log level을 WARN 설정
  • accessLogTest appender
    • logback-accessLog.log 파일로 분리
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="consoleLogback" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!--<pattern>[%date GMT][%-5level][%logger{36}.%M\(%line\)] %msg %n</pattern>-->
            <pattern>%cyan(%date GMT) [%highlight(%-5level)] [%magenta(%thread)] [%boldBlue(%logger{36}.%M\(%line\))] %msg %n</pattern>
        </encoder>
    </appender>
    <appender name="fileLogback" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>DENY</onMatch>
        </filter>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>DENY</onMatch>
        </filter>
        <file>${logging.path:-logs}/logback-file.log</file>
        <append>true</append>
        <encoder>
            <!--<pattern>[%date GMT][%-5level][%logger{36}.%M\(%line\)] %msg %n</pattern>-->
            <pattern>%cyan(%date GMT) [%highlight(%-5level)] [%magenta(%thread)] [%blue(%logger{36}.%M\(%line\))] %msg %n</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${logging.path:-logs}/logback-file.%d{yyyyMMdd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>10</maxHistory>
            <totalSizeCap>100MB</totalSizeCap>
        </rollingPolicy>
    </appender>
    <appender name="accessLogTest" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logging.path:-logs}/logback-accessLog.log</file>
        <append>true</append>
        <encoder>
            <!--<pattern>%d{ISO8601} %msg %n</pattern>-->
            <pattern>%cyan(%date GMT) [%highlight(%-5level)] [%magenta(%thread)] [%blue(%logger{36}.%M\(%line\))] %msg %n</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logging.path:-logs}/access.%d{yyyyMMdd}.log.gz
            </fileNamePattern>
            <maxHistory>2</maxHistory>
        </rollingPolicy>
    </appender>
    <appender name="errorLogback" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>warn</level>
        </filter>
 
        <file>${logging.path:-logs}/logback-error.log</file>
        <append>true</append>
        <encoder>
            <!--<pattern>[%date GMT][%-5level][%logger{36}.%M\(%line\)] %msg %n</pattern>-->
            <pattern>%cyan(%date GMT) [%highlight(%-5level)] [%magenta(%thread)] [%blue(%logger{36}.%M\(%line\))] %msg %n</pattern>
        </encoder>
 
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${logging.path:-logs}/logback-error.%d{yyyyMMdd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>10</maxHistory>
            <totalSizeCap>100MB</totalSizeCap>
        </rollingPolicy>
    </appender>
    <logger name="io.home.test.base.web" level="error" additivity="false">
        <appender-ref ref="errorLogback"/>
    </logger>
    <logger name="io.home.test.base.web" level="info" additivity="false">
        <appender-ref ref="fileLogback"/>
        <appender-ref ref="consoleLogback"/>
    </logger>
    <logger name="org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer" level="info"
            additivity="false">
        <appender-ref ref="fileLogback"/>
        <appender-ref ref="consoleLogback"/>
    </logger>
    <logger name="ACCESS" level="info" additivity="false">
        <appender-ref ref="accessLogTest" />
        <appender-ref ref="consoleLogback"/>
    </logger>
 
    <root>
        <level value="info"/>
        <appender-ref ref="consoleLogback"/>
    </root>
</configuration>

 

[ SpringBootLogbackApplication.java ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package io.home.test;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
import java.io.File;
import java.util.HashMap;
import java.util.Map;
 
@SpringBootApplication
public class SpringBootLogbackApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(SpringBootLogbackApplication.class, args);
    }
 
}

 

[ DefaultController.java ]

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package io.home.test.base.web;
 
import io.home.test.base.errors.exceptions.TestException01;
import io.home.test.base.errors.exceptions.TestException02;
import io.home.test.base.errors.exceptions.TestException03;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
 
@RestController
@RequestMapping(value = "/api/access")
public class DefaultController {
    Logger LOG = LoggerFactory.getLogger(DefaultController.class);
 
    @GetMapping
    public ResponseEntity<String> defaultGetResource() {
        LOG.info("---- access controller GET");
        return ResponseEntity.ok("GET Hello World!");
    }
 
    @GetMapping(value = "/params")
    public ResponseEntity<String> defaultGetParamsResource(@RequestParam(value = "queryString"String queryString) {
        LOG.info("---- access controller GET + Params?" + queryString);
        return ResponseEntity.ok("GET + Params Hello World!");
    }
 
    @PostMapping
    public ResponseEntity<String> defaultPostResource(@RequestBody String body) {
        LOG.info("---- access controller POST\n" + body);
        return ResponseEntity.ok("POST Hello World!");
    }
 
    private static int a = 0;
    @GetMapping(value = "/error")
    public ResponseEntity<String> defaultErrorResource() {
        a++;
        try {
            switch (a) {
                case 1:
                    LOG.info("---- access controller GET /error");
                case 2:
                    throw new TestException01("Test Exception 01");
                case 3:
                    throw new TestException02("Test Exception 02");
                case 4:
                    throw new TestException03("Test Exception 03");
                case 5:
                    LOG.info("---- access controller GET /error");
                    a = 0;
                    break;
            }
            return ResponseEntity.ok().body("Error Hello World");
        } catch (Exception e) {
            String errorMsg = e.getMessage();
            if (a == 2) {
                LOG.warn(errorMsg);
            } else {
                LOG.error(errorMsg, e);
            }
            return ResponseEntity.ok().body(errorMsg);
        }
    }
}

 

[ AccessLogFilter.java ]

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
package io.home.test.base.filter;
 
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.filter.OncePerRequestFilter;
 
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
@WebFilter(urlPatterns = {"/api/*"}, asyncSupported = true)
@Component
public class AccessLogFilter extends OncePerRequestFilter {
 
    @Autowired
    AccessLogger accessLogger;
 
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        long stime = System.nanoTime();
        try {
            chain.doFilter(request, response);
        } finally {
            long etime = System.nanoTime();
            long elapsed = etime - stime;
            accessLogger.log(request, response, elapsed);
        }
    }
}

 

[ AccessLogger.java ]

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package io.home.test.base.filter;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
@Service
public class AccessLogger {
 
    Logger log = LoggerFactory.getLogger("ACCESS");
 
    private String getURL(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        sb.append(request.getRequestURL());
 
        String queryString = request.getQueryString();
        if (queryString != null) {
            sb.append("?").append(queryString);
        }
        return sb.toString();
    }
 
    public void log(HttpServletRequest request, HttpServletResponse response, long elapsed) {
        String accessLog = buildAccessLog(request, response, elapsed);
        log.info(accessLog);
//        log.info("\"host\": {}, method: {}, url: {}, status: {}, elapsed: {}", remoteHost, method, url, status, elapsed);
    }
 
    private String buildAccessLog(HttpServletRequest request, HttpServletResponse response, long elapsed) {
 
        String remoteHost = request.getRemoteHost();
        String method = request.getMethod();
        String url = getURL(request);
        int status = response.getStatus();
 
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        if (remoteHost != null) {
            sb
                    .append("\"").append("remoteHost").append("\"")
                    .append(":")
                    .append("\"").append(remoteHost).append("\"");
        }
        if (method != null) {
            sb
                    .append(",")
                    .append("\"").append("method").append("\"")
                    .append(":")
                    .append("\"").append(method).append("\"");
        }
        if (url != null) {
            sb
                    .append("\"").append("url").append("\"")
                    .append(":")
                    .append("\"").append(url).append("\"");
        }
        if (status != 0) {
            sb
                    .append(",")
                    .append("\"").append("status").append("\"")
                    .append(":")
                    .append("\"").append(status).append("\"");
        }
        if (elapsed != 0) {
            sb
                    .append(",")
                    .append("\"").append("elapsed").append("\"")
                    .append(":")
                    .append(elapsed);
        }
        sb.append("}");
        return sb.toString();
    }
}

 

[ TestException01.java ]

1
2
3
4
5
package io.home.test.base.errors.exceptions;
 
public class TestException01 extends RuntimeException {
    public TestException01(String message) { super(message); }
}

 

[ TestException02.java ]

1
2
3
4
5
package io.home.test.base.errors.exceptions;
 
public class TestException02 extends RuntimeException {
    public TestException02(String message) { super(message); }
}

 

[ TestException03.java ]

1
2
3
4
5
package io.home.test.base.errors.exceptions;
 
public class TestException03 extends RuntimeException {
    public TestException03(String message) { super(message); }
}

 

4. 결과

로그 파일 생성 확인

각 로그 파일에 접근하여 tail로 확인 

 

4.1. logback-file.log

 

 

4.2. logback-accessLog.log

 

4.3. logback-error.log

 

반응형