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 }