Junit4 기반 테스트시 Log4 설정파일 위치 지정 방법이 있을까요?

질문에 답이 있었습니다.

질문의 내용은 웹 애플리케이션이 돌아갈 때 사용할 log4j 설정파일은 web.xml에서 설정할 수 있었는데, 테스트 할 때 사용할 설정 파일의 위치는 어떻게 설정할 수 있느냐 입니다. 저도 잘 모르겠어서 어떻게 해야 되지? 라는 고민을 하다가 가장 먼저 떠오른 방법은 문제를 우회하는 방법이었습니다. "왜 위치를 명시적으로 설정해야 되지?" 싶어서 말이죠.

그래서 생각한 방법이 소스 폴더에 설정 파일을 두는 방법입니다. Maven이 아닐 경우 보통 src와 test 라는 소스 폴더를 만들어서 사용할텐데요. 그 경우 둘 중 아무곳에나 설정 파일을 두기만 하면 output 폴더(이클립스 자바 프로젝트 기본 outpu 폴더는 bin폴더)로 이동하게 됩니다. 그럼 그 파일을 log4j가 찾아서 사용하죠. Maven일 경우네는 src/main/resouces에 두거나 src/test/resource에 두면 됩니다.

그러나 Maven일 때 주의 할 것이 있는데, 바로 해당 폴더 루트에 위치 시켜야 한다는 겁니다. src/main/recouces에 두지 않고 그 안에 새로운 폴더 하나를 만들어서 두면 그건 소스 폴더로 인식하지 않습니다. 따라서 log4j가 설정파일을 못찾게 되죠.

결론적으로 명시적으로 log4j 설정파일 위치를 설정해주고 싶습니다. 굳이 소스폴더가 아니여도 참조할 수 있게 말이죠. 그래서 살펴본 것이 web.xml에 등록되어 있는 org.springframework.web.util.Log4jConfigListener 이 클래스 입니다. 분명 저 클래스가 설정 파일 위치 정보를 가져가니까 어디선가는 그 파일을 읽어서 설정하겠죠.

그래서 Log4jConfigListener 이 클래스를 찾아갔더니 별로 코드가 없습니다.

public class Log4jConfigListener implements ServletContextListener {

    public void contextInitialized(ServletContextEvent event) {
        Log4jWebConfigurer.initLogging(event.getServletContext());
    }

    public void contextDestroyed(ServletContextEvent event) {
        Log4jWebConfigurer.shutdownLogging(event.getServletContext());
    }

}

이 코드에서 사용하고 있는 Log4jWebConfigurer를 찾아갔습니다. 그리고 그 안에 있는 initLogging 메소드 안에서 다음의 코드를 발견했습니다.

Log4jConfigurer.initLogging(location, refreshInterval);

빙고!! 게임은 끝났습니다.

유겐 휄러가 2003년에 만든 클래스더군요. Log4jConfigurer 클래스를 감상하면서.. 참 멋지다는 생각을 했습니다.

/*
 * Copyright 2002-2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.net.URL;

import org.apache.log4j.LogManager;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.xml.DOMConfigurator;

/**
 * Convenience class that features simple methods for custom log4j configuration.
 *
 * <p>Only needed for non-default log4j initialization, for example with a custom
 * config location or a refresh interval. By default, log4j will simply read its
 * configuration from a "log4j.properties" or "log4j.xml" file in the root of
 * the classpath.
 *
 * <p>For web environments, the analogous Log4jWebConfigurer class can be found
 * in the web package, reading in its configuration from context-params in
 * <code>web.xml</code>. In a J2EE web application, log4j is usually set up
 * via Log4jConfigListener or Log4jConfigServlet, delegating to
 * Log4jWebConfigurer underneath.
 *
 * @author Juergen Hoeller
 * @since 13.03.2003
 * @see org.springframework.web.util.Log4jWebConfigurer
 * @see org.springframework.web.util.Log4jConfigListener
 * @see org.springframework.web.util.Log4jConfigServlet
 */

public abstract class Log4jConfigurer {

    /** Pseudo URL prefix for loading from the class path: "classpath:" */
    public static final String CLASSPATH_URL_PREFIX = "classpath:";

    /** Extension that indicates a log4j XML config file: ".xml" */
    public static final String XML_FILE_EXTENSION = ".xml";

    /**
     * Initialize log4j from the given file location, with no config file refreshing.
     * Assumes an XML file in case of a ".xml" file extension, and a properties file
     * otherwise.
     * @param location the location of the config file: either a "classpath:" location
     * (e.g. "classpath:myLog4j.properties"), an absolute file URL
     * (e.g. "file:C:/log4j.properties), or a plain absolute path in the file system
     * (e.g. "C:/log4j.properties")
     * @throws FileNotFoundException if the location specifies an invalid file path
     */
    public static void initLogging(String location) throws FileNotFoundException {
        String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location);
        URL url = ResourceUtils.getURL(resolvedLocation);
        if (resolvedLocation.toLowerCase().endsWith(XML_FILE_EXTENSION)) {
            DOMConfigurator.configure(url);
        }
        else {
            PropertyConfigurator.configure(url);
        }
    }

    /**
     * Initialize log4j from the given location, with the given refresh interval
     * for the config file. Assumes an XML file in case of a ".xml" file extension,
     * and a properties file otherwise.
     * <p>Log4j's watchdog thread will asynchronously check whether the timestamp
     * of the config file has changed, using the given interval between checks.
     * A refresh interval of 1000 milliseconds (one second), which allows to
     * do on-demand log level changes with immediate effect, is not unfeasible.
     * <p><b>WARNING:</b> Log4j's watchdog thread does not terminate until VM shutdown;
     * in particular, it does not terminate on LogManager shutdown. Therefore, it is
     * recommended to <i>not</i> use config file refreshing in a production J2EE
     * environment; the watchdog thread would not stop on application shutdown there.
     * @param location the location of the config file: either a "classpath:" location
     * (e.g. "classpath:myLog4j.properties"), an absolute file URL
     * (e.g. "file:C:/log4j.properties), or a plain absolute path in the file system
     * (e.g. "C:/log4j.properties")
     * @param refreshInterval interval between config file refresh checks, in milliseconds
     * @throws FileNotFoundException if the location specifies an invalid file path
     */
    public static void initLogging(String location, long refreshInterval) throws FileNotFoundException {
        String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location);
        File file = ResourceUtils.getFile(resolvedLocation);
        if (!file.exists()) {
            throw new FileNotFoundException("Log4j config file [" + resolvedLocation + "] not found");
        }
        if (resolvedLocation.toLowerCase().endsWith(XML_FILE_EXTENSION)) {
            DOMConfigurator.configureAndWatch(file.getAbsolutePath(), refreshInterval);
        }
        else {
            PropertyConfigurator.configureAndWatch(file.getAbsolutePath(), refreshInterval);
        }
    }

    /**
     * Shut down log4j, properly releasing all file locks.
     * <p>This isn't strictly necessary, but recommended for shutting down
     * log4j in a scenario where the host VM stays alive (for example, when
     * shutting down an application in a J2EE environment).
     */
    public static void shutdownLogging() {
        LogManager.shutdown();
    }

    /**
     * Set the specified system property to the current working directory.
     * <p>This can be used e.g. for test environments, for applications that leverage
     * Log4jWebConfigurer's "webAppRootKey" support in a web environment.
     * @param key system property key to use, as expected in Log4j configuration
     * (for example: "demo.root", used as "${demo.root}/WEB-INF/demo.log")
     * @see org.springframework.web.util.Log4jWebConfigurer
     */
    public static void setWorkingDirSystemProperty(String key) {
        System.setProperty(key, new File("").getAbsolutePath());
    }

}

유틸 클래스인데, abstract로 해놨습니다. 인스턴스 만들지 말라는거죠. 멋지지 않나요. 캬캬. 유틸 클래스를 보통 무심하게 그냥 public class로 해놓기 일수인데, abstract라는 키워드 하나가 정말 멋지게 느껴지지 않나요. 그리고 저 충실한 JavaDoc 코멘트들.. 정말 유겐은 대단합니다.

그러니까... 저런 코드를 출처(스프링 프로젝트 홈피 링크 덜렁 남기는게 아니라, 저 클래스나 javadoc URL을 걸어놔야 출처를 명시했다고 생각합니다. 그게 보통 말하는 출처 아닌가요?? 장난이 아니고서야 누가 대체 출처를 www.google.com 이라고 달죠?)도 없이 원작자 이름도 빼고 베끼는 코드를 보면, 어떻게 흥분을 안 하겠습니까.. 에흄.. 유겐은 참 착하기도 하지.

얘기가 좀 샜는데, 어쨌거나 저 클래스를 잘 이용하면 이 글의 제목에 대한 답은 된 것 같습니다. 역시나 멋진 스프링은 아직도 공부할 것들이 무궁무진 합니다. 특히 전 소스코드는 자세히 들여본적이 거의 없는데, 저 코드를 보고나니까 좀 관심이 갑니다. 쉬운 클래스 부터 하나씩 갈펴봐야겠습니다. BeanFactory나 ApplicationContext는 넘 어려워서 원~