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

Revision 1577, 19.8 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.io.Serializable;
37import java.net.InetAddress;
38import java.net.UnknownHostException;
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.Collections;
42import java.util.HashMap;
43import java.util.Iterator;
44import java.util.List;
45import java.util.Map;
46import java.util.Random;
47import java.util.Set;
48import java.util.Map.Entry;
49import java.util.logging.Logger;
50
51import org.jcoderz.commons.util.ThrowableUtil;
52
53
54/**
55 * Implements code common to all Exceptions.
56 * <p>
57 * The two base exceptions {@link org.jcoderz.commons.BaseException}
58 * and {@link org.jcoderz.commons.BaseRuntimeException} and the
59 * {@link org.jcoderz.commons.LogEvent} use a object of this class
60 * as a member and delegate all common calls to this member.
61 * </p>
62 * <p>
63 * This class also implements the special
64 * {@link org.jcoderz.commons.Loggable} that allows to add named
65 * parameters and to get a more detailed logging, with an assigned
66 * error message.
67 * </p>
68 * <p>
69 * The error response id is used to mark log entries with an unique id. This id
70 * is also returned to the client (caller). If the client reports the error
71 * response id it can be used to find a specific log entries more quickly. If
72 * the nested exception has already an error response id, it is re-used for this
73 * exception and will not get a new one.
74 * </p>
75 * Functionality provided by this class is:
76 * <ul>
77 * <li>Create a unique <code>ERROR_RESPONSE_ID</code> parameter for each
78 * instance.</li>
79 * <li>Create a message that contains all parameters for a full informational
80 * toString() output.</li>
81 * <li>Handles nested exceptions so that all information is available and
82 * avoids duplicate information for the JDK1.4 environment which supports nested
83 * exceptions itself.</li>
84 * <li>Holds the constant names for commonly used exception parameters.</li>
85 * </ul>
86 *
87 * @author Andreas Mandel
88 */
89public class LoggableImpl
90      implements Serializable, Loggable
91{
92   /** Name of this class. */
93   public static final String CLASSNAME = LoggableImpl.class.getName();
94
95   /** Logger used for this class. */
96   public static final Logger logger = Logger.getLogger(CLASSNAME);
97
98   /** Key used for the log message info parameter object. */
99   public static final String MESSAGE_INFO_PARAMETER_NAME = "_MESSAGE_INFO";
100
101   /** Key used for the error response id added to the loggable. */
102   public static final String TRACKING_NUMBER_PARAMETER_NAME
103         = "_TRACKING_NUMBER";
104
105   /** Key used for the root cause added to the loggable. */
106   public static final String CAUSE_PARAMETER_NAME = "_CAUSE";
107
108   /** Key used for the thread id parameter object. */
109   public static final String THREAD_ID_PARAMETER_NAME = "_THREAD_ID";
110
111   /** Key used for the thread name parameter object. */
112   public static final String THREAD_NAME_PARAMETER_NAME = "_THREAD_NAME";
113
114   /** Key used for the instance id parameter object. */
115   public static final String INSTANCE_ID_PARAMETER_NAME = "_INSTANCE_ID";
116
117   /** Key used for the node id parameter object. */
118   public static final String NODE_ID_PARAMETER_NAME = "_NODE_ID";
119
120   /** Key used for the event time parameter of the loggable. */
121   public static final String EVENT_TIME_PARAMETER_NAME = "_TIME";
122
123   /** Name of the application of the loggable. */
124   public static final String APPLICATION_NAME_PARAMETER_NAME = "_APPLICATION";
125
126   /** Name of the group of the loggable. */
127   public static final String GROUP_NAME_PARAMETER_NAME = "_GROUP";
128
129   /** Context parameter values prefix. */
130   public static final String CONTEXT_PARAMETER_PREFIX = "CTX~";
131   
132   /** This nodes id. */
133   public static final String NODE_ID = getStaticNodeId();
134
135   /** Id for this instance. */
136   public static final String INSTANCE_ID;
137
138   /** Virtual thread Id generated for this thread. */
139   public static final ThreadIdHolder THREAD_ID_GENERATOR
140       = new ThreadIdHolder();
141
142   static final long serialVersionUID = 1;
143
144   /**
145    * Maximum number of steps to get the cause of an exception,
146    * until we stop climbing up the cause chain.
147    */
148   private static final int MAX_EXCEPTION_CHAIN_UP = 20;
149
150   /**
151    * In the first step use bea specific instance name, which is set as system
152    * property with the following name.
153    * TODO: Make this bea-independent, requires entry in logging.properties,
154    * system property, or something alike.
155    */
156   private static final String INSTANCE_NAME_PROPERTY = "weblogic.Name";
157
158   /** Random generator to create pseudo unique Ids for each loggable. */
159   private static final Random RANDOM_ID_GENERATOR = new Random();
160
161
162   private static final String DUMMY_INSTANCE_ID
163         = "P" + Integer.toHexString(RANDOM_ID_GENERATOR.nextInt());
164   private static final String DUMMY_NODE_ID = "127.0.0.1";
165
166   /**
167    * list of parameter for this exception The list is not thread save!
168    */
169   private final Map mParameters = new HashMap();
170
171   /**
172    * Remember the ERROR_RESPONSE_ID. Intention is to log this id with the
173    * exception and pass the Id to the recipient. It should be really easy to
174    * find the exception in the log.
175    */
176   private final String mTrackingNumber;
177
178   /** The error ID for this loggable */
179   private final LogMessageInfo mLogMessageInfo;
180
181   /** The point in time when this event occurred. */
182   private final long mEventTime;
183
184   /** The node id. */
185   private final String mNodeId;
186
187   /** The id for this instance id. */
188   private final String mInstanceId;
189
190   /** The thread id. */
191   private final long mThreadId;
192
193   /** The thread name. */
194   private final String mThreadName;
195
196   /**
197    * The Throwable that caused this loggable.
198    * Should be equal to mOuter.getCause()
199    */
200   private Throwable mCause;
201
202   /** The outer exception, where this loggable belongs to. */
203   private Loggable mOuter;
204
205   private String mClassName = null;
206   private String mMethodName = null;
207
208   static
209   {
210      INSTANCE_ID = getStaticInstanceId();
211   }
212
213   /**
214    * Create this loggable provide the 'Loggable' functionality for the
215    * given outer loggable.
216    * @param outer the the outer loggable.
217    * @param errorId the static LogMessageInfo for this Loggable.
218    */
219   public LoggableImpl (Loggable outer, LogMessageInfo errorId)
220   {
221      this(outer, errorId, THREAD_ID_GENERATOR.getThreadId(),
222          Thread.currentThread().getName(), INSTANCE_ID, NODE_ID);
223   }
224
225   /**
226    * Create this loggable provide the 'Loggable' functionality for the
227    * given outer loggable with an initial cause.
228    * @param outer the the outer loggable.
229    * @param errorId the static LogMessageInfo for this Loggable.
230    * @param cause the cause of the outer.
231    */
232   public LoggableImpl (Loggable outer, LogMessageInfo errorId,
233       Throwable cause)
234   {
235      this(outer, errorId, THREAD_ID_GENERATOR.getThreadId(),
236          Thread.currentThread().getName(), INSTANCE_ID,
237            NODE_ID, cause);
238   }
239
240   /**
241    * Create this loggable provide the 'Loggable' functionality for the
242    * given outer loggable with the given dynamic parameters.
243    * @param outer the the outer loggable.
244    * @param errorId the static LogMessageInfo for this Loggable.
245    * @param threadId the threadId to be set.
246    * @param threadName the threadName to be set.
247    * @param instanceId the instanceId to be set.
248    * @param nodeId the nodeId to be set.
249    */
250   public LoggableImpl (Loggable outer, LogMessageInfo errorId,
251       long threadId, String threadName, String instanceId, String nodeId)
252   {
253      mEventTime = System.currentTimeMillis();
254      mTrackingNumber = Integer.toHexString(RANDOM_ID_GENERATOR.nextInt());
255      mLogMessageInfo = errorId;
256      mThreadId = threadId;
257      mThreadName = threadName;
258      mInstanceId = instanceId;
259      mNodeId = nodeId;
260      mOuter = outer;
261      initInternalParameters();
262      initThreadContextParameters();
263   }
264
265   /**
266    * Create this loggable provide the 'Loggable' functionality for the
267    * given outer loggable with the given dynamic parameters and an
268    * initial cause..
269    * @param outer the the outer loggable.
270    * @param errorId the static LogMessageInfo for this Loggable.
271    * @param threadId the threadId to be set.
272    * @param threadName the threadName to be set.
273    * @param instanceId the instanceId to be set.
274    * @param nodeId the nodeId to be set.
275    * @param cause the cause of the outer.
276    */
277   public LoggableImpl (Loggable outer, LogMessageInfo errorId,
278       long threadId, String threadName, String instanceId, String nodeId,
279       Throwable cause)
280   {
281      mEventTime = System.currentTimeMillis();
282      ThrowableUtil.fixChaining(cause);
283      Throwable thr = cause;
284      int depth = 0;
285      while (thr != null
286          && !(thr instanceof Loggable)
287          && depth < MAX_EXCEPTION_CHAIN_UP)
288      {
289          thr = thr.getCause();
290          depth++;
291      }
292      if (thr instanceof Loggable)
293      {
294         mTrackingNumber = ((Loggable) thr).getTrackingNumber();
295      }
296      else
297      {
298         mTrackingNumber = Integer.toHexString(RANDOM_ID_GENERATOR.nextInt());
299      }
300      mLogMessageInfo = errorId;
301      mThreadId = threadId;
302      mThreadName = threadName;
303      mInstanceId = instanceId;
304      mNodeId = nodeId;
305      mOuter = outer;
306      initCause(cause);
307      initInternalParameters();
308      initThreadContextParameters();
309   }
310
311   /**
312    * Sets the cause of this throwable.
313    *
314    * This method should be called after the call to the
315    * {@link Throwable#initCause(Throwable)} for the case
316    * the super call fails.
317    *
318    * @param cause the cause of this Exception.
319    */
320   public final void initCause (Throwable cause)
321   {
322      mCause = cause;
323      addParameter(CAUSE_PARAMETER_NAME, cause);
324      ThrowableUtil.fixChaining(cause);
325      ThrowableUtil.collectNestedData(this);
326   }
327
328   /**
329    * Adds a new named parameter. The parameter is added at the end of the list
330    * of parameters. The same <code>name</code> might occur several times.
331    *
332    * @param name the name of the parameter.
333    * @param value The value of the parameter
334    */
335   public final void addParameter (String name, Serializable value)
336   {
337      List values = (List) mParameters.get(name);
338      if (values == null)
339      {
340         values = new ArrayList();
341         mParameters.put(name, values);
342      }
343      values.add(value);
344   }
345
346   /** {@inheritDoc} */
347   public List getParameter (String name)
348   {
349      final List values = (List) mParameters.get(name);
350
351      final List result;
352      if (values != null)
353      {
354         result = Collections.unmodifiableList(values);
355      }
356      else
357      {
358         result = Collections.EMPTY_LIST;
359      }
360      return result;
361   }
362
363   /** {@inheritDoc} */
364   public Set getParameterNames ()
365   {
366      return Collections.unmodifiableSet(mParameters.keySet());
367   }
368
369   /** {@inheritDoc} */
370   public final LogMessageInfo getLogMessageInfo ()
371   {
372      return mLogMessageInfo;
373   }
374
375   /** {@inheritDoc} */
376   public final String getTrackingNumber ()
377   {
378      return mTrackingNumber;
379   }
380
381   /** {@inheritDoc} */
382   public final long getEventTime ()
383   {
384      return mEventTime;
385   }
386
387   /** {@inheritDoc} */
388   public final String getNodeId ()
389   {
390      return mNodeId;
391   }
392
393   /** {@inheritDoc} */
394   public final String getInstanceId ()
395   {
396      return mInstanceId;
397   }
398
399   /** {@inheritDoc} */
400   public final long getThreadId ()
401   {
402      return mThreadId;
403   }
404
405   /** {@inheritDoc} */
406   public final String getThreadName ()
407   {
408      return mThreadName;
409   }
410
411   /** {@inheritDoc} */
412   public Throwable getCause ()
413   {
414      return mCause;
415   }
416
417   /** {@inheritDoc} */
418   public void log ()
419   {
420      getSource();
421      logger.logp(getLogMessageInfo().getLogLevel(), mClassName, mMethodName,
422            getMessage(), mOuter);
423   }
424
425   /** {@inheritDoc} */
426   public String getMessage ()
427   {
428      return getLogMessageInfo().formatMessage(
429          mParameters, new StringBuffer()).toString();
430   }
431
432   /** {@inheritDoc} */
433   public String getSourceClass ()
434   {
435       getSource();
436       return mClassName;
437   }
438
439   /** {@inheritDoc} */
440   public String getSourceMethod ()
441   {
442       getSource();
443       return mMethodName;
444   }
445
446   /** {@inheritDoc} */
447   public String toString ()
448   {
449      final StringBuffer sb = new StringBuffer();
450      if (mOuter != null)
451      {
452          sb.append(mOuter.getClass().getName());
453      }
454      else
455      {
456          sb.append(getClass().getName());
457      }
458      sb.append(": ");
459      getLogMessageInfo().formatMessage(mParameters, sb);
460      return sb.toString();
461   }
462
463   /** {@inheritDoc} */
464   public String toDetailedString ()
465   {
466       final StringBuffer sb = new StringBuffer();
467       LoggableImpl.appendParameters(sb, this);
468       Throwable cause = null;
469       if (mOuter != null)
470       {
471           cause = mOuter.getCause();
472       }
473       if (cause == null)
474       {
475           cause = getCause();
476       }
477       // add parameters of nested chain
478       int depth = 0;
479       while (cause != null && depth < MAX_EXCEPTION_CHAIN_UP)
480       {
481           if (cause instanceof Loggable)
482           {
483               sb.append("\nCaused by: ");
484               LoggableImpl.appendParameters(sb, (Loggable) cause);
485               break;
486           }
487           cause = cause.getCause();
488           depth++;
489       }
490       cause = null;
491       if (mOuter != null)
492       {
493           cause = mOuter.getCause();
494       }
495       if (cause == null)
496       {
497           cause = getCause();
498       }
499       if (cause != null)
500       {
501           sb.append('\n');
502           sb.append(ThrowableUtil.toString(cause));
503       }
504       return sb.toString();
505   }
506
507   private void initInternalParameters ()
508   {
509      addParameter(MESSAGE_INFO_PARAMETER_NAME, mLogMessageInfo);
510      addParameter(TRACKING_NUMBER_PARAMETER_NAME, mTrackingNumber);
511      addParameter(EVENT_TIME_PARAMETER_NAME, new Long(mEventTime));
512      addParameter(THREAD_ID_PARAMETER_NAME, new Long(mThreadId));
513      addParameter(THREAD_NAME_PARAMETER_NAME, mThreadName);
514      addParameter(INSTANCE_ID_PARAMETER_NAME, mInstanceId);
515      addParameter(NODE_ID_PARAMETER_NAME, mNodeId);
516      addParameter(APPLICATION_NAME_PARAMETER_NAME,
517            mLogMessageInfo.getAppName());
518      addParameter(GROUP_NAME_PARAMETER_NAME, mLogMessageInfo.getGroupName());
519   }
520
521   private final void initThreadContextParameters ()
522   {
523       final Iterator i = LogThreadContext.get().entrySet().iterator();
524       while (i.hasNext())
525       {
526           final Entry entry = (Entry) i.next();
527           addParameter(
528               CONTEXT_PARAMETER_PREFIX + entry.getKey(), 
529               String.valueOf(entry.getValue()));
530       }
531   }
532   
533   private final void getSource ()
534   {
535      // not analyzed yet.
536      if (mMethodName == null || mClassName == null)
537      {
538          final StackTraceElement[] stack = new Throwable().getStackTrace();
539          // First, search back to a method in the Logger class.
540          int ix = 0;
541          boolean found = false;
542          while (ix < stack.length)
543          {
544             final StackTraceElement frame = stack[ix];
545             final String cname = frame.getClassName();
546             if (cname.equals(CLASSNAME))
547             {
548                found = true;
549             }
550             else if (found)
551             {
552                break;
553             }
554             ix++;
555          }
556          // Now search for the first frame before the "LoggableImpl" class or
557          // LogMessageInfo class.
558          while (ix < stack.length)
559          {
560             final StackTraceElement frame = stack[ix];
561             try
562             {
563                final String cname = frame.getClassName();
564                final Class clazz = Class.forName(cname);
565                if (! (Loggable.class.isAssignableFrom(clazz)
566                      || LogMessageInfo.class.isAssignableFrom(clazz)))
567                {
568                   // We've found the relevant frame.
569                   setMethodAndClass(frame);
570                   break;
571                }
572             }
573             catch (ClassNotFoundException e)
574             {
575                setMethodAndClass(frame);
576                break;
577             }
578             ix++;
579          }
580      }
581   }
582
583   private void setMethodAndClass (final StackTraceElement frame)
584   {
585      mClassName = frame.getClassName();
586      mMethodName = frame.getMethodName();
587      final String fileName = frame.getFileName();
588      if (fileName != null)
589      {
590         final int lineNumber = frame.getLineNumber();
591         if (lineNumber >= 0)
592         {
593            mMethodName = frame.getMethodName()
594                  + "(" + fileName + ":" + lineNumber + ")";
595         }
596         else
597         {
598            mMethodName = frame.getMethodName() + "(" + fileName + ")";
599         }
600      }
601      else if (frame.getMethodName().indexOf('(') < 0)
602      {
603         mMethodName = frame.getMethodName() + "()";
604      }
605      else
606      {
607          mMethodName = frame.getMethodName();
608      }
609   }
610
611   private static void appendParameters (StringBuffer sb, Loggable loggable)
612   {
613       sb.append(loggable.toString());
614
615       final Object[] params = loggable.getParameterNames().toArray();
616       Arrays.sort(params);
617       final Iterator/*<String>*/ parameterNames
618           = Arrays.asList(params).iterator();
619       while (parameterNames.hasNext())
620       {
621           final String parameterName = (String) parameterNames.next();
622           sb.append("\n\t");
623           sb.append(parameterName);
624           sb.append(": \t");
625           sb.append(loggable.getParameter(parameterName));
626       }
627   }
628
629   private static String getStaticNodeId ()
630   {
631      String nodeId = DUMMY_NODE_ID;
632      try
633      {
634         nodeId = InetAddress.getLocalHost().getHostAddress();
635      }
636      catch (UnknownHostException e)
637      {
638         System.err.println("Error retrieving inet address of local host, "
639               + "setting " + DUMMY_NODE_ID + " as node id");
640      }
641      return nodeId;
642   }
643
644   private static String getStaticInstanceId ()
645   {
646      return System.getProperty(INSTANCE_NAME_PROPERTY, DUMMY_INSTANCE_ID);
647   }
648
649
650   private static class ThreadIdHolder
651         extends ThreadLocal
652   {
653      private static final long INITIAL_THREAD_ID = 10L;
654      private static long sNextThreadId = INITIAL_THREAD_ID;
655
656      protected Object initialValue ()
657      {
658         return new Long(sNextThreadId++);
659      }
660
661      long getThreadId ()
662      {
663         return ((Long) get()).longValue();
664      }
665   }
666}
Note: See TracBrowser for help on using the browser.