/*
* Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
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."));
}
}