001: /* ====================================================================
002: * The Apache Software License, Version 1.1
003: *
004: * Copyright (c) 1997-2003 The Apache Software Foundation. All rights
005: * reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The end-user documentation included with the redistribution,
020: * if any, must include the following acknowledgment:
021: * "This product includes software developed by the
022: * Apache Software Foundation (http://www.apache.org/)."
023: * Alternately, this acknowledgment may appear in the software
024: * itself, if and wherever such third-party acknowledgments
025: * normally appear.
026: *
027: * 4. The names "Jakarta", "Avalon", and "Apache Software Foundation"
028: * must not be used to endorse or promote products derived from this
029: * software without prior written permission. For written
030: * permission, please contact apache@apache.org.
031: *
032: * 5. Products derived from this software may not be called "Apache",
033: * nor may "Apache" appear in their name, without prior written
034: * permission of the Apache Software Foundation.
035: *
036: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
040: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: * SUCH DAMAGE.
048: * ====================================================================
049: *
050: * This software consists of voluntary contributions made by many
051: * individuals on behalf of the Apache Software Foundation. For more
052: * information on the Apache Software Foundation, please see
053: * <http://www.apache.org/>.
054: */
055: package org.apache.log.format;
056:
057: import java.io.StringWriter;
058: import java.text.SimpleDateFormat;
059: import java.util.Date;
060: import java.util.Stack;
061: import org.apache.log.ContextMap;
062: import org.apache.log.ContextStack;
063: import org.apache.log.LogEvent;
064: import org.apache.log.Priority;
065:
066: /**
067: * This formater formats the LogEvents according to a input pattern
068: * string.
069: * <p>
070: * The format of each pattern element can be
071: * <code>%[+|-][#[.#]]{field:subformat}</code>.
072: * </p>
073: * <ul>
074: * <li><p>The <code>+|-</code> indicates left or right justify.
075: * </p></li>
076: * <li><p>The <code>#.#</code> indicates the minimum and maximum
077: * size of output. You may omit the values and the field will be
078: * formatted without size restriction.<br />
079: * You may specify <code>#</code>, or <code>#.</code> to only
080: * define the minimum size.<br />
081: * You may specify <code>.#</code> to only define the maximum
082: * size.
083: * </p></li>
084: * <li><p><code>field</code> indicates which field is to be output and must be
085: * one of properties of LogEvent. The following fields are
086: * currently supported:
087: * <table border="0" cellpadding="4" cellspacing="0">
088: * <tr>
089: * <td><b>category</b></td>
090: * <td>Category value of the logging event.</td>
091: * </tr><tr>
092: * <td><b>context</b></td>
093: * <td>Context value of the logging event.</td>
094: * </tr><tr>
095: * <td><b>message</b></td>
096: * <td>Message value of the logging event.</td>
097: * </tr><tr>
098: * <td><b>time</b></td>
099: * <td>Time value of the logging event.</td>
100: * </tr><tr>
101: * <td><b>rtime</b></td>
102: * <td>Relative time value of the logging event.</td>
103: * </tr><tr>
104: * <td><b>throwable</b></td>
105: * <td>Throwable value of the logging event.</td>
106: * </tr><tr>
107: * <td><b>priority</b></td>
108: * <td>Priority value of the logging event.</td>
109: * </tr><tr>
110: * <td><b>thread</b></td>
111: * <td>Name of the thread which logged the event.</td>
112: * </tr>
113: * </table>
114: * </p></li>
115: *
116: * <li><p><code>subformat</code> indicates a particular subformat to
117: * use on the specified field, and is currently only supported by:
118: * <table border="0" cellpadding="4" cellspacing="0">
119: * <tr>
120: * <td><b>context</b></td>
121: * <td>Specifies the context map parameter name.</td>
122: * </tr><tr>
123: * <td><b>time</b></td>
124: * <td>Specifies the pattern to be pass to
125: * {@link java.text.SimpleDateFormat SimpleDateFormat} to format the time.</td>
126: * </tr>
127: * </table>
128: * </p></li>
129: * </ul>
130: * <p>A simple example of a typical PatternFormatter format would be:
131: * <pre><code>
132: * %{time} %5.5{priority}[%-10.10{category}]: %{message}
133: * </code></pre>
134: * </p><p>
135: * This would produce a line like:
136: * <pre><code>
137: * 1000928827905 DEBUG [ junit]: Sample message
138: * </code></pre>
139: * </p><p>
140: * The format string specifies that the logger should first print the
141: * time value of the log event without size restriction, then the
142: * priority of the log event with a minimum and maximum size of 5,
143: * then the category of the log event right justified with a minimum
144: * and maximum size of 10, followed by the message of the log event
145: * without any size restriction.
146: * </p>
147: * @author <a href="mailto:dev@avalon.apache.org">Avalon Development Team</a>
148: * @author <a href="mailto:peter@apache.org">Peter Donald</a>
149: * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
150: * @author <a href="mailto:leif@tanukisoftware.com">Leif Mortenson</a>
151: * @version CVS $Revision: 1.38 $ $Date: 2003/02/09 23:33:22 $
152: */
153: public class PatternFormatter implements Formatter,
154: org.apache.log.Formatter {
155: private static final int TYPE_TEXT = 1;
156: private static final int TYPE_CATEGORY = 2;
157: private static final int TYPE_CONTEXT = 3;
158: private static final int TYPE_MESSAGE = 4;
159: private static final int TYPE_TIME = 5;
160: private static final int TYPE_RELATIVE_TIME = 6;
161: private static final int TYPE_THROWABLE = 7;
162: private static final int TYPE_PRIORITY = 8;
163: private static final int TYPE_THREAD = 9;
164:
165: /**
166: * The maximum value used for TYPEs. Subclasses can define their own TYPEs
167: * starting at <code>MAX_TYPE + 1</code>.
168: */
169: protected static final int MAX_TYPE = TYPE_PRIORITY;
170:
171: private static final String TYPE_CATEGORY_STR = "category";
172: private static final String TYPE_CONTEXT_STR = "context";
173: private static final String TYPE_MESSAGE_STR = "message";
174: private static final String TYPE_TIME_STR = "time";
175: private static final String TYPE_RELATIVE_TIME_STR = "rtime";
176: private static final String TYPE_THROWABLE_STR = "throwable";
177: private static final String TYPE_PRIORITY_STR = "priority";
178: private static final String TYPE_THREAD_STR = "thread";
179:
180: private static final String SPACE_16 = " ";
181: private static final String SPACE_8 = " ";
182: private static final String SPACE_4 = " ";
183: private static final String SPACE_2 = " ";
184: private static final String SPACE_1 = " ";
185:
186: private static final String EOL = System.getProperty(
187: "line.separator", "\n");
188:
189: protected static class PatternRun {
190: public String m_data;
191: public boolean m_rightJustify;
192: public int m_minSize;
193: public int m_maxSize;
194: public int m_type;
195: public String m_format;
196: }
197:
198: private PatternRun m_formatSpecification[];
199:
200: private SimpleDateFormat m_simpleDateFormat;
201: private final Date m_date = new Date();
202:
203: /**
204: * @deprecated Use constructor PatternFormatter(String pattern) as this does not
205: * correctly initialize object
206: */
207: public PatternFormatter() {
208: }
209:
210: /**
211: * Creation of a new patter formatter baseed on a supplied pattern.
212: * @param pattern the patter
213: */
214: public PatternFormatter(final String pattern) {
215: parse(pattern);
216: }
217:
218: /**
219: * Extract and build a pattern from input string.
220: *
221: * @param stack the stack on which to place patterns
222: * @param pattern the input string
223: * @param index the start of pattern run
224: * @return the number of characters in pattern run
225: */
226: private int addPatternRun(final Stack stack, final char pattern[],
227: int index) {
228: final PatternRun run = new PatternRun();
229: final int start = index++;
230:
231: //first check for a +|- sign
232: if ('+' == pattern[index]) {
233: index++;
234: } else if ('-' == pattern[index]) {
235: run.m_rightJustify = true;
236: index++;
237: }
238:
239: if (Character.isDigit(pattern[index])) {
240: int total = 0;
241: while (Character.isDigit(pattern[index])) {
242: total = total * 10 + (pattern[index] - '0');
243: index++;
244: }
245: run.m_minSize = total;
246: }
247:
248: //check for . sign indicating a maximum is to follow
249: if (index < pattern.length && '.' == pattern[index]) {
250: index++;
251:
252: if (Character.isDigit(pattern[index])) {
253: int total = 0;
254: while (Character.isDigit(pattern[index])) {
255: total = total * 10 + (pattern[index] - '0');
256: index++;
257: }
258: run.m_maxSize = total;
259: }
260: }
261:
262: if (index >= pattern.length || '{' != pattern[index]) {
263: throw new IllegalArgumentException(
264: "Badly formed pattern at character " + index);
265: }
266:
267: int typeStart = index;
268:
269: while (index < pattern.length && pattern[index] != ':'
270: && pattern[index] != '}') {
271: index++;
272: }
273:
274: int typeEnd = index - 1;
275:
276: final String type = new String(pattern, typeStart + 1, typeEnd
277: - typeStart);
278:
279: run.m_type = getTypeIdFor(type);
280:
281: if (index < pattern.length && pattern[index] == ':') {
282: index++;
283: while (index < pattern.length && pattern[index] != '}')
284: index++;
285:
286: final int length = index - typeEnd - 2;
287:
288: if (0 != length) {
289: run.m_format = new String(pattern, typeEnd + 2, length);
290: }
291: }
292:
293: if (index >= pattern.length || '}' != pattern[index]) {
294: throw new IllegalArgumentException(
295: "Unterminated type in pattern at character "
296: + index);
297: }
298:
299: index++;
300:
301: stack.push(run);
302:
303: return index - start;
304: }
305:
306: /**
307: * Extract and build a text run from input string.
308: * It does special handling of '\n' and '\t' replaceing
309: * them with newline and tab.
310: *
311: * @param stack the stack on which to place runs
312: * @param pattern the input string
313: * @param index the start of the text run
314: * @return the number of characters in run
315: */
316: private int addTextRun(final Stack stack, final char pattern[],
317: int index) {
318: final PatternRun run = new PatternRun();
319: final int start = index;
320: boolean escapeMode = false;
321:
322: if ('%' == pattern[index]) {
323: index++;
324: }
325:
326: final StringBuffer sb = new StringBuffer();
327:
328: while (index < pattern.length && pattern[index] != '%') {
329: if (escapeMode) {
330: if ('n' == pattern[index]) {
331: sb.append(EOL);
332: } else if ('t' == pattern[index]) {
333: sb.append('\t');
334: } else {
335: sb.append(pattern[index]);
336: }
337: escapeMode = false;
338: } else if ('\\' == pattern[index]) {
339: escapeMode = true;
340: } else {
341: sb.append(pattern[index]);
342: }
343: index++;
344: }
345:
346: run.m_data = sb.toString();
347: run.m_type = TYPE_TEXT;
348:
349: stack.push(run);
350:
351: return index - start;
352: }
353:
354: /**
355: * Utility to append a string to buffer given certain constraints.
356: *
357: * @param sb the StringBuffer
358: * @param minSize the minimum size of output (0 to ignore)
359: * @param maxSize the maximum size of output (0 to ignore)
360: * @param rightJustify true if the string is to be right justified in it's box.
361: * @param output the input string
362: */
363: private void append(final StringBuffer sb, final int minSize,
364: final int maxSize, final boolean rightJustify,
365: final String output) {
366: final int size = output.length();
367:
368: if (size < minSize) {
369: //assert( minSize > 0 );
370: if (rightJustify) {
371: appendWhiteSpace(sb, minSize - size);
372: sb.append(output);
373: } else {
374: sb.append(output);
375: appendWhiteSpace(sb, minSize - size);
376: }
377: } else if (maxSize > 0 && maxSize < size) {
378: if (rightJustify) {
379: sb.append(output.substring(size - maxSize));
380: } else {
381: sb.append(output.substring(0, maxSize));
382: }
383: } else {
384: sb.append(output);
385: }
386: }
387:
388: /**
389: * Append a certain number of whitespace characters to a StringBuffer.
390: *
391: * @param sb the StringBuffer
392: * @param length the number of spaces to append
393: */
394: private void appendWhiteSpace(final StringBuffer sb, int length) {
395: while (length >= 16) {
396: sb.append(SPACE_16);
397: length -= 16;
398: }
399:
400: if (length >= 8) {
401: sb.append(SPACE_8);
402: length -= 8;
403: }
404:
405: if (length >= 4) {
406: sb.append(SPACE_4);
407: length -= 4;
408: }
409:
410: if (length >= 2) {
411: sb.append(SPACE_2);
412: length -= 2;
413: }
414:
415: if (length >= 1) {
416: sb.append(SPACE_1);
417: length -= 1;
418: }
419: }
420:
421: /**
422: * Format the event according to the pattern.
423: *
424: * @param event the event
425: * @return the formatted output
426: */
427: public String format(final LogEvent event) {
428: final StringBuffer sb = new StringBuffer();
429:
430: for (int i = 0; i < m_formatSpecification.length; i++) {
431: final PatternRun run = m_formatSpecification[i];
432:
433: //treat text differently as it doesn't need min/max padding
434: if (run.m_type == TYPE_TEXT) {
435: sb.append(run.m_data);
436: } else {
437: final String data = formatPatternRun(event, run);
438: if (null != data) {
439: append(sb, run.m_minSize, run.m_maxSize,
440: run.m_rightJustify, data);
441: }
442: }
443: }
444:
445: return sb.toString();
446: }
447:
448: /**
449: * Formats a single pattern run (can be extended in subclasses).
450: *
451: * @param run the pattern run to format.
452: * @return the formatted result.
453: */
454: protected String formatPatternRun(final LogEvent event,
455: final PatternRun run) {
456: switch (run.m_type) {
457: case TYPE_RELATIVE_TIME:
458: return getRTime(event.getRelativeTime(), run.m_format);
459: case TYPE_TIME:
460: return getTime(event.getTime(), run.m_format);
461: case TYPE_THROWABLE:
462: return getStackTrace(event.getThrowable(), run.m_format);
463: case TYPE_MESSAGE:
464: return getMessage(event.getMessage(), run.m_format);
465: case TYPE_CATEGORY:
466: return getCategory(event.getCategory(), run.m_format);
467: case TYPE_PRIORITY:
468: return getPriority(event.getPriority(), run.m_format);
469:
470: case TYPE_CONTEXT:
471: if (null == run.m_format
472: || run.m_format.startsWith("stack")) {
473: //Print a warning out to stderr here
474: //to indicate you are using a deprecated feature?
475: return getContext(event.getContextStack(), run.m_format);
476: } else {
477: return getContextMap(event.getContextMap(),
478: run.m_format);
479: }
480:
481: case TYPE_THREAD:
482: return getThread(run.m_format);
483:
484: default:
485: throw new IllegalStateException(
486: "Unknown Pattern specification." + run.m_type);
487: }
488: }
489:
490: /**
491: * Utility method to format category.
492: *
493: * @param category the category string
494: * @param format ancilliary format parameter - allowed to be null
495: * @return the formatted string
496: */
497: protected String getCategory(final String category,
498: final String format) {
499: return category;
500: }
501:
502: /**
503: * Get formatted priority string.
504: */
505: protected String getPriority(final Priority priority,
506: final String format) {
507: return priority.getName();
508: }
509:
510: /**
511: * Get formatted thread string.
512: */
513: protected String getThread(final String format) {
514: return Thread.currentThread().getName();
515: }
516:
517: /**
518: * Utility method to format context.
519: *
520: * @param stack the context stack
521: * @param format ancilliary format parameter - allowed to be null
522: * @return the formatted string
523: * @deprecated Use getContextStack rather than this method
524: */
525: protected String getContext(final ContextStack stack,
526: final String format) {
527: return getContextStack(stack, format);
528: }
529:
530: /**
531: * Utility method to format context.
532: *
533: * @param stack the context stack
534: * @param format ancilliary format parameter - allowed to be null
535: * @return the formatted string
536: */
537: protected String getContextStack(final ContextStack stack,
538: final String format) {
539: if (null == stack)
540: return "";
541: return stack.toString(Integer.MAX_VALUE);
542: }
543:
544: /**
545: * Utility method to format context map.
546: *
547: * @param map the context map
548: * @param format ancilliary format parameter - allowed to be null
549: * @return the formatted string
550: */
551: protected String getContextMap(final ContextMap map,
552: final String format) {
553: if (null == map)
554: return "";
555: return map.get(format, "").toString();
556: }
557:
558: /**
559: * Utility method to format message.
560: *
561: * @param message the message string
562: * @param format ancilliary format parameter - allowed to be null
563: * @return the formatted string
564: */
565: protected String getMessage(final String message,
566: final String format) {
567: return message;
568: }
569:
570: /**
571: * Utility method to format stack trace.
572: *
573: * @param throwable the throwable instance
574: * @param format ancilliary format parameter - allowed to be null
575: * @return the formatted string
576: */
577: protected String getStackTrace(final Throwable throwable,
578: final String format) {
579: if (null == throwable)
580: return "";
581: final StringWriter sw = new StringWriter();
582: throwable.printStackTrace(new java.io.PrintWriter(sw));
583: return sw.toString();
584: }
585:
586: /**
587: * Utility method to format relative time.
588: *
589: * @param time the time
590: * @param format ancilliary format parameter - allowed to be null
591: * @return the formatted string
592: */
593: protected String getRTime(final long time, final String format) {
594: return getTime(time, format);
595: }
596:
597: /**
598: * Utility method to format time.
599: *
600: * @param time the time
601: * @param format ancilliary format parameter - allowed to be null
602: * @return the formatted string
603: */
604: protected String getTime(final long time, final String format) {
605: if (null == format) {
606: return Long.toString(time);
607: } else {
608: synchronized (m_date) {
609: if (null == m_simpleDateFormat) {
610: m_simpleDateFormat = new SimpleDateFormat(format);
611: }
612: m_date.setTime(time);
613: return m_simpleDateFormat.format(m_date);
614: }
615: }
616: }
617:
618: /**
619: * Retrieve the type-id for a particular string.
620: *
621: * @param type the string
622: * @return the type-id
623: */
624: protected int getTypeIdFor(final String type) {
625: if (type.equalsIgnoreCase(TYPE_CATEGORY_STR)) {
626: return TYPE_CATEGORY;
627: } else if (type.equalsIgnoreCase(TYPE_CONTEXT_STR)) {
628: return TYPE_CONTEXT;
629: } else if (type.equalsIgnoreCase(TYPE_MESSAGE_STR)) {
630: return TYPE_MESSAGE;
631: } else if (type.equalsIgnoreCase(TYPE_PRIORITY_STR)) {
632: return TYPE_PRIORITY;
633: } else if (type.equalsIgnoreCase(TYPE_TIME_STR)) {
634: return TYPE_TIME;
635: } else if (type.equalsIgnoreCase(TYPE_RELATIVE_TIME_STR)) {
636: return TYPE_RELATIVE_TIME;
637: } else if (type.equalsIgnoreCase(TYPE_THREAD_STR)) {
638: return TYPE_THREAD;
639: } else if (type.equalsIgnoreCase(TYPE_THROWABLE_STR)) {
640: return TYPE_THROWABLE;
641: } else {
642: throw new IllegalArgumentException(
643: "Unknown Type in pattern - " + type);
644: }
645: }
646:
647: /**
648: * Parse the input pattern and build internal data structures.
649: *
650: * @param patternString the pattern
651: */
652: protected final void parse(final String patternString) {
653: final Stack stack = new Stack();
654: final int size = patternString.length();
655: final char pattern[] = new char[size];
656: int index = 0;
657:
658: patternString.getChars(0, size, pattern, 0);
659:
660: while (index < size) {
661: if (pattern[index] == '%'
662: && !(index != size - 1 && pattern[index + 1] == '%')) {
663: index += addPatternRun(stack, pattern, index);
664: } else {
665: index += addTextRun(stack, pattern, index);
666: }
667: }
668:
669: final int elementCount = stack.size();
670:
671: m_formatSpecification = new PatternRun[elementCount];
672:
673: for (int i = 0; i < elementCount; i++) {
674: m_formatSpecification[i] = (PatternRun) stack.elementAt(i);
675: }
676: }
677:
678: /**
679: * Set the string description that the format is extracted from.
680: *
681: * @param format the string format
682: * @deprecated Parse format in via constructor rather than use this method
683: */
684: public void setFormat(final String format) {
685: parse(format);
686: }
687: }
|