[logback-dev] Re: Logback Chainsaw Bridge

Maarten Bosteels maarten at apache.org
Thu Sep 27 22:41:34 CEST 2007


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 at 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 at 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 at 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 at qos.ch
> http://qos.ch/mailman/listinfo/logback-dev
>


More information about the logback-dev mailing list