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 }