
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