LogRecord.java revision 07c4a0eab5348a975ea278d17afdd608270d0f53
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 * Copyright (c) 2000, 2010, 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     */
214    public ResourceBundle getResourceBundle() {
215        return resourceBundle;
216    }
217
218    /**
219     * Set the localization resource bundle.
220     *
221     * @param bundle  localization bundle (may be null)
222     */
223    public void setResourceBundle(ResourceBundle bundle) {
224        resourceBundle = bundle;
225    }
226
227    /**
228     * Get the localization resource bundle name
229     * <p>
230     * This is the name for the ResourceBundle that should be
231     * used to localize the message string before formatting it.
232     * The result may be null if the message is not localizable.
233     */
234    public String getResourceBundleName() {
235        return resourceBundleName;
236    }
237
238    /**
239     * Set the localization resource bundle name.
240     *
241     * @param name  localization bundle name (may be null)
242     */
243    public void setResourceBundleName(String name) {
244        resourceBundleName = name;
245    }
246
247    /**
248     * Get the logging message level, for example Level.SEVERE.
249     * @return the logging message level
250     */
251    public Level getLevel() {
252        return level;
253    }
254
255    /**
256     * Set the logging message level, for example Level.SEVERE.
257     * @param level the logging message level
258     */
259    public void setLevel(Level level) {
260        if (level == null) {
261            throw new NullPointerException();
262        }
263        this.level = level;
264    }
265
266    /**
267     * Get the sequence number.
268     * <p>
269     * Sequence numbers are normally assigned in the LogRecord
270     * constructor, which assigns unique sequence numbers to
271     * each new LogRecord in increasing order.
272     * @return the sequence number
273     */
274    public long getSequenceNumber() {
275        return sequenceNumber;
276    }
277
278    /**
279     * Set the sequence number.
280     * <p>
281     * Sequence numbers are normally assigned in the LogRecord constructor,
282     * so it should not normally be necessary to use this method.
283     */
284    public void setSequenceNumber(long seq) {
285        sequenceNumber = seq;
286    }
287
288    /**
289     * Get the  name of the class that (allegedly) issued the logging request.
290     * <p>
291     * Note that this sourceClassName is not verified and may be spoofed.
292     * This information may either have been provided as part of the
293     * logging call, or it may have been inferred automatically by the
294     * logging framework.  In the latter case, the information may only
295     * be approximate and may in fact describe an earlier call on the
296     * stack frame.
297     * <p>
298     * May be null if no information could be obtained.
299     *
300     * @return the source class name
301     */
302    public String getSourceClassName() {
303        if (needToInferCaller) {
304            inferCaller();
305        }
306        return sourceClassName;
307    }
308
309    /**
310     * Set the name of the class that (allegedly) issued the logging request.
311     *
312     * @param sourceClassName the source class name (may be null)
313     */
314    public void setSourceClassName(String sourceClassName) {
315        this.sourceClassName = sourceClassName;
316        needToInferCaller = false;
317    }
318
319    /**
320     * Get the  name of the method that (allegedly) issued the logging request.
321     * <p>
322     * Note that this sourceMethodName is not verified and may be spoofed.
323     * This information may either have been provided as part of the
324     * logging call, or it may have been inferred automatically by the
325     * logging framework.  In the latter case, the information may only
326     * be approximate and may in fact describe an earlier call on the
327     * stack frame.
328     * <p>
329     * May be null if no information could be obtained.
330     *
331     * @return the source method name
332     */
333    public String getSourceMethodName() {
334        if (needToInferCaller) {
335            inferCaller();
336        }
337        return sourceMethodName;
338    }
339
340    /**
341     * Set the name of the method that (allegedly) issued the logging request.
342     *
343     * @param sourceMethodName the source method name (may be null)
344     */
345    public void setSourceMethodName(String sourceMethodName) {
346        this.sourceMethodName = sourceMethodName;
347        needToInferCaller = false;
348    }
349
350    /**
351     * Get the "raw" log message, before localization or formatting.
352     * <p>
353     * May be null, which is equivalent to the empty string "".
354     * <p>
355     * This message may be either the final text or a localization key.
356     * <p>
357     * During formatting, if the source logger has a localization
358     * ResourceBundle and if that ResourceBundle has an entry for
359     * this message string, then the message string is replaced
360     * with the localized value.
361     *
362     * @return the raw message string
363     */
364    public String getMessage() {
365        return message;
366    }
367
368    /**
369     * Set the "raw" log message, before localization or formatting.
370     *
371     * @param message the raw message string (may be null)
372     */
373    public void setMessage(String message) {
374        this.message = message;
375    }
376
377    /**
378     * Get the parameters to the log message.
379     *
380     * @return the log message parameters.  May be null if
381     *                  there are no parameters.
382     */
383    public Object[] getParameters() {
384        return parameters;
385    }
386
387    /**
388     * Set the parameters to the log message.
389     *
390     * @param parameters the log message parameters. (may be null)
391     */
392    public void setParameters(Object parameters[]) {
393        this.parameters = parameters;
394    }
395
396    /**
397     * Get an identifier for the thread where the message originated.
398     * <p>
399     * This is a thread identifier within the Java VM and may or
400     * may not map to any operating system ID.
401     *
402     * @return thread ID
403     */
404    public int getThreadID() {
405        return threadID;
406    }
407
408    /**
409     * Set an identifier for the thread where the message originated.
410     * @param threadID  the thread ID
411     */
412    public void setThreadID(int threadID) {
413        this.threadID = threadID;
414    }
415
416    /**
417     * Get event time in milliseconds since 1970.
418     *
419     * @return event time in millis since 1970
420     */
421    public long getMillis() {
422        return millis;
423    }
424
425    /**
426     * Set event time.
427     *
428     * @param millis event time in millis since 1970
429     */
430    public void setMillis(long millis) {
431        this.millis = millis;
432    }
433
434    /**
435     * Get any throwable associated with the log record.
436     * <p>
437     * If the event involved an exception, this will be the
438     * exception object. Otherwise null.
439     *
440     * @return a throwable
441     */
442    public Throwable getThrown() {
443        return thrown;
444    }
445
446    /**
447     * Set a throwable associated with the log event.
448     *
449     * @param thrown  a throwable (may be null)
450     */
451    public void setThrown(Throwable thrown) {
452        this.thrown = thrown;
453    }
454
455    private static final long serialVersionUID = 5372048053134512534L;
456
457    /**
458     * @serialData Default fields, followed by a two byte version number
459     * (major byte, followed by minor byte), followed by information on
460     * the log record parameter array.  If there is no parameter array,
461     * then -1 is written.  If there is a parameter array (possible of zero
462     * length) then the array length is written as an integer, followed
463     * by String values for each parameter.  If a parameter is null, then
464     * a null String is written.  Otherwise the output of Object.toString()
465     * is written.
466     */
467    private void writeObject(ObjectOutputStream out) throws IOException {
468        // We have to call defaultWriteObject first.
469        out.defaultWriteObject();
470
471        // Write our version number.
472        out.writeByte(1);
473        out.writeByte(0);
474        if (parameters == null) {
475            out.writeInt(-1);
476            return;
477        }
478        out.writeInt(parameters.length);
479        // Write string values for the parameters.
480        for (int i = 0; i < parameters.length; i++) {
481            if (parameters[i] == null) {
482                out.writeObject(null);
483            } else {
484                out.writeObject(parameters[i].toString());
485            }
486        }
487    }
488
489    private void readObject(ObjectInputStream in)
490                        throws IOException, ClassNotFoundException {
491        // We have to call defaultReadObject first.
492        in.defaultReadObject();
493
494        // Read version number.
495        byte major = in.readByte();
496        byte minor = in.readByte();
497        if (major != 1) {
498            throw new IOException("LogRecord: bad version: " + major + "." + minor);
499        }
500        int len = in.readInt();
501        if (len == -1) {
502            parameters = null;
503        } else {
504            parameters = new Object[len];
505            for (int i = 0; i < parameters.length; i++) {
506                parameters[i] = in.readObject();
507            }
508        }
509        // If necessary, try to regenerate the resource bundle.
510        if (resourceBundleName != null) {
511            try {
512                resourceBundle = ResourceBundle.getBundle(resourceBundleName);
513            } catch (MissingResourceException ex) {
514                try {
515                    resourceBundle = ResourceBundle.getBundle(resourceBundleName, Locale.getDefault(),
516                            Thread.currentThread().getContextClassLoader());
517                } catch (MissingResourceException innerE){
518                    // This is not a good place to throw an exception,
519                    // so we simply leave the resourceBundle null.
520                    resourceBundle = null;
521                }
522            }
523        }
524
525        needToInferCaller = false;
526    }
527
528    // Private method to infer the caller's class and method names
529    private void inferCaller() {
530        needToInferCaller = false;
531        // Android-changed: Use VMStack.getThreadStackTrace.
532        StackTraceElement[] stack = VMStack.getThreadStackTrace(Thread.currentThread());
533        int depth = stack.length;
534
535        boolean lookingForLogger = true;
536        for (int ix = 0; ix < depth; ix++) {
537            // Calling getStackTraceElement directly prevents the VM
538            // from paying the cost of building the entire stack frame.
539            //
540            // Android-changed: Use value from getThreadStackTrace.
541            StackTraceElement frame = stack[ix];
542            String cname = frame.getClassName();
543            boolean isLoggerImpl = isLoggerImplFrame(cname);
544            if (lookingForLogger) {
545                // Skip all frames until we have found the first logger frame.
546                if (isLoggerImpl) {
547                    lookingForLogger = false;
548                }
549            } else {
550                if (!isLoggerImpl) {
551                    // skip reflection call
552                    if (!cname.startsWith("java.lang.reflect.") && !cname.startsWith("sun.reflect.")) {
553                       // We've found the relevant frame.
554                       setSourceClassName(cname);
555                       setSourceMethodName(frame.getMethodName());
556                       return;
557                    }
558                }
559            }
560        }
561        // We haven't found a suitable frame, so just punt.  This is
562        // OK as we are only committed to making a "best effort" here.
563    }
564
565    private boolean isLoggerImplFrame(String cname) {
566        // the log record could be created for a platform logger
567        return (cname.equals("java.util.logging.Logger") ||
568                cname.startsWith("java.util.logging.LoggingProxyImpl") ||
569                cname.startsWith("sun.util.logging."));
570    }
571}
572