blob: 93093f868a8885be1b8a12b19665084019ff3d60 [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.logging.log4j.jul;
//note: NO import of Logger, Level, LogManager to prevent conflicts JUL/log4j
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Map.Entry;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationListener;
import org.apache.logging.log4j.core.config.Configurator;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.config.Reconfigurable;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
/**
* Test the Log4jBridgeHandler.
* Requires some configurations in the log-config-files, for format/layout
* see also jul() and log4j():
* - JUL-config ("logging-test.properties", must be set on JVM-start via "-D..."):
* + handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler, java.util.logging.ConsoleHandler
* + org.apache.logging.log4j.jul.Log4jBridgeHandler.appendSuffix = _JUL
* + java.util.logging.ConsoleHandler.level = ALL
* + java.util.logging.SimpleFormatter.format = JUL: %1$tT.%1$tL %4$s [%3$s: %2$s] - %5$s%6$s%n
* + .level = FINER
* - log4j2-config ("log4j2-test.xml"):
* + <Root level="TRACE">
* + <Appenders> <Console> with target="SYSTEM_ERR", follow="true",
* <PatternLayout> "log4j2: %d{HH:mm:ss.SSS} %5level - [%thread][%logger: %class/%method/%line] - %message%n"
*
* This test needs to capture syserr because it uses java.util.logging.ConsoleHandler.
* Also, it performs some outputs to console (sysout and syserr); see also field OUTPUT_CAPTURED.
*
* The code also contains evaluation/test code for development time. It is not used for the unit tests
* but kept here for reference and info. See field DEVTEST.
*/
@FixMethodOrder(org.junit.runners.MethodSorters.NAME_ASCENDING) // is nicer for manually checking console output
public class Log4jBridgeHandlerTest {
/** Perform developer tests? */
private static final boolean DEVTEST = false;
/** Do output the captured logger-output to stdout? */
private static final boolean OUTPUT_CAPTURED = !DEVTEST && Boolean.parseBoolean(
System.getProperty("log4j.Log4jBridgeHandlerTest.outputCaptured"));
/** This classes' simple name = relevant part of logger name. */
private static final String CSNAME = Log4jBridgeHandlerTest.class.getSimpleName();
// loggers used in many tests
private static final java.util.logging.Logger julLog = java.util.logging.Logger.getLogger(Log4jBridgeHandlerTest.class.getName());
private static final org.apache.logging.log4j.Logger log4jLog = org.apache.logging.log4j.LogManager.getLogger();
// capture sysout/syserr
//@Rule public final SystemErrRule systemOutRule = new SystemErrRule().enableLog();
private static final ByteArrayOutputStream sysoutBytes = new ByteArrayOutputStream(1024);
private static PrintStream prevSysErrStream;
@BeforeClass
public static void beforeClass() {
// debug output to easily recognize misconfig.:
//System.out.println("sys-props:\n" + System.getProperties());
System.out.println("sysout: logging-cfg-file: " + System.getProperty("java.util.logging.config.file"));
if (DEVTEST) devTestBeforeClass(); // call before stderr capturing
// JUL does not like setting stderr inbetween, so set it once and reset collecting stream
// for each method; (thus com.github.stefanbirkner:system-rules:SystemErrRule cannot be used)
System.err.println("vvv--- BEGIN capturing output to stderr ---vvv"
+ " (do output of captured text to orig. stderr: " + OUTPUT_CAPTURED + ")");
prevSysErrStream = System.err;
System.setErr(new PrintStream(sysoutBytes, true));
}
@AfterClass
public static void afterClass() {
// reset sysout/err to original value
System.setErr(prevSysErrStream);
System.err.println("^^^--- END capturing output of stderr ---^^^");
}
@Before
public void beforeTest() {
// reset sysout collector
sysoutBytes.reset();
}
/** Assert that captured sysout matches given regexp (any text may follow afterwards). */
private void assertSysoutMatches(String regex) {
//String logOutput = systemOutRule.getLogWithNormalizedLineSeparator();
String logOutput = sysoutBytes.toString();
if (OUTPUT_CAPTURED) prevSysErrStream.print(logOutput);
logOutput = logOutput.replace("\r\n", "\n");
regex = regex + "(.|\\n)*"; // allow any text with NL afterwards
assertTrue("Unmatching output:\n" + logOutput + "\n-- vs: --\n" + regex + "\n----", logOutput.matches(regex));
}
/** Get regex for a JUL console output. Must match JUL-Console-Formatter! */
private String jul(java.util.logging.Level lvl, String locationPartRE,
String msgPartRE, String exceptionClassAndMsgRE) {
return "JUL:.*" + lvl.getLocalizedName() + ".*" + CSNAME
+ ".*" + locationPartRE + ".*" + msgPartRE + ".*\n" // use real \n at end here for better error output
+ (exceptionClassAndMsgRE == null ? ""
: ".*" + exceptionClassAndMsgRE + ".*\\n(\tat .*\\n)*\\n?");
}
/** Get regex for a log4j console output. Must match log4j2-Console-Layout! */
private String log4j(org.apache.logging.log4j.Level lvl, boolean julBridged,
String methodPartRE, String msgPartRE, String exceptionClassAndMsgRE) {
return "log4j2:.*" + lvl.name() + ".*" + CSNAME + (julBridged ? "\\._JUL" : "")
+ ".*" + CSNAME + "/\\w*" + methodPartRE + "\\w*/.*"
+ msgPartRE + ".*\n" // use real \n at end here for better error output
+ (exceptionClassAndMsgRE == null ? ""
: ".*" + exceptionClassAndMsgRE + ".*\\n(\tat .*\\n)*\\n?");
}
@Test
public void test1SimpleLoggings1Jul() {
julLog.info("Test-'Info'-Log with JUL");
julLog.fine("Test-'Fine'-Log with JUL");
julLog.finest("Test-'Finest'-Log with JUL"); // should not be logged because JUL-level is FINER
julLog.warning("Test-'Warn'-Log with JUL"); // thus add another log afterwards to allow checking
String methodRE = "SimpleLoggings1Jul";
assertSysoutMatches(
log4j(org.apache.logging.log4j.Level.INFO, true, methodRE, "'Info'-Log with JUL", null)
+ jul(java.util.logging.Level.INFO, methodRE, "'Info'-Log with JUL", null)
+ log4j(org.apache.logging.log4j.Level.DEBUG, true, methodRE, "'Fine'-Log with JUL", null)
+ jul(java.util.logging.Level.FINE, methodRE, "'Fine'-Log with JUL", null)
// no finest/trace
+ log4j(org.apache.logging.log4j.Level.WARN, true, methodRE, "'Warn'-Log with JUL", null)
+ jul(java.util.logging.Level.WARNING, methodRE, "'Warn'-Log with JUL", null)
);
}
@Test
public void test1SimpleLoggings2Log4jDirect() {
log4jLog.info("Test-'Info'-Log with log4j2");
log4jLog.debug("Test-'Debug'-Log with log4j2");
log4jLog.trace("Test-'Trace'-Log with log4j2");
String methodRE = "SimpleLoggings2Log4jDirect";
assertSysoutMatches(
log4j(org.apache.logging.log4j.Level.INFO, false, methodRE, "'Info'-Log with log4j2", null)
+ log4j(org.apache.logging.log4j.Level.DEBUG, false, methodRE, "'Debug'-Log with log4j2", null)
+ log4j(org.apache.logging.log4j.Level.TRACE, false, methodRE, "'Trace'-Log with log4j2", null)
);
}
@Test
public void test2SubMethod() {
subMethodWithLogs(); // location info is sub method now
String methodRE = "subMethodWithLogs";
assertSysoutMatches(
log4j(org.apache.logging.log4j.Level.DEBUG, true, methodRE, "'Fine'-Log with JUL in subMethod", null)
+ jul(java.util.logging.Level.FINE, methodRE, "'Fine'-Log with JUL in subMethod", null)
+ log4j(org.apache.logging.log4j.Level.INFO, false, methodRE, "'Info'-Log with log4j2 in subMethod", null)
);
}
private void subMethodWithLogs() {
julLog.fine("Test-'Fine'-Log with JUL in subMethod");
log4jLog.info("Test-'Info'-Log with log4j2 in subMethod");
}
@Test
public void test3JulFlow1() {
// note: manually given source information get lost in log4j!
julLog.entering("enteringExampleClassParam", "enteringExampleMethodParam");
String methodRE = "JulFlow";
assertSysoutMatches(
log4j(org.apache.logging.log4j.Level.TRACE, true, methodRE, "ENTRY", null)
+ jul(java.util.logging.Level.FINER, "enteringExampleClassParam enteringExampleMethodParam", "ENTRY", null)
);
}
@Test
public void test3JulFlow2() {
// note: manually given source information get lost in log4j!
julLog.entering("enteringExampleClassParam", "enteringExampleMethodParam_withParams",
new Object[] {"with some", "parameters", 42} );
String methodRE = "JulFlow";
assertSysoutMatches(
log4j(org.apache.logging.log4j.Level.TRACE, true, methodRE,
"ENTRY.*with some.*param.*42", null)
+ jul(java.util.logging.Level.FINER, "enteringExampleClassParam enteringExampleMethodParam_withParams",
"ENTRY.*with some.*param.*42", null)
);
}
@Test
public void test3JulFlow3() {
// note: manually given source information get lost in log4j!
julLog.exiting("exitingExampleClassParam", "exitingExampleMethodParam",
Arrays.asList("array of Strings", "that are the exit", "result"));
String methodRE = "JulFlow";
assertSysoutMatches(
log4j(org.apache.logging.log4j.Level.TRACE, true, methodRE,
"RETURN.*array of Str.*that are.*result", null)
+ jul(java.util.logging.Level.FINER, "exitingExampleClassParam exitingExampleMethodParam",
"RETURN.*array of Str.*that are.*result", null)
);
}
@Test
public void test3JulFlow4() {
// note: manually given source information get lost in log4j!
julLog.throwing("throwingExampleClassParam", "throwingExampleMethodParam",
new IllegalStateException("ONLY TEST for JUL-throwing()"));
String methodRE = "JulFlow";
assertSysoutMatches(
log4j(org.apache.logging.log4j.Level.TRACE, true, methodRE,
"THROW", "IllegalStateException.*ONLY TEST for JUL-throwing")
+ jul(java.util.logging.Level.FINER, "throwingExampleClassParam throwingExampleMethodParam",
"THROW", "IllegalStateException.*ONLY TEST for JUL-throwing")
);
}
@Test
public void test4JulSpecials1() {
julLog.log(java.util.logging.Level.WARNING, "JUL-Test via log() as warning with exception",
new java.util.zip.DataFormatException("ONLY TEST for JUL.log()"));
String methodRE = "JulSpecials";
assertSysoutMatches(
log4j(org.apache.logging.log4j.Level.WARN, true, methodRE,
"JUL-Test via log\\(\\) as warning", "DataFormatException.*ONLY TEST for JUL")
+ jul(java.util.logging.Level.WARNING, methodRE, "JUL-Test via log\\(\\) as warning",
"DataFormatException.*ONLY TEST for JUL")
);
}
@Test
public void test4JulSpecials2() {
// test with MessageFormat
julLog.log(java.util.logging.Level.INFO, "JUL-Test via log() with parameters (0={0}, 1={1}, 2={2,number,##000.0})",
new Object[] {"a", "b", 42} );
String methodRE = "JulSpecials";
assertSysoutMatches(
log4j(org.apache.logging.log4j.Level.INFO, true, methodRE,
"JUL-Test via log\\(\\) with parameters \\(0=a, 1=b, 2=042.0\\)", null)
+ jul(java.util.logging.Level.INFO, methodRE,
"JUL-Test via log\\(\\) with parameters \\(0=a, 1=b, 2=042.0\\)", null)
);
}
// no test for logrb(ResourceBundle)-case as this is very specific and seldom used (in
// my opinion); and it does not add any real thing to test here
private void assertLogLevel(String loggerName, java.util.logging.Level julLevel) {
java.util.logging.Logger lg = java.util.logging.LogManager.getLogManager().getLogger(loggerName);
assertEquals("Logger '" + loggerName + "'", julLevel, (lg == null ? null : lg.getLevel()));
}
@Test
public void test5LevelPropFromConfigFile() {
// JUL levels are set from config files and the initial propagation
assertLogLevel("", java.util.logging.Level.FINE);
assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1", java.util.logging.Level.FINE);
assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested1", java.util.logging.Level.FINER);
assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested2.deeplyNested", java.util.logging.Level.WARNING);
assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate2", java.util.logging.Level.ALL);
assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate2.nested.deeplyNested", java.util.logging.Level.INFO);
// these are set in logging.properties but not in log4j2.xml:
assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate2.nested", null);
assertLogLevel("javax.mail", null);
// these should not exist:
assertLogLevel("log4j.Log4jBridgeHandlerTest", null);
assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested", null);
assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested1.deeplyNested", null);
}
@Test
public void test5LevelPropSetLevel() {
String name = "log4j.test.new_logger_level_set";
Configurator.setLevel(name, org.apache.logging.log4j.Level.DEBUG);
assertLogLevel(name, java.util.logging.Level.FINE);
test5LevelPropFromConfigFile(); // the rest should be untouched!
name = "log4j.Log4jBridgeHandlerTest.propagate1.nested1";
Configurator.setLevel(name, org.apache.logging.log4j.Level.WARN);
assertLogLevel(name, java.util.logging.Level.WARNING);
// the others around should be untouched
assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1", java.util.logging.Level.FINE);
assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested2.deeplyNested", java.util.logging.Level.WARNING);
// note: no need to check for the other set[Root]Level() methods, because they all call
// loggerContext.updateLoggers() which calls firePropertyChangeEvent()
}
@Test
public void test5LevelPropGC() {
// this test will fail if you comment out "julLoggerRefs.add(julLog);" in propagateLogLevels()
test5LevelPropFromConfigFile(); // at start, all should be fine
java.util.logging.Logger julLogRef = java.util.logging.Logger
.getLogger("log4j.Log4jBridgeHandlerTest.propagate1.nested1");
System.gc(); // a single call is sufficient
System.out.println("sysout: test5LevelPropGC() still has reference to JUL-logger: "
+ julLogRef.getName() + " / " + julLogRef);
try {
test5LevelPropFromConfigFile(); // even after GC the not referenced loggers should still be there
} catch (Throwable t) {
debugPrintJulLoggers("After GC");
// => JUL root logger, above explicitly referenced logger and its parent ("...propagate1")
// and the global referenced julLog ("...jul.Log4jBridgeHandlerTest") are still there, the
// others are null-references now
throw t;
}
}
/** Print all available JUL loggers to stdout. */
private static void debugPrintJulLoggers(String infoStr) {
java.util.logging.LogManager julMgr = java.util.logging.LogManager.getLogManager();
System.out.println("sysout: " + infoStr + " - for " + julMgr);
java.util.List<String> txt = new java.util.ArrayList<>();
int n = 1;
for (Enumeration<String> en = julMgr.getLoggerNames(); en.hasMoreElements(); ) {
String ln = en.nextElement();
java.util.logging.Logger lg = julMgr.getLogger(ln);
if (lg == null) {
txt.add("(!null-Logger '" + ln + "') #" + n);
} else if (lg.getLevel() == null) {
txt.add("(null-Level Logger '" + ln + "') #" + n);
} else {
txt.add("Logger '" + ln + "', lvl = " + lg.getLevel() + " #" + n);
}
n++;
} // for
java.util.Collections.sort(txt, String.CASE_INSENSITIVE_ORDER);
for (String s : txt) {
System.out.println(" - " + s);
}
}
////////////////
//////////////// INTERNAL DEVELOPER TESTS follow
//////////////// (these are NOT neccessary for unit test but source is kept here for reference and info)
static {
if (DEVTEST) {
System.out.println("sysout: static init. BEGIN");
// get log4j context impl. (requires log4j-core!)
// note: "LogManager.getContext();" does not work, it returns another instance!?!
LoggerContext context = LoggerContext.getContext(false); // this matches Configurator.setLevel() impl.
Configuration cfg = context.getConfiguration();
// print real loggers (=> is empty when using LogManager.getContext()!?! only contains already instantiated loggers)
System.out.println("LogCtx " + context + " '" + context.getName() + "', loc = "
+ context.getConfigLocation() + ", cfg = " + cfg + " = " + System.identityHashCode(cfg));
for (org.apache.logging.log4j.Logger lg : context.getLoggers()) {
System.out.println("- Logger '" + lg.getName() + "', lvl = " + lg.getLevel());
}
// print logger configurations (=> all loggers with level are present here)
System.out.println("Loggers in Cfg:");
for (Entry<String, LoggerConfig> entry : cfg.getLoggers().entrySet()) {
LoggerConfig lcfg = entry.getValue();
System.out.println("- '" + entry.getKey() + "' = '" + lcfg.getName() + "' / "
+ lcfg.getLevel() + "; " + lcfg);
}
// print JUL loggers (=> is completely init. here, even if first JUL log and BridgeHandler-creation happens later)
debugPrintJulLoggers("in static-class-init");
/* java.util.logging.LogManager julMgr = java.util.logging.LogManager.getLogManager();
System.out.println("\nJUL-Loggers for " + julMgr);
for (Enumeration<String> en = julMgr.getLoggerNames(); en.hasMoreElements(); ) {
String ln = en.nextElement();
java.util.logging.Logger lg = julMgr.getLogger(ln);
if (lg.getLevel() == null) {
System.out.println("- (null-Level Logger '" + ln + "')");
} else {
System.out.println("- Logger '" + ln + "', lvl = " + lg.getLevel());
}
} */
// changing of log4j config. is to be done via log4j.core.config.Configurator,
// e.g. setLevel(loggerName, newLevel)
// Note: the (internal) log4j.core.Logger has a setLevel() but this should not be used.
CfgListener listener = new CfgListener();
cfg.addListener(listener); // => onChange() is never called: not on start, not on setLevel
context.addPropertyChangeListener(listener);
System.out.println("sysout: static init. END");
} // if
}
private static void devTestBeforeClass() {
log4jLog.info("Dummy-Start-Log in beforeClass()"); // force init. of log4j (needed?? does not harm)
@SuppressWarnings("resource")
LoggerContext context = LoggerContext.getContext(false); // this matches Configurator.setLevel() impl. (instead of "LogManager.getContext();")
System.out.println("beforeClass(): LogCtx " + context + " '" + context.getName() + "', loc = " + context.getConfigLocation()
+ ", cfg = " + context.getConfiguration());
for (org.apache.logging.log4j.Logger lg : context.getLoggers()) {
System.out.println("- Logger '" + lg.getName() + "', lvl = " + lg.getLevel());
}
// force level change
System.out.println("sysout: now calling log4j-setLevel()");
Configurator.setLevel("log4jTest.Dummy_set_in_devTestBeforeClass", org.apache.logging.log4j.Level.DEBUG);
}
private static class CfgListener implements ConfigurationListener, PropertyChangeListener {
public CfgListener() {
System.out.println("sysout: CfgListener created: " + this);
}
@Override
public void onChange(Reconfigurable reconfigurable) { // from ConfigurationListener
System.out.println("sysout: CfgListener.CfgLi-onChange(): " + reconfigurable
+ " = " + System.identityHashCode(reconfigurable));
}
@Override
public void propertyChange(PropertyChangeEvent evt) { // from PropertyChangeListener
System.out.println("sysout: CfgListener.PropChLi-propertyChange(): " + evt);
}
}
}