/******************************************************************************* * Copyright (c) 2016 Sebastian Stenzel and others. * This file is licensed under the terms of the MIT license. * See the LICENSE.txt file for more info. * * Contributors: * Sebastian Stenzel - initial API and implementation *******************************************************************************/ package org.cryptomator.logging; import java.io.IOException; import java.io.Serializable; import java.net.URISyntaxException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.regex.Pattern; import org.apache.commons.lang3.SystemUtils; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender; import org.apache.logging.log4j.core.appender.FileManager; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; import org.apache.logging.log4j.util.Strings; /** * A preconfigured FileAppender only relying on a configurable system property, e.g. <code>-Dcryptomator.logPath=/var/log/cryptomator.log</code>.<br/> * Other than the normal {@link org.apache.logging.log4j.core.appender.FileAppender} paths can be resolved relative to the users home directory. */ @Plugin(name = ConfigurableFileAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) public class ConfigurableFileAppender extends AbstractOutputStreamAppender<FileManager> { static final String PLUGIN_NAME = "ConfigurableFile"; private static final Pattern DRIVE_LETTER_WITH_PRECEEDING_SLASH = Pattern.compile("^/[A-Z]:", Pattern.CASE_INSENSITIVE); private ConfigurableFileAppender(String name, Layout<? extends Serializable> layout, Filter filter, boolean ignoreExceptions, boolean immediateFlush, FileManager manager) { super(name, layout, filter, ignoreExceptions, immediateFlush, manager); LOGGER.info("Logging to " + manager.getFileName()); } @PluginBuilderFactory public static <B extends Builder<B>> B newBuilder() { return new Builder<B>().asBuilder(); } /** * Builds ConfigurableFileAppender instances. * * @param <B> * The type to build */ public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B> // implements org.apache.logging.log4j.core.util.Builder<ConfigurableFileAppender> { @Required(message = "No system property name containing the log file path provided.") @PluginBuilderAttribute("pathPropertyName") private String pathPropertyName; @PluginBuilderAttribute private boolean append = true; @Override public ConfigurableFileAppender build() { final String pathProperty = System.getProperty(pathPropertyName); if (Strings.isEmpty(pathProperty)) { LOGGER.warn("No log file location provided in system property \"" + pathPropertyName + "\""); return null; } final Path filePath = parsePath(pathProperty); if (filePath == null) { LOGGER.warn("Invalid path \"" + pathProperty + "\""); return null; } if (!Files.exists(filePath.getParent())) { try { Files.createDirectories(filePath.getParent()); } catch (IOException e) { LOGGER.error("Could not create parent directories for log file located at " + filePath.toString(), e); return null; } } FileManager manager = FileManager.getFileManager(filePath.toString(), append, false, isBufferedIo(), true, null, getOrCreateLayout(), getBufferSize(), getConfiguration()); return new ConfigurableFileAppender(getName(), getLayout(), getFilter(), isIgnoreExceptions(), isImmediateFlush(), manager); } public B withPathPropertyName(String pathPropertyName) { this.pathPropertyName = pathPropertyName; return asBuilder(); } public B withAppend(boolean append) { this.append = append; return asBuilder(); } } private static Path parsePath(String path) { if (path.startsWith("~/")) { // home-dir-relative Path: final Path userHome = FileSystems.getDefault().getPath(SystemUtils.USER_HOME); return userHome.resolve(path.substring(2)); } else if (path.startsWith("/")) { // absolute Path: return FileSystems.getDefault().getPath(path); } else { // relative Path: try { String jarFileLocation = ConfigurableFileAppender.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath(); if (SystemUtils.IS_OS_WINDOWS && DRIVE_LETTER_WITH_PRECEEDING_SLASH.matcher(jarFileLocation).find()) { // on windows we need to remove a preceeding slash from "/C:/foo/bar": jarFileLocation = jarFileLocation.substring(1); } final Path workingDir = FileSystems.getDefault().getPath(jarFileLocation).getParent(); return workingDir.resolve(path); } catch (URISyntaxException e) { LOGGER.error("Unable to resolve working directory ", e); return null; } } } }