diff --git a/NOTICE.txt b/NOTICE.txt index 0d4507a..87be535 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,4 +1,4 @@ -Apache Logging JDK Bridges +Apache Logging SLF4J Bridges Copyright 2024 The Apache Software Foundation This product includes software developed at diff --git a/pom.xml b/pom.xml index 36841e0..7bfb9df 100644 --- a/pom.xml +++ b/pom.xml @@ -60,6 +60,7 @@ log4j-api-to-slf4j + slf4j-to-log4j-api @@ -106,6 +107,12 @@ ${project.version} + + org.apache.logging.log4j + slf4j-to-log4j-api + ${project.version} + + diff --git a/slf4j-to-log4j-api/pom.xml b/slf4j-to-log4j-api/pom.xml new file mode 100644 index 0000000..49eeb11 --- /dev/null +++ b/slf4j-to-log4j-api/pom.xml @@ -0,0 +1,143 @@ + + + + 4.0.0 + + org.apache.logging.log4j + log4j-slf4j-parent + ${revision} + ../parent + + + slf4j-to-log4j-api + + SLF4J to Log4j API logging bridge + + SLF4J 2 provider (binding) for the Apache Log4j API. + It forwards SLF4J 2 calls to the Log4j API. + This effectively allows using Log4j as an implementation of SLF4J 2. + (Refer to the `log4j-to-slf4j` artifact for forwarding the Log4j API to SLF4J.) + + + + 1.8.0 + + + + + + org.osgi + org.osgi.framework + ${osgi.framework.version} + provided + + + + org.apache.logging.log4j + log4j-api + + + + org.slf4j + slf4j-api + + + + org.apache.logging.log4j + log4j-api-test + test + + + + org.apache.logging.log4j + log4j-core + test + + + + org.apache.logging.log4j + log4j-core-test + test + + + + org.assertj + assertj-core + test + + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-params + test + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + loop-test + + test + + + + + org.apache.logging.log4j + log4j-to-slf4j + ${log4j.version} + + + + **/OverflowTest.java + + + + + default-test + + + **/*Test.java + + + **/OverflowTest.java + + + + + + + + + diff --git a/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/Log4jEventBuilder.java b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/Log4jEventBuilder.java new file mode 100644 index 0000000..474fb05 --- /dev/null +++ b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/Log4jEventBuilder.java @@ -0,0 +1,163 @@ +/* + * 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.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import org.apache.logging.log4j.BridgeAware; +import org.apache.logging.log4j.CloseableThreadContext; +import org.apache.logging.log4j.CloseableThreadContext.Instance; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogBuilder; +import org.apache.logging.log4j.Logger; +import org.slf4j.Marker; +import org.slf4j.spi.CallerBoundaryAware; +import org.slf4j.spi.LoggingEventBuilder; + +public class Log4jEventBuilder implements LoggingEventBuilder, CallerBoundaryAware { + + private static final String FQCN = Log4jEventBuilder.class.getName(); + + private final Log4jMarkerFactory markerFactory; + private final Logger logger; + private final List arguments = new ArrayList<>(); + private String message = null; + private org.apache.logging.log4j.Marker marker = null; + private Throwable throwable = null; + private Map keyValuePairs = null; + private final Level level; + private String fqcn = FQCN; + + public Log4jEventBuilder(final Log4jMarkerFactory markerFactory, final Logger logger, final Level level) { + this.markerFactory = markerFactory; + this.logger = logger; + this.level = level; + } + + @Override + public LoggingEventBuilder setCause(final Throwable cause) { + this.throwable = cause; + return this; + } + + @Override + public LoggingEventBuilder addMarker(final Marker marker) { + this.marker = markerFactory.getLog4jMarker(marker); + return this; + } + + @Override + public LoggingEventBuilder addArgument(final Object p) { + arguments.add(p); + return this; + } + + @Override + public LoggingEventBuilder addArgument(final Supplier objectSupplier) { + arguments.add(objectSupplier.get()); + return this; + } + + @Override + public LoggingEventBuilder addKeyValue(final String key, final Object value) { + if (keyValuePairs == null) { + keyValuePairs = new HashMap<>(); + } + keyValuePairs.put(key, String.valueOf(value)); + return this; + } + + @Override + public LoggingEventBuilder addKeyValue(final String key, final Supplier valueSupplier) { + if (keyValuePairs == null) { + keyValuePairs = new HashMap<>(); + } + keyValuePairs.put(key, String.valueOf(valueSupplier.get())); + return this; + } + + @Override + public LoggingEventBuilder setMessage(final String message) { + this.message = message; + return this; + } + + @Override + public LoggingEventBuilder setMessage(final Supplier messageSupplier) { + this.message = messageSupplier.get(); + return this; + } + + @Override + public void log() { + final LogBuilder logBuilder = logger.atLevel(level).withMarker(marker).withThrowable(throwable); + if (logBuilder instanceof BridgeAware) { + ((BridgeAware) logBuilder).setEntryPoint(fqcn); + } + if (keyValuePairs == null || keyValuePairs.isEmpty()) { + logBuilder.log(message, arguments.toArray()); + } else { + try (final Instance c = CloseableThreadContext.putAll(keyValuePairs)) { + logBuilder.log(message, arguments.toArray()); + } + } + } + + @Override + public void log(final String message) { + setMessage(message); + log(); + } + + @Override + public void log(final String message, final Object arg) { + setMessage(message); + addArgument(arg); + log(); + } + + @Override + public void log(final String message, final Object arg0, final Object arg1) { + setMessage(message); + addArgument(arg0); + addArgument(arg1); + log(); + } + + @Override + public void log(final String message, final Object... args) { + setMessage(message); + for (final Object arg : args) { + addArgument(arg); + } + log(); + } + + @Override + public void log(final Supplier messageSupplier) { + setMessage(messageSupplier); + log(); + } + + @Override + public void setCallerBoundary(String fqcn) { + this.fqcn = fqcn; + } +} diff --git a/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/Log4jLogger.java b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/Log4jLogger.java new file mode 100644 index 0000000..59607e2 --- /dev/null +++ b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/Log4jLogger.java @@ -0,0 +1,427 @@ +/* + * 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.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.ParameterizedMessage; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.spi.ExtendedLogger; +import org.slf4j.Marker; +import org.slf4j.spi.LocationAwareLogger; +import org.slf4j.spi.LoggingEventBuilder; + +/** + * SLF4J logger implementation that uses Log4j. + */ +public class Log4jLogger implements LocationAwareLogger, Serializable { + + public static final String FQCN = Log4jLogger.class.getName(); + + private static final long serialVersionUID = 7869000638091304316L; + private transient ExtendedLogger logger; + private final String name; + private transient Log4jMarkerFactory markerFactory; + + public Log4jLogger(final Log4jMarkerFactory markerFactory, final ExtendedLogger logger, final String name) { + this.markerFactory = markerFactory; + this.logger = logger; + this.name = name; + } + + @Override + public void trace(final String format) { + logger.logIfEnabled(FQCN, Level.TRACE, null, format); + } + + @Override + public void trace(final String format, final Object o) { + logger.logIfEnabled(FQCN, Level.TRACE, null, format, o); + } + + @Override + public void trace(final String format, final Object arg1, final Object arg2) { + logger.logIfEnabled(FQCN, Level.TRACE, null, format, arg1, arg2); + } + + @Override + public void trace(final String format, final Object... args) { + logger.logIfEnabled(FQCN, Level.TRACE, null, format, args); + } + + @Override + public void trace(final String format, final Throwable t) { + logger.logIfEnabled(FQCN, Level.TRACE, null, format, t); + } + + @Override + public boolean isTraceEnabled() { + return logger.isEnabled(Level.TRACE, null, null); + } + + @Override + public boolean isTraceEnabled(final Marker marker) { + return logger.isEnabled(Level.TRACE, markerFactory.getLog4jMarker(marker), null); + } + + @Override + public void trace(final Marker marker, final String s) { + logger.logIfEnabled(FQCN, Level.TRACE, markerFactory.getLog4jMarker(marker), s); + } + + @Override + public void trace(final Marker marker, final String s, final Object o) { + logger.logIfEnabled(FQCN, Level.TRACE, markerFactory.getLog4jMarker(marker), s, o); + } + + @Override + public void trace(final Marker marker, final String s, final Object o, final Object o1) { + logger.logIfEnabled(FQCN, Level.TRACE, markerFactory.getLog4jMarker(marker), s, o, o1); + } + + @Override + public void trace(final Marker marker, final String s, final Object... objects) { + logger.logIfEnabled(FQCN, Level.TRACE, markerFactory.getLog4jMarker(marker), s, objects); + } + + @Override + public void trace(final Marker marker, final String s, final Throwable throwable) { + logger.logIfEnabled(FQCN, Level.TRACE, markerFactory.getLog4jMarker(marker), s, throwable); + } + + @Override + public void debug(final String format) { + logger.logIfEnabled(FQCN, Level.DEBUG, null, format); + } + + @Override + public void debug(final String format, final Object o) { + logger.logIfEnabled(FQCN, Level.DEBUG, null, format, o); + } + + @Override + public void debug(final String format, final Object arg1, final Object arg2) { + logger.logIfEnabled(FQCN, Level.DEBUG, null, format, arg1, arg2); + } + + @Override + public void debug(final String format, final Object... args) { + logger.logIfEnabled(FQCN, Level.DEBUG, null, format, args); + } + + @Override + public void debug(final String format, final Throwable t) { + logger.logIfEnabled(FQCN, Level.DEBUG, null, format, t); + } + + @Override + public boolean isDebugEnabled() { + return logger.isEnabled(Level.DEBUG, null, null); + } + + @Override + public boolean isDebugEnabled(final Marker marker) { + return logger.isEnabled(Level.DEBUG, markerFactory.getLog4jMarker(marker), null); + } + + @Override + public void debug(final Marker marker, final String s) { + logger.logIfEnabled(FQCN, Level.DEBUG, markerFactory.getLog4jMarker(marker), s); + } + + @Override + public void debug(final Marker marker, final String s, final Object o) { + logger.logIfEnabled(FQCN, Level.DEBUG, markerFactory.getLog4jMarker(marker), s, o); + } + + @Override + public void debug(final Marker marker, final String s, final Object o, final Object o1) { + logger.logIfEnabled(FQCN, Level.DEBUG, markerFactory.getLog4jMarker(marker), s, o, o1); + } + + @Override + public void debug(final Marker marker, final String s, final Object... objects) { + logger.logIfEnabled(FQCN, Level.DEBUG, markerFactory.getLog4jMarker(marker), s, objects); + } + + @Override + public void debug(final Marker marker, final String s, final Throwable throwable) { + logger.logIfEnabled(FQCN, Level.DEBUG, markerFactory.getLog4jMarker(marker), s, throwable); + } + + @Override + public void info(final String format) { + logger.logIfEnabled(FQCN, Level.INFO, null, format); + } + + @Override + public void info(final String format, final Object o) { + logger.logIfEnabled(FQCN, Level.INFO, null, format, o); + } + + @Override + public void info(final String format, final Object arg1, final Object arg2) { + logger.logIfEnabled(FQCN, Level.INFO, null, format, arg1, arg2); + } + + @Override + public void info(final String format, final Object... args) { + logger.logIfEnabled(FQCN, Level.INFO, null, format, args); + } + + @Override + public void info(final String format, final Throwable t) { + logger.logIfEnabled(FQCN, Level.INFO, null, format, t); + } + + @Override + public boolean isInfoEnabled() { + return logger.isEnabled(Level.INFO, null, null); + } + + @Override + public boolean isInfoEnabled(final Marker marker) { + return logger.isEnabled(Level.INFO, markerFactory.getLog4jMarker(marker), null); + } + + @Override + public void info(final Marker marker, final String s) { + logger.logIfEnabled(FQCN, Level.INFO, markerFactory.getLog4jMarker(marker), s); + } + + @Override + public void info(final Marker marker, final String s, final Object o) { + logger.logIfEnabled(FQCN, Level.INFO, markerFactory.getLog4jMarker(marker), s, o); + } + + @Override + public void info(final Marker marker, final String s, final Object o, final Object o1) { + logger.logIfEnabled(FQCN, Level.INFO, markerFactory.getLog4jMarker(marker), s, o, o1); + } + + @Override + public void info(final Marker marker, final String s, final Object... objects) { + logger.logIfEnabled(FQCN, Level.INFO, markerFactory.getLog4jMarker(marker), s, objects); + } + + @Override + public void info(final Marker marker, final String s, final Throwable throwable) { + logger.logIfEnabled(FQCN, Level.INFO, markerFactory.getLog4jMarker(marker), s, throwable); + } + + @Override + public void warn(final String format) { + logger.logIfEnabled(FQCN, Level.WARN, null, format); + } + + @Override + public void warn(final String format, final Object o) { + logger.logIfEnabled(FQCN, Level.WARN, null, format, o); + } + + @Override + public void warn(final String format, final Object arg1, final Object arg2) { + logger.logIfEnabled(FQCN, Level.WARN, null, format, arg1, arg2); + } + + @Override + public void warn(final String format, final Object... args) { + logger.logIfEnabled(FQCN, Level.WARN, null, format, args); + } + + @Override + public void warn(final String format, final Throwable t) { + logger.logIfEnabled(FQCN, Level.WARN, null, format, t); + } + + @Override + public boolean isWarnEnabled() { + return logger.isEnabled(Level.WARN, null, null); + } + + @Override + public boolean isWarnEnabled(final Marker marker) { + return logger.isEnabled(Level.WARN, markerFactory.getLog4jMarker(marker), null); + } + + @Override + public void warn(final Marker marker, final String s) { + logger.logIfEnabled(FQCN, Level.WARN, markerFactory.getLog4jMarker(marker), s); + } + + @Override + public void warn(final Marker marker, final String s, final Object o) { + logger.logIfEnabled(FQCN, Level.WARN, markerFactory.getLog4jMarker(marker), s, o); + } + + @Override + public void warn(final Marker marker, final String s, final Object o, final Object o1) { + logger.logIfEnabled(FQCN, Level.WARN, markerFactory.getLog4jMarker(marker), s, o, o1); + } + + @Override + public void warn(final Marker marker, final String s, final Object... objects) { + logger.logIfEnabled(FQCN, Level.WARN, markerFactory.getLog4jMarker(marker), s, objects); + } + + @Override + public void warn(final Marker marker, final String s, final Throwable throwable) { + logger.logIfEnabled(FQCN, Level.WARN, markerFactory.getLog4jMarker(marker), s, throwable); + } + + @Override + public void error(final String format) { + logger.logIfEnabled(FQCN, Level.ERROR, null, format); + } + + @Override + public void error(final String format, final Object o) { + logger.logIfEnabled(FQCN, Level.ERROR, null, format, o); + } + + @Override + public void error(final String format, final Object arg1, final Object arg2) { + logger.logIfEnabled(FQCN, Level.ERROR, null, format, arg1, arg2); + } + + @Override + public void error(final String format, final Object... args) { + logger.logIfEnabled(FQCN, Level.ERROR, null, format, args); + } + + @Override + public void error(final String format, final Throwable t) { + logger.logIfEnabled(FQCN, Level.ERROR, null, format, t); + } + + @Override + public boolean isErrorEnabled() { + return logger.isEnabled(Level.ERROR, null, null); + } + + @Override + public boolean isErrorEnabled(final Marker marker) { + return logger.isEnabled(Level.ERROR, markerFactory.getLog4jMarker(marker), null); + } + + @Override + public void error(final Marker marker, final String s) { + logger.logIfEnabled(FQCN, Level.ERROR, markerFactory.getLog4jMarker(marker), s); + } + + @Override + public void error(final Marker marker, final String s, final Object o) { + logger.logIfEnabled(FQCN, Level.ERROR, markerFactory.getLog4jMarker(marker), s, o); + } + + @Override + public void error(final Marker marker, final String s, final Object o, final Object o1) { + logger.logIfEnabled(FQCN, Level.ERROR, markerFactory.getLog4jMarker(marker), s, o, o1); + } + + @Override + public void error(final Marker marker, final String s, final Object... objects) { + logger.logIfEnabled(FQCN, Level.ERROR, markerFactory.getLog4jMarker(marker), s, objects); + } + + @Override + public void error(final Marker marker, final String s, final Throwable throwable) { + logger.logIfEnabled(FQCN, Level.ERROR, markerFactory.getLog4jMarker(marker), s, throwable); + } + + @Override + public void log( + final Marker marker, + final String fqcn, + final int level, + final String message, + final Object[] params, + final Throwable throwable) { + final Level log4jLevel = getLevel(level); + final org.apache.logging.log4j.Marker log4jMarker = markerFactory.getLog4jMarker(marker); + + if (!logger.isEnabled(log4jLevel, log4jMarker, message, params)) { + return; + } + final Message msg; + final Throwable actualThrowable; + if (params == null) { + msg = new SimpleMessage(message); + actualThrowable = throwable; + } else { + msg = new ParameterizedMessage(message, params, throwable); + actualThrowable = throwable != null ? throwable : msg.getThrowable(); + } + logger.logMessage(fqcn, log4jLevel, log4jMarker, msg, actualThrowable); + } + + @Override + public String getName() { + return name; + } + + /** + * Always treat de-serialization as a full-blown constructor, by validating the final state of + * the de-serialized object. + */ + private void readObject(final ObjectInputStream aInputStream) throws ClassNotFoundException, IOException { + // always perform the default de-serialization first + aInputStream.defaultReadObject(); + logger = LogManager.getContext().getLogger(name); + markerFactory = ((Log4jLoggerFactory) org.slf4j.LoggerFactory.getILoggerFactory()).getMarkerFactory(); + } + + /** + * This is the default implementation of writeObject. Customise if necessary. + */ + private void writeObject(final ObjectOutputStream aOutputStream) throws IOException { + // perform the default serialization for all non-transient, non-static fields + aOutputStream.defaultWriteObject(); + } + + private static Level getLevel(final int i) { + switch (i) { + case TRACE_INT: + return Level.TRACE; + case DEBUG_INT: + return Level.DEBUG; + case INFO_INT: + return Level.INFO; + case WARN_INT: + return Level.WARN; + case ERROR_INT: + return Level.ERROR; + } + return Level.ERROR; + } + + @Override + public LoggingEventBuilder makeLoggingEventBuilder(final org.slf4j.event.Level level) { + final Level log4jLevel = getLevel(level.toInt()); + return new Log4jEventBuilder(markerFactory, logger, log4jLevel); + } + + @Override + public boolean isEnabledForLevel(final org.slf4j.event.Level level) { + return logger.isEnabled(getLevel(level.toInt())); + } +} diff --git a/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java new file mode 100644 index 0000000..e5940be --- /dev/null +++ b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java @@ -0,0 +1,71 @@ +/* + * 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.function.Predicate; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.LoggingException; +import org.apache.logging.log4j.spi.AbstractLoggerAdapter; +import org.apache.logging.log4j.spi.LoggerContext; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.StackLocatorUtil; +import org.slf4j.ILoggerFactory; +import org.slf4j.Logger; + +/** + * Log4j implementation of SLF4J ILoggerFactory interface. + */ +public class Log4jLoggerFactory extends AbstractLoggerAdapter implements ILoggerFactory { + + private static final StatusLogger LOGGER = StatusLogger.getLogger(); + private static final String SLF4J_PACKAGE = "org.slf4j"; + private static final Predicate> CALLER_PREDICATE = clazz -> + !AbstractLoggerAdapter.class.equals(clazz) && !clazz.getName().startsWith(SLF4J_PACKAGE); + private static final String TO_SLF4J_CONTEXT = "org.apache.logging.slf4j.SLF4JLoggerContext"; + + private final Log4jMarkerFactory markerFactory; + + public Log4jLoggerFactory(final Log4jMarkerFactory markerFactory) { + this.markerFactory = markerFactory; + } + + @Override + protected Logger newLogger(final String name, final LoggerContext context) { + final String key = Logger.ROOT_LOGGER_NAME.equals(name) ? LogManager.ROOT_LOGGER_NAME : name; + return new Log4jLogger(markerFactory, validateContext(context).getLogger(key), name); + } + + @Override + protected LoggerContext getContext() { + final Class anchor = LogManager.getFactory().isClassLoaderDependent() + ? StackLocatorUtil.getCallerClass(Log4jLoggerFactory.class, CALLER_PREDICATE) + : null; + LOGGER.trace("Log4jLoggerFactory.getContext() found anchor {}", anchor); + return anchor == null ? LogManager.getContext(false) : getContext(anchor); + } + + Log4jMarkerFactory getMarkerFactory() { + return markerFactory; + } + + private LoggerContext validateContext(final LoggerContext context) { + if (TO_SLF4J_CONTEXT.equals(context.getClass().getName())) { + throw new LoggingException("log4j-slf4j2-impl cannot be present with log4j-to-slf4j"); + } + return context; + } +} diff --git a/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/Log4jMDCAdapter.java b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/Log4jMDCAdapter.java new file mode 100644 index 0000000..bae69e9 --- /dev/null +++ b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/Log4jMDCAdapter.java @@ -0,0 +1,151 @@ +/* + * 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.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.ThreadContext.ContextStack; +import org.apache.logging.log4j.status.StatusLogger; +import org.slf4j.spi.MDCAdapter; + +/** + * + */ +public class Log4jMDCAdapter implements MDCAdapter { + + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final ThreadLocalMapOfStacks mapOfStacks = new ThreadLocalMapOfStacks(); + + @Override + public void put(final String key, final String val) { + ThreadContext.put(key, val); + } + + @Override + public String get(final String key) { + return ThreadContext.get(key); + } + + @Override + public void remove(final String key) { + ThreadContext.remove(key); + } + + @Override + public void clear() { + ThreadContext.clearMap(); + } + + @Override + public Map getCopyOfContextMap() { + return ThreadContext.getContext(); + } + + @Override + public void setContextMap(final Map map) { + ThreadContext.clearMap(); + ThreadContext.putAll(map); + } + + @Override + public void pushByKey(final String key, final String value) { + if (key == null) { + ThreadContext.push(value); + } else { + final String oldValue = mapOfStacks.peekByKey(key); + if (!Objects.equals(ThreadContext.get(key), oldValue)) { + LOGGER.warn("The key {} was used in both the string and stack-valued MDC.", key); + } + mapOfStacks.pushByKey(key, value); + ThreadContext.put(key, value); + } + } + + @Override + public String popByKey(final String key) { + if (key == null) { + return ThreadContext.getDepth() > 0 ? ThreadContext.pop() : null; + } + final String value = mapOfStacks.popByKey(key); + if (!Objects.equals(ThreadContext.get(key), value)) { + LOGGER.warn("The key {} was used in both the string and stack-valued MDC.", key); + } + ThreadContext.put(key, mapOfStacks.peekByKey(key)); + return value; + } + + @Override + public Deque getCopyOfDequeByKey(final String key) { + if (key == null) { + final ContextStack stack = ThreadContext.getImmutableStack(); + final Deque copy = new ArrayDeque<>(stack.size()); + stack.forEach(copy::push); + return copy; + } + return mapOfStacks.getCopyOfDequeByKey(key); + } + + @Override + public void clearDequeByKey(final String key) { + if (key == null) { + ThreadContext.clearStack(); + } else { + mapOfStacks.clearByKey(key); + ThreadContext.put(key, null); + } + } + + private static class ThreadLocalMapOfStacks { + + private final ThreadLocal>> tlMapOfStacks = ThreadLocal.withInitial(HashMap::new); + + public void pushByKey(final String key, final String value) { + tlMapOfStacks + .get() + .computeIfAbsent(key, ignored -> new ArrayDeque<>()) + .push(value); + } + + public String popByKey(final String key) { + final Deque deque = tlMapOfStacks.get().get(key); + return deque != null ? deque.poll() : null; + } + + public Deque getCopyOfDequeByKey(final String key) { + final Deque deque = tlMapOfStacks.get().get(key); + return deque != null ? new ArrayDeque<>(deque) : null; + } + + public void clearByKey(final String key) { + final Deque deque = tlMapOfStacks.get().get(key); + if (deque != null) { + deque.clear(); + } + } + + public String peekByKey(final String key) { + final Deque deque = tlMapOfStacks.get().get(key); + return deque != null ? deque.peek() : null; + } + } +} diff --git a/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/Log4jMarker.java b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/Log4jMarker.java new file mode 100644 index 0000000..64b9ef9 --- /dev/null +++ b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/Log4jMarker.java @@ -0,0 +1,126 @@ +/* + * 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.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import org.apache.logging.log4j.MarkerManager; +import org.slf4j.IMarkerFactory; +import org.slf4j.Marker; + +/** + * Log4j/SLF4J {@link Marker} type bridge. + */ +public class Log4jMarker implements Marker { + + public static final long serialVersionUID = 1590472L; + + private final IMarkerFactory factory; + + private final org.apache.logging.log4j.Marker marker; + + /** + * Constructs a Log4jMarker using an existing Log4j {@link org.apache.logging.log4j.Marker}. + * @param marker The Log4j Marker upon which to base this Marker. + */ + public Log4jMarker(final IMarkerFactory markerFactory, final org.apache.logging.log4j.Marker marker) { + this.factory = markerFactory; + this.marker = marker; + } + + @Override + public void add(final Marker marker) { + if (marker == null) { + throw new IllegalArgumentException(); + } + final Marker m = factory.getMarker(marker.getName()); + this.marker.addParents(((Log4jMarker) m).getLog4jMarker()); + } + + @Override + public boolean contains(final Marker marker) { + if (marker == null) { + throw new IllegalArgumentException(); + } + return this.marker.isInstanceOf(marker.getName()); + } + + @Override + public boolean contains(final String s) { + return s != null ? this.marker.isInstanceOf(s) : false; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Log4jMarker)) { + return false; + } + final Log4jMarker other = (Log4jMarker) obj; + return Objects.equals(marker, other.marker); + } + + public org.apache.logging.log4j.Marker getLog4jMarker() { + return marker; + } + + @Override + public String getName() { + return marker.getName(); + } + + @Override + public boolean hasChildren() { + return marker.hasParents(); + } + + @Override + public int hashCode() { + return 31 + Objects.hashCode(marker); + } + + @Override + public boolean hasReferences() { + return marker.hasParents(); + } + + @Override + public Iterator iterator() { + final org.apache.logging.log4j.Marker[] log4jParents = this.marker.getParents(); + if (log4jParents == null) { + return Collections.emptyIterator(); + } + final List parents = new ArrayList<>(log4jParents.length); + for (final org.apache.logging.log4j.Marker m : log4jParents) { + parents.add(factory.getMarker(m.getName())); + } + return parents.iterator(); + } + + @Override + public boolean remove(final Marker marker) { + return marker != null ? this.marker.remove(MarkerManager.getMarker(marker.getName())) : false; + } +} diff --git a/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/Log4jMarkerFactory.java b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/Log4jMarkerFactory.java new file mode 100644 index 0000000..cedfc06 --- /dev/null +++ b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/Log4jMarkerFactory.java @@ -0,0 +1,151 @@ +/* + * 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.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.status.StatusLogger; +import org.slf4j.IMarkerFactory; +import org.slf4j.Marker; + +/** + * Log4j/SLF4J bridge to create SLF4J Markers based on name or based on existing SLF4J Markers. + */ +public class Log4jMarkerFactory implements IMarkerFactory { + + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final ConcurrentMap markerMap = new ConcurrentHashMap<>(); + + /** + * Returns a Log4j Marker that is compatible with SLF4J. + * @param name The name of the Marker. + * @return A Marker. + */ + @Override + public Marker getMarker(final String name) { + if (name == null) { + throw new IllegalArgumentException("Marker name must not be null"); + } + final Marker marker = markerMap.get(name); + if (marker != null) { + return marker; + } + final org.apache.logging.log4j.Marker log4jMarker = MarkerManager.getMarker(name); + return addMarkerIfAbsent(name, log4jMarker); + } + + private Marker addMarkerIfAbsent(final String name, final org.apache.logging.log4j.Marker log4jMarker) { + final Marker marker = new Log4jMarker(this, log4jMarker); + final Marker existing = markerMap.putIfAbsent(name, marker); + return existing == null ? marker : existing; + } + + /** + * Returns a Log4j Marker converted from an existing custom SLF4J Marker. + * @param marker The SLF4J Marker to convert. + * @return A converted Log4j/SLF4J Marker. + * @since 2.1 + */ + public Marker getMarker(final Marker marker) { + if (marker == null) { + throw new IllegalArgumentException("Marker must not be null"); + } + final Marker m = markerMap.get(marker.getName()); + if (m != null) { + return m; + } + return addMarkerIfAbsent(marker.getName(), convertMarker(marker)); + } + + /** + * Gets the Log4j2 marker associated to this SLF4J marker or creates a new one. + * + * @param marker a SLF4J marker + * @return a Log4j2 marker + */ + org.apache.logging.log4j.Marker getLog4jMarker(final Marker marker) { + if (marker == null) { + return null; + } else if (marker instanceof Log4jMarker) { + return ((Log4jMarker) marker).getLog4jMarker(); + } else { + return ((Log4jMarker) getMarker(marker)).getLog4jMarker(); + } + } + + static org.apache.logging.log4j.Marker convertMarker(final Marker original) { + if (original == null) { + throw new IllegalArgumentException("Marker must not be null"); + } + return convertMarker(original, new ArrayList()); + } + + private static org.apache.logging.log4j.Marker convertMarker( + final Marker original, final Collection visited) { + final org.apache.logging.log4j.Marker marker = MarkerManager.getMarker(original.getName()); + if (original.hasReferences()) { + final Iterator it = original.iterator(); + while (it.hasNext()) { + final Marker next = it.next(); + if (visited.contains(next)) { + LOGGER.warn("Found a cycle in Marker [{}]. Cycle will be broken.", next.getName()); + } else { + visited.add(next); + marker.addParents(convertMarker(next, visited)); + } + } + } + return marker; + } + + /** + * Returns true if the Marker exists. + * @param name The Marker name. + * @return {@code true} if the Marker exists, {@code false} otherwise. + */ + @Override + public boolean exists(final String name) { + return markerMap.containsKey(name); + } + + /** + * Log4j does not support detached Markers. This method always returns false. + * @param name The Marker name. + * @return {@code false} + */ + @Override + public boolean detachMarker(final String name) { + return false; + } + + /** + * Log4j does not support detached Markers for performance reasons. The returned Marker is attached. + * @param name The Marker name. + * @return The named Marker (unmodified). + */ + @Override + public Marker getDetachedMarker(final String name) { + LOGGER.warn("Log4j does not support detached Markers. Returned Marker [{}] will be unchanged.", name); + return getMarker(name); + } +} diff --git a/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/SLF4JLoggingException.java b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/SLF4JLoggingException.java new file mode 100644 index 0000000..57a65e1 --- /dev/null +++ b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/SLF4JLoggingException.java @@ -0,0 +1,41 @@ +/* + * 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; + +/** + * Exception thrown when the SLF4J adapter encounters a problem. + * + */ +public class SLF4JLoggingException extends RuntimeException { + + /** + * Generated serial version ID. + */ + private static final long serialVersionUID = -1618650972455089998L; + + public SLF4JLoggingException(final String msg) { + super(msg); + } + + public SLF4JLoggingException(final String msg, final Exception ex) { + super(msg, ex); + } + + public SLF4JLoggingException(final Exception ex) { + super(ex); + } +} diff --git a/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/SLF4JServiceProvider.java b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/SLF4JServiceProvider.java new file mode 100644 index 0000000..abfdcd1 --- /dev/null +++ b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/SLF4JServiceProvider.java @@ -0,0 +1,60 @@ +/* + * 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.slf4j.ILoggerFactory; +import org.slf4j.IMarkerFactory; +import org.slf4j.spi.MDCAdapter; + +@ServiceProvider(value = org.slf4j.spi.SLF4JServiceProvider.class, resolution = Resolution.MANDATORY) +public class SLF4JServiceProvider implements org.slf4j.spi.SLF4JServiceProvider { + + public static final String REQUESTED_API_VERSION = "2.0.99"; + + private ILoggerFactory loggerFactory; + private Log4jMarkerFactory markerFactory; + private MDCAdapter mdcAdapter; + + @Override + public ILoggerFactory getLoggerFactory() { + return loggerFactory; + } + + @Override + public IMarkerFactory getMarkerFactory() { + return markerFactory; + } + + @Override + public MDCAdapter getMDCAdapter() { + return mdcAdapter; + } + + @Override + public String getRequestedApiVersion() { + return REQUESTED_API_VERSION; + } + + @Override + public void initialize() { + markerFactory = new Log4jMarkerFactory(); + loggerFactory = new Log4jLoggerFactory(markerFactory); + mdcAdapter = new Log4jMDCAdapter(); + } +} diff --git a/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/message/ThrowableConsumingMessageFactory.java b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/message/ThrowableConsumingMessageFactory.java new file mode 100644 index 0000000..cb55499 --- /dev/null +++ b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/message/ThrowableConsumingMessageFactory.java @@ -0,0 +1,187 @@ +/* + * 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.message; + +import java.util.Arrays; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFactory2; +import org.apache.logging.log4j.message.ObjectMessage; +import org.apache.logging.log4j.message.ParameterizedMessage; +import org.apache.logging.log4j.message.SimpleMessage; + +/** + * A message factory that eagerly removes a trailing throwable argument. + *

+ * This factory implements the algorithm used by Logback 1.1.x or later to determine + * {@link Message#getThrowable()}: if the last argument is a {@link Throwable} and there are less arguments than + * the number of placeholders incremented by one, the last argument will be used as + * {@link Message#getThrowable()} and will not appear in the parameterized message. + *

+ *

+ * The usual Log4j semantic only looks for throwables once all the placeholders have been filled. + *

+ * @since 2.24.0 + */ +public final class ThrowableConsumingMessageFactory implements MessageFactory2 { + + private Message newParameterizedMessage(final Object throwable, final String pattern, final Object... args) { + return new ParameterizedMessage(pattern, args, (Throwable) throwable); + } + + @Override + public Message newMessage(final Object message) { + return new ObjectMessage(message); + } + + @Override + public Message newMessage(final String message) { + return new SimpleMessage(message); + } + + @Override + public Message newMessage(final String message, final Object... params) { + if (params != null && params.length > 0) { + final Object lastArg = params[params.length - 1]; + return lastArg instanceof Throwable + ? newParameterizedMessage(lastArg, message, Arrays.copyOf(params, params.length - 1)) + : newParameterizedMessage(null, message, params); + } + return new SimpleMessage(message); + } + + @Override + public Message newMessage(final CharSequence charSequence) { + return new SimpleMessage(charSequence); + } + + @Override + public Message newMessage(final String message, final Object p0) { + return p0 instanceof Throwable + ? newParameterizedMessage(p0, message) + : newParameterizedMessage(null, message, p0); + } + + @Override + public Message newMessage(final String message, final Object p0, final Object p1) { + return p1 instanceof Throwable + ? newParameterizedMessage(p1, message, p0) + : newParameterizedMessage(null, message, p0, p1); + } + + @Override + public Message newMessage(final String message, final Object p0, final Object p1, final Object p2) { + return p2 instanceof Throwable + ? newParameterizedMessage(p2, message, p0, p1) + : newParameterizedMessage(null, message, p0, p1, p2); + } + + @Override + public Message newMessage( + final String message, final Object p0, final Object p1, final Object p2, final Object p3) { + return p3 instanceof Throwable + ? newParameterizedMessage(p3, message, p0, p1, p2) + : newParameterizedMessage(null, message, p0, p1, p2, p3); + } + + @Override + public Message newMessage( + final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { + return p4 instanceof Throwable + ? newParameterizedMessage(p4, message, p0, p1, p2, p3) + : newParameterizedMessage(null, message, p0, p1, p2, p3, p4); + } + + @Override + public Message newMessage( + final String message, + final Object p0, + final Object p1, + final Object p2, + final Object p3, + final Object p4, + final Object p5) { + return p5 instanceof Throwable + ? newParameterizedMessage(p5, message, p0, p1, p2, p3, p4) + : newParameterizedMessage(null, message, p0, p1, p2, p3, p4, p5); + } + + @Override + public Message newMessage( + 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 p6 instanceof Throwable + ? newParameterizedMessage(p6, message, p0, p1, p2, p3, p4, p5) + : newParameterizedMessage(null, message, p0, p1, p2, p3, p4, p5, p6); + } + + @Override + public Message newMessage( + 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 p7 instanceof Throwable + ? newParameterizedMessage(p7, message, p0, p1, p2, p3, p4, p5, p6) + : newParameterizedMessage(null, message, p0, p1, p2, p3, p4, p5, p6, p7); + } + + @Override + public Message newMessage( + 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 p8 instanceof Throwable + ? newParameterizedMessage(p8, message, p0, p1, p2, p3, p4, p5, p6, p7) + : newParameterizedMessage(null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8); + } + + @Override + public Message newMessage( + 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 p9 instanceof Throwable + ? newParameterizedMessage(p9, message, p0, p1, p2, p3, p4, p5, p6, p7, p8) + : newParameterizedMessage(null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); + } +} diff --git a/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/message/package-info.java b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/message/package-info.java new file mode 100644 index 0000000..764c1cf --- /dev/null +++ b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/message/package-info.java @@ -0,0 +1,22 @@ +/* + * 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. + */ +@Export +@Version("2.24.0") +package org.apache.logging.slf4j.message; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/package-info.java b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/package-info.java new file mode 100644 index 0000000..511a8c7 --- /dev/null +++ b/slf4j-to-log4j-api/src/main/java/org/apache/logging/slf4j/package-info.java @@ -0,0 +1,30 @@ +/* + * 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. Note that this does indeed share the same package namespace as the one found in log4j-to-slf4j; + * this is intentional. The two JARs should not be used at the same time! Thus, in an OSGi environment + * where split packages are not allowed, this error is prevented due to both JARs sharing an exported package name. + */ +@Export +@Header(name = Constants.BUNDLE_ACTIVATIONPOLICY, value = Constants.ACTIVATION_LAZY) +@Version("2.21.0") +package org.apache.logging.slf4j; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.bundle.Header; +import org.osgi.annotation.versioning.Version; +import org.osgi.framework.Constants; diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/other/pkg/LoggerContextAnchorTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/other/pkg/LoggerContextAnchorTest.java new file mode 100644 index 0000000..a2b7c7f --- /dev/null +++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/other/pkg/LoggerContextAnchorTest.java @@ -0,0 +1,91 @@ +/* + * 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.other.pkg; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.status.StatusListener; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; + +/** + * Test LoggerContext lookups by verifying the anchor class representing calling code. + */ +public class LoggerContextAnchorTest { + private static final String PREFIX = "Log4jLoggerFactory.getContext() found anchor class "; + + @Test + public void testLoggerFactoryLookupClass() { + final String fqcn = getAnchorFqcn(() -> LoggerFactory.getLogger(LoggerContextAnchorTest.class)); + assertEquals(getClass().getName(), fqcn); + } + + @Test + public void testLoggerFactoryLookupString() { + final String fqcn = getAnchorFqcn(() -> LoggerFactory.getLogger("custom.logger")); + assertEquals(getClass().getName(), fqcn); + } + + @Test + public void testLoggerFactoryGetILoggerFactoryLookup() { + final String fqcn = + getAnchorFqcn(() -> LoggerFactory.getILoggerFactory().getLogger("custom.logger")); + assertEquals(getClass().getName(), fqcn); + } + + private static String getAnchorFqcn(final Runnable runnable) { + final List results = new CopyOnWriteArrayList<>(); + final StatusListener listener = new StatusListener() { + @Override + public void log(final StatusData data) { + final String formattedMessage = data.getMessage().getFormattedMessage(); + if (formattedMessage.startsWith(PREFIX)) { + results.add(formattedMessage.substring(PREFIX.length())); + } + } + + @Override + public Level getStatusLevel() { + return Level.TRACE; + } + + @Override + public void close() { + // nop + } + }; + final StatusLogger statusLogger = StatusLogger.getLogger(); + statusLogger.registerListener(listener); + try { + runnable.run(); + if (results.isEmpty()) { + throw new AssertionError("Failed to locate an anchor lookup status message"); + } + if (results.size() > 1) { + throw new AssertionError("Found multiple anchor lines: " + results); + } + return results.get(0); + } finally { + statusLogger.removeListener(listener); + } + } +} diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/CallerInformationTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/CallerInformationTest.java new file mode 100644 index 0000000..b0bc3e2 --- /dev/null +++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/CallerInformationTest.java @@ -0,0 +1,85 @@ +/* + * 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 java.util.List; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.spi.CallerBoundaryAware; +import org.slf4j.spi.LoggingEventBuilder; + +@LoggerContextSource("log4j2-calling-class.xml") +public class CallerInformationTest { + + @Test + public void testClassLogger(@Named("Class") final ListAppender app) throws Exception { + app.clear(); + final Logger logger = LoggerFactory.getLogger("ClassLogger"); + logger.info("Ignored message contents."); + logger.warn("Verifying the caller class is still correct."); + logger.error("Hopefully nobody breaks me!"); + logger.atInfo().log("Ignored message contents."); + logger.atWarn().log("Verifying the caller class is still correct."); + logger.atError().log("Hopefully nobody breaks me!"); + final List messages = app.getMessages(); + assertEquals(6, messages.size(), "Incorrect number of messages."); + for (final String message : messages) { + assertEquals(this.getClass().getName(), message, "Incorrect caller class name."); + } + } + + @Test + public void testMethodLogger(@Named("Method") final ListAppender app) throws Exception { + app.clear(); + final Logger logger = LoggerFactory.getLogger("MethodLogger"); + logger.info("More messages."); + logger.warn("CATASTROPHE INCOMING!"); + logger.error("ZOMBIES!!!"); + logger.warn("brains~~~"); + logger.info("Itchy. Tasty."); + logger.atInfo().log("More messages."); + logger.atWarn().log("CATASTROPHE INCOMING!"); + logger.atError().log("ZOMBIES!!!"); + logger.atWarn().log("brains~~~"); + logger.atInfo().log("Itchy. Tasty."); + final List messages = app.getMessages(); + assertEquals(10, messages.size(), "Incorrect number of messages."); + for (final String message : messages) { + assertEquals("testMethodLogger", message, "Incorrect caller method name."); + } + } + + @Test + public void testFqcnLogger(@Named("Fqcn") final ListAppender app) throws Exception { + app.clear(); + final Logger logger = LoggerFactory.getLogger("FqcnLogger"); + LoggingEventBuilder loggingEventBuilder = logger.atInfo(); + ((CallerBoundaryAware) loggingEventBuilder).setCallerBoundary("MyFqcn"); + loggingEventBuilder.log("A message"); + final List messages = app.getMessages(); + assertEquals(1, messages.size(), "Incorrect number of messages."); + for (final String message : messages) { + assertEquals("MyFqcn", message, "Incorrect fqcn."); + } + } +} diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/CustomFlatMarker.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/CustomFlatMarker.java new file mode 100644 index 0000000..3d82209 --- /dev/null +++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/CustomFlatMarker.java @@ -0,0 +1,74 @@ +/* + * 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.Iterator; +import org.slf4j.Marker; + +/** + * Test Marker that may contain no reference/parent Markers. + * @see LOG4J2-793 + */ +public class CustomFlatMarker implements Marker { + private static final long serialVersionUID = -4115520883240247266L; + + private final String name; + + public CustomFlatMarker(final String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public void add(final Marker reference) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Marker reference) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasChildren() { + return hasReferences(); + } + + @Override + public boolean hasReferences() { + return false; + } + + @Override + public Iterator iterator() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean contains(final Marker other) { + return false; + } + + @Override + public boolean contains(final String name) { + return false; + } +} diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4j1222Test.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4j1222Test.java new file mode 100644 index 0000000..6b4e509 --- /dev/null +++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4j1222Test.java @@ -0,0 +1,56 @@ +/* + * 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.assertInstanceOf; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Tests logging during shutdown. + */ +public class Log4j1222Test { + + @Test + public void homepageRendersSuccessfully() { + System.setProperty("log4j.configurationFile", "log4j2-console.xml"); + Runtime.getRuntime().addShutdownHook(new ShutdownHook()); + } + + private static class ShutdownHook extends Thread { + + private static class Holder { + private static final Logger LOGGER = LoggerFactory.getLogger(Log4j1222Test.class); + } + + @Override + public void run() { + super.run(); + trigger(); + } + + private void trigger() { + Holder.LOGGER.info("Attempt to trigger"); + assertInstanceOf( + Log4jLogger.class, + Holder.LOGGER, + "Logger is of type " + Holder.LOGGER.getClass().getName()); + } + } +} diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4j2_1482_Slf4jTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4j2_1482_Slf4jTest.java new file mode 100644 index 0000000..d8abaf6 --- /dev/null +++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4j2_1482_Slf4jTest.java @@ -0,0 +1,39 @@ +/* + * 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.core.test.layout.Log4j2_1482_Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Tests https://issues.apache.org/jira/browse/LOG4J2-1482 + */ +public class Log4j2_1482_Slf4jTest extends Log4j2_1482_Test { + + @Override + protected void log(final int runNumber) { + if (runNumber == 2) { + // System.out.println("Set a breakpoint here."); + } + final Logger logger = LoggerFactory.getLogger("auditcsvfile"); + final int val1 = 9, val2 = 11, val3 = 12; + logger.info("Info Message!", val1, val2, val3); + logger.info("Info Message!", val1, val2, val3); + logger.info("Info Message!", val1, val2, val3); + } +} diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4jEventBuilderTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4jEventBuilderTest.java new file mode 100644 index 0000000..8441bb1 --- /dev/null +++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4jEventBuilderTest.java @@ -0,0 +1,65 @@ +/* + * 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.List; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@LoggerContextSource("log4j2-config.xml") +public class Log4jEventBuilderTest { + + private final Logger logger; + private final ListAppender appender; + + public Log4jEventBuilderTest(@Named("List") final Appender appender) { + logger = LoggerFactory.getLogger("org.apache.test.Log4jEventBuilderTest"); + this.appender = (ListAppender) appender; + } + + @BeforeEach + public void setUp() { + appender.clear(); + } + + @Test + public void testKeyValuePairs() { + logger.atDebug().addKeyValue("testKeyValuePairs", "ok").log(); + final List events = appender.getEvents(); + assertThat(events).hasSize(1); + assertThat(events.get(0).getContextData().toMap()).containsEntry("testKeyValuePairs", "ok"); + } + + @Test + public void testArguments() { + logger.atDebug().setMessage("{}-{}").addArgument("a").addArgument("b").log(); + logger.atDebug().log("{}-{}", "a", "b"); + logger.atDebug().addArgument("a").log("{}-{}", "b"); + logger.atDebug().log("{}-{}", new Object[] {"a", "b"}); + assertThat(appender.getEvents()).hasSize(4).allMatch(event -> "a-b" + .equals(event.getMessage().getFormattedMessage())); + } +} diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4jMDCAdapterTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4jMDCAdapterTest.java new file mode 100644 index 0000000..0559608 --- /dev/null +++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4jMDCAdapterTest.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.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class Log4jMDCAdapterTest { + + private static final Log4jMDCAdapter MDC_ADAPTER = new Log4jMDCAdapter(); + private static final String KEY = "Log4j2"; + + private static Deque createDeque(final int size) { + final Deque result = new ArrayDeque<>(size); + IntStream.range(0, size).mapToObj(Integer::toString).forEach(result::addLast); + return result; + } + + private static Deque popDeque(final String key) { + final Deque result = new ArrayDeque<>(); + String value; + while ((value = MDC_ADAPTER.popByKey(key)) != null) { + result.addLast(value); + } + return result; + } + + static Stream keys() { + return Stream.of(KEY, "", null); + } + + @ParameterizedTest + @MethodSource("keys") + public void testPushPopByKey(final String key) { + MDC_ADAPTER.clearDequeByKey(key); + final Deque expectedValues = createDeque(100); + expectedValues.descendingIterator().forEachRemaining(v -> MDC_ADAPTER.pushByKey(key, v)); + assertThat(MDC_ADAPTER.getCopyOfDequeByKey(key)).containsExactlyElementsOf(expectedValues); + assertThat(popDeque(key)).containsExactlyElementsOf(expectedValues); + } +} diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4jMarkerTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4jMarkerTest.java new file mode 100644 index 0000000..4926d3b --- /dev/null +++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/Log4jMarkerTest.java @@ -0,0 +1,48 @@ +/* + * 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 static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class Log4jMarkerTest { + + private static Log4jMarkerFactory markerFactory; + + @BeforeAll + public static void startup() { + markerFactory = ((Log4jLoggerFactory) org.slf4j.LoggerFactory.getILoggerFactory()).getMarkerFactory(); + } + + @Test + public void testEquals() { + final Marker markerA = MarkerManager.getMarker(Log4jMarkerTest.class.getName() + "-A"); + final Marker markerB = MarkerManager.getMarker(Log4jMarkerTest.class.getName() + "-B"); + final Log4jMarker marker1 = new Log4jMarker(markerFactory, markerA); + final Log4jMarker marker2 = new Log4jMarker(markerFactory, markerA); + final Log4jMarker marker3 = new Log4jMarker(markerFactory, markerB); + assertEquals(marker1, marker2); + assertNotEquals(marker1, null); + assertNotEquals(null, marker1); + assertNotEquals(marker1, marker3); + } +} diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/LoggerContextTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/LoggerContextTest.java new file mode 100644 index 0000000..720bcfc --- /dev/null +++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/LoggerContextTest.java @@ -0,0 +1,44 @@ +/* + * 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.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Set; +import org.apache.logging.log4j.core.LifeCycle; +import org.apache.logging.log4j.spi.LoggerContext; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; + +/** + * Tests cleanup of the LoggerContexts. + */ +public class LoggerContextTest { + + @Test + public void testCleanup() throws Exception { + final Log4jLoggerFactory factory = (Log4jLoggerFactory) LoggerFactory.getILoggerFactory(); + factory.getLogger("test"); + Set set = factory.getLoggerContexts(); + final LoggerContext ctx1 = set.toArray(LoggerContext.EMPTY_ARRAY)[0]; + assertInstanceOf(LifeCycle.class, ctx1, "LoggerContext is not enabled for shutdown"); + ((LifeCycle) ctx1).stop(); + set = factory.getLoggerContexts(); + assertTrue(set.isEmpty(), "Expected no LoggerContexts"); + } +} diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/LoggerTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/LoggerTest.java new file mode 100644 index 0000000..86e6cb2 --- /dev/null +++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/LoggerTest.java @@ -0,0 +1,210 @@ +/* + * 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.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.slf4j.Marker; +import org.slf4j.spi.LocationAwareLogger; +import org.slf4j.spi.LoggingEventBuilder; + +/** + * + */ +@LoggerContextSource("log4j-test1.xml") +public class LoggerTest { + + private final Logger logger; + private final LoggerContext ctx; + + @Test + public void debug() { + logger.debug("Debug message"); + verify("o.a.l.s.LoggerTest Debug message MDC{}" + Strings.LINE_SEPARATOR); + } + + public LoggerTest(final LoggerContext context) { + this.ctx = context; + this.logger = LoggerFactory.getLogger("LoggerTest"); + } + + @Test + public void debugNoParms() { + logger.debug("Debug message {}"); + verify("o.a.l.s.LoggerTest Debug message {} MDC{}" + Strings.LINE_SEPARATOR); + logger.debug("Debug message {}", (Object[]) null); + verify("o.a.l.s.LoggerTest Debug message {} MDC{}" + Strings.LINE_SEPARATOR); + ((LocationAwareLogger) logger) + .log(null, Log4jLogger.class.getName(), LocationAwareLogger.DEBUG_INT, "Debug message {}", null, null); + verify("o.a.l.s.LoggerTest Debug message {} MDC{}" + Strings.LINE_SEPARATOR); + } + + @Test + public void debugWithParms() { + logger.debug("Hello, {}", "World"); + verify("o.a.l.s.LoggerTest Hello, World MDC{}" + Strings.LINE_SEPARATOR); + } + + @Test + public void mdc() { + + MDC.put("TestYear", "2010"); + logger.debug("Debug message"); + verify("o.a.l.s.LoggerTest Debug message MDC{TestYear=2010}" + Strings.LINE_SEPARATOR); + MDC.clear(); + logger.debug("Debug message"); + verify("o.a.l.s.LoggerTest Debug message MDC{}" + Strings.LINE_SEPARATOR); + } + + @Test + public void mdcStack() { + MDC.pushByKey("TestYear", "2010"); + logger.debug("Debug message"); + verify("o.a.l.s.LoggerTest Debug message MDC{TestYear=2010}" + Strings.LINE_SEPARATOR); + MDC.pushByKey("TestYear", "2011"); + logger.debug("Debug message"); + verify("o.a.l.s.LoggerTest Debug message MDC{TestYear=2011}" + Strings.LINE_SEPARATOR); + MDC.popByKey("TestYear"); + logger.debug("Debug message"); + verify("o.a.l.s.LoggerTest Debug message MDC{TestYear=2010}" + Strings.LINE_SEPARATOR); + MDC.clear(); + logger.debug("Debug message"); + verify("o.a.l.s.LoggerTest Debug message MDC{}" + Strings.LINE_SEPARATOR); + } + + /** + * @see LOG4J2-793 + */ + @Test + public void supportsCustomSLF4JMarkers() { + final Marker marker = new CustomFlatMarker("TEST"); + logger.debug(marker, "Test"); + verify("o.a.l.s.LoggerTest Test MDC{}" + Strings.LINE_SEPARATOR); + } + + @Test + public void testRootLogger() { + final Logger l = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + assertNotNull(l, "No Root Logger"); + assertEquals(Logger.ROOT_LOGGER_NAME, l.getName()); + } + + @Test + public void doubleSubst() { + logger.debug("Hello, {}", "Log4j {}"); + verify("o.a.l.s.LoggerTest Hello, Log4j {} MDC{}" + Strings.LINE_SEPARATOR); + } + + @Test + public void testThrowable() { + final Throwable expected = new RuntimeException(); + logger.debug("Hello {}", expected); + verifyThrowable(expected); + logger.debug("Hello {}", (Object) expected); + verifyThrowable(null); + logger.debug("Hello", expected); + verifyThrowable(expected); + logger.debug("Hello {}! {}", "World!", expected); + verifyThrowable(null); + logger.debug("Hello {}!", "World!", expected); + verifyThrowable(expected); + final LocationAwareLogger lal = (LocationAwareLogger) logger; + lal.log(null, LoggerTest.class.getName(), LocationAwareLogger.DEBUG_INT, "Hello {}", null, expected); + verifyThrowable(expected); + lal.log( + null, + LoggerTest.class.getName(), + LocationAwareLogger.DEBUG_INT, + "Hello {}", + new Object[] {expected}, + null); + verifyThrowable(null); + lal.log( + null, + LoggerTest.class.getName(), + LocationAwareLogger.DEBUG_INT, + "Hello {}", + new Object[] {"World!", expected}, + null); + verifyThrowable(expected); + } + + @Test + public void testLazyLoggingEventBuilder() { + final ListAppender appender = ctx.getConfiguration().getAppender("UnformattedList"); + final Level oldLevel = ctx.getRootLogger().getLevel(); + try { + Configurator.setRootLevel(Level.ERROR); + final LoggingEventBuilder builder = logger.makeLoggingEventBuilder(org.slf4j.event.Level.DEBUG); + Configurator.setRootLevel(Level.DEBUG); + builder.log(); + assertThat(appender.getEvents()).hasSize(1).map(LogEvent::getLevel).containsExactly(Level.DEBUG); + } finally { + Configurator.setRootLevel(oldLevel); + } + } + + private ListAppender getAppenderByName(final String name) { + final ListAppender listApp = ctx.getConfiguration().getAppender(name); + assertNotNull(listApp, "Missing Appender"); + return listApp; + } + + private void verify(final String expected) { + final ListAppender listApp = getAppenderByName("List"); + final List events = listApp.getMessages(); + assertEquals(1, events.size(), "Incorrect number of messages. Expected 1 Actual " + events.size()); + final String actual = events.get(0); + assertEquals(expected, actual, "Incorrect message. Expected \" + expected + \". Actual \" + actual"); + listApp.clear(); + } + + private void verifyThrowable(final Throwable expected) { + final ListAppender listApp = getAppenderByName("UnformattedList"); + final List events = listApp.getEvents(); + assertEquals(1, events.size(), "Incorrect number of messages"); + final LogEvent actual = events.get(0); + assertEquals(expected, actual.getThrown(), "Incorrect throwable."); + listApp.clear(); + } + + @BeforeEach + @AfterEach + public void cleanup( + @Named("List") final ListAppender list, @Named("UnformattedList") final ListAppender unformattedList) { + MDC.clear(); + list.clear(); + unformattedList.clear(); + } +} diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/MarkerTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/MarkerTest.java new file mode 100644 index 0000000..c81045a --- /dev/null +++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/MarkerTest.java @@ -0,0 +1,198 @@ +/* + * 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 static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * + */ +public class MarkerTest { + + private static final String CHILD_MAKER_NAME = MarkerTest.class.getSimpleName() + "-TEST"; + private static final String PARENT_MARKER_NAME = MarkerTest.class.getSimpleName() + "-PARENT"; + private static Log4jMarkerFactory markerFactory; + + @BeforeAll + public static void startup() { + markerFactory = ((Log4jLoggerFactory) org.slf4j.LoggerFactory.getILoggerFactory()).getMarkerFactory(); + } + + @BeforeEach + @AfterEach + public void clearMarkers() { + MarkerManager.clear(); + } + + @Test + public void testAddMarker() { + final String childMakerName = CHILD_MAKER_NAME + "-AM"; + final String parentMarkerName = PARENT_MARKER_NAME + "-AM"; + final org.slf4j.Marker slf4jMarker = org.slf4j.MarkerFactory.getMarker(childMakerName); + final org.slf4j.Marker slf4jParent = org.slf4j.MarkerFactory.getMarker(parentMarkerName); + slf4jMarker.add(slf4jParent); + final Marker log4jParent = MarkerManager.getMarker(parentMarkerName); + final Marker log4jMarker = MarkerManager.getMarker(childMakerName); + + assertInstanceOf(Log4jMarker.class, slf4jMarker, "Incorrect Marker class"); + assertTrue( + log4jMarker.isInstanceOf(log4jParent), + String.format( + "%s (log4jMarker=%s) is not an instance of %s (log4jParent=%s) in Log4j", + childMakerName, parentMarkerName, log4jMarker, log4jParent)); + assertTrue( + slf4jMarker.contains(slf4jParent), + String.format( + "%s (slf4jMarker=%s) is not an instance of %s (log4jParent=%s) in SLF4J", + childMakerName, parentMarkerName, slf4jMarker, slf4jParent)); + } + + @Test + public void testAddNullMarker() { + final String childMarkerName = CHILD_MAKER_NAME + "-ANM"; + final String parentMakerName = PARENT_MARKER_NAME + "-ANM"; + final org.slf4j.Marker slf4jMarker = org.slf4j.MarkerFactory.getMarker(childMarkerName); + final org.slf4j.Marker slf4jParent = org.slf4j.MarkerFactory.getMarker(parentMakerName); + slf4jMarker.add(slf4jParent); + final Marker log4jParent = MarkerManager.getMarker(parentMakerName); + final Marker log4jMarker = MarkerManager.getMarker(childMarkerName); + final Log4jMarker log4jSlf4jParent = new Log4jMarker(markerFactory, log4jParent); + final Log4jMarker log4jSlf4jMarker = new Log4jMarker(markerFactory, log4jMarker); + final org.slf4j.Marker nullMarker = null; + try { + log4jSlf4jParent.add(nullMarker); + fail("Expected " + IllegalArgumentException.class.getName()); + } catch (final IllegalArgumentException e) { + // expected + } + try { + log4jSlf4jMarker.add(nullMarker); + fail("Expected " + IllegalArgumentException.class.getName()); + } catch (final IllegalArgumentException e) { + // expected + } + } + + @Test + public void testAddSameMarker() { + final String childMarkerName = CHILD_MAKER_NAME + "-ASM"; + final String parentMakerName = PARENT_MARKER_NAME + "-ASM"; + final org.slf4j.Marker slf4jMarker = org.slf4j.MarkerFactory.getMarker(childMarkerName); + final org.slf4j.Marker slf4jParent = org.slf4j.MarkerFactory.getMarker(parentMakerName); + slf4jMarker.add(slf4jParent); + slf4jMarker.add(slf4jParent); + final Marker log4jParent = MarkerManager.getMarker(parentMakerName); + final Marker log4jMarker = MarkerManager.getMarker(childMarkerName); + assertTrue( + log4jMarker.isInstanceOf(log4jParent), + String.format( + "%s (log4jMarker=%s) is not an instance of %s (log4jParent=%s) in Log4j", + childMarkerName, parentMakerName, log4jMarker, log4jParent)); + assertTrue( + slf4jMarker.contains(slf4jParent), + String.format( + "%s (slf4jMarker=%s) is not an instance of %s (log4jParent=%s) in SLF4J", + childMarkerName, parentMakerName, slf4jMarker, slf4jParent)); + } + + @Test + public void testEquals() { + final String childMarkerName = CHILD_MAKER_NAME + "-ASM"; + final String parentMakerName = PARENT_MARKER_NAME + "-ASM"; + final org.slf4j.Marker slf4jMarker = org.slf4j.MarkerFactory.getMarker(childMarkerName); + final org.slf4j.Marker slf4jMarker2 = org.slf4j.MarkerFactory.getMarker(childMarkerName); + final org.slf4j.Marker slf4jParent = org.slf4j.MarkerFactory.getMarker(parentMakerName); + slf4jMarker.add(slf4jParent); + final Marker log4jParent = MarkerManager.getMarker(parentMakerName); + final Marker log4jMarker = MarkerManager.getMarker(childMarkerName); + final Marker log4jMarker2 = MarkerManager.getMarker(childMarkerName); + assertEquals(log4jMarker, log4jMarker2); + assertEquals(slf4jMarker, slf4jMarker2); + assertNotEquals(log4jParent, log4jMarker); + assertNotEquals(slf4jParent, slf4jMarker); + } + + @Test + public void testContainsNullMarker() { + final String childMarkerName = CHILD_MAKER_NAME + "-CM"; + final String parentMakerName = PARENT_MARKER_NAME + "-CM"; + final org.slf4j.Marker slf4jMarker = org.slf4j.MarkerFactory.getMarker(childMarkerName); + final org.slf4j.Marker slf4jParent = org.slf4j.MarkerFactory.getMarker(parentMakerName); + slf4jMarker.add(slf4jParent); + final Marker log4jParent = MarkerManager.getMarker(parentMakerName); + final Marker log4jMarker = MarkerManager.getMarker(childMarkerName); + final Log4jMarker log4jSlf4jParent = new Log4jMarker(markerFactory, log4jParent); + final Log4jMarker log4jSlf4jMarker = new Log4jMarker(markerFactory, log4jMarker); + final org.slf4j.Marker nullMarker = null; + try { + assertFalse(log4jSlf4jParent.contains(nullMarker)); + fail("Expected " + IllegalArgumentException.class.getName()); + } catch (final IllegalArgumentException e) { + // expected + } + try { + assertFalse(log4jSlf4jMarker.contains(nullMarker)); + fail("Expected " + IllegalArgumentException.class.getName()); + } catch (final IllegalArgumentException e) { + // expected + } + } + + @Test + public void testContainsNullString() { + final String childMarkerName = CHILD_MAKER_NAME + "-CS"; + final String parentMakerName = PARENT_MARKER_NAME + "-CS"; + final org.slf4j.Marker slf4jMarker = org.slf4j.MarkerFactory.getMarker(childMarkerName); + final org.slf4j.Marker slf4jParent = org.slf4j.MarkerFactory.getMarker(parentMakerName); + slf4jMarker.add(slf4jParent); + final Marker log4jParent = MarkerManager.getMarker(parentMakerName); + final Marker log4jMarker = MarkerManager.getMarker(childMarkerName); + final Log4jMarker log4jSlf4jParent = new Log4jMarker(markerFactory, log4jParent); + final Log4jMarker log4jSlf4jMarker = new Log4jMarker(markerFactory, log4jMarker); + final String nullStr = null; + assertFalse(log4jSlf4jParent.contains(nullStr)); + assertFalse(log4jSlf4jMarker.contains(nullStr)); + } + + @Test + public void testRemoveNullMarker() { + final String childMakerName = CHILD_MAKER_NAME + "-CM"; + final String parentMakerName = PARENT_MARKER_NAME + "-CM"; + final org.slf4j.Marker slf4jMarker = org.slf4j.MarkerFactory.getMarker(childMakerName); + final org.slf4j.Marker slf4jParent = org.slf4j.MarkerFactory.getMarker(parentMakerName); + slf4jMarker.add(slf4jParent); + final Marker log4jParent = MarkerManager.getMarker(parentMakerName); + final Marker log4jMarker = MarkerManager.getMarker(childMakerName); + final Log4jMarker log4jSlf4jParent = new Log4jMarker(markerFactory, log4jParent); + final Log4jMarker log4jSlf4jMarker = new Log4jMarker(markerFactory, log4jMarker); + final org.slf4j.Marker nullMarker = null; + assertFalse(log4jSlf4jParent.remove(nullMarker)); + assertFalse(log4jSlf4jMarker.remove(nullMarker)); + } +} diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/OverflowTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/OverflowTest.java new file mode 100644 index 0000000..d3c7871 --- /dev/null +++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/OverflowTest.java @@ -0,0 +1,41 @@ +/* + * 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.fail; + +import org.apache.logging.log4j.LoggingException; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; + +/** + * Tests StackOverflow when slf4j-impl and to-slf4j are both present. + */ +public class OverflowTest { + + @Test + public void log() { + try { + LoggerFactory.getLogger(OverflowTest.class); + fail("Failed to detect inclusion of log4j-to-slf4j"); + } catch (LoggingException ex) { + // Expected exception. + } catch (StackOverflowError error) { + fail("Failed to detect inclusion of log4j-to-slf4j, caught StackOverflowError"); + } + } +} diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/SerializeTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/SerializeTest.java new file mode 100644 index 0000000..320e424 --- /dev/null +++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/SerializeTest.java @@ -0,0 +1,40 @@ +/* + * 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.apache.logging.log4j.test.SerializableMatchers.serializesRoundTrip; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.io.Serializable; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + */ +@LoggerContextSource(value = "log4j-test1.xml") +public class SerializeTest { + + Logger logger = LoggerFactory.getLogger("LoggerTest"); + + @Test + public void testLogger() throws Exception { + assertThat((Serializable) logger, serializesRoundTrip()); + } +} diff --git a/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/message/ThrowableConsumingMessageFactoryTest.java b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/message/ThrowableConsumingMessageFactoryTest.java new file mode 100644 index 0000000..2a111be --- /dev/null +++ b/slf4j-to-log4j-api/src/test/java/org/apache/logging/slf4j/message/ThrowableConsumingMessageFactoryTest.java @@ -0,0 +1,138 @@ +/* + * 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.message; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFactory2; +import org.junit.jupiter.api.Test; + +public class ThrowableConsumingMessageFactoryTest { + + private static final String MESSAGE = "MESSAGE"; + private static final Object P0 = new Object(); + private static final Object P1 = new Object(); + private static final Object P2 = new Object(); + private static final Object P3 = new Object(); + private static final Object P4 = new Object(); + private static final Object P5 = new Object(); + private static final Object P6 = new Object(); + private static final Object P7 = new Object(); + private static final Object P8 = new Object(); + private static final Object P9 = new Object(); + private static final Object P10 = new Object(); + private static final Object THROWABLE = new Throwable(); + + @Test + void should_not_consume_last_object_parameter() { + final MessageFactory2 factory = new ThrowableConsumingMessageFactory(); + assertThat(factory.newMessage(MESSAGE, P0)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(1, null); + assertThat(factory.newMessage(MESSAGE, P0, P1)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(2, null); + assertThat(factory.newMessage(MESSAGE, P0, P1, P2)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(3, null); + assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(4, null); + assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(5, null); + assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(6, null); + assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, P6)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(7, null); + assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, P6, P7)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(8, null); + assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, P6, P7, P8)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(9, null); + assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(10, null); + assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(11, null); + } + + @Test + void should_consume_last_throwable_parameter() { + final MessageFactory2 factory = new ThrowableConsumingMessageFactory(); + assertThat(factory.newMessage(MESSAGE, THROWABLE)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(0, THROWABLE); + assertThat(factory.newMessage(MESSAGE, P0, THROWABLE)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(1, THROWABLE); + assertThat(factory.newMessage(MESSAGE, P0, P1, THROWABLE)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(2, THROWABLE); + assertThat(factory.newMessage(MESSAGE, P0, P1, P2, THROWABLE)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(3, THROWABLE); + assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, THROWABLE)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(4, THROWABLE); + assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, THROWABLE)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(5, THROWABLE); + assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, THROWABLE)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(6, THROWABLE); + assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, P6, THROWABLE)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(7, THROWABLE); + assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, P6, P7, THROWABLE)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(8, THROWABLE); + assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, P6, P7, P8, THROWABLE)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(9, THROWABLE); + assertThat(factory.newMessage(MESSAGE, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, THROWABLE)) + .extracting(m -> m.getParameters().length, Message::getThrowable) + .as("checking parameter count and throwable") + .containsExactly(10, THROWABLE); + } +} diff --git a/slf4j-to-log4j-api/src/test/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider b/slf4j-to-log4j-api/src/test/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider new file mode 100644 index 0000000..5552b70 --- /dev/null +++ b/slf4j-to-log4j-api/src/test/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider @@ -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 file, but we need it sooner in our tests +org.apache.logging.slf4j.SLF4JServiceProvider \ No newline at end of file diff --git a/slf4j-to-log4j-api/src/test/resources/log4j-test1.xml b/slf4j-to-log4j-api/src/test/resources/log4j-test1.xml new file mode 100644 index 0000000..30126e6 --- /dev/null +++ b/slf4j-to-log4j-api/src/test/resources/log4j-test1.xml @@ -0,0 +1,51 @@ + + + + + target/test.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + diff --git a/slf4j-to-log4j-api/src/test/resources/log4j2-1482.xml b/slf4j-to-log4j-api/src/test/resources/log4j2-1482.xml new file mode 100644 index 0000000..7c5e728 --- /dev/null +++ b/slf4j-to-log4j-api/src/test/resources/log4j2-1482.xml @@ -0,0 +1,42 @@ + + + + + target/log4j2-1482 + audit + param1,param2,param3${sys:line.separator} + + + + + + + + + + + + + + + + + + + diff --git a/slf4j-to-log4j-api/src/test/resources/log4j2-config.xml b/slf4j-to-log4j-api/src/test/resources/log4j2-config.xml new file mode 100644 index 0000000..d247bd1 --- /dev/null +++ b/slf4j-to-log4j-api/src/test/resources/log4j2-config.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + +