001: package com.protomatter.syslog;
002:
003: /**
004: * {{{ The Protomatter Software License, Version 1.0
005: * derived from The Apache Software License, Version 1.1
006: *
007: * Copyright (c) 1998-2002 Nate Sammons. All rights reserved.
008: *
009: * Redistribution and use in source and binary forms, with or without
010: * modification, are permitted provided that the following conditions
011: * are met:
012: *
013: * 1. Redistributions of source code must retain the above copyright
014: * notice, this list of conditions and the following disclaimer.
015: *
016: * 2. Redistributions in binary form must reproduce the above copyright
017: * notice, this list of conditions and the following disclaimer in
018: * the documentation and/or other materials provided with the
019: * distribution.
020: *
021: * 3. The end-user documentation included with the redistribution,
022: * if any, must include the following acknowledgment:
023: * "This product includes software developed for the
024: * Protomatter Software Project
025: * (http://protomatter.sourceforge.net/)."
026: * Alternately, this acknowledgment may appear in the software itself,
027: * if and wherever such third-party acknowledgments normally appear.
028: *
029: * 4. The names "Protomatter" and "Protomatter Software Project" must
030: * not be used to endorse or promote products derived from this
031: * software without prior written permission. For written
032: * permission, please contact support@protomatter.com.
033: *
034: * 5. Products derived from this software may not be called "Protomatter",
035: * nor may "Protomatter" appear in their name, without prior written
036: * permission of the Protomatter Software Project
037: * (support@protomatter.com).
038: *
039: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
040: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
041: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
042: * DISCLAIMED. IN NO EVENT SHALL THE PROTOMATTER SOFTWARE PROJECT OR
043: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
044: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
045: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
046: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
047: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
048: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
049: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
050: * SUCH DAMAGE. }}}
051: */
052:
053: import java.io.PrintWriter;
054: import java.util.*;
055: import java.text.*;
056: import java.net.*;
057: import java.io.*;
058: import java.lang.reflect.*;
059:
060: import com.protomatter.util.*;
061:
062: /**
063: * A simple log entry formatter. This class is used by several of
064: * the included <tt>Syslogger</tt> implementations to format their
065: * log entries.
066: *
067: * @see com.protomatter.syslog.xml.SimpleSyslogTextFormatter_Helper XML configuration class
068: */
069: public class SimpleSyslogTextFormatter implements SyslogTextFormatter {
070: private static String crString = System
071: .getProperty("line.separator");
072: private static char[] cr = System.getProperty("line.separator")
073: .toCharArray();
074: private static char[] rb = " [".toCharArray();
075: private static char[] lb = "] ".toCharArray();
076: private static char rb_ns = '[';
077: private static char[] sp = " ".toCharArray();
078: private static char[] parens = "()".toCharArray();
079: private static char sp_1 = ' ';
080: private static char dot = '.';
081: private static char colon = ':';
082:
083: private static char[] DEBUG = "DBUG".toCharArray();
084: private static char[] INFO = "INFO".toCharArray();
085: private static char[] WARNING = "WARN".toCharArray();
086: private static char[] ERROR = "EROR".toCharArray();
087: private static char[] FATAL = "FTAL".toCharArray();
088: private static char[] UNKNOWN_LEVEL = "????".toCharArray();
089:
090: private static String CH_ALL_CHANNEL = "ALL_CHANNEL";
091: private static String CH_DEF_CHANNEL = "DEFAULT_CHANNEL";
092:
093: private DateFormat dateFormat = null;
094: private TimeZone dateFormatTimeZone = TimeZone.getDefault();
095: private String dateFormatString = null;
096: private long lastDate = -1;
097: private char[] lastDateString = null;
098: private int dateFormatCacheTime = 1000;
099: private boolean showCaller = true;
100: private boolean showChannel = false;
101: private boolean showThreadName = false;
102: private boolean showHostName = false;
103:
104: private int classWidth = 20;
105: private int hostWidth = 15;
106: private int channelWidth = 15;
107: private int threadWidth = 15;
108:
109: /**
110: * Default constructor.
111: */
112: public SimpleSyslogTextFormatter() {
113: super ();
114: setDateFormat("MM/dd/yyyy HH:mm:ss");
115: }
116:
117: /**
118: * Format the given log entry.
119: */
120: public void formatLogEntry(StringBuffer b, SyslogMessage message) {
121: synchronized (b) {
122: b.append(formatDate(message.time));
123: b.append(rb);
124: b.append(getStringForLevel(message.level));
125: b.append(lb);
126:
127: if (showChannel) {
128: if (message.channel.equals(Syslog.ALL_CHANNEL)) {
129: b.append(rb_ns);
130: justify(b, CH_ALL_CHANNEL, getChannelWidth());
131: b.append(lb);
132: } else if (message.channel
133: .equals(Syslog.DEFAULT_CHANNEL)) {
134: b.append(rb_ns);
135: justify(b, CH_DEF_CHANNEL, getChannelWidth());
136: b.append(lb);
137: } else {
138: b.append(rb_ns);
139: justify(b, message.channel, getChannelWidth());
140: b.append(lb);
141: }
142: }
143:
144: if (showHostName) {
145: justify(b, getHostname(message.host), getHostWidth());
146: b.append(sp_1);
147: }
148:
149: if (showThreadName) {
150: b.append(rb_ns);
151: b.append("T:");
152: justify(b, message.threadName, getThreadWidth());
153: b.append(lb);
154: }
155: if (showCaller) {
156: formatLoggerClassName(b, message);
157: }
158:
159: if (message.msg != null) {
160: b.append(sp);
161: b.append(message.msg);
162: }
163: b.append(cr);
164: if (message.detail != null) {
165: formatMessageDetail(b, message);
166: }
167: }
168: }
169:
170: public void formatLoggerClassName(StringBuffer b,
171: SyslogMessage message) {
172: trimFromLastPeriod(b, message.loggerClassname,
173: message.callingMethodName,
174: message.callingMethodLineNumber, getClassWidth());
175: }
176:
177: public void formatMessageDetail(StringBuffer b,
178: SyslogMessage message) {
179: if (message.detail == null)
180: return;
181:
182: String temp = null;
183:
184: // include stack traces in the output if it's a Throwable
185: if (message.detail instanceof Throwable) {
186: ByteArrayOutputStream bs = new ByteArrayOutputStream();
187: PrintWriter pw = new PrintWriter(bs);
188: Throwable e = (Throwable) message.detail;
189: Throwable e2 = e;
190: Object[] junk = null;
191: String methodCalled = null;
192: while (e2 != null) {
193: if (methodCalled != null) {
194: pw.print(methodCalled);
195: pw.print("(): ");
196: }
197: e2.printStackTrace(pw);
198: junk = getNextException(e2);
199: e2 = (Throwable) junk[1];
200: methodCalled = (String) junk[0];
201: }
202: pw.flush();
203: temp = bs.toString();
204: b.append(temp);
205: if (!temp.endsWith(crString))
206: b.append(cr);
207: } else {
208: temp = message.detail.toString();
209: b.append(temp);
210: if (!temp.endsWith(crString))
211: b.append(cr);
212: }
213: }
214:
215: /**
216: * Get the "next" exception in this series. This method
217: * will look for a no-arg method on the given object that
218: * returns a subclass of throwable. It will skip the
219: * "fillInStackTrace" method. The return type is
220: * a two-element object array. The first element is
221: * the method name (or null) that was called to get
222: * the next exception, and the second element is the
223: * instance of the Throwable.
224: */
225: protected Object[] getNextException(Throwable t) {
226: if (t == null)
227: return null;
228: Method methods[] = t.getClass().getMethods();
229: Class pt[] = null;
230: Class rt = null;
231: boolean isFIST = false;
232: String name = null;
233: Object junk[] = new Object[2];
234:
235: for (int i = 0; i < methods.length; i++) {
236: rt = methods[i].getReturnType();
237: pt = methods[i].getParameterTypes();
238: name = methods[i].getName();
239: isFIST = name.equals("fillInStackTrace"); // skip this guy
240: if (!isFIST && pt.length == 0
241: && Throwable.class.isAssignableFrom(rt)) {
242: try {
243: junk[0] = name;
244: junk[1] = (Throwable) methods[i].invoke(t,
245: new Object[0]);
246: return junk;
247: } catch (Throwable x) {
248: // OK, give up.
249: return junk;
250: }
251: }
252: }
253: return junk;
254: }
255:
256: protected char[] getStringForLevel(int level) {
257: switch (level) {
258: case Syslog.DEBUG:
259: return DEBUG;
260: case Syslog.INFO:
261: return INFO;
262: case Syslog.WARNING:
263: return WARNING;
264: case Syslog.ERROR:
265: return ERROR;
266: case Syslog.FATAL:
267: return FATAL;
268: default:
269: return UNKNOWN_LEVEL;
270: }
271: }
272:
273: public String getHostname(InetAddress host) {
274: if (host == null)
275: return "<null>";
276:
277: String ip = host.getHostAddress();
278: String name = host.getHostName();
279: if (ip.equals(name))
280: return ip;
281:
282: int idx = name.indexOf(".");
283: if (idx == -1)
284: return name;
285: return name.substring(0, idx);
286: }
287:
288: /**
289: * Set the format for logging dates.
290: */
291: public void setDateFormat(String format) {
292: this .dateFormatString = format;
293: this .dateFormat = new SimpleDateFormat(format);
294: setDateFormatTimezone(TimeZone.getDefault());
295: resetDateFormat();
296: }
297:
298: /**
299: * Get the format for logging dates.
300: */
301: public String getDateFormat() {
302: return this .dateFormatString;
303: }
304:
305: /**
306: * Set wether we should show the host name in the output.
307: * If this is set to <tt>true</tt> and the hostname
308: * has not been set on Syslog yet, the <tt>Syslog.setLocalHostName()</tt>
309: * method is called.
310: */
311: public void setShowHostName(boolean showHostName) {
312: this .showHostName = showHostName;
313: if (showHostName && (Syslog.getLocalHostName() == null)) {
314: Syslog.setLocalHostName();
315: }
316: }
317:
318: /**
319: * Get wether we should show the host name in the output.
320: */
321: public boolean getShowHostName() {
322: return this .showHostName;
323: }
324:
325: /**
326: * Set wether we should show the thread name in the output.
327: */
328: public void setShowThreadName(boolean showThreadName) {
329: this .showThreadName = showThreadName;
330: }
331:
332: /**
333: * Get wether we should show the thread name in the output.
334: */
335: public boolean getShowThreadName() {
336: return this .showThreadName;
337: }
338:
339: /**
340: * Set wether we should show the caller name in the output.
341: */
342: public void setShowCaller(boolean showCaller) {
343: this .showCaller = showCaller;
344: }
345:
346: /**
347: * Get wether we should show the caller name in the output.
348: */
349: public boolean getShowCaller() {
350: return this .showCaller;
351: }
352:
353: /**
354: * Set wether we should show the channel name in the output.
355: */
356: public void setShowChannel(boolean showChannel) {
357: this .showChannel = showChannel;
358: }
359:
360: /**
361: * Get wether we should show the channel name in the output.
362: */
363: public boolean getShowChannel() {
364: return this .showChannel;
365: }
366:
367: /**
368: * Set the number of milliseconds a date format should
369: * be cached. This is also the resolution of the timestamp
370: * in log entries. Default is 1000ms (1 second).
371: */
372: public void setDateFormatCacheTime(int cacheTime) {
373: this .dateFormatCacheTime = cacheTime;
374: }
375:
376: /**
377: * Get the number of milliseconds a date format should
378: * be cached. This is also the resolution of the timestamp
379: * in log entries. Default is 1000ms (1 second).
380: */
381: public int getDateFormatCacheTime() {
382: return this .dateFormatCacheTime;
383: }
384:
385: /**
386: * Set the timezone of the date format. Default
387: * is <tt>TimeZone.getDefault()</tt>.
388: */
389: public void setDateFormatTimezone(TimeZone zone) {
390: this .dateFormatTimeZone = zone;
391: this .dateFormat.setTimeZone(zone);
392: }
393:
394: /**
395: * Get the timezone of the date format.
396: */
397: public TimeZone getDateFormatTimezone() {
398: return this .dateFormatTimeZone;
399: }
400:
401: /**
402: * Format the given date with the dateformat that's been set.
403: * This will cache the date until it's been long enough
404: * for the date format cache time to have expired.
405: *
406: * @see #setDateFormatCacheTime
407: */
408: protected char[] formatDate(long theDate) {
409: // do the check outside a synchronized block so we
410: // don't sync unless absolutely necessary.
411: if (lastDate == -1 || theDate > lastDate + dateFormatCacheTime) {
412: synchronized (dateFormat) {
413: // check again now that we're synchronized.
414: if (lastDate == -1
415: || theDate > lastDate + dateFormatCacheTime) {
416: lastDateString = dateFormat.format(
417: new Date(theDate)).toCharArray();
418: lastDate = theDate;
419: }
420: }
421: }
422: return lastDateString;
423: }
424:
425: /**
426: * Given something like "foo.bar.Baz" this will return "Baz".
427: */
428: protected void trimFromLastPeriod(StringBuffer b, String s,
429: int width) {
430: trimFromLastPeriod(b, s, null,
431: StackTraceInfo.LINE_NUMBER_UNKNOWN, width);
432: }
433:
434: /**
435: * Given something like "foo.bar.Baz" this will return "Baz".
436: */
437: protected void trimFromLastPeriod(StringBuffer b, String className,
438: String method, int line, int width) {
439: char data[] = (className == null) ? new char[0] : className
440: .toCharArray();
441:
442: int i = data.length;
443: for (; --i >= 0 && data[i] != '.';)
444: ;
445:
446: i++;
447: int len = data.length - i;
448: b.append(data, i, len);
449: len = width - len;
450:
451: if (method != null) {
452: b.append(dot);
453: b.append(method);
454: b.append(parens);
455: len -= 3; // dot + parens
456: len -= method.length();
457: if (line != StackTraceInfo.LINE_NUMBER_UNKNOWN) {
458: b.append(colon);
459: String lineString = String.valueOf(line);
460: b.append(lineString);
461: len--;
462: len -= lineString.length();
463: }
464: }
465:
466: for (; --len >= 0;)
467: b.append(sp_1);
468: }
469:
470: /**
471: * Trim a string after the last "." (dot).
472: */
473: protected String trimFromLastPeriod(String s) {
474: int pos = s.lastIndexOf('.');
475: if (pos >= 0)
476: return s.substring(pos + 1);
477: else
478: return s;
479: }
480:
481: /**
482: * Reset the <tt>formatDate(...)</tt> method so that it's
483: * guaranteed to not return a cached date string the
484: * next time it's called.
485: */
486: public void resetDateFormat() {
487: this .lastDate = -1;
488: }
489:
490: /**
491: * Get the log header. This simply returns an empty string.
492: */
493: public String getLogHeader() {
494: return "";
495: }
496:
497: /**
498: * Get the log footer. This simply returns an empty string.
499: */
500: public String getLogFooter() {
501: return "";
502: }
503:
504: /**
505: * Get the width of the caller class name field.
506: */
507: public int getClassWidth() {
508: return this .classWidth;
509: }
510:
511: /**
512: * Set the width of the caller class name field.
513: */
514: public void setClassWidth(int classWidth) {
515: this .classWidth = classWidth;
516: }
517:
518: /**
519: * Get the width of the hostname field.
520: */
521: public int getHostWidth() {
522: return this .hostWidth;
523: }
524:
525: /**
526: * Set the width of the hostname field.
527: */
528: public void setHostWidth(int hostWidth) {
529: this .hostWidth = hostWidth;
530: }
531:
532: /**
533: * Get the width of the channel field.
534: */
535: public int getChannelWidth() {
536: return this .channelWidth;
537: }
538:
539: /**
540: * Set the width of the channel field.
541: */
542: public void setChannelWidth(int channelWidth) {
543: this .channelWidth = channelWidth;
544: }
545:
546: /**
547: * Get the width of the thread field.
548: */
549: public int getThreadWidth() {
550: return this .threadWidth;
551: }
552:
553: /**
554: * Set the width of the thread field.
555: */
556: public void setThreadWidth(int threadWidth) {
557: this .threadWidth = threadWidth;
558: }
559:
560: /**
561: * Justify text to given width in the buffer.
562: */
563: protected void justify(StringBuffer out, StringBuffer in, int width) {
564: justify(out, in.toString(), width);
565: }
566:
567: /**
568: * Justify text to given width in the buffer.
569: */
570: protected void justify(StringBuffer out, String in, int width) {
571: out.append(in);
572: for (int i = in.length(); i < width; i++)
573: out.append(" ");
574: }
575: }
|