The American Dream Realized: NYC-based Java Consultant. Author of Play Framework Mods Elastic Search, RabbitMQ. JavaLobby Featured MVB Writer. I blog at http://geeks.aretotally.in and tweet at http://twitter.com/_felipera. Felipe is a DZone MVB and is not an employee of DZone and has posted 13 posts at DZone. View Full User Profile

Log4Play: Log4j Appender and UI with Play Framework, Knockout.js and WebSockets

05.16.2011
| 9412 views |
  • submit to reddit


Log4Play is a Play! Framework module that provides a log4j appender which publishes log entries to an EventStream. In theory, you can use it from any Java application that uses log4j, but I have only tested it with a Play! Framework application. Log4Play provides an user interface which uses a WebSocket to create a live stream of log messages. The user interface allows you to tail the logs of your application without needing to login to the actual box. I worked on it with Deepthi Rallabandi, who I am working with on an Accenture project; I just introduced her to Play!. It has been a pleasant surprise to see how quickly she's progressing, which again confirms how productive Play! is. In one day, she went from knowing nothing about WebSockets, and not that much Web experience, to having a full working WebSockets-based application with Play!. She used my previous WebSockets article to guide her through the process. So let me go over the implementation.

First we created a Log4J appender.
public class PlayWebSocketLogAppender extends WriterAppender implements Appender {

	/**
	 * Publish log event to WebSocket Stream
	 * 
	 * @see org.apache.log4j.WriterAppender#append(org.apache.log4j.spi.LoggingEvent)
	 */
	@Override
	public void append(LoggingEvent event) {
		LogStream.publish(new Log4PlayEvent(event));
	}
}


Then we created a plugin to add the appender to log4j automatically, so you don't have to modify your log4j configuration. A PlayPlugin allows you to customize the behavior of the framework, I highly recommend you going through the source code.
public class Log4PlayPlugin extends PlayPlugin {

	/**
	 * On application start.
	 */
	@Override
	public void onApplicationStart() {
		// Add appender that will stream log messages as Log4PlayEvent instances
		// through WebSocket (Log4Play.WebSocket.stream)
		PlayWebSocketLogAppender appender = new PlayWebSocketLogAppender();
		Logger.log4j.addAppender(appender);

		// Add routes for the UI
		Router.addRoute("GET", "/@logs", "Log4Play.index");
		Router.addRoute("WS", "/@logs/stream", "Log4Play.WebSocket.stream");
	}

}


Then we created an event stream which will be receiving the log messages from the appender.
public abstract class LogStream {

	/** The stream. */
	public static final ArchivedEventStream<log4playevent> stream = new ArchivedEventStream<log4playevent>(50);

	/**
	 * Gets the stream.
	 * 
	 * @return the stream
	 */
	public static EventStream<log4playevent> getStream() {
		return stream.eventStream();
	}

	/**
	 * Publish.
	 * 
	 * @param event
	 *            the event
	 */
	public static void publish(Log4PlayEvent event) {
		stream.publish(event);
	}

}

Notice how we are using an ArchivedEventStream which we can use to display x numbers of messages as soon as the user loads the user interface, instead of seeing a blank page which will then display log messages as they happen from that point on. That's the main difference between the ArchivedEventStream and the EventStream which I used on my first WebSockets article, WebSockets with Play Framework 1.2 in Action!.

Then we defined a WebSocketController which listens to messages dropped on the event stream and push them to the client.
public class Log4Play extends Controller {

	/**
	 * Index.
	 */
	public static void index() {
		render();
	}

	/**
	 * The Class WebSocket.
	 */
	public static class WebSocket extends WebSocketController {

		/**
		 * Index.
		 */
		public static void index() {
			EventStream<play.modules.log4play.log4playevent> loggingStream = play.modules.log4play.LogStream.getStream();
			while (inbound.isOpen()) {
				try {
					Promise<play.modules.log4play.log4playevent> promise = loggingStream.nextEvent();
					play.modules.log4play.Log4PlayEvent event = await(promise);
					outbound.sendJson(event);

				} catch (Throwable t) {
					Logger.error(play.modules.log4play.ExceptionUtil.getStackTrace(t));
				}
			}
		}

	}

}

The difference between this implementation and the one from my previous article is that on this case a JSON object is getting sent to the view, instead of a plain string.

Then on the view side we used Knockout.js.
    // Define Knockout.js Observable
    var viewModel = {};
    viewModel.details = ko.observable();
    ko.applyBindings(viewModel);
    viewModel.details("Log Events will start showing up here...");

    // Create a socket
    var socket = new WebSocket('@@{Log4Play.WebSocket.index()}');
    
    // Display a message
    var data = "";
    var display = function(json) {
		var event = JSON.parse(json);
    	if ( event != null && json != null ) {
	    	var checkLevel = document.getElementById("log4play_level_" + event.level);
	    	if ( checkLevel != null && checkLevel.checked == true ) {
	    		var item = event.level + ' - ' + event.category  + ' - ' + event.date + ' - ' + event.message;
	    		data = item + data;
	    		viewModel.details(data);
	    	}
    	}
    } 
    
    // Message received on the socket
    socket.onmessage = function(event) {
    	display(event.data);
    }


Installation
Under dependencies.yml:
require:
– play → log4play 0.0.1
Under routes:
WS      /logstream                    Log4Play.WebSocket.index
GET     /@logs                          Log4Play.index
GET     /public_log4play/          staticDir:public_log4play


Live Demo
A live demo is available at http://log4play.mashup.fm:9030/@logs. As soon as you enable the module on your application you should have the same UI available as well under /@logs.

Source Code
The source code is available on Github at https://github.com/feliperazeek/log4play.

Now Go Play!

Article originally posted at http://geeks.aretotally.in/log4play-log4j-ui-mashed-up-with-play-framework-knockout-js-and-websockets.
Published at DZone with permission of Felipe Oliveira, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)