/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.cocoon.util.log; import java.io.StringWriter; import java.util.Stack; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.SystemUtils; import org.apache.log.LogEvent; import org.apache.log.Priority; import org.apache.log.format.Formatter; import org.apache.log.util.DefaultErrorHandler; /** * A refactoring of <code>org.apache.log.format.PatternFormatter</code> that * can be extended. * This formater formats the LogEntries according to a input pattern string. * * The format of each pattern element can be %[+|-]#.#{field:subformat} * * The +|- indicates left or right justify. * The #.# indicates the minimum and maximum size of output. * 'field' indicates which field is to be output and must be one of * properties of LogEvent * 'subformat' indicates a particular subformat and is currently unused. * * @deprecated This class will be removed in 2.2 * @author <a href="mailto:donaldp@apache.org">Peter Donald</a> * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a> * @version CVS $Id$ */ public class ExtensiblePatternFormatter implements Formatter { protected final static int TYPE_TEXT = 1; protected final static int TYPE_CATEGORY = 2; protected final static int TYPE_MESSAGE = 4; protected final static int TYPE_TIME = 5; protected final static int TYPE_RELATIVE_TIME = 6; protected final static int TYPE_THROWABLE = 7; protected final static int TYPE_PRIORITY = 8; /** * The maximum value used for TYPEs. Subclasses can define their own TYPEs * starting at <code>MAX_TYPE + 1</code>. */ protected final static int MAX_TYPE = 8; protected final static String TYPE_CATEGORY_STR = "category"; protected final static String TYPE_MESSAGE_STR = "message"; protected final static String TYPE_TIME_STR = "time"; protected final static String TYPE_RELATIVE_TIME_STR = "rtime"; protected final static String TYPE_THROWABLE_STR = "throwable"; protected final static String TYPE_PRIORITY_STR = "priority"; protected static class PatternRun { public String m_data; public boolean m_rightJustify; public int m_minSize; public int m_maxSize; public int m_type; public String m_format; } protected PatternRun m_formatSpecification[]; /** * Extract and build a pattern from input string. * * @param stack the stack on which to place patterns * @param pattern the input string * @param index the start of pattern run * @return the number of characters in pattern run */ protected int addPatternRun(final Stack stack, final char pattern[], int index) { final PatternRun run = new PatternRun(); final int start = index++; // first check for a +|- sign if ('+' == pattern[index]) { index++; } else if ('-' == pattern[index]) { run.m_rightJustify = true; index++; } if (Character.isDigit(pattern[index])) { int total = 0; while (Character.isDigit(pattern[index])) { total = total * 10 + (pattern[index] - '0'); index++; } run.m_minSize = total; } //check for . sign indicating a maximum is to follow if (index < pattern.length && '.' == pattern[index]) { index++; if (Character.isDigit(pattern[index])) { int total = 0; while (Character.isDigit(pattern[index])) { total = total * 10 + (pattern[ index ] - '0'); index++; } run.m_maxSize = total; } } if (index >= pattern.length || '{' != pattern[index]) { throw new IllegalArgumentException( "Badly formed pattern at character " + index ); } int typeStart = index; while (index < pattern.length && pattern[index]!= ':' && pattern[index] != '}' ) { index++; } int typeEnd = index - 1; final String type = new String(pattern, typeStart + 1, typeEnd - typeStart); run.m_type = getTypeIdFor( type ); if (index < pattern.length && pattern[index] == ':' ) { index++; while (index < pattern.length && pattern[index] != '}' ) { index++; } final int length = index - typeEnd - 2; if (0 != length) { run.m_format = new String(pattern, typeEnd + 2, length); } } if (index >= pattern.length || '}' != pattern[index]) { throw new IllegalArgumentException( "Unterminated type in pattern at character " + index ); } index++; stack.push( run ); return index - start; } /** * Extract and build a text run from input string. * It does special handling of '\n' and '\t' replaceing * them with newline and tab. * * @param stack the stack on which to place runs * @param pattern the input string * @param index the start of the text run * @return the number of characters in run */ protected int addTextRun( final Stack stack, final char pattern[], int index ) { final PatternRun run = new PatternRun(); final int start = index; boolean escapeMode = false; if ('%' == pattern[index]) { index++; } final StringBuffer sb = new StringBuffer(); while (index < pattern.length && pattern[index] != '%') { if (escapeMode) { if ('n' == pattern[ index ]) { sb.append( SystemUtils.LINE_SEPARATOR ); } else if ('t' == pattern[ index ]) { sb.append( '\t' ); } else { sb.append( pattern[ index ] ); } escapeMode = false; } else if ('\\' == pattern[ index ]) { escapeMode = true; } else { sb.append( pattern[ index ] ); } index++; } run.m_data = sb.toString(); run.m_type = TYPE_TEXT; stack.push(run); return index - start; } /** * Utility to append a string to buffer given certain constraints. * * @param sb the StringBuffer * @param minSize the minimum size of output (0 to ignore) * @param maxSize the maximum size of output (0 to ignore) * @param rightJustify true if the string is to be right justified in it's box. * @param output the input string */ protected void append(final StringBuffer sb, final int minSize, final int maxSize, final boolean rightJustify, final String output) { if (output.length() < minSize) { if (rightJustify) { sb.append(StringUtils.leftPad(output, minSize)); } else { sb.append(StringUtils.rightPad(output, minSize)); } } else if (maxSize > 0) { if (rightJustify) { sb.append(StringUtils.right(output, maxSize)); } else { sb.append(StringUtils.left(output, maxSize)); } } else { sb.append(output); } } /** * Format the event according to the pattern. * * @param event the event * @return the formatted output */ public String format(final LogEvent event) { final StringBuffer sb = new StringBuffer(); for( int i = 0; i < m_formatSpecification.length; i++ ) { final PatternRun run = m_formatSpecification[i]; //treat text differently as it doesn't need min/max padding if (run.m_type == TYPE_TEXT) { sb.append(run.m_data); } else { final String data = formatPatternRun(event, run); if (null != data) { append(sb, run.m_minSize, run.m_maxSize, run.m_rightJustify, data); } } } return sb.toString(); } /** * Formats a single pattern run (can be extended in subclasses). * * @param run the pattern run to format. * @return the formatted result. */ protected String formatPatternRun( final LogEvent event, final PatternRun run ) { String str = null; switch (run.m_type) { case TYPE_RELATIVE_TIME: str = getTime( event.getRelativeTime(), run.m_format ); break; case TYPE_TIME: str = getTime( event.getTime(), run.m_format ); break; case TYPE_THROWABLE: str = getStackTrace( event.getThrowable(), run.m_format ); break; case TYPE_MESSAGE: str = getMessage( event.getMessage(), run.m_format ); break; case TYPE_CATEGORY: str = getCategory( event.getCategory(), run.m_format ); break; case TYPE_PRIORITY: str = getPriority( event.getPriority(), run.m_format ); break; default: new DefaultErrorHandler().error("Unknown Pattern specification." + run.m_type, null, null); } return str; } /** * Utility method to format category. * * @param category the category string * @param format ancilliary format parameter - allowed to be null * @return the formatted string */ protected String getCategory(final String category, final String format) { return category; } /** * Get formatted priority string. */ protected String getPriority(final Priority priority, final String format) { return priority.getName(); } /** * Correct a context string by replacing '.''s with a '_'. * * @param context the un-fixed context * @return the fixed context */ protected final String fix(final String context) { return context.replace( '.', '_' ); } /** * Utility method to format message. * * @param message the message string * @param format ancilliary format parameter - allowed to be null * @return the formatted string */ protected String getMessage(final String message, final String format) { return message; } /** * Utility method to format stack trace. * * @param throwable the throwable instance * @param format ancilliary format parameter - allowed to be null * @return the formatted string */ protected String getStackTrace(final Throwable throwable, final String format) { if (null != throwable) { final StringWriter sw = new StringWriter(); throwable.printStackTrace(new java.io.PrintWriter(sw)); return sw.toString(); } return ""; } /** * Utility method to format time. * * @param time the time * @param format ancilliary format parameter - allowed to be null * @return the formatted string */ protected String getTime(final long time, final String format) { return Long.toString(time); } /** * Retrieve the type-id for a particular string. * * @param type the string * @return the type-id */ protected int getTypeIdFor(final String type) { if (type.equalsIgnoreCase(TYPE_CATEGORY_STR)) { return TYPE_CATEGORY; } else if (type.equalsIgnoreCase(TYPE_MESSAGE_STR)) { return TYPE_MESSAGE; } else if (type.equalsIgnoreCase(TYPE_PRIORITY_STR)) { return TYPE_PRIORITY; } else if (type.equalsIgnoreCase(TYPE_TIME_STR)) { return TYPE_TIME; } else if (type.equalsIgnoreCase(TYPE_RELATIVE_TIME_STR)) { return TYPE_RELATIVE_TIME; } else if (type.equalsIgnoreCase(TYPE_THROWABLE_STR)) { return TYPE_THROWABLE; } else { throw new IllegalArgumentException( "Unknown Type in pattern - " + type ); } } /** * Parse the input pattern and build internal data structures. * * @param patternString the pattern */ protected void parse(final String patternString) { final Stack stack = new Stack(); final int size = patternString.length(); final char pattern[] = new char[size]; int index = 0; patternString.getChars(0, size, pattern, 0); while (index < size) { if (pattern[index] == '%' && !(index != size - 1 && pattern[index + 1] == '%' )) { index += addPatternRun(stack, pattern, index); } else { index += addTextRun(stack, pattern, index); } } final int elementCount = stack.size(); m_formatSpecification = new PatternRun[elementCount]; for (int i = 0; i < elementCount; i++) { m_formatSpecification[i] = (PatternRun) stack.elementAt(i); } } /** * Set the string description that the format is extracted from. * * @param format the string format */ public void setFormat(final String format) { parse(format); } }