package ecologylab.oodss.logging;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.ArrayList;
import ecologylab.appframework.ApplicationEnvironment;
import ecologylab.appframework.Memory;
import ecologylab.appframework.PropertiesAndDirectories;
import ecologylab.appframework.types.prefs.Pref;
import ecologylab.collections.Scope;
import ecologylab.generic.Debug;
import ecologylab.generic.Generic;
import ecologylab.generic.StartAndStoppable;
import ecologylab.io.Files;
import ecologylab.oodss.distributed.client.NIOClient;
import ecologylab.oodss.distributed.common.NetworkingConstants;
import ecologylab.oodss.distributed.common.ServicesHostsAndPorts;
import ecologylab.oodss.distributed.exception.MessageTooLargeException;
import ecologylab.oodss.messages.DefaultServicesTranslations;
import ecologylab.oodss.messages.ResponseMessage;
import ecologylab.serialization.ElementState;
import ecologylab.serialization.SIMPLTranslationException;
import ecologylab.serialization.SimplTypesScope;
import ecologylab.serialization.XMLTools;
import ecologylab.serialization.annotations.simpl_collection;
import ecologylab.serialization.annotations.simpl_scope;
import ecologylab.serialization.formatenums.StringFormat;
/**
* Provides a framework for interaction logging. Uses ecologylab.serialization to serialize user and
* agent actions, and write them either to a file on the user's local machine, or, across the
* network, to the LoggingServer.
*
* @author andruid
* @author Zachary O. Toups (zach@ecologylab.net)
*/
public class Logging<T extends MixedInitiativeOp> extends ElementState implements
StartAndStoppable, ServicesHostsAndPorts
{
public static final String MIXED_INITIATIVE_OP_TRANSLATION_SCOPE = "MIXED_INITIATIVE_OP_TRANSLATION_SCOPE";
/**
* This field is used for reading a log in from a file, but not for writing one, because we dont
* the write the log file all at once, and so can't automatically translate the start tag and end
* tag for this element.
*/
@simpl_collection
@simpl_scope(MIXED_INITIATIVE_OP_TRANSLATION_SCOPE)
protected ArrayList<T> opSequence;
Thread thread;
/**
* Does all the work of logging, if there is any work to be done. If this is null, then there is
* no logging; conversely, if there is no logging, this is null.
*/
ArrayList<LogWriter> logWriters = null;
/**
* This is the Vector for the operations that are being queued up before they can go to
* outgoingOps.
*/
private StringBuilder incomingOpsBuffer;
/**
* This is the Vector for the operations that are in the process of being written out.
*/
private StringBuilder outgoingOpsBuffer;
/** Stores the pointer to outgoingOpsBuffer for swapQueues. */
private StringBuilder tempOpsBuffer;
static final int THREAD_PRIORITY = 1;
/** Amount of time for writer thread to sleep; 15 seconds */
static final int SLEEP_TIME = 15000;
static final long sessionStartTime = System.currentTimeMillis();
long lastGcTime;
/** Amount of time to wait before booting the garbage collector; 5 minutes */
static final long KICK_GC_INTERVAL = 300000;
private static final String SESSION_LOG_START = "\n<session_log>\n ";
static final String OP_SEQUENCE_START = "\n\n<op_sequence>\n\n";
static final String OP_SEQUENCE_END = "\n</op_sequence>\n";
/** Logging closing message string written to the logging file at the end */
public static final String LOG_CLOSING = "\n</op_sequence></session_log>\n\n";
/** Logging Header message string written to the logging file in the begining */
static final String BEGIN_EMIT = XMLTools.xmlHeader()
+ SESSION_LOG_START;
/** Preference setting for no logging. */
public static final int NO_LOGGING = 0;
/** Preference setting for logging to a file using normal IO. */
public static final int LOG_TO_FILE = 1;
/** Preference setting for logging to a remote server. */
public static final int LOG_TO_SERVICES_SERVER = 2;
/** Preference setting for logging to a file using memory-mapped IO. */
public static final int LOG_TO_MEMORY_MAPPED_FILE = 4;
/** Preference setting for logging both to a memory-mapped file and a server. */
public static final int LOG_TO_MM_FILE_AND_SERVER_REDUNDANT = LOG_TO_MEMORY_MAPPED_FILE
& LOG_TO_SERVICES_SERVER;
/** Preference setting for logging both to a normal IO file and a server. */
public static final int LOG_TO_FILE_AND_SERVER_REDUNDANT = LOG_TO_FILE
& LOG_TO_SERVICES_SERVER;
static final int MAX_OPS_BEFORE_WRITE = 10;
public static final String LOGGING_HOST_PARAM = "logging_host";
public static final String LOGGING_PORT_PARAM = "logging_port";
public static final String LOGGING_MODE_PARAM = "log_mode";
final int maxOpsBeforeWrite;
final int maxBufferSizeToWrite;
/** used to prevent writes from getting interrupt()'ed */
private Object threadSemaphore = new Object();
private volatile boolean runMethodDone = false;
volatile boolean finished;
private boolean running = false;
/**
* Instantiates a Logging object based on the given log file name. This constructor assumes that a
* set of loaded {@link ecologylab.appframework.types.prefs.Pref Pref}s will handle other
* settings, indicating how logging will be performed, and the server setup (if logging over a
* network).
*/
public Logging(String logFileName)
{
this(logFileName, MAX_OPS_BEFORE_WRITE);
}
/**
* Instantiates a Logging object based on the given log file name and the maximum operations
* before write. This constructor assumes that a set of loaded
* {@link ecologylab.appframework.types.prefs.Pref Pref}s will handle other settings, indicating
* how logging will be performed, and the server setup (if logging over a network).
*/
public Logging(String logFileName, int maxOpsBeforeWrite)
{
this( logFileName,
false,
maxOpsBeforeWrite,
Pref.lookupInt(LOGGING_MODE_PARAM, NO_LOGGING),
Pref.lookupString(LOGGING_HOST_PARAM, "localhost"),
Pref.lookupInt(LOGGING_PORT_PARAM, ServicesHostsAndPorts.LOGGING_PORT),
null);
}
/**
* Instantiates a Logging object based on the supplied parameters. This constructor does not rely
* on {@link ecologylab.appframework.types.prefs.Pref Pref}s.
*
* @param logFileName
* the name of the file to which the log will be written.
* @param logFileNameAbsolute
* TODO
* @param maxOpsBeforeWrite
* the maximum number of ops to record in memory before writing them to the set media.
* @param logMode
* the media to which the logger will write, such as a memory-mapped file or a server.
* @param loggingHost
* the host to which to log if using networked logging (may be null if local logging is
* desired).
* @param loggingPort
* the port of the host to which to log if using networked logging (may be 0 if local
* logging is desired).
* @deprecated Use {@link #Logging(String,boolean,int,int,String,int,ApplicationEnvironment)}
* instead
*/
@Deprecated
public Logging( String logFileName,
boolean logFileNameAbsolute,
int maxOpsBeforeWrite,
int logMode,
String loggingHost,
int loggingPort)
{
this( logFileName,
logFileNameAbsolute,
maxOpsBeforeWrite,
logMode,
loggingHost,
loggingPort,
null);
}
/**
* Instantiates a Logging object based on the supplied parameters. This constructor does not rely
* on {@link ecologylab.appframework.types.prefs.Pref Pref}s.
*
* @param logFileName
* the name of the file to which the log will be written.
* @param logFileNameAbsolute
* TODO
* @param maxOpsBeforeWrite
* the maximum number of ops to record in memory before writing them to the set media.
* @param logMode
* the media to which the logger will write, such as a memory-mapped file or a server.
* @param loggingHost
* the host to which to log if using networked logging (may be null if local logging is
* desired).
* @param loggingPort
* the port of the host to which to log if using networked logging (may be 0 if local
* logging is desired).
* @param environment
* TODO
*/
public Logging( String logFileName,
boolean logFileNameAbsolute,
int maxOpsBeforeWrite,
int logMode,
String loggingHost,
int loggingPort,
ApplicationEnvironment environment)
{
this(maxOpsBeforeWrite);
if (logMode == NO_LOGGING)
{
debug("logging disabled; NO_LOGGING specified");
}
else
{
logWriters = new ArrayList<LogWriter>(1);
if ((logMode & LOG_TO_FILE) == LOG_TO_FILE)
{
if (logFileName == null)
{
debug("Logging disabled; no file name specified");
}
else
{
File logDir;
if (!logFileNameAbsolute)
{
if (environment == null)
logDir = PropertiesAndDirectories.logDir();
else
logDir = PropertiesAndDirectories.logDir(environment);
}
else
{
logDir = new File(logFileName);
}
if (logDir == null)
{
debug("Can't write to logDir=" + logDir);
}
else
{
File logFile;
if (!logFileNameAbsolute)
{
logFile = new File(logDir, logFileName);
}
else
{
logFile = logDir;
}
debug("Logging to file: " + logFile.getAbsolutePath());
try
{
XMLTools.createParentDirs(logFile);
}
catch (SIMPLTranslationException e)
{
e.printStackTrace();
}
BufferedWriter bufferedWriter = Files.openWriter(logFile);
if (bufferedWriter != null)
{
try
{
logWriters.add(new FileLogWriter(logFile, bufferedWriter));
}
catch (IOException e)
{
e.printStackTrace();
}
}
else
{
debug("ERROR: cant open writer to " + logFile);
}
}
}
}
if ((logMode & LOG_TO_MEMORY_MAPPED_FILE) == LOG_TO_MEMORY_MAPPED_FILE)
{
if (logFileName == null)
{
debug("Logging disabled; no file name specified");
}
else
{
File logDir;
if (!logFileNameAbsolute)
{
logDir = PropertiesAndDirectories.logDir();
}
else
{
logDir = new File(logFileName);
}
if (logDir == null)
{
debug("Can't write to logDir=" + logDir);
}
else
{
File logFile;
if (!logFileNameAbsolute)
{
logFile = new File(logDir, logFileName);
}
else
{
logFile = logDir;
}
try
{
XMLTools.createParentDirs(logFile);
}
catch (SIMPLTranslationException e)
{
e.printStackTrace();
}
debug("Logging to file: " + logFile.getAbsolutePath());
try
{
logWriters.add(new MemoryMappedFileLogWriter(logFile));
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
}
if ((logMode & LOG_TO_SERVICES_SERVER) == LOG_TO_SERVICES_SERVER)
{
/**
* Create the logging client which communicates with the logging server
*/
if (loggingHost == null)
loggingHost = LOGGING_HOST;
NIOClient loggingClient = null;
try
{
loggingClient = new NIOClient(loggingHost,
loggingPort,
DefaultServicesTranslations.get(),
new Scope());
// CONNECT TO SERVER
if (loggingClient.connect())
{
logWriters.add(new NetworkLogWriter(loggingClient, this));
debug("logging to server: " + loggingHost + ":" + loggingPort);
}
else
{
loggingClient = null;
debug("Logging disabled: cannot reach server");
}
}
catch (IOException e1)
{
e1.printStackTrace();
}
debug("**************************************************************connecting to server.");
}
}
}
/**
* Constructor for automatic translation from XML
*
*/
public Logging()
{
this(MAX_OPS_BEFORE_WRITE);
}
protected Logging(int maxOpsBeforeWrite)
{
this.maxOpsBeforeWrite = maxOpsBeforeWrite;
final int maxBufferSizeToWrite = maxOpsBeforeWrite * 1024;
incomingOpsBuffer = new StringBuilder(maxBufferSizeToWrite);
outgoingOpsBuffer = new StringBuilder(maxBufferSizeToWrite);
this.maxBufferSizeToWrite = maxBufferSizeToWrite;
}
private void swapBuffers()
{
synchronized (outgoingOpsBuffer)
{
synchronized (incomingOpsBuffer)
{
if (outgoingOpsBuffer.length() == 0)
{
tempOpsBuffer = outgoingOpsBuffer;
outgoingOpsBuffer = incomingOpsBuffer;
incomingOpsBuffer = tempOpsBuffer;
}
}
}
}
/**
* Translates op to XML then logs it, if this Logging object is running. Returns true if the
* operation was placed in the buffer to be written, false if it failed (because the op could not
* be translated to XML).
*
* @param op
* - the operation to be logged.
*/
public boolean logAction(MixedInitiativeOp op)
{
if (!this.finished && logWriters != null)
{
try
{
synchronized (incomingOpsBuffer)
{
SimplTypesScope.serialize(op, incomingOpsBuffer, StringFormat.XML);
}
// final int bufferLength = incomingOpsBuffer.length();
// if ((thread != null) && (bufferLength > maxBufferSizeToWrite))
// {
// synchronized (threadSemaphore)
// {
// debugA("interrupting thread to do i/o now: " + bufferLength
// + "/" + maxBufferSizeToWrite);
// thread.interrupt();
// // end sleep in that thread prematurely to do i/o
// }
// }
return true;
}
catch (SIMPLTranslationException e)
{
e.printStackTrace();
}
}
return false;
}
/**
* Returns the size of the list of log ops. May not be the correct value if called during logging.
* This method should only be used for playback purposes.
*
* @return the size of opSequence.
*/
public int size()
{
return this.opSequence.size();
}
/**
* Write the start of the log header out to the log file OR, send the begining logging file
* message so that logging server write the start of the log header.
* <p/>
* Then start the looping thread that periodically wakes up and performs log i/o.
*/
@Override
public void start()
{
debug("Logging starting up...");
if ((logWriters != null) && (thread == null))
{
thread = new Thread(this);
thread.setPriority(THREAD_PRIORITY);
thread.start();
this.running = true;
}
}
/**
* Finishes writing any queued actions, then sends the epilogue; then shuts down.
*
*/
@Override
public synchronized void stop()
{
debug("shutting down...");
if (!finished && thread != null)
{
finished = true;
debug("initiating shutdown sequence...");
int timesToWait = 100;
while (!this.runMethodDone && timesToWait-- > 0)
{
debug("waiting on run method to finish log writing (attempts remaining " + timesToWait
+ ")...");
thread.interrupt();
Generic.sleep(500);
}
if (timesToWait == 0)
{
debug("...giving up on waiting for run thread; continuing shutdown.");
}
else
{
debug("...done.");
}
this.thread = null;
synchronized (threadSemaphore)
{ // since we only write from run() if we're low on memory, we may
// have all the ops still unwritten.
writeBufferedOps();
}
if (logWriters != null)
{
final Epilogue epilogue = getEpilogue();
// necessary for acquiring wrapper characters, like </op_sequence>
final SendEpilogue sendEpilogue = new SendEpilogue(this, epilogue);
for (LogWriter logWriter : logWriters)
{
logWriter.setPriority(9);
debug("stop() writing epilogue to " + logWriter);
logWriter.writeLogMessage(sendEpilogue);
}
// try
// {
// debug("epilogue contents: " + sendEpilogue.serialize());
// }
// catch (SIMPLTranslationException e)
// {
// e.printStackTrace();
// }
synchronized (threadSemaphore)
{
debug("forcing final write");
writeBufferedOps();
}
for (LogWriter logWriter : logWriters)
{
debug("stop() closing " + logWriter);
logWriter.close();
}
debug("...stop() finished.");
logWriters = null;
}
}
debug("...shutdown complete.");
this.running = false;
}
/**
* Logging to a file is delayed to the actions of this thread, because otherwise, it can mess up
* priorities in the system, because events get logged in the highest priority thread.
* <p/>
* This MUST be the only thread that ever calls writeQueuedActions().
*/
@Override
public void run()
{
this.runMethodDone = false;
this.finished = false;
final Prologue prologue = getPrologue();
String uid = Pref.lookupString("uid", "0");
Logging.this.debug("Logging: Sending Prologue userID:" + uid);
prologue.setUserID(uid);
// necessary for acquiring wrapper characters, like <op_sequence>
final SendPrologue sendPrologue = new SendPrologue(Logging.this, prologue);
for (LogWriter logWriter : logWriters)
{
debug("sending prologue");
logWriter.writeLogMessage(sendPrologue);
}
lastGcTime = System.currentTimeMillis();
while (!finished)
{
Thread.interrupted();
Generic.sleep(SLEEP_TIME, false);
if (finished)
debug("run thread awakened for final run");
if (!Memory.reclaimIfLow())
{
synchronized (threadSemaphore)
{
writeBufferedOps();
}
}
if (finished)
debug("run thread finishing");
}
debug("run thread finished.");
// now that we are finished, we let everyone else know
this.runMethodDone = true;
}
/**
* Use the LogWriter, if there is one, to output queued actions to the log.
* <p/>
* NB: This method is SINGLE Threaded! It is not thread safe. It must only be called from the
* run() method.
*/
protected void writeBufferedOps()
{
StringBuilder bufferToWrite = incomingOpsBuffer;
synchronized (bufferToWrite)
{
int size = bufferToWrite.length();
if (size == 0)
return;
swapBuffers();
// what was incomingOps is now outgoing!
if (logWriters != null)
{
for (LogWriter logWriter : logWriters)
{
logWriter.writeBufferedOps(bufferToWrite);
}
}
// debug("Logging: writeQueuedActions() after output loop.");
bufferToWrite.setLength(0);
}
}
/**
* A message at the beginnging of the log. This method may be overridden to return a subclass of
* Prologue, by subclasses of this, that wish to emit application specific information at the
* start of a log.
*
* @return
*/
public Prologue getPrologue()
{
return new Prologue();
}
/**
* A message at the end of the log. This method may be overridden to return a subclass of
* Epilogue, by subclasses of this, that wish to emit application specific information at the end
* of a log.
*
* @return
*/
protected Epilogue getEpilogue()
{
Epilogue epilogue = new Epilogue();
return epilogue;
}
/**
* Return our session start timestamp.
*
* @return
*/
public static final long sessionStartTime()
{
return sessionStartTime;
}
/**
* Objects that process queuedActions for writing, either to a local file, or, using the network,
* to the LoggingServer.
*
* @author andruid
* @author Zachary O. Toups (zach@ecologylab.net)
*/
protected abstract class LogWriter extends Debug
{
LogWriter() throws IOException
{
}
/**
* Get the bufferToLog() from the LogRequestMessage. Write it out!
*
* @param logRequestMessage
*/
void writeLogMessage(LogEvent logRequestMessage)
{
StringBuilder buffy = logRequestMessage.bufferToLog();
writeLogMessage(buffy);
}
abstract void writeLogMessage(StringBuilder xmlBuffy);
void writeBufferedOps(StringBuilder opsBuffer)
{
writeLogMessage(opsBuffer);
}
abstract void close();
public void setPriority(int priority)
{
}
}
/**
* LogWriter that uses a memory-mapped local file for logging.
*
* @author andruid
* @author Zachary O. Toups (zach@ecologylab.net)
*/
protected class MemoryMappedFileLogWriter extends LogWriter
{
// BufferedWriter bufferedWriter;
MappedByteBuffer buffy = null;
FileChannel channel = null;
private CharsetEncoder encoder = Charset.forName(NetworkingConstants.CHARACTER_ENCODING)
.newEncoder();
private File logFile;
/**
* The base size for the log file, and the amount it will be incremented whenever its buffer
* overflows.
*/
static final int LOG_FILE_INCREMENT = 1024 * 512;
private int endOfMappedBytes = LOG_FILE_INCREMENT;
MemoryMappedFileLogWriter(File logFile) throws IOException
{
this.logFile = logFile;
channel = new RandomAccessFile(logFile, "rw").getChannel();
// allocate LOG_FILE_INCREMENT for the file size; this will be
// incremented as necessary
buffy = channel.map(MapMode.READ_WRITE, 0, LOG_FILE_INCREMENT);
// if (bufferedWriter == null)
// throw new IOException("Can't log to File with null
// buffereredWriter.");
// this.bufferedWriter = bufferedWriter;
Logging.this.debugA("Logging to " + logFile + " " + buffy);
}
/**
* Get the bufferToLog() from the LogRequestMessage. Write it out!
*
* @param buffer
*/
@Override
void writeLogMessage(StringBuilder buffer)
{
try
{
putInBuffer(encoder.encode(CharBuffer.wrap(buffer)));
}
catch (CharacterCodingException e)
{
e.printStackTrace();
}
}
@Override
public void close()
{
try
{
buffy.force();
// shrink the file to the appropriate size
int fileSize = 0;
fileSize = endOfMappedBytes - LOG_FILE_INCREMENT + buffy.position();
buffy = null;
channel.close();
channel = null;
// do garbage collection to ensure that the file is no longer
// mapped
System.runFinalization();
System.gc();
debug("final log file size is " + fileSize + " bytes.");
debug("truncating log.");
boolean truncated = false;
int numTries = 0;
while (!truncated)
{
try
{
new RandomAccessFile(logFile, "rw").getChannel().truncate(fileSize);
truncated = true;
}
catch (IOException e)
{
debug("truncation failed because the file is still mapped, attempting to garbage collect...AGAIN.");
// do garbage collection to ensure that the file is no
// longer mapped
System.runFinalization();
System.gc();
numTries++;
}
if (numTries == 100)
{
debug("Tried to unmap file 100 times; failing now.");
truncated = true;
}
}
}
catch (IOException e1)
{
e1.printStackTrace();
}
}
private void putInBuffer(ByteBuffer incoming)
{
try
{
int remaining = buffy.remaining();
if (remaining < incoming.remaining())
{ // we want to write more than will fit; so we just write
// what we can first...
debug("not enough space in the buffer: " + remaining + "/"
+ incoming.remaining() + "; old range: " + (endOfMappedBytes - LOG_FILE_INCREMENT) + "-"
+ endOfMappedBytes+"; new range: " + (endOfMappedBytes) + "-"
+ (endOfMappedBytes + LOG_FILE_INCREMENT));
byte[] temp = new byte[remaining];
incoming.get(temp, 0, remaining);
buffy.put(temp);
// ensure that the buffer has been written out
buffy.force();
// then shift buffy to map to the next segment of the file
buffy = channel.map(MapMode.READ_WRITE, endOfMappedBytes, LOG_FILE_INCREMENT);
endOfMappedBytes += LOG_FILE_INCREMENT;
// recursively call on the remainder of incoming
putInBuffer(incoming);
}
else
{
if (show(5))
debug("writing to buffer: " + remaining + "bytes remaining before resize");
buffy.put(incoming);
}
}
catch (NullPointerException e)
{
debug("null pointer; data to be written:");
try
{
debug(Charset.forName("ASCII").newDecoder().decode(incoming).toString());
}
catch (CharacterCodingException e1)
{
e1.printStackTrace();
}
e.printStackTrace();
}
catch (CharacterCodingException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
/**
* LogWriter that uses a local file for logging.
*
* @author andruid
* @author Zachary O. Toups (zach@ecologylab.net)
*/
protected class FileLogWriter extends LogWriter
{
BufferedWriter bufferedWriter;
FileLogWriter(File logFile, BufferedWriter bufferedWriter) throws IOException
{
if (bufferedWriter == null)
throw new IOException("Can't log to File with null buffereredWriter.");
this.bufferedWriter = bufferedWriter;
Logging.this.debugA("Logging to " + logFile + " " + bufferedWriter);
}
/**
* Write the opsBuffer to a file.
*/
@Override
void writeLogMessage(StringBuilder buffy)
{
try
{
bufferedWriter.append(buffy);
}
catch (IOException e)
{
e.printStackTrace();
}
}
@Override
void close()
{
try
{
bufferedWriter.close();
}
catch (IOException e)
{
e.printStackTrace();
}
bufferedWriter = null;
}
}
/**
* LogWriter that connects to the ServicesServer over the network for logging.
*
* @author andruid
* @author Zachary O. Toups (zach@ecologylab.net)
*/
protected class NetworkLogWriter extends LogWriter
{
NIOClient loggingClient;
/** Object for sending a batch of ops to the LoggingServer. */
final LogOps logOps;
/**
* The owner of this Logging object, used to ensure that, during shutdown, this object blocks
* until it is finished sending; needed to determine the current status.
*/
final Logging loggingParent;
NetworkLogWriter(NIOClient loggingClient, Logging loggingParent) throws IOException
{
if (loggingClient == null)
throw new IOException("Can't log to Network with null loggingClient.");
this.loggingClient = loggingClient;
Logging.this.debug("Logging to service via connection: " + loggingClient);
// logOps = new LogOps(maxBufferSizeToWrite);
logOps = new LogOps();
this.loggingParent = loggingParent;
}
@Override
public void setPriority(int priority)
{
NIOClient loggingClient = this.loggingClient;
if (loggingClient != null)
loggingClient.setPriority(priority);
}
/**
* Writes the given message to the logging server. Recursively calls itself if the message is
* too large to send at once.
*/
@Override
void writeLogMessage(LogEvent message)
{
try
{
if (!this.loggingParent.finished)
{
debug(">> sending a normal log message (non-blocking): ");
try
{
debug(SimplTypesScope.serialize(message, StringFormat.XML).toString());
}
catch (SIMPLTranslationException e)
{
e.printStackTrace();
}
loggingClient.nonBlockingSendMessage(message);
}
else
{
// finishing, wait 50 seconds
int timeOutMillis = 50000;
debug(">> network logging finishing, waiting up to " + (timeOutMillis / 1000)
+ " seconds...");
ResponseMessage rm = loggingClient.sendMessage(message, timeOutMillis);
if (rm == null)
debug("!!! gave up waiting for server response after " + (timeOutMillis / 1000)
+ " seconds.");
else
debug(">>> final log ops sent successfully!");
}
}
catch (IOException e)
{
e.printStackTrace();
}
catch (MessageTooLargeException e)
{
StringBuilder bufferToLog = message.bufferToLog();
int half = bufferToLog.length() / 2;
warning("!!!! attempted to send a message that was too large, splitting in half and trying again recursively ("
+ bufferToLog.length() + "/2 == " + half + ")");
LogOps firstHalf = new LogOps();
firstHalf.setBuffer(new StringBuilder(bufferToLog.subSequence(0, half)));
LogOps secondHalf = new LogOps();
secondHalf.setBuffer(new StringBuilder(bufferToLog.subSequence(half, bufferToLog.length())));
this.writeLogMessage(firstHalf);
this.writeLogMessage(secondHalf);
// if (message instanceof SendPrologue)
// { // if this is a send prologue, send prologue has to happen first
// message.setBuffer(new StringBuilder(bufferToLog.subSequence(0, half)));
// this.writeLogMessage(message);
// }
// else
// {
// this.writeBufferedOps(new StringBuilder(bufferToLog.subSequence(0, half)));
// }
//
// if (message instanceof SendEpilogue)
// { // if this is a send epilogue, send epilogue has to happen last
// message.setBuffer(new StringBuilder(bufferToLog.subSequence(half,
// bufferToLog.length())));
// this.writeLogMessage(message);
// }
// else
// {
// this.writeBufferedOps(new StringBuilder(bufferToLog.subSequence(half, bufferToLog
// .length())));
// }
}
}
@Override
void writeBufferedOps(StringBuilder buffy)
{
logOps.setBuffer(buffy);
writeLogMessage(logOps);
logOps.clear();
buffy.setLength(0);
}
/**
* Close the connection to the loggingServer.
*/
@Override
void close()
{
debug("close.");
loggingClient.disconnect();
loggingClient = null;
}
@Override
void writeLogMessage(StringBuilder xmlBuffy)
{
throw new UnsupportedOperationException();
}
}
/**
* @return the opSequence
*/
public ArrayList<T> getOpSequence()
{
return opSequence;
}
/**
* @return the running
*/
public boolean isRunning()
{
return running;
}
}