Created
June 15, 2012 12:19
-
-
Save jroper/2936195 to your computer and use it in GitHub Desktop.
Play 2.0 Comet log tailing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package util.logging; | |
import ch.qos.logback.classic.spi.ILoggingEvent; | |
import ch.qos.logback.core.UnsynchronizedAppenderBase; | |
import play.libs.F; | |
import java.util.Collections; | |
import java.util.Set; | |
import java.util.concurrent.ConcurrentHashMap; | |
import java.util.concurrent.atomic.AtomicInteger; | |
/** | |
* Comet logging appender | |
*/ | |
public class CometAppender extends UnsynchronizedAppenderBase<ILoggingEvent> { | |
private static final int MAX_LOG_BUFFER = 20; | |
private final Set<F.Callback<ILoggingEvent>> listeners = Collections.newSetFromMap( | |
new ConcurrentHashMap<F.Callback<ILoggingEvent>, Boolean>()); | |
private final ILoggingEvent[] buffer = new ILoggingEvent[MAX_LOG_BUFFER]; | |
private final AtomicInteger pos = new AtomicInteger(); | |
public CometAppender() { | |
INSTANCE = this; | |
} | |
@Override | |
protected void append(ILoggingEvent event) { | |
for (F.Callback<ILoggingEvent> listener: listeners) { | |
try { | |
listener.invoke(event); | |
} catch (Throwable throwable) { | |
// If the listener is throwing exceptions, remove it | |
listeners.remove(listener); | |
} | |
} | |
// store event in cyclic buffer | |
buffer[pos.getAndIncrement() % MAX_LOG_BUFFER] = event; | |
} | |
private static volatile CometAppender INSTANCE; | |
public static void addListener(F.Callback<ILoggingEvent> listener) { | |
if (INSTANCE != null) { | |
if (INSTANCE.listeners.size() < 10) { | |
int pos = INSTANCE.pos.get(); | |
for (int i = pos; i < pos + MAX_LOG_BUFFER; i++) { | |
ILoggingEvent event = INSTANCE.buffer[i % MAX_LOG_BUFFER]; | |
if (event != null) { | |
try { | |
listener.invoke(event); | |
} catch (Throwable throwable) { | |
// The listener is bad, don't use it | |
return; | |
} | |
} | |
} | |
INSTANCE.listeners.add(listener); | |
} | |
} | |
} | |
public static void removeListener(F.Callback<ILoggingEvent> listener) { | |
if (INSTANCE != null) { | |
INSTANCE.listeners.remove(listener); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@(time: String, log: ch.qos.logback.classic.spi.ILoggingEvent) | |
<li class="@log.getLevel.toString.toLowerCase"> | |
<span class="time">@time</span> | |
<span class="level">@log.getLevel.toString</span> | |
<span class="logger">@log.getLoggerName</span> | |
<span class="thread">@log.getThreadName</span> | |
<span class="message">@log.getFormattedMessage</span> | |
@if(log.getThrowableProxy != null) { | |
<pre class="exception">@{ ch.qos.logback.classic.spi.ThrowableProxyUtil.asString(log.getThrowableProxy) }</pre> | |
} | |
<script>window.scrollTo(0, document.body.scrollHeight);</script> | |
</li> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package controllers; | |
import ch.qos.logback.classic.spi.ILoggingEvent; | |
import play.libs.F; | |
import play.mvc.Result; | |
import play.mvc.Results; | |
import play.mvc.Security; | |
import services.auth.AdminAuthenticator; | |
import util.logging.CometAppender; | |
import java.text.SimpleDateFormat; | |
import java.util.Date; | |
/** | |
* Used for tailing logs | |
*/ | |
@Security.Authenticated(AdminAuthenticator.class) | |
public class LogController extends Results { | |
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss.SSSS"); | |
public static Result tailLogs() { | |
Chunks<String> chunks = new StringChunks() { | |
@Override | |
public void onReady(final Out<String> out) { | |
// First, write out the main page template | |
out.write(views.html.log.logs.render().body()); | |
// Create logging callback | |
final F.Callback<ILoggingEvent> callback = new F.Callback<ILoggingEvent>() { | |
@Override | |
public void invoke(ILoggingEvent event) throws Throwable { | |
out.write(views.html.log.log.render(dateFormat.format(new Date(event.getTimeStamp())), event).body()); | |
} | |
}; | |
// Add it as a listener to the appender | |
CometAppender.addListener(callback); | |
// Add disconnected listener to remove the listener from the appender | |
out.onDisconnected(new F.Callback0() { | |
@Override | |
public void invoke() throws Throwable { | |
CometAppender.removeListener(callback); | |
} | |
}); | |
} | |
}; | |
return ok(chunks).as("text/html"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<configuration> | |
<conversionRule conversionWord="coloredLevel" converterClass="play.api.Logger$ColoredLevel" /> | |
<appender name="FILE" class="ch.qos.logback.core.FileAppender"> | |
<file>${application.home}/logs/application.log</file> | |
<encoder> | |
<pattern>%date - [%level] - from %logger in %thread %n%message%n%xException%n</pattern> | |
</encoder> | |
</appender> | |
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | |
<encoder> | |
<pattern>%coloredLevel %logger{15} - %message%n%xException{5}</pattern> | |
</encoder> | |
</appender> | |
<appender name="COMET" class="util.logging.CometAppender"> | |
</appender> | |
<logger name="play" level="INFO" /> | |
<logger name="application" level="INFO" /> | |
<root level="ERROR"> | |
<appender-ref ref="STDOUT" /> | |
<appender-ref ref="FILE" /> | |
<appender-ref ref="COMET" /> | |
</root> | |
</configuration> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Application Logs</title> | |
<style> | |
body { | |
background-color: black; | |
margin: 0; | |
padding: 20px; | |
color: lime; | |
font-family: Monaco, monospace; | |
font-size: 11px; | |
} | |
ul { | |
margin: 0; | |
padding: 0; | |
} | |
li { | |
list-style-type: none; | |
margin: 0 5px; | |
padding: 0; | |
} | |
.time { | |
min-width: 100px; | |
display: block; | |
float: left; | |
color: white; | |
} | |
.level { | |
min-width: 50px; | |
display: block; | |
float: left; | |
} | |
.fatal .level { | |
color: #ff4500; | |
} | |
.error .level { | |
color: red; | |
} | |
.warn .level { | |
color: yellow; | |
} | |
.info .level { | |
color: #87ceeb; | |
} | |
.debug .level { | |
color: purple; | |
} | |
.logger { | |
min-width: 100px; | |
color: #6b8e23; | |
display: block; | |
float: left; | |
} | |
.thread { | |
min-width: 300px; | |
color: gray; | |
display: block; | |
float: left; | |
} | |
.message { | |
} | |
.exception { | |
font-family: Monaco, monospace; | |
} | |
</style> | |
</head> | |
<body> | |
<h2>Application Logs</h2> | |
<p>Note: This only shows the logs from one node, and when you restart Play, you'll need to stop loading | |
this page and hit refresh.</p> | |
<ul> | |
@* Intentionally left open, so logs can gradually append as they come *@ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment