/** * Copyright (c) 2002-2015 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.wrapper; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; import java.util.logging.LogManager; /** * The logging service is mainly responsible for doing two things: * <ul> * <li> 1) Reset the value of property {@code java.util.logging.FileHandler.pattern} (which is defined in * {@code windows-wrapper-logging.properties} file) to an absolute path if it is given as a relative path. * The absolute path uses our default working directory {@code %NEO4J_HOME%} as its parent directory. * <li> 2) Create directories if they do not exist before for the path specified by * {@code java.util.logging.FileHandler.pattern}. * </ul> * * The property {@code java.util.logging.FileHandler.pattern} defines where the windows-wrapper logs are stored. * If it is given in a relative path, the {@code java.util.logging} will use the place where the program runs as * the parent path. However by default, we assume that the parent directory should always be our working directory * {@code %NEO4J_HOME%}. Therefore we need to reset the relative path to a full path to avoid logs being created in * an unexpected place. * * Besides we also need to create missing directories on this path to avoid * {@link java.util.logging.FileHandler#openFiles IO exceptions}. * <p> * The value of property {@code java.util.logging.FileHandler.pattern} uses a special pattern format. More info * about the pattern format could be found in {@link java.util.logging.FileHandler FileHandler} */ public class LoggingService { static final String LOGGING_CONFIG_FILE_KEY = "java.util.logging.config.file"; static final String LOGGING_FILE_NAME_PATTERN_KEY = "java.util.logging.FileHandler.pattern"; private static final String DEFAULT_LOGGING_CONFIG_FILE_PATH = getDefaultLoggingConfigFilePath(); private String winWrapperLogNamePattern; public LoggingService() { } // only for testing LoggingService( String pattern ) { this.winWrapperLogNamePattern = pattern; } public void initLogger() throws IOException { LogManager logManager = LogManager.getLogManager(); winWrapperLogNamePattern = logManager.getProperty( LOGGING_FILE_NAME_PATTERN_KEY ); // Reset the pattern property if is relative if ( namePatternIsRelative() ) { resetLogNamePatternProperty( logManager ); } // Retrieve the logging directory from the pattern property and create the directory if it does not exist. File logDir = getLogDir(); if ( !logDir.exists() ) { logDir.mkdirs(); } } private boolean namePatternIsRelative() { boolean isInTempDir = winWrapperLogNamePattern.startsWith( "%h" ); boolean isInUserHomeDir = winWrapperLogNamePattern.startsWith( "%t" ); boolean isRelative = !new File( winWrapperLogNamePattern ).isAbsolute(); return !isInTempDir && !isInUserHomeDir && isRelative; } /** * Change the value of property {@code java.util.logging.FileHandler.pattern} to use {@code %NEO4J_HOME%} as parent * directory and call {@link java.util.logging.LogManager LogManager} to reload the change. * <p> * By invoking this method, we ensure no matter where a user starts NEO4J, we will always use {@code %NEO4J_HOME%} * as parent directory for windows-wrapper logs. Otherwise, we will use the directory where the program * starts as the parent directory when creating windows-wrapper logs. * @param logManager * @throws IOException if we failed to read from the windows-wrapper property file or update configuration * properties for {@link java.util.logging.LogManager LogManager} */ void resetLogNamePatternProperty( LogManager logManager ) throws IOException { // Load all the properties from configuration file String logConfigFileName = System.getProperty( LOGGING_CONFIG_FILE_KEY, DEFAULT_LOGGING_CONFIG_FILE_PATH ); try ( FileInputStream logConfigIn = new FileInputStream( new File( logConfigFileName ) ) ) { Properties logProperties = new Properties(); logProperties.load( logConfigIn ); // Reset the logFileNamePattern to use %NEO4J_HOME% as its parent folder String workingDir = System.getProperty( ServerProcess.WorkingDir ); workingDir = workingDir.replaceAll( "%", "%%" ); // translate to the format used by FileHandler winWrapperLogNamePattern = new File( workingDir, winWrapperLogNamePattern ).getAbsolutePath(); // Reload the new change logProperties.setProperty( LOGGING_FILE_NAME_PATTERN_KEY, winWrapperLogNamePattern ); ByteArrayOutputStream internalOut = new ByteArrayOutputStream(); logProperties.store( internalOut, null ); logManager.readConfiguration( new ByteArrayInputStream( internalOut.toByteArray() ) ); } } /** * This method returns the directory in which the windows-wrapper logs will be generated. The path to this directory * could be retrieved from the property {@code java.util.logging.FileHandler.pattern}. However the property * utilizes a special path pattern. * The pattern consists of some special components that will be replaced at runtime by * {@link java.util.logging.FileHandler FileHandler}: * <ul> * <li> "/" the local pathname separator * <li> "%t" the system temporary directory * <li> "%h" the value of the "user.home" system property * <li> "%g" the generation number to distinguish rotated logs * <li> "%u" a unique number to resolve conflicts * <li> "%%" translates to a single percent sign "%" * </ul> * * This method will throw {@link java.io.IOException IOException} if the directory path contains components * in wrong positions: * * <ul> * <li> having "%t" or "%h" in the middle of the directory path * <li> having "%g" or "%u" in the directory path * </ul> **/ File getLogDir() throws IOException { String logDirNamePattern = new File( winWrapperLogNamePattern ).getParent(); String logDirParentDirName = null; // if ( logDirNamePattern.startsWith( "%t" ) ) { String tmpDirName = System.getProperty( "java.io.tmpdir" ); if ( tmpDirName == null ) { tmpDirName = System.getProperty( "user.home" ); } logDirParentDirName = tmpDirName; logDirNamePattern = logDirNamePattern.substring( 2 ); } else if ( logDirNamePattern.startsWith( "%h" ) ) { logDirParentDirName = System.getProperty( "user.home" ); logDirNamePattern = logDirNamePattern.substring( 2 ); } boolean translationSymbolFound = false; for ( int i = 0; i < logDirNamePattern.length(); i++ ) { char ch = logDirNamePattern.charAt( i ); if ( ch == '%' ) { char nextCh = logDirNamePattern.charAt( i + 1 ); switch ( nextCh ) { case '%': i++; translationSymbolFound = true; break; case 'u': case 'g': throw new IOException( "Cannot make directories automatically for directory paths containing %u or %g: " + new File( winWrapperLogNamePattern ).getParent() + "; Please change \"" + LOGGING_FILE_NAME_PATTERN_KEY + "=" + winWrapperLogNamePattern + "\"" ); case 't': case 'h': throw new IOException( "Cannot understand %t or %h in the middle of a path: " + winWrapperLogNamePattern + "; Please change \"" + LOGGING_FILE_NAME_PATTERN_KEY + "=" + winWrapperLogNamePattern + "\"" ); } } } if ( translationSymbolFound ) { logDirNamePattern = logDirNamePattern.replaceAll( "%%", "%" ); } return new File( logDirParentDirName, logDirNamePattern ); } private static String getDefaultLoggingConfigFilePath() { //Default: JDK_HOME/jre/lib/logging.properties String javaHome = System.getProperty( "java.home" ); return new File( javaHome, "/lib/logging.properties" ).getAbsolutePath(); } }