Whiteship's Note


Logging/StaticLog

Java : 2008.05.09 22:00


참조 : http://wiki.apache.org/jakarta-commons/Logging/StaticLog

로깅을 보통 두 가지 방법 중 하나를 사용할 것이다.

public class Foo {
  private Log log = LogFactory.getLog(Foo.class);
  ....
}

그리고

public class Foo {
  private static Log log = LogFactory.getLog(Foo.class);
  ....
}

static 키워드를 사용하는 것이 특정 상황에서는 이익이 될 수도 있지만, 그렇지 않은 경우 예상치 못한 문제를 발생시킬 수 있다. 여기서 각각이 어떤 경우에 유용한지 살펴본다.

Static 문제

static 사용의 기술적인 결과는 매우 명백하다. 해당 클래스 타입의 여러 객체들 사이에서 단일 Log 레퍼런스를 공유해서 사용한다. 분명히 메모리를 효율적으로 사용할 수 있다. 딱 하나의 레퍼런스(4 또는 8 바이트)만 있으면 몇 개의 객체를 생성하든 아무 상관없다. 그리고 CPU 효율도 좋다. 왜냐면 Log 인스턴스를 딱 한 번만 찾아서 가져오면Look up 되기 때문이다.

독립적인 애플리케이션 코드에서 static을 사용하는 괜찮은 생각이다.

하지만 자바 코드를 라이브러리 형태로 J2EE 서버같은 컨테이너에 배포해야 한다면 문제가 발생할 수 있다. 보통 컨테이너는  클래스 로더 계층 구조를 가지고 있다. 배포된 애플리케이션 마다 자바 ClassLoader 객체 하나씩과 컨테이너에 배포된 모든 애플리케이션들이 공유하는 클래스로더의 최상위 클래스로더Ancestry ClassLoader로 공유 클래스로더를 가지고 있다. 이때 애플리케이션 수준의(j2ee 컨테이너 또는 서블릿의 "webapp" 수준) 클래스 로더에서 Log 객체에 대한 static 레퍼런스를 참조하면 아무런 문제가 없다. 만약 여러 개의 애플리케이션이 해당 클래스를 배포하더라도 그들 각각의 복사체를 가지고 있게 될 뿐 애플리케이션 사이에서는 아무런 문제가 없다.

하지만 "private static Log log = ..."식의 코드를 사용하는 클래스가 여러 독립적인 애플리케이션들의 최상위 클래스로더에 배포될 수도 있다. 이 경우, log 멤버 변수는 딱 한 번만 초기화 된다. 왜냐면 클래스 로더는 딱 하나의 클래스 복사체만 가질 수 있으니까. 이 초기화는 해당 클래스의 객체를 만들려고 하거나 static 메소드를 호출할 때 한다. 클래스 초기화가 발생할 때 log 멤버는 어떤 값으로 설정 될까?
  • 각각의 "애플리케이션"에 상관 없이 "컨네이너" 레벨에 있는 계층 구조의 일부에 있는 Log 객체에 대한 레퍼런스
  • 현재 애플리케이션과 관계 있는 계층 구조의 일부에 있는 Log 객체에 대한 레퍼런스
  • log 메소드를 호출할 때마다 "현재 애플리케이션"이 무엇인지 판단하고 그에 해당하는 Log 객체로 위임하는 "프록시" 객체에 대한 레퍼런스
첫 번째 옵션은 로깅이 각각 애플리케이션 수준으로 설정되지 못한다는 것을 뜻한다. 로깅 설정이 뭐라고 설정되어 있건간에 모든 애플리케이션이 동일한 Log 레퍼런스를 사용하는 것이다. 그럼 모든 애플리케이션의 로그정보가 섞여서 출력될 것이다. 이게 가장 큰 문제다.

두 번째 옵션은 Log 객체가 자기를 맨 처음 호출한 애플리케이션에 설정된다는 것을 뜻한다. 컨테이너에 들어있는 다른 애플리케이션들의 로그 메시지는 모두 첫 번째 애플리케이션이 설정해둔 목적지로 보낼 것이다. 이것도 분명히 큰 문제다.

세번째 옵션은 각각의 애플리케이션 로깅 설정을 허용하고 각각의 필요한 로그 메시지들을 바르게 출력하고 필터링 한다. 하지만 성능 문제가 심각하다. 이것도 그다지 허용할 만한 방법이 아니다.

이 문서에서 "쓰레드 컨텍스트 클래스로더"나 다른 기술적인 문제는 언급하고 있지 않다. 문제는 그렇게 자세한 부분이 아니라 일반적인 개념에 있다. Log 객체를 여러 애플리케이션에서 공유할 때 애플리케이션 마다의 설정을 적용하는 것이 불가능하며 타당한 성능을 보장할 수 없다.

이 모든 문제의 진짜 핵심은 독립적이어야 하는 "애플리케이션"들에서 공유하는 데이터(클래스에 있는 static 멤버)가 있다는 것이다. 애플리케이션들 사이에 공유하는 클래스가 없다면 아무런 문제도 없다. 하지만 컨테이너 밴더들은 계속해서 공유 클래스를 쓰길 권하고 개발자들은 계속 그렇게 사용하고 있다.(이건 저자의 개인적인 생각)

그 해결책은 공유 클래스패스에 배포될 가능성이 있는 코드에서 Log 객체에 대한 "static" 레퍼런스를 사용하지 않는 것이다.

SLF4J 도 이런 문제를 가지고 있는가?

그렇다. SLF4J도 위에 나열한 것과 동일한 문제를 겪고 있으며 권장하는 방법은 동일하다. 공유 클래스패스에 배포될 코드에서는 log 객체에 대한 static 레퍼런스를 사용하지 말아라.

...중략...

java.util.logging도 이 문제를 가지고 있는가?

그렇다. java.util.logging API도 위에 나열한 것과 동일한 문제를 겪고 있으며 권장하는 방법은 동일하다. 공유 클래스패스에 배포될 코드에서는 log 객체에 대한 static 레퍼런스를 사용하지 말아라.

...중략...

Static logger 대안책

static을 떼어내라.

...중략...

그럼 static 메소드는 어떻게 하냐?

  public static void doStuff(...) {
    Log log = LogFactory.getLog("stuff");
    log.warn(...);
  }

...중략...

컨테이너가 지원하는 로깅

몇몇 컨테이너는 위의 문제를 해결하려고 노력했다. 특히 JBoss는 커스텀 log4j 필터를 제공해서 어쩌구 저쭈구 생략..

기존의 라이브러리 코드 수정하기

이 문서를 읽고나서 static Log를 static이 아닌 Log 객체를 사용하도록 고치려는 시도를 할 수 있는데, 이 때 주의할 것이 있다. 클래스의 직렬화 형식이 바뀐다는 것이다. 클래스의 serialVersionID를 꼭 바꿔주어라. 그래야 옛날 클래스 대신 새 클래스를 로딩할 것이다.

다음과 같이 스레드 세이프 싱글톤 객체를 사용하도록 할 수 있음.

  private transient Log log;
  private Log getLog() {
    if (log == null)
      log=LogFactory.getLog(Some.class);
    return log;
  }

  // old code
  // log.debug("foo");

  // new code
  getLog().debug("foo");

또다른 대안으로는 readResolve 메소드를 만들어서 역직렬화 시에 자동으로 호출하게 할 수도 있다.

  private transient Log log = LogFactory.getLog(Some.class);
  private Object readResolve() {
     log = LogFactory.getLog(Some.class);
     return this;
  }


====================================

아 어려워.
어쨋거나 static Log 쓰지 말라는 것..
JEE 컨테이너들의 클래스 로더 구조 땜시 희한한 문제가 발생할 수 있음.
top


객체지향의 구멍 static

Java : 2006.11.04 23:44


DAUM DNA 개발자네트워크 beta기술자료 게시판에 Java와 관련된 글 세개가 올라왔습니다.
이 세개의 글 중에 두번째 글을 보며 몰랐던 부분을 정리하려고 합니다. 그 중에 가장 처음으로 static에 관한 부분입니다.

안그래도 몇일 전 static scope의 멤버는 되도록이면 사용하지 않는 것이 좋다는 문구를 Agile Java 4장에서 발견하고 그 이유를 찾아봤더니 객체지향 적이지 않아서라고 적혀 있었습니다. 어떤 점에서 객체 지향 적이지 않다고 하는 것인지 궁금하여 커뮤니티에 글을 올렸고 만족스런 답변을 들을 수 있었습니다. 그러던 중 이 글을 만나게 되어 더욱 반갑게 느껴졌습니다.

static method는 this가 없다. instance method에는 숨겨진 파라미터로 this가 건네진다. (아래"객체지향에 흔희 있는 오해" 참고) 하지만 static method는 절차지향의 함수와 동일하므로 숨겨진 파라미터 this는없다. 그래서 static method에서는 전달한 this가 없으므로 instance method를 호출하거나 instancefield를 참조할 수 없는 것이다.

몰랐던 사실입니다. 그래서 class안에 있는 어떤 메소드에서도 this라는 키워드를 사용할 수 있었던 것이군요. 그렇다면 supre라는 키워드 또한 건네지지 않을런지... 궁금해 지는 군요.

우리는 흔히 "글로벌 변수는 될수있는한 사용하지 않는 것이 좋다"라고 한다. 그 이유는 글로벌 변수는 어디서든 참조할 수 있고 값을 변경할 수 있기 때문이다.

따라서 static에서 public은 final을 붙여 상수로 사용해야지 그 외의 용도는 자제하는 것이 좋을 것이다.

그렇군요. agile java 4장에서 말하고 있는 내용과 거의 일치하고 있습니다. 그리고 여기서는 그에 대한 해결책으로 final을 붙여 상수로 사용할 때만으로 용도를 제한하고 있군요.

final 키워드가 나오면서 final을 사용할 때 주의할 것이 나옵니다. final Date endDate = new Date(); 여기서 endDate가 가리키는 객체가 바뀔 수 없다는 것이지 객체의 내용은 바뀔 수 가 있다는 것입니다. (이 부분은 레퍼런스의 위험성에서도 잠깐 언급이 됐었습니다.)그럼에도 불구하고 종종 객체의 내용이 바뀌지 않을 것으로 예상하게 되는 착각을 하게 될 때가 있지요. 그것을 지적하는 내용이였습니다.

static을 사용할 때 기억해야 할 두 가지
  1. static field는 final의 경우와 달리 정말 "하나여도 되는지" 여부를 잘 생각해야 한다.
  2. static method는 주저하지 말고 쓰되 다음 두가지의 경우 매우 활용적이다.
    1. 다른 많은 클래스에서 사용하는 Utility Method 군을 만드는 경우. (주로 Utility Class의 method)
    2. 클래스 안에서만 사용하는 "하청 메소드(private method)". 이유를 예를 들어 설명하면, 아래와 같은 조금은 과장된 클래스가 있다고 하자.

그리고 마지막으로 하청 메소드의 예를 보여 주고 있는데 인상 적입니다.

private static 으로 선언된 메소드를 사용하면 scope이 이 메소드를 가지고 있는 class로 제한이 되고 다른 인스턴스 변수들을 안전하게 사용할 수 있는 장점이 생기네요.


'Java' 카테고리의 다른 글

XML을 EXEL 파일로 바꾸기  (0) 2006.11.08
Enumeration & Iterator  (2) 2006.11.07
Auto (un)boxing은 -128~127  (4) 2006.11.07
탬플릿 클레스 만들기  (3) 2006.11.05
local inner class의 예  (0) 2006.11.05
객체지향의 구멍 static  (0) 2006.11.04
i++와 i=i+1 속도 비교  (0) 2006.11.02
Comparator를 사용하여 비교하기.  (0) 2006.11.02
Arrays.sort() & Collections.sort()  (2) 2006.11.02
What is Object?  (12) 2006.11.01
Agile Java 2장 연습문제 풀기  (2) 2006.10.29
top

TAG static