com.vivareal.logger.appender.UDPAppender.java Source code

Java tutorial

Introduction

Here is the source code for com.vivareal.logger.appender.UDPAppender.java

Source

package com.vivareal.logger.appender;

/*
 * ============================================================================
 *                   The Apache Software License, Version 1.1
 * ============================================================================
 *
 *    Copyright (C) 1999 The Apache Software Foundation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modifica-
 * tion, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of  source code must  retain the above copyright  notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. The end-user documentation included with the redistribution, if any, must
 *    include  the following  acknowledgment:  "This product includes  software
 *    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
 *    Alternately, this  acknowledgment may  appear in the software itself,  if
 *    and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "log4j" and  "Apache Software Foundation"  must not be used to
 *    endorse  or promote  products derived  from this  software without  prior
 *    written permission. For written permission, please contact
 *    apache@apache.org.
 *
 * 5. Products  derived from this software may not  be called "Apache", nor may
 *    "Apache" appear  in their name,  without prior written permission  of the
 *    Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
 * APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
 * DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
 * ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
 * (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software  consists of voluntary contributions made  by many individuals
 * on  behalf of the Apache Software  Foundation.  For more  information on the
 * Apache Software Foundation, please see <http://www.apache.org/>.
 *
 */

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.net.SocketAppender;
import org.apache.log4j.spi.LoggingEvent;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.vivareal.logger.LogstashEvent;

/**
 * 
 * 
 * Sends log information as a UDP datagrams.
 * 
 * <p>
 * The UDPAppender is meant to be used as a diagnostic logging tool so that
 * logging can be monitored by a simple UDP client.
 * 
 * <p>
 * Messages are not sent as LoggingEvent objects but as text after applying the
 * designated Layout.
 * 
 * <p>
 * The port and remoteHost properties can be set in configuration properties. By
 * setting the remoteHost to a broadcast address any number of clients can
 * listen for log messages.
 * 
 * <p>
 * This was inspired and really extended/copied from {@link SocketAppender}.
 * Please see the docs for the proper credit to the authors of that class.
 * 
 * @author <a href="mailto:kbrown@versatilesolutions.com">Kevin Brown</a>
 * @author Scott Deboy <sdeboy@apache.org>
 */
public class UDPAppender extends AppenderSkeleton {
    /**
     * The default port number for the UDP packets. (9991).
     */
    static final int DEFAULT_PORT = 9991;

    /**
     * The default reconnection delay (30000 milliseconds or 30 seconds).
     */
    static final int DEFAULT_RECONNECTION_DELAY = 30000;

    static final int MAX_MESSAGE_SIZE = 3072;

    static final int MAX_STACK_TRACE_SIZE = 4096;

    /**
     * We remember host name as String in addition to the resolved InetAddress
     * so that it can be returned via getOption().
     */
    String localMachine;
    String remoteHost;
    String application;
    String overrideProperties = "true";
    InetAddress address;
    int port = DEFAULT_PORT;
    DatagramSocket outSocket;
    int reconnectionDelay = DEFAULT_RECONNECTION_DELAY;
    boolean locationInfo = false;
    int count = 0;
    private Connector connector;

    private String environment;

    public UDPAppender() {
    }

    /**
     * Sends UDP packets to the <code>address</code> and <code>port</code>.
     */
    public UDPAppender(InetAddress address, int port) {
        this.address = address;
        this.remoteHost = address.getHostName();
        this.port = port;
        connect(address, port);
    }

    /**
     * Sends UDP packets to the <code>address</code> and <code>port</code>.
     */
    public UDPAppender(String host, int port) {
        this.port = port;
        this.address = getAddressByName(host);
        this.remoteHost = host;
        connect(address, port);
    }

    /**
     * Open the UDP sender for the <b>RemoteHost</b> and <b>Port</b>.
     */
    public void activateOptions() {
        try {
            localMachine = InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException uhe) {
            try {
                localMachine = InetAddress.getLocalHost().getHostAddress();
            } catch (UnknownHostException uhe2) {
                localMachine = "unknown";
            }
        }

        // allow system property of log4japp to be primary
        if (application == null) {
            application = System.getProperty("application");
        } else {
            if (System.getProperty("application") != null) {
                application = application + "-" + System.getProperty("application");
            }
        }

        // if not passed in, allow null app (app property won't be set)
        connect(address, port);
    }

    /**
     * Close this appender.
     * <p>
     * This will mark the appender as closed and call then {@link #cleanUp}
     * method.
     */
    public synchronized void close() {
        if (closed) {
            return;
        }

        this.closed = true;
        cleanUp();
    }

    /**
     * Close the UDP Socket and release the underlying connector thread if it
     * has been created
     */
    public void cleanUp() {
        if (outSocket != null) {
            try {
                outSocket.close();
            } catch (Exception e) {
                LogLog.error("Could not close outSocket.", e);
            }

            outSocket = null;
        }

        if (connector != null) {
            // LogLog.debug("Interrupting the connector.");
            connector.interrupted = true;
            connector = null; // allow gc
        }
    }

    void connect(InetAddress address, int port) {
        if (this.address == null) {
            return;
        }

        try {
            // First, close the previous connection if any.
            cleanUp();
            outSocket = new DatagramSocket();
            outSocket.connect(address, port);
        } catch (IOException e) {
            LogLog.error("Could not open UDP Socket for sending. We will try again later.", e);
            fireConnector();
        }
    }

    public void append(LoggingEvent event) {
        if (event == null) {
            return;
        }

        if (address == null) {
            errorHandler.error("No remote host is set for UDPAppender named \"" + this.name + "\".");

            return;
        }

        if (outSocket != null) {
            // if the values already exist, don't set (useful when forwarding
            // from a simplesocketserver
            if ((overrideProperties != null) && overrideProperties.equalsIgnoreCase("true")) {
                event.setProperty("log4jmachinename", localMachine);

                if (application != null) {
                    event.setProperty("log4japp", application);
                }
            }

            try {

                TimeZone tz = TimeZone.getTimeZone("UTC");
                DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
                df.setTimeZone(tz);
                String timestamp = df.format(new Date(event.getTimeStamp()));

                LogstashEvent logstashEvent = new LogstashEvent();
                logstashEvent.setApplication(application);
                logstashEvent.setLoggerName(event.getLoggerName());
                logstashEvent.setNdc(event.getNDC());
                logstashEvent.setPath(event.getLoggerName());
                logstashEvent.setPriority(event.getLevel().toString());
                logstashEvent.setThread(event.getThreadName());
                logstashEvent.setTimestamp(timestamp);
                logstashEvent.setEnvironment(environment);

                if (event.getLocationInformation() != null) {
                    logstashEvent.setClassName(event.getLocationInformation().getClassName());
                    logstashEvent.setFile(event.getLocationInformation().getFileName());
                    logstashEvent.setMethod(event.getLocationInformation().getMethodName());
                }

                String message = event.getRenderedMessage().trim();
                logstashEvent.setMessage(message);
                if (message.length() > MAX_MESSAGE_SIZE) {
                    StringBuilder sb = new StringBuilder();
                    String truncatedMessage = message.substring(0, MAX_MESSAGE_SIZE - 3);
                    sb.append(truncatedMessage);
                    sb.append("...");
                    logstashEvent.setMessage(sb.toString());
                }

                if (event.getThrowableInformation() != null) {
                    String stackTrace = StringUtils.join(event.getThrowableStrRep(), "\n");
                    logstashEvent.setStackTrace(stackTrace);
                    if (stackTrace.length() > MAX_STACK_TRACE_SIZE) {
                        StringBuilder sb = new StringBuilder();
                        String truncatedStackTrace = stackTrace.substring(0, MAX_STACK_TRACE_SIZE - 3);
                        sb.append(truncatedStackTrace);
                        sb.append("...");
                        logstashEvent.setStackTrace(sb.toString());
                    }
                }

                ObjectMapper mapper = new ObjectMapper();
                String json = mapper.writeValueAsString(logstashEvent);

                StringBuffer buf = new StringBuffer(json);
                DatagramPacket dp = new DatagramPacket(buf.toString().getBytes("ASCII"), buf.length(), address,
                        port);
                outSocket.send(dp);
            } catch (IOException e) {
                outSocket = null;
                LogLog.warn("Detected problem with UDP connection: " + e);

                if (reconnectionDelay > 0) {
                    fireConnector();
                }
            }
        }
    }

    void fireConnector() {
        if (connector == null) {
            LogLog.debug("Starting a new connector thread.");
            connector = new Connector();
            connector.setDaemon(true);
            connector.setPriority(Thread.MIN_PRIORITY);
            connector.start();
        }
    }

    static InetAddress getAddressByName(String host) {
        try {
            return InetAddress.getByName(host);
        } catch (Exception e) {
            LogLog.error("Could not find address of [" + host + "].", e);

            return null;
        }
    }

    /**
     * The UDPAppender uses layouts. Hence, this method returns
     * <code>true</code>.
     */
    public boolean requiresLayout() {
        return false;
    }

    /**
     * The <b>RemoteHost</b> option takes a string value which should be the
     * host name or ipaddress to send the UDP packets.
     */
    public void setRemoteHost(String host) {
        address = getAddressByName(host);
        remoteHost = host;
    }

    /**
     * Returns value of the <b>RemoteHost</b> option.
     */
    public String getRemoteHost() {
        return remoteHost;
    }

    /**
     * The <b>App</b> option takes a string value which should be the name of
     * the application getting logged. If property was already set (via system
     * property), don't set here.
     */
    public void setApplication(String application) {
        this.application = application;
    }

    /**
     * Returns value of the <b>App</b> option.
     */
    public String getApplication() {
        return application;
    }

    /**
     * The <b>OverrideProperties</b> option allows configurations where the
     * appender does not apply the machinename/appname properties - the
     * properties will be used as provided.
     */
    public void setOverrideProperties(String overrideProperties) {
        this.overrideProperties = overrideProperties;
    }

    /**
     * Returns value of the <b>OverrideProperties</b> option.
     */
    public String getOverrideProperties() {
        return overrideProperties;
    }

    /**
     * The <b>Port</b> option takes a positive integer representing the port
     * where UDP packets will be sent.
     */
    public void setPort(int port) {
        this.port = port;
    }

    /**
     * Returns value of the <b>Port</b> option.
     */
    public int getPort() {
        return port;
    }

    /**
     * The <b>ReconnectionDelay</b> option takes a positive integer representing
     * the number of milliseconds to wait between each failed attempt to
     * establish an outgoing socket. The default value of this option is 30000
     * which corresponds to 30 seconds.
     * 
     * <p>
     * Setting this option to zero turns off reconnection capability.
     */
    public void setReconnectionDelay(int delay) {
        this.reconnectionDelay = delay;
    }

    /**
     * Returns value of the <b>ReconnectionDelay</b> option.
     */
    public int getReconnectionDelay() {
        return reconnectionDelay;
    }

    /**
     * The Connector will retry the UDP socket. It does this by attempting to
     * open a new UDP socket every <code>reconnectionDelay</code> milliseconds.
     * 
     * <p>
     * It stops trying whenever a connection is established. It will restart to
     * try reconnect to the server when previpously open connection is droppped.
     * 
     * @author Ceki G&uuml;lc&uuml;
     * @since 0.8.4
     */
    class Connector extends Thread {
        boolean interrupted = false;

        public void run() {
            DatagramSocket socket;

            while (!interrupted) {
                try {
                    sleep(reconnectionDelay);
                    LogLog.debug("Attempting to establish UDP Datagram Socket");
                    socket = new DatagramSocket();

                    synchronized (this) {
                        outSocket = socket;
                        connector = null;

                        break;
                    }
                } catch (InterruptedException e) {
                    LogLog.debug("Connector interrupted. Leaving loop.");

                    return;
                } catch (IOException e) {
                    LogLog.debug("Could not establish an outgoing MulticastSocket." + e);
                }
            }

            // LogLog.debug("Exiting Connector.run() method.");
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.log4j.net.NetworkBased#isActive()
     */
    public boolean isActive() {
        // TODO handle active/inactive
        return true;
    }

    public String getEnvironment() {
        return environment;
    }

    public void setEnvironment(String environment) {
        this.environment = environment;
    }
}