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