[Spring Boot 실습 #10] Log4j2 적용 - level 별 appender 분리
1. Log4j2 활용하여 appender 분리 및 level 별로 로그 파일 분리
2. 개발환경
2.1. 개발환경
2.2. 라이브러리
3. 구현
3.1. 프로젝트 구조
3.2. 구현 - 소스코드
4. 결과
4.1. log4j2-file.log
4.2. log4j2-accessLog.log
4.3. log4j2-error.log
1. log4j2 활용하여 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 ]
- spring-boot는 기본적으로 logback이 slf4j를 구현하도록 설정
- log4j2를 설정하기 위해서는 logback dependency 제외 후 log4j2를 추가
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
|
<?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-log4j2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-log4j2</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
<!-- 2021. 12. 10 Log4JShell Situation -->
<!--<log4j2.version>2.17.1</log4j2.version>-->
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- exclude logback configurations -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</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>
|
[ log4j2-spring.xml ]
- FileAppender
- log4j2-file.log 파일로 분리
- ThresholdFilter 활용하여 log level을 info, warn, error, fatal 설정 & trace, debug 무시
- ThresholdFilter 활용하여 log level을 error, fatal 무시
- ErrorAppender
- log4j2-error.log 파일로 분리
- ThresholdFilter 활용하여 log level을 error, fatal 설정
- AccessAppender
- log4j2-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
88
89
90
91
92
93
94
95
96
97
98
|
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Properties>
<Property name="LOG_MSG">%style{%d}{bright,cyan} [%highlight{%-5level}] [%style{%t}{bright,magenta}] %style{%C{1.}}{bright,blue}: %msg%n%throwable</Property>
</Properties>
<Appenders>
<Console name="ConsoleAppender" target="SYSTEM_OUT">
<!--<PatternLayout pattern="%style{%d}{bright,cyan} [%highlight{%-5level}] [%style{%t}{bright,magenta}] %style{%C{1.}}{bright,blue}: %msg%n%throwable" />-->
<PatternLayout charset="UTF-8" pattern="${LOG_MSG}"/>
</Console>
<RollingFile name="AccessAppender"
fileName="./logs/log4j2-accessLog.log"
filePattern="./logs/$${date:yyyy-MM}/log4j2-accessLog-%d{-dd-MMMM-yyyy}-%i.log.gz">
<Filters>
<!-- accept info, warn, error, fatal and denies debug/trace -->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
<!--<PatternLayout>
<pattern>%style{%d}{bright,cyan} [%highlight{%5level}] [%style{%t}{bright,magenta}] %style{%C{1.}}{bright,blue}: %m%n</pattern>
</PatternLayout>-->
<PatternLayout charset="UTF-8" pattern="${LOG_MSG}"/>
<Policies>
<!-- rollover on startup, daily and when the file reaches 10 MegaBytes -->
<OnStartupTriggeringPolicy />
<SizeBasedTriggeringPolicy size="10 MB" />
<TimeBasedTriggeringPolicy />
</Policies>
</RollingFile>
<RollingFile name="FileAppender"
fileName="./logs/log4j2-file.log"
filePattern="./logs/$${date:yyyy-MM}/log4j2-file-%d{-dd-MMMM-yyyy}-%i.log.gz">
<Filters>
<!-- deny error, fatal -->
<ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
<!-- accept info, warn, error, fatal and denies debug/trace -->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<!--<PatternLayout>
<pattern>%style{%d}{bright,cyan} [%highlight{%5level}] [%style{%t}{bright,magenta}] %style{%C{1.}}{bright,blue}: %m%n</pattern>
</PatternLayout>-->
<PatternLayout charset="UTF-8" pattern="${LOG_MSG}"/>
<Policies>
<!-- rollover on startup, daily and when the file reaches 10 MegaBytes -->
<OnStartupTriggeringPolicy />
<SizeBasedTriggeringPolicy size="10 MB" />
<TimeBasedTriggeringPolicy />
</Policies>
</RollingFile>
<RollingFile name="ErrorLogFileAppender"
fileName="./logs/log4j2-error.log"
filePattern="./logs/$${date:yyyy-MM}/log4j2-error-%d{-dd-MMMM-yyyy}-%i.log.gz">
<Filters>
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
<!--<PatternLayout>
<pattern>%style{%d}{bright,cyan} [%highlight{%5level}] [%style{%t}{bright,magenta}] %style{%C{1.}}{bright,blue}: %m%n</pattern>
</PatternLayout>-->
<PatternLayout charset="UTF-8" pattern="${LOG_MSG}"/>
<Policies>
<!-- rollover on startup, daily and when the file reaches 10 MegaBytes -->
<OnStartupTriggeringPolicy />
<SizeBasedTriggeringPolicy size="10 MB" />
<TimeBasedTriggeringPolicy />
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="ConsoleAppender" />
</Root>
<Logger name="org.springframework" additivity="false">
<AppenderRef ref="ErrorLogFileAppender"/>
<AppenderRef ref="ConsoleAppender"/>
<AppenderRef ref="FileAppender"/>
</Logger>
<Logger name="io.home.test.base.filter" additivity="false">
<AppenderRef ref="AccessAppender" />
<AppenderRef ref="ConsoleAppender"/>
</Logger>
<Logger name="io.home.test" additivity="false">
<AppenderRef ref="ErrorLogFileAppender"/>
<AppenderRef ref="FileAppender" />
<AppenderRef ref="ConsoleAppender"/>
</Logger>
</Loggers>
</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(AccessLogger.class);
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. log4j2-file.log
4.2. log4j2-accessLog.log
4.3. log4j2-error.log
'스프링 (Spring) > Spring Boot' 카테고리의 다른 글
[Spring Boot 실습 #12] 에러 처리 (Exception Handling) - 임의의 http status code 반환 (0) | 2022.04.20 |
---|---|
[Spring Boot 실습 #11] 에러 처리 (Exception Handling) - 정상 범위의 http status code로 반환 (0) | 2022.04.19 |
[Spring Boot 실습 #9] Logback 설정 및 적용 - level 별 appender 분리 (0) | 2022.03.17 |
[Spring Boot 실습 #8] Access Log - request body 포함 (0) | 2022.03.16 |
[Spring Boot 실습 #7] H2 Database 설정 - h2 console 접근 (0) | 2021.12.10 |