Java tutorial
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ülcü * @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; } }