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.Layout;
    22  import org.apache.log4j.helpers.SyslogQuietWriter;
    23  import org.apache.log4j.helpers.SyslogWriter;
    24  import org.apache.log4j.spi.LoggingEvent;
    25
    26  import java.text.SimpleDateFormat;
    27  import java.util.Date;
    28  import java.util.Locale;
    29  import java.net.InetAddress;
    30  import java.net.UnknownHostException;
    31
    32  // Contributors: Yves Bossel <ybossel@opengets.cl>
    33  //               Christopher Taylor <cstaylor@pacbell.net>
    34
    35  /***
    36      Use SyslogAppender to send log messages to a remote syslog daemon.
    37
    38      @author Ceki G&uuml;lc&uuml;
    39      @author Anders Kristensen
    40   */
    41  public class SyslogAppender extends AppenderSkeleton {
    42    // The following constants are extracted from a syslog.h file
    43    // copyrighted by the Regents of the University of California
    44    // I hope nobody at Berkley gets offended.
    45
    46    /*** Kernel messages */
    47    final static public int LOG_KERN     = 0;
    48    /*** Random user-level messages */
    49    final static public int LOG_USER     = 1<<3;
    50    /*** Mail system */
    51    final static public int LOG_MAIL     = 2<<3;
    52    /*** System daemons */
    53    final static public int LOG_DAEMON   = 3<<3;
    54    /*** security/authorization messages */
    55    final static public int LOG_AUTH     = 4<<3;
    56    /*** messages generated internally by syslogd */
    57    final static public int LOG_SYSLOG   = 5<<3;
    58
    59    /*** line printer subsystem */
    60    final static public int LOG_LPR      = 6<<3;
    61    /*** network news subsystem */
    62    final static public int LOG_NEWS     = 7<<3;
    63    /*** UUCP subsystem */
    64    final static public int LOG_UUCP     = 8<<3;
    65    /*** clock daemon */
    66    final static public int LOG_CRON     = 9<<3;
    67    /*** security/authorization  messages (private) */
    68    final static public int LOG_AUTHPRIV = 10<<3;
    69    /*** ftp daemon */
    70    final static public int LOG_FTP      = 11<<3;
    71
    72    // other codes through 15 reserved for system use
    73    /*** reserved for local use */
    74    final static public int LOG_LOCAL0 = 16<<3;
    75    /*** reserved for local use */
    76    final static public int LOG_LOCAL1 = 17<<3;
    77    /*** reserved for local use */
    78    final static public int LOG_LOCAL2 = 18<<3;
    79    /*** reserved for local use */
    80    final static public int LOG_LOCAL3 = 19<<3;
    81    /*** reserved for local use */
    82    final static public int LOG_LOCAL4 = 20<<3;
    83    /*** reserved for local use */
    84    final static public int LOG_LOCAL5 = 21<<3;
    85    /*** reserved for local use */
    86    final static public int LOG_LOCAL6 = 22<<3;
    87    /*** reserved for local use*/
    88    final static public int LOG_LOCAL7 = 23<<3;
    89
    90    protected static final int SYSLOG_HOST_OI = 0;
    91    protected static final int FACILITY_OI = 1;
    92
    93    static final String TAB = "    ";
    94
    95    // Have LOG_USER as default
    96    int syslogFacility = LOG_USER;
    97    String facilityStr;
    98    boolean facilityPrinting = false;
    99
    100   //SyslogTracerPrintWriter stp;
    101   SyslogQuietWriter sqw;
    102   String syslogHost;
    103
    104     /***
    105      * If true, the appender will generate the HEADER (timestamp and host name)
    106      * part of the syslog packet.
    107      * @since 1.2.15
    108      */
    109   private boolean header = false;
    110     /***
    111      * Date format used if header = true.
    112      * @since 1.2.15
    113      */
    114   private final SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd HH:mm:ss ", Locale.ENGLISH);
    115     /***
    116      * Host name used to identify messages from this appender.
    117      * @since 1.2.15
    118      */
    119   private String localHostname;
    120
    121     /***
    122      * Set to true after the header of the layout has been sent or if it has none.
    123      */
    124   private boolean layoutHeaderChecked = false;
    125
    126   public
    127   SyslogAppender() {
    128     this.initSyslogFacilityStr();
    129   }
    130
    131   public
    132   SyslogAppender(Layout layout, int syslogFacility) {
    133     this.layout = layout;
    134     this.syslogFacility = syslogFacility;
    135     this.initSyslogFacilityStr();
    136   }
    137
    138   public
    139   SyslogAppender(Layout layout, String syslogHost, int syslogFacility) {
    140     this(layout, syslogFacility);
    141     setSyslogHost(syslogHost);
    142   }
    143
    144   /***
    145      Release any resources held by this SyslogAppender.
    146
    147      @since 0.8.4
    148    */
    149   synchronized
    150   public
    151   void close() {
    152     closed = true;
    153     if (sqw != null) {
    154         try {
    155             if (layoutHeaderChecked && layout != null && layout.getFooter() != null) {
    156                 sendLayoutMessage(layout.getFooter());
    157             }
    158             sqw.close();
    159             sqw = null;
    160         } catch(java.io.IOException ex) {
    161             sqw = null;
    162         }
    163     }
    164   }
    165
    166   private
    167   void initSyslogFacilityStr() {
    168     facilityStr = getFacilityString(this.syslogFacility);
    169
    170     if (facilityStr == null) {
    171       System.err.println("\"" + syslogFacility +
    172                   "\" is an unknown syslog facility. Defaulting to \"USER\".");
    173       this.syslogFacility = LOG_USER;
    174       facilityStr = "user:";
    175     } else {
    176       facilityStr += ":";
    177     }
    178   }
    179
    180   /***
    181      Returns the specified syslog facility as a lower-case String,
    182      e.g. "kern", "user", etc.
    183   */
    184   public
    185   static
    186   String getFacilityString(int syslogFacility) {
    187     switch(syslogFacility) {
    188     case LOG_KERN:      return "kern";
    189     case LOG_USER:      return "user";
    190     case LOG_MAIL:      return "mail";
    191     case LOG_DAEMON:    return "daemon";
    192     case LOG_AUTH:      return "auth";
    193     case LOG_SYSLOG:    return "syslog";
    194     case LOG_LPR:       return "lpr";
    195     case LOG_NEWS:      return "news";
    196     case LOG_UUCP:      return "uucp";
    197     case LOG_CRON:      return "cron";
    198     case LOG_AUTHPRIV:  return "authpriv";
    199     case LOG_FTP:       return "ftp";
    200     case LOG_LOCAL0:    return "local0";
    201     case LOG_LOCAL1:    return "local1";
    202     case LOG_LOCAL2:    return "local2";
    203     case LOG_LOCAL3:    return "local3";
    204     case LOG_LOCAL4:    return "local4";
    205     case LOG_LOCAL5:    return "local5";
    206     case LOG_LOCAL6:    return "local6";
    207     case LOG_LOCAL7:    return "local7";
    208     default:            return null;
    209     }
    210   }
    211
    212   /***
    213      Returns the integer value corresponding to the named syslog
    214      facility, or -1 if it couldn't be recognized.
    215
    216      @param facilityName one of the strings KERN, USER, MAIL, DAEMON,
    217             AUTH, SYSLOG, LPR, NEWS, UUCP, CRON, AUTHPRIV, FTP, LOCAL0,
    218             LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7.
    219             The matching is case-insensitive.
    220
    221      @since 1.1
    222   */
    223   public
    224   static
    225   int getFacility(String facilityName) {
    226     if(facilityName != null) {
    227       facilityName = facilityName.trim();
    228     }
    229     if("KERN".equalsIgnoreCase(facilityName)) {
    230       return LOG_KERN;
    231     } else if("USER".equalsIgnoreCase(facilityName)) {
    232       return LOG_USER;
    233     } else if("MAIL".equalsIgnoreCase(facilityName)) {
    234       return LOG_MAIL;
    235     } else if("DAEMON".equalsIgnoreCase(facilityName)) {
    236       return LOG_DAEMON;
    237     } else if("AUTH".equalsIgnoreCase(facilityName)) {
    238       return LOG_AUTH;
    239     } else if("SYSLOG".equalsIgnoreCase(facilityName)) {
    240       return LOG_SYSLOG;
    241     } else if("LPR".equalsIgnoreCase(facilityName)) {
    242       return LOG_LPR;
    243     } else if("NEWS".equalsIgnoreCase(facilityName)) {
    244       return LOG_NEWS;
    245     } else if("UUCP".equalsIgnoreCase(facilityName)) {
    246       return LOG_UUCP;
    247     } else if("CRON".equalsIgnoreCase(facilityName)) {
    248       return LOG_CRON;
    249     } else if("AUTHPRIV".equalsIgnoreCase(facilityName)) {
    250       return LOG_AUTHPRIV;
    251     } else if("FTP".equalsIgnoreCase(facilityName)) {
    252       return LOG_FTP;
    253     } else if("LOCAL0".equalsIgnoreCase(facilityName)) {
    254       return LOG_LOCAL0;
    255     } else if("LOCAL1".equalsIgnoreCase(facilityName)) {
    256       return LOG_LOCAL1;
    257     } else if("LOCAL2".equalsIgnoreCase(facilityName)) {
    258       return LOG_LOCAL2;
    259     } else if("LOCAL3".equalsIgnoreCase(facilityName)) {
    260       return LOG_LOCAL3;
    261     } else if("LOCAL4".equalsIgnoreCase(facilityName)) {
    262       return LOG_LOCAL4;
    263     } else if("LOCAL5".equalsIgnoreCase(facilityName)) {
    264       return LOG_LOCAL5;
    265     } else if("LOCAL6".equalsIgnoreCase(facilityName)) {
    266       return LOG_LOCAL6;
    267     } else if("LOCAL7".equalsIgnoreCase(facilityName)) {
    268       return LOG_LOCAL7;
    269     } else {
    270       return -1;
    271     }
    272   }
    273
    274
    275   private void splitPacket(final String header, final String packet) {
    276       int byteCount = packet.getBytes().length;
    277       //
    278       //   if packet is less than RFC 3164 limit
    279       //      of 1024 bytes, then write it
    280       //      (must allow for up 5to 5 characters in the PRI section
    281       //          added by SyslogQuietWriter)
    282       if (byteCount <= 1019) {
    283           sqw.write(packet);
    284       } else {
    285           int split = header.length() + (packet.length() - header.length())/2;
    286           splitPacket(header, packet.substring(0, split) + "...");
    287           splitPacket(header, header + "..." + packet.substring(split));
    288       }
    289   }
    290
    291   public
    292   void append(LoggingEvent event) {
    293
    294     if(!isAsSevereAsThreshold(event.getLevel()))
    295       return;
    296
    297     // We must not attempt to append if sqw is null.
    298     if(sqw == null) {
    299       errorHandler.error("No syslog host is set for SyslogAppedender named \""+
    300 			this.name+"\".");
    301       return;
    302     }
    303
    304     if (!layoutHeaderChecked) {
    305         if (layout != null && layout.getHeader() != null) {
    306             sendLayoutMessage(layout.getHeader());
    307         }
    308         layoutHeaderChecked = true;
    309     }
    310
    311     String hdr = getPacketHeader(event.timeStamp);
    312     String packet = layout.format(event);
    313     if(facilityPrinting || hdr.length() > 0) {
    314         StringBuffer buf = new StringBuffer(hdr);
    315         if(facilityPrinting) {
    316             buf.append(facilityStr);
    317         }
    318         buf.append(packet);
    319         packet = buf.toString();
    320     }
    321
    322     sqw.setLevel(event.getLevel().getSyslogEquivalent());
    323     //
    324     //   if message has a remote likelihood of exceeding 1024 bytes
    325     //      when encoded, consider splitting message into multiple packets
    326     if (packet.length() > 256) {
    327         splitPacket(hdr, packet);
    328     } else {
    329         sqw.write(packet);
    330     }
    331
    332     if (layout.ignoresThrowable()) {
    333       String[] s = event.getThrowableStrRep();
    334       if (s != null) {
    335         for(int i = 0; i < s.length; i++) {
    336             if (s[i].startsWith("\t")) {
    337                sqw.write(hdr+TAB+s[i].substring(1));
    338             } else {
    339                sqw.write(hdr+s[i]);
    340             }
    341         }
    342       }
    343     }
    344   }
    345
    346   /***
    347      This method returns immediately as options are activated when they
    348      are set.
    349   */
    350   public
    351   void activateOptions() {
    352       if (header) {
    353         getLocalHostname();
    354       }
    355       if (layout != null && layout.getHeader() != null) {
    356           sendLayoutMessage(layout.getHeader());
    357       }
    358       layoutHeaderChecked = true;
    359   }
    360
    361   /***
    362      The SyslogAppender requires a layout. Hence, this method returns
    363      <code>true</code>.
    364
    365      @since 0.8.4 */
    366   public
    367   boolean requiresLayout() {
    368     return true;
    369   }
    370
    371   /***
    372     The <b>SyslogHost</b> option is the name of the the syslog host
    373     where log output should go.  A non-default port can be specified by
    374     appending a colon and port number to a host name,
    375     an IPv4 address or an IPv6 address enclosed in square brackets.
    376
    377     <b>WARNING</b> If the SyslogHost is not set, then this appender
    378     will fail.
    379    */
    380   public
    381   void setSyslogHost(final String syslogHost) {
    382     this.sqw = new SyslogQuietWriter(new SyslogWriter(syslogHost),
    383 				     syslogFacility, errorHandler);
    384     //this.stp = new SyslogTracerPrintWriter(sqw);
    385     this.syslogHost = syslogHost;
    386   }
    387
    388   /***
    389      Returns the value of the <b>SyslogHost</b> option.
    390    */
    391   public
    392   String getSyslogHost() {
    393     return syslogHost;
    394   }
    395
    396   /***
    397      Set the syslog facility. This is the <b>Facility</b> option.
    398
    399      <p>The <code>facilityName</code> parameter must be one of the
    400      strings KERN, USER, MAIL, DAEMON, AUTH, SYSLOG, LPR, NEWS, UUCP,
    401      CRON, AUTHPRIV, FTP, LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4,
    402      LOCAL5, LOCAL6, LOCAL7. Case is unimportant.
    403
    404      @since 0.8.1 */
    405   public
    406   void setFacility(String facilityName) {
    407     if(facilityName == null)
    408       return;
    409
    410     syslogFacility = getFacility(facilityName);
    411     if (syslogFacility == -1) {
    412       System.err.println("["+facilityName +
    413                   "] is an unknown syslog facility. Defaulting to [USER].");
    414       syslogFacility = LOG_USER;
    415     }
    416
    417     this.initSyslogFacilityStr();
    418
    419     // If there is already a sqw, make it use the new facility.
    420     if(sqw != null) {
    421       sqw.setSyslogFacility(this.syslogFacility);
    422     }
    423   }
    424
    425   /***
    426      Returns the value of the <b>Facility</b> option.
    427    */
    428   public
    429   String getFacility() {
    430     return getFacilityString(syslogFacility);
    431   }
    432
    433   /***
    434     If the <b>FacilityPrinting</b> option is set to true, the printed
    435     message will include the facility name of the application. It is
    436     <em>false</em> by default.
    437    */
    438   public
    439   void setFacilityPrinting(boolean on) {
    440     facilityPrinting = on;
    441   }
    442
    443   /***
    444      Returns the value of the <b>FacilityPrinting</b> option.
    445    */
    446   public
    447   boolean getFacilityPrinting() {
    448     return facilityPrinting;
    449   }
    450
    451   /***
    452    * If true, the appender will generate the HEADER part (that is, timestamp and host name)
    453    * of the syslog packet.  Default value is false for compatibility with existing behavior,
    454    * however should be true unless there is a specific justification.
    455    * @since 1.2.15
    456   */
    457   public final boolean getHeader() {
    458       return header;
    459   }
    460
    461     /***
    462      * Returns whether the appender produces the HEADER part (that is, timestamp and host name)
    463      * of the syslog packet.
    464      * @since 1.2.15
    465     */
    466   public final void setHeader(final boolean val) {
    467       header = val;
    468   }
    469
    470     /***
    471      * Get the host name used to identify this appender.
    472      * @return local host name
    473      * @since 1.2.15
    474      */
    475   private String getLocalHostname() {
    476       if (localHostname == null) {
    477           try {
    478             InetAddress addr = InetAddress.getLocalHost();
    479             localHostname = addr.getHostName();
    480           } catch (UnknownHostException uhe) {
    481             localHostname = "UNKNOWN_HOST";
    482           }
    483       }
    484       return localHostname;
    485   }
    486
    487     /***
    488      * Gets HEADER portion of packet.
    489      * @param timeStamp number of milliseconds after the standard base time.
    490      * @return HEADER portion of packet, will be zero-length string if header is false.
    491      * @since 1.2.15
    492      */
    493   private String getPacketHeader(final long timeStamp) {
    494       if (header) {
    495         StringBuffer buf = new StringBuffer(dateFormat.format(new Date(timeStamp)));
    496         //  RFC 3164 says leading space, not leading zero on days 1-9
    497         if (buf.charAt(4) == '0') {
    498           buf.setCharAt(4, ' ');
    499         }
    500         buf.append(getLocalHostname());
    501         buf.append(' ');
    502         return buf.toString();
    503       }
    504       return "";
    505   }
    506
    507     /***
    508      * Set header or footer of layout.
    509      * @param msg message body, may not be null.
    510      */
    511   private void sendLayoutMessage(final String msg) {
    512       if (sqw != null) {
    513           String packet = msg;
    514           String hdr = getPacketHeader(new Date().getTime());
    515           if(facilityPrinting || hdr.length() > 0) {
    516               StringBuffer buf = new StringBuffer(hdr);
    517               if(facilityPrinting) {
    518                   buf.append(facilityStr);
    519               }
    520               buf.append(msg);
    521               packet = buf.toString();
    522           }
    523           sqw.setLevel(6);
    524           sqw.write(packet);
    525       }
    526   }
    527 }