blob: 7fc062b8b3df888be7680055157f18bec9eb06eb [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.solr.util;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.solr.common.util.SuppressForbidden;
import org.apache.solr.SolrTestCaseJ4;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.impl.MutableLogEvent;
import static org.hamcrest.core.StringContains.containsString;
@SuppressForbidden(reason="We need to use log4J2 classes directly to check that the ErrorLogMuter is working")
public class TestErrorLogMuter extends SolrTestCaseJ4 {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@LogLevel("=WARN")
public void testErrorMutingRegex() throws Exception {
final ListAppender rootSanityCheck = new ListAppender("sanity-checker");
try {
LoggerContext.getContext(false).getConfiguration().getRootLogger().addAppender(rootSanityCheck, Level.WARN, null);
LoggerContext.getContext(false).updateLoggers();
try (ErrorLogMuter x = ErrorLogMuter.regex("eRrOr\\s+Log")) {
assertEquals(0, x.getCount());
log.error("This is an {} Log msg that x should be muted", "eRrOr");
assertEquals(1, x.getCount());
log.error("This is an {} Log msg that x should not mute", "err");
log.warn("This is an warn message, mentioning 'eRrOr Log', that should also not be muted");
assertEquals(1, x.getCount());
log.error("This {} because of the {} msg", "error", "thowable",
new Exception("outer", new Exception("inner eRrOr Log throwable")));
assertEquals(2, x.getCount());
}
} finally {
LoggerContext.getContext(false).getConfiguration().getRootLogger().removeAppender(rootSanityCheck.getName());
LoggerContext.getContext(false).updateLoggers();
}
// the root loger should not have seen anything that was muted...
assertEquals(2, rootSanityCheck.getEvents().size());
assertThat(rootSanityCheck.getEvents().get(0).getMessage().getFormattedMessage(), containsString("should not mute"));
assertThat(rootSanityCheck.getEvents().get(1).getMessage().getFormattedMessage(), containsString("also not be muted"));
}
@LogLevel("=WARN")
public void testMultipleMuters() throws Exception {
// Add a ListAppender to our ROOT logger so we can sanity check what log messages it gets
final ListAppender rootSanityCheck = new ListAppender("sanity-checker");
try {
LoggerContext.getContext(false).getConfiguration().getRootLogger().addAppender(rootSanityCheck, Level.WARN, null);
LoggerContext.getContext(false).updateLoggers();
// sanity check that muters "mute" in the order used...
// (If this fails, then it means log4j has changed the precedence order it uses when addFilter is called,
// if that happens, we'll need to change our impl to check if an impl of some special "container" Filter subclass we create.
// is in the list of ROOT filters -- if not add one, and then "add" the specific muting filter to our "container" Filter)
try (ErrorLogMuter x = ErrorLogMuter.substring("xxx");
ErrorLogMuter y = ErrorLogMuter.regex(Pattern.compile("YYY", Pattern.CASE_INSENSITIVE));
ErrorLogMuter z = ErrorLogMuter.regex("(xxx|yyy)")) {
log.error("xx{} ", "x");
log.error(" yyy");
log.error("xxx yyy");
log.warn("xxx yyy");
log.error("abc", new Exception("yyy"));
assertEquals(2, x.getCount()); // x is first, so it swallows up the "x + y" message
assertEquals(2, y.getCount()); // doesn't get anything x already got
assertEquals(0, z.getCount()); // doesn't get anything x already got
}
} finally {
LoggerContext.getContext(false).getConfiguration().getRootLogger().removeAppender(rootSanityCheck.getName());
LoggerContext.getContext(false).updateLoggers();
}
assertEquals(1, rootSanityCheck.getEvents().size()); // our warning
}
@LogLevel("=WARN")
public void testDeprecatedBaseClassMethods() throws Exception {
// Add a ListAppender to our ROOT logger so we can sanity check what log messages it gets
final ListAppender rootSanityCheck = new ListAppender("sanity-checker");
try {
LoggerContext.getContext(false).getConfiguration().getRootLogger().addAppender(rootSanityCheck, Level.WARN, null);
LoggerContext.getContext(false).updateLoggers();
log.error("this matches the default ignore_exception pattern");
log.error("something matching foo that should make it"); // 1
assertEquals(1, rootSanityCheck.getEvents().size());
ignoreException("foo");
log.error("something matching foo that should NOT make it");
ignoreException("foo");
ignoreException("ba+r");
log.error("something matching foo that should still NOT make it");
log.error("something matching baaaar that should NOT make it");
log.warn("A warning should be fine even if it matches ignore_exception and foo and bar"); // 2
assertEquals(2, rootSanityCheck.getEvents().size());
unIgnoreException("foo");
log.error("another thing matching foo that should make it"); // 3
assertEquals(3, rootSanityCheck.getEvents().size());
log.error("something matching baaaar that should still NOT make it");
resetExceptionIgnores();
log.error("this still matches the default ignore_exception pattern");
log.error("but something matching baaaar should make it now"); // 4
assertEquals(4, rootSanityCheck.getEvents().size());
} finally {
LoggerContext.getContext(false).getConfiguration().getRootLogger().removeAppender(rootSanityCheck.getName());
LoggerContext.getContext(false).updateLoggers();
}
assertEquals(4, rootSanityCheck.getEvents().size());
}
/**
* Maintains an in memory List of log events.
* <p>
* Inspired by <code>org.apache.logging.log4j.core.test.appender.ListAppender</code>
* but we have much simpler needs.
*/
@SuppressForbidden(reason="We need to use log4J2 classes directly to check that the ErrorLogMuter is working")
public static final class ListAppender extends AbstractAppender {
// Use Collections.synchronizedList rather than CopyOnWriteArrayList because we expect
// more frequent writes than reads.
private final List<LogEvent> events = Collections.synchronizedList(new ArrayList<>());
private final List<LogEvent> publicEvents = Collections.unmodifiableList(events);
public ListAppender(final String name) {
super(name, null, null, true, Property.EMPTY_ARRAY);
assert null != name;
}
@Override
public void append(final LogEvent event) {
if (event instanceof MutableLogEvent) {
// must take snapshot or subsequent calls to logger.log() will modify this event
events.add(((MutableLogEvent) event).createMemento());
} else {
events.add(event);
}
if (log.isDebugEnabled()) {
log.debug("{} intercepted a log event (#{})", this.getName(), events.size());
}
}
/** Returns an immutable view of captured log events, contents can change as events are logged */
public List<LogEvent> getEvents() {
return publicEvents;
}
}
}