View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17
    18  package org.apache.log4j.net;
    19
    20  import org.apache.log4j.AppenderSkeleton;
    21  import org.apache.log4j.helpers.Constants;
    22  import org.apache.log4j.spi.LoggingEvent;
    23  import org.apache.log4j.helpers.LogLog;
    24  import org.apache.log4j.xml.XMLLayout;
    25
    26  import java.io.IOException;
    27  import java.net.DatagramPacket;
    28  import java.net.DatagramSocket;
    29  import java.net.InetAddress;
    30  import java.net.UnknownHostException;
    31
    32
    33
    34  /**
    35   *  Sends log information as a UDP datagrams.
    36   *
    37   *  <p>The UDPAppender is meant to be used as a diagnostic logging tool
    38   *  so that logging can be monitored by a simple UDP client.
    39   *
    40   *  <p>Messages are not sent as LoggingEvent objects but as text after
    41   *  applying the designated Layout.
    42   *
    43   *  <p>The port and remoteHost properties can be set in configuration properties.
    44   *  By setting the remoteHost to a broadcast address any number of clients can
    45   *  listen for log messages.
    46   *
    47   *  <p>This was inspired and really extended/copied from {@link SocketAppender}.
    48   *  Please see the docs for the proper credit to the authors of that class.
    49   *
    50   *  @author  <a href="mailto:kbrown@versatilesolutions.com">Kevin Brown</a>
    51   *  @author Scott Deboy &lt;sdeboy@apache.org&gt;
    52   */
    53  public class UDPAppender extends AppenderSkeleton implements PortBased{
    54    /**
    55      * The default port number for the UDP packets, 9991.
    56    */
    57    public static final int DEFAULT_PORT = 9991;
    58
    59    /**
    60       We remember host name as String in addition to the resolved
    61       InetAddress so that it can be returned via getOption().
    62    */
    63    String hostname;
    64    String remoteHost;
    65    String application;
    66    String encoding;
    67    InetAddress address;
    68    int port = DEFAULT_PORT;
    69    DatagramSocket outSocket;
    70
    71    /**
    72     * The MulticastDNS zone advertised by a UDPAppender
    73     */
    74    public static final String ZONE = "_log4j_xml_udp_appender.local.";
    75
    76    // if there is something irrecoverably wrong with the settings, there is no
    77    // point in sending out packeets.
    78    boolean inError = false;
    79    private boolean advertiseViaMulticastDNS;
    80    private ZeroConfSupport zeroConf;
    81
    82      public UDPAppender() {
    83        super(false);
    84    }
    85
    86    /**
    87       Sends UDP packets to the <code>address</code> and <code>port</code>.
    88    */
    89    public UDPAppender(final InetAddress address, final int port) {
    90      super(false);
    91      this.address = address;
    92      this.remoteHost = address.getHostName();
    93      this.port = port;
    94      activateOptions();
    95    }
    96
    97    /**
    98       Sends UDP packets to the <code>address</code> and <code>port</code>.
    99    */
    100   public UDPAppender(final String host, final int port) {
    101     super(false);
    102     this.port = port;
    103     this.address = getAddressByName(host);
    104     this.remoteHost = host;
    105     activateOptions();
    106   }
    107
    108   /**
    109      Open the UDP sender for the <b>RemoteHost</b> and <b>Port</b>.
    110   */
    111   public void activateOptions() {
    112     try {
    113       hostname = InetAddress.getLocalHost().getHostName();
    114     } catch (UnknownHostException uhe) {
    115       try {
    116         hostname = InetAddress.getLocalHost().getHostAddress();
    117       } catch (UnknownHostException uhe2) {
    118         hostname = "unknown";
    119       }
    120     }
    121
    122     //allow system property of application to be primary
    123     if (application == null) {
    124       application = System.getProperty(Constants.APPLICATION_KEY);
    125     } else {
    126       if (System.getProperty(Constants.APPLICATION_KEY) != null) {
    127         application = application + "-" + System.getProperty(Constants.APPLICATION_KEY);
    128       }
    129     }
    130
    131     if(remoteHost != null) {
    132       address = getAddressByName(remoteHost);
    133       connect(address, port);
    134     } else {
    135       String err = "The RemoteHost property is required for SocketAppender named "+ name;
    136       LogLog.error(err);
    137       throw new IllegalStateException(err);
    138     }
    139
    140     if (layout == null) {
    141         layout = new XMLLayout();
    142     }
    143
    144     if (advertiseViaMulticastDNS) {
    145       zeroConf = new ZeroConfSupport(ZONE, port, getName());
    146       zeroConf.advertise();
    147     }
    148
    149     super.activateOptions();
    150   }
    151
    152   /**
    153      Close this appender.
    154      <p>This will mark the appender as closed and
    155      call then {@link #cleanUp} method.
    156   */
    157   public synchronized void close() {
    158     if (closed) {
    159       return;
    160     }
    161
    162     if (advertiseViaMulticastDNS) {
    163       zeroConf.unadvertise();
    164     }
    165
    166     this.closed = true;
    167     cleanUp();
    168   }
    169
    170   /**
    171      Close the UDP Socket and release the underlying
    172      connector thread if it has been created
    173    */
    174   public void cleanUp() {
    175     if (outSocket != null) {
    176       try {
    177         outSocket.close();
    178       } catch (Exception e) {
    179         LogLog.error("Could not close outSocket.", e);
    180       }
    181
    182       outSocket = null;
    183     }
    184   }
    185
    186   void connect(InetAddress address, int port) {
    187     if (this.address == null) {
    188       return;
    189     }
    190
    191     try {
    192       // First, close the previous connection if any.
    193       cleanUp();
    194       outSocket = new DatagramSocket();
    195       outSocket.connect(address, port);
    196     } catch (IOException e) {
    197       LogLog.error(
    198         "Could not open UDP Socket for sending.", e);
    199       inError = true;
    200     }
    201   }
    202
    203   public void append(LoggingEvent event) {
    204     if(inError) {
    205       return;
    206     }
    207
    208     if (event == null) {
    209       return;
    210     }
    211
    212     if (address == null) {
    213       return;
    214     }
    215
    216     if (outSocket != null) {
    217       event.setProperty(Constants.HOSTNAME_KEY, hostname);
    218       if (application != null) {
    219         event.setProperty(Constants.APPLICATION_KEY, application);
    220       }
    221
    222       try {
    223         StringBuffer buf = new StringBuffer(layout.format(event));
    224
    225         byte[] payload;
    226         if(encoding == null) {
    227           payload = buf.toString().getBytes();
    228         } else {
    229           payload = buf.toString().getBytes(encoding);
    230         }
    231
    232         DatagramPacket dp =
    233            new DatagramPacket(payload, payload.length, address, port);
    234         outSocket.send(dp);
    235       } catch (IOException e) {
    236         outSocket = null;
    237         LogLog.warn("Detected problem with UDP connection: " + e);
    238       }
    239     }
    240   }
    241
    242   public boolean isActive() {
    243     return !inError;
    244   }
    245
    246   InetAddress getAddressByName(String host) {
    247     try {
    248       return InetAddress.getByName(host);
    249     } catch (Exception e) {
    250       LogLog.error("Could not find address of [" + host + "].", e);
    251       return null;
    252     }
    253   }
    254
    255   /**
    256      The UDPAppender uses layouts. Hence, this method returns
    257      <code>true</code>.
    258   */
    259   public boolean requiresLayout() {
    260     return true;
    261   }
    262
    263   /**
    264      The <b>RemoteHost</b> option takes a string value which should be
    265      the host name or ipaddress to send the UDP packets.
    266    */
    267   public void setRemoteHost(String host) {
    268     remoteHost = host;
    269   }
    270
    271   /**
    272      Returns value of the <b>RemoteHost</b> option.
    273    */
    274   public String getRemoteHost() {
    275     return remoteHost;
    276   }
    277
    278   /**
    279      The <b>App</b> option takes a string value which should be the name of the application getting logged.
    280      If property was already set (via system property), don't set here.
    281    */
    282   public void setApplication(String app) {
    283     this.application = app;
    284   }
    285
    286   /**
    287      Returns value of the <b>App</b> option.
    288    */
    289   public String getApplication() {
    290     return application;
    291   }
    292
    293   /**
    294      The <b>Encoding</b> option specifies how the bytes are encoded.  If this option is not specified,
    295      the System encoding is used.
    296    */
    297   public void setEncoding(String encoding) {
    298     this.encoding = encoding;
    299   }
    300
    301   /**
    302      Returns value of the <b>Encoding</b> option.
    303    */
    304   public String getEncoding() {
    305     return encoding;
    306   }
    307
    308     /**
    309      The <b>Port</b> option takes a positive integer representing
    310      the port where UDP packets will be sent.
    311    */
    312   public void setPort(int port) {
    313     this.port = port;
    314   }
    315
    316   /**
    317      Returns value of the <b>Port</b> option.
    318    */
    319   public int getPort() {
    320     return port;
    321   }
    322
    323   public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
    324     this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
    325   }
    326
    327   public boolean isAdvertiseViaMulticastDNS() {
    328     return advertiseViaMulticastDNS;
    329   }
    330 }