org.objectweb.howl.log.Logger Maven / Gradle / Ivy
/*
* JOnAS: Java(TM) Open Application Server
* Copyright (C) 2004 Bull S.A.
* All rights reserved.
*
* Contact: [email protected]
*
* This software is licensed under the BSD license.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ------------------------------------------------------------------------------
* $Id: Logger.java,v 1.14 2006/04/21 15:03:36 girouxm Exp $
* ------------------------------------------------------------------------------
*/
package org.objectweb.howl.log;
import java.io.IOException;
/**
* Manage a configured set of two or more physical log files.
*
* Log files have a configured maximum size. When a file has
* reached the configured capacity, Logger switches to
* the next available alternate file. Normally, log files are created
* in advance to guarantee that space is available during execution.
*
*
Each log file has a file header containing information
* allowing Logger to reposition and replay the logs
* during a recovery scenario.
*
*
LogFile marking
*
The LogFile's mark is the the position within the file
* of the oldest active entry.
* Initially the mark is set at the beginning of the file.
* At some configured interval, the caller invokes mark()
* with the key of the oldest active entry in the log.
*
*
For XA the key would be for the oldest transaction still
* in committing state. In theory, XA could call mark() every
* time a DONE record is logged. In practice, it should only be
* necessary to call mark() every minute or so depending on the
* capacity of the log files.
*
*
The Logger maintains an active mark within the set
* of log files. A file may be reused only if the mark does not
* reside within the file. The Logger will throw
* LogFileOverflowException if an attempt is made to switch to a
* file that contains a mark.
*
* @author Michael Giroux
*
*/
public class Logger extends LogObject
{
/**
* indicates whether the LogFile is open.
*
Logger methods return LogClosedException when log is closed.
*/
protected volatile boolean isClosed = true;
/**
* Manages a pool of buffers used for log file IO.
*/
LogBufferManager bmgr = null;
/**
* Manages a pool of files used for log file IO.
*/
LogFileManager lfmgr = null;
/**
* @return activeMark member of the associated LogFileManager.
*/
public long getActiveMark()
{
return lfmgr.activeMark;
}
/**
* Construct a Logger using default Configuration object.
* @throws IOException
*/
public Logger()
throws IOException
{
this(new Configuration());
}
/**
* Construct a Logger using a Configuration supplied
* by the caller.
* @param config Configuration object
* @throws IOException
*/
public Logger(Configuration config)
throws IOException
{
super(config);
lfmgr = new LogFileManager(config);
bmgr = new LogBufferManager(config);
}
/**
* add a USER record consisting of byte[][] to log.
*
*
if sync parameter is true, then the method will
* block (in bmgr.put()) until the data buffer is forced to disk.
* Otherwise, the method returns immediately.
*
* @param data record data
* @param sync true if call should block until force
*
* @return a key that can be used to locate the record.
* Some implementations may use the key as a correlation ID
* to associate related records.
*
* When automark is disabled (false) the caller must
* invoke mark() using this key to indicate the location
* of the oldest active entry in the log.
*
* @throws LogClosedException
* @throws LogRecordSizeException
* @throws LogFileOverflowException
* @throws InterruptedException
* @throws IOException
*
* @see #mark(long)
* @see #setAutoMark(boolean)
*/
public long put(byte[][] data, boolean sync)
throws LogClosedException, LogRecordSizeException, LogFileOverflowException,
InterruptedException, IOException
{
return put(LogRecordType.USER, data, sync);
}
/**
* add a USER record consisting of byte[] to the log.
*
*
wrap byte[] data in a new byte[][]
* and delegates call to put(byte[][], boolean)
*
* @param data byte[] to be written to log
* @param sync true if caller wishes to block waiting for the
* record to force to disk.
* @return log key for the record
* @throws LogClosedException
* @throws LogRecordSizeException
* @throws LogFileOverflowException
* @throws InterruptedException
* @throws IOException
*/
public long put(byte[] data, boolean sync)
throws LogClosedException, LogRecordSizeException, LogFileOverflowException,
InterruptedException, IOException
{
return put(LogRecordType.USER, new byte[][]{data}, sync);
}
/**
* Sub-classes call this method to write log records with
* a specific record type.
*
* @param type a record type defined in LogRecordType.
* @param data record data to be logged.
* @param sync boolean indicating whether call should
* wait for data to be written to physical disk.
*
* @return a log key that can be used to reference
* the record.
*/
protected long put(short type, byte[][] data, boolean sync)
throws LogClosedException, LogRecordSizeException, LogFileOverflowException,
InterruptedException, IOException
{
synchronized(this)
{
if (isClosed) throw new LogClosedException();
}
// QUESTION: should we deal with exceptions here?
long key = bmgr.put(type, data, sync);
lfmgr.setCurrentKey(key);
return key;
}
/**
* sets the LogFile's mark.
*
*
mark() provides a generalized method for callers
* to inform the Logger that log space can be released
* for reuse.
*
*
calls LogFileManager to process the request.
*
* @param key is a log key returned by a previous call to put().
* @param force a boolean that indicates whether the mark data
* should be forced to disk. When set to true the caller
* is blocked until the mark record is forced to disk.
*
* @throws InvalidLogKeyException
* if key parameter is out of range.
* key must be greater than current activeMark and less than the most recent
* key returned by put().
* @throws LogClosedException
* if this logger instance has been closed.
*/
public void mark(long key, boolean force)
throws InvalidLogKeyException, LogClosedException, IOException, InterruptedException
{
synchronized(this)
{
if (isClosed)
throw new LogClosedException("log is closed");
}
lfmgr.mark(key, force);
}
/**
* calls Logger.mark(key, force) with force set to true .
*
Caller is blocked until mark record is forced to disk.
* @param key a log key returned by a previous call to put().
* @throws InvalidLogKeyException
* @throws LogClosedException
* @throws IOException
* @throws InterruptedException
* @see #mark(long, boolean)
*/
public void mark(long key)
throws InvalidLogKeyException, LogClosedException, IOException, InterruptedException
{
mark(key, true);
}
/**
* Sets the LogFile marking mode.
*
*
passes call to LogFileManager
*
* @param autoMark true to indicate automatic marking.
*/
public void setAutoMark(boolean autoMark)
throws InvalidLogKeyException, LogClosedException, LogFileOverflowException, IOException, InterruptedException
{
synchronized(this)
{
if (this.isClosed) throw new LogClosedException();
}
lfmgr.setAutoMark(autoMark);
}
/**
* close the Log files and perform necessary cleanup tasks.
*/
public void close() throws IOException, InterruptedException
{
// prevent new threads from adding to the log
synchronized(this) { isClosed = true; }
lfmgr.close();
}
/**
* open Log files and perform necessart initialization.
*
* TODO: consider open(String name) to allow named configurations.
* this would allow utility to open two loggers and copy
* old records to new files.
*
*/
public void open()
throws InvalidFileSetException,
IOException, LogConfigurationException, InvalidLogBufferException, InterruptedException
{
lfmgr.open();
try {
bmgr.open();
} catch (ClassNotFoundException e) {
String cnf = "LogBuffer Class not found: " + config.getBufferClassName();
LogConfigurationException lce = new LogConfigurationException(cnf, e);
throw lce;
}
// read header information from each file
lfmgr.init(bmgr);
// indicate that Log is ready for use.
synchronized(this) { isClosed = false; }
}
/**
* Registers a LogEventListener for log event notifications.
*
* @param eventListener object to be notified of logger events.
*/
public void setLogEventListener(LogEventListener eventListener)
{
lfmgr.setLogEventListener(eventListener);
}
/**
* Replays log from a specified mark forward to the current mark.
*
*
Beginning with the record located at mark
* the Logger reads log records forward to the end of the log.
* USER records are passed to the listener onRecord()
* method. When the end of log has been reached, replay returns
* one final record with a type of END_OF_LOG to inform listener
* that no further records will be returned.
*
*
If an error is encountered while reading the log, the
* listener onError method is called. Replay terminates
* when any error occurs and when END_OF_LOG is encountered.
*
* @param listener an object that implements ReplayListener interface.
* @param mark a log key to begin replay from.
*
The mark should be a valid log key returned by the put()
* method. To replay the entire log beginning with the oldest available
* record, mark should be set to zero (0L).
* @throws LogConfigurationException
* most likely because the configured LogBuffer class cannot be found.
* @throws InvalidLogKeyException
* if mark is not a valid log key.
*/
public void replay(ReplayListener listener, long mark)
throws InvalidLogKeyException, LogConfigurationException
{
// replay only the user records.
bmgr.replay(listener, mark, false);
}
/**
* Replays log from the active mark forward to the current position.
*
* @param listener an object that implements ReplayListener interface.
* @throws LogConfigurationException
* most likely because the configured LogBuffer class cannot be found.
* @see #replay(ReplayListener, long)
*/
public void replay(ReplayListener listener) throws LogConfigurationException
{
try {
bmgr.replay(listener, lfmgr.activeMark, false);
} catch (InvalidLogKeyException e) {
// should not happen -- use assert to catch during development
assert false : "Unhandled InvalidLogKeyException" + e.toString();
}
}
/**
* Allows sub-classes of Logger to replay control records.
*
* @param listener ReplayListener to receive the records
* @param mark starting mark (log key) for the replay.
* @param replayCtrlRecords boolean indicating whether to
* return CTRL records.
* @throws InvalidLogKeyException
* If the mark parameter specifies an invalid log key
* (one that does not exist in the current set of log files.)
* @throws LogConfigurationException
*
* @see org.objectweb.howl.log.LogBufferManager#replay(ReplayListener, long, boolean)
*/
protected void replay(ReplayListener listener, long mark, boolean replayCtrlRecords)
throws InvalidLogKeyException, LogConfigurationException
{
bmgr.replay(listener, mark, replayCtrlRecords);
}
/**
* Read a specific record from the log.
*
Control records are not filtered by this method.
* If the requested mark is valid and identifies a control record,
* the record will be returned.
* @param lr LogRecord to be updated or null if caller wishes a new
* LogRecord to be allocated.
* @param mark a log key identifying the location of the record
* within the journal
* @return LogRecord containing requested record
* @throws InvalidLogKeyException
* if logkey parameter is < 0L or if the requested key is not within the current range
* of keys in the log.
* @throws LogConfigurationException
* @throws LogException
*/ // FEATURE: 300792
public LogRecord get(LogRecord lr, long mark) throws InvalidLogKeyException,
LogConfigurationException,
LogException, InvalidLogBufferException
{
/* this code is similar to LogBufferManager.replay() -- potential for refactor */
int bsn = bmgr.bsnFromMark(mark);
if (mark < 0 || (bsn == 0 && mark != 0))
throw new InvalidLogKeyException(Long.toHexString(mark));
if (lr == null)
lr = new LogRecord((config.getBufferSize() * 1024)/4); // default to 1/4 buffer size
// allocate a LogBuffer that we can use to read the journal
try {
if (lr.buffer == null)
lr.buffer = bmgr.getLogBuffer(-1);
} catch (ClassNotFoundException e) {
throw new LogConfigurationException(e);
}
LogBuffer buffer = lr.buffer;
// read block containing requested mark
try {
bmgr.forceCurrentBuffer();
lfmgr.read(buffer, bsn);
} catch (IOException e) {
LogFile lf = buffer.lf;
String msg = "Error reading " + lf.file + " @ position [" + lf.position + "]";
throw new LogException(msg, e);
}
if (buffer.bsn == -1)
{
lr.type = LogRecordType.END_OF_LOG;
return lr;
}
// verify we have the desired block
// if requested mark == 0 then we start with the oldest block available
int markBSN = (mark == 0) ? buffer.bsn : bmgr.bsnFromMark(mark);
if (markBSN != buffer.bsn) {
InvalidLogBufferException lbe = new InvalidLogBufferException(
"block read [" + buffer.bsn + "] not block requested: " + markBSN);
throw lbe;
}
/*
* position buffer to requested mark.
*
* Although the mark contains a buffer offset, we search forward
* through the buffer to guarantee that we have the start
* of a record. This protects against using marks that were
* not generated by the current Logger.
*/
lr.get(buffer); // get first record in buffer
if (mark > 0 && mark > bmgr.markFromBsn(markBSN,0)) {
while(lr.key < mark) {
lr.get(buffer);
}
if (lr.key != mark) {
String msg = "The requested mark [" + Long.toHexString(mark) +
"] was not found in the log.";
// BUG 300733 following line changed to throw an exception
throw new InvalidLogKeyException(msg);
}
}
return lr;
}
/**
* Read the journal record that follows the record identified by lr.
* @param lr LogRecord to be updated with the next journal record.
*
The LogRecord lr
must have been returned
* by a previous call to Logger.get(LogRecord, long).
*
Effectively, the record identified by lr is located, and the record
* immediately following it is returned.
* @return LogRecord containing the requested record.
* @throws IllegalArgumentException
* if lr parameter is null or if the lr.buffer member is null.
*/ // FEATURE: 300792
public LogRecord getNext(LogRecord lr)
throws InvalidLogBufferException, LogException
{
if (lr == null || lr.buffer == null) throw new IllegalArgumentException();
LogBuffer buffer = lr.buffer;
// get next record
lr.get(buffer);
if (lr.isEOB())
{
long bsn = buffer.bsn; // so we can test for wraparound
try {
lfmgr.read(buffer, buffer.bsn+1);
} catch (IOException e) {
LogFile lf = lr.buffer.lf;
String msg = "Error reading " + lf.file + " @ position [" + lf.position + "]";
throw new LogException(msg, e);
}
if (buffer.bsn == -1 || buffer.bsn < bsn) // BUG 304982
{
lr.type = LogRecordType.END_OF_LOG;
return lr;
}
lr.get(buffer);
}
return lr;
}
/**
* return an XML node containing statistics for the Logger,
* the LogFile pool and the LogBuffer pool.
*
*
The getStats method for the LogBufferManager and LogFileManager
* are called to include statistics for these contained objects.
*
* @return String contiining XML node.
*/
public String getStats()
{
String name = this.getClass().getName();
StringBuffer stats = new StringBuffer(
""
);
// TODO: append Logger specific stats
stats.append(bmgr.getStats());
stats.append(lfmgr.getStats());
stats.append("\n " +
"\n");
return stats.toString();
}
}