diff --git a/log4j-api-to-slf4j/pom.xml b/log4j-api-to-slf4j/pom.xml new file mode 100644 index 0000000..4ff29b1 --- /dev/null +++ b/log4j-api-to-slf4j/pom.xml @@ -0,0 +1,137 @@ + + + + 4.0.0 + + org.apache.logging.log4j + log4j-slf4j-parent + ${revision} + ../parent + + + log4j-api-to-slf4j + + Log4j API to SLF4J logging bridge + + Forwards the Log4j API calls to SLF4J. + (Refer to the `log4j-slf4j[2]-impl` artifacts for forwarding SLF4J to the Log4j API.) + + + + 1.5.12 + 1.8.0 + + + [1.7,3) + + + org.slf4j.*;version="${slf4j.support.range}" + + + + + + + org.osgi + org.osgi.framework + ${osgi.framework.version} + provided + + + + org.apache.logging.log4j + log4j-api + + + + org.slf4j + slf4j-api + + + + org.assertj + assertj-core + test + + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-params + test + + + + ch.qos.logback + logback-classic + ${logback.version} + test + + + + ch.qos.logback + logback-core + ${logback.version} + test + + + + ch.qos.logback + logback-core + ${logback.version} + test-jar + test + + + + + org.apache.logging.log4j + log4j-api-test + test + + + + org.mockito + mockito-core + test + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + + + + + diff --git a/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/Activator.java b/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/Activator.java new file mode 100644 index 0000000..5080869 --- /dev/null +++ b/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/Activator.java @@ -0,0 +1,31 @@ +/* + * 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.slf4j; + +import org.apache.logging.log4j.util.ProviderActivator; +import org.osgi.annotation.bundle.Header; + +@Header(name = org.osgi.framework.Constants.BUNDLE_ACTIVATOR, value = "${@class}") +@Header( + name = org.osgi.framework.Constants.BUNDLE_ACTIVATIONPOLICY, + value = org.osgi.framework.Constants.ACTIVATION_LAZY) +public class Activator extends ProviderActivator { + + public Activator() { + super(new SLF4JProvider()); + } +} diff --git a/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/MDCContextMap.java b/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/MDCContextMap.java new file mode 100644 index 0000000..4f2ef19 --- /dev/null +++ b/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/MDCContextMap.java @@ -0,0 +1,107 @@ +/* + * 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.slf4j; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import org.apache.logging.log4j.spi.CleanableThreadContextMap; +import org.apache.logging.log4j.util.SortedArrayStringMap; +import org.apache.logging.log4j.util.StringMap; +import org.slf4j.MDC; + +/** + * Bind the ThreadContextMap to the SLF4J MDC. + */ +public class MDCContextMap implements CleanableThreadContextMap { + + private static final StringMap EMPTY_CONTEXT_DATA = new SortedArrayStringMap(1); + + static { + EMPTY_CONTEXT_DATA.freeze(); + } + + @Override + public void put(final String key, final String value) { + MDC.put(key, value); + } + + @Override + public void putAll(final Map m) { + for (final Entry entry : m.entrySet()) { + MDC.put(entry.getKey(), entry.getValue()); + } + } + + @Override + public String get(final String key) { + return MDC.get(key); + } + + @Override + public void remove(final String key) { + MDC.remove(key); + } + + @Override + public void removeAll(final Iterable keys) { + for (final String key : keys) { + MDC.remove(key); + } + } + + @Override + public void clear() { + MDC.clear(); + } + + @Override + public boolean containsKey(final String key) { + final Map map = MDC.getCopyOfContextMap(); + return map != null && map.containsKey(key); + } + + @Override + public Map getCopy() { + final Map contextMap = MDC.getCopyOfContextMap(); + return contextMap != null ? contextMap : new HashMap<>(); + } + + @Override + public Map getImmutableMapOrNull() { + return MDC.getCopyOfContextMap(); + } + + @Override + public boolean isEmpty() { + final Map map = MDC.getCopyOfContextMap(); + return map == null || map.isEmpty(); + } + + @Override + public StringMap getReadOnlyContextData() { + final Map copy = getCopy(); + if (copy.isEmpty()) { + return EMPTY_CONTEXT_DATA; + } + final StringMap result = new SortedArrayStringMap(); + for (final Entry entry : copy.entrySet()) { + result.putValue(entry.getKey(), entry.getValue()); + } + return result; + } +} diff --git a/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogBuilder.java b/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogBuilder.java new file mode 100644 index 0000000..0d6df98 --- /dev/null +++ b/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogBuilder.java @@ -0,0 +1,267 @@ +/* + * 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.slf4j; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogBuilder; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.spi.ExtendedLogger; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.LambdaUtil; +import org.apache.logging.log4j.util.StackLocatorUtil; +import org.apache.logging.log4j.util.Supplier; + +public class SLF4JLogBuilder implements LogBuilder { + + private static Message EMPTY_MESSAGE = new SimpleMessage(""); + private static final String FQCN = SLF4JLogBuilder.class.getName(); + private static final Logger LOGGER = StatusLogger.getLogger(); + + private ExtendedLogger logger; + private Level level; + private Marker marker; + private Throwable throwable; + private volatile boolean inUse; + private final long threadId; + + public SLF4JLogBuilder(final SLF4JLogger logger, final Level level) { + this.logger = logger; + this.level = level; + this.threadId = Thread.currentThread().getId(); + this.inUse = level != null; + } + + public SLF4JLogBuilder() { + this(null, null); + } + + public LogBuilder reset(final SLF4JLogger logger, final Level level) { + this.logger = logger; + this.level = level; + this.marker = null; + this.throwable = null; + this.inUse = true; + return this; + } + + public boolean isInUse() { + return this.inUse; + } + + private boolean isValid() { + if (!inUse) { + LOGGER.warn("Attempt to reuse LogBuilder was ignored. {}", StackLocatorUtil.getCallerClass(2)); + return false; + } + if (this.threadId != Thread.currentThread().getId()) { + LOGGER.warn("LogBuilder can only be used on the owning thread. {}", StackLocatorUtil.getCallerClass(2)); + return false; + } + return true; + } + + private void logMessage(Message message) { + try { + logger.logMessage(FQCN, level, marker, message, throwable); + } finally { + inUse = false; + } + } + + @Override + public LogBuilder withMarker(final Marker marker) { + this.marker = marker; + return this; + } + + @Override + public LogBuilder withThrowable(final Throwable throwable) { + this.throwable = throwable; + return this; + } + + @Override + public LogBuilder withLocation() { + LOGGER.info("Call to withLocation() ignored since SLF4J does not support setting location information."); + return this; + } + + @Override + public LogBuilder withLocation(final StackTraceElement location) { + return withLocation(); + } + + @Override + public void log(CharSequence message) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message)); + } + } + + @Override + public void log(String message) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message)); + } + } + + @Override + public void log(String message, Object... params) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, params)); + } + } + + @Override + public void log(String message, Supplier... params) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, LambdaUtil.getAll(params))); + } + } + + @Override + public void log(Message message) { + if (isValid()) { + logMessage(message); + } + } + + @Override + public void log(final Supplier messageSupplier) { + if (isValid()) { + logMessage(messageSupplier.get()); + } + } + + @Override + public Message logAndGet(final Supplier messageSupplier) { + Message message = null; + if (isValid()) { + logMessage(message = messageSupplier.get()); + } + return message; + } + + @Override + public void log(Object message) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message)); + } + } + + @Override + public void log(String message, Object p0) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0)); + } + } + + @Override + public void log(String message, Object p0, Object p1) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1)); + } + } + + @Override + public void log(String message, Object p0, Object p1, Object p2) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2)); + } + } + + @Override + public void log(String message, Object p0, Object p1, Object p2, Object p3) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3)); + } + } + + @Override + public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4)); + } + } + + @Override + public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5)); + } + } + + @Override + public void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6)); + } + } + + @Override + public void log( + String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7)); + } + } + + @Override + public void log( + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8)); + } + } + + @Override + public void log( + String message, + Object p0, + Object p1, + Object p2, + Object p3, + Object p4, + Object p5, + Object p6, + Object p7, + Object p8, + Object p9) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)); + } + } + + @Override + public void log() { + if (isValid()) { + logMessage(EMPTY_MESSAGE); + } + } +} diff --git a/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java b/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java new file mode 100644 index 0000000..26e94c6 --- /dev/null +++ b/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java @@ -0,0 +1,381 @@ +/* + * 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.slf4j; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogBuilder; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.message.LoggerNameAwareMessage; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.spi.AbstractLogger; +import org.apache.logging.log4j.util.Constants; +import org.slf4j.LoggerFactory; +import org.slf4j.MarkerFactory; +import org.slf4j.spi.LocationAwareLogger; + +public class SLF4JLogger extends AbstractLogger { + + private static final long serialVersionUID = 1L; + /** + * Logback supports turbo filters, that can override the logger's level. + * Therefore we can never return a no-op builder. + */ + private static final boolean LAZY_LEVEL_CHECK = "ch.qos.logback.classic.LoggerContext" + .equals(LoggerFactory.getILoggerFactory().getClass().getName()); + + private static final ThreadLocal logBuilder = ThreadLocal.withInitial(SLF4JLogBuilder::new); + + private final org.slf4j.Logger logger; + private final LocationAwareLogger locationAwareLogger; + + public SLF4JLogger(final String name, final MessageFactory messageFactory, final org.slf4j.Logger logger) { + super(name, messageFactory); + this.logger = logger; + this.locationAwareLogger = logger instanceof LocationAwareLogger ? (LocationAwareLogger) logger : null; + } + + public SLF4JLogger(final String name, final org.slf4j.Logger logger) { + super(name); + this.logger = logger; + this.locationAwareLogger = logger instanceof LocationAwareLogger ? (LocationAwareLogger) logger : null; + } + + private int convertLevel(final Level level) { + switch (level.getStandardLevel()) { + case DEBUG: + return LocationAwareLogger.DEBUG_INT; + case TRACE: + return LocationAwareLogger.TRACE_INT; + case INFO: + return LocationAwareLogger.INFO_INT; + case WARN: + return LocationAwareLogger.WARN_INT; + case ERROR: + return LocationAwareLogger.ERROR_INT; + default: + return LocationAwareLogger.ERROR_INT; + } + } + + @Override + public Level getLevel() { + if (logger.isTraceEnabled()) { + return Level.TRACE; + } + if (logger.isDebugEnabled()) { + return Level.DEBUG; + } + if (logger.isInfoEnabled()) { + return Level.INFO; + } + if (logger.isWarnEnabled()) { + return Level.WARN; + } + if (logger.isErrorEnabled()) { + return Level.ERROR; + } + // Option: throw new IllegalStateException("Unknown SLF4JLevel"); + // Option: return Level.ALL; + return Level.OFF; + } + + public org.slf4j.Logger getLogger() { + return locationAwareLogger != null ? locationAwareLogger : logger; + } + + private static org.slf4j.Marker getMarker(final Marker marker) { + // No marker is provided in the common case, small methods + // are optimized more effectively. + return marker == null ? null : convertMarker(marker); + } + + private static org.slf4j.Marker convertMarker(final Marker marker) { + final org.slf4j.Marker slf4jMarker = MarkerFactory.getMarker(marker.getName()); + final Marker[] parents = marker.getParents(); + if (parents != null) { + for (final Marker parent : parents) { + final org.slf4j.Marker slf4jParent = getMarker(parent); + if (!slf4jMarker.contains(slf4jParent)) { + slf4jMarker.add(slf4jParent); + } + } + } + return slf4jMarker; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final Message data, final Throwable t) { + return isEnabledFor(level, marker); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final CharSequence data, final Throwable t) { + return isEnabledFor(level, marker); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final Object data, final Throwable t) { + return isEnabledFor(level, marker); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String data) { + return isEnabledFor(level, marker); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String data, final Object... p1) { + return isEnabledFor(level, marker); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0) { + return isEnabledFor(level, marker); + } + + @Override + public boolean isEnabled( + final Level level, final Marker marker, final String message, final Object p0, final Object p1) { + return isEnabledFor(level, marker); + } + + @Override + public boolean isEnabled( + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2) { + return isEnabledFor(level, marker); + } + + @Override + public boolean isEnabled( + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3) { + return isEnabledFor(level, marker); + } + + @Override + public boolean isEnabled( + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4) { + return isEnabledFor(level, marker); + } + + @Override + public boolean isEnabled( + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5) { + return isEnabledFor(level, marker); + } + + @Override + public boolean isEnabled( + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6) { + return isEnabledFor(level, marker); + } + + @Override + public boolean isEnabled( + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6, + final Object p7) { + return isEnabledFor(level, marker); + } + + @Override + public boolean isEnabled( + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6, + final Object p7, + final Object p8) { + return isEnabledFor(level, marker); + } + + @Override + public boolean isEnabled( + final Level level, + final Marker marker, + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5, + final Object p6, + final Object p7, + final Object p8, + final Object p9) { + return isEnabledFor(level, marker); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String data, final Throwable t) { + return isEnabledFor(level, marker); + } + + private boolean isEnabledFor(final Level level, final Marker marker) { + final org.slf4j.Marker slf4jMarker = getMarker(marker); + switch (level.getStandardLevel()) { + case DEBUG: + return logger.isDebugEnabled(slf4jMarker); + case TRACE: + return logger.isTraceEnabled(slf4jMarker); + case INFO: + return logger.isInfoEnabled(slf4jMarker); + case WARN: + return logger.isWarnEnabled(slf4jMarker); + case ERROR: + return logger.isErrorEnabled(slf4jMarker); + default: + return logger.isErrorEnabled(slf4jMarker); + } + } + + @Override + public void logMessage( + final String fqcn, final Level level, final Marker marker, final Message message, final Throwable t) { + final org.slf4j.Marker slf4jMarker = getMarker(marker); + final String formattedMessage = message.getFormattedMessage(); + if (locationAwareLogger != null) { + if (message instanceof LoggerNameAwareMessage) { + ((LoggerNameAwareMessage) message).setLoggerName(getName()); + } + locationAwareLogger.log(slf4jMarker, fqcn, convertLevel(level), formattedMessage, null, t); + } else { + switch (level.getStandardLevel()) { + case DEBUG: + logger.debug(slf4jMarker, formattedMessage, t); + break; + case TRACE: + logger.trace(slf4jMarker, formattedMessage, t); + break; + case INFO: + logger.info(slf4jMarker, formattedMessage, t); + break; + case WARN: + logger.warn(slf4jMarker, formattedMessage, t); + break; + case ERROR: + logger.error(slf4jMarker, formattedMessage, t); + break; + default: + logger.error(slf4jMarker, formattedMessage, t); + break; + } + } + } + + @Override + public LogBuilder always() { + return atLevel(Level.OFF); + } + + @Override + public LogBuilder atTrace() { + return atLevel(Level.TRACE); + } + + @Override + public LogBuilder atDebug() { + return atLevel(Level.DEBUG); + } + + @Override + public LogBuilder atInfo() { + return atLevel(Level.INFO); + } + + @Override + public LogBuilder atWarn() { + return atLevel(Level.WARN); + } + + @Override + public LogBuilder atError() { + return atLevel(Level.ERROR); + } + + @Override + public LogBuilder atFatal() { + return atLevel(Level.TRACE); + } + + @Override + protected LogBuilder getLogBuilder(final Level level) { + final SLF4JLogBuilder builder = logBuilder.get(); + return Constants.ENABLE_THREADLOCALS && !builder.isInUse() + ? builder.reset(this, level) + : new SLF4JLogBuilder(this, level); + } + + @Override + public LogBuilder atLevel(final Level level) { + // TODO: wrap SLF4J 2.x LoggingEventBuilder + if (LAZY_LEVEL_CHECK) { + return getLogBuilder(level); + } + return super.atLevel(level); + } +} diff --git a/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContext.java b/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContext.java new file mode 100644 index 0000000..407a963 --- /dev/null +++ b/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContext.java @@ -0,0 +1,77 @@ +/* + * 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.slf4j; + +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.message.ParameterizedMessageFactory; +import org.apache.logging.log4j.spi.ExtendedLogger; +import org.apache.logging.log4j.spi.LoggerContext; +import org.apache.logging.log4j.spi.LoggerRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SLF4JLoggerContext implements LoggerContext { + + private final LoggerRegistry loggerRegistry = new LoggerRegistry<>(); + + private static final MessageFactory DEFAULT_MESSAGE_FACTORY = ParameterizedMessageFactory.INSTANCE; + + @Override + public Object getExternalContext() { + return null; + } + + @Override + public ExtendedLogger getLogger(final String name) { + return getLogger(name, DEFAULT_MESSAGE_FACTORY); + } + + @Override + public ExtendedLogger getLogger(final String name, final MessageFactory messageFactory) { + final MessageFactory effectiveMessageFactory = + messageFactory != null ? messageFactory : DEFAULT_MESSAGE_FACTORY; + final ExtendedLogger oldLogger = loggerRegistry.getLogger(name, effectiveMessageFactory); + if (oldLogger != null) { + return oldLogger; + } + final ExtendedLogger newLogger = createLogger(name, effectiveMessageFactory); + loggerRegistry.putIfAbsent(name, effectiveMessageFactory, newLogger); + return loggerRegistry.getLogger(name, effectiveMessageFactory); + } + + private static ExtendedLogger createLogger(final String name, final MessageFactory messageFactory) { + final Logger logger = LoggerFactory.getLogger(name); + return new SLF4JLogger(name, messageFactory, logger); + } + + @Override + public boolean hasLogger(final String name) { + return loggerRegistry.hasLogger(name, DEFAULT_MESSAGE_FACTORY); + } + + @Override + public boolean hasLogger(final String name, final MessageFactory messageFactory) { + final MessageFactory effectiveMessageFactory = + messageFactory != null ? messageFactory : DEFAULT_MESSAGE_FACTORY; + return loggerRegistry.hasLogger(name, effectiveMessageFactory); + } + + @Override + public boolean hasLogger(final String name, final Class messageFactoryClass) { + return loggerRegistry.hasLogger(name, messageFactoryClass); + } +} diff --git a/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContextFactory.java b/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContextFactory.java new file mode 100644 index 0000000..e1ca5d4 --- /dev/null +++ b/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContextFactory.java @@ -0,0 +1,69 @@ +/* + * 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.slf4j; + +import java.net.URI; +import org.apache.logging.log4j.spi.LoggerContext; +import org.apache.logging.log4j.spi.LoggerContextFactory; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.LoaderUtil; + +public class SLF4JLoggerContextFactory implements LoggerContextFactory { + private static final StatusLogger LOGGER = StatusLogger.getLogger(); + private static final LoggerContext context = new SLF4JLoggerContext(); + + public SLF4JLoggerContextFactory() { + // LOG4J2-230, LOG4J2-204 (improve error reporting when misconfigured) + boolean misconfigured = false; + try { + LoaderUtil.loadClass("org.slf4j.helpers.Log4jLoggerFactory"); + misconfigured = true; + } catch (final ClassNotFoundException classNotFoundIsGood) { + LOGGER.debug("org.slf4j.helpers.Log4jLoggerFactory is not on classpath. Good!"); + } + if (misconfigured) { + throw new IllegalStateException("slf4j-impl jar is mutually exclusive with log4j-to-slf4j jar " + + "(the first routes calls from SLF4J to Log4j, the second from Log4j to SLF4J)"); + } + } + + @Override + public LoggerContext getContext( + final String fqcn, final ClassLoader loader, final Object externalContext, final boolean currentContext) { + return context; + } + + @Override + public LoggerContext getContext( + final String fqcn, + final ClassLoader loader, + final Object externalContext, + final boolean currentContext, + final URI configLocation, + final String name) { + return context; + } + + @Override + public void removeContext(final LoggerContext ignored) {} + + @Override + public boolean isClassLoaderDependent() { + // context is always used + return false; + } +} diff --git a/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JProvider.java b/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JProvider.java new file mode 100644 index 0000000..52a11a5 --- /dev/null +++ b/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JProvider.java @@ -0,0 +1,49 @@ +/* + * 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.slf4j; + +import aQute.bnd.annotation.Resolution; +import aQute.bnd.annotation.spi.ServiceProvider; +import org.apache.logging.log4j.spi.LoggerContextFactory; +import org.apache.logging.log4j.spi.Provider; +import org.apache.logging.log4j.spi.ThreadContextMap; +import org.jspecify.annotations.NullMarked; + +/** + * Bind the Log4j API to SLF4J. + */ +@NullMarked +@ServiceProvider(value = Provider.class, resolution = Resolution.OPTIONAL) +public class SLF4JProvider extends Provider { + + private static final LoggerContextFactory CONTEXT_FACTORY = new SLF4JLoggerContextFactory(); + private static final ThreadContextMap THREAD_CONTEXT_MAP = new MDCContextMap(); + + public SLF4JProvider() { + super(15, CURRENT_VERSION, SLF4JLoggerContextFactory.class, MDCContextMap.class); + } + + @Override + public LoggerContextFactory getLoggerContextFactory() { + return CONTEXT_FACTORY; + } + + @Override + public ThreadContextMap getThreadContextMapInstance() { + return THREAD_CONTEXT_MAP; + } +} diff --git a/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/package-info.java b/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/package-info.java new file mode 100644 index 0000000..12e4bed --- /dev/null +++ b/log4j-api-to-slf4j/src/main/java/org/apache/logging/slf4j/package-info.java @@ -0,0 +1,25 @@ +/* + * 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. + */ +/** + * SLF4J support. + */ +@Export +@Version("2.24.1") +package org.apache.logging.slf4j; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/CallerInformationTest.java b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/CallerInformationTest.java new file mode 100644 index 0000000..ab6c5bc --- /dev/null +++ b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/CallerInformationTest.java @@ -0,0 +1,61 @@ +/* + * 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.slf4j; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.testUtil.StringListAppender; +import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; + +@UsingStatusListener +@LoggerContextSource +public class CallerInformationTest { + + @Test + public void testClassLogger() throws Exception { + final SLF4JLogger logger = (SLF4JLogger) LogManager.getLogger("ClassLogger"); + final StringListAppender app = TestUtil.getListAppender(logger, "Class"); + logger.info("Ignored message contents."); + logger.warn("Verifying the caller class is still correct."); + logger.error("Hopefully nobody breaks me!"); + final List messages = app.strList; + assertEquals(3, messages.size(), "Incorrect number of messages."); + for (final String message : messages) { + assertEquals(this.getClass().getName(), message, "Incorrect caller class name."); + } + } + + @Test + public void testMethodLogger() throws Exception { + final SLF4JLogger logger = (SLF4JLogger) LogManager.getLogger("MethodLogger"); + final StringListAppender app = TestUtil.getListAppender(logger, "Method"); + logger.info("More messages."); + logger.warn("CATASTROPHE INCOMING!"); + logger.error("ZOMBIES!!!"); + logger.warn("brains~~~"); + logger.info("Itchy. Tasty."); + final List messages = app.strList; + assertEquals(5, messages.size(), "Incorrect number of messages."); + for (final String message : messages) { + assertEquals("testMethodLogger", message, "Incorrect caller method name."); + } + } +} diff --git a/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/Log4j2Jira1688Test.java b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/Log4j2Jira1688Test.java new file mode 100644 index 0000000..18779b1 --- /dev/null +++ b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/Log4j2Jira1688Test.java @@ -0,0 +1,68 @@ +/* + * 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.slf4j; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Tests LOG4J2-1688 Multiple loggings of arguments are setting these arguments to null. + */ +public class Log4j2Jira1688Test { + + @Test + public void testLog4j2() { + + // Argument-array creation + final int limit = 37; + final Object[] args = createArray(limit); + final Object[] originalArgs = Arrays.copyOf(args, args.length); + + System.out.println("args " + Arrays.toString(args)); + + // Logger definition + final String someFormat = "test {}"; + final Logger logger = LoggerFactory.getLogger(this.getClass()); + + // First logging of args + logger.error(someFormat, args); // Only the first element (args[0]) of args will be logged - why? + // GG: because the pattern {} picks up the 1 st argument, not the whole array + assertThat(args).containsExactly(originalArgs); + + // Bug: The second logging of args sets all elements of args to null + logger.error(someFormat, args); + // GG: All is well args is still intact + System.out.println("args " + Arrays.toString(args)); + assertThat(args).containsExactly(originalArgs); + } + + /** + * @param size + * @return + */ + private static Object[] createArray(final int size) { + final Object[] args = new Object[size]; + for (int i = 0; i < args.length; i++) { + args[i] = i; + } + return args; + } +} diff --git a/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LogBuilderTest.java b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LogBuilderTest.java new file mode 100644 index 0000000..8d07307 --- /dev/null +++ b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LogBuilderTest.java @@ -0,0 +1,106 @@ +/* + * 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.slf4j; + +import static org.assertj.core.api.Assertions.assertThat; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.testUtil.StringListAppender; +import java.util.function.Consumer; +import java.util.stream.Stream; +import org.apache.logging.log4j.CloseableThreadContext; +import org.apache.logging.log4j.CloseableThreadContext.Instance; +import org.apache.logging.log4j.LogBuilder; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +@UsingStatusListener +@LoggerContextSource +public class LogBuilderTest { + + private static final CharSequence CHAR_SEQUENCE = "CharSequence"; + private static final String STRING = "String"; + private static final Message MESSAGE = new SimpleMessage(); + private static final Object[] P = {"p0", "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9"}; + private static final Object OBJECT = "Object"; + + // Log4j objects + private static Logger logger; + // Logback objects + private static LoggerContext context; + private static StringListAppender list; + + @BeforeAll + public static void setUp() throws Exception { + final org.slf4j.Logger slf4jLogger = context.getLogger(LogBuilderTest.class); + logger = LogManager.getLogger(LogBuilderTest.class); + assertThat(slf4jLogger).isSameAs(((SLF4JLogger) logger).getLogger()); + final ch.qos.logback.classic.Logger rootLogger = context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); + rootLogger.detachAppender("console"); + list = TestUtil.getListAppender(rootLogger, "LIST"); + assertThat(list).isNotNull().extracting("strList").isNotNull(); + list.strList.clear(); + } + + static Stream> logBuilderMethods() { + return Stream.of( + logBuilder -> logBuilder.log(), + logBuilder -> logBuilder.log(CHAR_SEQUENCE), + logBuilder -> logBuilder.log(MESSAGE), + logBuilder -> logBuilder.log(OBJECT), + logBuilder -> logBuilder.log(STRING), + logBuilder -> logBuilder.log(STRING, P[0]), + logBuilder -> logBuilder.log(STRING, P[0], P[1]), + logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2]), + logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3]), + logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4]), + logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5]), + logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5], P[6]), + logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5], P[6], P[7]), + logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5], P[6], P[7], P[8]), + logBuilder -> logBuilder.log(STRING, P[0], P[1], P[2], P[3], P[4], P[5], P[6], P[7], P[8], P[9]), + logBuilder -> logBuilder.log(STRING, P), + logBuilder -> logBuilder.log(STRING, () -> OBJECT), + logBuilder -> logBuilder.log(() -> MESSAGE)); + } + + @ParameterizedTest + @MethodSource("logBuilderMethods") + void testTurboFilter(final Consumer consumer) { + consumer.accept(logger.atTrace()); + try (final Instance c = CloseableThreadContext.put("callerId", "Log4j2")) { + consumer.accept(logger.atTrace()); + assertThat(list.strList).hasSize(1); + } + list.strList.clear(); + } + + @ParameterizedTest + @MethodSource("logBuilderMethods") + void testLevelThreshold(final Consumer consumer) { + consumer.accept(logger.atInfo()); + assertThat(list.strList).hasSize(1); + list.strList.clear(); + } +} diff --git a/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerContextResolver.java b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerContextResolver.java new file mode 100644 index 0000000..dc9dd5d --- /dev/null +++ b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerContextResolver.java @@ -0,0 +1,166 @@ +/* + * 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.slf4j; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.joran.spi.JoranException; +import java.net.URL; +import org.apache.logging.log4j.test.junit.ExtensionContextAnchor; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.jupiter.api.extension.ExtensionContextException; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.support.TypeBasedParameterResolver; +import org.junit.platform.commons.support.AnnotationSupport; +import org.junit.platform.commons.support.HierarchyTraversalMode; +import org.junit.platform.commons.support.ModifierSupport; +import org.junit.platform.commons.support.ReflectionSupport; +import org.slf4j.LoggerFactory; + +class LoggerContextResolver extends TypeBasedParameterResolver + implements BeforeAllCallback, BeforeEachCallback { + + private static final Object KEY = LoggerContextHolder.class; + + @Override + public void beforeEach(ExtensionContext extensionContext) throws Exception { + final Class testClass = extensionContext.getRequiredTestClass(); + if (AnnotationSupport.isAnnotated(testClass, LoggerContextSource.class)) { + final LoggerContextHolder holder = + ExtensionContextAnchor.getAttribute(KEY, LoggerContextHolder.class, extensionContext); + if (holder == null) { + throw new IllegalStateException( + "Specified @LoggerContextSource but no LoggerContext found for test class " + + testClass.getCanonicalName()); + } + } + AnnotationSupport.findAnnotation(extensionContext.getRequiredTestMethod(), LoggerContextSource.class) + .ifPresent(source -> { + final LoggerContextHolder holder = new LoggerContextHolder(source, extensionContext); + ExtensionContextAnchor.setAttribute(KEY, holder, extensionContext); + }); + final LoggerContextHolder holder = + ExtensionContextAnchor.getAttribute(KEY, LoggerContextHolder.class, extensionContext); + if (holder != null) { + ReflectionSupport.findFields( + extensionContext.getRequiredTestClass(), + f -> ModifierSupport.isNotStatic(f) && f.getType().equals(LoggerContext.class), + HierarchyTraversalMode.TOP_DOWN) + .forEach(f -> { + try { + f.setAccessible(true); + f.set(extensionContext.getRequiredTestInstance(), holder.getLoggerContext()); + } catch (ReflectiveOperationException e) { + throw new ExtensionContextException("Failed to inject field " + f, e); + } + }); + } + } + + @Override + public void beforeAll(ExtensionContext extensionContext) throws Exception { + final Class testClass = extensionContext.getRequiredTestClass(); + AnnotationSupport.findAnnotation(testClass, LoggerContextSource.class).ifPresent(testSource -> { + final LoggerContextHolder holder = new LoggerContextHolder(testSource, extensionContext); + ExtensionContextAnchor.setAttribute(KEY, holder, extensionContext); + }); + final LoggerContextHolder holder = + ExtensionContextAnchor.getAttribute(KEY, LoggerContextHolder.class, extensionContext); + if (holder != null) { + ReflectionSupport.findFields( + extensionContext.getRequiredTestClass(), + f -> ModifierSupport.isStatic(f) && f.getType().equals(LoggerContext.class), + HierarchyTraversalMode.TOP_DOWN) + .forEach(f -> { + try { + f.setAccessible(true); + f.set(null, holder.getLoggerContext()); + } catch (ReflectiveOperationException e) { + throw new ExtensionContextException("Failed to inject field " + f, e); + } + }); + } + } + + @Override + public LoggerContext resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return ExtensionContextAnchor.getAttribute(KEY, LoggerContextHolder.class, extensionContext) + .getLoggerContext(); + } + + static final class LoggerContextHolder implements Store.CloseableResource { + + private final LoggerContext context; + private final Logger logger; + + private LoggerContextHolder(final LoggerContextSource source, final ExtensionContext extensionContext) { + this.context = (LoggerContext) LoggerFactory.getILoggerFactory(); + Class clazz = extensionContext.getRequiredTestClass(); + this.logger = context.getLogger(clazz); + + final JoranConfigurator configurator = new JoranConfigurator(); + final URL configLocation = getConfigLocation(source, extensionContext); + configurator.setContext(context); + try { + configurator.doConfigure(configLocation); + } catch (final JoranException e) { + throw new ExtensionContextException("Failed to initialize Logback logger context for " + clazz, e); + } + } + + private static URL getConfigLocation( + final LoggerContextSource source, final ExtensionContext extensionContext) { + final String value = source.value(); + Class clazz = extensionContext.getRequiredTestClass(); + URL url = null; + if (value.isEmpty()) { + while (clazz != null) { + url = clazz.getResource(clazz.getSimpleName() + ".xml"); + if (url != null) { + break; + } + clazz = clazz.getSuperclass(); + } + } else { + url = clazz.getClassLoader().getResource(value); + } + if (url != null) { + return url; + } + throw new ExtensionContextException("Failed to find a default configuration for " + clazz); + } + + public LoggerContext getLoggerContext() { + return context; + } + + public Logger getLogger() { + return logger; + } + + @Override + public void close() { + context.stop(); + } + } +} diff --git a/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerContextSource.java b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerContextSource.java new file mode 100644 index 0000000..d71921c --- /dev/null +++ b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerContextSource.java @@ -0,0 +1,52 @@ +/* + * 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.slf4j; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.apache.logging.log4j.test.junit.TempLoggingDirectory; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Specifies a configuration file to use for unit tests. + * + * This is similar to the org.apache.logging.log4j.core.junit.LoggerContextSource annotation for Log4j. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Documented +@Inherited +@Tag("functional") +@ExtendWith(TempLoggingDirectory.class) +@ExtendWith(LoggerContextResolver.class) +@ExtendWith(LoggerResolver.class) +public @interface LoggerContextSource { + /** + * Specifies the name of the configuration file to use for the annotated test. + *

+ * Defaults to the fully qualified name of the test class with '.xml' appended. + * E.g. this class would have a default of + * {@code org/apache/logging/log4j/core/test/junit/LoggerContextSource.xml}. + *

+ */ + String value() default ""; +} diff --git a/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerResolver.java b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerResolver.java new file mode 100644 index 0000000..28f9411 --- /dev/null +++ b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerResolver.java @@ -0,0 +1,84 @@ +/* + * 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.slf4j; + +import ch.qos.logback.classic.Logger; +import org.apache.logging.log4j.test.junit.ExtensionContextAnchor; +import org.apache.logging.slf4j.LoggerContextResolver.LoggerContextHolder; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContextException; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.support.TypeBasedParameterResolver; +import org.junit.platform.commons.support.HierarchyTraversalMode; +import org.junit.platform.commons.support.ModifierSupport; +import org.junit.platform.commons.support.ReflectionSupport; + +public class LoggerResolver extends TypeBasedParameterResolver + implements BeforeAllCallback, BeforeEachCallback { + + private static final Object KEY = LoggerContextHolder.class; + + @Override + public Logger resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return ExtensionContextAnchor.getAttribute(KEY, LoggerContextHolder.class, extensionContext) + .getLogger(); + } + + @Override + public void beforeAll(ExtensionContext extensionContext) throws Exception { + final LoggerContextHolder holder = + ExtensionContextAnchor.getAttribute(KEY, LoggerContextHolder.class, extensionContext); + if (holder != null) { + ReflectionSupport.findFields( + extensionContext.getRequiredTestClass(), + f -> ModifierSupport.isStatic(f) && f.getType().equals(Logger.class), + HierarchyTraversalMode.TOP_DOWN) + .forEach(f -> { + try { + f.setAccessible(true); + f.set(null, holder.getLogger()); + } catch (ReflectiveOperationException e) { + throw new ExtensionContextException("Failed to inject field " + f, e); + } + }); + } + } + + @Override + public void beforeEach(ExtensionContext extensionContext) throws Exception { + final LoggerContextHolder holder = + ExtensionContextAnchor.getAttribute(KEY, LoggerContextHolder.class, extensionContext); + if (holder != null) { + ReflectionSupport.findFields( + extensionContext.getRequiredTestClass(), + f -> ModifierSupport.isNotStatic(f) && f.getType().equals(Logger.class), + HierarchyTraversalMode.TOP_DOWN) + .forEach(f -> { + try { + f.setAccessible(true); + f.set(extensionContext.getRequiredTestInstance(), holder.getLogger()); + } catch (ReflectiveOperationException e) { + throw new ExtensionContextException("Failed to inject field " + f, e); + } + }); + } + } +} diff --git a/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerTest.java b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerTest.java new file mode 100644 index 0000000..c6a3cec --- /dev/null +++ b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerTest.java @@ -0,0 +1,268 @@ +/* + * 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.slf4j; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.theInstance; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.testUtil.StringListAppender; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Proxy; +import java.util.Date; +import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.message.ParameterizedMessageFactory; +import org.apache.logging.log4j.message.StringFormatterMessageFactory; +import org.apache.logging.log4j.spi.AbstractLogger; +import org.apache.logging.log4j.spi.MessageFactory2Adapter; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.MDC; + +@UsingStatusListener +@LoggerContextSource +public class LoggerTest { + + private static final Object OBJ = new Object(); + // Log4j objects + private Logger logger; + // Logback objects + private LoggerContext context; + private StringListAppender list; + + @BeforeEach + public void setUp() throws Exception { + final org.slf4j.Logger slf4jLogger = context.getLogger(getClass()); + logger = LogManager.getLogger(); + assertThat(slf4jLogger, is(theInstance(((SLF4JLogger) logger).getLogger()))); + final ch.qos.logback.classic.Logger rootLogger = context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); + rootLogger.detachAppender("console"); + list = TestUtil.getListAppender(rootLogger, "LIST"); + assertThat(list, is(notNullValue())); + assertThat(list.strList, is(notNullValue())); + list.strList.clear(); + } + + @Test + public void basicFlow() { + logger.traceEntry(); + logger.traceExit(); + assertThat(list.strList, hasSize(2)); + } + + @Test + public void basicFlowDepreacted() { + logger.entry(); + logger.exit(); + assertThat(list.strList, hasSize(2)); + } + + @Test + public void simpleFlowDeprecated() { + logger.entry(OBJ); + logger.exit(0); + assertThat(list.strList, hasSize(2)); + } + + @Test + public void simpleFlow() { + logger.entry(OBJ); + logger.traceExit(0); + assertThat(list.strList, hasSize(2)); + } + + @Test + public void throwing() { + logger.throwing(new IllegalArgumentException("Test Exception")); + assertThat(list.strList, hasSize(1)); + } + + @Test + public void catching() { + try { + throw new NullPointerException(); + } catch (final Exception e) { + logger.catching(e); + } + assertThat(list.strList, hasSize(1)); + } + + @Test + public void debug() { + logger.debug("Debug message"); + assertThat(list.strList, hasSize(1)); + } + + @Test + public void getLogger_String_MessageFactoryMismatch() { + final Logger testLogger = testMessageFactoryMismatch( + "getLogger_String_MessageFactoryMismatch", + StringFormatterMessageFactory.INSTANCE, + ParameterizedMessageFactory.INSTANCE); + testLogger.debug("%,d", Integer.MAX_VALUE); + assertThat(list.strList, hasSize(1)); + assertThat(list.strList, hasItem(String.format("%,d", Integer.MAX_VALUE))); + } + + @Test + public void getLogger_String_MessageFactoryMismatchNull() { + final Logger testLogger = testMessageFactoryMismatch( + "getLogger_String_MessageFactoryMismatchNull", StringFormatterMessageFactory.INSTANCE, null); + testLogger.debug("%,d", Integer.MAX_VALUE); + assertThat(list.strList, hasSize(1)); + assertThat(list.strList, hasItem(String.format("%,d", Integer.MAX_VALUE))); + } + + private Logger testMessageFactoryMismatch( + final String name, final MessageFactory messageFactory1, final MessageFactory messageFactory2) { + final Logger testLogger = LogManager.getLogger(name, messageFactory1); + assertThat(testLogger, is(notNullValue())); + checkMessageFactory(messageFactory1, testLogger); + final Logger testLogger2 = LogManager.getLogger(name, messageFactory2); + checkMessageFactory(messageFactory2, testLogger2); + return testLogger; + } + + private static void checkMessageFactory(final MessageFactory messageFactory1, final Logger testLogger1) { + if (messageFactory1 == null) { + assertEquals( + AbstractLogger.DEFAULT_MESSAGE_FACTORY_CLASS, + testLogger1.getMessageFactory().getClass()); + } else { + MessageFactory actual = testLogger1.getMessageFactory(); + if (actual instanceof MessageFactory2Adapter) { + actual = ((MessageFactory2Adapter) actual).getOriginal(); + } + assertEquals(messageFactory1, actual); + } + } + + @Test + public void debugObject() { + logger.debug(new Date()); + assertThat(list.strList, hasSize(1)); + } + + @Test + public void debugWithParms() { + logger.debug("Hello, {}", "World"); + assertThat(list.strList, hasSize(1)); + final String message = list.strList.get(0); + assertEquals("Hello, World", message); + } + + @Test + public void paramIncludesSubstitutionMarker_locationAware() { + logger.info("Hello, {}", "foo {} bar"); + assertThat(list.strList, hasSize(1)); + final String message = list.strList.get(0); + assertEquals("Hello, foo {} bar", message); + } + + @Test + public void paramIncludesSubstitutionMarker_nonLocationAware() { + final org.slf4j.Logger slf4jLogger = context.getLogger(getClass()); + final Logger nonLocationAwareLogger = + new SLF4JLogger(slf4jLogger.getName(), (org.slf4j.Logger) Proxy.newProxyInstance( + getClass().getClassLoader(), new Class[] {org.slf4j.Logger.class}, (proxy, method, args) -> { + try { + return method.invoke(slf4jLogger, args); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + })); + nonLocationAwareLogger.info("Hello, {}", "foo {} bar"); + assertThat(list.strList, hasSize(1)); + final String message = list.strList.get(0); + assertEquals("Hello, foo {} bar", message); + } + + @Test + public void testImpliedThrowable() { + logger.debug("This is a test", new Throwable("Testing")); + final List msgs = list.strList; + assertThat(msgs, hasSize(1)); + final String expected = "java.lang.Throwable: Testing"; + assertTrue(msgs.get(0).contains(expected), "Incorrect message data"); + } + + @SuppressWarnings("unchecked") + @Test + public void mdc() { + ThreadContext.put("TestYear", Integer.toString(2010)); + logger.debug("Debug message"); + ThreadContext.clearMap(); + logger.debug("Debug message"); + assertThat(list.strList, hasSize(2)); + assertTrue(list.strList.get(0).startsWith("2010"), "Incorrect year"); + } + + @Test + public void mdcNullBackedIsEmpty() { + assertNull(MDC.getCopyOfContextMap(), "Setup wrong"); + assertTrue(ThreadContext.isEmpty()); + } + + @Test + public void mdcNullBackedContainsKey() { + assertNull(MDC.getCopyOfContextMap(), "Setup wrong"); + assertFalse(ThreadContext.containsKey("something")); + } + + @Test + public void mdcNullBackedContainsNullKey() { + assertNull(MDC.getCopyOfContextMap(), "Setup wrong"); + assertFalse(ThreadContext.containsKey(null)); + } + + @Test + public void mdcContainsNullKey() { + try { + ThreadContext.put("some", "thing"); + assertNotNull(MDC.getCopyOfContextMap(), "Setup wrong"); + assertFalse(ThreadContext.containsKey(null)); + } finally { + ThreadContext.clearMap(); + } + } + + @Test + public void mdcCannotContainNullKey() { + try { + ThreadContext.put(null, "something"); + fail("should throw"); + } catch (IllegalArgumentException | NullPointerException e) { + // expected + } + } +} diff --git a/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/MDCContextMapTest.java b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/MDCContextMapTest.java new file mode 100644 index 0000000..6673de4 --- /dev/null +++ b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/MDCContextMapTest.java @@ -0,0 +1,50 @@ +/* + * 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.slf4j; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import org.apache.logging.log4j.spi.ThreadContextMap; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.Issue; +import org.slf4j.MDCTestHelper; +import org.slf4j.spi.MDCAdapter; + +class MDCContextMapTest { + + @Test + @Issue("https://github.com/apache/logging-log4j2/issues/1426") + void nonNullGetCopy() { + final ThreadContextMap contextMap = new MDCContextMap(); + final MDCAdapter mockAdapter = mock(MDCAdapter.class); + when(mockAdapter.getCopyOfContextMap()).thenReturn(null); + final MDCAdapter adapter = MDCTestHelper.replaceMDCAdapter(mockAdapter); + try { + assertThat(contextMap.getImmutableMapOrNull()).isNull(); + assertThat(contextMap.getCopy()).isNotNull(); + verify(mockAdapter, times(2)).getCopyOfContextMap(); + verifyNoMoreInteractions(mockAdapter); + } finally { + MDCTestHelper.replaceMDCAdapter(adapter); + } + } +} diff --git a/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/TestUtil.java b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/TestUtil.java new file mode 100644 index 0000000..afcd9a8 --- /dev/null +++ b/log4j-api-to-slf4j/src/test/java/org/apache/logging/slf4j/TestUtil.java @@ -0,0 +1,47 @@ +/* + * 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.slf4j; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.spi.AppenderAttachable; +import ch.qos.logback.core.testUtil.StringListAppender; +import org.slf4j.Logger; + +/** + * Utility methods for unit tests integrating with Logback. + * + * @since 2.1 + */ +public final class TestUtil { + + public static StringListAppender getListAppender(final SLF4JLogger slf4jLogger, final String name) { + final Logger logger = slf4jLogger.getLogger(); + if (!(logger instanceof AppenderAttachable)) { + throw new AssertionError("SLF4JLogger.getLogger() did not return an instance of AppenderAttachable"); + } + @SuppressWarnings("unchecked") + final AppenderAttachable attachable = (AppenderAttachable) logger; + return getListAppender(attachable, name); + } + + public static StringListAppender getListAppender( + final AppenderAttachable logger, final String name) { + return (StringListAppender) logger.getAppender(name); + } + + private TestUtil() {} +} diff --git a/log4j-api-to-slf4j/src/test/java/org/slf4j/MDCTestHelper.java b/log4j-api-to-slf4j/src/test/java/org/slf4j/MDCTestHelper.java new file mode 100644 index 0000000..0256131 --- /dev/null +++ b/log4j-api-to-slf4j/src/test/java/org/slf4j/MDCTestHelper.java @@ -0,0 +1,28 @@ +/* + * 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.slf4j; + +import org.slf4j.spi.MDCAdapter; + +public class MDCTestHelper { + + public static MDCAdapter replaceMDCAdapter(final MDCAdapter adapter) { + final MDCAdapter old = MDC.mdcAdapter; + MDC.mdcAdapter = adapter; + return old; + } +} diff --git a/log4j-api-to-slf4j/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider b/log4j-api-to-slf4j/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider new file mode 100644 index 0000000..d0396bb --- /dev/null +++ b/log4j-api-to-slf4j/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider @@ -0,0 +1,17 @@ +# 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 +# +# https://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. +## +# BND can generate this, but we need it faster +org.apache.logging.slf4j.SLF4JProvider \ No newline at end of file diff --git a/log4j-api-to-slf4j/src/test/resources/org/apache/logging/slf4j/CallerInformationTest.xml b/log4j-api-to-slf4j/src/test/resources/org/apache/logging/slf4j/CallerInformationTest.xml new file mode 100644 index 0000000..9cef52d --- /dev/null +++ b/log4j-api-to-slf4j/src/test/resources/org/apache/logging/slf4j/CallerInformationTest.xml @@ -0,0 +1,38 @@ + + + + + + + + %class + + + + + + + + + %method + + + + + + diff --git a/log4j-api-to-slf4j/src/test/resources/org/apache/logging/slf4j/LogBuilderTest.xml b/log4j-api-to-slf4j/src/test/resources/org/apache/logging/slf4j/LogBuilderTest.xml new file mode 100644 index 0000000..9305cc8 --- /dev/null +++ b/log4j-api-to-slf4j/src/test/resources/org/apache/logging/slf4j/LogBuilderTest.xml @@ -0,0 +1,39 @@ + + + + + + ACCEPT + callerId + INFO + + Log4j2 + TRACE + + + + + + %msg + + + + + + + diff --git a/log4j-api-to-slf4j/src/test/resources/org/apache/logging/slf4j/LoggerTest.xml b/log4j-api-to-slf4j/src/test/resources/org/apache/logging/slf4j/LoggerTest.xml new file mode 100644 index 0000000..ec38ed6 --- /dev/null +++ b/log4j-api-to-slf4j/src/test/resources/org/apache/logging/slf4j/LoggerTest.xml @@ -0,0 +1,29 @@ + + + + + + + %X{TestYear}%msg + + + + + + + diff --git a/parent/pom.xml b/parent/pom.xml index 5a1db4f..cfb8791 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -31,9 +31,19 @@ https://logging.apache.org/log4j/2.x/manual/installation.html + + false + 3.26.3 + 7.0.0 + 2.2 + 1.0.0 + 5.11.3 2.24.1 + 5.14.2 + 2.0.0 + 1.1.2 2.0.16 @@ -41,19 +51,76 @@ + + org.assertj + assertj-bom + ${assertj.version} + pom + import + + + + org.junit + junit-bom + ${junit.version} + pom + import + + org.apache.logging.log4j - log4j-api + log4j-bom ${log4j.version} + pom + import + + + + org.mockito + mockito-bom + ${mockito.version} + pom + import org.slf4j - slf4j-api + slf4j-bom ${slf4j.version} + pom + import + + + + biz.aQute.bnd + biz.aQute.bnd.annotation + ${bnd.annotation.version} + provided + + + + org.jspecify + jspecify + ${jspecify.version} + + + + org.osgi + org.osgi.annotation.bundle + ${osgi.bundle.version} + provided + + + + org.osgi + org.osgi.annotation.versioning + ${osgi.versioning.version} + + + diff --git a/pom.xml b/pom.xml index 02bc526..36841e0 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,7 @@ parent + log4j-api-to-slf4j @@ -97,7 +98,15 @@ - + + + + org.apache.logging.log4j + log4j-api-to-slf4j + ${project.version} + + + diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml new file mode 100644 index 0000000..b0c83f0 --- /dev/null +++ b/spotbugs-exclude.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + +