All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yammer.dropwizard.jetty.AsyncRequestLog Maven / Gradle / Ivy

package com.yammer.dropwizard.jetty;

// TODO: 10/12/11  -- write tests for AsyncRequestLog

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.RolloverFileOutputStream;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * A non-blocking, asynchronous {@link RequestLog} implementation which implements a subset of the
 * functionality of {@link org.eclipse.jetty.server.NCSARequestLog}. Log entries are added to an
 * in-memory queue and an offline thread handles the responsibility of batching them to disk. The
 * date format is fixed, UTC time zone is fixed, and latency is always logged.
 */
public class AsyncRequestLog extends AbstractLifeCycle implements RequestLog {
    private static final AtomicInteger THREAD_COUNTER = new AtomicInteger();
    private static final Logger LOGGER = LoggerFactory.getLogger(AsyncRequestLog.class);
    private static final int BATCH_SIZE = 10000;

    private class Dispatcher implements Runnable {
        private volatile boolean running = true;
        private final List statements = new ArrayList(BATCH_SIZE);

        @Override
        public void run() {
            while (running) {
                try {
                    statements.add(queue.take());
                    queue.drainTo(statements, BATCH_SIZE);
                    for (String statement : statements) {
                        writer.println(statement);
                    }
                    writer.flush();
                    statements.clear();
                } catch (InterruptedException ignored) {
                    Thread.currentThread().interrupt();
                }
            }
        }

        public void stop() {
            this.running = true;
        }
    }

    private static final ThreadLocal DATE_CACHE = new ThreadLocal() {
        @Override
        protected DateCache initialValue() {
            final DateCache cache = new DateCache("dd/MMM/yyyy:HH:mm:ss Z",
                                                  Locale.getDefault());
            cache.setTimeZoneID("UTC");
            return cache;
        }
    };

    private final BlockingQueue queue;
    private final String filenamePattern;
    private final int numberOfFilesToRetain;
    private final Thread dispatchThread;
    private final Dispatcher dispatcher;
    private PrintWriter writer;

    /**
     * Creates a new {@link AsyncRequestLog}.
     *
     * @param filenamePattern          The filename pattern to which the log statements will be
     *                                 written. If {@code filenamePattern} contains the string
     *                                 {@code yyyy_mm_dd}, the file will be rotated every day, with
     *                                 {@code yyyy_mm_dd} being replaced by the year, month, and
     *                                 day. If {@code filenamePattern} is {@code null}, statements
     *                                 will be logged to STDOUT.
     * @param numberOfFilesToRetain    If {@code filenamePattern} is to be rotated, the number of
     *                                 total log files (including the active one) to be kept.
     */
    public AsyncRequestLog(String filenamePattern, int numberOfFilesToRetain) {
        this.filenamePattern = filenamePattern;
        this.numberOfFilesToRetain = numberOfFilesToRetain;
        this.writer = null;
        this.queue = new LinkedBlockingQueue();
        this.dispatcher = new Dispatcher();
        this.dispatchThread = new Thread(dispatcher);
        dispatchThread.setName("async-request-log-dispatcher-" + THREAD_COUNTER.incrementAndGet());
        dispatchThread.setDaemon(true);
    }

    @Override
    @SuppressWarnings({"IOResourceOpenedButNotSafelyClosed", "UseOfSystemOutOrSystemErr"})
    protected void doStart() throws Exception {
        if (filenamePattern == null) {
            this.writer = new PrintWriter(System.out);
        } else {
            final RolloverFileOutputStream outputStream = new RolloverFileOutputStream(
                    filenamePattern,
                    true,
                    numberOfFilesToRetain,
                    TimeZone.getTimeZone("UTC"));
            this.writer = new PrintWriter(outputStream);
            LOGGER.info("Opened {}", outputStream.getDatedFilename());
        }

        dispatchThread.start();
    }

    @Override
    protected void doStop() throws Exception {
        dispatcher.stop();
        if (writer != null) {
            writer.close();
        }
    }

    @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(DATE_CACHE.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)));
            }
            buf.append(' ');
        } else {
            buf.append(" - ");
        }

        final long now = System.currentTimeMillis();
        final long dispatchTime = request.getDispatchTime();

        buf.append(' ');
        buf.append(now - ((dispatchTime == 0) ? request.getTimeStamp() : dispatchTime));

        buf.append(' ');
        buf.append(now - request.getTimeStamp());

        queue.add(buf.toString());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy