/*
* 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;
/**
* A {@code Handler} put the description of log events into a cycled memory
* buffer.
* <p>
* Mostly this {@code MemoryHandler} just puts the given {@code LogRecord} into
* the internal buffer and doesn't perform any formatting or any other process.
* When the buffer is full, the earliest buffered records will be discarded.
* <p>
* Every {@code MemoryHandler} has a target handler, and push action can be
* triggered so that all buffered records will be output to the target handler
* and normally the latter will publish the records. After the push action, the
* buffer will be cleared.
* <p>
* The push method can be called directly, but will also be called automatically
* if a new <code>LogRecord</code> is added that has a level greater than or
* equal to than the value defined for the property
* java.util.logging.MemoryHandler.push.
* <p>
* {@code MemoryHandler} will read following {@code LogManager} properties for
* initialization, if given properties are not defined or has invalid values,
* default value will be used.
* <ul>
* <li>java.util.logging.MemoryHandler.filter specifies the {@code Filter}
* class name, defaults to no {@code Filter}.</li>
* <li>java.util.logging.MemoryHandler.level specifies the level for this
* {@code Handler}, defaults to {@code Level.ALL}.</li>
* <li>java.util.logging.MemoryHandler.push specifies the push level, defaults
* to level.SEVERE.</li>
* <li>java.util.logging.MemoryHandler.size specifies the buffer size in number
* of {@code LogRecord}, defaults to 1000.</li>
* <li>java.util.logging.MemoryHandler.target specifies the class of the target
* {@code Handler}, no default value, which means this property must be
* specified either by property setting or by constructor.</li>
* </ul>
*/
public class MemoryHandler extends Handler {
// default maximum buffered number of LogRecord
private static final int DEFAULT_SIZE = 1000;
// target handler
private Handler target;
// buffer size
private int size = DEFAULT_SIZE;
// push level
private Level push = Level.SEVERE;
// LogManager instance for convenience
private final LogManager manager = LogManager.getLogManager();
// buffer
private LogRecord[] buffer;
// current position in buffer
private int cursor;
/**
* Default constructor, construct and init a {@code MemoryHandler} using
* {@code LogManager} properties or default values.
*
* @throws RuntimeException
* if property value are invalid and no default value could be
* used.
*/
public MemoryHandler() {
String className = this.getClass().getName();
// init target
final String targetName = manager.getProperty(className + ".target");
try {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader == null) {
loader = ClassLoader.getSystemClassLoader();
}
Class<?> targetClass = loader.loadClass(targetName);
target = (Handler) targetClass.newInstance();
} catch (Exception e) {
throw new RuntimeException("Cannot load target handler '" + targetName + "'");
}
// init size
String sizeString = manager.getProperty(className + ".size");
if (sizeString != null) {
try {
size = Integer.parseInt(sizeString);
if (size <= 0) {
size = DEFAULT_SIZE;
}
} catch (Exception e) {
printInvalidPropMessage(className + ".size", sizeString, e);
}
}
// init push level
String pushName = manager.getProperty(className + ".push");
if (pushName != null) {
try {
push = Level.parse(pushName);
} catch (Exception e) {
printInvalidPropMessage(className + ".push", pushName, e);
}
}
// init other properties which are common for all Handler
initProperties("ALL", null, "java.util.logging.SimpleFormatter", null);
buffer = new LogRecord[size];
}
/**
* Construct and init a {@code MemoryHandler} using given target, size and
* push level, other properties using {@code LogManager} properties or
* default values.
*
* @param target
* the given {@code Handler} to output
* @param size
* the maximum number of buffered {@code LogRecord}, greater than
* zero
* @param pushLevel
* the push level
* @throws IllegalArgumentException
* if {@code size <= 0}
* @throws RuntimeException
* if property value are invalid and no default value could be
* used.
*/
public MemoryHandler(Handler target, int size, Level pushLevel) {
if (size <= 0) {
throw new IllegalArgumentException("size <= 0");
}
target.getLevel();
pushLevel.intValue();
this.target = target;
this.size = size;
this.push = pushLevel;
initProperties("ALL", null, "java.util.logging.SimpleFormatter", null);
buffer = new LogRecord[size];
}
/**
* Close this handler and target handler, free all associated resources.
*/
@Override
public void close() {
manager.checkAccess();
target.close();
setLevel(Level.OFF);
}
/**
* Call target handler to flush any buffered output. Note that this doesn't
* cause this {@code MemoryHandler} to push.
*/
@Override
public void flush() {
target.flush();
}
/**
* Put a given {@code LogRecord} into internal buffer. If given record is
* not loggable, just return. Otherwise it is stored in the buffer.
* Furthermore if the record's level is not less than the push level, the
* push action is triggered to output all the buffered records to the target
* handler, and the target handler will publish them.
*
* @param record
* the log record
*/
@Override public synchronized void publish(LogRecord record) {
if (!isLoggable(record)) {
return;
}
if (cursor >= size) {
cursor = 0;
}
buffer[cursor++] = record;
if (record.getLevel().intValue() >= push.intValue()) {
push();
}
}
/**
* Return the push level.
*
* @return the push level
*/
public Level getPushLevel() {
return push;
}
/**
* Check if given {@code LogRecord} would be put into this
* {@code MemoryHandler}'s internal buffer.
* <p>
* The given {@code LogRecord} is loggable if and only if it has appropriate
* level and it pass any associated filter's check.
* <p>
* Note that the push level is not used for this check.
*
* @param record
* the given {@code LogRecord}
* @return the given {@code LogRecord} if it should be logged, {@code false}
* if {@code LogRecord} is {@code null}.
*/
@Override
public boolean isLoggable(LogRecord record) {
return super.isLoggable(record);
}
/**
* Triggers a push action to output all buffered records to the target handler,
* and the target handler will publish them. Then the buffer is cleared.
*/
public void push() {
for (int i = cursor; i < size; i++) {
if (buffer[i] != null) {
target.publish(buffer[i]);
}
buffer[i] = null;
}
for (int i = 0; i < cursor; i++) {
if (buffer[i] != null) {
target.publish(buffer[i]);
}
buffer[i] = null;
}
cursor = 0;
}
/**
* Set the push level. The push level is used to check the push action
* triggering. When a new {@code LogRecord} is put into the internal
* buffer and its level is not less than the push level, the push action
* will be triggered. Note that set new push level won't trigger push action.
*
* @param newLevel
* the new level to set.
*/
public void setPushLevel(Level newLevel) {
manager.checkAccess();
newLevel.intValue();
this.push = newLevel;
}
}