package cz.cuni.mff.d3s.been.logging;
import static java.util.concurrent.TimeUnit.SECONDS;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import com.hazelcast.core.IQueue;
import com.hazelcast.core.RuntimeInterruptedException;
import cz.cuni.mff.d3s.been.BeenPackageIdentifier;
import cz.cuni.mff.d3s.been.cluster.IClusterService;
import cz.cuni.mff.d3s.been.cluster.Names;
import cz.cuni.mff.d3s.been.cluster.Reaper;
import cz.cuni.mff.d3s.been.cluster.ServiceException;
import cz.cuni.mff.d3s.been.cluster.context.ClusterContext;
import cz.cuni.mff.d3s.been.core.persistence.Entities;
import cz.cuni.mff.d3s.been.core.persistence.EntityCarrier;
import cz.cuni.mff.d3s.been.util.JSONUtils;
import cz.cuni.mff.d3s.been.util.JsonException;
/**
* A persistence hook for service log messages
*/
public class ServiceLogPersister implements IClusterService {
volatile private IQueue<EntityCarrier> logPersistence;
private BlockingQueue<LogMessage> logs = new LinkedBlockingQueue<>();
private ClusterContext ctx;
private String beenId;
private String hostRuntimeId;
private ServiceLogPersisterThread persisterThread;
/**
* Create a BEEN service log persister. Once activated, this persister will
* hook up to {@link PersistentServiceLogHandler} instances, making them dump
* logs to the persistence layer.
*/
ServiceLogPersister() {}
/**
* Get the static instance hooked up to service log handlers. Needs to be
* activated with a cluster context to start working.
*
* @param hostRuntimeId
* ID of the runtime that's doing the logging. Should be
* <code>null</code> if there is no host runtime running in this
* instance
* @param beenId
* ID of this BEEN instance
* @param ctx Cluster context of this BEEN instance
*
* @return The log persister
*/
public static ServiceLogPersister getHandlerInstance(ClusterContext ctx, String beenId, String hostRuntimeId) {
final ServiceLogPersister persister = PersistentServiceLogHandler.persister;
persister.ctx = ctx;
persister.beenId = beenId;
persister.hostRuntimeId = hostRuntimeId;
return persister;
}
/**
* Log a message
*
* @param msg
* message to log
*/
void log(LogMessage msg) {
logs.add(msg);
}
@Override
public void start() throws ServiceException {
this.logPersistence = ctx.getQueue(Names.PERSISTENCE_QUEUE_NAME);
this.persisterThread = new ServiceLogPersisterThread(logPersistence, logs, beenId, hostRuntimeId);
this.persisterThread.start();
}
@Override
public void stop() {
logPersistence = null;
ctx = null;
hostRuntimeId = null;
beenId = null;
persisterThread.setStop();
try {
persisterThread.interrupt();
persisterThread.join(TimeUnit.SECONDS.toMillis(30));
} catch (InterruptedException e) {
// not much we can do at this point
e.printStackTrace();
}
}
@Override
public Reaper createReaper() {
return new Reaper() {
@Override
protected void reap() throws InterruptedException {
ServiceLogPersister.this.stop();
}
};
}
private static class ServiceLogPersisterThread extends Thread {
private final IQueue<EntityCarrier> logPersistence;
private final BlockingQueue<LogMessage> logs;
private final String beenId;
private final String hostRuntimeId;
private volatile boolean run = true;
private final JSONUtils jsonUtils = JSONUtils.newInstance();
ServiceLogPersisterThread(
final IQueue<EntityCarrier> logPersistence,
final BlockingQueue<LogMessage> logs,
String beenId,
String hostRuntimeId) {
this.logPersistence = logPersistence;
this.logs = logs;
this.beenId = beenId;
this.hostRuntimeId = hostRuntimeId;
}
void setStop() {
this.run = false;
}
@Override
public void run() {
LogMessage polledMsg;
Collection<LogMessage> drain = new ArrayList<>();
while (run && !Thread.currentThread().isInterrupted()) {
try {
polledMsg = logs.poll(30, SECONDS);
} catch (InterruptedException e) {
break;
}
if (polledMsg == null) {
continue;
}
persist(polledMsg);
logs.drainTo(drain);
for (LogMessage logMessage : drain) {
persist(logMessage);
}
drain.clear();
}
// drain what we can
logs.drainTo(drain);
for (LogMessage logMessage : drain) {
persist(logMessage);
}
}
private void persist(final LogMessage polledMsg) {
final ServiceLogMessage serviceMessage = new ServiceLogMessage().withMessage(polledMsg).withHostRuntimeId(
hostRuntimeId).withBeenId(beenId).withServiceName(extractServiceName(polledMsg.getName()));
try {
logPersistence.put(new EntityCarrier().withId(Entities.LOG_SERVICE.getId()).withData(
jsonUtils.serialize(serviceMessage)));
} catch (InterruptedException | RuntimeInterruptedException e) {
System.err.println(String.format(
"Cannot log following message to cluster: threads handling distributed data structures were unexpectedly interrupted.\n%s",
polledMsg.toString()));
} catch (JsonException e) {
System.err.println(String.format("Cannot serialize following message: %s", polledMsg.toString()));
}
}
private String extractServiceName(String loggerName) {
final String BEEN_PREFIX = BeenPackageIdentifier.class.getPackage().getName();
if (loggerName.startsWith(BEEN_PREFIX)) {
return loggerName.substring(BEEN_PREFIX.length(), loggerName.indexOf('.', BEEN_PREFIX.length()));
} else {
return loggerName.substring(0, loggerName.lastIndexOf('.'));
}
}
}
}