Showing posts with label logging. Show all posts
Showing posts with label logging. Show all posts

Friday, October 02, 2009

Using log4j's FallbackErrorHandler

Our applications currently use a DailyRollingFileAppender for logging, but since they run on NFS across a number of different servers, we quite often get errors due to stale NFS file handles, when log4j tries to write to the files. We sometimes also get errors if the logging mount point is missing on some of the servers.

I've been trying to find a way to switch to a different appender (such as a ConsoleAppender), if log4j fails to write to the log files. At first I thought of writing my own custom appender, to wrap up a FileAppender and a ConsoleAppender, and to switch to the ConsoleAppender if the FileAppender threw an IOException, but then I came across the FallbackErrorHandler, which allows you to configure a backup appender, which takes over if the primary appender fails for whatever reason.

This is how you can set up your log4j.xml file to use a FallbackErrorHandler:

1. Create a backup appender:
The backup appender will be used if the primary appender fails. My backup is a ConsoleAppender:

  <appender name="console" class="org.apache.log4j.ConsoleAppender">
    <param name="Target" value="System.out"/>
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d %-5p %30.30c - %m%n"/>
    </layout>
  </appender>
2. Add a FallbackErrorHandler to your primary appender:
My primary appender is a DailyRollingFileAppender. Add a FallbackErrorHandler to it and tell it to use the "console" (backup) appender, using the appender-ref tag. The root-ref tag refers to the logger that is currently using that appender. If you have a different logger use the logger-ref tag to refer to it instead.
  <appender name="file" class="org.apache.log4j.DailyRollingFileAppender">
  <errorHandler class="org.apache.log4j.varia.FallbackErrorHandler">
       <root-ref/>
       <appender-ref ref="console"/>
  </errorHandler>
    <param name="File" value="C:/temp/test.log"/>
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d %-5p %30.30c - %m%n"/>
    </layout>
  </appender>
3. Trying it out:
To test this works, make your log file read-only, or change the path of the file to one which doesn't exist. When you run your application, you will see log4j print an error to stderr, and start logging to console, instead of file. If you turn log4j debug on you will see the message: "FB: INITIATING FALLBACK PROCEDURE." before console logging begins.

The complete log4j.xml configuration:
Here is my complete config file. (I tried setting up a log4j.properties file, but ran into problems and wasn't able to.)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="console" class="org.apache.log4j.ConsoleAppender">
        <param name="Target" value="System.out" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d %-5p %30.30c - %m%n" />
        </layout>
    </appender>
    <appender name="file" class="org.apache.log4j.DailyRollingFileAppender">
        <errorHandler class="org.apache.log4j.varia.FallbackErrorHandler">
            <root-ref />
            <appender-ref ref="console" />
        </errorHandler>
        <param name="File" value="C:/temp/test.log" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d %-5p %30.30c - %m%n" />
        </layout>
    </appender>
    <root>
        <level value="INFO" />
        <appender-ref ref="file" />
    </root>
</log4j:configuration>

Friday, June 13, 2008

Change Logging Levels using JMX [Howto]

This handy example shows you how you can use Java Management Extensions (JMX) to change your application's logging level.

Step 1: Create your management interface which consists of all the attributes which can be read or set and all the operations that can be invoked. In this case, we want to change the logging level of our application.

public interface MyAppMBean{
    public void setLoggingLevel(String level) ;
    public String getLoggingLevel() ;
}
Step 2: Create a class which implements the MBean interface
import java.lang.management.ManagementFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import com.sun.jdmk.comm.HtmlAdaptorServer;

public class MyApp implements MyAppMBean{

    private static Logger logger = Logger.getLogger(MyApp.class);

    public void go() throws Exception{
        while(true){
            logger.debug("DEBUG") ;
            logger.info("INFO") ;
            Thread.sleep(2000);
        }
    }

    public void setLoggingLevel(String level){
        logger.info("Setting logging level to: " + level);
        Level newLevel = Level.toLevel(level, Level.INFO);
        Logger.getRootLogger().setLevel(newLevel);
    }

    public String getLoggingLevel(){
        return Logger.getRootLogger().getLevel().toString() ;
    }
}
Step 3: Register the MBean with the MBeanServer. Also register an HTMLAdaptorServer which allows us to manage an MBeanServer through a web browser.
public static void main(String[] args) throws Exception{
    MyApp app = new MyApp() ;
    ObjectName objName = new ObjectName("MyApp:name=MyApp");
    MBeanServer server = ManagementFactory.getPlatformMBeanServer();
    server.registerMBean(app, objName);

    int portNumber=9393;
    ObjectName htmlName = new ObjectName(
       "MyApp:name=MyAppHtmlAdaptor,port="+portNumber) ;
    HtmlAdaptorServer html = new HtmlAdaptorServer(portNumber);
    html.setPort(portNumber);
    server.registerMBean(html, htmlName);
    html.start();
    app.go();
}
Step 4: Compile. Make sure you have log4j, jmxtools and a log4j properties file in your classpath

Step 6: Run MyApp. You need to add the following JVM properties:

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
Step 5: Open jconsole
  • Click on MyApp and press Connect
  • Go to the MBeans tab and choose MyApp from the tree on the left
  • You can then change your Logging Level while the application is running!
Step 6: Open the web console
  • Go to http://localhost:9393
  • Click on name=MyApp
  • Change the LoggingLevel!