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