| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache license, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the license for the specific language governing permissions and |
| * limitations under the license. |
| */ |
| package org.apache.logging.log4j.core.appender; |
| |
| import java.lang.reflect.Field; |
| import java.util.Map; |
| |
| import org.apache.logging.log4j.Level; |
| import org.apache.logging.log4j.LogManager; |
| |
| import org.apache.logging.log4j.core.Logger; |
| import org.apache.logging.log4j.core.LoggerContext; |
| import org.apache.logging.log4j.core.appender.rolling.DirectWriteRolloverStrategy; |
| import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy; |
| import org.apache.logging.log4j.core.config.Configurator; |
| import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; |
| import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; |
| import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; |
| import org.apache.logging.log4j.core.layout.PatternLayout; |
| import org.apache.logging.log4j.plugins.util.Builder; |
| import org.junit.Test; |
| |
| /** |
| * Class Description goes here. |
| * Created by rgoers on 2019-02-02 |
| */ |
| public class ReconfigureAppenderTest { |
| private RollingFileAppender appender; |
| |
| @Test |
| public void addAndRemoveAppenderTest() |
| { |
| // this will create a rolling file appender and add it to the logger |
| // of this class. The file manager is created for the first time. |
| // see AbstractManager.getManager(...). |
| this.createAndAddAppender(); |
| |
| // let's write something to the logger to ensure the output stream is opened. |
| // We expect this call to create a a new output stream (which is does). |
| // see OutputStreamManager.writeToDestination(...). |
| Logger logger = (Logger)LogManager.getLogger(this.getClass()); |
| logger.info("test message 1"); |
| |
| // this will close the rolling file appender and remove it from the logger |
| // of this class. We expect the file output stream to be closed (which is it) |
| // however the FileManager instance is kept in AbstractManager.MAP. This means that |
| // when we create a new rolling file appender with the DirectWriteRolloverStrategy |
| // this OLD file manager will be retrieved from the map (since it has the SAME file pattern) |
| // and this is a problem as the output stream on that file manager is CLOSED. The problem |
| // here is that we attempt to remove a file manager call NULL instead of FILE PATTERN |
| this.removeAppender(); |
| |
| // create a new rolling file appender for this logger. We expect this to create a new file |
| // manager as the old one should have been removed. Since the instance of this file manager |
| // is still in AbstractManager.MAP, it is returned and assigned to our new rolling file |
| // appender instance. The problem here is that the file manager is create with the name |
| // FILE PATTERN and that its output stream is closed. |
| this.createAndAddAppender(); |
| |
| // try and log something. This will not be logged anywhere. An exception will be thrown: |
| // Caused by: java.io.IOException: Stream Closed |
| logger.info("test message 2"); |
| |
| // remove the appender as before. |
| this.removeAppender(); |
| |
| // this method will use reflection to go and remove the instance of FileManager from the AbstractManager.MAP |
| // ourselves. This means that the rolling file appender has been stopped (previous method) AND its |
| // manager has been removed. |
| this.removeManagerUsingReflection(); |
| |
| // now that the instance of FileManager is not present in MAP, creating the appender will actually |
| // create a new rolling file manager, and put this in the map (keyed on file pattern again). |
| this.createAndAddAppender(); |
| |
| // because we have a new instance of file manager, this will create a new output stream. We can verify |
| // this by looking inside the filepattern.1.log file inside the working directory, and noticing that |
| // we have 'test message 1' followed by 'test message 3'. 'test message 2' is missing because we attempted |
| // to write while the output stream was closed. |
| logger.info("test message 3"); |
| |
| // possible fixes: |
| // 1) create the RollingFileManager and set it's name to FILE PATTERN when using DirectWriteRolloverStrategy |
| // 2) when stopping the appender (and thus the manager), remove on FILE PATTERN if DirectWriteRolloverStrategy |
| // 3) on OutputStreamManager.getOutputStream(), determine if the output stream is closed, and if it is create |
| // a new one. Note that this isn't really desirable as the only fix as if the file pattern had to change |
| // an instance of file manager would still exist in MAP, causing a resource leak. |
| |
| // now the obvious problem here is that if multiple file appenders use the same rolling file manager. We may run |
| // into a case where the file manager is removed and the output stream is closed, and the remaining appenders |
| // may not work correctly. I'm not sure of the use case in this scenario, and if people actually do this |
| // but based on the code it would be possible. I have also not tested this scenario out as it is not the |
| // scenario we would ever use, but it should be considered while fixing this issue. |
| } |
| private void removeManagerUsingReflection() |
| { |
| try |
| { |
| Field field = AbstractManager.class.getDeclaredField("MAP"); |
| field.setAccessible(true); |
| |
| // Retrieve the map itself. |
| Map<String, AbstractManager> map = |
| (Map<String, AbstractManager>)field.get(null); |
| |
| // Remove the file manager keyed on file pattern. |
| map.remove(appender.getFilePattern()); |
| } |
| catch (Exception e) |
| { |
| e.printStackTrace(); |
| } |
| } |
| |
| private void removeAppender() |
| { |
| Logger logger = (Logger)LogManager.getLogger(this.getClass()); |
| |
| // This call attempts to remove the file manager, but uses the name of the appender |
| // (NULL in this case) instead of PATTERN. |
| // see AbstractManager.stop(...). |
| appender.stop(); |
| logger.removeAppender(appender); |
| } |
| |
| private void createAndAddAppender() |
| { |
| ConfigurationBuilder<BuiltConfiguration> config_builder = |
| ConfigurationBuilderFactory.newConfigurationBuilder(); |
| |
| // All loggers must have a root logger. The default root logger logs ERRORs to the console. |
| // Override this with a root logger that does not log anywhere as we leave it up the the |
| // appenders on the logger. |
| config_builder.add(config_builder.newRootLogger(Level.INFO)); |
| |
| // Initialise the logger context. |
| LoggerContext logger_context = |
| Configurator.initialize(config_builder.build()); |
| |
| // Retrieve the logger. |
| Logger logger = (Logger) LogManager.getLogger(this.getClass()); |
| |
| Builder pattern_builder = PatternLayout.newBuilder().setPattern( |
| "[%d{dd-MM-yy HH:mm:ss}] %p %m %throwable %n"); |
| |
| PatternLayout pattern_layout = (PatternLayout) pattern_builder.build(); |
| |
| appender = RollingFileAppender |
| .newBuilder() |
| .setLayout(pattern_layout) |
| .setName("rollingfileappender") |
| .setFilePattern("target/filepattern.%i.log") |
| .setPolicy(SizeBasedTriggeringPolicy.createPolicy("5 MB")) |
| .setAppend(true) |
| .setStrategy( |
| DirectWriteRolloverStrategy |
| .newBuilder() |
| .setConfig(logger_context.getConfiguration()) |
| .setMaxFiles("5") |
| .build()) |
| .setConfiguration(logger_context.getConfiguration()) |
| .build(); |
| |
| appender.start(); |
| |
| logger.addAppender(appender); |
| } |
| } |