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

biz.neustar.loggly.LogglyHandler Maven / Gradle / Ivy

package biz.neustar.loggly;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.params.ConnPerRouteBean;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.codehaus.jackson.map.ObjectMapper;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.*;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;

/**
 * A handler for JDK logging that sends JSON-formatted messages to Loggly.
 */
public class LogglyHandler extends Handler {
    private static final ObjectMapper OM = new ObjectMapper();

    private DefaultHttpClient httpClient;
    private ThreadPoolExecutor pool;
    private Queue retryQueue;
    private boolean allowRetry = true;
    private String inputUrl;

    /**
     * Creates a handler with the specified input URL, 10 threads, and support for 5000 messages in the backlog.
     *
     * @param inputUrl - the URL provided by Loggly for sending log messages to
     * @see #LogglyHandler(String, int, int)
     */
    public LogglyHandler(String inputUrl) {
        this(inputUrl, 10, 5000);
    }

    /**
     * Creates a handler with the specified input URL, max thread count, and message backlog support.
     *
     * @param inputUrl - the URL provided by Loggly for sending log messages to
     * @param maxThreads - the max number of concurrent background threads that are allowed to send data to Loggly
     * @param backlog - the max number of log messages that can be queued up (anything beyond will be thrown away)
     */
    public LogglyHandler(String inputUrl, int maxThreads, int backlog) {
        this.inputUrl = inputUrl;

        pool = new ThreadPoolExecutor(maxThreads, maxThreads,
                60L, TimeUnit.SECONDS,
                new LinkedBlockingDeque(backlog),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r, "Loggly Thread");
                        thread.setDaemon(true);
                        return thread;
                    }
                }, new ThreadPoolExecutor.DiscardOldestPolicy());
        pool.allowCoreThreadTimeOut(true);

        retryQueue = new LinkedBlockingQueue(backlog);

        Thread retryThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (allowRetry) {
                    // drain the retry requests
                    LogglySample sample = null;
                    while ((sample = retryQueue.poll()) != null) {
                        if (sample.retryCount > 10) {
                            // todo: capture statistics about the failure (exception and/or status code)
                            //       and then report on it in some sort of thoughtful way to standard err
                        } else {
                            pool.submit(sample);
                        }
                    }

                    // retry every 10 seconds
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        System.err.println("Retry sleep was interrupted, giving up on retry thread");
                        return;
                    }
                }
            }
        }, "Loggly Retry Thraed");
        retryThread.setDaemon(true);
        retryThread.start();

        HttpParams params = new BasicHttpParams();
        ConnManagerParams.setMaxTotalConnections(params, maxThreads);
        ConnPerRouteBean connPerRoute = new ConnPerRouteBean(maxThreads);
        ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);

        SchemeRegistry registry = new SchemeRegistry();
        try {
            registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
        } catch (Exception e) {
            throw new RuntimeException("Could not register SSL socket factor for Loggly", e);
        }

        ThreadSafeClientConnManager connManager = new ThreadSafeClientConnManager(params, registry);


        httpClient = new DefaultHttpClient(connManager, params);

        // because the threads a daemon threads, we want to give them a chance
        // to finish up before we totally shut down
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                close();
            }
        }));
    }

    @Override
    public void publish(LogRecord record) {
        final Map map = Loggly.getMap();

        String level = "UNKN";
        if (record.getLevel().equals(Level.WARNING)) {
            level = "WARN";
        } else if (record.getLevel().equals(Level.SEVERE)) {
            level = "SEVR";
        } else if (record.getLevel().equals(Level.INFO)) {
            level = "INFO";
        } else if (record.getLevel().equals(Level.FINE)) {
            level = "FINE";
        } else if (record.getLevel().equals(Level.FINEST)) {
            level = "FNST";
        } else if (record.getLevel().equals(Level.FINER)) {
            level = "FINR";
        } else if (record.getLevel().equals(Level.CONFIG)) {
            level = "CONF";
        } else if (record.getLevel().equals(Level.OFF)) {
            level = "OFF ";
        } else if (record.getLevel().equals(Level.ALL)) {
            level = "ALL ";
        }

        // and the log message itself
        if (record.getParameters() != null && record.getParameters().length > 0) {
            java.util.Formatter formatter = new java.util.Formatter();
            formatter.format(record.getMessage(), record.getParameters());
            map.put("message", formatter.toString());
        } else {
            map.put("message", record.getMessage());
        }

        // finally, other metadata
        map.put("thread", Thread.currentThread().getName());
        map.put("loggerName", record.getLoggerName());
        map.put("level", level);

        if (record.getThrown() != null) {
            StringWriter sw = new StringWriter();
            record.getThrown().printStackTrace(new PrintWriter(sw));
            map.put("stackTrace", sw.toString());
        }

        pool.submit(new LogglySample(map));
    }

    @Override
    public void flush() {
    }

    @Override
    public synchronized void close() throws SecurityException {
        if (pool.isShutdown()) {
            return;
        }

        try {
            // first, anything in the retry queue should be tried one last time and then we give up on it
            allowRetry = false;
            for (LogglySample sample : retryQueue) {
                pool.submit(sample);
            }
            retryQueue.clear();

            System.out.println("Shutting down Loggly handler - waiting 90 seconds for " + pool.getQueue().size() + " logs to finish");
            pool.shutdown();
            try {
                boolean result = pool.awaitTermination(90, TimeUnit.SECONDS);
                if (!result) {
                    System.out.println("Not all Loggly messages sent out - still had " + pool.getQueue().size() + " left :(");
                    pool.shutdownNow();
                }
            } catch (InterruptedException e) {
                // ignore
            }
        } finally {
            httpClient.getConnectionManager().shutdown();
            System.out.println("Loggly handler shut down");
        }
    }

    private class LogglySample implements Runnable {
        private final Map map;
        private int retryCount = 0;
        private Exception exception;
        private int statusCode;

        public LogglySample(Map map) {
            this(map, 0);
        }

        private LogglySample(Map map, int retryCount) {
            this.map = map;
            this.retryCount = retryCount;
        }

        @Override
        public void run() {
            HttpEntity entity = null;
            try {
                HttpPost post = new HttpPost(inputUrl);
                StringWriter writer = new StringWriter();
                OM.writeValue(writer, map);
                post.setEntity(new StringEntity(writer.toString()));
                post.setHeader("Content-Type", "application/x-www-form-urlencoded");
                HttpResponse response = httpClient.execute(post);
                entity = response.getEntity();
                statusCode = response.getStatusLine().getStatusCode();
                if (statusCode != 200) {
                    if (allowRetry) {
                        retryCount++;
                        retryQueue.offer(this);
                    }
                }
            } catch (Exception e) {
                if (allowRetry) {
                    exception = e;
                    retryCount++;
                    retryQueue.offer(this);
                }
            } finally {
                if (entity != null) {
                    try {
                        entity.consumeContent();
                    } catch (IOException e) {
                        // safe to ignore
                    }
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy