LOG?
로그란 기록을 나타낸다. 그럼 어떤 기록인가?
바로 우리가 보고 싶은 것을 기록한다 보면 된다.
왜 필요했나?
처음 웹 프로젝트를 진행하고 배포하게 되었을때, react와 spring의 연동으로 배포를하게 되었는데
로컬 서버로는 정상 동작되었고, 이제 배포를 해야하는 시기가 왔었다. 따라서 aws ec2에 배포를하게 되었는데
문제는 여기서 발생했다 war파일로 배포를 할때 로그인 상태에서 react파일을 가져오지 못해는 상태였습니다.
이때 뭐를 확인할 방법이 없어서 추론만하다가 log를 사용해서 해보아라 라고 어느 분께 들어서 LOG를 먼저 공부하고자 포스트를 쓰게됐습니다.
(얼마전까지 자바도 모르는 상태였습니다...)
결국 서비스의 동작 상태를 정확히 알고 어떤 에러가 났는지를 확인하기 위해 필요
자바 Logging Framework
자바에는 로그를 도와주는 많은 프레임워크가 있었습니다. 그 중 유명했던 것은
- JUL(java.util.logging)
- JDK 1.4이상의 표준 logging API
- 다른 것에 비해 성능이 별로다
- 기능 부족
- log4j : 아파치 제단에서 제공
- thread safe
- 여러 종류의 append 지원
- log4j2: 아파치 제단에서 제공
- 비동기 로깅 지원
- 가장 빠르다
- 멀티 쓰레드 환경에서 높은 성능
- logback : log4j의 단점 개선 및 기능 추가
- SLF4J
SLF4J는 나머지 API와는 조금 다른 결입니다.
SLF4J (Simple Logging Facade for Java)
이름 그대로 logging에 대한 facade 패턴을 가지고 있습니다. 제가 이해한 바로는 Java에는 많은 로깅 프레임워크가 있고 그중 하나를 사용하고 있다고 생각하자, 만약 이때 로깅 API를 다른 것으로 바꾸어햐 한다면? 생각만해도 어지러운데 이를 한 번에 도와주는 것이 SLF4J이다.
더 정확히는 SLF4J API는 다양한 로깅 프레임워크를 하나의 API로 접근할 수 있게 해주는 역할을 한다.
따라서 SLF4J API를 호출하여도 실제로 돌아가는 프레임워크는 다른 일반 프레임워크인 것이다.
Logging 단계
위험에도 단계가 있듯이 Logging에도 단계가 있습니다. 총 7개의 단계가 있고
- SEVERE (highest, 가장 위험)
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
Log4j의 경우
- FATAL
- ERROR
- WARN
- INFO
- DEBUG
- TRACE
예외로 OFF와 ALL이 있는데, OFF는 모든 logging을 끄고, ALL은 모든 logging을 사용합니다.
Logging 사용 시 주의 점
log 내에서 msg를 전달 시 내부 연산은 많은 시간을 낭비한다.
Appenders
appender는 log 메세지를 특정 위치에 전달해주는 역할을 한다
- ConsoleAppeder
- FileAppender
- JDBCAppender
- STMP
- etc...
Logging 사용
Logger logger = Logger.getLogger(Example.class.getName());
public static void main(String[] args) {
Logger logger = Logger.getLogger(Example.class.getName());
logger.severe("1단계 (가장 위험)");
logger.warning("2단계");
logger.info("3단계");
logger.config("4단계");
logger.fine("5단계");
logger.finer("6단계");
logger.finest("7단계");
} |
위와 같이 선언하여 사용하면 됩니다.

근데 결과는 다르게 나오게 됩니다. 바로 초기 설정 값이 Level 값이 특정 값으로 되어있기 때문입니다.
따라서 변경해 주기 위해서는 코드로 변경을 하거나 .properties로 변경을 해주어야 합니다.
1. 코드로 변경
따라서 변경해 주어야하기때문에 logger.setLevel(Level.ALL);를 해주어야 하는데 아마 해도 변화가 없습니다.
root 로그를 접근해서 해당 값을 변경해주어야 하기 때문입니다.
[Logger.java]를 보면
이와 같이 되어있으므로 logger.getLogger("");로 root를 접근하여 각 핸들러를 가져온 후 자신이 원하는 핸들러에 넣으면 됩니다.
Logger root = Logger.getLogger("");
root.setLevel(Level.ALL);
for(Handler h : root.getHandlers()){
if(h instanceof ConsoleHandler) {
h.setLevel(Level.ALL);
}
}
|
즉, 위 예제는 사용하고 있는 handler가 Console이든 File이든 Socket이든 전부든 모두 확인 후 그 중 원하는 것만 변경하는 코드 입니다.
위와 같은 결과가 나온다.
2. properties 파일 변경
.properties파일의 경우 대부분 jre의 lib 내부에 있다 하여 보았지만 다른.properties파일은 있었지만 logging.properties는 없었습니다. 저의 경우 corretto를 사용 중이었고 jdk폴더의 lib가 아닌 conf폴더에 있었습니다.
이런식으로 초기설정이 INFO로 되어있는데 이 부분을 고치면 됩니다.
FileHandler 추가
FileHandler fileHandler = new FileHandler(filePath);
fileHandler.setLevel(Level.ALL);
logger.addHandler(fileHandler);
이렇게 추가하여 실행을 하게 되면 log.txt 가 생성되는데 만약 다른 클래스에서 똑같이 로그를 사용하고 싶어서 추가하게 되면 log.txt.1 가 하나 더 추가되면서 쓸모 없는 파일이 생성되고 코드도 길어지게 된다.
따라서 여러 파일에서 같이 사용하고 만약 자신이 원하는 포맷형식과 handler를 추가하고 싶을때를 위해 새로 클래스를 만들었습니다.
Custom Log
(모든 예외처리는 어떤 예외가 있는지 보여주기 위해 함수 밖으로 안빼고 그냥 놔뒀습니다.)
먼저 공통으로 사용할 logger가 필요하다 따라서 final과 static을 사용한 logger변수가 한 개 필요하고
[UserLog.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
|
package me.ex.log;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
public class UserLog {
// 공통으로 사용하기위해 final과 static
private final static Logger logger = Logger.getLogger(UserLog.class.getName());
// log를 저장할 파일 위치
private static final String fileName = "filePath";
// File에 저장하기 위한 handler
private FileHandler fileHandler;
public UserLog(){
/* Handler 설정 : 어디에 출력할지를 결정
Console Handler : 시스템 콘솔에 출력
File Handler : 파일을 생성
SocketHandler : 원격 TCP사용
*/
try {
fileHandler = new FileHandler(fileName);
// 여러 핸들러를 통해 다른 파일에 저장 가능
}catch (IOException e) {
e.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}
// 입력 형식
fileHandler.setFormatter(new CustomFormatter());
// 레벨 지정
fileHandler.setLevel(Level.WARNING);
UserLog.logger.addHandler(fileHandler);
/* highest
SEVERE
WARNING
INFO
CONFIG
FINE
FINER
FINEST
*/
}
public static Logger getLogger(){ // 해당 함수로 공통 Logger 가져온다.
return UserLog.logger;
}
}
|
[CustomFormatter.java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package me.ex.log;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
public class CustomFormatter extends Formatter {
@Override
public String format(LogRecord record) {
SimpleDateFormat logTime = new SimpleDateFormat("MM-dd-yyyy HH:mmm:ss");
Calendar cal = new GregorianCalendar();
cal.setTimeInMillis(record.getMillis());
return "["+ record.getLevel()+"]"
+"["+logTime.format(cal.getTime())+"]"
+"["+record.getSourceClassName()+"||"
+record.getSourceMethodName()+"()] : "
+record.getMessage() + "\n";
}
}
|
Formatter를 상속받아 사용하며 추상메서드를 오버라이딩하여 사용해야합니다.
[main]
1
2
3
4
5
6
7
8
9
|
public class Example {
private final static UserLog logger = new UserLog();
public static void main(String[] args) {
Logger logger = UserLog.getLogger();
logger.warning("테스트1 에러");
TestClass.logTest(); // 5~6줄 있는 코드, 똑같은 코드다
}
}
|
로그 관련하여 추천하는 블로그
https://blog.lulab.net/programmer/what-should-i-log-with-an-intention-method-and-level/ 너무 잘 나와있다.
http://dveamer.github.io/backend/HowToUseSlf4j.html