Log4j, configuring a Web App to use a relative path

JavaWeb ApplicationsLog4j

Java Problem Overview


I have a java webapp that has to be deployed on either Win or Linux machines. I now want to add log4j for logging and I'd like to use a relative path for the log file as I don't want to change the file path on every deployment. The container will most likely be Tomcat but not necessarily.

What's the best way of doing this?

Java Solutions


Solution 1 - Java

Tomcat sets a catalina.home system property. You can use this in your log4j properties file. Something like this:

log4j.rootCategory=DEBUG,errorfile

log4j.appender.errorfile.File=${catalina.home}/logs/LogFilename.log

On Debian (including Ubuntu), ${catalina.home} will not work because that points at /usr/share/tomcat6 which has no link to /var/log/tomcat6. Here just use ${catalina.base}.

If your using another container, try to find a similar system property, or define your own. Setting the system property will vary by platform, and container. But for Tomcat on Linux/Unix I would create a setenv.sh in the CATALINA_HOME/bin directory. It would contain:

export JAVA_OPTS="-Dcustom.logging.root=/var/log/webapps"

Then your log4j.properties would be:

log4j.rootCategory=DEBUG,errorfile

log4j.appender.errorfile.File=${custom.logging.root}/LogFilename.log

Solution 2 - Java

I've finally done it in this way.

Added a ServletContextListener that does the following:

public void contextInitialized(ServletContextEvent event) {
	ServletContext context = event.getServletContext();
	System.setProperty("rootPath", context.getRealPath("/"));
}

Then in the log4j.properties file:

log4j.appender.file.File=${rootPath}WEB-INF/logs/MyLog.log

By doing it in this way Log4j will write into the right folder as long as you don't use it before the "rootPath" system property has been set. This means that you cannot use it from the ServletContextListener itself but you should be able to use it from anywhere else in the app.

It should work on every web container and OS as it's not dependent on a container specific system property and it's not affected by OS specific path issues. Tested with Tomcat and Orion web containers and on Windows and Linux and it works fine so far.

What do you think?

Solution 3 - Java

If you use Spring you can:

  1. create a log4j configuration file, e.g. "/WEB-INF/classes/log4j-myapp.properties" DO NOT name it "log4j.properties"

Example:

log4j.rootLogger=ERROR, stdout, rollingFile

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n

log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender
log4j.appender.rollingFile.File=${myWebapp-instance-root}/WEB-INF/logs/application.log
log4j.appender.rollingFile.MaxFileSize=512KB
log4j.appender.rollingFile.MaxBackupIndex=10
log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.rollingFile.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.rollingFile.Encoding=UTF-8

We'll define "myWebapp-instance-root" later on point (3)

  1. Specify config location in web.xml:

    log4jConfigLocation /WEB-INF/classes/log4j-myapp.properties

  2. Specify a unique variable name for your webapp's root, e.g. "myWebapp-instance-root"

    webAppRootKey myWebapp-instance-root

  3. Add a Log4jConfigListener:

    org.springframework.web.util.Log4jConfigListener

If you choose a different name, remember to change it in log4j-myapp.properties, too.

See my article (Italian only... but it should be understandable): http://www.megadix.it/content/configurare-path-relativi-log4j-utilizzando-spring

UPDATE (2009/08/01) I've translated my article to English: http://www.megadix.it/node/136

Solution 4 - Java

Just a comment on Iker's solution.

ServletContext is a good solution for your problem. But I don't think it is good for maintains. Most of the time log files are required to be saved for long time.

Since ServletContext makes the file under the deployed file, it will be removed when server is redeployed. My suggest is to go with rootPath's parent folder instead of child one.

Solution 5 - Java

Doesn't log4j just use the application root directory if you don't specify a root directory in your FileAppender's path property? So you should just be able to use:

log4j.appender.file.File=logs/MyLog.log

It's been awhile since I've done Java web development, but this seems to be the most intuitive, and also doesn't collide with other unfortunately named logs writing to the ${catalina.home}/logs directory.

Solution 6 - Java

As a further comment on https://stackoverflow.com/a/218037/2279200 - this may break, if the web app implicitely starts other ServletContextListener's, which may get called earlier and which already try to use log4j - in this case, the log4j configuration will be read and parsed already before the property determining the log root directory is set => the log files will appear somewhere below the current directory (the current directory when starting tomcat).

I could only think of following solution to this problem:

  • rename your log4j.properties (or logj4.xml) file to something which log4j will not automatically read.
  • In your context filter, after setting the property, call the DOM/PropertyConfigurator helper class to ensure that your log4j-.{xml,properties} is read
  • Reset the log4j configuration (IIRC there is a method to do that)

This is a bit brute force, but methinks it is the only way to make it watertight.

Solution 7 - Java

My suggestion is the log file should always be logged above the root context of the webApp, so in case we redeploy the webApp, we don't want to override the existing log files.

Solution 8 - Java

In case you're using Maven I have a great solution for you:

  1. Edit your pom.xml file to include following lines:

    <profiles>
        <profile>
            <id>linux</id>
            <activation>
                <os>
                    <family>unix</family>
                </os>
            </activation>
            <properties>
                <logDirectory>/var/log/tomcat6</logDirectory>
            </properties>
        </profile>
        <profile>
            <id>windows</id>
            <activation>
                <os>
                    <family>windows</family>
                </os>
            </activation>
            <properties>
                <logDirectory>${catalina.home}/logs</logDirectory>
            </properties>
        </profile>
    </profiles>
    

Here you define logDirectory property specifically to OS family. 2. Use already defined logDirectory property in log4j.properties file:

    log4j.appender.FILE=org.apache.log4j.RollingFileAppender
    log4j.appender.FILE.File=${logDirectory}/mylog.log
    log4j.appender.FILE.MaxFileSize=30MB
    log4j.appender.FILE.MaxBackupIndex=10
    log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
    log4j.appender.FILE.layout.ConversionPattern=%d{ISO8601} [%x] %-5p [%t] [%c{1}] %m%n

3. That's it!

P.S.: I'm sure this can be achieved using Ant but unfortunately I don't have enough experience with it.

Solution 9 - Java

My solution is similar to Iker Jimenez's solution, but instead of using System.setProperty(...) I use org.apache.log4j.PropertyConfigurator.configure(Properties). For that I also need log4j to be unable to find its configuration on its own and I load it manually (both points described in Wolfgang Liebich's answer).

This works for Jetty and Tomcat, standalone or run from IDE, requires zero configuration, allows to put each app's logs in their own folder, no matter how many apps inside the container (which is the problem with the System-based solution). This way one can also put the log4j config file anywhere inside the web app (e.g. in one project we had all config files inside WEB-INF/).

Details:

  1. I have my properties in the log4j-no-autoload.properties file in the classpath (e.g. in my Maven project it's originally in src/main/resources, gets packaged into WEB-INF/classes),

  2. It has the file appender configured as e.g.:

    log4j.appender.MyAppFileAppender = org.apache.log4j.FileAppender
    log4j.appender.MyAppFileAppender.file = ${webAppRoot}/WEB-INF/logs/my-app.log
    ...
    
  3. And I have a context listener like this (gets much shorter with Java 7's "try-with-resource" syntax):

    @WebListener
    public class ContextListener implements ServletContextListener {
        @Override
        public void contextInitialized(final ServletContextEvent event) {
            Properties props = new Properties();
            InputStream strm =
                ContextListener.class.getClassLoader()
                    .getResourceAsStream("log4j-no-autoload.properties");
            try {
                props.load(strm);
            } catch (IOException propsLoadIOE) {
                throw new Error("can't load logging config file", propsLoadIOE);
            } finally {
                try {
                    strm.close();
                } catch (IOException configCloseIOE) {
                    throw new Error("error closing logging config file", configCloseIOE);
                }
            }
            props.put("webAppRoot", event.getServletContext().getRealPath("/"));
            PropertyConfigurator.configure(props);
            // from now on, I can use LoggerFactory.getLogger(...)
        }
        ...
    }
    

Solution 10 - Java

You can specify relative path to the log file, using the work directory:

appender.file.fileName = ${sys:user.dir}/log/application.log

This is independent from the servlet container and does not require passing custom variable to the system environment.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionIker JimenezView Question on Stackoverflow
Solution 1 - JavaSteve KView Answer on Stackoverflow
Solution 2 - JavaIker JimenezView Answer on Stackoverflow
Solution 3 - JavaDimitri De FranciscisView Answer on Stackoverflow
Solution 4 - JavaliziView Answer on Stackoverflow
Solution 5 - JavaSpencer KormosView Answer on Stackoverflow
Solution 6 - JavaWolfgang LiebichView Answer on Stackoverflow
Solution 7 - Javauser553633View Answer on Stackoverflow
Solution 8 - Javaalex.antaniukView Answer on Stackoverflow
Solution 9 - JavastarikoffView Answer on Stackoverflow
Solution 10 - JavaDimitar IIView Answer on Stackoverflow