/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.log4j.net; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.regex.Pattern; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Layout; import org.apache.log4j.helpers.SyslogQuietWriter; import org.apache.log4j.helpers.SyslogWriter; import org.apache.log4j.spi.LoggingEvent; // Contributors: Yves Bossel // Christopher Taylor /** Use SyslogAppender to send log messages to a remote syslog daemon. @author Ceki Gülcü @author Anders Kristensen */ public class SyslogAppender extends AppenderSkeleton { // The following constants are extracted from a syslog.h file // copyrighted by the Regents of the University of California // I hope nobody at Berkley gets offended. /** * Maximum length of a TAG string. */ private static final int MAX_TAG_LEN = 32; /** Kernel messages */ final static public int LOG_KERN = 0; /** Random user-level messages */ final static public int LOG_USER = 1<<3; /** Mail system */ final static public int LOG_MAIL = 2<<3; /** System daemons */ final static public int LOG_DAEMON = 3<<3; /** security/authorization messages */ final static public int LOG_AUTH = 4<<3; /** messages generated internally by syslogd */ final static public int LOG_SYSLOG = 5<<3; /** line printer subsystem */ final static public int LOG_LPR = 6<<3; /** network news subsystem */ final static public int LOG_NEWS = 7<<3; /** UUCP subsystem */ final static public int LOG_UUCP = 8<<3; /** clock daemon */ final static public int LOG_CRON = 9<<3; /** security/authorization messages (private) */ final static public int LOG_AUTHPRIV = 10<<3; /** ftp daemon */ final static public int LOG_FTP = 11<<3; // other codes through 15 reserved for system use /** reserved for local use */ final static public int LOG_LOCAL0 = 16<<3; /** reserved for local use */ final static public int LOG_LOCAL1 = 17<<3; /** reserved for local use */ final static public int LOG_LOCAL2 = 18<<3; /** reserved for local use */ final static public int LOG_LOCAL3 = 19<<3; /** reserved for local use */ final static public int LOG_LOCAL4 = 20<<3; /** reserved for local use */ final static public int LOG_LOCAL5 = 21<<3; /** reserved for local use */ final static public int LOG_LOCAL6 = 22<<3; /** reserved for local use*/ final static public int LOG_LOCAL7 = 23<<3; protected static final int SYSLOG_HOST_OI = 0; protected static final int FACILITY_OI = 1; static final String TAB = " "; static final Pattern NOT_ALPHANUM = Pattern.compile("[^\\p{Alnum}]"); // Have LOG_USER as default int syslogFacility = LOG_USER; String facilityStr; boolean facilityPrinting = false; //SyslogTracerPrintWriter stp; SyslogQuietWriter sqw; String syslogHost; /** * If true, the appender will generate the HEADER (timestamp and host name) * part of the syslog packet. * @since 1.2.15 */ private boolean header = false; /** * The TAG part of the syslog message. * * @since 1.2.18 */ private String tag = null; /** * Date format used if header = true. * @since 1.2.15 */ private final SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd HH:mm:ss ", Locale.ENGLISH); /** * Host name used to identify messages from this appender. * @since 1.2.15 */ private String localHostname; /** * Set to true after the header of the layout has been sent or if it has none. */ private boolean layoutHeaderChecked = false; public SyslogAppender() { this.initSyslogFacilityStr(); } public SyslogAppender(Layout layout, int syslogFacility) { this.layout = layout; this.syslogFacility = syslogFacility; this.initSyslogFacilityStr(); } public SyslogAppender(Layout layout, String syslogHost, int syslogFacility) { this(layout, syslogFacility); setSyslogHost(syslogHost); } /** Release any resources held by this SyslogAppender. @since 0.8.4 */ synchronized public void close() { closed = true; if (sqw != null) { try { if (layoutHeaderChecked && layout != null && layout.getFooter() != null) { sendLayoutMessage(layout.getFooter()); } sqw.close(); sqw = null; } catch(java.io.InterruptedIOException e) { Thread.currentThread().interrupt(); sqw = null; } catch(IOException e) { sqw = null; } } } private void initSyslogFacilityStr() { facilityStr = getFacilityString(this.syslogFacility); if (facilityStr == null) { System.err.println("\"" + syslogFacility + "\" is an unknown syslog facility. Defaulting to \"USER\"."); this.syslogFacility = LOG_USER; facilityStr = "user:"; } else { facilityStr += ":"; } } /** Returns the specified syslog facility as a lower-case String, e.g. "kern", "user", etc. */ public static String getFacilityString(int syslogFacility) { switch(syslogFacility) { case LOG_KERN: return "kern"; case LOG_USER: return "user"; case LOG_MAIL: return "mail"; case LOG_DAEMON: return "daemon"; case LOG_AUTH: return "auth"; case LOG_SYSLOG: return "syslog"; case LOG_LPR: return "lpr"; case LOG_NEWS: return "news"; case LOG_UUCP: return "uucp"; case LOG_CRON: return "cron"; case LOG_AUTHPRIV: return "authpriv"; case LOG_FTP: return "ftp"; case LOG_LOCAL0: return "local0"; case LOG_LOCAL1: return "local1"; case LOG_LOCAL2: return "local2"; case LOG_LOCAL3: return "local3"; case LOG_LOCAL4: return "local4"; case LOG_LOCAL5: return "local5"; case LOG_LOCAL6: return "local6"; case LOG_LOCAL7: return "local7"; default: return null; } } /** Returns the integer value corresponding to the named syslog facility, or -1 if it couldn't be recognized. @param facilityName one of the strings KERN, USER, MAIL, DAEMON, AUTH, SYSLOG, LPR, NEWS, UUCP, CRON, AUTHPRIV, FTP, LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7. The matching is case-insensitive. @since 1.1 */ public static int getFacility(String facilityName) { if(facilityName != null) { facilityName = facilityName.trim(); } if("KERN".equalsIgnoreCase(facilityName)) { return LOG_KERN; } else if("USER".equalsIgnoreCase(facilityName)) { return LOG_USER; } else if("MAIL".equalsIgnoreCase(facilityName)) { return LOG_MAIL; } else if("DAEMON".equalsIgnoreCase(facilityName)) { return LOG_DAEMON; } else if("AUTH".equalsIgnoreCase(facilityName)) { return LOG_AUTH; } else if("SYSLOG".equalsIgnoreCase(facilityName)) { return LOG_SYSLOG; } else if("LPR".equalsIgnoreCase(facilityName)) { return LOG_LPR; } else if("NEWS".equalsIgnoreCase(facilityName)) { return LOG_NEWS; } else if("UUCP".equalsIgnoreCase(facilityName)) { return LOG_UUCP; } else if("CRON".equalsIgnoreCase(facilityName)) { return LOG_CRON; } else if("AUTHPRIV".equalsIgnoreCase(facilityName)) { return LOG_AUTHPRIV; } else if("FTP".equalsIgnoreCase(facilityName)) { return LOG_FTP; } else if("LOCAL0".equalsIgnoreCase(facilityName)) { return LOG_LOCAL0; } else if("LOCAL1".equalsIgnoreCase(facilityName)) { return LOG_LOCAL1; } else if("LOCAL2".equalsIgnoreCase(facilityName)) { return LOG_LOCAL2; } else if("LOCAL3".equalsIgnoreCase(facilityName)) { return LOG_LOCAL3; } else if("LOCAL4".equalsIgnoreCase(facilityName)) { return LOG_LOCAL4; } else if("LOCAL5".equalsIgnoreCase(facilityName)) { return LOG_LOCAL5; } else if("LOCAL6".equalsIgnoreCase(facilityName)) { return LOG_LOCAL6; } else if("LOCAL7".equalsIgnoreCase(facilityName)) { return LOG_LOCAL7; } else { return -1; } } private void splitPacket(final String header, final String packet) { int byteCount = packet.getBytes().length; // // if packet is less than RFC 3164 limit // of 1024 bytes, then write it // (must allow for up 5to 5 characters in the PRI section // added by SyslogQuietWriter) if (byteCount <= 1019) { sqw.write(packet); } else { int split = header.length() + (packet.length() - header.length())/2; splitPacket(header, packet.substring(0, split) + "..."); splitPacket(header, header + "..." + packet.substring(split)); } } public void append(LoggingEvent event) { if(!isAsSevereAsThreshold(event.getLevel())) { return; } // We must not attempt to append if sqw is null. if(sqw == null) { errorHandler.error("No syslog host is set for SyslogAppedender named \""+ this.name+"\"."); return; } if (!layoutHeaderChecked) { if (layout != null && layout.getHeader() != null) { sendLayoutMessage(layout.getHeader()); } layoutHeaderChecked = true; } String hdr = getPacketHeader(event.timeStamp); String packet; if (layout == null) { packet = String.valueOf(event.getMessage()); } else { packet = layout.format(event); } if(facilityPrinting || hdr.length() > 0) { StringBuffer buf = new StringBuffer(hdr); if(facilityPrinting) { buf.append(facilityStr); } buf.append(packet); packet = buf.toString(); } sqw.setLevel(event.getLevel().getSyslogEquivalent()); // // if message has a remote likelihood of exceeding 1024 bytes // when encoded, consider splitting message into multiple packets if (packet.length() > 256) { splitPacket(hdr, packet); } else { sqw.write(packet); } if (layout == null || layout.ignoresThrowable()) { String[] s = event.getThrowableStrRep(); if (s != null) { for(int i = 0; i < s.length; i++) { if (s[i].startsWith("\t")) { sqw.write(hdr+TAB+s[i].substring(1)); } else { sqw.write(hdr+s[i]); } } } } } /** This method returns immediately as options are activated when they are set. */ public void activateOptions() { if (header) { getLocalHostname(); } if (layout != null && layout.getHeader() != null) { sendLayoutMessage(layout.getHeader()); } layoutHeaderChecked = true; } /** The SyslogAppender requires a layout. Hence, this method returns true. @since 0.8.4 */ public boolean requiresLayout() { return true; } /** The SyslogHost option is the name of the the syslog host where log output should go. A non-default port can be specified by appending a colon and port number to a host name, an IPv4 address or an IPv6 address enclosed in square brackets. WARNING If the SyslogHost is not set, then this appender will fail. */ public void setSyslogHost(final String syslogHost) { this.sqw = new SyslogQuietWriter(new SyslogWriter(syslogHost), syslogFacility, errorHandler); //this.stp = new SyslogTracerPrintWriter(sqw); this.syslogHost = syslogHost; } /** Returns the value of the SyslogHost option. */ public String getSyslogHost() { return syslogHost; } /** Set the syslog facility. This is the Facility option.

The facilityName parameter must be one of the strings KERN, USER, MAIL, DAEMON, AUTH, SYSLOG, LPR, NEWS, UUCP, CRON, AUTHPRIV, FTP, LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7. Case is unimportant. @since 0.8.1 */ public void setFacility(String facilityName) { if(facilityName == null) { return; } syslogFacility = getFacility(facilityName); if (syslogFacility == -1) { System.err.println("["+facilityName + "] is an unknown syslog facility. Defaulting to [USER]."); syslogFacility = LOG_USER; } this.initSyslogFacilityStr(); // If there is already a sqw, make it use the new facility. if(sqw != null) { sqw.setSyslogFacility(this.syslogFacility); } } /** Returns the value of the Facility option. */ public String getFacility() { return getFacilityString(syslogFacility); } /** If the FacilityPrinting option is set to true, the printed message will include the facility name of the application. It is false by default. */ public void setFacilityPrinting(boolean on) { facilityPrinting = on; } /** Returns the value of the FacilityPrinting option. */ public boolean getFacilityPrinting() { return facilityPrinting; } /** * If true, the appender will generate the HEADER part (that is, timestamp and host name) * of the syslog packet. Default value is false for compatibility with existing behavior, * however should be true unless there is a specific justification. * @since 1.2.15 */ public final boolean getHeader() { return header; } /** * Returns whether the appender produces the HEADER part (that is, timestamp and host name) * of the syslog packet. * @since 1.2.15 */ public final void setHeader(final boolean val) { header = val; } /** * Sets the Tag option. * *

* If non-{@code null}, the printed HEADER will include the specified tag followed by a colon. If {@code null}, then no tag is printed. *

*

* The default value is {@code null}. *

* * @param tag * the TAG to be printed out with the header * @see #getTag() * @since 1.2.18 */ public void setTag(final String tag) { String newTag = tag; if (newTag != null) { if (newTag.length() > MAX_TAG_LEN) { newTag = newTag.substring(0, MAX_TAG_LEN); } if (NOT_ALPHANUM.matcher(newTag).find()) { throw new IllegalArgumentException("tag contains non-alphanumeric characters"); } } this.tag = newTag; } /** * 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. *

* The default value is {@code null}. *

* * @return the TAG, max length 32. * @see #setTag(String) * @since 1.2.18 */ public String getTag() { return this.tag; } /** * Get the host name used to identify this appender. * @return local host name * @since 1.2.15 */ private String getLocalHostname() { if (localHostname == null) { try { InetAddress addr = InetAddress.getLocalHost(); localHostname = addr.getHostName(); } catch (UnknownHostException uhe) { localHostname = "UNKNOWN_HOST"; } } return localHostname; } /** * Gets HEADER portion of packet. * @param timeStamp number of milliseconds after the standard base time. * @return HEADER portion of packet, will be zero-length string if header is false. * @since 1.2.15 */ private String getPacketHeader(final long timeStamp) { if (header) { StringBuffer buf = new StringBuffer(dateFormat.format(new Date(timeStamp))); // RFC 3164 says leading space, not leading zero on days 1-9 if (buf.charAt(4) == '0') { buf.setCharAt(4, ' '); } buf.append(getLocalHostname()); buf.append(' '); if(this.tag != null) { buf.append(this.tag); buf.append(": "); } return buf.toString(); } return ""; } /** * Set header or footer of layout. * @param msg message body, may not be null. */ private void sendLayoutMessage(final String msg) { if (sqw != null) { String packet = msg; String hdr = getPacketHeader(new Date().getTime()); if(facilityPrinting || hdr.length() > 0) { StringBuffer buf = new StringBuffer(hdr); if(facilityPrinting) { buf.append(facilityStr); } buf.append(msg); packet = buf.toString(); } sqw.setLevel(6); sqw.write(packet); } } }