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: package org.apache.cocoon.util.log;
018:
019: import java.io.StringWriter;
020: import java.util.Stack;
021:
022: import org.apache.commons.lang.StringUtils;
023: import org.apache.commons.lang.SystemUtils;
024: import org.apache.log.LogEvent;
025: import org.apache.log.Priority;
026: import org.apache.log.format.Formatter;
027: import org.apache.log.util.DefaultErrorHandler;
028:
029: /**
030: * A refactoring of <code>org.apache.log.format.PatternFormatter</code> that
031: * can be extended.
032: * This formater formats the LogEntries according to a input pattern string.
033: *
034: * The format of each pattern element can be %[+|-]#.#{field:subformat}
035: *
036: * The +|- indicates left or right justify.
037: * The #.# indicates the minimum and maximum size of output.
038: * 'field' indicates which field is to be output and must be one of
039: * properties of LogEvent
040: * 'subformat' indicates a particular subformat and is currently unused.
041: *
042: * @deprecated This class will be removed in 2.2
043: * @author <a href="mailto:donaldp@apache.org">Peter Donald</a>
044: * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
045: * @version CVS $Id: ExtensiblePatternFormatter.java 433543 2006-08-22 06:22:54Z crossley $
046: */
047: public class ExtensiblePatternFormatter implements Formatter {
048: protected final static int TYPE_TEXT = 1;
049: protected final static int TYPE_CATEGORY = 2;
050: protected final static int TYPE_MESSAGE = 4;
051: protected final static int TYPE_TIME = 5;
052: protected final static int TYPE_RELATIVE_TIME = 6;
053: protected final static int TYPE_THROWABLE = 7;
054: protected final static int TYPE_PRIORITY = 8;
055:
056: /**
057: * The maximum value used for TYPEs. Subclasses can define their own TYPEs
058: * starting at <code>MAX_TYPE + 1</code>.
059: */
060: protected final static int MAX_TYPE = 8;
061:
062: protected final static String TYPE_CATEGORY_STR = "category";
063: protected final static String TYPE_MESSAGE_STR = "message";
064: protected final static String TYPE_TIME_STR = "time";
065: protected final static String TYPE_RELATIVE_TIME_STR = "rtime";
066: protected final static String TYPE_THROWABLE_STR = "throwable";
067: protected final static String TYPE_PRIORITY_STR = "priority";
068:
069: protected static class PatternRun {
070: public String m_data;
071: public boolean m_rightJustify;
072: public int m_minSize;
073: public int m_maxSize;
074: public int m_type;
075: public String m_format;
076: }
077:
078: protected PatternRun m_formatSpecification[];
079:
080: /**
081: * Extract and build a pattern from input string.
082: *
083: * @param stack the stack on which to place patterns
084: * @param pattern the input string
085: * @param index the start of pattern run
086: * @return the number of characters in pattern run
087: */
088: protected int addPatternRun(final Stack stack,
089: final char pattern[], int index) {
090: final PatternRun run = new PatternRun();
091: final int start = index++;
092:
093: // first check for a +|- sign
094: if ('+' == pattern[index]) {
095: index++;
096: } else if ('-' == pattern[index]) {
097: run.m_rightJustify = true;
098: index++;
099: }
100:
101: if (Character.isDigit(pattern[index])) {
102: int total = 0;
103: while (Character.isDigit(pattern[index])) {
104: total = total * 10 + (pattern[index] - '0');
105: index++;
106: }
107: run.m_minSize = total;
108: }
109:
110: //check for . sign indicating a maximum is to follow
111: if (index < pattern.length && '.' == pattern[index]) {
112: index++;
113: if (Character.isDigit(pattern[index])) {
114: int total = 0;
115: while (Character.isDigit(pattern[index])) {
116: total = total * 10 + (pattern[index] - '0');
117: index++;
118: }
119: run.m_maxSize = total;
120: }
121: }
122:
123: if (index >= pattern.length || '{' != pattern[index]) {
124: throw new IllegalArgumentException(
125: "Badly formed pattern at character " + index);
126: }
127:
128: int typeStart = index;
129:
130: while (index < pattern.length && pattern[index] != ':'
131: && pattern[index] != '}') {
132: index++;
133: }
134:
135: int typeEnd = index - 1;
136:
137: final String type = new String(pattern, typeStart + 1, typeEnd
138: - typeStart);
139:
140: run.m_type = getTypeIdFor(type);
141:
142: if (index < pattern.length && pattern[index] == ':') {
143: index++;
144: while (index < pattern.length && pattern[index] != '}') {
145: index++;
146: }
147: final int length = index - typeEnd - 2;
148:
149: if (0 != length) {
150: run.m_format = new String(pattern, typeEnd + 2, length);
151: }
152: }
153:
154: if (index >= pattern.length || '}' != pattern[index]) {
155: throw new IllegalArgumentException(
156: "Unterminated type in pattern at character "
157: + index);
158: }
159: index++;
160: stack.push(run);
161: return index - start;
162: }
163:
164: /**
165: * Extract and build a text run from input string.
166: * It does special handling of '\n' and '\t' replaceing
167: * them with newline and tab.
168: *
169: * @param stack the stack on which to place runs
170: * @param pattern the input string
171: * @param index the start of the text run
172: * @return the number of characters in run
173: */
174: protected int addTextRun(final Stack stack, final char pattern[],
175: int index) {
176: final PatternRun run = new PatternRun();
177: final int start = index;
178: boolean escapeMode = false;
179:
180: if ('%' == pattern[index]) {
181: index++;
182: }
183: final StringBuffer sb = new StringBuffer();
184: while (index < pattern.length && pattern[index] != '%') {
185: if (escapeMode) {
186: if ('n' == pattern[index]) {
187: sb.append(SystemUtils.LINE_SEPARATOR);
188: } else if ('t' == pattern[index]) {
189: sb.append('\t');
190: } else {
191: sb.append(pattern[index]);
192: }
193: escapeMode = false;
194: } else if ('\\' == pattern[index]) {
195: escapeMode = true;
196: } else {
197: sb.append(pattern[index]);
198: }
199: index++;
200: }
201: run.m_data = sb.toString();
202: run.m_type = TYPE_TEXT;
203: stack.push(run);
204: return index - start;
205: }
206:
207: /**
208: * Utility to append a string to buffer given certain constraints.
209: *
210: * @param sb the StringBuffer
211: * @param minSize the minimum size of output (0 to ignore)
212: * @param maxSize the maximum size of output (0 to ignore)
213: * @param rightJustify true if the string is to be right justified in it's box.
214: * @param output the input string
215: */
216: protected void append(final StringBuffer sb, final int minSize,
217: final int maxSize, final boolean rightJustify,
218: final String output) {
219: if (output.length() < minSize) {
220: if (rightJustify) {
221: sb.append(StringUtils.leftPad(output, minSize));
222: } else {
223: sb.append(StringUtils.rightPad(output, minSize));
224: }
225: } else if (maxSize > 0) {
226: if (rightJustify) {
227: sb.append(StringUtils.right(output, maxSize));
228: } else {
229: sb.append(StringUtils.left(output, maxSize));
230: }
231: } else {
232: sb.append(output);
233: }
234: }
235:
236: /**
237: * Format the event according to the pattern.
238: *
239: * @param event the event
240: * @return the formatted output
241: */
242: public String format(final LogEvent event) {
243: final StringBuffer sb = new StringBuffer();
244:
245: for (int i = 0; i < m_formatSpecification.length; i++) {
246: final PatternRun run = m_formatSpecification[i];
247: //treat text differently as it doesn't need min/max padding
248: if (run.m_type == TYPE_TEXT) {
249: sb.append(run.m_data);
250: } else {
251: final String data = formatPatternRun(event, run);
252: if (null != data) {
253: append(sb, run.m_minSize, run.m_maxSize,
254: run.m_rightJustify, data);
255: }
256: }
257: }
258: return sb.toString();
259: }
260:
261: /**
262: * Formats a single pattern run (can be extended in subclasses).
263: *
264: * @param run the pattern run to format.
265: * @return the formatted result.
266: */
267: protected String formatPatternRun(final LogEvent event,
268: final PatternRun run) {
269: String str = null;
270:
271: switch (run.m_type) {
272: case TYPE_RELATIVE_TIME:
273: str = getTime(event.getRelativeTime(), run.m_format);
274: break;
275: case TYPE_TIME:
276: str = getTime(event.getTime(), run.m_format);
277: break;
278: case TYPE_THROWABLE:
279: str = getStackTrace(event.getThrowable(), run.m_format);
280: break;
281: case TYPE_MESSAGE:
282: str = getMessage(event.getMessage(), run.m_format);
283: break;
284: case TYPE_CATEGORY:
285: str = getCategory(event.getCategory(), run.m_format);
286: break;
287: case TYPE_PRIORITY:
288: str = getPriority(event.getPriority(), run.m_format);
289: break;
290: default:
291: new DefaultErrorHandler().error(
292: "Unknown Pattern specification." + run.m_type,
293: null, null);
294: }
295: return str;
296: }
297:
298: /**
299: * Utility method to format category.
300: *
301: * @param category the category string
302: * @param format ancilliary format parameter - allowed to be null
303: * @return the formatted string
304: */
305: protected String getCategory(final String category,
306: final String format) {
307: return category;
308: }
309:
310: /**
311: * Get formatted priority string.
312: */
313: protected String getPriority(final Priority priority,
314: final String format) {
315: return priority.getName();
316: }
317:
318: /**
319: * Correct a context string by replacing '.''s with a '_'.
320: *
321: * @param context the un-fixed context
322: * @return the fixed context
323: */
324: protected final String fix(final String context) {
325: return context.replace('.', '_');
326: }
327:
328: /**
329: * Utility method to format message.
330: *
331: * @param message the message string
332: * @param format ancilliary format parameter - allowed to be null
333: * @return the formatted string
334: */
335: protected String getMessage(final String message,
336: final String format) {
337: return message;
338: }
339:
340: /**
341: * Utility method to format stack trace.
342: *
343: * @param throwable the throwable instance
344: * @param format ancilliary format parameter - allowed to be null
345: * @return the formatted string
346: */
347: protected String getStackTrace(final Throwable throwable,
348: final String format) {
349: if (null != throwable) {
350: final StringWriter sw = new StringWriter();
351: throwable.printStackTrace(new java.io.PrintWriter(sw));
352: return sw.toString();
353: }
354: return "";
355: }
356:
357: /**
358: * Utility method to format time.
359: *
360: * @param time the time
361: * @param format ancilliary format parameter - allowed to be null
362: * @return the formatted string
363: */
364: protected String getTime(final long time, final String format) {
365: return Long.toString(time);
366: }
367:
368: /**
369: * Retrieve the type-id for a particular string.
370: *
371: * @param type the string
372: * @return the type-id
373: */
374: protected int getTypeIdFor(final String type) {
375: if (type.equalsIgnoreCase(TYPE_CATEGORY_STR)) {
376: return TYPE_CATEGORY;
377: } else if (type.equalsIgnoreCase(TYPE_MESSAGE_STR)) {
378: return TYPE_MESSAGE;
379: } else if (type.equalsIgnoreCase(TYPE_PRIORITY_STR)) {
380: return TYPE_PRIORITY;
381: } else if (type.equalsIgnoreCase(TYPE_TIME_STR)) {
382: return TYPE_TIME;
383: } else if (type.equalsIgnoreCase(TYPE_RELATIVE_TIME_STR)) {
384: return TYPE_RELATIVE_TIME;
385: } else if (type.equalsIgnoreCase(TYPE_THROWABLE_STR)) {
386: return TYPE_THROWABLE;
387: } else {
388: throw new IllegalArgumentException(
389: "Unknown Type in pattern - " + type);
390: }
391: }
392:
393: /**
394: * Parse the input pattern and build internal data structures.
395: *
396: * @param patternString the pattern
397: */
398: protected void parse(final String patternString) {
399: final Stack stack = new Stack();
400: final int size = patternString.length();
401: final char pattern[] = new char[size];
402: int index = 0;
403:
404: patternString.getChars(0, size, pattern, 0);
405: while (index < size) {
406: if (pattern[index] == '%'
407: && !(index != size - 1 && pattern[index + 1] == '%')) {
408: index += addPatternRun(stack, pattern, index);
409: } else {
410: index += addTextRun(stack, pattern, index);
411: }
412: }
413: final int elementCount = stack.size();
414: m_formatSpecification = new PatternRun[elementCount];
415:
416: for (int i = 0; i < elementCount; i++) {
417: m_formatSpecification[i] = (PatternRun) stack.elementAt(i);
418: }
419: }
420:
421: /**
422: * Set the string description that the format is extracted from.
423: *
424: * @param format the string format
425: */
426: public void setFormat(final String format) {
427: parse(format);
428: }
429: }
|