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