root/trunk/src/java/org/jcoderz/commons/LogFormatter.java

Revision 1577, 15.3 kB (checked in by amandel, 2 years ago)

Allow to add context parameters to the log records via LogThreadContext?.
The parameters are added as logging parameters with a CTX~ prefix.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 *
4 * Copyright 2006, The jCoderZ.org Project. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 *    * Redistributions of source code must retain the above copyright
11 *      notice, this list of conditions and the following disclaimer.
12 *    * Redistributions in binary form must reproduce the above
13 *      copyright notice, this list of conditions and the following
14 *      disclaimer in the documentation and/or other materials
15 *      provided with the distribution.
16 *    * Neither the name of the jCoderZ.org Project nor the names of
17 *      its contributors may be used to endorse or promote products
18 *      derived from this software without specific prior written
19 *      permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
25 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33package org.jcoderz.commons;
34
35
36import java.util.ArrayList;
37import java.util.HashMap;
38import java.util.List;
39import java.util.Map;
40import java.util.logging.Formatter;
41import java.util.logging.LogRecord;
42import java.util.logging.Logger;
43
44import org.jcoderz.commons.logging.LogLineFormat;
45import org.jcoderz.commons.logging.LogLineFormatFactory;
46
47/**
48 * This type implements a Formatter to be used for logging in a format, which
49 * allows filtering of log files with standard tools and little effort. It
50 * formats both standard a {@link java.util.logging.LogRecord} and instances of
51 * {@link org.jcoderz.commons.Loggable}.
52 *
53 */
54public class LogFormatter
55      extends Formatter
56{
57    /**
58     * Name of the logger that controls which log level is needed as minimum
59     * to trigger stack traces with log messages.
60     */
61    public static final String MSG_LOGGER_STACK_TRACE = "msgLoggerStackTrace";
62    private static final Logger FWK_TRACE_LOGGER_LOGGER
63        = Logger.getLogger(MSG_LOGGER_STACK_TRACE);
64    private final ThreadLocal mMessageFormatters = new ThreadLocal();
65
66   /** {@inheritDoc} */
67   public String format (LogRecord record)
68   {
69      final StringBuffer sb = new StringBuffer();
70      Loggable loggable = null;
71      if (record.getParameters() != null && record.getParameters().length > 0)
72      {
73         if (record.getParameters()[0] instanceof Loggable)
74         {
75            loggable = (Loggable) record.getParameters()[0];
76         }
77      }
78      format(sb, record, loggable);
79      return sb.toString();
80   }
81
82   /**
83    * Gets the message format for a log line with <code>type</code> as type
84    * specifier.
85    *
86    * @param type The type specifier for the requested message format.
87    *
88    * @return MessageFormat for log line of type <code>type</code>
89    */
90   private LogLineFormat getMessageFormat (
91         final LogLineFormat.LogLineType type)
92   {
93      Map formatters = (Map) mMessageFormatters.get();
94      if (formatters == null)
95      {
96         formatters = createMessageFormats();
97         mMessageFormatters.set(formatters);
98      }
99      return (LogLineFormat) formatters.get(type);
100   }
101
102   private Map createMessageFormats ()
103   {
104      final Map rc = new HashMap();
105
106      addMessageFormat(rc, LogLineFormat.TRACE_MESSAGE);
107      addMessageFormat(rc, LogLineFormat.EXCEPTION_MESSAGE);
108      addMessageFormat(rc, LogLineFormat.LOG_MESSAGE);
109      addMessageFormat(rc, LogLineFormat.ERROR_MESSAGE);
110      addMessageFormat(rc, LogLineFormat.STACKTRACE_MESSAGE);
111      addMessageFormat(rc, LogLineFormat.PARAMETER_LINE);
112      addMessageFormat(rc, LogLineFormat.NESTED_MESSAGE);
113
114      return rc;
115   }
116
117   /**
118    * Formats a LogRecord, which does not carry any parameters. In this case it
119    * is a trace record, not a Loggable is logged.
120    *
121    * @param sb the StringBuffer where to append the formatted log record
122    * @param record the log record to format
123    * @param trackingIdSequence a list collecting all tracking ids of messages
124    * being formatted by one call.
125    */
126   private void formatLogRecord (
127         final StringBuffer sb,
128         final LogRecord record,
129         final List trackingIdSequence)
130   {
131      LogLineFormat.LogLineType type;
132      if (record.getThrown() != null)
133      {
134         type = LogLineFormat.EXCEPTION_MESSAGE;
135      }
136      else
137      {
138         type = LogLineFormat.TRACE_MESSAGE;
139      }
140      final LogLineFormat format = getMessageFormat(type);
141      format.format(sb, record, null, trackingIdSequence, null, null);
142   }
143
144   /**
145    * Appends a full stack trace carried by the supplied LogRecord or Loggable
146    * to the string buffer. If neither of them carries a Throwable, nothing is
147    * done here. The stack trace appended by this contains the complete chain
148    * of throwables.
149    *
150    * @param sb the StringBuffer to which to append the stack trace
151    * @param record The LogRecord
152    * @param loggable the Loggable, might be null.
153    * @param trackingIdSequence the list collecting the sequence of tracking
154    * ids, must not be null.
155    */
156   private void appendStackTrace (
157         final StringBuffer sb,
158         final LogRecord record,
159         final Loggable loggable,
160         final List trackingIdSequence)
161   {
162      Throwable thrown = getTopLevelThrown(record, loggable);
163      Throwable outerTrace = null;
164      final LogLineFormat.LogLineType type = LogLineFormat.STACKTRACE_MESSAGE;
165      final LogLineFormat format = getMessageFormat(type);
166      while (thrown != null)
167      {
168         if (thrown instanceof Loggable)
169         {
170            addTrackingNumber(trackingIdSequence, (Loggable) thrown);
171         }
172         format.format(sb, record, loggable, trackingIdSequence,
173               thrown, outerTrace);
174         outerTrace = thrown;
175         thrown = outerTrace.getCause();
176      }
177   }
178
179   /**
180    * Appends the parameters carried by the supplied LogRecord or Loggable
181    * to the string buffer. If there are no parameters, nothing is done here.
182    *
183    * @param sb the StringBuffer to which to append the stack trace
184    * @param loggable the Loggable, might be null.
185    * @param trackingIdSequence the list collecting the sequence of tracking
186    * ids, must not be null.
187    */
188   private void appendParameters (
189         final StringBuffer sb,
190         final LogRecord record,
191         final Loggable loggable,
192         final List trackingIdSequence)
193   {
194      final LogLineFormat.LogLineType type = LogLineFormat.PARAMETER_LINE;
195      final LogLineFormat format = getMessageFormat(type);
196      format.format(sb, record, loggable, trackingIdSequence, null, null);
197   }
198
199   /**
200    * This loops through the nested Loggables/throwables and formats the
201    * complete message stack.
202    *
203    * @param sb The StringBuffer where to append the formatted message stack.
204    * @param record The source LogRecord to format
205    * @param loggable The first instance of Loggable, might be null if
206    * <code>record</code> does not carry a Loggable.
207    */
208   private void format (
209         final StringBuffer sb,
210         final LogRecord record,
211         final Loggable loggable)
212   {
213      List trackingIds = initialiseTrackingIds(record, loggable);
214      Loggable currentLoggable = loggable;
215      boolean isFirst = true;
216
217      Throwable cause = null;
218      while (isFirst || (! ((currentLoggable == null) && (cause == null))))
219      {
220         Throwable nestedCause = null;
221         if (currentLoggable != null)
222         {
223            formatLoggable(sb, record, currentLoggable, trackingIds);
224            nestedCause = currentLoggable.getCause();
225         }
226         else if (isFirst)
227         {
228            formatLogRecord(sb, record, trackingIds);
229            nestedCause = record.getThrown();
230         }
231         isFirst = false;
232         cause = (cause != null) ? cause.getCause() : nestedCause;
233         currentLoggable = null;
234
235         if (cause != null)
236         {
237            appendNestingLevel(sb, record, cause, trackingIds);
238            if (cause instanceof Loggable)
239            {
240               currentLoggable = (Loggable) cause;
241            }
242         }
243      }
244      // for messages: do not log stack traces for log messages of level
245      // below the FWK_TRACE_LOGGER_LOGGER log level.
246      if (!(loggable instanceof LogEvent)
247          || FWK_TRACE_LOGGER_LOGGER.isLoggable(record.getLevel()))
248      {
249          trackingIds = initialiseTrackingIds(record, loggable);
250          appendStackTrace(sb, record, loggable, trackingIds);
251      }
252   }
253
254   private void formatLoggable (
255         final StringBuffer sb,
256         final LogRecord record,
257         final Loggable loggable,
258         final List trackingIds)
259   {
260      final LogLineFormat.LogLineType type = determineType(loggable);
261      final LogLineFormat format = getMessageFormat(type);
262      format.format(sb, record, loggable, trackingIds, null, null);
263      appendParameters(sb, record, loggable, trackingIds);
264   }
265
266   /**
267    * Creates the message format for the specified type and adds it to the
268    * supplied map.
269    *
270    * @param msgFormats The map to which to add the new message format with
271    * <code>type</code> as key.
272    * @param type The type for which to create the format and add to the map.
273    */
274   private void addMessageFormat (
275         final Map msgFormats,
276         final LogLineFormat.LogLineType type)
277   {
278      final LogLineFormat format = LogLineFormatFactory.create(type);
279      msgFormats.put(type, format);
280   }
281
282   /**
283    * Determines the log line type for the supplied Loggable.
284    *
285    * @param loggable The Loggable for which to determine the logline type.
286    *
287    * @return The correct LogLineType for <code>loggable</code>.
288    *
289    * @see LogLineType
290    */
291   private LogLineFormat.LogLineType determineType (final Loggable loggable)
292   {
293      final Throwable cause;
294      final LogLineFormat.LogLineType rc;
295
296      if (loggable instanceof Throwable)
297      {
298         cause = (Throwable) loggable;
299      }
300      else
301      {
302         cause = loggable.getCause();
303      }
304      if ((cause != null) && ! (cause instanceof LogEvent))
305      {
306         rc = LogLineFormat.ERROR_MESSAGE;
307      }
308      else
309      {
310         rc = LogLineFormat.LOG_MESSAGE;
311      }
312      return rc;
313   }
314
315  /**
316   * Appends a nesting level to the StringBuffer. This is performed if the
317   * current Loggable carries a cause, which might be a Loggable itself.
318   * In case the cause is a Loggable, the tracking id sequence is extended with
319   * its tracking id and the symbol name is logged here. If the cause is not a
320   * Loggable, its name and message are logged.
321   *
322   * @param sb The StringBuffer where to append the nesting level.
323   * @param record The LogRecord currently formatted.
324   * @param cause The Throwable causing the nesting level.
325   * @param trackingIdSequence The list collecting the sequence of tracking ids.
326   */
327   private void appendNestingLevel (
328         final StringBuffer sb,
329         final LogRecord record,
330         final Throwable cause,
331         final List trackingIdSequence)
332   {
333      final Loggable loggable;
334      if (cause instanceof Loggable)
335      {
336         loggable = (Loggable) cause;
337      }
338      else
339      {
340         loggable = null;
341      }
342      final LogLineFormat.LogLineType type = LogLineFormat.NESTED_MESSAGE;
343      final LogLineFormat format = getMessageFormat(type);
344      if (loggable == null)
345      {
346         format.format(sb, record, null, trackingIdSequence, null, cause);
347      }
348      else
349      {
350         addTrackingNumber(trackingIdSequence, loggable);
351         format.format(sb, record, loggable, trackingIdSequence, null,
352               loggable.getLogMessageInfo().getSymbol());
353      }
354   }
355
356   /**
357    * Initialises the list holding the sequence of tracking ids. Creates a new
358    * list and fills it with the first tracking id, which is taken from the
359    * supplied loggable. If this is null, the sequence number of the supplied
360    * log record is taken.
361    *
362    * @param record The log record to format. Must not be null.
363    * @param loggable The loggable being encapsulated by <code>record</code>,
364    * might be null.
365    *
366    * @return List with first tracking id.
367    */
368   private List initialiseTrackingIds (
369         final LogRecord record,
370         final Loggable loggable)
371   {
372      final List rc = new ArrayList();
373
374      if (loggable != null)
375      {
376         addTrackingNumber(rc, loggable);
377      }
378      else
379      {
380         addTrackingNumber(rc, record);
381      }
382      return rc;
383   }
384
385   /**
386    * Adds the record's sequence number as new tracking id to the sequence of
387    * tracking ids, if it is not already included as last element.
388    *
389    * @param trackingIds The list storing the sequence of tracking ids.
390    * @param record The record for which to add the sequence number.
391    */
392   private void addTrackingNumber (
393         final List trackingIds,
394         final LogRecord record)
395   {
396      addTrackingNumber(trackingIds,
397            Integer.toHexString((int) record.getSequenceNumber()));
398   }
399
400   /**
401    * Adds the loggable's tracking number as new tracking id to the sequence of
402    * tracking ids, if it is not already included as last element.
403    *
404    * @param trackingIds The list storing the sequence of tracking ids.
405    * @param loggable The Loggable for which to add the tracking number.
406    */
407   private void addTrackingNumber (
408         final List trackingIds,
409         final Loggable loggable)
410   {
411      addTrackingNumber(trackingIds, loggable.getTrackingNumber());
412   }
413
414   /**
415    * Adds the new tracking number as new tracking id to the sequence of
416    * tracking ids, if it is not already included as last element.
417    *
418    * @param trackingIds The list storing the sequence of tracking ids.
419    * @param newId The number to add to the sequence.
420    */
421   private void addTrackingNumber (
422         final List trackingIds,
423         final String newId)
424   {
425      if (! trackingIds.isEmpty())
426      {
427         if (! trackingIds.get(trackingIds.size() - 1).equals(newId))
428         {
429            trackingIds.add(newId);
430         }
431      }
432      else
433      {
434         trackingIds.add(newId);
435      }
436   }
437
438   /**
439    * Gets the top level throwable from the supplied LogRecord and Loggable.
440    * This is either the cause of <code>record or loggable</code> or
441    * <code>loggable</code> itself.
442    *
443    * @param record The LogRecord currently formatted.
444    * @param loggable The Loggable carried by <code>record</code>
445    *
446    * @return top level Throwable, might be null if no such.
447    */
448   private Throwable getTopLevelThrown (
449         final LogRecord record,
450         final Loggable loggable)
451   {
452      final Throwable thrown;
453
454      if (loggable == null)
455      {
456         thrown = record.getThrown();
457      }
458      else
459      {
460         if (loggable instanceof Throwable)
461         {
462            thrown = (Throwable) loggable;
463         }
464         else
465         {
466            thrown = loggable.getCause();
467         }
468      }
469      return thrown;
470   }
471}
Note: See TracBrowser for help on using the browser.