HaxElit
HaxElit

Reputation: 4073

Tomcat WAR - Configure Logback to use app name in path

I have logback deployed in my war file lib folder and I have the following logback.xml in the classes folder.

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
  <property name="destination" value="${catalina.base:-./temp}/logs/${appName:-myapp}" />

  <appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${destination}.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!-- rollover daily -->
      <fileNamePattern>${destination}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
      <!-- Keep logs for 7 days -->
      <maxHistory>7</maxHistory>

      <timeBasedFileNamingAndTriggeringPolicy
            class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
        <!-- or whenever the file size reaches 100MB -->
        <maxFileSize>100MB</maxFileSize>
      </timeBasedFileNamingAndTriggeringPolicy>
    </rollingPolicy>
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="error">
    <appender-ref ref="ROLLING" />
  </root>
</configuration>

On line 3 I have some variable substitutions that create the path for my log file.

  <property name="destination" value="${catalina.base:-./temp}/logs/${appName:-myapp}" />

I want to make it so ${appName} evaluates to the current name of the war file as deployed.

So if my web apps folder looked like so

webapps
 - myapp.war
 - myapp-dev.war

The ${destination} property for myapp.war would evaluate to .../logs/myapp and myapp-dev.war would evaluate to .../logs/myapp-dev. Is there a JNDI property or something I can access to access the appName ?

I would like to avoid having to manually reconfigure the logger.

Thanks!

Upvotes: 17

Views: 21483

Answers (4)

thomasmey
thomasmey

Reputation: 11

Isn't that the ContextSelector for? You may have a look at ContextJNDISelector and here http://logback.qos.ch/manual/contextSelector.html

Upvotes: 1

Michael-O
Michael-O

Reputation: 18415

EDIT 2013-06: I have made this Listener available as OSS on Maven Central. Check out the project homepage.

Yes, this is feasible. First of all, you can always rely on catalina.base because without it Tomcat won't run. In order to inject the context name as property. Write a context listener which will put the context name into the JNDI context and remove at shutdown. After you have done that, You can retrieve the value with JNDI directly with logback. There is direct support for that. Write that in the the contextName element and you're done.

I have implemented this on my own already and it works for all of my projects. I can share the entire code on monday if you or someone else is interested.

Edit, here is the code:

import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.deploy.ContextEnvironment;
import org.apache.commons.lang.StringUtils;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;

public class LogbackContextNameListener implements LifecycleListener {

    private static final Log logger = LogFactory
            .getLog(LogbackContextNameListener.class);
    private Context context;

    private String name = "logback/contextName";

    @Override
    public void lifecycleEvent(LifecycleEvent le) {

        if (le.getLifecycle() instanceof Context)
            context = (Context) le.getLifecycle();
        else
            return;

        if (le.getType().equals(Lifecycle.START_EVENT)) {
            ContextEnvironment ce = new ContextEnvironment();
            ce.setName(getName());
            ce.setOverride(false);
            ce.setType("java.lang.String");
            String value = StringUtils.remove(context.getServletContext()
                    .getContextPath(), '/');
            ce.setValue(value);
            logger.debug(String.format("Adding env entry '%s' with value '%s'",
                    getName(), value));
            context.getNamingResources().addEnvironment(ce);
        }

        if (le.getType().equals(Lifecycle.STOP_EVENT)) {
            logger.debug(String.format("Removing env entry '%s'", getName()));
            context.getNamingResources().removeEnvironment(name);
        }

    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        if (StringUtils.isEmpty(name))
            throw new IllegalArgumentException(
                    "Parameter 'name' cannot be empty");

        this.name = name;
    }

}

A suitable config looks like this:

<configuration scan="true" scanPeriod="30 minutes">

    <insertFromJNDI env-entry-name="java:comp/env/logback/contextName" as="contextName" />
    <contextName>${contextName}</contextName>

    <appender name="FILE"
        class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${catalina.base}/logs/${CONTEXT_NAME}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- daily rollover -->
            <fileNamePattern>${catalina.base}/logs/${CONTEXT_NAME}.log.%d.gz</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%-27(%d{HH:mm:ss.SSS} [%.-12thread]) %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="INFO"><!-- WARN -->
        <appender-ref ref="FILE" />
    </root>

</configuration>

This works flawlessly in Tomcat 6. I guess, it will run on Tomcat 7 w/o changes.

Upvotes: 12

Dawoon Yi
Dawoon Yi

Reputation: 436

maxHistory doesn't mean the number of log files. It means the number of months.

Upvotes: 0

Ceki
Ceki

Reputation: 27510

This builds on Michael-O's answer. Considering that catalina.base is always a defined system property when running under Tomcat, we only have to worry about defining appName. Logback offers support for retrieving variables from JNDI. If appName is defined in JNDI, your configuration file becomes:

<configuration>
  <!-- retrieve appName from JNDI to set the variable appName -->
  <insertFromJNDI env-entry-name="java:comp/env/appName" as="appName" />
  <!-- let the context name be the applicaiton name -->
  <contextName>${appName}</contextName>

  <property name="destination" 
            value="${catalina.base:-./temp}/logs/${CONTEXT_NAME:-myapp}" />

  <appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${destination}.log</file>
    ... remainder of config file omitted for brevity  
  </appender>
</configuration>

I'd like to mention that you could also just define appName directly in logback.xml instead of in JNDI. (After all, the logback.xml file ships with your web-app where its name is already established. However, your question explicitly excludes this hypothesis.) Thus, you logback.xml file could be simplified to:

<configuration>
  <contextName>the_name_of_your_webapp</contextName>
  <property name="destination" 
            value="${catalina.base:-./temp}/logs/${CONTEXT_NAME:-myapp}" />
   ... the rest omitted for brevity
</configuration? 

BTW, once you find a satisfactory solution, do not hesitate to share it by posting on logback-user list.

Upvotes: 5

Related Questions