80

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?

Iker Jimenez
  • 7,105
  • 9
  • 49
  • 46
  • 3
    This is really only half the question. Having a dynamic path for the log file is great, but what about the config file itself. If it is just the log file location that is dynamic then you would have the same log levels in all the places this is deployed, and I don't imagine that is desirable. I would like to know the best approach for dynamically specifying the config so my dev environment could log at DEBUG and prod at INFO/WARN. What do you think? – Lucas Oct 07 '11 at 11:24

10 Answers10

101

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
Patrick
  • 33,984
  • 10
  • 106
  • 126
Steve K
  • 19,408
  • 6
  • 52
  • 50
  • 2
    I honestly don't see what advantage this approach has over using the Listener I've explained in my answer.I don't care what container it is, it will just work no matter where I deploy it whereas in your approach I have to change a value if I change the environment. – Iker Jimenez Oct 20 '08 at 16:01
  • 4
    Both solutions are using system properties, we are just setting them differently. It really just depends, on flexibility versus simplicity. We manage all the Tomcat servers that our application runs on, so I like the flexibility. If your distributing a war for 3rd party use, simplicity makes sense – Steve K Oct 20 '08 at 16:39
55

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?

Iker Jimenez
  • 7,105
  • 9
  • 49
  • 46
  • 4
    This is a good idea, but I think catalina.home might be safer to use as it will always be set/available before any log4j code initializes. – matt b Oct 20 '08 at 13:59
  • 7
    That would be true if I was only using Tomcat, but my requirement is that it has to work on any container with 0 configuration. My approach fulfills this and nobody has so far proposed a better approach for this. – Iker Jimenez Oct 20 '08 at 16:03
  • 1
    This looks like the best portable approach to me. The only thing better than simple configuration is no configuration at all. – Kyle W. Cartmell May 31 '10 at 16:15
  • 2
    This solution may work for Web applications that use Servlets, but Steve K's solution (http://stackoverflow.com/questions/216781/log4j-configuring-a-web-app-to-use-a-relative-path/216805#216805) works for any application that uses Log4j. – Derek Mahar Jun 13 '10 at 11:38
  • 2
    Spencer K's solution, also based on relative paths, works for any application that uses Log4j, assuming that you set the base directory to a predictable path. – Derek Mahar Jun 13 '10 at 11:55
  • 1
    As I said in the question, you cannot assume that we are deploying on a Tomcat here. Cannot use ${catalina.home} then. – Iker Jimenez Jun 16 '10 at 14:25
  • 12
    This solution only works if you have a single WebApp configured to use it because the System properties are global to tomcat a second app starting up would overwrite the value that the first webapp set. You could give each webapp a unique property name but if you are going to do that then you may as well just use ${catalina.home} and add the unique part of the path in the log4j.properties file as it is less error prone. – 3urdoch Oct 15 '10 at 10:27
  • 1
    ${catalina.home} on Linux is usually /var/log/tomcat6/. As long as you're OK writing your logs to a non standard spot like /var/lib/tomcat6/webapps//logs/ then it's fine. – Leif Gruenwoldt Aug 13 '12 at 20:01
  • 1
    Note that if you're running from a WAR (i.e. not unpacked) getRealPath may not return a usable path. – David Carboni Nov 13 '12 at 07:34
  • 1
    I am using with spring. It works only if I add this listener before other spring listener(ContextLoaderListener and RequestContextListener). Let me know if you know??? – swemon Jan 16 '13 at 06:44
  • 1
    using this solution, what's happening if you deploy a new war ? your logs will be overwritten. Then it's not a good solution for preprod or production mode because you loose the historic of your webapp logs on each deployment. – Nico Oct 13 '15 at 14:52
14

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)

2) Specify config location in web.xml:

<context-param>
  <param-name>log4jConfigLocation</param-name>
  <param-value>/WEB-INF/classes/log4j-myapp.properties</param-value>
</context-param>

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

<context-param>
  <param-name>webAppRootKey</param-name>
  <param-value>myWebapp-instance-root</param-value>
</context-param>

4) Add a Log4jConfigListener:

<listener>
  <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>

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

Dimitri De Franciscis
  • 1,022
  • 11
  • 20
6

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.

Community
  • 1
  • 1
lizi
  • 69
  • 1
  • 2
5

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.

Spencer Kormos
  • 8,381
  • 3
  • 28
  • 45
  • 4
    From what I've seen it can use the user's home directory, or the container's home directory if you don't give an absolute path.Unreliable. – Iker Jimenez Oct 20 '08 at 16:06
  • 1
    @Iker: Why can't you just explicitly set the application root directory in your container or application configuration? Once you've done that once in development and production, you can use relative paths reliably. Assuming the root directory is set correctly, relative paths are the most portable (re-locatable) solution. – Derek Mahar Jun 13 '10 at 11:50
  • 1
    I know this is an old post but I wanted to make a note on this for others. For tomcat if the application root directory isn't explicitly set I believe it defaults to whichever directory you started tomcat from. – Geren White Jul 07 '13 at 14:57
2

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.

Community
  • 1
  • 1
Wolfgang Liebich
  • 400
  • 2
  • 12
1

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.

alex.antaniuk
  • 69
  • 1
  • 3
1

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(...)
        }
        ...
    }
    
Community
  • 1
  • 1
starikoff
  • 1,601
  • 19
  • 23
1

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.

Dimitar II
  • 2,299
  • 32
  • 33
1

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.

Leigh
  • 28,765
  • 10
  • 55
  • 103
user553633
  • 11
  • 1