/** * This file is part of the source code and related artifacts for eGym Application. * * Copyright © 2013 eGym GmbH */ package de.egym.logqueue; import java.util.List; import net.jcip.annotations.ThreadSafe; import org.joda.time.DateTime; import com.google.inject.Inject; import com.google.inject.Singleton; @Singleton @ThreadSafe class EgymLogQueueImpl implements EgymLogQueue { /** Keeps track of the per-thread request log builders. This is possible because each thread processes only one request at a time. */ private static final ThreadLocal<EgymLogRequestRecordBuilder> threadRequestLogRecordBuilder = new ThreadLocal<EgymLogRequestRecordBuilder>(); /** All configured logging pipelines. */ private final List<EgymLogPipeline> pipelines; @Inject EgymLogQueueImpl(final EgymLogPipelineService pipelineService) { this.pipelines = pipelineService.createPipelines(); pipelineSanityCheck(); } @Override public void startRequest() { final EgymLogRequestRecordBuilder requestDescriptor = new EgymLogRequestRecordBuilder(DateTime.now()); threadRequestLogRecordBuilder.set(requestDescriptor); } @Override public void endRequest() { final EgymLogRequestRecordBuilder requestRecordBuilder = threadRequestLogRecordBuilder.get(); if (requestRecordBuilder == null) { throw new IllegalStateException("No active request. You need to call startRequest() first."); } try { final EgymLogRequestRecord requestLogRecord = requestRecordBuilder.build(); flush(requestLogRecord); threadRequestLogRecordBuilder.remove(); } catch (Exception e) { handleInternalLoggingFailure(e); } } @Override public void log(EgymLogRecord logRecord) { try { logInternal(logRecord); } catch (Exception e) { handleInternalLoggingFailure(e); } } /** * Handles logging internally by appending the record to the currently active request record builder or by delegating it to the * no-request path. * * @param logRecord * the record to log. Must not be null. */ private void logInternal(EgymLogRecord logRecord) { if (logRecord == null) { throw new IllegalArgumentException("logRecord must not be null"); } final EgymLogRequestRecordBuilder requestRecordBuilder = threadRequestLogRecordBuilder.get(); if (requestRecordBuilder == null) { // Print directly if not in a request. logWithoutRequest(logRecord); } else { // Otherwise the log record is added to the request specific queue. requestRecordBuilder.addLogRecord(logRecord); } } /** * Logs a record without having a request context. * * @param logRecord * the record to log. Must not be null. */ private void logWithoutRequest(EgymLogRecord logRecord) { if (logRecord == null) { throw new IllegalArgumentException("logRecord must not be null"); } // Ignore it if log level is too low. if (!EgymLogLevels.isSufficientLogLevel(logRecord.getLogLevel(), EgymLogLevels.getThresholdDefault())) { return; } final EgymLogRequestRecordBuilder requestRecordBuilder = new EgymLogRequestRecordBuilder(logRecord.getTimestamp()); requestRecordBuilder.addLogRecord(logRecord); flush(requestRecordBuilder.build()); } /** * Flushes the request record by sending it into the pipelines. * * @param requestRecord * the record to flush. Must not be null. */ private void flush(EgymLogRequestRecord requestRecord) { if (requestRecord == null) { throw new IllegalArgumentException("requestRecord must not be null"); } for (EgymLogPipeline pipeline : pipelines) { pipeline.log(requestRecord); } } /** * Worst-case scenario: An exception occurs while logging. All we can do now is to fall back to stderr to avoid any further issues. */ private void handleInternalLoggingFailure(Exception e) { if (e == null) { return; } try { e.printStackTrace(); } catch (Throwable t) { // If our worst case exception reporting fails with an exception there's not much we can do about it... } } /** * Verifies that there is at least one pipeline and prints a warning if this condition is not met. */ private void pipelineSanityCheck() { if (pipelines == null || pipelines.isEmpty()) { System.err.println("WARNING: You do not have any log pipelines configures. Logging will not work correctly."); } for (EgymLogPipeline pipeline : pipelines) { if (pipeline == null) { throw new IllegalStateException("pipelines must not contain null entries"); } } } }