From 97f96c5ad52259c2e620370309cf129b7ba30f54 Mon Sep 17 00:00:00 2001 From: Alexander Dorokhine Date: Sat, 6 Dec 2014 14:33:22 -0800 Subject: [PATCH] A field trial for the automatically named logger. Can be enabled with -Dorg.slf4j.LoggerFactory.autoNamedLoggerFieldTrial=true. Prints an error (and stacktrace) to stderr when a mismatch between custom and automatic logger names is detected. --- .../main/java/org/slf4j/LoggerFactory.java | 24 +++- .../src/main/java/org/slf4j/helpers/Util.java | 44 +++++- .../slf4j/AutoNamedLoggerFieldTrialTest.java | 125 ++++++++++++++++++ 3 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 slf4j-simple/src/test/java/org/slf4j/AutoNamedLoggerFieldTrialTest.java diff --git a/slf4j-api/src/main/java/org/slf4j/LoggerFactory.java b/slf4j-api/src/main/java/org/slf4j/LoggerFactory.java index b4808da9..73451f6b 100644 --- a/slf4j-api/src/main/java/org/slf4j/LoggerFactory.java +++ b/slf4j-api/src/main/java/org/slf4j/LoggerFactory.java @@ -82,6 +82,13 @@ public final class LoggerFactory { static SubstituteLoggerFactory TEMP_FACTORY = new SubstituteLoggerFactory(); static NOPLoggerFactory NOP_FALLBACK_FACTORY = new NOPLoggerFactory(); + // Support for the automatically-named logger field trial. + static final String AUTO_NAMED_LOGGER_FIELD_TRIAL_PROPERTY = + "org.slf4j.LoggerFactory.autoNamedLoggerFieldTrial"; + static boolean AUTO_NAMED_LOGGER_FIELD_TRIAL = + Boolean.getBoolean(AUTO_NAMED_LOGGER_FIELD_TRIAL_PROPERTY); + + /** * It is LoggerFactory's responsibility to track version changes and manage * the compatibility list. @@ -293,8 +300,21 @@ public final class LoggerFactory { * @return logger */ @Nonnull - public static Logger getLogger(@Nonnull Class clazz) { - return getLogger(clazz.getName()); + public static Logger getLogger(@Nonnull Class clazz) { + Logger logger = getLogger(clazz.getName()); + if (AUTO_NAMED_LOGGER_FIELD_TRIAL) { + String autoName = Util.getCallingClassName(); + if (!logger.getName().equals(autoName)) { + IllegalStateException exception = new IllegalStateException(String.format( + "Auto-named logger field trial: mismatch detected between " + + "given logger name and automatic logger name. Given name: \"%s\"; " + + "automatic name: \"%s\". If this is unexpected, please file a bug " + + "against slf4j. Set property %s to \"false\" to disable this check.", + logger.getName(), autoName, AUTO_NAMED_LOGGER_FIELD_TRIAL_PROPERTY)); + exception.printStackTrace(System.err); + } + } + return logger; } /** diff --git a/slf4j-api/src/main/java/org/slf4j/helpers/Util.java b/slf4j-api/src/main/java/org/slf4j/helpers/Util.java index d0f2f9d1..7c2fddc5 100644 --- a/slf4j-api/src/main/java/org/slf4j/helpers/Util.java +++ b/slf4j-api/src/main/java/org/slf4j/helpers/Util.java @@ -31,8 +31,48 @@ package org.slf4j.helpers; * * @author Ceki Gülcü */ -public class Util { - +public final class Util { + + private Util() {} + + /** + * In order to call {@link SecurityManager#getClassContext()}, which is a + * protected method, we add this wrapper which allows the method to be visible + * inside this package. + */ + private static final class ClassContextSecurityManager extends SecurityManager { + protected Class[] getClassContext() { + return super.getClassContext(); + } + }; + private static final ClassContextSecurityManager SECURITY_MANAGER = + new ClassContextSecurityManager(); + + /** + * Returns the name of the class which called the invoking method. + * @return the name of the class which called the invoking method. + */ + public static String getCallingClassName() { + Class[] trace = SECURITY_MANAGER.getClassContext(); + String thisClassName = Util.class.getName(); + + // Advance until Util is found + int i; + for (i = 0 ; i < trace.length ; i++) { + if (thisClassName.equals(trace[i].getName())) + break; + } + + // trace[i] = Util; trace[i+1] = caller; trace[i+2] = caller's caller + if (i >= trace.length || i + 2 >= trace.length) { + throw new IllegalStateException( + "Failed to find org.slf4j.helpers.Util or its caller in the stack; " + + "this should not happen"); + } + + return trace[i+2].getName(); + } + static final public void report(String msg, Throwable t) { System.err.println(msg); System.err.println("Reported exception:"); diff --git a/slf4j-simple/src/test/java/org/slf4j/AutoNamedLoggerFieldTrialTest.java b/slf4j-simple/src/test/java/org/slf4j/AutoNamedLoggerFieldTrialTest.java new file mode 100644 index 00000000..bbc254c2 --- /dev/null +++ b/slf4j-simple/src/test/java/org/slf4j/AutoNamedLoggerFieldTrialTest.java @@ -0,0 +1,125 @@ +/** + * Copyright (c) 2004-2011 QOS.ch + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +package org.slf4j; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests that the automatically named logger field trial works and + * doesn't cause problems or trigger if disabled. + *

+ * This test can't live inside slf4j-api because the NOP Logger doesn't + * remember its name. + */ +public class AutoNamedLoggerFieldTrialTest { + + private static final String MISMATCH_STRING = "Auto-named logger field trial: mismatch detected"; + + private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + private final PrintStream oldErr = System.err; + + @Before + public void setUp() { + System.setErr(new PrintStream(byteArrayOutputStream)); + } + + @After + public void tearDown() { + setTrialEnabled(false); + System.setErr(oldErr); + } + + /* + * Pass in the wrong class to the Logger with the field trial disabled, and + * make sure there are no errors. + */ + @Test + public void testNoTriggerWithoutProperty() { + setTrialEnabled(false); + Logger logger = LoggerFactory.getLogger(String.class); + assertEquals("java.lang.String", logger.getName()); + assertIsMismatch(false); + } + + /* + * Pass in the wrong class to the Logger with the field trial enabled, and + * make sure there ARE errors. + */ + @Test + public void testTriggerWithProperty() { + setTrialEnabled(true); + LoggerFactory.getLogger(String.class); + assertIsMismatch(true); + } + + /* + * Checks the whole error message to ensure all the names show up correctly. + */ + @Test + public void testTriggerWholeMessage() { + setTrialEnabled(true); + LoggerFactory.getLogger(String.class); + assertTrue(String.valueOf(byteArrayOutputStream).contains( + "Auto-named logger field trial: mismatch detected between given logger " + + "name and automatic logger name. Given name: \"java.lang.String\"; " + + "automatic name: \"org.slf4j.AutoNamedLoggerFieldTrialTest\". If this " + + "is unexpected, please file a bug against slf4j. Set property " + + "org.slf4j.LoggerFactory.autoNamedLoggerFieldTrial to \"false\" to " + + "disable this check.")); + } + + /* + * Checks that there are no errors with the trial enabled if the + * class matches. + */ + @Test + public void testPassIfMatch() { + setTrialEnabled(true); + Logger logger = LoggerFactory.getLogger(AutoNamedLoggerFieldTrialTest.class); + assertEquals("org.slf4j.AutoNamedLoggerFieldTrialTest", logger.getName()); + assertIsMismatch(false); + } + + private void assertIsMismatch(boolean isMismatch) { + assertEquals( + isMismatch, + String.valueOf(byteArrayOutputStream).contains(MISMATCH_STRING)); + } + + private static void setTrialEnabled(boolean enabled) { + // The system property is read into a static variable at initialization time + // so we cannot just reset the system property to test this feature. + // Therefore we set the variable directly. + LoggerFactory.AUTO_NAMED_LOGGER_FIELD_TRIAL = enabled; + } +}