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