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