/* | 
|
 * 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 java.util.*;  | 
|
import java.util.concurrent.atomic.AtomicInteger;  | 
|
import java.util.concurrent.atomic.AtomicLong;  | 
|
import java.io.*;  | 
|
import sun.misc.JavaLangAccess;  | 
|
import sun.misc.SharedSecrets;  | 
|
/**  | 
|
* LogRecord objects are used to pass logging requests between  | 
|
* the logging framework and individual log Handlers.  | 
|
* <p>  | 
|
* 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.  | 
|
* <p>  | 
|
* 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.  | 
|
* <p>  | 
|
* <b> Serialization notes:</b>  | 
|
* <ul>  | 
|
* <li>The LogRecord class is serializable.  | 
|
*  | 
|
* <li> Because objects in the parameters array may not be serializable,  | 
|
* during serialization all objects in the parameters array are  | 
|
* written as the corresponding Strings (using Object.toString).  | 
|
*  | 
|
* <li> The ResourceBundle is not transmitted as part of the serialized  | 
|
* form, but the resource bundle name is, and the recipient object's  | 
|
* readObject method will attempt to locate a suitable resource bundle.  | 
|
*  | 
|
* </ul>  | 
|
*  | 
|
* @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<Integer> 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. | 
|
     * <p> | 
|
     * The sequence property will be initialized with a new unique value. | 
|
     * These sequence values are allocated in increasing order within a VM. | 
|
     * <p> | 
|
     * The millis property will be initialized to the current time. | 
|
     * <p> | 
|
     * The thread ID property will be initialized with a unique ID for | 
|
     * the current thread. | 
|
     * <p> | 
|
     * 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 | 
|
     * <p> | 
|
     * 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 | 
|
     * <p> | 
|
     * 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. | 
|
     * <p> | 
|
     * 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. | 
|
     * <p> | 
|
     * 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. | 
|
     * <p> | 
|
     * 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. | 
|
     * <p> | 
|
     * 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. | 
|
     * <p> | 
|
     * 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. | 
|
     * <p> | 
|
     * 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. | 
|
     * <p> | 
|
     * May be null, which is equivalent to the empty string "". | 
|
     * <p> | 
|
     * This message may be either the final text or a localization key. | 
|
     * <p> | 
|
     * 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. | 
|
     * <p> | 
|
     * 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. | 
|
     * <p> | 
|
     * 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<Object> 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) {  | 
|
// 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;  | 
|
JavaLangAccess access = SharedSecrets.getJavaLangAccess();  | 
|
Throwable throwable = new Throwable();  | 
|
int depth = access.getStackTraceDepth(throwable);  | 
|
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. | 
|
StackTraceElement frame =  | 
|
access.getStackTraceElement(throwable, 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."));  | 
|
}  | 
|
}  |