blob: 01c58698324edea06b6986d99c8af8e56cde18b4 [file] [log] [blame]
/*
* 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);
}
}