001: /**
002: * Copyright (C) 2001-2003 France Telecom R&D
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 2 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */package org.objectweb.util.monolog.wrapper.javaLog;
018:
019: import org.objectweb.util.monolog.file.api.Pattern;
020: import org.objectweb.util.monolog.wrapper.common.AbstractFactory;
021:
022: import java.util.logging.Formatter;
023: import java.util.logging.LogRecord;
024: import java.util.StringTokenizer;
025: import java.util.ArrayList;
026: import java.util.List;
027: import java.util.Calendar;
028: import java.util.Date;
029: import java.io.StringWriter;
030: import java.io.PrintWriter;
031:
032: /**
033: * The goal of this class is to format a LogRecord with regard to a pattern.
034: * In order to support support additional logging layer on top monolog, this
035: * class permits to specify package name or class names of its own logging layer.
036: * By default the org.apache.commons.logging package is included. To specify
037: * the list of names you have to assign the system property 'monolog.wrappers'
038: * with a list (separated with commas or white spaces) of your names.
039: *
040: * @author S.Chassande-Barrioz
041: */
042: public class MonologFormatter extends Formatter {
043:
044: public final static String WRAPPERS_PROPERTY = "monolog.wrappers";
045:
046: /**
047: * Number of items to get back in stack trace to see the caller
048: */
049: private static final int STACK_TRACE_ITEMS = 9;
050:
051: private static String[] LOGWRAPPER = {
052: "org.objectweb.util.monolog.wrapper",
053: "org.apache.commons.logging" };
054:
055: static {
056: String wrap = System.getProperty(WRAPPERS_PROPERTY);
057: if (wrap != null) {
058: ArrayList ws = new ArrayList(5);
059: StringTokenizer st = new StringTokenizer(wrap, ",;: /",
060: false);
061: while (st.hasMoreTokens()) {
062: ws.add(st.nextToken());
063: }
064: String[] wsa = new String[LOGWRAPPER.length + ws.size()];
065: System.arraycopy(LOGWRAPPER, 0, wsa, 0, LOGWRAPPER.length);
066: for (int i = 0; i < ws.size(); i++) {
067: wsa[LOGWRAPPER.length + i] = (String) ws.get(i);
068: }
069: LOGWRAPPER = wsa;
070: }
071: }
072:
073: private final static int PATTERN_ID_LEVEL = -100;
074: private final static int PATTERN_ID_TOPIC = -200;
075: private final static int PATTERN_ID_DATE = -300;
076: private final static int PATTERN_ID_THREAD = -400;
077: private final static int PATTERN_ID_MESSAGE = -500;
078: private final static int PATTERN_ID_METHOD = -600;
079: private final static int PATTERN_ID_OBJECT = -700;
080: private final static int PATTERN_ID_LINE_NUMBER = -800;
081: private final static int PATTERN_ID_NEW_LINE = -900;
082: private final static int PATTERN_ID_INTERVAL = 100;
083:
084: private final static String TOKENS = "{}" + Pattern.LEVEL
085: + Pattern.TOPIC + Pattern.DATE + Pattern.THREAD
086: + Pattern.MESSAGE + Pattern.METHOD + Pattern.OBJECT
087: + Pattern.LINE_NUMBER + Pattern.PREFIX + Pattern.NEW_LINE;
088:
089: private final static String patternIdToString(int id) {
090: switch (id) {
091: case PATTERN_ID_LEVEL:
092: return "" + Pattern.LEVEL;
093: case PATTERN_ID_TOPIC:
094: return "" + Pattern.TOPIC;
095: case PATTERN_ID_DATE:
096: return "" + Pattern.DATE;
097: case PATTERN_ID_THREAD:
098: return "" + Pattern.THREAD;
099: case PATTERN_ID_MESSAGE:
100: return "" + Pattern.MESSAGE;
101: case PATTERN_ID_METHOD:
102: return "" + Pattern.METHOD;
103: case PATTERN_ID_OBJECT:
104: return "" + Pattern.OBJECT;
105: case PATTERN_ID_LINE_NUMBER:
106: return "" + Pattern.LINE_NUMBER;
107: case PATTERN_ID_NEW_LINE:
108: return "" + Pattern.NEW_LINE;
109: default:
110: return null;
111: }
112: }
113:
114: Calendar calendar;
115:
116: private static long previousTime;
117:
118: private static char[] previousTimeWithoutMillis = new char[20]; // "YYYY-MM-DD HH:mm:ss."
119:
120: /**
121: * the pattern in the string format (user value)
122: */
123: String strPattern;
124:
125: /**
126: * An array of pattern id representing the user pattern
127: */
128: int[] pattern;
129:
130: /**
131: * An array of String used into the pattern
132: */
133: String[] strings;
134:
135: public MonologFormatter() {
136: calendar = Calendar.getInstance();
137: }
138:
139: public MonologFormatter(String strPattern) {
140: this ();
141: setPattern(strPattern);
142: }
143:
144: public String getPattern() {
145: return strPattern;
146: }
147:
148: public void setPattern(String p) {
149: if (AbstractFactory.debug) {
150: AbstractFactory.debug("Pattern=" + p);
151: }
152: this .strPattern = p;
153: if (strPattern == null) {
154: pattern = new int[0];
155: } else {
156: StringTokenizer st = new StringTokenizer(p, TOKENS, true);
157: ArrayList sections = new ArrayList();
158: boolean isPrefix = false;
159: boolean isObject = false;
160: boolean isInSubObject = false;
161: int subObjectNumber = 1;
162: while (st.hasMoreElements()) {
163: String token = st.nextToken();
164: if (AbstractFactory.debug) {
165: AbstractFactory.debug("token=<" + token + ">");
166: }
167: if (isObject && token.equals("{")) {
168: isInSubObject = true;
169: isObject = false;
170: }
171: if (token.length() == 1) {
172: char c = token.charAt(0);
173: switch (c) {
174: case '{':
175: if (!isInSubObject) {
176: addSection(sections, token);
177: }
178: break;
179: case '}':
180: if (isInSubObject) {
181: int old = ((Integer) sections.get(sections
182: .size() - 1)).intValue();
183: sections.set(sections.size() - 1,
184: new Integer(old - subObjectNumber));
185: isInSubObject = false;
186: } else {
187: addSection(sections, token);
188: }
189: break;
190: case Pattern.PREFIX:
191: if (isPrefix) {
192: sections
193: .add(String.valueOf(Pattern.PREFIX));
194: }
195: isPrefix = !isPrefix;
196: break;
197: case Pattern.LEVEL:
198: isPrefix = treatPattern(sections, token,
199: PATTERN_ID_LEVEL, isPrefix);
200: break;
201: case Pattern.TOPIC:
202: isPrefix = treatPattern(sections, token,
203: PATTERN_ID_TOPIC, isPrefix);
204: break;
205: case Pattern.DATE:
206: isPrefix = treatPattern(sections, token,
207: PATTERN_ID_DATE, isPrefix);
208: break;
209: case Pattern.THREAD:
210: isPrefix = treatPattern(sections, token,
211: PATTERN_ID_THREAD, isPrefix);
212: break;
213: case Pattern.MESSAGE:
214: isPrefix = treatPattern(sections, token,
215: PATTERN_ID_MESSAGE, isPrefix);
216: break;
217: case Pattern.METHOD:
218: isPrefix = treatPattern(sections, token,
219: PATTERN_ID_METHOD, isPrefix);
220: break;
221: case Pattern.OBJECT:
222: isPrefix = treatPattern(sections, token,
223: PATTERN_ID_OBJECT, isPrefix);
224: isObject = true;
225: break;
226: case Pattern.LINE_NUMBER:
227: isPrefix = treatPattern(sections, token,
228: PATTERN_ID_LINE_NUMBER, isPrefix);
229: break;
230: case Pattern.NEW_LINE:
231: isPrefix = treatPattern(sections, token,
232: PATTERN_ID_NEW_LINE, isPrefix);
233: break;
234: default:
235: if (isInSubObject) {
236: subObjectNumber = Integer.parseInt(token);
237: } else {
238: addSection(sections, token);
239: }
240: break;
241: }
242: } else if (isObject) {
243: //Ignore
244: } else if (isInSubObject) {
245: subObjectNumber = Integer.parseInt(token);
246: } else {
247: addSection(sections, token);
248: }
249: }
250: pattern = new int[sections.size()];
251: if (AbstractFactory.debug) {
252: AbstractFactory.debug("building pattern array...");
253: AbstractFactory
254: .debug("nb of pattern:" + pattern.length);
255: }
256: ArrayList stringList = new ArrayList(sections.size());
257: int cpt = 0;
258: for (int i = 0; i < pattern.length; i++) {
259: Object o = sections.get(i);
260: if (o instanceof String) {
261: if (AbstractFactory.debug) {
262: AbstractFactory
263: .debug("add current pattern into strings: ["
264: + cpt + ", " + o + "]");
265: }
266: stringList.add(o);
267: pattern[i] = cpt;
268: cpt++;
269: } else if (o instanceof Integer) {
270: if (AbstractFactory.debug) {
271: AbstractFactory
272: .debug("add current pattern as negative number:"
273: + o);
274: }
275: pattern[i] = ((Integer) o).intValue();
276: }
277: }
278: strings = (String[]) stringList.toArray(new String[cpt]);
279: if (AbstractFactory.debug) {
280: AbstractFactory.debug("nb of string:" + strings.length);
281: }
282: }
283: }
284:
285: private boolean treatPattern(List sections, String token,
286: int tokenId, boolean isPrefix) {
287: if (AbstractFactory.debug) {
288: AbstractFactory.debug("treatPttern(" + tokenId + "):"
289: + " isPrefix=" + isPrefix + " token=" + token
290: + " sections=" + sections);
291: }
292: if (isPrefix) {
293: sections.add(new Integer(tokenId));
294: return false;
295: } else {
296: addSection(sections, token);
297: return isPrefix;
298: }
299: }
300:
301: private void addSection(List sections, String s) {
302: int size = sections.size();
303: if (size == 0) {
304: if (AbstractFactory.debug) {
305: AbstractFactory.debug("addSection(" + s + ", "
306: + sections + "): first elem");
307: }
308: sections.add(s);
309: } else {
310: Object last = sections.get(size - 1);
311: if (last instanceof String) {
312: sections.set(size - 1, last + s);
313: if (AbstractFactory.debug) {
314: AbstractFactory.debug("addSection(" + s + ", "
315: + sections + "): concat: "
316: + sections.get(size - 1));
317: }
318: } else {
319: if (AbstractFactory.debug) {
320: AbstractFactory.debug("addSection(" + s + ", "
321: + sections + "): new elem");
322: }
323: sections.add(s);
324: }
325: }
326: }
327:
328: /**
329: * Format the given log record and return the formatted string.
330: * <p>
331: * The resulting formatted String will normally include a
332: * localized and formated version of the LogRecord's message field.
333: * The Formatter.formatMessage convenience method can (optionally)
334: * be used to localize and format the message field.
335: *
336: * @param record the log record to be formatted.
337: * @return the formatted log record
338: */
339: public String format(LogRecord record) {
340: StringBuffer sb = new StringBuffer();
341: String[] ctx = null;
342: for (int i = 0; i < pattern.length; i++) {
343: int p = pattern[i];
344: if (AbstractFactory.debug) {
345: AbstractFactory.debug("format: pattern=" + p + "="
346: + patternIdToString(p));
347: }
348: switch (p) {
349: case PATTERN_ID_LEVEL:
350: sb.append(record.getLevel().getName());
351: break;
352: case PATTERN_ID_TOPIC:
353: sb.append(record.getLoggerName());
354: break;
355: case PATTERN_ID_DATE:
356: format(new Date(record.getMillis()), sb);
357: break;
358: case PATTERN_ID_THREAD:
359: sb.append(Thread.currentThread().getName());
360: break;
361: case PATTERN_ID_MESSAGE:
362: sb.append(record.getMessage());
363: break;
364: case PATTERN_ID_METHOD:
365: if (ctx == null) {
366: ctx = getContext();
367: }
368: sb.append(ctx[1]);
369: break;
370: case PATTERN_ID_OBJECT:
371: if (ctx == null) {
372: ctx = getContext();
373: }
374: sb.append(ctx[0]);
375: break;
376: case PATTERN_ID_LINE_NUMBER:
377: if (ctx == null) {
378: ctx = getContext();
379: }
380: sb.append(ctx[2]);
381: break;
382: case PATTERN_ID_NEW_LINE:
383: sb.append("\n");
384: break;
385: default:
386: if (p < 0) {
387: if (ctx == null) {
388: ctx = getContext();
389: }
390: if (p > (PATTERN_ID_OBJECT - PATTERN_ID_INTERVAL)
391: && p < PATTERN_ID_OBJECT) {
392: p = PATTERN_ID_OBJECT - p;
393: String res = ctx[0];
394: if (p == 1) {// optimize the usual case
395: int idx = res.lastIndexOf('.');
396: if (idx != -1) {
397: res = res.substring(idx + 1);
398: }
399: } else if (p == 0) {
400: //Nothing to do
401: } else {
402: int idx = res.lastIndexOf('.');
403: for (; p > 1 && idx != -1; p--) {
404: if (idx != -1) {
405: idx = res.lastIndexOf('.', idx - 1);
406: }
407: }
408: if (idx != -1) {
409: res = res.substring(idx + 1);
410: }
411: }
412: sb.append(res);
413: }
414: } else if (p >= strings.length) {
415: System.err
416: .println("ERROR: String identifier unknown: "
417: + p);
418: } else {
419: sb.append(strings[p]);
420: }
421: }
422: }
423: if (record.getThrown() != null) {
424: StringWriter sw = new StringWriter();
425: record.getThrown().printStackTrace(new PrintWriter(sw));
426: sb.append(sw.getBuffer());
427: }
428: return sb.toString();
429: }
430:
431: /**
432: * Calculate the class name, the method name and the line number of the
433: * logger user.
434: * @return a string array containing 3 String
435: * [ "classname", "method name", "line number"]
436: * ex: ["com.foo.Bar", "myMethod", "512"]
437: */
438: public static String[] getContext() {
439:
440: Throwable t = new Throwable().fillInStackTrace();
441: StringWriter sw = new StringWriter();
442: t.printStackTrace(new PrintWriter(sw));
443: String m = sw.getBuffer().toString();
444: int fin = 0;
445: int deb = 0;
446: // remove monolog methods from the stack trace
447: for (int i = 0; i < STACK_TRACE_ITEMS; i++) {
448: deb = m.indexOf("\n", deb) + 1;
449: }
450: boolean isWrapper = true;
451: deb = m.indexOf("at ", deb) + 3;
452: while (isWrapper) {
453: isWrapper = false;
454: for (int i = 0; i < LOGWRAPPER.length && !isWrapper; i++) {
455: isWrapper |= m.startsWith(LOGWRAPPER[i], deb);
456: }
457: if (isWrapper) {
458: deb = m.indexOf("at ", deb) + 3;
459: }
460: }
461: fin = m.indexOf("\n", deb);
462: m = m.substring(deb, fin);
463:
464: //m = %C.%M(Toto.java:%L)
465: deb = m.indexOf("(");
466: fin = m.indexOf(":");
467: String[] res = new String[3];
468: res[2] = (fin == -1 ? "unknown" : m.substring(fin + 1, m
469: .length() - 1));
470: m = m.substring(0, deb);
471:
472: //m = %C.%M
473: fin = m.lastIndexOf('.');
474: res[0] = m.substring(0, fin);
475: res[1] = m.substring(fin + 1);
476: return res;
477: }
478:
479: /**
480: Appends to <code>sbuf</code> the time in the format
481: "YYYY-MM-DD HH:mm:ss,SSS" for example, "2004-04-28 15:49:37,459"
482:
483: @param date the date to format
484: @param sbuf the string buffer to write to
485: */
486: public void format(Date date, StringBuffer sbuf) {
487: long now = date.getTime();
488: int millis = (int) (now % 1000);
489:
490: if ((now - millis) != previousTime) {
491: // We reach this point at most once per second
492: // across all threads instead of each time format()
493: // is called. This saves considerable CPU time.
494:
495: calendar.setTime(date);
496:
497: int start = sbuf.length();
498:
499: int year = calendar.get(Calendar.YEAR);
500: sbuf.append(year);
501: sbuf.append('-');
502: int month = calendar.get(Calendar.MONTH);
503: month++;
504: if (month < 10) {
505: sbuf.append('0');
506: }
507: sbuf.append(month);
508: sbuf.append('-');
509: int day = calendar.get(Calendar.DAY_OF_MONTH);
510: if (day < 10) {
511: sbuf.append('0');
512: }
513: sbuf.append(day);
514:
515: sbuf.append(' ');
516:
517: int hour = calendar.get(Calendar.HOUR_OF_DAY);
518: if (hour < 10) {
519: sbuf.append('0');
520: }
521: sbuf.append(hour);
522: sbuf.append(':');
523:
524: int mins = calendar.get(Calendar.MINUTE);
525: if (mins < 10) {
526: sbuf.append('0');
527: }
528: sbuf.append(mins);
529: sbuf.append(':');
530:
531: int secs = calendar.get(Calendar.SECOND);
532: if (secs < 10) {
533: sbuf.append('0');
534: }
535: sbuf.append(secs);
536: sbuf.append(',');
537:
538: // store the time string for next time to avoid recomputation
539: sbuf.getChars(start, sbuf.length(),
540: previousTimeWithoutMillis, 0);
541:
542: previousTime = now - millis;
543: } else {
544: sbuf.append(previousTimeWithoutMillis);
545: }
546: if (millis < 100) {
547: sbuf.append('0');
548: }
549: if (millis < 10) {
550: sbuf.append('0');
551: }
552: sbuf.append(millis);
553: }
554:
555: public String format(String msg, String levelName, String topic,
556: long time) {
557: StringBuffer sb = new StringBuffer();
558: String[] ctx = null;
559: for (int i = 0; i < pattern.length; i++) {
560: int p = pattern[i];
561: if (AbstractFactory.debug) {
562: AbstractFactory.debug("format: pattern=" + p + "="
563: + patternIdToString(p));
564: }
565: switch (p) {
566: case PATTERN_ID_LEVEL:
567: sb.append(levelName);
568: break;
569: case PATTERN_ID_TOPIC:
570: sb.append(topic);
571: break;
572: case PATTERN_ID_DATE:
573: format(new Date(time), sb);
574: break;
575: case PATTERN_ID_THREAD:
576: sb.append(Thread.currentThread().getName());
577: break;
578: case PATTERN_ID_MESSAGE:
579: sb.append(msg);
580: break;
581: case PATTERN_ID_METHOD:
582: if (ctx == null) {
583: ctx = getContext();
584: }
585: sb.append(ctx[1]);
586: break;
587: case PATTERN_ID_OBJECT:
588: if (ctx == null) {
589: ctx = getContext();
590: }
591: sb.append(ctx[0]);
592: break;
593: case PATTERN_ID_LINE_NUMBER:
594: if (ctx == null) {
595: ctx = getContext();
596: }
597: sb.append(ctx[2]);
598: break;
599: case PATTERN_ID_NEW_LINE:
600: sb.append("\n");
601: break;
602: default:
603: if (p < 0) {
604: if (ctx == null) {
605: ctx = getContext();
606: }
607: if (p > (PATTERN_ID_OBJECT - PATTERN_ID_INTERVAL)
608: && p < PATTERN_ID_OBJECT) {
609: p = PATTERN_ID_OBJECT - p;
610: String res = ctx[0];
611: if (p == 1) {// optimize the usual case
612: int idx = res.lastIndexOf('.');
613: if (idx != -1) {
614: res = res.substring(idx + 1);
615: }
616: } else if (p == 0) {
617: //Nothing to do
618: } else {
619: int idx = res.lastIndexOf('.');
620: for (; p > 1 && idx != -1; p--) {
621: if (idx != -1) {
622: idx = res.lastIndexOf('.', idx - 1);
623: }
624: }
625: if (idx != -1) {
626: res = res.substring(idx + 1);
627: }
628: }
629: sb.append(res);
630: }
631: } else if (p >= strings.length) {
632: System.err
633: .println("ERROR: String identifier unknown: "
634: + p);
635: } else {
636: sb.append(strings[p]);
637: }
638: }
639: }
640: return sb.toString();
641: }
642:
643: }
|