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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 package org.apache.log4j.net;
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;
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;
34 // Contributors: Yves Bossel <ybossel@opengets.cl>
35 // Christopher Taylor <cstaylor@pacbell.net>
38 Use SyslogAppender to send log messages to a remote syslog daemon.
40 @author Ceki Gülcü
41 @author Anders Kristensen
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.
49 * Maximum length of a TAG string.
51 private static final int MAX_TAG_LEN = 32;
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;
58 final static public int LOG_MAIL = 2<<3;
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;
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;
71 final static public int LOG_UUCP = 8<<3;
73 final static public int LOG_CRON = 9<<3;
74 /** security/authorization messages (private) */
75 final static public int LOG_AUTHPRIV = 10<<3;
77 final static public int LOG_FTP = 11<<3;
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;
97 protected static final int SYSLOG_HOST_OI = 0;
98 protected static final int FACILITY_OI = 1;
100 static final String TAB = " ";
102 static final Pattern NOT_ALPHANUM = Pattern.compile("[^\\p{Alnum}]");
104 // Have LOG_USER as default
105 int syslogFacility = LOG_USER;
107 boolean facilityPrinting = false;
109 //SyslogTracerPrintWriter stp;
110 SyslogQuietWriter sqw;
114 * If true, the appender will generate the HEADER (timestamp and host name)
115 * part of the syslog packet.
118 private boolean header = false;
121 * The TAG part of the syslog message.
125 private String tag = null;
128 * Date format used if header = true.
131 private final SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd HH:mm:ss ", Locale.ENGLISH);
134 * Host name used to identify messages from this appender.
137 private String localHostname;
140 * Set to true after the header of the layout has been sent or if it has none.
142 private boolean layoutHeaderChecked = false;
146 this.initSyslogFacilityStr();
150 SyslogAppender(Layout layout, int syslogFacility) {
151 this.layout = layout;
152 this.syslogFacility = syslogFacility;
153 this.initSyslogFacilityStr();
157 SyslogAppender(Layout layout, String syslogHost, int syslogFacility) {
158 this(layout, syslogFacility);
159 setSyslogHost(syslogHost);
163 Release any resources held by this SyslogAppender.
173 if (layoutHeaderChecked && layout != null && layout.getFooter() != null) {
174 sendLayoutMessage(layout.getFooter());
178 } catch(java.io.InterruptedIOException e) {
179 Thread.currentThread().interrupt();
181 } catch(IOException e) {
188 void initSyslogFacilityStr() {
189 facilityStr = getFacilityString(this.syslogFacility);
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:";
202 Returns the specified syslog facility as a lower-case String,
203 e.g. "kern", "user", etc.
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;
234 Returns the integer value corresponding to the named syslog
235 facility, or -1 if it couldn't be recognized.
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.
246 int getFacility(String facilityName) {
247 if(facilityName != null) {
248 facilityName = facilityName.trim();
250 if("KERN".equalsIgnoreCase(facilityName)) {
252 } else if("USER".equalsIgnoreCase(facilityName)) {
254 } else if("MAIL".equalsIgnoreCase(facilityName)) {
256 } else if("DAEMON".equalsIgnoreCase(facilityName)) {
258 } else if("AUTH".equalsIgnoreCase(facilityName)) {
260 } else if("SYSLOG".equalsIgnoreCase(facilityName)) {
262 } else if("LPR".equalsIgnoreCase(facilityName)) {
264 } else if("NEWS".equalsIgnoreCase(facilityName)) {
266 } else if("UUCP".equalsIgnoreCase(facilityName)) {
268 } else if("CRON".equalsIgnoreCase(facilityName)) {
270 } else if("AUTHPRIV".equalsIgnoreCase(facilityName)) {
272 } else if("FTP".equalsIgnoreCase(facilityName)) {
274 } else if("LOCAL0".equalsIgnoreCase(facilityName)) {
276 } else if("LOCAL1".equalsIgnoreCase(facilityName)) {
278 } else if("LOCAL2".equalsIgnoreCase(facilityName)) {
280 } else if("LOCAL3".equalsIgnoreCase(facilityName)) {
282 } else if("LOCAL4".equalsIgnoreCase(facilityName)) {
284 } else if("LOCAL5".equalsIgnoreCase(facilityName)) {
286 } else if("LOCAL6".equalsIgnoreCase(facilityName)) {
288 } else if("LOCAL7".equalsIgnoreCase(facilityName)) {
296 private void splitPacket(final String header, final String packet) {
297 int byteCount = packet.getBytes().length;
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) {
306 int split = header.length() + (packet.length() - header.length())/2;
307 splitPacket(header, packet.substring(0, split) + "...");
308 splitPacket(header, header + "..." + packet.substring(split));
313 void append(LoggingEvent event) {
315 if(!isAsSevereAsThreshold(event.getLevel())) {
319 // We must not attempt to append if sqw is null.
321 errorHandler.error("No syslog host is set for SyslogAppedender named \""+
326 if (!layoutHeaderChecked) {
327 if (layout != null && layout.getHeader() != null) {
328 sendLayoutMessage(layout.getHeader());
330 layoutHeaderChecked = true;
333 String hdr = getPacketHeader(event.timeStamp);
335 if (layout == null) {
336 packet = String.valueOf(event.getMessage());
338 packet = layout.format(event);
340 if(facilityPrinting || hdr.length() > 0) {
341 StringBuffer buf = new StringBuffer(hdr);
342 if(facilityPrinting) {
343 buf.append(facilityStr);
346 packet = buf.toString();
349 sqw.setLevel(event.getLevel().getSyslogEquivalent());
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);
359 if (layout == null || layout.ignoresThrowable()) {
360 String[] s = event.getThrowableStrRep();
362 for(int i = 0; i < s.length; i++) {
363 if (s[i].startsWith("\t")) {
364 sqw.write(hdr+TAB+s[i].substring(1));
374 This method returns immediately as options are activated when they
378 void activateOptions() {
382 if (layout != null && layout.getHeader() != null) {
383 sendLayoutMessage(layout.getHeader());
385 layoutHeaderChecked = true;
389 The SyslogAppender requires a layout. Hence, this method returns
394 boolean requiresLayout() {
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.
404 <b>WARNING</b> If the SyslogHost is not set, then this appender
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;
416 Returns the value of the <b>SyslogHost</b> option.
419 String getSyslogHost() {
424 Set the syslog facility. This is the <b>Facility</b> option.
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.
433 void setFacility(String facilityName) {
434 if(facilityName == null) {
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;
445 this.initSyslogFacilityStr();
447 // If there is already a sqw, make it use the new facility.
449 sqw.setSyslogFacility(this.syslogFacility);
454 Returns the value of the <b>Facility</b> option.
457 String getFacility() {
458 return getFacilityString(syslogFacility);
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.
467 void setFacilityPrinting(boolean on) {
468 facilityPrinting = on;
472 Returns the value of the <b>FacilityPrinting</b> option.
475 boolean getFacilityPrinting() {
476 return facilityPrinting;
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.
485 public final boolean getHeader() {
490 * Returns whether the appender produces the HEADER part (that is, timestamp and host name)
491 * of the syslog packet.
494 public final void setHeader(final boolean val) {
499 * Sets the <b>Tag</b> option.
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.
505 * The default value is {@code null}.
509 * the TAG to be printed out with the header
513 public void setTag(final String tag) {
515 if (newTag != null) {
516 if (newTag.length() > MAX_TAG_LEN) {
517 newTag = newTag.substring(0, MAX_TAG_LEN);
519 if (NOT_ALPHANUM.matcher(newTag).find()) {
520 throw new IllegalArgumentException("tag contains non-alphanumeric characters");
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.
530 * The default value is {@code null}.
533 * @return the TAG, max length 32.
534 * @see #setTag(String)
537 public String getTag() {
542 * Get the host name used to identify this appender.
543 * @return local host name
546 private String getLocalHostname() {
547 if (localHostname == null) {
549 InetAddress addr = InetAddress.getLocalHost();
550 localHostname = addr.getHostName();
551 } catch (UnknownHostException uhe) {
552 localHostname = "UNKNOWN_HOST";
555 return localHostname;
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.
564 private String getPacketHeader(final long timeStamp) {
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, ' ');
571 buf.append(getLocalHostname());
573 if(this.tag != null) {
574 buf.append(this.tag);
577 return buf.toString();
583 * Set header or footer of layout.
584 * @param msg message body, may not be null.
586 private void sendLayoutMessage(final String 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);
596 packet = buf.toString();