package com.equalexperts.logging.impl; import com.equalexperts.logging.DiagnosticContextSupplier; import com.equalexperts.logging.LogMessage; import java.io.IOException; import java.io.PrintStream; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.util.Optional; import java.util.function.Consumer; /** * Constructs the various non-trivial dependencies that OpsLogger implementations need. */ public class InfrastructureFactory { public static final Consumer<Throwable> DEFAULT_ERROR_HANDLER = (error) -> error.printStackTrace(System.err); public static final DiagnosticContextSupplier EMPTY_CONTEXT_SUPPLIER = Collections::emptyMap; private final Optional<Path> logfilePath; private final Optional<PrintStream> loggerOutput; private final Optional<Boolean> storeStackTracesInFilesystem; private final Optional<Path> stackTraceStoragePath; private final Optional<DiagnosticContextSupplier> correlationIdSupplier; private final Optional<Consumer<Throwable>> errorHandler; public InfrastructureFactory(Optional<Path> logfilePath, Optional<PrintStream> loggerOutput, Optional<Boolean> storeStackTracesInFilesystem, Optional<Path> stackTraceStoragePath, Optional<DiagnosticContextSupplier> correlationIdSupplier, Optional<Consumer<Throwable>> errorHandler) { this.logfilePath = logfilePath; this.loggerOutput = loggerOutput; this.storeStackTracesInFilesystem = storeStackTracesInFilesystem; this.stackTraceStoragePath = stackTraceStoragePath; this.correlationIdSupplier = correlationIdSupplier; this.errorHandler = errorHandler; } public <T extends Enum<T> & LogMessage> Destination<T> configureDestination() throws UncheckedIOException { try { StackTraceProcessor stackTraceProcessor = this.configureStackTraceProcessor(); if (logfilePath.isPresent()) { if (!Files.isSymbolicLink(logfilePath.get().getParent())) { Files.createDirectories(logfilePath.get().getParent()); } FileChannelProvider provider = new FileChannelProvider(logfilePath.get()); ActiveRotationRegistry registry = ActiveRotationRegistry.getSingletonInstance(); return registry.add(new PathDestination<>(provider, stackTraceProcessor, registry)); } return new OutputStreamDestination<>(loggerOutput.orElse(System.out), stackTraceProcessor); } catch (IOException e) { throw new UncheckedIOException(e); } } public Consumer<Throwable> configureErrorHandler() { return errorHandler.orElse(DEFAULT_ERROR_HANDLER); } public DiagnosticContextSupplier configureContextSupplier() { return correlationIdSupplier.orElse(EMPTY_CONTEXT_SUPPLIER); } private StackTraceProcessor configureStackTraceProcessor() throws IOException { Optional<Path> storagePath = this.determineStackTraceProcessorPath(); if (storagePath.isPresent()) { if (!Files.isSymbolicLink(storagePath.get())) { Files.createDirectories(storagePath.get()); } return new FilesystemStackTraceProcessor(storagePath.get(), new ThrowableFingerprintCalculator()); } return new SimpleStackTraceProcessor(); } private Optional<Path> determineStackTraceProcessorPath() { if (storeStackTracesInFilesystem.isPresent()) { //storing stack traces in the filesystem has been explicitly configured if (!storeStackTracesInFilesystem.get()) { return Optional.empty(); //explicitly disabled } if (stackTraceStoragePath.isPresent()) { //use the explicitly provided location when one is set return stackTraceStoragePath; } if (!logfilePath.isPresent()) { throw new IllegalStateException("Cannot store stack traces in the filesystem without providing a path"); } } //No explicit path provided. Store stack traces in the same directory as the log file, if one is specified. return logfilePath.map(Path::getParent); } //region test hooks: allow tests to determine the values passed into the constructor public Optional<Path> getLogfilePath() { return logfilePath; } public Optional<PrintStream> getLoggerOutput() { return loggerOutput; } public Optional<Boolean> getStoreStackTracesInFilesystem() { return storeStackTracesInFilesystem; } public Optional<Path> getStackTraceStoragePath() { return stackTraceStoragePath; } public Optional<DiagnosticContextSupplier> getContextSupplier() { return correlationIdSupplier; } public Optional<Consumer<Throwable>> getErrorHandler() { return errorHandler; } //endregion }