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