/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package java.util.logging;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
/**
* A {@code LogRecord} object represents a logging request. It is passed between
* the logging framework and individual logging handlers. Client applications
* should not modify a {@code LogRecord} object that has been passed into the
* logging framework.
*
* The {@code LogRecord} class will infer the source method name and source
* class name the first time they are accessed if the client application didn't
* specify them explicitly. This automatic inference is based on the analysis of
* the call stack and is not guaranteed to be precise. Client applications
* should force the initialization of these two fields by calling
* {@code getSourceClassName} or {@code getSourceMethodName} if they expect to
* use them after passing the {@code LogRecord} object to another thread or
* transmitting it over RMI.
*/
public class LogRecord implements Serializable {
private static final long serialVersionUID = 5372048053134512534L;
// The major byte used in serialization.
private static final int MAJOR = 1;
// The minor byte used in serialization.
private static final int MINOR = 4;
// Store the current value for the sequence number.
private static long currentSequenceNumber = 0;
// Store the id for each thread.
private static ThreadLocal currentThreadId = new ThreadLocal();
// The base id as the starting point for thread ID allocation.
private static int initThreadId = 0;
/**
* The logging level.
*
* @serial
*/
private Level level;
/**
* The sequence number.
*
* @serial
*/
private long sequenceNumber;
/**
* The name of the class that issued the logging call.
*
* @serial
*/
private String sourceClassName;
/**
* The name of the method that issued the logging call.
*
* @serial
*/
private String sourceMethodName;
/**
* The original message text.
*
* @serial
*/
private String message;
/**
* The ID of the thread that issued the logging call.
*
* @serial
*/
private int threadID;
/**
* The time that the event occurred, in milliseconds since 1970.
*
* @serial
*/
private long millis;
/**
* The associated {@code Throwable} object if any.
*
* @serial
*/
private Throwable thrown;
/**
* The name of the source logger.
*
* @serial
*/
private String loggerName;
/**
* The name of the resource bundle used to localize the log message.
*
* @serial
*/
private String resourceBundleName;
// The associated resource bundle if any.
private transient ResourceBundle resourceBundle;
// The parameters.
private transient Object[] parameters;
// If the source method and source class has been initialized
private transient boolean sourceInitialized;
/**
* Constructs a {@code LogRecord} object using the supplied the logging
* level and message. The millis property is set to the current time. The
* sequence property is set to a new unique value, allocated in increasing
* order within the VM. The thread ID is set to a unique value
* for the current thread. All other properties are set to {@code null}.
*
* @param level
* the logging level, may not be {@code null}.
* @param msg
* the raw message.
* @throws NullPointerException
* if {@code level} is {@code null}.
*/
public LogRecord(Level level, String msg) {
if (level == null) {
throw new NullPointerException("level == null");
}
this.level = level;
this.message = msg;
this.millis = System.currentTimeMillis();
synchronized (LogRecord.class) {
this.sequenceNumber = currentSequenceNumber++;
Integer id = currentThreadId.get();
if (id == null) {
this.threadID = initThreadId;
currentThreadId.set(Integer.valueOf(initThreadId++));
} else {
this.threadID = id.intValue();
}
}
this.sourceClassName = null;
this.sourceMethodName = null;
this.loggerName = null;
this.parameters = null;
this.resourceBundle = null;
this.resourceBundleName = null;
this.thrown = null;
}
/**
* Gets the logging level.
*
* @return the logging level.
*/
public Level getLevel() {
return level;
}
/**
* Sets the logging level.
*
* @param level
* the level to set.
* @throws NullPointerException
* if {@code level} is {@code null}.
*/
public void setLevel(Level level) {
if (level == null) {
throw new NullPointerException("level == null");
}
this.level = level;
}
/**
* Gets the name of the logger.
*
* @return the logger name.
*/
public String getLoggerName() {
return loggerName;
}
/**
* Sets the name of the logger.
*
* @param loggerName
* the logger name to set.
*/
public void setLoggerName(String loggerName) {
this.loggerName = loggerName;
}
/**
* Gets the raw message.
*
* @return the raw message, may be {@code null}.
*/
public String getMessage() {
return message;
}
/**
* Sets the raw message. When this record is formatted by a logger that has
* a localization resource bundle that contains an entry for {@code message},
* then the raw message is replaced with its localized version.
*
* @param message
* the raw message to set, may be {@code null}.
*/
public void setMessage(String message) {
this.message = message;
}
/**
* Gets the time when this event occurred, in milliseconds since 1970.
*
* @return the time when this event occurred, in milliseconds since 1970.
*/
public long getMillis() {
return millis;
}
/**
* Sets the time when this event occurred, in milliseconds since 1970.
*
* @param millis
* the time when this event occurred, in milliseconds since 1970.
*/
public void setMillis(long millis) {
this.millis = millis;
}
/**
* Gets the parameters.
*
* @return the array of parameters or {@code null} if there are no
* parameters.
*/
public Object[] getParameters() {
return parameters;
}
/**
* Sets the parameters.
*
* @param parameters
* the array of parameters to set, may be {@code null}.
*/
public void setParameters(Object[] parameters) {
this.parameters = parameters;
}
/**
* Gets the resource bundle used to localize the raw message during
* formatting.
*
* @return the associated resource bundle, {@code null} if none is
* available or the message is not localizable.
*/
public ResourceBundle getResourceBundle() {
return resourceBundle;
}
/**
* Sets the resource bundle used to localize the raw message during
* formatting.
*
* @param resourceBundle
* the resource bundle to set, may be {@code null}.
*/
public void setResourceBundle(ResourceBundle resourceBundle) {
this.resourceBundle = resourceBundle;
}
/**
* Gets the name of the resource bundle.
*
* @return the name of the resource bundle, {@code null} if none is
* available or the message is not localizable.
*/
public String getResourceBundleName() {
return resourceBundleName;
}
/**
* Sets the name of the resource bundle.
*
* @param resourceBundleName
* the name of the resource bundle to set.
*/
public void setResourceBundleName(String resourceBundleName) {
this.resourceBundleName = resourceBundleName;
}
/**
* Gets the sequence number.
*
* @return the sequence number.
*/
public long getSequenceNumber() {
return sequenceNumber;
}
/**
* Sets the sequence number. It is usually not necessary to call this method
* to change the sequence number because the number is allocated when this
* instance is constructed.
*
* @param sequenceNumber
* the sequence number to set.
*/
public void setSequenceNumber(long sequenceNumber) {
this.sequenceNumber = sequenceNumber;
}
/**
* Gets the name of the class that is the source of this log record. This
* information can be changed, may be {@code null} and is untrusted.
*
* @return the name of the source class of this log record (possiblity {@code null})
*/
public String getSourceClassName() {
initSource();
return sourceClassName;
}
/*
* Init the sourceClass and sourceMethod fields.
*/
private void initSource() {
if (sourceInitialized) {
return;
}
boolean sawLogger = false;
for (StackTraceElement element : new Throwable().getStackTrace()) {
String current = element.getClassName();
if (current.startsWith(Logger.class.getName())) {
sawLogger = true;
} else if (sawLogger) {
this.sourceClassName = element.getClassName();
this.sourceMethodName = element.getMethodName();
break;
}
}
sourceInitialized = true;
}
/**
* Sets the name of the class that is the source of this log record.
*
* @param sourceClassName
* the name of the source class of this log record, may be
* {@code null}.
*/
public void setSourceClassName(String sourceClassName) {
sourceInitialized = true;
this.sourceClassName = sourceClassName;
}
/**
* Gets the name of the method that is the source of this log record.
*
* @return the name of the source method of this log record.
*/
public String getSourceMethodName() {
initSource();
return sourceMethodName;
}
/**
* Sets the name of the method that is the source of this log record.
*
* @param sourceMethodName
* the name of the source method of this log record, may be
* {@code null}.
*/
public void setSourceMethodName(String sourceMethodName) {
sourceInitialized = true;
this.sourceMethodName = sourceMethodName;
}
/**
* Gets a unique ID of the thread originating the log record. Every thread
* becomes a different ID.
*
* Notice : the ID doesn't necessary map the OS thread ID
*
*
* @return the ID of the thread originating this log record.
*/
public int getThreadID() {
return threadID;
}
/**
* Sets the ID of the thread originating this log record.
*
* @param threadID
* the new ID of the thread originating this log record.
*/
public void setThreadID(int threadID) {
this.threadID = threadID;
}
/**
* Gets the {@code Throwable} object associated with this log record.
*
* @return the {@code Throwable} object associated with this log record.
*/
public Throwable getThrown() {
return thrown;
}
/**
* Sets the {@code Throwable} object associated with this log record.
*
* @param thrown
* the new {@code Throwable} object to associate with this log
* record.
*/
public void setThrown(Throwable thrown) {
this.thrown = thrown;
}
/*
* Customized serialization.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeByte(MAJOR);
out.writeByte(MINOR);
if (parameters == null) {
out.writeInt(-1);
} else {
out.writeInt(parameters.length);
for (Object element : parameters) {
out.writeObject((element == null) ? null : element.toString());
}
}
}
/*
* Customized deserialization.
*/
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
in.defaultReadObject();
byte major = in.readByte();
byte minor = in.readByte();
// only check MAJOR version
if (major != MAJOR) {
throw new IOException("Different version " + Byte.valueOf(major) + "." + Byte.valueOf(minor));
}
int length = in.readInt();
if (length >= 0) {
parameters = new Object[length];
for (int i = 0; i < parameters.length; i++) {
parameters[i] = in.readObject();
}
}
if (resourceBundleName != null) {
try {
resourceBundle = Logger.loadResourceBundle(resourceBundleName);
} catch (MissingResourceException e) {
// Cannot find the specified resource bundle
resourceBundle = null;
}
}
}
}