Over a million developers have joined DZone.

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

DZone's Guide to

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

· Java Zone
Free Resource

Learn how to troubleshoot and diagnose some of the most common performance issues in Java today. Brought to you in partnership with AppDynamics.


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)
	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.
	public void onApplicationStart() {
		// Add appender that will stream log messages as Log4PlayEvent instances
		// through WebSocket (Log4Play.WebSocket.stream)
		PlayWebSocketLogAppender appender = new PlayWebSocketLogAppender();

		// 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) {


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() {

	 * 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);

				} catch (Throwable 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();
    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;
    // Message received on the socket
    socket.onmessage = function(event) {

Under dependencies.yml:
– 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.

Understand the needs and benefits around implementing the right monitoring solution for a growing containerized market. Brought to you in partnership with AppDynamics.


Opinions expressed by DZone contributors are their own.


Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.


{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}