/* * Copyright (C) 2014 The Android Open Source Project * Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.util.logging; import dalvik.system.VMStack; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.io.*; /** * LogRecord objects are used to pass logging requests between * the logging framework and individual log Handlers. *

* When a LogRecord is passed into the logging framework it * logically belongs to the framework and should no longer be * used or updated by the client application. *

* Note that if the client application has not specified an * explicit source method name and source class name, then the * LogRecord class will infer them automatically when they are * first accessed (due to a call on getSourceMethodName or * getSourceClassName) by analyzing the call stack. Therefore, * if a logging Handler wants to pass off a LogRecord to another * thread, or to transmit it over RMI, and if it wishes to subsequently * obtain method name or class name information it should call * one of getSourceClassName or getSourceMethodName to force * the values to be filled in. *

* Serialization notes: *

* * @since 1.4 */ public class LogRecord implements java.io.Serializable { private static final AtomicLong globalSequenceNumber = new AtomicLong(0); /** * The default value of threadID will be the current thread's * thread id, for ease of correlation, unless it is greater than * MIN_SEQUENTIAL_THREAD_ID, in which case we try harder to keep * our promise to keep threadIDs unique by avoiding collisions due * to 32-bit wraparound. Unfortunately, LogRecord.getThreadID() * returns int, while Thread.getId() returns long. */ private static final int MIN_SEQUENTIAL_THREAD_ID = Integer.MAX_VALUE / 2; private static final AtomicInteger nextThreadId = new AtomicInteger(MIN_SEQUENTIAL_THREAD_ID); private static final ThreadLocal threadIds = new ThreadLocal<>(); /** * @serial Logging message level */ private Level level; /** * @serial Sequence number */ private long sequenceNumber; /** * @serial Class that issued logging call */ private String sourceClassName; /** * @serial Method that issued logging call */ private String sourceMethodName; /** * @serial Non-localized raw message text */ private String message; /** * @serial Thread ID for thread that issued logging call. */ private int threadID; /** * @serial Event time in milliseconds since 1970 */ private long millis; /** * @serial The Throwable (if any) associated with log message */ private Throwable thrown; /** * @serial Name of the source Logger. */ private String loggerName; /** * @serial Resource bundle name to localized log message. */ private String resourceBundleName; private transient boolean needToInferCaller; private transient Object parameters[]; private transient ResourceBundle resourceBundle; /** * Returns the default value for a new LogRecord's threadID. */ private int defaultThreadID() { long tid = Thread.currentThread().getId(); if (tid < MIN_SEQUENTIAL_THREAD_ID) { return (int) tid; } else { Integer id = threadIds.get(); if (id == null) { id = nextThreadId.getAndIncrement(); threadIds.set(id); } return id; } } /** * Construct a LogRecord with the given level and message values. *

* The sequence property will be initialized with a new unique value. * These sequence values are allocated in increasing order within a VM. *

* The millis property will be initialized to the current time. *

* The thread ID property will be initialized with a unique ID for * the current thread. *

* All other properties will be initialized to "null". * * @param level a logging level value * @param msg the raw non-localized logging message (may be null) */ public LogRecord(Level level, String msg) { // Make sure level isn't null, by calling random method. level.getClass(); this.level = level; message = msg; // Assign a thread ID and a unique sequence number. sequenceNumber = globalSequenceNumber.getAndIncrement(); threadID = defaultThreadID(); millis = System.currentTimeMillis(); needToInferCaller = true; } /** * Get the source Logger's name. * * @return source logger name (may be null) */ public String getLoggerName() { return loggerName; } /** * Set the source Logger's name. * * @param name the source logger name (may be null) */ public void setLoggerName(String name) { loggerName = name; } /** * Get the localization resource bundle *

* This is the ResourceBundle that should be used to localize * the message string before formatting it. The result may * be null if the message is not localizable, or if no suitable * ResourceBundle is available. * @return the localization resource bundle */ public ResourceBundle getResourceBundle() { return resourceBundle; } /** * Set the localization resource bundle. * * @param bundle localization bundle (may be null) */ public void setResourceBundle(ResourceBundle bundle) { resourceBundle = bundle; } /** * Get the localization resource bundle name *

* This is the name for the ResourceBundle that should be * used to localize the message string before formatting it. * The result may be null if the message is not localizable. * @return the localization resource bundle name */ public String getResourceBundleName() { return resourceBundleName; } /** * Set the localization resource bundle name. * * @param name localization bundle name (may be null) */ public void setResourceBundleName(String name) { resourceBundleName = name; } /** * Get the logging message level, for example Level.SEVERE. * @return the logging message level */ public Level getLevel() { return level; } /** * Set the logging message level, for example Level.SEVERE. * @param level the logging message level */ public void setLevel(Level level) { if (level == null) { throw new NullPointerException(); } this.level = level; } /** * Get the sequence number. *

* Sequence numbers are normally assigned in the LogRecord * constructor, which assigns unique sequence numbers to * each new LogRecord in increasing order. * @return the sequence number */ public long getSequenceNumber() { return sequenceNumber; } /** * Set the sequence number. *

* Sequence numbers are normally assigned in the LogRecord constructor, * so it should not normally be necessary to use this method. * @param seq the sequence number */ public void setSequenceNumber(long seq) { sequenceNumber = seq; } /** * Get the name of the class that (allegedly) issued the logging request. *

* Note that this sourceClassName is not verified and may be spoofed. * This information may either have been provided as part of the * logging call, or it may have been inferred automatically by the * logging framework. In the latter case, the information may only * be approximate and may in fact describe an earlier call on the * stack frame. *

* May be null if no information could be obtained. * * @return the source class name */ public String getSourceClassName() { if (needToInferCaller) { inferCaller(); } return sourceClassName; } /** * Set the name of the class that (allegedly) issued the logging request. * * @param sourceClassName the source class name (may be null) */ public void setSourceClassName(String sourceClassName) { this.sourceClassName = sourceClassName; needToInferCaller = false; } /** * Get the name of the method that (allegedly) issued the logging request. *

* Note that this sourceMethodName is not verified and may be spoofed. * This information may either have been provided as part of the * logging call, or it may have been inferred automatically by the * logging framework. In the latter case, the information may only * be approximate and may in fact describe an earlier call on the * stack frame. *

* May be null if no information could be obtained. * * @return the source method name */ public String getSourceMethodName() { if (needToInferCaller) { inferCaller(); } return sourceMethodName; } /** * Set the name of the method that (allegedly) issued the logging request. * * @param sourceMethodName the source method name (may be null) */ public void setSourceMethodName(String sourceMethodName) { this.sourceMethodName = sourceMethodName; needToInferCaller = false; } /** * Get the "raw" log message, before localization or formatting. *

* May be null, which is equivalent to the empty string "". *

* This message may be either the final text or a localization key. *

* During formatting, if the source logger has a localization * ResourceBundle and if that ResourceBundle has an entry for * this message string, then the message string is replaced * with the localized value. * * @return the raw message string */ public String getMessage() { return message; } /** * Set the "raw" log message, before localization or formatting. * * @param message the raw message string (may be null) */ public void setMessage(String message) { this.message = message; } /** * Get the parameters to the log message. * * @return the log message parameters. May be null if * there are no parameters. */ public Object[] getParameters() { return parameters; } /** * Set the parameters to the log message. * * @param parameters the log message parameters. (may be null) */ public void setParameters(Object parameters[]) { this.parameters = parameters; } /** * Get an identifier for the thread where the message originated. *

* This is a thread identifier within the Java VM and may or * may not map to any operating system ID. * * @return thread ID */ public int getThreadID() { return threadID; } /** * Set an identifier for the thread where the message originated. * @param threadID the thread ID */ public void setThreadID(int threadID) { this.threadID = threadID; } /** * Get event time in milliseconds since 1970. * * @return event time in millis since 1970 */ public long getMillis() { return millis; } /** * Set event time. * * @param millis event time in millis since 1970 */ public void setMillis(long millis) { this.millis = millis; } /** * Get any throwable associated with the log record. *

* If the event involved an exception, this will be the * exception object. Otherwise null. * * @return a throwable */ public Throwable getThrown() { return thrown; } /** * Set a throwable associated with the log event. * * @param thrown a throwable (may be null) */ public void setThrown(Throwable thrown) { this.thrown = thrown; } private static final long serialVersionUID = 5372048053134512534L; /** * @serialData Default fields, followed by a two byte version number * (major byte, followed by minor byte), followed by information on * the log record parameter array. If there is no parameter array, * then -1 is written. If there is a parameter array (possible of zero * length) then the array length is written as an integer, followed * by String values for each parameter. If a parameter is null, then * a null String is written. Otherwise the output of Object.toString() * is written. */ private void writeObject(ObjectOutputStream out) throws IOException { // We have to call defaultWriteObject first. out.defaultWriteObject(); // Write our version number. out.writeByte(1); out.writeByte(0); if (parameters == null) { out.writeInt(-1); return; } out.writeInt(parameters.length); // Write string values for the parameters. for (int i = 0; i < parameters.length; i++) { if (parameters[i] == null) { out.writeObject(null); } else { out.writeObject(parameters[i].toString()); } } } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { // We have to call defaultReadObject first. in.defaultReadObject(); // Read version number. byte major = in.readByte(); byte minor = in.readByte(); if (major != 1) { throw new IOException("LogRecord: bad version: " + major + "." + minor); } int len = in.readInt(); if (len < -1) { throw new NegativeArraySizeException(); } else if (len == -1) { parameters = null; } else if (len < 255) { parameters = new Object[len]; for (int i = 0; i < parameters.length; i++) { parameters[i] = in.readObject(); } } else { List params = new ArrayList<>(Math.min(len, 1024)); for (int i = 0; i < len; i++) { params.add(in.readObject()); } parameters = params.toArray(new Object[params.size()]); } // If necessary, try to regenerate the resource bundle. if (resourceBundleName != null) { try { // use system class loader to ensure the ResourceBundle // instance is a different instance than null loader uses final ResourceBundle bundle = ResourceBundle.getBundle(resourceBundleName, Locale.getDefault(), ClassLoader.getSystemClassLoader()); resourceBundle = bundle; } catch (MissingResourceException ex) { try { resourceBundle = ResourceBundle.getBundle(resourceBundleName, Locale.getDefault(), Thread.currentThread().getContextClassLoader()); } catch (MissingResourceException innerE){ // This is not a good place to throw an exception, // so we simply leave the resourceBundle null. resourceBundle = null; } } } needToInferCaller = false; } // Private method to infer the caller's class and method names private void inferCaller() { needToInferCaller = false; // Android-changed: Use VMStack.getThreadStackTrace. StackTraceElement[] stack = VMStack.getThreadStackTrace(Thread.currentThread()); int depth = stack.length; boolean lookingForLogger = true; for (int ix = 0; ix < depth; ix++) { // Calling getStackTraceElement directly prevents the VM // from paying the cost of building the entire stack frame. // // Android-changed: Use value from getThreadStackTrace. StackTraceElement frame = stack[ix]; String cname = frame.getClassName(); boolean isLoggerImpl = isLoggerImplFrame(cname); if (lookingForLogger) { // Skip all frames until we have found the first logger frame. if (isLoggerImpl) { lookingForLogger = false; } } else { if (!isLoggerImpl) { // skip reflection call if (!cname.startsWith("java.lang.reflect.") && !cname.startsWith("sun.reflect.")) { // We've found the relevant frame. setSourceClassName(cname); setSourceMethodName(frame.getMethodName()); return; } } } } // We haven't found a suitable frame, so just punt. This is // OK as we are only committed to making a "best effort" here. } private boolean isLoggerImplFrame(String cname) { // the log record could be created for a platform logger return (cname.equals("java.util.logging.Logger") || cname.startsWith("java.util.logging.LoggingProxyImpl") || cname.startsWith("sun.util.logging.")); } }