Monday, March 16, 2009

Logging JSch with Apache Commons Logging

I recently created a Java application at work which used JSch for copying files via SFTP. For the most part, JSch has worked very well for my needs. However, I ran into some problems with the way it handles timeouts, and wanted to debug JSch's behavior. I use Apache Commons Logging (usually with Log4j) in all my applications, and was a bit disappointed to discover that JSch doesn't use Commons Logging, but instead uses it's a custom com.jcraft.jsch.Logger interface. I ended writing my own simple Logger class which wraps around Apache Commons Log. This works, but has a couple of caveats due to the limitations of JSch's logging design:
  • JSch uses a single instance of Logger which is shared by all JSch classes, so all messages logged by JSch classes are logged with a single class/category name.

  • JSch's Logger instance is a static property of the JSch class. Therefore, you can't configure configure different Logger instances for individual JSch connections.



import com.jcraft.jsch.Logger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* Class to route log messages generated by JSch to Apache Commons Logging.
*
* @author mrabbitt
* @see com.jcraft.jsch.Logger
*/
public class JSchCommonsLogger implements Logger {
private Log log;

/**
* Constructor with custom category name
*
* @param logName the category name used by this logger for Apache Commons.
*/
public JSchCommonsLogger(String logName) {
log = LogFactory.getLog(logName);
}

/**
* Default constructor
*/
public JSchCommonsLogger() {
this(Logger.class.getName());
}

/* (non-Javadoc)
* @see com.jcraft.jsch.Logger#isEnabled(int)
*/
public boolean isEnabled(int level) {
switch (level) {
case DEBUG:
return log.isDebugEnabled();
case INFO:
return log.isInfoEnabled();
case WARN:
return log.isWarnEnabled();
case ERROR:
return log.isErrorEnabled();
case FATAL:
return log.isFatalEnabled();
}
return false;
}

/* (non-Javadoc)
* @see com.jcraft.jsch.Logger#log(int, java.lang.String)
*/
public void log(int level, String message) {
switch (level) {
case DEBUG:
log.debug(message);
break;
case INFO:
log.info(message);
break;
case WARN:
log.warn(message);
break;
case ERROR:
log.error(message);
break;
case FATAL:
log.fatal(message);
break;
}
}

}


To use this, you'll need to do something like this somewhere in your code:


private static JSchCommonsLogger jschLogger = new JSchCommonsLogger();
com.jcraft.jsch.JSch.setLogger(jschLogger);


If you specify no argument to the JSchCommonsLogger() constructor, messages will be logged as com.jcraft.jsch. So, for example: If you're using Log4j, you can put something like this into a log4.properties to log errrors:

log4j.category.com.jcraft.jsch=ERROR

No comments:

Post a Comment