package grails.plugin.lightweightdeploy.logging; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.LoggingEvent; import ch.qos.logback.core.Appender; import ch.qos.logback.core.spi.AppenderAttachableImpl; import com.codahale.metrics.Clock; import java.util.*; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jetty.http.HttpHeaders; import org.eclipse.jetty.server.Authentication; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.RequestLog; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.DateCache; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.slf4j.MDC; import javax.servlet.http.Cookie; public class AsyncRequestLog extends AbstractLifeCycle implements RequestLog { private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(); private static final int BATCH_SIZE = 10000; private List<String> cookies; private class Dispatcher implements Runnable { private volatile boolean running = true; private final List<String> statements = new ArrayList<String>(BATCH_SIZE); @Override public void run() { while (running) { try { statements.add(queue.take()); queue.drainTo(statements, BATCH_SIZE); for (String statement : statements) { final LoggingEvent event = new LoggingEvent(); event.setLevel(Level.INFO); event.setMessage(statement); appenders.appendLoopOnAppenders(event); } statements.clear(); } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); } } } public void stop() { this.running = false; } } private final Clock clock; @SuppressWarnings("ThreadLocalNotStaticFinal") private final ThreadLocal<DateCache> dateCache; private final BlockingQueue<String> queue; private final Dispatcher dispatcher; private final Thread dispatchThread; private final AppenderAttachableImpl<ILoggingEvent> appenders; public AsyncRequestLog(Clock clock, AppenderAttachableImpl<ILoggingEvent> appenders, final TimeZone timeZone, List<String> cookies) { this.clock = clock; this.cookies = cookies; this.queue = new LinkedBlockingQueue<String>(); this.dispatcher = new Dispatcher(); this.dispatchThread = new Thread(dispatcher); dispatchThread.setName("async-request-log-dispatcher-" + THREAD_COUNTER.incrementAndGet()); dispatchThread.setDaemon(true); this.dateCache = new ThreadLocal<DateCache>() { @Override protected DateCache initialValue() { final DateCache cache = new DateCache("dd/MMM/yyyy:HH:mm:ss Z", Locale.US); cache.setTimeZoneID(timeZone.getID()); return cache; } }; this.appenders = appenders; } @Override protected void doStart() throws Exception { final Iterator<Appender<ILoggingEvent>> iterator = appenders.iteratorForAppenders(); while (iterator.hasNext()) { iterator.next().start(); } dispatchThread.start(); } @Override protected void doStop() throws Exception { dispatcher.stop(); final Iterator<Appender<ILoggingEvent>> iterator = appenders.iteratorForAppenders(); while (iterator.hasNext()) { iterator.next().stop(); } } @Override public void log(Request request, Response response) { // copied almost entirely from NCSARequestLog final StringBuilder buf = new StringBuilder(256); String address = request.getHeader(HttpHeaders.X_FORWARDED_FOR); if (address == null) { address = request.getRemoteAddr(); } buf.append(address); buf.append(" - "); final Authentication authentication = request.getAuthentication(); if (authentication instanceof Authentication.User) { buf.append(((Authentication.User) authentication).getUserIdentity() .getUserPrincipal() .getName()); } else { buf.append('-'); } buf.append(" ["); buf.append(dateCache.get().format(request.getTimeStamp())); buf.append("] \""); buf.append(request.getMethod()); buf.append(' '); buf.append(request.getUri().toString()); buf.append(' '); buf.append(request.getProtocol()); buf.append("\" "); if (request.getAsyncContinuation().isInitial()) { int status = response.getStatus(); if (status <= 0) { status = 404; } buf.append((char) ('0' + ((status / 100) % 10))); buf.append((char) ('0' + ((status / 10) % 10))); buf.append((char) ('0' + (status % 10))); } else { buf.append("Async"); } final long responseLength = response.getContentCount(); if (responseLength >= 0) { buf.append(' '); if (responseLength > 99999) { buf.append(responseLength); } else { if (responseLength > 9999) { buf.append((char) ('0' + ((responseLength / 10000) % 10))); } if (responseLength > 999) { buf.append((char) ('0' + ((responseLength / 1000) % 10))); } if (responseLength > 99) { buf.append((char) ('0' + ((responseLength / 100) % 10))); } if (responseLength > 9) { buf.append((char) ('0' + ((responseLength / 10) % 10))); } buf.append((char) ('0' + (responseLength % 10))); } } else { buf.append(" -"); } buf.append(' '); String referer = request.getHeader(HttpHeaders.REFERER); if (referer == null) { buf.append("\"-\""); } else { buf.append('"'); buf.append(referer); buf.append('"'); } buf.append(' '); String agent = request.getHeader(HttpHeaders.USER_AGENT); if (agent == null) { buf.append("\"-\""); } else { buf.append('"'); buf.append(agent); buf.append('"'); } final long now = clock.getTime(); final long dispatchTime = request.getDispatchTime(); buf.append(' '); buf.append(now - ((dispatchTime == 0) ? request.getTimeStamp() : dispatchTime)); buf.append(' '); buf.append(now - request.getTimeStamp()); buf.append(' '); buf.append(request.getAttribute("requestId")); buf.append(" \""); if (request.getCookies() != null) { boolean firstCookie = true; for (Cookie cookie : request.getCookies()) { if (cookies.contains(cookie.getName())) { if (!firstCookie) buf.append("; "); buf.append(cookie.getName()); buf.append('='); buf.append(cookie.getValue()); if (firstCookie) firstCookie = false; } } } buf.append('"'); queue.add(buf.toString()); } }