2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 package org.apache.log4j.helpers;
19 import org.apache.log4j.Layout;
20 import org.apache.log4j.spi.LoggingEvent;
21 import org.apache.log4j.spi.LocationInfo;
22 import java.text.DateFormat;
23 import java.text.SimpleDateFormat;
24 import java.util.Date;
26 import java.util.Arrays;
28 // Contributors: Nelson Minar <(nelson@monkey.org>
29 // Igor E. Poteryaev <jah@mail.ru>
30 // Reinhard Deschler <reinhard.deschler@web.de>
33 Most of the work of the {@link org.apache.log4j.PatternLayout} class
34 is delegated to the PatternParser class.
36 <p>It is this class that parses conversion patterns and creates
37 a chained list of {@link OptionConverter OptionConverters}.
39 @author <a href=mailto:"cakalijp@Maritz.com">James P. Cakalic</a>
40 @author Ceki Gülcü
41 @author Anders Kristensen
45 public class PatternParser {
47 private static final char ESCAPE_CHAR = '%';
49 private static final int LITERAL_STATE = 0;
50 private static final int CONVERTER_STATE = 1;
51 private static final int DOT_STATE = 3;
52 private static final int MIN_STATE = 4;
53 private static final int MAX_STATE = 5;
55 static final int FULL_LOCATION_CONVERTER = 1000;
56 static final int METHOD_LOCATION_CONVERTER = 1001;
57 static final int CLASS_LOCATION_CONVERTER = 1002;
58 static final int LINE_LOCATION_CONVERTER = 1003;
59 static final int FILE_LOCATION_CONVERTER = 1004;
61 static final int RELATIVE_TIME_CONVERTER = 2000;
62 static final int THREAD_CONVERTER = 2001;
63 static final int LEVEL_CONVERTER = 2002;
64 static final int NDC_CONVERTER = 2003;
65 static final int MESSAGE_CONVERTER = 2004;
68 protected StringBuffer currentLiteral = new StringBuffer(32);
69 protected int patternLength;
71 PatternConverter head;
72 PatternConverter tail;
73 protected FormattingInfo formattingInfo = new FormattingInfo();
74 protected String pattern;
77 PatternParser(String pattern) {
78 this.pattern = pattern;
79 patternLength = pattern.length();
80 state = LITERAL_STATE;
84 void addToList(PatternConverter pc) {
94 String extractOption() {
95 if((i < patternLength) && (pattern.charAt(i) == '{')) {
96 int end = pattern.indexOf('}', i);
98 String r = pattern.substring(i + 1, end);
108 The option is expected to be in decimal and positive. In case of
109 error, zero is returned. */
111 int extractPrecisionOption() {
112 String opt = extractOption();
116 r = Integer.parseInt(opt);
119 "Precision option (" + opt + ") isn't a positive integer.");
123 catch (NumberFormatException e) {
124 LogLog.error("Category option \""+opt+"\" not a decimal integer.", e);
131 PatternConverter parse() {
134 while(i < patternLength) {
135 c = pattern.charAt(i++);
138 // In literal state, the last char is always a literal.
139 if(i == patternLength) {
140 currentLiteral.append(c);
143 if(c == ESCAPE_CHAR) {
144 // peek at the next char.
145 switch(pattern.charAt(i)) {
147 currentLiteral.append(c);
151 currentLiteral.append(Layout.LINE_SEP);
155 if(currentLiteral.length() != 0) {
156 addToList(new LiteralPatternConverter(
157 currentLiteral.toString()));
158 //LogLog.debug("Parsed LITERAL converter: \""
159 // +currentLiteral+"\".");
161 currentLiteral.setLength(0);
162 currentLiteral.append(c); // append %
163 state = CONVERTER_STATE;
164 formattingInfo.reset();
168 currentLiteral.append(c);
171 case CONVERTER_STATE:
172 currentLiteral.append(c);
175 formattingInfo.leftAlign = true;
181 if(c >= '0' && c <= '9') {
182 formattingInfo.min = c - '0';
185 finalizeConverter(c);
190 currentLiteral.append(c);
191 if(c >= '0' && c <= '9') {
192 formattingInfo.min = formattingInfo.min*10 + (c - '0');
193 } else if(c == '.') {
196 finalizeConverter(c);
200 currentLiteral.append(c);
201 if(c >= '0' && c <= '9') {
202 formattingInfo.max = c - '0';
206 LogLog.error("Error occured in position "+i
207 +".\n Was expecting digit, instead got char \""+c+"\".");
208 state = LITERAL_STATE;
212 currentLiteral.append(c);
213 if(c >= '0' && c <= '9') {
214 formattingInfo.max = formattingInfo.max*10 + (c - '0');
216 finalizeConverter(c);
217 state = LITERAL_STATE;
222 if(currentLiteral.length() != 0) {
223 addToList(new LiteralPatternConverter(currentLiteral.toString()));
224 //LogLog.debug("Parsed LITERAL converter: \""+currentLiteral+"\".");
230 void finalizeConverter(char c) {
231 PatternConverter pc = null;
234 pc = new CategoryPatternConverter(formattingInfo,
235 extractPrecisionOption());
236 //LogLog.debug("CATEGORY converter.");
237 //formattingInfo.dump();
238 currentLiteral.setLength(0);
241 pc = new ClassNamePatternConverter(formattingInfo,
242 extractPrecisionOption());
243 //LogLog.debug("CLASS_NAME converter.");
244 //formattingInfo.dump();
245 currentLiteral.setLength(0);
248 String dateFormatStr = AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT;
250 String dOpt = extractOption();
252 dateFormatStr = dOpt;
255 if(dateFormatStr.equalsIgnoreCase(
256 AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT)) {
257 df = new ISO8601DateFormat();
258 } else if(dateFormatStr.equalsIgnoreCase(
259 AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT)) {
260 df = new AbsoluteTimeDateFormat();
261 } else if(dateFormatStr.equalsIgnoreCase(
262 AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT)) {
263 df = new DateTimeDateFormat();
266 df = new SimpleDateFormat(dateFormatStr);
268 catch (IllegalArgumentException e) {
269 LogLog.error("Could not instantiate SimpleDateFormat with " +
271 df = (DateFormat) OptionConverter.instantiateByClassName(
272 "org.apache.log4j.helpers.ISO8601DateFormat",
273 DateFormat.class, null);
276 pc = new DatePatternConverter(formattingInfo, df);
277 //LogLog.debug("DATE converter {"+dateFormatStr+"}.");
278 //formattingInfo.dump();
279 currentLiteral.setLength(0);
282 pc = new LocationPatternConverter(formattingInfo,
283 FILE_LOCATION_CONVERTER);
284 //LogLog.debug("File name converter.");
285 //formattingInfo.dump();
286 currentLiteral.setLength(0);
289 pc = new LocationPatternConverter(formattingInfo,
290 FULL_LOCATION_CONVERTER);
291 //LogLog.debug("Location converter.");
292 //formattingInfo.dump();
293 currentLiteral.setLength(0);
296 pc = new LocationPatternConverter(formattingInfo,
297 LINE_LOCATION_CONVERTER);
298 //LogLog.debug("LINE NUMBER converter.");
299 //formattingInfo.dump();
300 currentLiteral.setLength(0);
303 pc = new BasicPatternConverter(formattingInfo, MESSAGE_CONVERTER);
304 //LogLog.debug("MESSAGE converter.");
305 //formattingInfo.dump();
306 currentLiteral.setLength(0);
309 pc = new LocationPatternConverter(formattingInfo,
310 METHOD_LOCATION_CONVERTER);
311 //LogLog.debug("METHOD converter.");
312 //formattingInfo.dump();
313 currentLiteral.setLength(0);
316 pc = new BasicPatternConverter(formattingInfo, LEVEL_CONVERTER);
317 //LogLog.debug("LEVEL converter.");
318 //formattingInfo.dump();
319 currentLiteral.setLength(0);
322 pc = new BasicPatternConverter(formattingInfo,
323 RELATIVE_TIME_CONVERTER);
324 //LogLog.debug("RELATIVE time converter.");
325 //formattingInfo.dump();
326 currentLiteral.setLength(0);
329 pc = new BasicPatternConverter(formattingInfo, THREAD_CONVERTER);
330 //LogLog.debug("THREAD converter.");
331 //formattingInfo.dump();
332 currentLiteral.setLength(0);
335 if(i < patternLength) {
336 char cNext = pattern.charAt(i);
337 if(cNext >= '0' && cNext <= '9') {
338 pc = new UserFieldPatternConverter(formattingInfo, cNext - '0');
339 LogLog.debug("USER converter ["+cNext+"].");
340 formattingInfo.dump();
341 currentLiteral.setLength(0);
345 LogLog.error("Unexpected char" +cNext+" at position "+i);
349 pc = new BasicPatternConverter(formattingInfo, NDC_CONVERTER);
350 //LogLog.debug("NDC converter.");
351 currentLiteral.setLength(0);
354 String xOpt = extractOption();
355 pc = new MDCPatternConverter(formattingInfo, xOpt);
356 currentLiteral.setLength(0);
359 LogLog.error("Unexpected char [" +c+"] at position "+i
360 +" in conversion patterrn.");
361 pc = new LiteralPatternConverter(currentLiteral.toString());
362 currentLiteral.setLength(0);
369 void addConverter(PatternConverter pc) {
370 currentLiteral.setLength(0);
371 // Add the pattern converter to the list.
373 // Next pattern is assumed to be a literal.
374 state = LITERAL_STATE;
375 // Reset formatting info
376 formattingInfo.reset();
379 // ---------------------------------------------------------------------
381 // ---------------------------------------------------------------------
383 private static class BasicPatternConverter extends PatternConverter {
386 BasicPatternConverter(FormattingInfo formattingInfo, int type) {
387 super(formattingInfo);
392 String convert(LoggingEvent event) {
394 case RELATIVE_TIME_CONVERTER:
395 return (Long.toString(event.timeStamp - LoggingEvent.getStartTime()));
396 case THREAD_CONVERTER:
397 return event.getThreadName();
398 case LEVEL_CONVERTER:
399 return event.getLevel().toString();
401 return event.getNDC();
402 case MESSAGE_CONVERTER: {
403 return event.getRenderedMessage();
405 default: return null;
410 private static class LiteralPatternConverter extends PatternConverter {
411 private String literal;
413 LiteralPatternConverter(String value) {
419 void format(StringBuffer sbuf, LoggingEvent event) {
420 sbuf.append(literal);
424 String convert(LoggingEvent event) {
429 private static class DatePatternConverter extends PatternConverter {
430 private DateFormat df;
433 DatePatternConverter(FormattingInfo formattingInfo, DateFormat df) {
434 super(formattingInfo);
440 String convert(LoggingEvent event) {
441 date.setTime(event.timeStamp);
442 String converted = null;
444 converted = df.format(date);
446 catch (Exception ex) {
447 LogLog.error("Error occured while converting date.", ex);
453 private static class MDCPatternConverter extends PatternConverter {
456 MDCPatternConverter(FormattingInfo formattingInfo, String key) {
457 super(formattingInfo);
462 String convert(LoggingEvent event) {
464 StringBuffer buf = new StringBuffer("{");
465 Map properties = event.getProperties();
466 if (properties.size() > 0) {
467 Object[] keys = properties.keySet().toArray();
469 for (int i = 0; i < keys.length; i++) {
473 buf.append(properties.get(keys[i]));
478 return buf.toString();
480 Object val = event.getMDC(key);
484 return val.toString();
491 private class LocationPatternConverter extends PatternConverter {
494 LocationPatternConverter(FormattingInfo formattingInfo, int type) {
495 super(formattingInfo);
500 String convert(LoggingEvent event) {
501 LocationInfo locationInfo = event.getLocationInformation();
503 case FULL_LOCATION_CONVERTER:
504 return locationInfo.fullInfo;
505 case METHOD_LOCATION_CONVERTER:
506 return locationInfo.getMethodName();
507 case LINE_LOCATION_CONVERTER:
508 return locationInfo.getLineNumber();
509 case FILE_LOCATION_CONVERTER:
510 return locationInfo.getFileName();
511 default: return null;
516 private static abstract class NamedPatternConverter extends PatternConverter {
519 NamedPatternConverter(FormattingInfo formattingInfo, int precision) {
520 super(formattingInfo);
521 this.precision = precision;
525 String getFullyQualifiedName(LoggingEvent event);
528 String convert(LoggingEvent event) {
529 String n = getFullyQualifiedName(event);
533 int len = n.length();
535 // We substract 1 from 'len' when assigning to 'end' to avoid out of
536 // bounds exception in return r.substring(end+1, len). This can happen if
537 // precision is 1 and the category name ends with a dot.
539 for(int i = precision; i > 0; i--) {
540 end = n.lastIndexOf('.', end-1);
545 return n.substring(end+1, len);
550 private class ClassNamePatternConverter extends NamedPatternConverter {
552 ClassNamePatternConverter(FormattingInfo formattingInfo, int precision) {
553 super(formattingInfo, precision);
556 String getFullyQualifiedName(LoggingEvent event) {
557 return event.getLocationInformation().getClassName();
561 private class CategoryPatternConverter extends NamedPatternConverter {
563 CategoryPatternConverter(FormattingInfo formattingInfo, int precision) {
564 super(formattingInfo, precision);
567 String getFullyQualifiedName(LoggingEvent event) {
568 return event.getLoggerName();