| /* |
| * 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 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; |
| |
| import java.text.SimpleDateFormat; |
| import java.util.Date; |
| import java.util.Locale; |
| import java.net.InetAddress; |
| import java.net.UnknownHostException; |
| |
| // Contributors: Yves Bossel <ybossel@opengets.cl> |
| // Christopher Taylor <cstaylor@pacbell.net> |
| |
| /** |
| 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. |
| |
| /** 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 = " "; |
| |
| // 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; |
| /** |
| * 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.IOException ex) { |
| 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 = 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.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 |
| <code>true</code>. |
| |
| @since 0.8.4 */ |
| public |
| boolean requiresLayout() { |
| return true; |
| } |
| |
| /** |
| The <b>SyslogHost</b> 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. |
| |
| <b>WARNING</b> 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 <b>SyslogHost</b> option. |
| */ |
| public |
| String getSyslogHost() { |
| return syslogHost; |
| } |
| |
| /** |
| Set the syslog facility. This is the <b>Facility</b> option. |
| |
| <p>The <code>facilityName</code> 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 <b>Facility</b> option. |
| */ |
| public |
| String getFacility() { |
| return getFacilityString(syslogFacility); |
| } |
| |
| /** |
| If the <b>FacilityPrinting</b> option is set to true, the printed |
| message will include the facility name of the application. It is |
| <em>false</em> by default. |
| */ |
| public |
| void setFacilityPrinting(boolean on) { |
| facilityPrinting = on; |
| } |
| |
| /** |
| Returns the value of the <b>FacilityPrinting</b> 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; |
| } |
| |
| /** |
| * 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(' '); |
| 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); |
| } |
| } |
| } |