IGNITE-6946: Change Ignite Logger configuration on the fly. Fixes #3400.
authorStanislav Lukyanov <stanlukyanov@gmail.com>
Wed, 31 Jan 2018 21:02:45 +0000 (13:02 -0800)
committerValentin Kulichenko <valentin.kulichenko@gmail.com>
Wed, 31 Jan 2018 21:02:45 +0000 (13:02 -0800)
Signed-off-by: Valentin Kulichenko <valentin.kulichenko@gmail.com>
config/ignite-log4j2.xml
modules/log4j/src/main/java/org/apache/ignite/logger/log4j/Log4JLogger.java
modules/log4j/src/test/config/log4j-debug.xml [new file with mode: 0644]
modules/log4j/src/test/config/log4j-info.xml [new file with mode: 0644]
modules/log4j/src/test/java/org/apache/ignite/logger/log4j/GridLog4jConfigUpdateTest.java [new file with mode: 0644]
modules/log4j/src/test/java/org/apache/ignite/logger/log4j/GridLog4jWatchDelayTest.java [new file with mode: 0644]
modules/log4j/src/test/java/org/apache/ignite/testsuites/IgniteLog4jTestSuite.java
modules/log4j2/src/test/config/log4j2-debug.xml [new file with mode: 0644]
modules/log4j2/src/test/config/log4j2-info.xml [new file with mode: 0644]
modules/log4j2/src/test/java/org/apache/ignite/logger/log4j2/Log4j2ConfigUpdateTest.java [new file with mode: 0644]
modules/log4j2/src/test/java/org/apache/ignite/testsuites/IgniteLog4j2TestSuite.java

index 7154ae8..b55c563 100644 (file)
@@ -17,7 +17,7 @@
   limitations under the License.
 -->
 
-<Configuration>
+<Configuration monitorInterval="60">
     <Appenders>
         <Console name="CONSOLE" target="SYSTEM_OUT">
             <PatternLayout pattern="[%d{ISO8601}][%-5p][%t][%c{1}]%notEmpty{[%markerSimpleName]} %m%n"/>
index 02e7b35..ec0a5b3 100644 (file)
@@ -39,8 +39,10 @@ import org.apache.log4j.Category;
 import org.apache.log4j.ConsoleAppender;
 import org.apache.log4j.FileAppender;
 import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
 import org.apache.log4j.Logger;
 import org.apache.log4j.PatternLayout;
+import org.apache.log4j.helpers.FileWatchdog;
 import org.apache.log4j.varia.LevelRangeFilter;
 import org.apache.log4j.xml.DOMConfigurator;
 import org.jetbrains.annotations.Nullable;
@@ -184,25 +186,48 @@ public class Log4JLogger implements IgniteLogger, LoggerNodeIdAware, Log4jFileAw
 
     /**
      * Creates new logger with given configuration {@code path}.
+     * Calling this constructor is equivalent to calling {@code Log4JLogger(path, FileWatchdog.DEFAULT_DELAY}.
      *
      * @param path Path to log4j configuration XML file.
      * @throws IgniteCheckedException Thrown in case logger can't be created.
      */
     public Log4JLogger(final String path) throws IgniteCheckedException {
+        this(path, FileWatchdog.DEFAULT_DELAY);
+    }
+
+    /**
+     * Creates new logger with given configuration {@code path}.
+     * <p>
+     * If {@code watchDelay} is not zero, created logger will check the configuration file for changes once every
+     * {@code watchDelay} milliseconds, and update its configuration if the file was changed.
+     * See {@link DOMConfigurator#configureAndWatch(String, long)} for details.
+     *
+     * @param path Path to log4j configuration XML file.
+     * @param watchDelay delay in milliseconds used to check configuration file for changes.
+     * @throws IgniteCheckedException Thrown in case logger can't be created.
+     */
+    public Log4JLogger(final String path, long watchDelay) throws IgniteCheckedException {
         if (path == null)
             throw new IgniteCheckedException("Configuration XML file for Log4j must be specified.");
 
+        if (watchDelay < 0)
+            throw new IgniteCheckedException("watchDelay can't be negative: " + watchDelay);
+
         this.cfg = path;
 
-        final URL cfgUrl = U.resolveIgniteUrl(path);
+        final File cfgFile = U.resolveIgnitePath(path);
 
-        if (cfgUrl == null)
+        if (cfgFile == null)
             throw new IgniteCheckedException("Log4j configuration path was not found: " + path);
 
         addConsoleAppenderIfNeeded(null, new C1<Boolean, Logger>() {
             @Override public Logger apply(Boolean init) {
-                if (init)
-                    DOMConfigurator.configure(cfgUrl);
+                if (init) {
+                    if (watchDelay > 0)
+                        DOMConfigurator.configureAndWatch(cfgFile.getPath(), watchDelay);
+                    else
+                        DOMConfigurator.configure(cfgFile.getPath());
+                }
 
                 return Logger.getRootLogger();
             }
@@ -213,23 +238,46 @@ public class Log4JLogger implements IgniteLogger, LoggerNodeIdAware, Log4jFileAw
 
     /**
      * Creates new logger with given configuration {@code cfgFile}.
+     * Calling this constructor is equivalent to calling {@code Log4JLogger(cfgFile, FileWatchdog.DEFAULT_DELAY}.
      *
      * @param cfgFile Log4j configuration XML file.
      * @throws IgniteCheckedException Thrown in case logger can't be created.
      */
     public Log4JLogger(File cfgFile) throws IgniteCheckedException {
+        this(cfgFile, FileWatchdog.DEFAULT_DELAY);
+    }
+
+    /**
+     * Creates new logger with given configuration {@code cfgFile}.
+     * <p>
+     * If {@code watchDelay} is not zero, created logger will check the configuration file for changes once every
+     * {@code watchDelay} milliseconds, and update its configuration if the file was changed.
+     * See {@link DOMConfigurator#configureAndWatch(String, long)} for details.
+     *
+     * @param cfgFile Log4j configuration XML file.
+     * @param watchDelay delay in milliseconds used to check configuration file for changes.
+     * @throws IgniteCheckedException Thrown in case logger can't be created.
+     */
+    public Log4JLogger(final File cfgFile, final long watchDelay) throws IgniteCheckedException {
         if (cfgFile == null)
             throw new IgniteCheckedException("Configuration XML file for Log4j must be specified.");
 
         if (!cfgFile.exists() || cfgFile.isDirectory())
             throw new IgniteCheckedException("Log4j configuration path was not found or is a directory: " + cfgFile);
 
+        if (watchDelay < 0)
+            throw new IgniteCheckedException("watchDelay can't be negative: " + watchDelay);
+
         cfg = cfgFile.getAbsolutePath();
 
         addConsoleAppenderIfNeeded(null, new C1<Boolean, Logger>() {
             @Override public Logger apply(Boolean init) {
-                if (init)
-                    DOMConfigurator.configure(cfg);
+                if (init) {
+                    if (watchDelay > 0)
+                        DOMConfigurator.configureAndWatch(cfgFile.getPath(), watchDelay);
+                    else
+                        DOMConfigurator.configure(cfgFile.getPath());
+                }
 
                 return Logger.getRootLogger();
             }
@@ -240,20 +288,43 @@ public class Log4JLogger implements IgniteLogger, LoggerNodeIdAware, Log4jFileAw
 
     /**
      * Creates new logger with given configuration {@code cfgUrl}.
+     * Calling this constructor is equivalent to calling {@code Log4JLogger(cfgUrl, FileWatchdog.DEFAULT_DELAY}.
      *
      * @param cfgUrl URL for Log4j configuration XML file.
      * @throws IgniteCheckedException Thrown in case logger can't be created.
      */
     public Log4JLogger(final URL cfgUrl) throws IgniteCheckedException {
+        this(cfgUrl, FileWatchdog.DEFAULT_DELAY);
+    }
+
+    /**
+     * Creates new logger with given configuration {@code cfgUrl}.
+     * <p>
+     * If {@code watchDelay} is not zero, created logger will check the configuration file for changes once every
+     * {@code watchDelay} milliseconds, and update its configuration if the file was changed.
+     * See {@link DOMConfigurator#configureAndWatch(String, long)} for details.
+     *
+     * @param cfgUrl URL for Log4j configuration XML file.
+     * @param watchDelay delay in milliseconds used to check configuration file for changes.
+     * @throws IgniteCheckedException Thrown in case logger can't be created.
+     */
+    public Log4JLogger(final URL cfgUrl, final long watchDelay) throws IgniteCheckedException {
         if (cfgUrl == null)
             throw new IgniteCheckedException("Configuration XML file for Log4j must be specified.");
 
+        if (watchDelay < 0)
+            throw new IgniteCheckedException("watchDelay can't be negative: " + watchDelay);
+
         cfg = cfgUrl.getPath();
 
         addConsoleAppenderIfNeeded(null, new C1<Boolean, Logger>() {
             @Override public Logger apply(Boolean init) {
-                if (init)
-                    DOMConfigurator.configure(cfgUrl);
+                if (init) {
+                    if (watchDelay > 0)
+                        DOMConfigurator.configureAndWatch(cfg, watchDelay);
+                    else
+                        DOMConfigurator.configure(cfg);
+                }
 
                 return Logger.getRootLogger();
             }
@@ -552,4 +623,17 @@ public class Log4JLogger implements IgniteLogger, LoggerNodeIdAware, Log4jFileAw
             }
         }
     }
+
+    /**
+     * Cleans up the logger configuration. Should be used in unit tests only for sequential tests run with
+     * different configurations
+     */
+    static void cleanup() {
+        synchronized (mux) {
+            if (inited)
+                LogManager.shutdown();
+
+            inited = false;
+        }
+    }
 }
diff --git a/modules/log4j/src/test/config/log4j-debug.xml b/modules/log4j/src/test/config/log4j-debug.xml
new file mode 100644 (file)
index 0000000..2c36194
--- /dev/null
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  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.
+-->
+
+<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN"
+    "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="false">
+    <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
+        <param name="Target" value="System.out"/>
+
+        <param name="Threshold" value="DEBUG"/>
+
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="[%p] %m%n"/>
+        </layout>
+    </appender>
+
+    <appender name="FILE" class="org.apache.log4j.RollingFileAppender">
+        <param name="File" value="${IGNITE_HOME}/work/log/GridLog4jConfigUpdateTest.log"/>
+        <param name="Append" value="true"/>
+        <param name="MaxFileSize" value="10MB"/>
+        <param name="MaxBackupIndex" value="10"/>
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="[%p] %m%n"/>
+        </layout>
+    </appender>
+
+    <root>
+        <level value="DEBUG"/>
+
+        <appender-ref ref="CONSOLE"/>
+        <appender-ref ref="FILE"/>
+    </root>
+</log4j:configuration>
diff --git a/modules/log4j/src/test/config/log4j-info.xml b/modules/log4j/src/test/config/log4j-info.xml
new file mode 100644 (file)
index 0000000..6e6d145
--- /dev/null
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  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.
+-->
+
+<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN"
+    "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="false">
+    <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
+        <param name="Target" value="System.out"/>
+
+        <param name="Threshold" value="DEBUG"/>
+
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="[%p] %m%n"/>
+        </layout>
+    </appender>
+
+    <appender name="FILE" class="org.apache.log4j.RollingFileAppender">
+        <param name="File" value="${IGNITE_HOME}/work/log/GridLog4jConfigUpdateTest.log"/>
+        <param name="Append" value="true"/>
+        <param name="MaxFileSize" value="10MB"/>
+        <param name="MaxBackupIndex" value="10"/>
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="[%p] %m%n"/>
+        </layout>
+    </appender>
+
+    <root>
+        <level value="INFO"/>
+
+        <appender-ref ref="CONSOLE"/>
+        <appender-ref ref="FILE"/>
+    </root>
+</log4j:configuration>
diff --git a/modules/log4j/src/test/java/org/apache/ignite/logger/log4j/GridLog4jConfigUpdateTest.java b/modules/log4j/src/test/java/org/apache/ignite/logger/log4j/GridLog4jConfigUpdateTest.java
new file mode 100644 (file)
index 0000000..c0d9591
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * 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.ignite.logger.log4j;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.Date;
+import junit.framework.TestCase;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.log4j.helpers.FileWatchdog;
+
+/**
+ * Checking that Log4j configuration is updated when its source file is changed.
+ */
+public class GridLog4jConfigUpdateTest extends TestCase {
+    /** Path to log4j configuration with INFO enabled. */
+    private static final String LOG_CONFIG_INFO = "modules/log4j/src/test/config/log4j-info.xml";
+
+    /** Path to log4j configuration with DEBUG enabled. */
+    private static final String LOG_CONFIG_DEBUG = "modules/log4j/src/test/config/log4j-debug.xml";
+
+    /** Path to log4j configuration with DEBUG enabled. */
+    private static final String LOG_CONFIG_MAIN = "work/log/log4j-GridLog4jConfigUpdateTest.xml";
+
+    /** Path to log file. */
+    private static final String LOG_DEST = "work/log/GridLog4jConfigUpdateTest.log";
+
+    /**
+     * Time to wait before updating the configuration file.
+     * Should be large enough to be recorded on different file systems.
+     */
+    private static final int DELAY_BEFORE_MODIFICATION = 5000;
+
+    /**
+     * Check that changing log4j config file causes the logger configuration to be updated.
+     * String-accepting constructor is used.
+     */
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    public void testConfigChangeStringConstructor() throws Exception {
+        checkConfigUpdate(new Log4JLoggerSupplier() {
+            @Override public Log4JLogger get(File cfgFile) throws Exception {
+                return new Log4JLogger(cfgFile.getPath(), 10);
+            }
+        }, 5000); // use larger delay to avoid relying on exact timing
+    }
+
+    /**
+     * Check that changing log4j config file causes the logger configuration to be updated.
+     * String-accepting constructor is used.
+     */
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    public void testConfigChangeStringConstructorDefaultDelay() throws Exception {
+        checkConfigUpdate(new Log4JLoggerSupplier() {
+            @Override public Log4JLogger get(File cfgFile) throws Exception {
+                return new Log4JLogger(cfgFile.getPath());
+            }
+        }, FileWatchdog.DEFAULT_DELAY + 5000); // use larger delay to avoid relying on exact timing
+    }
+
+    /**
+     * Check that changing log4j config file causes the logger configuration to be updated.
+     * File-accepting constructor is used.
+     */
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    public void testConfigChangeFileConstructor() throws Exception {
+        checkConfigUpdate(new Log4JLoggerSupplier() {
+            @Override public Log4JLogger get(File cfgFile) throws Exception {
+                return new Log4JLogger(cfgFile, 10);
+            }
+        }, 5000); // use larger delay to avoid relying on exact timing
+    }
+
+    /**
+     * Check that changing log4j config file causes the logger configuration to be updated.
+     * File-accepting constructor is used.
+     */
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    public void testConfigChangeUrlConstructor() throws Exception {
+        checkConfigUpdate(new Log4JLoggerSupplier() {
+            @Override public Log4JLogger get(File cfgFile) throws Exception {
+                return new Log4JLogger(cfgFile.toURI().toURL(), 10);
+            }
+        }, 5000); // use larger delay to avoid relying on exact timing
+    }
+
+    /**
+     * Checks that Log4JLogger is updated after configuration file is changed.
+     *
+     * @param logSupplier Function returning a logger instance to be tested.
+     * @param delay time to wait after configuration file has been changed.
+     */
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    private void checkConfigUpdate(Log4JLoggerSupplier logSupplier, long delay) throws Exception {
+        Log4JLogger.cleanup();
+
+        File infoCfgFile = new File(U.getIgniteHome(), LOG_CONFIG_INFO);
+        File debugCfgFile = new File(U.getIgniteHome(), LOG_CONFIG_DEBUG);
+        File mainCfgFile = new File(U.getIgniteHome(), LOG_CONFIG_MAIN);
+        File logFile = new File(U.getIgniteHome(), LOG_DEST);
+
+        System.out.println("INFO config: " + infoCfgFile);
+        System.out.println("DEBUG config: " + debugCfgFile);
+        System.out.println("Main config: " + mainCfgFile);
+        System.out.println("Log file: " + logFile);
+
+        if (logFile.delete())
+            System.out.println("Old log file was deleted.");
+
+        // Install INFO config.
+        mainCfgFile.getParentFile().mkdirs();
+        Files.copy(infoCfgFile.toPath(), mainCfgFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+        mainCfgFile.setLastModified(new Date().getTime());
+
+        Log4JLogger log = logSupplier.get(mainCfgFile);
+
+        log.info("Accepted info");
+        log.debug("Ignored debug");
+
+        // Wait a bit before copying the file so that new modification date is guaranteed to be different.
+        Thread.sleep(DELAY_BEFORE_MODIFICATION);
+
+        // Replace current config with DEBUG config.
+        Files.copy(debugCfgFile.toPath(), mainCfgFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+        mainCfgFile.setLastModified(new Date().getTime());
+
+        // Wait for the update to happen.
+        Thread.sleep(delay);
+
+        log.debug("Accepted debug");
+
+        String logContent = U.readFileToString(logFile.getPath(), "UTF-8");
+
+        assertTrue(logContent.contains("[INFO] Accepted info"));
+        assertFalse(logContent.contains("[DEBUG] Ignored debug"));
+        assertTrue(logContent.contains("[DEBUG] Accepted debug"));
+
+        mainCfgFile.delete();
+        Log4JLogger.cleanup();
+    }
+
+    /** Creates Log4JLogger instance for testing. */
+    private interface Log4JLoggerSupplier {
+        /** Creates Log4JLogger instance for testing. */
+        Log4JLogger get(File cfgFile) throws Exception;
+    }
+
+}
diff --git a/modules/log4j/src/test/java/org/apache/ignite/logger/log4j/GridLog4jWatchDelayTest.java b/modules/log4j/src/test/java/org/apache/ignite/logger/log4j/GridLog4jWatchDelayTest.java
new file mode 100644 (file)
index 0000000..f9306fe
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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.ignite.logger.log4j;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import org.apache.ignite.IgniteCheckedException;
+import org.junit.Test;
+
+/**
+ * Checking Log4JLogger constructors accepting watchDelay parameter.
+ */
+public class GridLog4jWatchDelayTest {
+    /** Path to log4j configuration file. */
+    private static final String LOG_CONFIG = "modules/log4j/src/test/config/log4j-info.xml";
+
+    /** Check negative watchDelay in String constructor. */
+    @Test(expected = IgniteCheckedException.class)
+    public void testNegativeWatchDelayString() throws IgniteCheckedException {
+        new Log4JLogger(LOG_CONFIG, -1);
+    }
+
+    /** Check negative watchDelay in String constructor. */
+    @Test(expected = IgniteCheckedException.class)
+    public void testNegativeWatchDelayFile() throws IgniteCheckedException {
+        new Log4JLogger(new File(LOG_CONFIG), -1);
+    }
+
+    /** Check negative watchDelay in String constructor. */
+    @Test(expected = IgniteCheckedException.class)
+    public void testNegativeWatchDelayUrl() throws IgniteCheckedException, MalformedURLException {
+        new Log4JLogger(new File(LOG_CONFIG).toURI().toURL(), -1);
+    }
+}
index 2c0af79..c7a099c 100644 (file)
 
 package org.apache.ignite.testsuites;
 
-import junit.framework.TestSuite;
+import org.apache.ignite.logger.log4j.GridLog4jConfigUpdateTest;
 import org.apache.ignite.logger.log4j.GridLog4jCorrectFileNameTest;
 import org.apache.ignite.logger.log4j.GridLog4jInitializedTest;
 import org.apache.ignite.logger.log4j.GridLog4jLoggingFileTest;
 import org.apache.ignite.logger.log4j.GridLog4jLoggingPathTest;
 import org.apache.ignite.logger.log4j.GridLog4jLoggingUrlTest;
 import org.apache.ignite.logger.log4j.GridLog4jNotInitializedTest;
+import org.apache.ignite.logger.log4j.GridLog4jWatchDelayTest;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
 
 /**
  * Log4j logging tests.
  */
-public class IgniteLog4jTestSuite extends TestSuite {
-    /**
-     * @return Test suite.
-     * @throws Exception Thrown in case of the failure.
-     */
-    public static TestSuite suite() throws Exception {
-        TestSuite suite = new TestSuite("Log4j Logging Test Suite");
-
-        suite.addTest(new TestSuite(GridLog4jInitializedTest.class));
-        suite.addTest(new TestSuite(GridLog4jNotInitializedTest.class));
-        suite.addTest(new TestSuite(GridLog4jCorrectFileNameTest.class));
-        suite.addTest(new TestSuite(GridLog4jLoggingFileTest.class));
-        suite.addTest(new TestSuite(GridLog4jLoggingPathTest.class));
-        suite.addTest(new TestSuite(GridLog4jLoggingUrlTest.class));
-
-        return suite;
-    }
-}
\ No newline at end of file
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+    GridLog4jInitializedTest.class,
+    GridLog4jNotInitializedTest.class,
+    GridLog4jCorrectFileNameTest.class,
+    GridLog4jLoggingFileTest.class,
+    GridLog4jLoggingPathTest.class,
+    GridLog4jLoggingUrlTest.class,
+    GridLog4jConfigUpdateTest.class,
+    GridLog4jWatchDelayTest.class,
+})
+public class IgniteLog4jTestSuite { }
diff --git a/modules/log4j2/src/test/config/log4j2-debug.xml b/modules/log4j2/src/test/config/log4j2-debug.xml
new file mode 100644 (file)
index 0000000..4c96491
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  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.
+-->
+
+<Configuration monitorInterval="5">
+    <Appenders>
+        <Console name="CONSOLE" target="SYSTEM_OUT">
+            <PatternLayout pattern="[%p] %m%n"/>
+        </Console>
+
+        <File name="FILE" fileName="${sys:IGNITE_HOME}/work/log/Log4j2ConfigUpdateTest.log" append="true">
+            <PatternLayout pattern="[%p] %m%n"/>
+        </File>
+    </Appenders>
+
+    <Loggers>
+        <Root level="DEBUG">
+            <AppenderRef ref="CONSOLE"/>
+            <AppenderRef ref="FILE"/>
+        </Root>
+    </Loggers>
+</Configuration>
diff --git a/modules/log4j2/src/test/config/log4j2-info.xml b/modules/log4j2/src/test/config/log4j2-info.xml
new file mode 100644 (file)
index 0000000..1036e45
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  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.
+-->
+
+<Configuration monitorInterval="5">
+    <Appenders>
+        <Console name="CONSOLE" target="SYSTEM_OUT">
+            <PatternLayout pattern="[%p] %m%n"/>
+        </Console>
+
+        <File name="FILE" fileName="${sys:IGNITE_HOME}/work/log/Log4j2ConfigUpdateTest.log" append="true">
+            <PatternLayout pattern="[%p] %m%n"/>
+        </File>
+    </Appenders>
+
+    <Loggers>
+        <Root level="INFO">
+            <AppenderRef ref="CONSOLE"/>
+            <AppenderRef ref="FILE"/>
+        </Root>
+    </Loggers>
+</Configuration>
diff --git a/modules/log4j2/src/test/java/org/apache/ignite/logger/log4j2/Log4j2ConfigUpdateTest.java b/modules/log4j2/src/test/java/org/apache/ignite/logger/log4j2/Log4j2ConfigUpdateTest.java
new file mode 100644 (file)
index 0000000..d9f7517
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * 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.ignite.logger.log4j2;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.Date;
+import junit.framework.TestCase;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ * Checking that Log4j2 configuration is updated when its source file is changed.
+ */
+public class Log4j2ConfigUpdateTest extends TestCase {
+    /** Path to log4j2 configuration with INFO enabled. */
+    private static final String LOG_CONFIG_INFO = "modules/log4j2/src/test/config/log4j2-info.xml";
+
+    /** Path to log4j2 configuration with DEBUG enabled. */
+    private static final String LOG_CONFIG_DEBUG = "modules/log4j2/src/test/config/log4j2-debug.xml";
+
+    /** Path to log4j2 configuration with DEBUG enabled. */
+    private static final String LOG_CONFIG_MAIN = "work/log/log4j2-Log4j2ConfigUpdateTest.xml";
+
+    /** Path to log file. */
+    private static final String LOG_DEST = "work/log/Log4j2ConfigUpdateTest.log";
+
+    /**
+     * Time to wait before logger configuration changes.
+     * This value is made greater than the `monitorInterval` in the config files to avoid relying on exact timing.
+     */
+    private static final long UPDATE_DELAY = 10000;
+
+    /**
+     * Time to wait before updating the configuration file.
+     * Should be large enough to be recorded on different file systems.
+     */
+    private static final int DELAY_BEFORE_MODIFICATION = 5000;
+
+    /**
+     * Check that changing log4j2 config file causes the logger configuration to be updated.
+     * String-accepting constructor is used.
+     */
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    public void testConfigChangeStringConstructor() throws Exception {
+        checkConfigUpdate(new Log4J2LoggerSupplier() {
+            @Override public Log4J2Logger get(File cfgFile) throws Exception {
+                return new Log4J2Logger(cfgFile.getPath());
+            }
+        }); // use larger delay to avoid relying on exact timing
+    }
+
+    /**
+     * Check that changing log4j config file causes the logger configuration to be updated.
+     * File-accepting constructor is used.
+     */
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    public void testConfigChangeFileConstructor() throws Exception {
+        checkConfigUpdate(new Log4J2LoggerSupplier() {
+            @Override public Log4J2Logger get(File cfgFile) throws Exception {
+                return new Log4J2Logger(cfgFile);
+            }
+        }); // use larger delay to avoid relying on exact timing
+    }
+
+    /**
+     * Check that changing log4j config file causes the logger configuration to be updated.
+     * File-accepting constructor is used.
+     */
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    public void testConfigChangeUrlConstructor() throws Exception {
+        checkConfigUpdate(new Log4J2LoggerSupplier() {
+            @Override public Log4J2Logger get(File cfgFile) throws Exception {
+                return new Log4J2Logger(cfgFile.toURI().toURL());
+            }
+        }); // use larger delay to avoid relying on exact timing
+    }
+
+    /**
+     * Checks that Log4JLogger is updated after configuration file is changed.
+     *  @param logSupplier Function returning a logger instance to be tested.
+     */
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    private void checkConfigUpdate(Log4J2LoggerSupplier logSupplier) throws Exception {
+        Log4J2Logger.cleanup();
+
+        File infoCfgFile = new File(U.getIgniteHome(), LOG_CONFIG_INFO);
+        File debugCfgFile = new File(U.getIgniteHome(), LOG_CONFIG_DEBUG);
+        File mainCfgFile = new File(U.getIgniteHome(), LOG_CONFIG_MAIN);
+        File logFile = new File(U.getIgniteHome(), LOG_DEST);
+
+        System.out.println("INFO config: " + infoCfgFile);
+        System.out.println("DEBUG config: " + debugCfgFile);
+        System.out.println("Main config: " + mainCfgFile);
+        System.out.println("Log file: " + logFile);
+
+        if (logFile.delete())
+            System.out.println("Old log file was deleted.");
+
+        // Install INFO config.
+        mainCfgFile.getParentFile().mkdirs();
+        Files.copy(infoCfgFile.toPath(), mainCfgFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+        mainCfgFile.setLastModified(new Date().getTime());
+
+        Log4J2Logger log = logSupplier.get(mainCfgFile);
+
+        log.info("Accepted info");
+        log.debug("Ignored debug");
+
+        // Wait a bit before copying the file so that new modification date is guaranteed to be different.
+        Thread.sleep(DELAY_BEFORE_MODIFICATION);
+
+        // Replace current config with DEBUG config.
+        Files.copy(debugCfgFile.toPath(), mainCfgFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+        mainCfgFile.setLastModified(new Date().getTime());
+
+        // Wait for the update to happen.
+        Thread.sleep(UPDATE_DELAY);
+
+        log.debug("Accepted debug");
+
+        String logContent = U.readFileToString(logFile.getPath(), "UTF-8");
+
+        assertTrue(logContent.contains("[INFO] Accepted info"));
+        assertFalse(logContent.contains("[DEBUG] Ignored debug"));
+        assertTrue(logContent.contains("[DEBUG] Accepted debug"));
+
+        mainCfgFile.delete();
+        Log4J2Logger.cleanup();
+    }
+
+    /** Creates Log4J2Logger instance for testing. */
+    private interface Log4J2LoggerSupplier {
+        /** Creates Log4J2Logger instance for testing. */
+        Log4J2Logger get(File cfgFile) throws Exception;
+    }
+}
index 66270b2..0b8cefa 100644 (file)
@@ -18,6 +18,7 @@
 package org.apache.ignite.testsuites;
 
 import junit.framework.TestSuite;
+import org.apache.ignite.logger.log4j2.Log4j2ConfigUpdateTest;
 import org.apache.ignite.logger.log4j2.Log4j2LoggerMarkerTest;
 import org.apache.ignite.logger.log4j2.Log4j2LoggerSelfTest;
 import org.apache.ignite.logger.log4j2.Log4j2LoggerVerboseModeSelfTest;
@@ -36,6 +37,7 @@ public class IgniteLog4j2TestSuite extends TestSuite {
         suite.addTestSuite(Log4j2LoggerSelfTest.class);
         suite.addTestSuite(Log4j2LoggerVerboseModeSelfTest.class);
         suite.addTestSuite(Log4j2LoggerMarkerTest.class);
+        suite.addTestSuite(Log4j2ConfigUpdateTest.class);
 
         return suite;
     }