IGNITE-6946: Change Ignite Logger configuration on the fly. Fixes #3400.

Signed-off-by: Valentin Kulichenko <valentin.kuliche...@gmail.com>


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/ebfab709
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/ebfab709
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/ebfab709

Branch: refs/heads/ignite-7485-2
Commit: ebfab709eafb78dbd920183e647eba442cc45667
Parents: 0088405
Author: Stanislav Lukyanov <stanlukya...@gmail.com>
Authored: Wed Jan 31 13:02:45 2018 -0800
Committer: Valentin Kulichenko <valentin.kuliche...@gmail.com>
Committed: Wed Jan 31 13:02:45 2018 -0800

----------------------------------------------------------------------
 config/ignite-log4j2.xml                        |   2 +-
 .../apache/ignite/logger/log4j/Log4JLogger.java | 100 +++++++++++-
 modules/log4j/src/test/config/log4j-debug.xml   |  50 ++++++
 modules/log4j/src/test/config/log4j-info.xml    |  50 ++++++
 .../logger/log4j/GridLog4jConfigUpdateTest.java | 163 +++++++++++++++++++
 .../logger/log4j/GridLog4jWatchDelayTest.java   |  49 ++++++
 .../ignite/testsuites/IgniteLog4jTestSuite.java |  35 ++--
 modules/log4j2/src/test/config/log4j2-debug.xml |  37 +++++
 modules/log4j2/src/test/config/log4j2-info.xml  |  37 +++++
 .../logger/log4j2/Log4j2ConfigUpdateTest.java   | 152 +++++++++++++++++
 .../testsuites/IgniteLog4j2TestSuite.java       |   2 +
 11 files changed, 649 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/ebfab709/config/ignite-log4j2.xml
----------------------------------------------------------------------
diff --git a/config/ignite-log4j2.xml b/config/ignite-log4j2.xml
index 7154ae8..b55c563 100644
--- a/config/ignite-log4j2.xml
+++ b/config/ignite-log4j2.xml
@@ -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"/>

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebfab709/modules/log4j/src/main/java/org/apache/ignite/logger/log4j/Log4JLogger.java
----------------------------------------------------------------------
diff --git 
a/modules/log4j/src/main/java/org/apache/ignite/logger/log4j/Log4JLogger.java 
b/modules/log4j/src/main/java/org/apache/ignite/logger/log4j/Log4JLogger.java
index 02e7b35..ec0a5b3 100644
--- 
a/modules/log4j/src/main/java/org/apache/ignite/logger/log4j/Log4JLogger.java
+++ 
b/modules/log4j/src/main/java/org/apache/ignite/logger/log4j/Log4JLogger.java
@@ -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;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebfab709/modules/log4j/src/test/config/log4j-debug.xml
----------------------------------------------------------------------
diff --git a/modules/log4j/src/test/config/log4j-debug.xml 
b/modules/log4j/src/test/config/log4j-debug.xml
new file mode 100644
index 0000000..2c36194
--- /dev/null
+++ b/modules/log4j/src/test/config/log4j-debug.xml
@@ -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>

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebfab709/modules/log4j/src/test/config/log4j-info.xml
----------------------------------------------------------------------
diff --git a/modules/log4j/src/test/config/log4j-info.xml 
b/modules/log4j/src/test/config/log4j-info.xml
new file mode 100644
index 0000000..6e6d145
--- /dev/null
+++ b/modules/log4j/src/test/config/log4j-info.xml
@@ -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>

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebfab709/modules/log4j/src/test/java/org/apache/ignite/logger/log4j/GridLog4jConfigUpdateTest.java
----------------------------------------------------------------------
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
index 0000000..c0d9591
--- /dev/null
+++ 
b/modules/log4j/src/test/java/org/apache/ignite/logger/log4j/GridLog4jConfigUpdateTest.java
@@ -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;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebfab709/modules/log4j/src/test/java/org/apache/ignite/logger/log4j/GridLog4jWatchDelayTest.java
----------------------------------------------------------------------
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
index 0000000..f9306fe
--- /dev/null
+++ 
b/modules/log4j/src/test/java/org/apache/ignite/logger/log4j/GridLog4jWatchDelayTest.java
@@ -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);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebfab709/modules/log4j/src/test/java/org/apache/ignite/testsuites/IgniteLog4jTestSuite.java
----------------------------------------------------------------------
diff --git 
a/modules/log4j/src/test/java/org/apache/ignite/testsuites/IgniteLog4jTestSuite.java
 
b/modules/log4j/src/test/java/org/apache/ignite/testsuites/IgniteLog4jTestSuite.java
index 2c0af79..c7a099c 100644
--- 
a/modules/log4j/src/test/java/org/apache/ignite/testsuites/IgniteLog4jTestSuite.java
+++ 
b/modules/log4j/src/test/java/org/apache/ignite/testsuites/IgniteLog4jTestSuite.java
@@ -17,32 +17,29 @@
 
 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 { }

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebfab709/modules/log4j2/src/test/config/log4j2-debug.xml
----------------------------------------------------------------------
diff --git a/modules/log4j2/src/test/config/log4j2-debug.xml 
b/modules/log4j2/src/test/config/log4j2-debug.xml
new file mode 100644
index 0000000..4c96491
--- /dev/null
+++ b/modules/log4j2/src/test/config/log4j2-debug.xml
@@ -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>

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebfab709/modules/log4j2/src/test/config/log4j2-info.xml
----------------------------------------------------------------------
diff --git a/modules/log4j2/src/test/config/log4j2-info.xml 
b/modules/log4j2/src/test/config/log4j2-info.xml
new file mode 100644
index 0000000..1036e45
--- /dev/null
+++ b/modules/log4j2/src/test/config/log4j2-info.xml
@@ -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>

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebfab709/modules/log4j2/src/test/java/org/apache/ignite/logger/log4j2/Log4j2ConfigUpdateTest.java
----------------------------------------------------------------------
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
index 0000000..d9f7517
--- /dev/null
+++ 
b/modules/log4j2/src/test/java/org/apache/ignite/logger/log4j2/Log4j2ConfigUpdateTest.java
@@ -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;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ebfab709/modules/log4j2/src/test/java/org/apache/ignite/testsuites/IgniteLog4j2TestSuite.java
----------------------------------------------------------------------
diff --git 
a/modules/log4j2/src/test/java/org/apache/ignite/testsuites/IgniteLog4j2TestSuite.java
 
b/modules/log4j2/src/test/java/org/apache/ignite/testsuites/IgniteLog4j2TestSuite.java
index 66270b2..0b8cefa 100644
--- 
a/modules/log4j2/src/test/java/org/apache/ignite/testsuites/IgniteLog4j2TestSuite.java
+++ 
b/modules/log4j2/src/test/java/org/apache/ignite/testsuites/IgniteLog4j2TestSuite.java
@@ -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;
     }

Reply via email to