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