/* |
|
* 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.")); |
|
} |
|
} |