해당 포스트를 작성하는 목적
자바의신vol2 의 20장 공부와 더불어, 시스템클래스에 대해서 알아보기 위해 작성한다.
20장에서는 java.lang패키지와 함께 wrapper클래스, System클래스를 다루고있다.
이번 포스트에서는 System클래스에 대한 공부와, System.out.println() 을 왜 디버깅용으로 사용하면 안되는지, 로그와의 차이점이 무엇인지 알아보자.
각종 정보를 확인하기 위한 System클래스
Java프로그램은 운영체제상에서 바로 실행되는 것이 아니라, JVM위에서 실행되기 때문에 자바코드로 운영체제의 기능을 접근하기란 쉽지 않다. 그러나 System클래스를 이용하면 일부 기능을 사용할 수있다. System클래스의 메소드는 모두 static으로 선언되어 생성자가 없이 메소드를 호출할 수있다.
System클래스는 다음과 같은 다양한 기능을 수행한다.
- 시스템 속성(Property)값 관리
- 시스템 환경(Enviroment)값 조회
- GC수행
- JVM종료
- 현재 시간 조회
- 기타 관리용 메소드들
추후에 설명하겠지만, 여기서 GC수행과 JVM종료 메소드는 절.대 사용하지 말자.
시스템 속성(Property) 관리
속성 값 관련 메소드들
리턴 타입 | 메소드 | 설명 |
---|---|---|
static String | clearProperty(String key) | key에 지정된 시스템 속성을 제거한다. |
static Properites | getProperties() | 현재 시스템 속성을 Properties 클래스 형태로 제공 |
static String | getProperty(String key) | key에 지정된 문자열로 된 시스템 속성 값(value)를 얻는다. |
static String | getProperty(String key, String def) | key에 지정된 문자열로된 시스템 속성값(value)를 얻고, 만약 없으면 def에 지정된 값을 반환한다. |
static void | setProperties(Properties props) | Properties 타입으로 넘겨주는 매개 변수에 있는 값들을 시스템 속성에 넣는다. |
static String | setProperty(String key, String value) | key로 지정된 시스템 속성 값을 value로 대체 한다. |
Property란?...
Property를 이해하기 위해서는 먼저 Properties라는 클래스를 이해 해야한다.
Properties는 java.util 패키지에 속하며, HashTable을 상속받은 클래스다.
개발자의 필요여부와 상관없이 자바 프로그램을 실행하면 Properties 객체가 생성되며, 그 값은 언제 어디서든 같은 JVM내에서라면 꺼내서 사용할 수 있다. 다음 예제코드를 확인해 보자.
public void systemPropertiesCheck() {
System.out.println("Java.version="+System.getProperty("java.version"));
}
/*
Java.version=1.8.0_112
*/
시스템 환경(Enviroment) 값 조회
환경 값 메소드
리턴 타입 | 메소드 | 설명 |
---|---|---|
static Map<String, String> | getenv() | 현재 시스템 환경에 대한 Map형태의 리턴값을 받는다. |
static String | getenv(String name) | 지정한 name에 해당하는 값을 받는다. |
제목 없음 |
앞서 살펴본 Properties에는 조회도 할 수있고, 수정도 할 수있었다. 하지만 환경값 env는 조회만이 가능하다. 대부분 OS나 장비와 관련된 값들이다.
public void systemPropertiesCheck() {
System.out.println("Java.version="+System.getProperty("java.version"));
System.out.println("JAVA_HOME="+System.getenv("JAVA_HOME"));
}
/*
Java.version=1.8.0_112
JAVA_HOME=null
*/
시스템변수로 JAVA_HOME을 따로 설정하지 않아 null이 나왔다.
GC수행
- gc() : 가비키 컬렉터를 실행한다.
- runFinalization() : GC처리를 기다리는 모든 객체에 대해 finalize()메소드를 실행한다.
GC와 관련된 메소드는 개발자가 절대 직접쓰지 않기로 한다. 이 메소드를 실행하면, 시스템은 모든 일을 멈추고, 해당 메소드를 수행한다.
JVM종료
- exit(int status) : 현재 수행중인 JVM을 멈춘다.
위와 마찬가지로 절대 쓰지 않도록 한다. JVM을 멈춘다는 것은 프로그램을 종료한다는 것과 다를바가 없다.
현재 시간 조회
시간조회 메소드
이름 | 메소드 | 설명 |
---|---|---|
static Long | currentTimeMillis() | 현재 시간을 밀리초 단위로 리턴한다. |
static Long | nanoTime() | 현재 시간을 나노초 단위로 리턴한다. |
currentTimeMillis()메소드는 현재 시간을 나타낼때 매우 유용하며, UTC기준 1970.01.01.00:00부터 현재가지 밀리초 단위의 차이를 출력한다.
nanoTime() 메소드는 현재시간을 알아내기 위한 메소드는 아니고, 시간의 차이를 측정하기 위한 메소드 이다.
System.out을 살펴보자
System클래스에 선언된 out(System.out)과 err(System.err)변수는 PrintStream이라는 동일한 클래스의 객체이다. 단지 정상출력인지, 에러가 났을때의 출력인지 차이만 존재한다.
println() 메소드로 줄바꿈을 처리할땐 println("")도 상관없으나, String 객체가 생성되므로,
println()을 사용하도록 하자. 또 println()의 한가지 예외적인 상황에 대해서 코드로 이해해보자
public void printNUll() {
Object obj = null;
System.out.println(obj);
System.out.println(obj +"is object's value");
}
/*
null
nullis object's value
*/
null이 출력된다. 객체를 출력할 때 단순 .toString()메소드를 호출한다고 알고있었다면, null.toString()이 되므로 당연히 오류가 날것이라고 생각했을 것이다. 그 이유는 print()와 println()메소드에서는 단순히 toString()호출하지 않기 때문이다. println() 내부구현을 보면 print()내부적으로 호출하고, print()내부구현을 보면 toString()이 아닌 String.valueOf() 메소드를 호출한다. 다시 String.valueOf()메소드를 확인하면, null을 체크하고 toString()을 호출하는 것을 볼수 있다. 즉, null에 대한 예외처리가 되어있기 때문에, null이 매개변수로 전달되어도 오류가 나지 않는다. 아래 코드를 확인 해보자.
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
public void print(String s) {
write(String.valueOf(s));
}
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
System.out.println() 은 왜 로그로 쓰면 안되는가?
결정적인 이유는 바로 '성능'이다. println()메소드 코드를 확인해보자.
/**
* Prints a String and then terminate the line. This method behaves as
* though it invokes {@link #print(String)} and then
* {@link #println()}.
*
* @param x The {@code String} to be printed.
*/
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
synchronized 블록을 확인할 수있다. 동기화를 진행함으로써 오버헤드는 증가, 성능은 저하되고,
만일 하나의 연산이 끝나지 않았다면, 계속해서 멈춰있게 된다. 즉, 하나의 연산으로 인해 시스템 전체가 정지되는 상황을 초래할 수있다.
그외에 내용을 콘솔에 출력함으로 운영환경에서의 실질적인 디버깅은 할수없다. 즉, 개발환경에서만 가능하기 때문에 처음부터 로그를 쓰는 것이 훨씬 경제적이다.
로깅이란?
System.out.print()를 로그로 쓰기 어렵다면, Java에서 사용한 로그는 어떤게 좋을까??
로그의 개념과 제공되는 라이브러리들을 알아보자
로깅(Logging)이란?
- 정보를 제공하는 일련의 기록인 로그(log)를 생성하도록 시스템을 작성하는 활동
- 프린트 줄 넣기(printlining)는 간단한, 보통은 일시적인, 로그를 생성하기만 한다.
- 시스템 설계자들은 시스템의 복잡성 때문에 로그를 이해하고 사용해야 한다.
- 로그가 제공하는 정보의 양은, 이상적으로는 프로그램이 실행되는 중에도, 설정 가능해야 한다.
- 일반적으로 로그 기록의 이점로그는 재현하기 힘든 버그에 대한 유용한 정보를 제공할 수 있다.로그는 성능에 관한 통계와 정보를 제공할 수 있다.설정이 가능할 때, 로그는 예기치 못한 특정 문제들을 디버그하기 위해, 그 문제들을 처리하도록 코드를 수정하여 다시 적용하지(redeploy) 않아도, 일반적인 정보를 갈무리할 수 있게 한다.
로그 라이브러리 종류
- java.util.logging
- JDK 1.4부터 포함된 표준 로깅 API
- 별도 라이브러리 추가 불필요
- 기능이 많이 부족해 다른 로그 라이브러리를 더 많이 사용
- Apache Commons logging
- 아파치 재단에 Commons 라이브러리 중에 로그 출력을 제공하는 라이브러리
- Log4j
- 아파치 제단에서 제공하며 가장 많이 사용되는 로깅 라이브러리
- Logback
- Log4j를 개발한 Ceki Gulcu가 Log4j의 단점 개선 및 기능을 추가하여 개발한 로깅 라이브러리
LogBack라이브러리의 로깅 레벨과 쓰임새
- trace : debug보다 세분화된 정보, 개발환경에서는 쓰이지만 실제 운영환경에서는 쓰지 않는다.
- debug : 디버깅하는데 유용한 세분화된 정보 info보다 더 자세하다.
- info : 진행상황 같은 간결한 일반 정보
- warn : 오류는 아니지만 잠재적인 오류 원인이 될 수 있는 경고성 정보 (메모리가 거의 다차가거나, DB커넥션 시간이 우리가 설정한 것보다 더 길어질 경우)
- error : 요청을 처리하는 중 문제가 발생한 오류 정보, 시스템종료는 되지 않는경우!
- fatal : 매우 심각한 에러가 발생할 경우 출력되는 로그, 주로 이 레벨이 사용되면 시스템 종료가 되는경우가 많다. 그러나 시스템이 정상적으로 종료되는 현상이 아니기 때문에, 로그가 정상적으로남는다는 보장을 할 수없다. 그렇기에 최대한 사용하지 않는것이 좋다!
회원조회 기능에서 회원이 DB에 없을 경우 NPE를 던진다고 가정해보자, 이 이벤트의 로그는 어떤 레벨을 써야 적절할까??
로그를 쓸때는 SL4FJ를 이용하자!
SLF4J란?
- logging 관련 라이브러리는 다양하다.
- 이러한 라이브러리들을 하나의 통일된 방식으로 사용할 수 있는 방법을 SLF4J는 제공한다.
- SLF4J는 로깅 Facade이다.
- 로깅에 대한 추상 레이어를 제공하는 것이고 interface의 모음이다.
아래의 그림을 확인해보자.
SLF4J를 통해 다양한 로깅라이브러리와 연결되는 것을 볼수있다. 즉 Java가 JVM 위에서 동작하기 때문에 OS에 독립적이듯이, SLF4J를 사용하면 다양한 로깅라이브러리에 종속적이지 않게 코드를 작성 할 수있다. 만일 기존에 사용하던 log4j를 걷어내고 logback으로 교체하는 업무가 주어졌다고 가정해보자. 라이브러리를 교체한 순간 로그를 출력하던 코드는 모두 빨간줄이 뜨게 될것이고, 모두 수정해줘야 할것이다. 뿐만아니라 수많은 수정사항에 대해 commit을 해야하는 상황이 닥칠 것이다.
그러나 SLF4J를 사용하면, 설정에서 라이브러리만 바꿔주면 된다.
SLF4J의 구성과 작동방식
또한, 로깅라이브러리는 여러가지가 있지만, 가장 널리쓰이는 Log4j의 보완버전인 LogBack이 제일 좋다. 아래의 그림을 보자
SLF4J는 API,Binding,Bridge 3가지 모듈로 이루어져있다.
- API : 로깅 인터페이스로, 애플리케이션이은 API를 통해 로깅을 호출한다.
- Binding : 실제 사용하는 로깅라이브러리와 SlF4J를 묶어주는 역할, SLF4J의 API는 실제 로거라이브러리의 API를 호출해주며, 1개만 설정이 가능하다. 따로 설정하지 않을시 기본값으로 LogBack을 사용한다.
- Bridge : 레거시 코드를 위한 모듈로, 로그호출시, SLF4J 인터페이스로연결 한다. 그후 Binding에서 설정한 로깅라이브러리(LogBack)를 호출하게 된다. Bridge는 여러개를 설정해도 상관 없으며, Binding과 같은 값은 들어가면 안된다.
로깅 작성시의 주의사항
로깅은 읽는 대상들을 위해서 작성이 되어야한다.
그 대상으로는 기계와 개발자 둘이 있다. 그중 기계가 읽을때 주의사항은 아래와 같다.
- 한글이 아닌 영어로 작성한다.한글로 작성시 인코딩을 거쳐야 하므로 추가적인 작업이 소요된다. 반면 영어는 아스키코드로 바로 변환되므로 그런 소요가 없다.
- 파싱을 쉽게 할수있는 구분자를 추가해라만약 구분자를 따로 만들지 않았다면 정규표현식을 사용해서 파싱을 해야한다. 처음부터 파싱에 용이하게 구분자를 추가하자.
다음으로는 두번째 독자인 개발자를 위한 주의사항이다.
- 로그메세지는 자세하게 적는다.어떤 작업이 성공을 했는지? 에러가 났다면 어디에서 에러가 났는지??
- 로그메세지에 컨텍스트(맥락)을 담아라사람은 구조화 되있지 않은 데이터를 보고도 앞뒤상황을 생각할수 있다. 이때 발생한 에러의 앞뒤 상황, 맥락을 함께 적어주면 훨씬빠르게 왜 에러가 발생했는지 알수있으며, 이 에러에 대한 해결 방법까지 같이 적어주면 좋다.
- The Database is down
- Failed to get references for user id=1. Configuration DB is not responding. Will retry again in 5 minutes
- ex)
그외 주의사항
- 로그파일/DB의 생명주기 & 저장소 용량
- 개인정보
- 시스템 주요정보(시스템 보안 및 계정 정보)
로그를 활용하고 싶다면!
참고만 하자
- Sentry
- Elastic Search & Kibana
- Splunk
로그에 대한 세세한 설정이나, 사용법은 참조 사이트를 통해서 추후에 알아보자!
포스트 작성을 통해 알게된점
Reference
자바의신2
System.out.println()을 사용하지 말아야 하는 이유
StringBuilder vs System.out.println :: 마이구미
BoostCoure-웹프로그래밍풀스택-slf4j 설정하기
'프로그래밍 > Java' 카테고리의 다른 글
Lombok 파해쳐보기 - 1(미완) (0) | 2021.05.24 |
---|---|
ArrayList VS LinkedList (0) | 2021.05.21 |
상속과 컴포지션 (0) | 2021.05.20 |
java.lang -1 WrapperClass (0) | 2021.05.18 |
JAVA의 역사와 JVM-2 (0) | 2021.05.18 |
Uploaded by Notion2Tistory v1.1.0