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