
Hello, I've written a Logback Chainsaw Bridge: your programs can use logback and you can receive the events in Chainsaw using the SImpleReceiver. The idea is pretty simple: * You specify a "ch.qos.logback.classic.net.SocketAppender" in logback.xml * LogbackChainsawBridge reads incoming logback events from the wire * LogbackChainsawBridge converts the events to Log4j events * LogbackChainsawBridge sends the events to chainsaw using a SocketAppender It's certainly not a perfect solution, I hope there will be a cool LogBackViewer someday that can compete with chainsaw. (maybe when I have lots of time...) Or does it exist already ? In the meantime, some of you might find it interesting. The following attributes are sent to chainsaw: * ThreadName * Logger * Timestamp * Message * Level * MDC * CallerData (LocationInfo) * Throwable Feedback welcome. Maarten package ch.org.logback; import java.net.ServerSocket; import java.net.Socket; import java.io.ObjectInputStream; import java.io.BufferedInputStream; import java.io.IOException; import java.io.EOFException; import java.util.Map; import java.util.Hashtable; /** * This program will listen on the specified port for Logback logging events * and forward them as Log4j events to another port (to be received by Chainsaw) */ public class LogbackChainsawBridge { static int port; static boolean includeCallerData = false; public static void main(String argv[]) throws Exception { if (argv.length == 0) { init("5555", "false"); } if (argv.length == 1) { init(argv[0], "false"); } if (argv.length == 2) { init(argv[0], argv[1]); } if (argv.length > 2) { usage("too many arguments"); } runServer(); } static void runServer() { org.apache.log4j.net.SocketAppender socketAppender = new org.apache.log4j.net.SocketAppender("localhost", 4445); try { info("Listening on port " + port); ServerSocket serverSocket = new ServerSocket(port); //noinspection InfiniteLoopStatement while (true) { info("Waiting to accept a new client."); Socket socket = serverSocket.accept(); info("Connected to client at " + socket.getInetAddress()); info("Starting new socket node."); new Thread(new SocketHandler(socket, socketAppender)).start(); } } catch (Exception e) { e.printStackTrace(); } } static void usage(String msg) { System.err.println(msg); System.err.println("Usage: java " + MySimpleSocketServer.class.getName() + "[port] [includeCallerData]"); System.err.println(" port : on which port the logback events are coming on"); System.err.println(" includeCallerData : true when you want to include caller data"); System.exit(1); } private static void info(String message) { System.out.println(message); } static void init(String portStr, String includeCallerDataStr) { try { port = Integer.parseInt(portStr); } catch (NumberFormatException e) { e.printStackTrace(); usage("Could not interpret port number [" + portStr + "]."); } includeCallerData = Boolean.parseBoolean(includeCallerDataStr); } private static org.apache.log4j.spi.ThrowableInformation convertToLog4jhTrowableInformation ( ch.qos.logback.classic.spi.ThrowableInformation throwableInformation) { if (throwableInformation == null || throwableInformation.getThrowableStrRep() == null || throwableInformation.getThrowableStrRep().length == 0) { return null; } return new org.apache.log4j.spi.ThrowableInformation(throwableInformation.getThrowableStrRep()); } private static org.apache.log4j.spi.LocationInfo convertToLog4jLocationInfo ( ch.qos.logback.classic.spi.CallerData[] callerData) { if (!includeCallerData || callerData == null || callerData.length == 0) { return org.apache.log4j.spi.LocationInfo.NA_LOCATION_INFO; } ch.qos.logback.classic.spi.CallerData data = callerData[0]; return new org.apache.log4j.spi.LocationInfo( data.getFileName(), data.getClassName(), data.getMethodName(), String.valueOf(data.getLineNumber())); } private static org.apache.log4j.spi.LoggingEvent convertToLog4jEvent ( ch.qos.logback.classic.spi.LoggingEvent event) { String fqnOfCategoryClass = "todo: fqn"; long timestamp = event.getTimeStamp(); org.apache.log4j.Level level = org.apache.log4j.Level.toLevel( event.getLevel().toInt() ); String message = event.getFormattedMessage(); String threadName = event.getThreadName(); String loggerName = event.getLoggerRemoteView().getName(); org.apache.log4j.spi.ThrowableInformation throwableInformation = convertToLog4jhTrowableInformation(event.getThrowableInformation()); String ndc = null; // not supported by SLF4J and LogBack ? org.apache.log4j.spi.LocationInfo locationInfo = convertToLog4jLocationInfo(event.getCallerData()); Map<String,String> mdc = event.getMDCPropertyMap(); org.apache.log4j.spi.LoggingEvent log4jEvent = new org.apache.log4j.spi.LoggingEvent(); log4jEvent.setFQNOfLoggerClass(fqnOfCategoryClass); log4jEvent.setLoggerName(loggerName); log4jEvent.setTimeStamp(timestamp); log4jEvent.setLevel(level); log4jEvent.setMessage(message); log4jEvent.setThreadName(threadName); log4jEvent.setThrowableInformation(throwableInformation); if (mdc != null) { //noinspection unchecked log4jEvent.setProperties(new Hashtable(mdc)); } log4jEvent.setLocationInformation(locationInfo); log4jEvent.setNDC(ndc); //log4jEvent.setSequenceNumber(4); log4jEvent.setRenderedMessage(message); return log4jEvent; } private static class SocketHandler implements Runnable { Socket socket; ObjectInputStream ois; org.apache.log4j.net.SocketAppender log4jAppender; public SocketHandler(Socket socket, org.apache.log4j.net.SocketAppender log4jAppender) { this.socket = socket; this.log4jAppender = log4jAppender; try { ois = new ObjectInputStream(new BufferedInputStream(socket .getInputStream())); } catch (Exception e) { System.err.println("Could not open ObjectInputStream to " + socket); e.printStackTrace(); } } public void run() { ch.qos.logback.classic.spi.LoggingEvent event; try { while (true) { // read an event from the wire try { event = (ch.qos.logback.classic.spi.LoggingEvent) ois.readObject(); } catch (EOFException e) { info("client disconnected"); break; } catch (IOException e) { e.printStackTrace(); break; } catch (ClassNotFoundException e) { e.printStackTrace(); break; } if (event == null) { continue; } org.apache.log4j.spi.LoggingEvent log4jEvent = convertToLog4jEvent(event); log4jAppender.append(log4jEvent); } } catch (Exception e) { System.err.println("Unexpected exception. Closing connection."); e.printStackTrace(); } try { ois.close(); } catch (Exception e) { System.err.println("Could not close connection."); e.printStackTrace(); } } } }

I forgot to mention that the code currently depends on log4j-1.3 (which is an abandoned version) because otherwise chainsaw wouldn't show the location-info. Maarten On 9/26/07, Maarten Bosteels <maarten@apache.org> wrote:
Hello,
I've written a Logback Chainsaw Bridge: your programs can use logback and you can receive the events in Chainsaw using the SImpleReceiver.
The idea is pretty simple:
* You specify a "ch.qos.logback.classic.net.SocketAppender" in logback.xml * LogbackChainsawBridge reads incoming logback events from the wire * LogbackChainsawBridge converts the events to Log4j events * LogbackChainsawBridge sends the events to chainsaw using a SocketAppender
It's certainly not a perfect solution, I hope there will be a cool LogBackViewer someday that can compete with chainsaw. (maybe when I have lots of time...) Or does it exist already ?
In the meantime, some of you might find it interesting. The following attributes are sent to chainsaw: * ThreadName * Logger * Timestamp * Message * Level * MDC * CallerData (LocationInfo) * Throwable
Feedback welcome.
Maarten
package ch.org.logback;
import java.net.ServerSocket; import java.net.Socket; import java.io.ObjectInputStream; import java.io.BufferedInputStream; import java.io.IOException; import java.io.EOFException; import java.util.Map; import java.util.Hashtable;
/** * This program will listen on the specified port for Logback logging events * and forward them as Log4j events to another port (to be received by Chainsaw) */ public class LogbackChainsawBridge {
static int port;
static boolean includeCallerData = false;
public static void main(String argv[]) throws Exception { if (argv.length == 0) { init("5555", "false"); } if (argv.length == 1) { init(argv[0], "false"); } if (argv.length == 2) { init(argv[0], argv[1]); }
if (argv.length > 2) { usage("too many arguments"); } runServer(); }
static void runServer() { org.apache.log4j.net.SocketAppender socketAppender = new org.apache.log4j.net.SocketAppender("localhost", 4445); try { info("Listening on port " + port); ServerSocket serverSocket = new ServerSocket(port); //noinspection InfiniteLoopStatement while (true) { info("Waiting to accept a new client."); Socket socket = serverSocket.accept(); info("Connected to client at " + socket.getInetAddress()); info("Starting new socket node."); new Thread(new SocketHandler(socket, socketAppender)).start(); } } catch (Exception e) { e.printStackTrace(); } }
static void usage(String msg) { System.err.println(msg); System.err.println("Usage: java " + MySimpleSocketServer.class.getName() + "[port] [includeCallerData]"); System.err.println(" port : on which port the logback events are coming on"); System.err.println(" includeCallerData : true when you want to include caller data"); System.exit(1); }
private static void info(String message) { System.out.println(message); }
static void init(String portStr, String includeCallerDataStr) { try { port = Integer.parseInt(portStr); } catch (NumberFormatException e) { e.printStackTrace(); usage("Could not interpret port number [" + portStr + "]."); } includeCallerData = Boolean.parseBoolean(includeCallerDataStr); }
private static org.apache.log4j.spi.ThrowableInformation convertToLog4jhTrowableInformation ( ch.qos.logback.classic.spi.ThrowableInformation throwableInformation) {
if (throwableInformation == null || throwableInformation.getThrowableStrRep() == null || throwableInformation.getThrowableStrRep().length == 0) { return null; } return new org.apache.log4j.spi.ThrowableInformation(throwableInformation.getThrowableStrRep()); }
private static org.apache.log4j.spi.LocationInfo convertToLog4jLocationInfo ( ch.qos.logback.classic.spi.CallerData[] callerData) { if (!includeCallerData || callerData == null || callerData.length == 0) { return org.apache.log4j.spi.LocationInfo.NA_LOCATION_INFO; } ch.qos.logback.classic.spi.CallerData data = callerData[0]; return new org.apache.log4j.spi.LocationInfo( data.getFileName(), data.getClassName(), data.getMethodName(), String.valueOf(data.getLineNumber())); }
private static org.apache.log4j.spi.LoggingEvent convertToLog4jEvent ( ch.qos.logback.classic.spi.LoggingEvent event) { String fqnOfCategoryClass = "todo: fqn"; long timestamp = event.getTimeStamp(); org.apache.log4j.Level level = org.apache.log4j.Level.toLevel( event.getLevel().toInt() ); String message = event.getFormattedMessage(); String threadName = event.getThreadName(); String loggerName = event.getLoggerRemoteView().getName(); org.apache.log4j.spi.ThrowableInformation throwableInformation = convertToLog4jhTrowableInformation(event.getThrowableInformation()); String ndc = null; // not supported by SLF4J and LogBack ?
org.apache.log4j.spi.LocationInfo locationInfo = convertToLog4jLocationInfo(event.getCallerData());
Map<String,String> mdc = event.getMDCPropertyMap();
org.apache.log4j.spi.LoggingEvent log4jEvent = new org.apache.log4j.spi.LoggingEvent();
log4jEvent.setFQNOfLoggerClass(fqnOfCategoryClass); log4jEvent.setLoggerName(loggerName); log4jEvent.setTimeStamp(timestamp); log4jEvent.setLevel(level); log4jEvent.setMessage(message); log4jEvent.setThreadName(threadName); log4jEvent.setThrowableInformation(throwableInformation); if (mdc != null) { //noinspection unchecked log4jEvent.setProperties(new Hashtable(mdc)); } log4jEvent.setLocationInformation(locationInfo); log4jEvent.setNDC(ndc); //log4jEvent.setSequenceNumber(4); log4jEvent.setRenderedMessage(message); return log4jEvent; }
private static class SocketHandler implements Runnable {
Socket socket; ObjectInputStream ois; org.apache.log4j.net.SocketAppender log4jAppender;
public SocketHandler(Socket socket, org.apache.log4j.net.SocketAppender log4jAppender) { this.socket = socket; this.log4jAppender = log4jAppender; try { ois = new ObjectInputStream(new BufferedInputStream(socket .getInputStream())); } catch (Exception e) { System.err.println("Could not open ObjectInputStream to " + socket); e.printStackTrace(); } }
public void run() { ch.qos.logback.classic.spi.LoggingEvent event; try { while (true) { // read an event from the wire try { event = (ch.qos.logback.classic.spi.LoggingEvent) ois.readObject(); } catch (EOFException e) { info("client disconnected"); break; } catch (IOException e) { e.printStackTrace(); break; } catch (ClassNotFoundException e) { e.printStackTrace(); break; }
if (event == null) { continue; } org.apache.log4j.spi.LoggingEvent log4jEvent = convertToLog4jEvent(event);
log4jAppender.append(log4jEvent);
} } catch (Exception e) { System.err.println("Unexpected exception. Closing connection."); e.printStackTrace(); }
try { ois.close(); } catch (Exception e) { System.err.println("Could not close connection."); e.printStackTrace(); } } }
}

Hi Maarten, I really like the idea. The existing socketAppender could be adapted to convert a logback LoggingEvent into log4j format before sending it off to chainsaw. A lot less code to maintain. Anyway, maybe we should aim for 1.2.x compatibility instead of 1.3? Slightly off topic but do you use Chainsaw regularly? How would you rate it on a scale of 1 to 10? Maarten Bosteels wrote:
I forgot to mention that the code currently depends on log4j-1.3 (which is an abandoned version) because otherwise chainsaw wouldn't show the location-info.
Maarten
On 9/26/07, Maarten Bosteels <maarten@apache.org> wrote:
Hello,
I've written a Logback Chainsaw Bridge: your programs can use logback and you can receive the events in Chainsaw using the SImpleReceiver.
The idea is pretty simple:
* You specify a "ch.qos.logback.classic.net.SocketAppender" in logback.xml * LogbackChainsawBridge reads incoming logback events from the wire * LogbackChainsawBridge converts the events to Log4j events * LogbackChainsawBridge sends the events to chainsaw using a SocketAppender
It's certainly not a perfect solution, I hope there will be a cool LogBackViewer someday that can compete with chainsaw. (maybe when I have lots of time...) Or does it exist already ?
In the meantime, some of you might find it interesting. The following attributes are sent to chainsaw: * ThreadName * Logger * Timestamp * Message * Level * MDC * CallerData (LocationInfo) * Throwable
Feedback welcome.
Maarten
package ch.org.logback;
import java.net.ServerSocket; import java.net.Socket; import java.io.ObjectInputStream; import java.io.BufferedInputStream; import java.io.IOException; import java.io.EOFException; import java.util.Map; import java.util.Hashtable;
/** * This program will listen on the specified port for Logback logging events * and forward them as Log4j events to another port (to be received by Chainsaw) */ public class LogbackChainsawBridge {
static int port;
static boolean includeCallerData = false;
public static void main(String argv[]) throws Exception { if (argv.length == 0) { init("5555", "false"); } if (argv.length == 1) { init(argv[0], "false"); } if (argv.length == 2) { init(argv[0], argv[1]); }
if (argv.length > 2) { usage("too many arguments"); } runServer(); }
static void runServer() { org.apache.log4j.net.SocketAppender socketAppender = new org.apache.log4j.net.SocketAppender("localhost", 4445); try { info("Listening on port " + port); ServerSocket serverSocket = new ServerSocket(port); //noinspection InfiniteLoopStatement while (true) { info("Waiting to accept a new client."); Socket socket = serverSocket.accept(); info("Connected to client at " + socket.getInetAddress()); info("Starting new socket node."); new Thread(new SocketHandler(socket, socketAppender)).start(); } } catch (Exception e) { e.printStackTrace(); } }
static void usage(String msg) { System.err.println(msg); System.err.println("Usage: java " + MySimpleSocketServer.class.getName() + "[port] [includeCallerData]"); System.err.println(" port : on which port the logback events are coming on"); System.err.println(" includeCallerData : true when you want to include caller data"); System.exit(1); }
private static void info(String message) { System.out.println(message); }
static void init(String portStr, String includeCallerDataStr) { try { port = Integer.parseInt(portStr); } catch (NumberFormatException e) { e.printStackTrace(); usage("Could not interpret port number [" + portStr + "]."); } includeCallerData = Boolean.parseBoolean(includeCallerDataStr); }
private static org.apache.log4j.spi.ThrowableInformation convertToLog4jhTrowableInformation ( ch.qos.logback.classic.spi.ThrowableInformation throwableInformation) {
if (throwableInformation == null || throwableInformation.getThrowableStrRep() == null || throwableInformation.getThrowableStrRep().length == 0) { return null; } return new org.apache.log4j.spi.ThrowableInformation(throwableInformation.getThrowableStrRep()); }
private static org.apache.log4j.spi.LocationInfo convertToLog4jLocationInfo ( ch.qos.logback.classic.spi.CallerData[] callerData) { if (!includeCallerData || callerData == null || callerData.length == 0) { return org.apache.log4j.spi.LocationInfo.NA_LOCATION_INFO; } ch.qos.logback.classic.spi.CallerData data = callerData[0]; return new org.apache.log4j.spi.LocationInfo( data.getFileName(), data.getClassName(), data.getMethodName(), String.valueOf(data.getLineNumber())); }
private static org.apache.log4j.spi.LoggingEvent convertToLog4jEvent ( ch.qos.logback.classic.spi.LoggingEvent event) { String fqnOfCategoryClass = "todo: fqn"; long timestamp = event.getTimeStamp(); org.apache.log4j.Level level = org.apache.log4j.Level.toLevel( event.getLevel().toInt() ); String message = event.getFormattedMessage(); String threadName = event.getThreadName(); String loggerName = event.getLoggerRemoteView().getName(); org.apache.log4j.spi.ThrowableInformation throwableInformation = convertToLog4jhTrowableInformation(event.getThrowableInformation()); String ndc = null; // not supported by SLF4J and LogBack ?
org.apache.log4j.spi.LocationInfo locationInfo = convertToLog4jLocationInfo(event.getCallerData());
Map<String,String> mdc = event.getMDCPropertyMap();
org.apache.log4j.spi.LoggingEvent log4jEvent = new org.apache.log4j.spi.LoggingEvent();
log4jEvent.setFQNOfLoggerClass(fqnOfCategoryClass); log4jEvent.setLoggerName(loggerName); log4jEvent.setTimeStamp(timestamp); log4jEvent.setLevel(level); log4jEvent.setMessage(message); log4jEvent.setThreadName(threadName); log4jEvent.setThrowableInformation(throwableInformation); if (mdc != null) { //noinspection unchecked log4jEvent.setProperties(new Hashtable(mdc)); } log4jEvent.setLocationInformation(locationInfo); log4jEvent.setNDC(ndc); //log4jEvent.setSequenceNumber(4); log4jEvent.setRenderedMessage(message); return log4jEvent; }
private static class SocketHandler implements Runnable {
Socket socket; ObjectInputStream ois; org.apache.log4j.net.SocketAppender log4jAppender;
public SocketHandler(Socket socket, org.apache.log4j.net.SocketAppender log4jAppender) { this.socket = socket; this.log4jAppender = log4jAppender; try { ois = new ObjectInputStream(new BufferedInputStream(socket .getInputStream())); } catch (Exception e) { System.err.println("Could not open ObjectInputStream to " + socket); e.printStackTrace(); } }
public void run() { ch.qos.logback.classic.spi.LoggingEvent event; try { while (true) { // read an event from the wire try { event = (ch.qos.logback.classic.spi.LoggingEvent) ois.readObject(); } catch (EOFException e) { info("client disconnected"); break; } catch (IOException e) { e.printStackTrace(); break; } catch (ClassNotFoundException e) { e.printStackTrace(); break; }
if (event == null) { continue; } org.apache.log4j.spi.LoggingEvent log4jEvent = convertToLog4jEvent(event);
log4jAppender.append(log4jEvent);
} } catch (Exception e) { System.err.println("Unexpected exception. Closing connection."); e.printStackTrace(); }
try { ois.close(); } catch (Exception e) { System.err.println("Could not close connection."); e.printStackTrace(); } } }
}
_______________________________________________ logback-dev mailing list logback-dev@qos.ch http://qos.ch/mailman/listinfo/logback-dev
-- Ceki Gülcü Logback: The reliable, generic, fast and flexible logging framework for Java. http://logback.qos.ch

Hello Ceki, Indeed, creating a SocketAppender that converts events to log4j LoggingEvents makes more sense. Less code, no need to start a separate server and less delay. But are you considering adding such an Appender to logback ? Then LogBack would depend on log4j ? Rating Chainsaw: actually I almost never use it, I find the interface unattractive and currently it depends on the abandoned log4j-1.3 ... Let's say I'd give it 7/10 because it actually works and it probably has a lot of features. (have never tried the JMS, VFS and DB extensions) During development I use the Log4jMonitor plugin for IntelliJ IDEA. It is pretty simple, but it looks good and it works very well. And it has one killer feature: every LocationInfo is turned into a hyperlink that will jump directly to the corresponding source code ! I had to adapt (decompile) it a little bit for this feature to work for IDEA 6.x (meanwhile the author has send me the source code) For our production servers I must admit that we just tail and grep on the plain text log-files. But I was thinking about using a proper LogViewer à la Chainsaw for production as well. Although tail + grep has some advantages as well: no more network traffic when you minimize the X-window. I have been looking at VigiLog [1], which looks very good, but it cannot (yet) read events from a socket and it can't tail a log-file. And it does not yet support LogBack. Yesterday I tried out Lumberjack (cool name, by the way) which also looks promising (it's not yet released). I have some questions for Joern Huxhorn, the author of Lumberjack * I don't understand why it is file-based instead of memory-based ? You probably have another usage pattern. For me it's enough if it would keep the last 20.000 events (like chainsaw does) and keep those events in memory * the search feature works great, but I would like an option to do live filters the same way (ie, without opening a new tab) Maybe I will have a go at writing my own LogViewer GUI but I have very little Swing experience (nor SWT), and a constant lack of free time :-) Currently the SocketAppender relies on java serialization, but I'm wondering if it wouldn't be better to define a language independent protocol for encoding/decoding logging events. Then SocketAppenders for log4j, LogBack, log4cxx,.. could use the same protocol and everyone would benefit. That's how the XmlSocketAppender in log4cxx is able to work with Chainsaw, but I would prefer a binary protocol to mimimize bandwidth. (eg. Hessian) Even it's only used by LogBack it would have benefits, changes to the serialized form of LoggingEvent wouldn't break older clients, and it might be faster than java serialization. One last thing: currently the SocketAppender is synchronous, slowing down the application when the network is slow, wouldn't it make sense to do the I/O in a separate thread ? Or create a MINA-based appender :-) Would that even work, since MINA itself depends on SLF4J ? Regards, and thanks for reading this far :-) Maarten [1] http://vigilog.sourceforge.net/index.html [2] http://lumberjack.huxhorn.de/ [3] http://mina.apache.org/ On 9/27/07, Ceki Gulcu <ceki@qos.ch> wrote:
Hi Maarten,
I really like the idea.
The existing socketAppender could be adapted to convert a logback LoggingEvent into log4j format before sending it off to chainsaw. A lot less code to maintain.
Anyway, maybe we should aim for 1.2.x compatibility instead of 1.3?
Slightly off topic but do you use Chainsaw regularly? How would you rate it on a scale of 1 to 10?
Maarten Bosteels wrote:
I forgot to mention that the code currently depends on log4j-1.3 (which is an abandoned version) because otherwise chainsaw wouldn't show the location-info.
Maarten
On 9/26/07, Maarten Bosteels <maarten@apache.org> wrote:
Hello,
I've written a Logback Chainsaw Bridge: your programs can use logback and you can receive the events in Chainsaw using the SImpleReceiver.
The idea is pretty simple:
* You specify a "ch.qos.logback.classic.net.SocketAppender" in logback.xml * LogbackChainsawBridge reads incoming logback events from the wire * LogbackChainsawBridge converts the events to Log4j events * LogbackChainsawBridge sends the events to chainsaw using a SocketAppender
It's certainly not a perfect solution, I hope there will be a cool LogBackViewer someday that can compete with chainsaw. (maybe when I have lots of time...) Or does it exist already ?
In the meantime, some of you might find it interesting. The following attributes are sent to chainsaw: * ThreadName * Logger * Timestamp * Message * Level * MDC * CallerData (LocationInfo) * Throwable
Feedback welcome.
Maarten
package ch.org.logback;
import java.net.ServerSocket; import java.net.Socket; import java.io.ObjectInputStream; import java.io.BufferedInputStream; import java.io.IOException; import java.io.EOFException; import java.util.Map; import java.util.Hashtable;
/** * This program will listen on the specified port for Logback logging events * and forward them as Log4j events to another port (to be received by Chainsaw) */ public class LogbackChainsawBridge {
static int port;
static boolean includeCallerData = false;
public static void main(String argv[]) throws Exception { if (argv.length == 0) { init("5555", "false"); } if (argv.length == 1) { init(argv[0], "false"); } if (argv.length == 2) { init(argv[0], argv[1]); }
if (argv.length > 2) { usage("too many arguments"); } runServer(); }
static void runServer() { org.apache.log4j.net.SocketAppender socketAppender = new org.apache.log4j.net.SocketAppender("localhost", 4445); try { info("Listening on port " + port); ServerSocket serverSocket = new ServerSocket(port); //noinspection InfiniteLoopStatement while (true) { info("Waiting to accept a new client."); Socket socket = serverSocket.accept(); info("Connected to client at " + socket.getInetAddress()); info("Starting new socket node."); new Thread(new SocketHandler(socket, socketAppender)).start(); } } catch (Exception e) { e.printStackTrace(); } }
static void usage(String msg) { System.err.println(msg); System.err.println("Usage: java " + MySimpleSocketServer.class.getName() + "[port] [includeCallerData]"); System.err.println(" port : on which port the logback events are coming on"); System.err.println(" includeCallerData : true when you want to include caller data"); System.exit(1); }
private static void info(String message) { System.out.println(message); }
static void init(String portStr, String includeCallerDataStr) { try { port = Integer.parseInt(portStr); } catch (NumberFormatException e) { e.printStackTrace(); usage("Could not interpret port number [" + portStr + "]."); } includeCallerData = Boolean.parseBoolean(includeCallerDataStr); }
private static org.apache.log4j.spi.ThrowableInformation convertToLog4jhTrowableInformation ( ch.qos.logback.classic.spi.ThrowableInformation throwableInformation) {
if (throwableInformation == null || throwableInformation.getThrowableStrRep() == null || throwableInformation.getThrowableStrRep().length == 0) { return null; } return new org.apache.log4j.spi.ThrowableInformation(throwableInformation.getThrowableStrRep()); }
private static org.apache.log4j.spi.LocationInfo convertToLog4jLocationInfo ( ch.qos.logback.classic.spi.CallerData[] callerData) { if (!includeCallerData || callerData == null || callerData.length == 0) { return org.apache.log4j.spi.LocationInfo.NA_LOCATION_INFO; } ch.qos.logback.classic.spi.CallerData data = callerData[0]; return new org.apache.log4j.spi.LocationInfo( data.getFileName(), data.getClassName(), data.getMethodName(), String.valueOf(data.getLineNumber())); }
private static org.apache.log4j.spi.LoggingEvent convertToLog4jEvent ( ch.qos.logback.classic.spi.LoggingEvent event) { String fqnOfCategoryClass = "todo: fqn"; long timestamp = event.getTimeStamp(); org.apache.log4j.Level level = org.apache.log4j.Level.toLevel( event.getLevel().toInt() ); String message = event.getFormattedMessage(); String threadName = event.getThreadName(); String loggerName = event.getLoggerRemoteView().getName(); org.apache.log4j.spi.ThrowableInformation throwableInformation = convertToLog4jhTrowableInformation(event.getThrowableInformation()); String ndc = null; // not supported by SLF4J and LogBack ?
org.apache.log4j.spi.LocationInfo locationInfo = convertToLog4jLocationInfo(event.getCallerData());
Map<String,String> mdc = event.getMDCPropertyMap();
org.apache.log4j.spi.LoggingEvent log4jEvent = new org.apache.log4j.spi.LoggingEvent();
log4jEvent.setFQNOfLoggerClass(fqnOfCategoryClass); log4jEvent.setLoggerName(loggerName); log4jEvent.setTimeStamp(timestamp); log4jEvent.setLevel(level); log4jEvent.setMessage(message); log4jEvent.setThreadName(threadName); log4jEvent.setThrowableInformation(throwableInformation); if (mdc != null) { //noinspection unchecked log4jEvent.setProperties(new Hashtable(mdc)); } log4jEvent.setLocationInformation(locationInfo); log4jEvent.setNDC(ndc); //log4jEvent.setSequenceNumber(4); log4jEvent.setRenderedMessage(message); return log4jEvent; }
private static class SocketHandler implements Runnable {
Socket socket; ObjectInputStream ois; org.apache.log4j.net.SocketAppender log4jAppender;
public SocketHandler(Socket socket, org.apache.log4j.net.SocketAppender log4jAppender) { this.socket = socket; this.log4jAppender = log4jAppender; try { ois = new ObjectInputStream(new BufferedInputStream(socket .getInputStream())); } catch (Exception e) { System.err.println("Could not open ObjectInputStream to " + socket); e.printStackTrace(); } }
public void run() { ch.qos.logback.classic.spi.LoggingEvent event; try { while (true) { // read an event from the wire try { event = (ch.qos.logback.classic.spi.LoggingEvent) ois.readObject(); } catch (EOFException e) { info("client disconnected"); break; } catch (IOException e) { e.printStackTrace(); break; } catch (ClassNotFoundException e) { e.printStackTrace(); break; }
if (event == null) { continue; } org.apache.log4j.spi.LoggingEvent log4jEvent = convertToLog4jEvent(event);
log4jAppender.append(log4jEvent);
} } catch (Exception e) { System.err.println("Unexpected exception. Closing connection."); e.printStackTrace(); }
try { ois.close(); } catch (Exception e) { System.err.println("Could not close connection."); e.printStackTrace(); } } }
}
_______________________________________________ logback-dev mailing list logback-dev@qos.ch http://qos.ch/mailman/listinfo/logback-dev
-- Ceki Gülcü Logback: The reliable, generic, fast and flexible logging framework for Java. http://logback.qos.ch _______________________________________________ logback-dev mailing list logback-dev@qos.ch http://qos.ch/mailman/listinfo/logback-dev

Hi again. First of all: I hope it's ok to post semi-offtopic stuff to the logback-developer-list. I'd like to keep this of the user list until I release a version incl. source. If using the logback-developers-list for a message like this is inappropriate/a problem, please let me know. You can find a new version (0.9.15) at http://lumberjack.huxhorn.de/lumberjack.zip I fixed some caching stuff... Maarten Bosteels wrote:
Yesterday I tried out Lumberjack (cool name, by the way) which also
Yes, I like that name, too, but http://lumberjack.sourceforge.net/ is already used by another logging-related project so I will most likely change it before official release.
looks promising (it's not yet released). I have some questions for Joern Huxhorn, the author of Lumberjack * I don't understand why it is file-based instead of memory-based ? You probably have another usage pattern. For me it's enough if it would keep the last 20.000 events (like chainsaw does) and keep those events in memory
I'm using disk- instead of memory-based logging mainly because Chainsaw crashed with OutOfMemoryExceptions in case of heavy load. This resulted in freezes of our webapp, i.e. the webapp froze as long as chainsaw was still running! I fixed the freeze-problem in chainsaw (close socket in case of *any* throwable).and decided to write my own viewer in my spare-time... One of my main objectives was the processing of a potentially very large numbers of events from multiple sources at the same time.
* the search feature works great, but I would like an option to do live filters the same way (ie, without opening a new tab)
If you press shift-enter in an already filtered view then the current filter is replaced with the new combined filter. This is probably (nearly) what you want. The problem with the disk-based approach is the same as it's main feature - large number of events. It's quite easy (fast) to filter 10.000 in-memory-events... but it's absolutely necessary to perform the filtering in a different view that is updated asynchronously in case the disk-based solution. That's why at least one tab is required... I plan to implement a filter-manager similar to the "Message Filters" dialog of Thunderbird but the release date is "when it's done" ;) I don't know if this will be implemented pre-1.0. I'll probably also implement groovy-filters so you can write your own complex filters in groovy.
Maybe I will have a go at writing my own LogViewer GUI but I have very little Swing experience (nor SWT), and a constant lack of free time :-)
Currently the SocketAppender relies on java serialization, but I'm wondering if it wouldn't be better to define a language independent protocol for encoding/decoding logging events. Then SocketAppenders for log4j, LogBack, log4cxx,.. could use the same protocol and everyone would benefit. That's how the XmlSocketAppender in log4cxx is able to work with Chainsaw, but I would prefer a binary protocol to mimimize bandwidth. (eg. Hessian)
I'm gzipping the serialized log events in my log-files which saves about 55%. This could also be done in SocketAppender... I wanted to implement it and submit a patch but I don't have enough spare-time left. I second that xml-events wouldn't hurt but an XmlSocketAppender should'nt be contained in the classic/access modules but in new modules instead to keep dependencies low (e.g. stax).
Even it's only used by LogBack it would have benefits, changes to the serialized form of LoggingEvent wouldn't break older clients, and it might be faster than java serialization.
One last thing: currently the SocketAppender is synchronous, slowing down the application when the network is slow, wouldn't it make sense to do the I/O in a separate thread ?
The problem with asynchronous SocketAppenders is that you have essentially three options: a) you keep events in an in memory-back-buffer. This will lead to out-of-memory situations if more events are produced than transfered. At this point your app will either explode or drop events. Both is not really an option. b) you keep the events in a disk-based buffer.This will lead to out-of-disk-space situations if more events are produced than transfered. See a) ;) So event transmission must be synchronous. We sometimes have 5+ people receiving log-events from our test-server at the same time and the current SocketAppender pauses some seconds for every unavailable ip (Socket is opened without timeout) so I thought about a SocketAppender serving multiple ip's asynchronously while still being synchronous at event-level. This would have the added benefit that the logging-event can be serialized (gzipped?) just once before sending the same bytes to all available sockets instead of doing it for every SocketAppender.
Or create a MINA-based appender :-) Would that even work, since MINA itself depends on SLF4J ?
Regards, and thanks for reading this far :-) Maarten
Nice! I didn't know about this project! I have to check it out... Regards, and *also* thanks for reading this far :-) Joern.

The problem with asynchronous SocketAppenders is that you have essentially three options: a) you keep events in an in memory-back-buffer. This will lead to out-of-memory situations if more events are produced than transfered. At this point your app will either explode or drop events. Both is not really an option. b) you keep the events in a disk-based buffer.This will lead to out-of-disk-space situations if more events are produced than transfered. See a) ;)
So event transmission must be synchronous.
You can also use a bounded blocking queue. In this way the process is usually asynchronous but falls back to (nearly) synchronous it there are too many events and slows down the system. But I think the througput will be higher even in the latter case.
participants (4)
-
Ceki Gulcu
-
Hontvari Jozsef
-
Joern Huxhorn
-
Maarten Bosteels