001/*
002 * (C) Copyright 2019 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Salem Aouana
018 */
019
020package org.nuxeo.runtime.test.logging;
021
022import static org.junit.Assert.assertEquals;
023import static org.junit.Assert.assertFalse;
024import static org.junit.Assert.assertNotNull;
025import static org.junit.Assert.assertTrue;
026
027import java.util.ArrayList;
028import java.util.Collection;
029import java.util.List;
030
031import org.apache.logging.log4j.Level;
032import org.apache.logging.log4j.LogManager;
033import org.apache.logging.log4j.Logger;
034import org.apache.logging.log4j.core.Appender;
035import org.apache.logging.log4j.core.Filter;
036import org.apache.logging.log4j.core.LoggerContext;
037import org.apache.logging.log4j.core.appender.ConsoleAppender;
038import org.apache.logging.log4j.core.filter.ThresholdFilter;
039import org.junit.ClassRule;
040import org.junit.Rule;
041import org.junit.Test;
042import org.junit.rules.TestRule;
043import org.junit.runner.Description;
044import org.junit.runner.RunWith;
045import org.junit.runners.model.FrameworkMethod;
046import org.junit.runners.model.Statement;
047import org.nuxeo.runtime.test.runner.ConsoleLogLevelThreshold;
048import org.nuxeo.runtime.test.runner.Features;
049import org.nuxeo.runtime.test.runner.FeaturesRunner;
050import org.nuxeo.runtime.test.runner.LogFeature;
051import org.nuxeo.runtime.test.runner.LoggerLevel;
052import org.nuxeo.runtime.test.runner.LoggerLevels;
053import org.nuxeo.runtime.test.runner.RuntimeFeature;
054
055/**
056 * Test for {@link LogFeature}.
057 *
058 * @since 11.1
059 */
060@RunWith(FeaturesRunner.class)
061@Features({ RuntimeFeature.class, LogFeature.class })
062@LoggerLevel(name = "org.nuxeo.FakeClass2", level = "DEBUG")
063@LoggerLevel(name = "org.nuxeo.FakeClass3", level = "FATAL")
064public class TestLogFeature {
065
066    protected static final Logger log = LogManager.getLogger(TestLogFeature.class);
067
068    protected static final String FAKE_LOGGER_NAME_1 = "org.nuxeo.FakeClass1";
069
070    protected static final String FAKE_LOGGER_NAME_2 = "org.nuxeo.FakeClass2";
071
072    protected static final String FAKE_LOGGER_NAME_3 = "org.nuxeo.FakeClass3";
073
074    @ClassRule
075    public final static LogFeatureClassCheckerRule LOG_FEATURE_CLASS_CHECKER_RULE = new LogFeatureClassCheckerRule();
076
077    @Rule
078    public final LogFeatureMethodCheckerRule logFeatureMethodCheckerRule = new LogFeatureMethodCheckerRule();
079
080    @Test
081    @LoggerLevel(name = FAKE_LOGGER_NAME_1, level = "TRACE")
082    public void shouldAddNewLogger() {
083        assertTrue(LogManager.getLogger(FAKE_LOGGER_NAME_1).isTraceEnabled());
084    }
085
086    @Test
087    @LoggerLevel(klass = TestLogFeature.class, level = "DEBUG")
088    public void shouldUpdateLogger() {
089        assertTrue(LogManager.getLogger(TestLogFeature.class).isDebugEnabled());
090    }
091
092    @Test
093    @LoggerLevel(name = FAKE_LOGGER_NAME_2, level = "TRACE")
094    @LoggerLevel(name = FAKE_LOGGER_NAME_3, level = "INFO")
095    public void shouldOverrideTestLoggerDefinition() {
096        assertTrue(LogManager.getLogger(FAKE_LOGGER_NAME_2).isTraceEnabled());
097        assertTrue(LogManager.getLogger(FAKE_LOGGER_NAME_3).isInfoEnabled());
098    }
099
100    @Test
101    public void shouldInheritLogger() {
102        assertTrue(LogManager.getLogger(FAKE_LOGGER_NAME_2).isDebugEnabled());
103        assertTrue(LogManager.getLogger(FAKE_LOGGER_NAME_3).isFatalEnabled());
104    }
105
106    @Test
107    @ConsoleLogLevelThreshold("FATAL")
108    public void shouldSetConsoleLogThreshold() {
109        LoggerContext context = LoggerContext.getContext(false);
110        org.apache.logging.log4j.core.Logger rootLogger = context.getRootLogger();
111        Appender appender = rootLogger.getAppenders().get("CONSOLE_LOG_FEATURE");
112        assertNotNull(appender);
113        ConsoleAppender console = (ConsoleAppender) appender;
114        Filter filter = console.getFilter();
115        assertNotNull(filter);
116        assertTrue(filter instanceof ThresholdFilter);
117        ThresholdFilter threshold = (ThresholdFilter) filter;
118        assertEquals(Level.FATAL, threshold.getLevel());
119    }
120
121    /**
122     * Ensures that all log levels are preserved before launching the whole class test
123     * {@link org.nuxeo.runtime.test.runner.RunnerFeature#beforeRun(FeaturesRunner)} and correctly restored after that
124     * which means after the execution of {@link org.nuxeo.runtime.test.runner.RunnerFeature#afterRun(FeaturesRunner)}
125     */
126    public static class LogFeatureClassCheckerRule implements TestRule {
127        @Override
128        public Statement apply(final Statement base, final Description description) {
129            return new Statement() {
130                @Override
131                public void evaluate() throws Throwable {
132                    before();
133                    try {
134                        base.evaluate();
135                    } finally {
136                        after();
137                    }
138                }
139
140                protected void before() {
141                    LoggerContext context = LoggerContext.getContext(false);
142
143                    // Inherit from "org.nuxeo" see log4j2-test.xml
144                    assertTrue(log.isInfoEnabled());
145
146                    // Ensure that there is no loggers for these two fake classes
147                    assertFalse(context.hasLogger(FAKE_LOGGER_NAME_1));
148                    assertFalse(context.hasLogger(FAKE_LOGGER_NAME_2));
149                    assertFalse(context.hasLogger(FAKE_LOGGER_NAME_3));
150                }
151
152                protected void after() {
153                    // Ensure that we have the original level value, as it was before the whole test.
154                    assertTrue(log.isInfoEnabled());
155
156                    // The new created loggers should be turned off.
157                    assertEquals(Level.OFF, LogManager.getLogger(FAKE_LOGGER_NAME_1).getLevel());
158                    assertEquals(Level.OFF, LogManager.getLogger(FAKE_LOGGER_NAME_2).getLevel());
159                    assertEquals(Level.OFF, LogManager.getLogger(FAKE_LOGGER_NAME_3).getLevel());
160                }
161            };
162        }
163    }
164
165    /**
166     * Ensures that all log levels are preserved before launching each test method
167     * {@link org.nuxeo.runtime.test.runner.RunnerFeature#beforeMethodRun(FeaturesRunner, FrameworkMethod, Object)} and
168     * correctly restored after that which means after the execution of
169     * {@link org.nuxeo.runtime.test.runner.RunnerFeature#afterMethodRun(FeaturesRunner, FrameworkMethod, Object)}
170     */
171    public class LogFeatureMethodCheckerRule implements TestRule {
172        @Override
173        public Statement apply(final Statement base, final Description description) {
174            return new Statement() {
175                @Override
176                public void evaluate() throws Throwable {
177                    before(description);
178                    try {
179                        base.evaluate();
180                    } finally {
181                        after(description);
182                    }
183                }
184
185                protected void before(final Description description) {
186                    getLoggersAnnotations(description).forEach(a -> check(a, true));
187                }
188
189                protected void after(final Description description) {
190                    getLoggersAnnotations(description).forEach(a -> check(a, false));
191                }
192            };
193        }
194
195        protected Collection<LoggerLevel> getLoggersAnnotations(Description description) {
196            var annotations = new ArrayList<LoggerLevel>();
197
198            LoggerLevel logger = description.getAnnotation(LoggerLevel.class);
199            if (logger != null) {
200                annotations.add(logger);
201            }
202
203            LoggerLevels loggers = description.getAnnotation(LoggerLevels.class);
204            if (loggers != null) {
205                annotations.addAll(List.of(loggers.value()));
206            }
207
208            return annotations;
209        }
210
211        protected void check(LoggerLevel logger, boolean before) {
212            LoggerContext context = LoggerContext.getContext(false);
213            if (logger.klass().equals(TestLogFeature.class)) {
214                assertEquals(Level.INFO, log.getLevel());
215            } else {
216                switch (logger.name()) {
217                case FAKE_LOGGER_NAME_1:
218                    if (before) {
219                        assertFalse(context.hasLogger(FAKE_LOGGER_NAME_1));
220                    } else {
221                        assertEquals(Level.OFF, LogManager.getLogger(FAKE_LOGGER_NAME_1).getLevel());
222                    }
223                    break;
224                case FAKE_LOGGER_NAME_2:
225                    assertEquals(Level.DEBUG, LogManager.getLogger(FAKE_LOGGER_NAME_2).getLevel());
226                    break;
227                case FAKE_LOGGER_NAME_3:
228                    assertEquals(Level.FATAL, LogManager.getLogger(FAKE_LOGGER_NAME_3).getLevel());
229                    break;
230
231                default:
232                    return;
233                }
234            }
235
236        }
237    }
238}