001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.log4j.net; 019 020import org.apache.log4j.AppenderSkeleton; 021import org.apache.log4j.Layout; 022import org.apache.log4j.helpers.SyslogQuietWriter; 023import org.apache.log4j.helpers.SyslogWriter; 024import org.apache.log4j.spi.LoggingEvent; 025 026import java.text.SimpleDateFormat; 027import java.util.Date; 028import java.util.Locale; 029import java.net.InetAddress; 030import java.net.UnknownHostException; 031import java.io.IOException; 032 033// Contributors: Yves Bossel <ybossel@opengets.cl> 034// Christopher Taylor <cstaylor@pacbell.net> 035 036/** 037 Use SyslogAppender to send log messages to a remote syslog daemon. 038 039 @author Ceki Gülcü 040 @author Anders Kristensen 041 */ 042public class SyslogAppender extends AppenderSkeleton { 043 // The following constants are extracted from a syslog.h file 044 // copyrighted by the Regents of the University of California 045 // I hope nobody at Berkley gets offended. 046 047 /** Kernel messages */ 048 final static public int LOG_KERN = 0; 049 /** Random user-level messages */ 050 final static public int LOG_USER = 1<<3; 051 /** Mail system */ 052 final static public int LOG_MAIL = 2<<3; 053 /** System daemons */ 054 final static public int LOG_DAEMON = 3<<3; 055 /** security/authorization messages */ 056 final static public int LOG_AUTH = 4<<3; 057 /** messages generated internally by syslogd */ 058 final static public int LOG_SYSLOG = 5<<3; 059 060 /** line printer subsystem */ 061 final static public int LOG_LPR = 6<<3; 062 /** network news subsystem */ 063 final static public int LOG_NEWS = 7<<3; 064 /** UUCP subsystem */ 065 final static public int LOG_UUCP = 8<<3; 066 /** clock daemon */ 067 final static public int LOG_CRON = 9<<3; 068 /** security/authorization messages (private) */ 069 final static public int LOG_AUTHPRIV = 10<<3; 070 /** ftp daemon */ 071 final static public int LOG_FTP = 11<<3; 072 073 // other codes through 15 reserved for system use 074 /** reserved for local use */ 075 final static public int LOG_LOCAL0 = 16<<3; 076 /** reserved for local use */ 077 final static public int LOG_LOCAL1 = 17<<3; 078 /** reserved for local use */ 079 final static public int LOG_LOCAL2 = 18<<3; 080 /** reserved for local use */ 081 final static public int LOG_LOCAL3 = 19<<3; 082 /** reserved for local use */ 083 final static public int LOG_LOCAL4 = 20<<3; 084 /** reserved for local use */ 085 final static public int LOG_LOCAL5 = 21<<3; 086 /** reserved for local use */ 087 final static public int LOG_LOCAL6 = 22<<3; 088 /** reserved for local use*/ 089 final static public int LOG_LOCAL7 = 23<<3; 090 091 protected static final int SYSLOG_HOST_OI = 0; 092 protected static final int FACILITY_OI = 1; 093 094 static final String TAB = " "; 095 096 // Have LOG_USER as default 097 int syslogFacility = LOG_USER; 098 String facilityStr; 099 boolean facilityPrinting = false; 100 101 //SyslogTracerPrintWriter stp; 102 SyslogQuietWriter sqw; 103 String syslogHost; 104 105 /** 106 * If true, the appender will generate the HEADER (timestamp and host name) 107 * part of the syslog packet. 108 * @since 1.2.15 109 */ 110 private boolean header = false; 111 /** 112 * Date format used if header = true. 113 * @since 1.2.15 114 */ 115 private final SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd HH:mm:ss ", Locale.ENGLISH); 116 /** 117 * Host name used to identify messages from this appender. 118 * @since 1.2.15 119 */ 120 private String localHostname; 121 122 /** 123 * Set to true after the header of the layout has been sent or if it has none. 124 */ 125 private boolean layoutHeaderChecked = false; 126 127 public 128 SyslogAppender() { 129 this.initSyslogFacilityStr(); 130 } 131 132 public 133 SyslogAppender(Layout layout, int syslogFacility) { 134 this.layout = layout; 135 this.syslogFacility = syslogFacility; 136 this.initSyslogFacilityStr(); 137 } 138 139 public 140 SyslogAppender(Layout layout, String syslogHost, int syslogFacility) { 141 this(layout, syslogFacility); 142 setSyslogHost(syslogHost); 143 } 144 145 /** 146 Release any resources held by this SyslogAppender. 147 148 @since 0.8.4 149 */ 150 synchronized 151 public 152 void close() { 153 closed = true; 154 if (sqw != null) { 155 try { 156 if (layoutHeaderChecked && layout != null && layout.getFooter() != null) { 157 sendLayoutMessage(layout.getFooter()); 158 } 159 sqw.close(); 160 sqw = null; 161 } catch(java.io.InterruptedIOException e) { 162 Thread.currentThread().interrupt(); 163 sqw = null; 164 } catch(IOException e) { 165 sqw = null; 166 } 167 } 168 } 169 170 private 171 void initSyslogFacilityStr() { 172 facilityStr = getFacilityString(this.syslogFacility); 173 174 if (facilityStr == null) { 175 System.err.println("\"" + syslogFacility + 176 "\" is an unknown syslog facility. Defaulting to \"USER\"."); 177 this.syslogFacility = LOG_USER; 178 facilityStr = "user:"; 179 } else { 180 facilityStr += ":"; 181 } 182 } 183 184 /** 185 Returns the specified syslog facility as a lower-case String, 186 e.g. "kern", "user", etc. 187 */ 188 public 189 static 190 String getFacilityString(int syslogFacility) { 191 switch(syslogFacility) { 192 case LOG_KERN: return "kern"; 193 case LOG_USER: return "user"; 194 case LOG_MAIL: return "mail"; 195 case LOG_DAEMON: return "daemon"; 196 case LOG_AUTH: return "auth"; 197 case LOG_SYSLOG: return "syslog"; 198 case LOG_LPR: return "lpr"; 199 case LOG_NEWS: return "news"; 200 case LOG_UUCP: return "uucp"; 201 case LOG_CRON: return "cron"; 202 case LOG_AUTHPRIV: return "authpriv"; 203 case LOG_FTP: return "ftp"; 204 case LOG_LOCAL0: return "local0"; 205 case LOG_LOCAL1: return "local1"; 206 case LOG_LOCAL2: return "local2"; 207 case LOG_LOCAL3: return "local3"; 208 case LOG_LOCAL4: return "local4"; 209 case LOG_LOCAL5: return "local5"; 210 case LOG_LOCAL6: return "local6"; 211 case LOG_LOCAL7: return "local7"; 212 default: return null; 213 } 214 } 215 216 /** 217 Returns the integer value corresponding to the named syslog 218 facility, or -1 if it couldn't be recognized. 219 220 @param facilityName one of the strings KERN, USER, MAIL, DAEMON, 221 AUTH, SYSLOG, LPR, NEWS, UUCP, CRON, AUTHPRIV, FTP, LOCAL0, 222 LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7. 223 The matching is case-insensitive. 224 225 @since 1.1 226 */ 227 public 228 static 229 int getFacility(String facilityName) { 230 if(facilityName != null) { 231 facilityName = facilityName.trim(); 232 } 233 if("KERN".equalsIgnoreCase(facilityName)) { 234 return LOG_KERN; 235 } else if("USER".equalsIgnoreCase(facilityName)) { 236 return LOG_USER; 237 } else if("MAIL".equalsIgnoreCase(facilityName)) { 238 return LOG_MAIL; 239 } else if("DAEMON".equalsIgnoreCase(facilityName)) { 240 return LOG_DAEMON; 241 } else if("AUTH".equalsIgnoreCase(facilityName)) { 242 return LOG_AUTH; 243 } else if("SYSLOG".equalsIgnoreCase(facilityName)) { 244 return LOG_SYSLOG; 245 } else if("LPR".equalsIgnoreCase(facilityName)) { 246 return LOG_LPR; 247 } else if("NEWS".equalsIgnoreCase(facilityName)) { 248 return LOG_NEWS; 249 } else if("UUCP".equalsIgnoreCase(facilityName)) { 250 return LOG_UUCP; 251 } else if("CRON".equalsIgnoreCase(facilityName)) { 252 return LOG_CRON; 253 } else if("AUTHPRIV".equalsIgnoreCase(facilityName)) { 254 return LOG_AUTHPRIV; 255 } else if("FTP".equalsIgnoreCase(facilityName)) { 256 return LOG_FTP; 257 } else if("LOCAL0".equalsIgnoreCase(facilityName)) { 258 return LOG_LOCAL0; 259 } else if("LOCAL1".equalsIgnoreCase(facilityName)) { 260 return LOG_LOCAL1; 261 } else if("LOCAL2".equalsIgnoreCase(facilityName)) { 262 return LOG_LOCAL2; 263 } else if("LOCAL3".equalsIgnoreCase(facilityName)) { 264 return LOG_LOCAL3; 265 } else if("LOCAL4".equalsIgnoreCase(facilityName)) { 266 return LOG_LOCAL4; 267 } else if("LOCAL5".equalsIgnoreCase(facilityName)) { 268 return LOG_LOCAL5; 269 } else if("LOCAL6".equalsIgnoreCase(facilityName)) { 270 return LOG_LOCAL6; 271 } else if("LOCAL7".equalsIgnoreCase(facilityName)) { 272 return LOG_LOCAL7; 273 } else { 274 return -1; 275 } 276 } 277 278 279 private void splitPacket(final String header, final String packet) { 280 int byteCount = packet.getBytes().length; 281 // 282 // if packet is less than RFC 3164 limit 283 // of 1024 bytes, then write it 284 // (must allow for up 5to 5 characters in the PRI section 285 // added by SyslogQuietWriter) 286 if (byteCount <= 1019) { 287 sqw.write(packet); 288 } else { 289 int split = header.length() + (packet.length() - header.length())/2; 290 splitPacket(header, packet.substring(0, split) + "..."); 291 splitPacket(header, header + "..." + packet.substring(split)); 292 } 293 } 294 295 public 296 void append(LoggingEvent event) { 297 298 if(!isAsSevereAsThreshold(event.getLevel())) 299 return; 300 301 // We must not attempt to append if sqw is null. 302 if(sqw == null) { 303 errorHandler.error("No syslog host is set for SyslogAppedender named \""+ 304 this.name+"\"."); 305 return; 306 } 307 308 if (!layoutHeaderChecked) { 309 if (layout != null && layout.getHeader() != null) { 310 sendLayoutMessage(layout.getHeader()); 311 } 312 layoutHeaderChecked = true; 313 } 314 315 String hdr = getPacketHeader(event.timeStamp); 316 String packet; 317 if (layout == null) { 318 packet = String.valueOf(event.getMessage()); 319 } else { 320 packet = layout.format(event); 321 } 322 if(facilityPrinting || hdr.length() > 0) { 323 StringBuffer buf = new StringBuffer(hdr); 324 if(facilityPrinting) { 325 buf.append(facilityStr); 326 } 327 buf.append(packet); 328 packet = buf.toString(); 329 } 330 331 sqw.setLevel(event.getLevel().getSyslogEquivalent()); 332 // 333 // if message has a remote likelihood of exceeding 1024 bytes 334 // when encoded, consider splitting message into multiple packets 335 if (packet.length() > 256) { 336 splitPacket(hdr, packet); 337 } else { 338 sqw.write(packet); 339 } 340 341 if (layout == null || layout.ignoresThrowable()) { 342 String[] s = event.getThrowableStrRep(); 343 if (s != null) { 344 for(int i = 0; i < s.length; i++) { 345 if (s[i].startsWith("\t")) { 346 sqw.write(hdr+TAB+s[i].substring(1)); 347 } else { 348 sqw.write(hdr+s[i]); 349 } 350 } 351 } 352 } 353 } 354 355 /** 356 This method returns immediately as options are activated when they 357 are set. 358 */ 359 public 360 void activateOptions() { 361 if (header) { 362 getLocalHostname(); 363 } 364 if (layout != null && layout.getHeader() != null) { 365 sendLayoutMessage(layout.getHeader()); 366 } 367 layoutHeaderChecked = true; 368 } 369 370 /** 371 The SyslogAppender requires a layout. Hence, this method returns 372 <code>true</code>. 373 374 @since 0.8.4 */ 375 public 376 boolean requiresLayout() { 377 return true; 378 } 379 380 /** 381 The <b>SyslogHost</b> option is the name of the the syslog host 382 where log output should go. A non-default port can be specified by 383 appending a colon and port number to a host name, 384 an IPv4 address or an IPv6 address enclosed in square brackets. 385 386 <b>WARNING</b> If the SyslogHost is not set, then this appender 387 will fail. 388 */ 389 public 390 void setSyslogHost(final String syslogHost) { 391 this.sqw = new SyslogQuietWriter(new SyslogWriter(syslogHost), 392 syslogFacility, errorHandler); 393 //this.stp = new SyslogTracerPrintWriter(sqw); 394 this.syslogHost = syslogHost; 395 } 396 397 /** 398 Returns the value of the <b>SyslogHost</b> option. 399 */ 400 public 401 String getSyslogHost() { 402 return syslogHost; 403 } 404 405 /** 406 Set the syslog facility. This is the <b>Facility</b> option. 407 408 <p>The <code>facilityName</code> parameter must be one of the 409 strings KERN, USER, MAIL, DAEMON, AUTH, SYSLOG, LPR, NEWS, UUCP, 410 CRON, AUTHPRIV, FTP, LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4, 411 LOCAL5, LOCAL6, LOCAL7. Case is unimportant. 412 413 @since 0.8.1 */ 414 public 415 void setFacility(String facilityName) { 416 if(facilityName == null) 417 return; 418 419 syslogFacility = getFacility(facilityName); 420 if (syslogFacility == -1) { 421 System.err.println("["+facilityName + 422 "] is an unknown syslog facility. Defaulting to [USER]."); 423 syslogFacility = LOG_USER; 424 } 425 426 this.initSyslogFacilityStr(); 427 428 // If there is already a sqw, make it use the new facility. 429 if(sqw != null) { 430 sqw.setSyslogFacility(this.syslogFacility); 431 } 432 } 433 434 /** 435 Returns the value of the <b>Facility</b> option. 436 */ 437 public 438 String getFacility() { 439 return getFacilityString(syslogFacility); 440 } 441 442 /** 443 If the <b>FacilityPrinting</b> option is set to true, the printed 444 message will include the facility name of the application. It is 445 <em>false</em> by default. 446 */ 447 public 448 void setFacilityPrinting(boolean on) { 449 facilityPrinting = on; 450 } 451 452 /** 453 Returns the value of the <b>FacilityPrinting</b> option. 454 */ 455 public 456 boolean getFacilityPrinting() { 457 return facilityPrinting; 458 } 459 460 /** 461 * If true, the appender will generate the HEADER part (that is, timestamp and host name) 462 * of the syslog packet. Default value is false for compatibility with existing behavior, 463 * however should be true unless there is a specific justification. 464 * @since 1.2.15 465 */ 466 public final boolean getHeader() { 467 return header; 468 } 469 470 /** 471 * Returns whether the appender produces the HEADER part (that is, timestamp and host name) 472 * of the syslog packet. 473 * @since 1.2.15 474 */ 475 public final void setHeader(final boolean val) { 476 header = val; 477 } 478 479 /** 480 * Get the host name used to identify this appender. 481 * @return local host name 482 * @since 1.2.15 483 */ 484 private String getLocalHostname() { 485 if (localHostname == null) { 486 try { 487 InetAddress addr = InetAddress.getLocalHost(); 488 localHostname = addr.getHostName(); 489 } catch (UnknownHostException uhe) { 490 localHostname = "UNKNOWN_HOST"; 491 } 492 } 493 return localHostname; 494 } 495 496 /** 497 * Gets HEADER portion of packet. 498 * @param timeStamp number of milliseconds after the standard base time. 499 * @return HEADER portion of packet, will be zero-length string if header is false. 500 * @since 1.2.15 501 */ 502 private String getPacketHeader(final long timeStamp) { 503 if (header) { 504 StringBuffer buf = new StringBuffer(dateFormat.format(new Date(timeStamp))); 505 // RFC 3164 says leading space, not leading zero on days 1-9 506 if (buf.charAt(4) == '0') { 507 buf.setCharAt(4, ' '); 508 } 509 buf.append(getLocalHostname()); 510 buf.append(' '); 511 return buf.toString(); 512 } 513 return ""; 514 } 515 516 /** 517 * Set header or footer of layout. 518 * @param msg message body, may not be null. 519 */ 520 private void sendLayoutMessage(final String msg) { 521 if (sqw != null) { 522 String packet = msg; 523 String hdr = getPacketHeader(new Date().getTime()); 524 if(facilityPrinting || hdr.length() > 0) { 525 StringBuffer buf = new StringBuffer(hdr); 526 if(facilityPrinting) { 527 buf.append(facilityStr); 528 } 529 buf.append(msg); 530 packet = buf.toString(); 531 } 532 sqw.setLevel(6); 533 sqw.write(packet); 534 } 535 } 536}