CallerContradictionAnalyserTest.java
/*
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2026, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v2.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
*/
package ch.qos.logback.classic.model.processor;
import ch.qos.logback.classic.ClassicTestConstants;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.classic.util.LogbackMDCAdapter;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.status.Status;
import ch.qos.logback.core.status.testUtil.StatusChecker;
import org.junit.jupiter.api.Test;
import org.slf4j.spi.MDCAdapter;
import static ch.qos.logback.classic.model.processor.CallerContradictionAnalyser.INCLUDE_CALLER_DATA_NAME_SUFFIX;
import static ch.qos.logback.classic.model.processor.CallerContradictionAnalyser.LAYOUT_NAME_SUFFIX;
import static ch.qos.logback.classic.model.processor.CallerContradictionAnalyser.SKIP_CALLER_CONTRADICTION_ANALYSIS_PROPERTY;
import static ch.qos.logback.classic.model.processor.CallerInstructionLogic.CALLER_CONTRADICTION_URL;
import static ch.qos.logback.classic.model.processor.CallerInstructionLogic.LONE_PREPROCESS_WANT_MSG_TEMPLATE;
import static ch.qos.logback.classic.model.processor.CallerInstructionLogic.NO_CONTRADICTIONS_MSG;
import static ch.qos.logback.classic.model.processor.CallerInstructionLogic.WARNING_MSG_TEMPLATE;
/**
* End-to-end coverage of {@link CallerContradictionAnalyser},
* {@link CallerContradictionWarnAnalyser} and {@link CallerInstructionLogic}
* via real configuration files.
*/
public class CallerContradictionAnalyserTest {
private static final String INPUT_DIR =
ClassicTestConstants.JORAN_INPUT_PREFIX + "callerContradiction/";
LoggerContext loggerContext = new LoggerContext();
MDCAdapter mdcAdapter = new LogbackMDCAdapter();
StatusChecker checker = new StatusChecker(loggerContext);
void configure(String file) throws JoranException {
loggerContext.setMDCAdapter(mdcAdapter);
JoranConfigurator jc = new JoranConfigurator();
jc.setContext(loggerContext);
jc.doConfigure(file);
}
// --- no contradiction ---
@Test
public void asyncIncludeCallerDataTrueWrapsCallerPatternNoWarn() throws JoranException {
// PREPROCESS_WANT (ASYNC) + DIRECT_WANT (CONSOLE) is allowed
configure(INPUT_DIR + "asyncWithCallerDataWrapsCallerPattern.xml");
assertNoContradiction();
}
@Test
public void asyncDefaultIncludeCallerDataWrapsNonCallerPatternNoWarn() throws JoranException {
// DO_NOT_WANT (ASYNC) alone is allowed
configure(INPUT_DIR + "asyncNoCallerDataWrapsNoCallerPattern.xml");
assertNoContradiction();
}
@Test
public void consoleWithCallerPatternOnlyNoWarn() throws JoranException {
// DIRECT_WANT (CONSOLE) alone is allowed
configure(INPUT_DIR + "consoleWithCallerPatternOnly.xml");
assertNoContradiction();
}
// --- DO_NOT_WANT vs DIRECT_WANT ---
@Test
public void asyncDefaultIncludeCallerDataWrapsCallerPatternTriggersWarn() throws JoranException {
// DO_NOT_WANT (ASYNC) + DIRECT_WANT (CONSOLE)
configure(INPUT_DIR + "asyncNoCallerDataWrapsCallerPattern.xml");
assertContradictionWarning(String.format(WARNING_MSG_TEMPLATE, "ASYNC", "CONSOLE"));
}
@Test
public void skipPropertyInConfigDisablesContradictionAnalysis() throws JoranException {
// Would warn without the skip property; with it, analysis is not run.
configure(INPUT_DIR + "asyncNoCallerDataWrapsCallerPatternSkipAnalysis.xml");
assertAnalysisSkipped();
}
@Test
public void skipPropertyOnContextDisablesContradictionAnalysis() throws JoranException {
loggerContext.putProperty(SKIP_CALLER_CONTRADICTION_ANALYSIS_PROPERTY, "true");
configure(INPUT_DIR + "asyncNoCallerDataWrapsCallerPattern.xml");
assertAnalysisSkipped();
}
@Test
public void socketDefaultIncludeCallerDataWithCallerPatternTriggersWarn() throws JoranException {
// DO_NOT_WANT (SOCKET) + DIRECT_WANT (CONSOLE)
configure(INPUT_DIR + "socketDefaultIncludeCallerDataWithCallerPattern.xml");
assertContradictionWarning(String.format(WARNING_MSG_TEMPLATE, "SOCKET", "CONSOLE"));
}
@Test
public void smtpDefaultIncludeCallerDataLayoutWithCallerPatternTriggersWarn() throws JoranException {
// DO_NOT_WANT (EMAIL.includeCallerData) + DIRECT_WANT (EMAIL.layout)
configure(INPUT_DIR + "smtpDefaultIncludeCallerDataLayoutWithCallerPattern.xml");
assertContradictionWarning(String.format(WARNING_MSG_TEMPLATE,
"EMAIL" + INCLUDE_CALLER_DATA_NAME_SUFFIX, "EMAIL" + LAYOUT_NAME_SUFFIX));
}
@Test
public void twoAsyncSuppressSameCallerPatternJoinsNamesInWarning() throws JoranException {
// DO_NOT_WANT (ASYNC1, ASYNC2) + DIRECT_WANT (CONSOLE, FILE)
configure(INPUT_DIR + "twoAsyncSuppressSameCallerPattern.xml");
assertContradictionWarning(String.format(WARNING_MSG_TEMPLATE, "ASYNC1, ASYNC2", "CONSOLE, FILE"));
}
// --- lone PREPROCESS_WANT ---
@Test
public void asyncIncludeCallerDataTrueWrapsNonCallerPatternTriggersLonePreprocessWarn()
throws JoranException {
// PREPROCESS_WANT (ASYNC) without any DIRECT_WANT
configure(INPUT_DIR + "asyncWithCallerDataWrapsNoCallerPattern.xml");
assertContradictionWarning(String.format(LONE_PREPROCESS_WANT_MSG_TEMPLATE, "ASYNC"));
}
@Test
public void smtpIncludeCallerDataTrueLayoutWithoutCallerPatternTriggersLonePreprocessWarn()
throws JoranException {
// PREPROCESS_WANT (EMAIL.includeCallerData) without any DIRECT_WANT
// (Console and SMTP layout patterns do not use caller converters)
configure(INPUT_DIR + "smtpIncludeCallerDataTrueLayoutWithoutCallerPattern.xml");
assertContradictionWarning(String.format(LONE_PREPROCESS_WANT_MSG_TEMPLATE,
"EMAIL" + INCLUDE_CALLER_DATA_NAME_SUFFIX));
}
// --- SMTP PREPROCESS_WANT + DIRECT_WANT (consistent) ---
@Test
public void smtpIncludeCallerDataTrueLayoutWithCallerPatternNoWarn() throws JoranException {
// PREPROCESS_WANT (EMAIL.includeCallerData) + DIRECT_WANT (EMAIL.layout)
configure(INPUT_DIR + "smtpIncludeCallerDataTrueLayoutWithCallerPattern.xml");
assertNoContradiction();
}
// --- DO_NOT_WANT vs PREPROCESS_WANT ---
@Test
public void twoAsyncDifferentIncludeCallerDataNoCallerPatternTriggersWarn() throws JoranException {
// DO_NOT_WANT (ASYNC_0) + PREPROCESS_WANT (ASYNC_1)
configure(INPUT_DIR + "twoAsyncDifferentIncludeCallerDataNoCallerPattern.xml");
assertContradictionWarning(String.format(WARNING_MSG_TEMPLATE, "ASYNC_0", "ASYNC_1"));
}
// --- DO_NOT_WANT vs PREPROCESS_WANT and vs DIRECT_WANT ---
@Test
public void twoAsyncDifferentIncludeCallerDataWithCallerPatternTriggersTwoWarns()
throws JoranException {
// DO_NOT_WANT (ASYNC_0) + PREPROCESS_WANT (ASYNC_1) + DIRECT_WANT (CONSOLE)
configure(INPUT_DIR + "twoAsyncDifferentIncludeCallerDataWithCallerPattern.xml");
checker.assertContainsMatch(Status.WARN,
String.format(WARNING_MSG_TEMPLATE, "ASYNC_0", "ASYNC_1"));
checker.assertContainsMatch(Status.WARN,
String.format(WARNING_MSG_TEMPLATE, "ASYNC_0", "CONSOLE"));
checker.assertContainsMatch(Status.WARN, "See " + CALLER_CONTRADICTION_URL + " for details");
checker.assertMatchCount(NO_CONTRADICTIONS_MSG, 0);
}
private void assertNoContradiction() {
checker.assertMatchCount(NO_CONTRADICTIONS_MSG, 1);
checker.assertMatchCount(CALLER_CONTRADICTION_URL, 0);
}
private void assertContradictionWarning(String expectedWarnMsg) {
checker.assertContainsMatch(Status.WARN, expectedWarnMsg);
checker.assertContainsMatch(Status.WARN, "See " + CALLER_CONTRADICTION_URL + " for details");
checker.assertMatchCount(NO_CONTRADICTIONS_MSG, 0);
}
private void assertAnalysisSkipped() {
checker.assertMatchCount(NO_CONTRADICTIONS_MSG, 0);
checker.assertMatchCount(CALLER_CONTRADICTION_URL, 0);
checker.assertMatchCount(String.format(WARNING_MSG_TEMPLATE, "ASYNC", "CONSOLE"), 0);
}
}