1   /*
2    * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
3    * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
4    */
5   
6   package java.util.logging;
7   import java.util.*;
8   import java.io.*;
9   import sun.misc.JavaLangAccess;
10  import sun.misc.SharedSecrets;
11  
12  /**
13   * LogRecord objects are used to pass logging requests between
14   * the logging framework and individual log Handlers.
15   * <p>
16   * When a LogRecord is passed into the logging framework it 
17   * logically belongs to the framework and should no longer be
18   * used or updated by the client application.
19   * <p>
20   * Note that if the client application has not specified an
21   * explicit source method name and source class name, then the
22   * LogRecord class will infer them automatically when they are
23   * first accessed (due to a call on getSourceMethodName or
24   * getSourceClassName) by analyzing the call stack.  Therefore,
25   * if a logging Handler wants to pass off a LogRecord to another
26   * thread, or to transmit it over RMI, and if it wishes to subsequently
27   * obtain method name or class name information it should call
28   * one of getSourceClassName or getSourceMethodName to force
29   * the values to be filled in.
30   * <p>
31   * <b> Serialization notes:</b>
32   * <ul>
33   * <li>The LogRecord class is serializable.
34   *
35   * <li> Because objects in the parameters array may not be serializable,
36   * during serialization all objects in the parameters array are
37   * written as the corresponding Strings (using Object.toString).
38   *
39   * <li> The ResourceBundle is not transmitted as part of the serialized
40   * form, but the resource bundle name is, and the recipient object's
41   * readObject method will attempt to locate a suitable resource bundle.
42   *
43   * </ul>
44   *
45   * @version %I%, %G%
46   * @since 1.4
47   */
48  
49  public class LogRecord implements java.io.Serializable {
50      private static long globalSequenceNumber;
51      private static int nextThreadId=10;
52      private static ThreadLocal threadIds = new ThreadLocal();
53  
54      /**
55       * @serial Logging message level
56       */
57      private Level level;
58  
59      /**
60       * @serial Sequence number
61       */
62      private long sequenceNumber;
63  
64      /**
65       * @serial Class that issued logging call
66       */
67      private String sourceClassName;
68  
69      /**
70       * @serial Method that issued logging call
71       */
72      private String sourceMethodName;
73  
74      /**
75       * @serial Non-localized raw message text
76       */
77      private String message;
78  
79      /**
80       * @serial Thread ID for thread that issued logging call.
81       */
82      private int threadID;
83  
84      /**
85       * @serial Event time in milliseconds since 1970
86       */
87      private long millis;
88  
89      /**
90       * @serial The Throwable (if any) associated with log message
91       */
92      private Throwable thrown;
93  
94      /**
95       * @serial Name of the source Logger.
96       */
97      private String loggerName;
98  
99      /**
100      * @serial Resource bundle name to localized log message.
101      */
102     private String resourceBundleName;
103 
104     private transient boolean needToInferCaller;
105     private transient Object parameters[];
106     private transient ResourceBundle resourceBundle;
107 
108     /**
109      * Construct a LogRecord with the given level and message values.
110      * <p>
111      * The sequence property will be initialized with a new unique value.
112      * These sequence values are allocated in increasing order within a VM.
113      * <p>
114      * The millis property will be initialized to the current time.
115      * <p>
116      * The thread ID property will be initialized with a unique ID for
117      * the current thread.
118      * <p>
119      * All other properties will be initialized to "null". 
120      * 
121      * @param level  a logging level value
122      * @param msg  the raw non-localized logging message (may be null)
123      */
124     public LogRecord(Level level, String msg) {
125     // Make sure level isn't null, by calling random method.
126     level.getClass();
127     this.level = level;
128     message = msg;
129     // Assign a thread ID and a unique sequence number.
130     synchronized (LogRecord.class) {
131         sequenceNumber = globalSequenceNumber++;
132         Integer id = (Integer)threadIds.get();
133         if (id == null) {
134         id = new Integer(nextThreadId++);
135         threadIds.set(id);
136         }
137         threadID = id.intValue();
138     }
139     millis = System.currentTimeMillis(); 
140     needToInferCaller = true;
141    }
142 
143     /**
144      * Get the source Logger name's
145      *
146      * @return source logger name (may be null)
147      */
148     public String getLoggerName() {
149     return loggerName;
150     }
151    
152     /**
153      * Set the source Logger name.
154      *
155      * @param name   the source logger name (may be null)
156      */
157     public void setLoggerName(String name) {
158     loggerName = name;
159     }
160    
161     /**
162      * Get the localization resource bundle
163      * <p>
164      * This is the ResourceBundle that should be used to localize
165      * the message string before formatting it.  The result may
166      * be null if the message is not localizable, or if no suitable
167      * ResourceBundle is available.
168      */
169     public ResourceBundle getResourceBundle() {
170     return resourceBundle;
171     }
172    
173     /**
174      * Set the localization resource bundle.
175      *
176      * @param bundle  localization bundle (may be null)
177      */
178     public void setResourceBundle(ResourceBundle bundle) {
179     resourceBundle = bundle;
180     }
181    
182     /**
183      * Get the localization resource bundle name
184      * <p>
185      * This is the name for the ResourceBundle that should be
186      * used to localize the message string before formatting it.
187      * The result may be null if the message is not localizable.
188      */
189     public String getResourceBundleName() {
190     return resourceBundleName;
191     }
192    
193     /**
194      * Set the localization resource bundle name.
195      *
196      * @param name  localization bundle name (may be null)
197      */
198     public void setResourceBundleName(String name) {
199     resourceBundleName = name;
200     }
201    
202     /**
203      * Get the logging message level, for example Level.SEVERE.
204      * @return the logging message level
205      */  
206     public Level getLevel() {
207     return level;
208     }
209 
210     /**
211      * Set the logging message level, for example Level.SEVERE.
212      * @param level the logging message level
213      */  
214     public void setLevel(Level level) {
215     if (level == null) {
216         throw new NullPointerException();
217     }
218     this.level = level;
219     }
220 
221     /** 
222      * Get the sequence number.
223      * <p>
224      * Sequence numbers are normally assigned in the LogRecord
225      * constructor, which assigns unique sequence numbers to
226      * each new LogRecord in increasing order.
227      * @return the sequence number
228      */
229     public long getSequenceNumber() {
230     return sequenceNumber;
231     }
232 
233     /** 
234      * Set the sequence number.
235      * <p>
236      * Sequence numbers are normally assigned in the LogRecord constructor,
237      * so it should not normally be necessary to use this method.
238      */
239     public void setSequenceNumber(long seq) {
240     sequenceNumber = seq;
241     }
242 
243     /**
244      * Get the  name of the class that (allegedly) issued the logging request.
245      * <p>
246      * Note that this sourceClassName is not verified and may be spoofed.
247      * This information may either have been provided as part of the
248      * logging call, or it may have been inferred automatically by the
249      * logging framework.  In the latter case, the information may only
250      * be approximate and may in fact describe an earlier call on the
251      * stack frame.
252      * <p>
253      * May be null if no information could be obtained.
254      *
255      * @return the source class name
256      */  
257     public String getSourceClassName() {
258     if (needToInferCaller) {
259         inferCaller();
260     }
261     return sourceClassName;
262     }
263 
264     /**
265      * Set the name of the class that (allegedly) issued the logging request.
266      *
267      * @param sourceClassName the source class name (may be null)
268      */  
269     public void setSourceClassName(String sourceClassName) {
270     this.sourceClassName = sourceClassName;
271     needToInferCaller = false;
272     }
273 
274     /**
275      * Get the  name of the method that (allegedly) issued the logging request.
276      * <p>
277      * Note that this sourceMethodName is not verified and may be spoofed.
278      * This information may either have been provided as part of the
279      * logging call, or it may have been inferred automatically by the
280      * logging framework.  In the latter case, the information may only
281      * be approximate and may in fact describe an earlier call on the
282      * stack frame.
283      * <p>
284      * May be null if no information could be obtained.
285      *
286      * @return the source method name
287      */  
288     public String getSourceMethodName() {
289     if (needToInferCaller) {
290         inferCaller();
291     }
292     return sourceMethodName;
293     }
294 
295     /**
296      * Set the name of the method that (allegedly) issued the logging request.
297      *
298      * @param sourceMethodName the source method name (may be null)
299      */  
300     public void setSourceMethodName(String sourceMethodName) {
301     this.sourceMethodName = sourceMethodName;
302     needToInferCaller = false;
303     }
304 
305     /**
306      * Get the "raw" log message, before localization or formatting.
307      * <p>
308      * May be null, which is equivalent to the empty string "".
309      * <p>
310      * This message may be either the final text or a localization key.
311      * <p>
312      * During formatting, if the source logger has a localization
313      * ResourceBundle and if that ResourceBundle has an entry for
314      * this message string, then the message string is replaced
315      * with the localized value.
316      *
317      * @return the raw message string
318      */
319     public String getMessage() {
320     return message;
321     }
322 
323     /**
324      * Set the "raw" log message, before localization or formatting.
325      *
326      * @param message the raw message string (may be null)
327      */
328     public void setMessage(String message) {
329     this.message = message;
330     }
331 
332     /**
333      * Get the parameters to the log message.
334      *
335      * @return the log message parameters.  May be null if
336      *          there are no parameters.
337      */
338     public Object[] getParameters() {
339     return parameters;
340     }
341 
342     /**
343      * Set the parameters to the log message.
344      *
345      * @param parameters the log message parameters. (may be null)
346      */
347     public void setParameters(Object parameters[]) {
348     this.parameters = parameters;
349     }
350 
351     /**
352      * Get an identifier for the thread where the message originated.
353      * <p>
354      * This is a thread identifier within the Java VM and may or
355      * may not map to any operating system ID.
356      *
357      * @return thread ID
358      */
359     public int getThreadID() {
360     return threadID;
361     }
362 
363     /**
364      * Set an identifier for the thread where the message originated.
365      * @param threadID  the thread ID
366      */
367     public void setThreadID(int threadID) {
368     this.threadID = threadID;
369     }
370 
371     /**
372      * Get event time in milliseconds since 1970.
373      *
374      * @return event time in millis since 1970
375      */
376     public long getMillis() {
377     return millis;
378     }
379 
380     /**
381      * Set event time.
382      *
383      * @param millis event time in millis since 1970
384      */
385     public void setMillis(long millis) {
386     this.millis = millis;
387     }
388 
389     /**
390      * Get any throwable associated with the log record.
391      * <p>
392      * If the event involved an exception, this will be the
393      * exception object. Otherwise null.
394      *
395      * @return a throwable
396      */
397     public Throwable getThrown() {
398     return thrown;
399     }
400 
401     /**
402      * Set a throwable associated with the log event.
403      *
404      * @param thrown  a throwable (may be null)
405      */
406     public void setThrown(Throwable thrown) {
407     this.thrown = thrown;
408     }
409 
410     private static final long serialVersionUID = 5372048053134512534L;
411 
412     /**
413      * @serialData Default fields, followed by a two byte version number
414      * (major byte, followed by minor byte), followed by information on
415      * the log record parameter array.  If there is no parameter array, 
416      * then -1 is written.  If there is a parameter array (possible of zero
417      * length) then the array length is written as an integer, followed
418      * by String values for each parameter.  If a parameter is null, then
419      * a null String is written.  Otherwise the output of Object.toString()
420      * is written.
421      */
422     private void writeObject(ObjectOutputStream out) throws IOException {
423     // We have to call defaultWriteObject first.
424     out.defaultWriteObject();
425 
426     // Write our version number.
427     out.writeByte(1);
428     out.writeByte(0);
429     if (parameters == null) {
430         out.writeInt(-1);
431         return;
432     }
433     out.writeInt(parameters.length);
434     // Write string values for the parameters.
435     for (int i = 0; i < parameters.length; i++) {
436         if (parameters[i] == null) {
437             out.writeObject(null);
438         } else {
439             out.writeObject(parameters[i].toString());
440         }
441     }
442     }
443 
444     private void readObject(ObjectInputStream in) 
445             throws IOException, ClassNotFoundException {
446     // We have to call defaultReadObject first.
447     in.defaultReadObject();
448 
449     // Read version number.
450     byte major = in.readByte();
451     byte minor = in.readByte();
452     if (major != 1) {
453         throw new IOException("LogRecord: bad version: " + major + "." + minor);
454     }
455     int len = in.readInt();
456     if (len == -1) {
457         parameters = null;
458     } else {
459         parameters = new Object[len];
460         for (int i = 0; i < parameters.length; i++) {
461             parameters[i] = in.readObject();
462         }
463     }
464     // If necessary, try to regenerate the resource bundle.
465     if (resourceBundleName != null) {
466         try {
467             resourceBundle = ResourceBundle.getBundle(resourceBundleName);
468         } catch (MissingResourceException ex) {
469         // This is not a good place to throw an exception,
470         // so we simply leave the resourceBundle null.
471         resourceBundle = null;
472         }
473     }
474 
475     needToInferCaller = false;
476     }
477 
478     // Private method to infer the caller's class and method names
479     private void inferCaller() {
480         needToInferCaller = false;
481         JavaLangAccess access = SharedSecrets.getJavaLangAccess();
482         Throwable throwable = new Throwable();
483         int depth = access.getStackTraceDepth(throwable);
484 
485         String logClassName = "java.util.logging.Logger";
486         boolean lookingForLogger = true;
487         for (int ix = 0; ix < depth; ix++) {
488             // Calling getStackTraceElement directly prevents the VM
489             // from paying the cost of building the entire stack frame.
490             StackTraceElement frame =
491                 access.getStackTraceElement(throwable, ix);
492             String cname = frame.getClassName();
493             if (lookingForLogger) {
494                 // Skip all frames until we have found the first logger frame.
495                 if (cname.equals(logClassName)) {
496                     lookingForLogger = false;
497                 }
498             } else {
499                 if (!cname.equals(logClassName)) {
500                     // We've found the relevant frame.
501                     setSourceClassName(cname);
502                     setSourceMethodName(frame.getMethodName());
503                     return;
504                 }
505             }
506         }
507         // We haven't found a suitable frame, so just punt.  This is
508         // OK as we are only committed to making a "best effort" here.
509     }
510 }
511