This is an automated email from the ASF dual-hosted git repository. rmannibucau pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/openwebbeans.git
The following commit(s) were added to refs/heads/master by this push: new 9dc611f OWB-1317 adding slf4j integration 9dc611f is described below commit 9dc611f3a433f857878dbb8e1a3e1373f28563d6 Author: Romain Manni-Bucau <rmannibu...@gmail.com> AuthorDate: Tue Mar 17 18:14:09 2020 +0100 OWB-1317 adding slf4j integration --- pom.xml | 1 + .../webbeans/logger/WebBeansLoggerFacade.java | 10 +- webbeans-slf4j/pom.xml | 61 +++ .../org/apache/openwebbeans/slf4j/Slf4jLogger.java | 601 +++++++++++++++++++++ .../openwebbeans/slf4j/Slf4jLoggerFactory.java | 40 ++ ...rg.apache.webbeans.logger.WebBeansLoggerFactory | 17 + .../openwebbeans/slf4j/Slf4jLoggerFactoryTest.java | 71 +++ 7 files changed, 800 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fae44c8..8b423c4 100644 --- a/pom.xml +++ b/pom.xml @@ -648,6 +648,7 @@ <module>webbeans-gradle</module> <module>webbeans-se</module> <module>webbeans-junit5</module> + <module>webbeans-slf4j</module> </modules> <dependencyManagement> diff --git a/webbeans-impl/src/main/java/org/apache/webbeans/logger/WebBeansLoggerFacade.java b/webbeans-impl/src/main/java/org/apache/webbeans/logger/WebBeansLoggerFacade.java index 09dbe33..fc47060 100644 --- a/webbeans-impl/src/main/java/org/apache/webbeans/logger/WebBeansLoggerFacade.java +++ b/webbeans-impl/src/main/java/org/apache/webbeans/logger/WebBeansLoggerFacade.java @@ -25,12 +25,18 @@ package org.apache.webbeans.logger; import org.apache.webbeans.config.OWBLogConst; import org.apache.webbeans.util.WebBeansConstants; +import javax.annotation.Priority; import java.text.MessageFormat; import java.util.Locale; import java.util.MissingResourceException; import java.util.ResourceBundle; +import java.util.ServiceLoader; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.StreamSupport; + +import static java.util.Comparator.comparing; +import static java.util.Optional.ofNullable; /** * Wrapper class around the JUL logger class to include some checks before the @@ -79,7 +85,9 @@ public final class WebBeansLoggerFacade } else { - FACTORY = new JULLoggerFactory(); + FACTORY = StreamSupport.stream(ServiceLoader.load(WebBeansLoggerFactory.class).spliterator(), false) + .max(comparing(it -> ofNullable(it.getClass().getAnnotation(Priority.class)).map(Priority::value).orElse(0))) + .orElseGet(JULLoggerFactory::new); } Logger logger = FACTORY.getLogger(WebBeansLoggerFacade.class); diff --git a/webbeans-slf4j/pom.xml b/webbeans-slf4j/pom.xml new file mode 100644 index 0000000..b9cc0f5 --- /dev/null +++ b/webbeans-slf4j/pom.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>openwebbeans</artifactId> + <groupId>org.apache.openwebbeans</groupId> + <version>2.0.16-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>openwebbeans-slf4j</artifactId> + <name>Slf4j Integration</name> + + <properties> + <slf4j.version>1.7.30</slf4j.version> + </properties> + + <dependencies> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>openwebbeans-impl</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>${slf4j.version}</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <version>${slf4j.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + </dependencies> +</project> diff --git a/webbeans-slf4j/src/main/java/org/apache/openwebbeans/slf4j/Slf4jLogger.java b/webbeans-slf4j/src/main/java/org/apache/openwebbeans/slf4j/Slf4jLogger.java new file mode 100644 index 0000000..0ef80b3 --- /dev/null +++ b/webbeans-slf4j/src/main/java/org/apache/openwebbeans/slf4j/Slf4jLogger.java @@ -0,0 +1,601 @@ +/* + * 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.openwebbeans.slf4j; + +import org.slf4j.spi.LocationAwareLogger; + +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.logging.Filter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +// mainly from cxf +class Slf4jLogger extends Logger +{ + private final org.slf4j.Logger logger; + private LocationAwareLogger locationAwareLogger; + + Slf4jLogger(final String name, final String resourceBundleName) + { + super(name, resourceBundleName); + logger = org.slf4j.LoggerFactory.getLogger(name); + if (LocationAwareLogger.class.isInstance(logger)) + { + locationAwareLogger = LocationAwareLogger.class.cast(logger); + } + } + + @Override + public void log(final LogRecord record) + { + if (isLoggable(record.getLevel())) + { + doLog(record); + } + } + + @Override + public void log(final Level level, final String msg) + { + if (isLoggable(level)) + { + doLog(new LogRecord(level, msg)); + } + } + + @Override + public void log(final Level level, final String msg, final Object param1) + { + if (isLoggable(level)) + { + final LogRecord lr = new LogRecord(level, msg); + lr.setParameters(new Object[]{param1}); + doLog(lr); + } + } + + @Override + public void log(final Level level, final String msg, final Object[] params) + { + if (isLoggable(level)) + { + final LogRecord lr = new LogRecord(level, msg); + lr.setParameters(params); + doLog(lr); + } + } + + @Override + public void log(final Level level, final String msg, final Throwable thrown) + { + if (isLoggable(level)) + { + final LogRecord lr = new LogRecord(level, msg); + lr.setThrown(thrown); + doLog(lr); + } + } + + @Override + public void logp(final Level level, final String sourceClass, final String sourceMethod, final String msg) + { + if (isLoggable(level)) + { + final LogRecord lr = new LogRecord(level, msg); + lr.setSourceClassName(sourceClass); + lr.setSourceMethodName(sourceMethod); + doLog(lr); + } + } + + @Override + public void logp(final Level level, final String sourceClass, final String sourceMethod, final String msg, final Object param1) + { + if (isLoggable(level)) + { + final LogRecord lr = new LogRecord(level, msg); + lr.setSourceClassName(sourceClass); + lr.setSourceMethodName(sourceMethod); + lr.setParameters(new Object[]{param1}); + doLog(lr); + } + } + + @Override + public void logp(final Level level, final String sourceClass, final String sourceMethod, final String msg, Object[] params) + { + if (isLoggable(level)) + { + final LogRecord lr = new LogRecord(level, msg); + lr.setSourceClassName(sourceClass); + lr.setSourceMethodName(sourceMethod); + lr.setParameters(params); + doLog(lr); + } + } + + @Override + public void logp(final Level level, final String sourceClass, final String sourceMethod, final String msg, final Throwable thrown) + { + if (isLoggable(level)) + { + final LogRecord lr = new LogRecord(level, msg); + lr.setSourceClassName(sourceClass); + lr.setSourceMethodName(sourceMethod); + lr.setThrown(thrown); + doLog(lr); + } + } + + @Override + public void logrb(final Level level, final String sourceClass, final String sourceMethod, final String bundleName, final String msg) + { + if (isLoggable(level)) + { + final LogRecord lr = new LogRecord(level, msg); + lr.setSourceClassName(sourceClass); + lr.setSourceMethodName(sourceMethod); + doLog(lr, bundleName); + } + } + + @Override + public void logrb(final Level level, final String sourceClass, final String sourceMethod, + final String bundleName, final String msg, final Object param1) + { + if (isLoggable(level)) + { + final LogRecord lr = new LogRecord(level, msg); + lr.setSourceClassName(sourceClass); + lr.setSourceMethodName(sourceMethod); + lr.setParameters(new Object[]{param1}); + doLog(lr, bundleName); + } + } + + @Override + public void logrb(final Level level, final String sourceClass, final String sourceMethod, + final String bundleName, final String msg, Object[] params) + { + if (isLoggable(level)) + { + final LogRecord lr = new LogRecord(level, msg); + lr.setSourceClassName(sourceClass); + lr.setSourceMethodName(sourceMethod); + lr.setParameters(params); + doLog(lr, bundleName); + } + } + + @Override + public void logrb(final Level level, final String sourceClass, final String sourceMethod, + final String bundleName, final String msg, final Throwable thrown) + { + if (isLoggable(level)) + { + final LogRecord lr = new LogRecord(level, msg); + lr.setSourceClassName(sourceClass); + lr.setSourceMethodName(sourceMethod); + lr.setThrown(thrown); + doLog(lr, bundleName); + } + } + + @Override + public void entering(final String sourceClass, final String sourceMethod) + { + if (isLoggable(Level.FINER)) + { + logp(Level.FINER, sourceClass, sourceMethod, "ENTRY"); + } + } + + @Override + public void entering(final String sourceClass, final String sourceMethod, final Object param1) + { + if (isLoggable(Level.FINER)) + { + logp(Level.FINER, sourceClass, sourceMethod, "ENTRY {0}", param1); + } + } + + @Override + public void entering(final String sourceClass, final String sourceMethod, final Object[] params) + { + if (isLoggable(Level.FINER)) + { + final String msg = "ENTRY"; + if (params == null) + { + logp(Level.FINER, sourceClass, sourceMethod, msg); + return; + } + final StringBuilder builder = new StringBuilder(msg); + for (int i = 0; i < params.length; i++) + { + builder.append(" {"); + builder.append(i); + builder.append('}'); + } + logp(Level.FINER, sourceClass, sourceMethod, builder.toString(), params); + } + } + + @Override + public void exiting(final String sourceClass, final String sourceMethod) + { + if (isLoggable(Level.FINER)) + { + logp(Level.FINER, sourceClass, sourceMethod, "RETURN"); + } + } + + @Override + public void exiting(final String sourceClass, final String sourceMethod, final Object result) + { + if (isLoggable(Level.FINER)) + { + logp(Level.FINER, sourceClass, sourceMethod, "RETURN {0}", result); + } + } + + @Override + public void throwing(final String sourceClass, final String sourceMethod, final Throwable thrown) + { + if (isLoggable(Level.FINER)) + { + final LogRecord lr = new LogRecord(Level.FINER, "THROW"); + lr.setSourceClassName(sourceClass); + lr.setSourceMethodName(sourceMethod); + lr.setThrown(thrown); + doLog(lr); + } + } + + @Override + public void severe(final String msg) + { + if (isLoggable(Level.SEVERE)) + { + doLog(new LogRecord(Level.SEVERE, msg)); + } + } + + @Override + public void warning(final String msg) + { + if (isLoggable(Level.WARNING)) + { + doLog(new LogRecord(Level.WARNING, msg)); + } + } + + @Override + public void info(final String msg) + { + if (isLoggable(Level.INFO)) + { + doLog(new LogRecord(Level.INFO, msg)); + } + } + + @Override + public void config(final String msg) + { + if (isLoggable(Level.CONFIG)) + { + doLog(new LogRecord(Level.CONFIG, msg)); + } + } + + @Override + public void fine(final String msg) + { + if (isLoggable(Level.FINE)) + { + doLog(new LogRecord(Level.FINE, msg)); + } + } + + @Override + public void finer(final String msg) + { + if (isLoggable(Level.FINER)) + { + doLog(new LogRecord(Level.FINER, msg)); + } + } + + @Override + public void finest(final String msg) + { + if (isLoggable(Level.FINEST)) + { + doLog(new LogRecord(Level.FINEST, msg)); + } + } + + @Override + public void setLevel(final Level newLevel) + { + // no-op + } + + private void doLog(final LogRecord lr) + { + lr.setLoggerName(getName()); + final String rbname = getResourceBundleName(); + if (rbname != null) + { + lr.setResourceBundleName(rbname); + lr.setResourceBundle(getResourceBundle()); + } + internalLog(lr); + } + + private void doLog(final LogRecord lr, final String rbname) + { + lr.setLoggerName(getName()); + if (rbname != null) + { + lr.setResourceBundleName(rbname); + lr.setResourceBundle(loadResourceBundle(rbname)); + } + internalLog(lr); + } + + private void internalLog(final LogRecord record) + { + final Filter filter = getFilter(); + if (filter != null && !filter.isLoggable(record)) + { + return; + } + final String msg = formatMessage(record); + internalLogFormatted(msg, record); + } + + private String formatMessage(final LogRecord record) + { + final ResourceBundle catalog = record.getResourceBundle(); + String format = record.getMessage(); + if (catalog != null) + { + try + { + format = catalog.getString(record.getMessage()); + } + catch (MissingResourceException ex) + { + format = record.getMessage(); + } + } + try + { + final Object[] parameters = record.getParameters(); + if (parameters == null || parameters.length == 0) + { + return format; + } + if (format.contains("{0") || format.contains("{1") + || format.contains("{2") || format.contains("{3")) + { + return java.text.MessageFormat.format(format, parameters); + } + return format; + } + catch (final Exception ex) + { + return format; + } + } + + private static ResourceBundle loadResourceBundle(final String resourceBundleName) + { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + if (null != cl) + { + try + { + return ResourceBundle.getBundle(resourceBundleName, Locale.getDefault(), cl); + } + catch (final MissingResourceException e) + { + // no-op + } + } + cl = ClassLoader.getSystemClassLoader(); + if (null != cl) + { + try + { + return ResourceBundle.getBundle(resourceBundleName, Locale.getDefault(), cl); + } + catch (final MissingResourceException e) + { + // no-op + } + } + return null; + } + + @Override + public Level getLevel() + { + if (logger.isTraceEnabled()) + { + return Level.FINEST; + } + else if (logger.isDebugEnabled()) + { + return Level.FINER; + } + else if (logger.isInfoEnabled()) + { + return Level.INFO; + } + else if (logger.isWarnEnabled()) + { + return Level.WARNING; + } + else if (logger.isErrorEnabled()) + { + return Level.SEVERE; + } + return Level.OFF; + } + + @Override + public boolean isLoggable(final Level level) + { + final int i = level.intValue(); + if (i == Level.OFF.intValue()) + { + return false; + } + else if (i >= Level.SEVERE.intValue()) + { + return logger.isErrorEnabled(); + } + else if (i >= Level.WARNING.intValue()) + { + return logger.isWarnEnabled(); + } + else if (i >= Level.INFO.intValue()) + { + return logger.isInfoEnabled(); + } + else if (i >= Level.FINER.intValue()) + { + return logger.isDebugEnabled(); + } + return logger.isTraceEnabled(); + } + + private void internalLogFormatted(final String msg, final LogRecord record) + { + final Level level = record.getLevel(); + final Throwable t = record.getThrown(); + final Handler[] targets = getHandlers(); + if (targets != null) + { + for (Handler h : targets) + { + h.publish(record); + } + } + if (!getUseParentHandlers()) + { + return; + } + + if (Level.FINE.equals(level)) + { + if (locationAwareLogger == null) + { + logger.debug(msg, t); + } + else + { + locationAwareLogger.log(null, Logger.class.getName(), LocationAwareLogger.DEBUG_INT, msg, null, t); + } + } + else if (Level.INFO.equals(level)) + { + if (locationAwareLogger == null) + { + logger.info(msg, t); + } + else + { + locationAwareLogger.log(null, Logger.class.getName(), LocationAwareLogger.INFO_INT, msg, null, t); + } + } + else if (Level.WARNING.equals(level)) + { + if (locationAwareLogger == null) + { + logger.warn(msg, t); + } + else + { + locationAwareLogger.log(null, Logger.class.getName(), LocationAwareLogger.WARN_INT, msg, null, t); + } + } + else if (Level.FINER.equals(level)) + { + if (locationAwareLogger == null) + { + logger.trace(msg, t); + } + else + { + locationAwareLogger.log(null, Logger.class.getName(), LocationAwareLogger.DEBUG_INT, msg, null, t); + } + } + else if (Level.FINEST.equals(level)) + { + if (locationAwareLogger == null) + { + logger.trace(msg, t); + } + else + { + locationAwareLogger.log(null, Logger.class.getName(), LocationAwareLogger.TRACE_INT, msg, null, t); + } + } + else if (Level.ALL.equals(level)) + { + if (locationAwareLogger == null) + { + logger.error(msg, t); + } + else + { + locationAwareLogger.log(null, Logger.class.getName(), LocationAwareLogger.ERROR_INT, msg, null, t); + } + } + else if (Level.SEVERE.equals(level)) + { + if (locationAwareLogger == null) + { + logger.error(msg, t); + } + else + { + locationAwareLogger.log(null, Logger.class.getName(), LocationAwareLogger.ERROR_INT, msg, null, t); + } + } + else if (Level.CONFIG.equals(level)) + { + if (locationAwareLogger == null) + { + logger.debug(msg, t); + } + else + { + locationAwareLogger.log(null, Logger.class.getName(), LocationAwareLogger.DEBUG_INT, msg, null, t); + } + } + } +} diff --git a/webbeans-slf4j/src/main/java/org/apache/openwebbeans/slf4j/Slf4jLoggerFactory.java b/webbeans-slf4j/src/main/java/org/apache/openwebbeans/slf4j/Slf4jLoggerFactory.java new file mode 100644 index 0000000..271d3c8 --- /dev/null +++ b/webbeans-slf4j/src/main/java/org/apache/openwebbeans/slf4j/Slf4jLoggerFactory.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.openwebbeans.slf4j; + +import org.apache.webbeans.logger.WebBeansLoggerFactory; + +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.logging.Logger; + +public class Slf4jLoggerFactory implements WebBeansLoggerFactory +{ + @Override + public Logger getLogger(final Class<?> clazz, final Locale desiredLocale) + { + return new Slf4jLogger(clazz.getName(), ResourceBundle.getBundle("openwebbeans/Messages", desiredLocale).toString()); + } + + @Override + public Logger getLogger(final Class<?> clazz) + { + return new Slf4jLogger(clazz.getName(), "openwebbeans/Messages"); + } +} diff --git a/webbeans-slf4j/src/main/resources/META-INF/services/org.apache.webbeans.logger.WebBeansLoggerFactory b/webbeans-slf4j/src/main/resources/META-INF/services/org.apache.webbeans.logger.WebBeansLoggerFactory new file mode 100644 index 0000000..6a57c79 --- /dev/null +++ b/webbeans-slf4j/src/main/resources/META-INF/services/org.apache.webbeans.logger.WebBeansLoggerFactory @@ -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 +# +# 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. +org.apache.openwebbeans.slf4j.Slf4jLoggerFactory diff --git a/webbeans-slf4j/src/test/java/org/apache/openwebbeans/slf4j/Slf4jLoggerFactoryTest.java b/webbeans-slf4j/src/test/java/org/apache/openwebbeans/slf4j/Slf4jLoggerFactoryTest.java new file mode 100644 index 0000000..64e267c --- /dev/null +++ b/webbeans-slf4j/src/test/java/org/apache/openwebbeans/slf4j/Slf4jLoggerFactoryTest.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.openwebbeans.slf4j; + +import org.apache.webbeans.logger.WebBeansLoggerFacade; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.logging.Logger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class Slf4jLoggerFactoryTest { + @Test + public void ensureLogGoesOnSlf4j() { + final Logger logger = WebBeansLoggerFacade.getLogger(Slf4jLoggerFactoryTest.class); + assertTrue(logger.getClass().getName(), Slf4jLogger.class.isInstance(logger)); + + final PrintStream original = System.err; + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + System.setErr(new PrintStream(new OutputStream() { + + @Override + public void write(final int b) { + buffer.write(b); + original.write(b); + } + + @Override + public void write(final byte[] b) throws IOException { + buffer.write(b); + original.write(b); + } + + @Override + public void write(final byte[] b, final int off, final int len) { + buffer.write(b, off, len); + original.write(b, off, len); + } + })); + try { + logger.info("test log"); + } finally { + System.setErr(original); + } + assertEquals( + "[main] INFO " + getClass().getName() + " - test log\n", + new String(buffer.toByteArray(), StandardCharsets.UTF_8)); + } +}