Restructure the Spring Boot for Apache Geode project to mirror Spring Boot's project structure.

Resolves gh-60.
This commit is contained in:
John Blum
2022-02-24 16:23:35 -08:00
parent 0e8e3baeeb
commit 192133b95e
576 changed files with 0 additions and 19 deletions

View File

@@ -0,0 +1,155 @@
/*
* Copyright 2017-present the original author or authors.
*
* Licensed 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.
*/
package org.springframework.geode.logging.slf4j.logback;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.Context;
/**
* {@link CompositeAppender} is an {@link Appender} implementation implementing
* the <a href="https://en.wikipedia.org/wiki/Composite_pattern">Composite Software Design Pattern</a>
*
* The {@literal Composite Software Design Pattern} enables two or more {@link Appender} objects to be composed
* and treated as a single instance of {@link Appender}.
*
* @author John Blum
* @see ch.qos.logback.core.Appender
* @see ch.qos.logback.core.AppenderBase
* @since 1.3.0
*/
public class CompositeAppender<T> extends AppenderBase<T> {
protected static final String DEFAULT_NAME = "composite";
private final Appender<T> one;
private final Appender<T> two;
/**
* Factory method used to compose two {@link Appender} objects into a {@literal Composite} {@link Appender}.
*
* @param <T> {@link Class type} of {@link Appender} to compose.
* @param one first {@link Appender} to compose.
* @param two second {@link Appender} to compose.
* @return {@link Appender} one if {@link Appender} two is {@literal null};
* Return {@link Appender} two if {@link Appender} one is {@literal null}.
* Otherwise, return a {@literal Composite} {@link Appender} composed of {@link Appender} one
* and {@link Appender} two.
* @see ch.qos.logback.core.Appender
*/
public static <T> Appender<T> compose(Appender<T> one, Appender<T> two) {
return one == null ? two : two == null ? one : new CompositeAppender<>(one, two);
}
/**
* Composes an array of {@link Appender Appenders} into a {@link CompositeAppender}.
*
* This operation is null-safe.
*
* @param <T> {@link Class type} of the logging events processed by the {@link Appender Appenders}.
* @param appenders array of {@link Appender Appenders} to compose; may be {@literal null}.
* @return a composition of the array of {@link Appender Appenders}; returns {@literal null} if the array is empty.
* @see #compose(Iterable)
*/
@SuppressWarnings("unchecked")
public static <T> Appender<T> compose(Appender<T>... appenders) {
List<Appender<T>> resolvedAppenders = appenders != null
? Arrays.asList(appenders)
: Collections.emptyList();
return compose(resolvedAppenders);
}
/**
* Composes the {@link Iterable} of {@link Appender Appenders} into a {@link CompositeAppender}.
*
* This operation is null-safe.
*
* @param <T> {@link Class type} of the logging events processed by the {@link Appender Appenders}.
* @param appenders {@link Iterable} of {@link Appender Appenders} to compose; may be {@literal null}.
* @return a composition of the {@link Iterable} of {@link Appender Appenders}; returns {@literal null}
* if the {@link Iterable} is {@literal null} or empty.
* @see #compose(Appender, Appender)
* @see java.lang.Iterable
*/
public static <T> Appender<T> compose(Iterable<Appender<T>> appenders) {
Appender<T> currentAppender = null;
appenders = appenders != null ? appenders : Collections::emptyIterator;
for (Appender<T> appender : appenders) {
currentAppender = compose(currentAppender, appender);
}
return currentAppender;
}
/**
* Constructs a new instance of {@link CompositeAppender} composed of {@link Appender} one and {@link Appender} two.
*
* @param one first {@link Appender} in the composite.
* @param two second {@link Appender} in the composite.
* @see ch.qos.logback.core.Appender
*/
private CompositeAppender(Appender<T> one, Appender<T> two) {
this.one = one;
this.two = two;
this.name = DEFAULT_NAME;
this.started = true;
}
protected Appender<T> getAppenderOne() {
return this.one;
}
protected Appender<T> getAppenderTwo() {
return this.two;
}
@Override
public void setContext(Context context) {
super.setContext(context);
getAppenderOne().setContext(context);
getAppenderTwo().setContext(context);
}
@Override
public Context getContext() {
Context context = super.getContext();
context = context != null ? context : getAppenderOne().getContext();
context = context != null ? context : getAppenderTwo().getContext();
return context;
}
@Override
protected void append(T eventObject) {
getAppenderOne().doAppend(eventObject);
getAppenderTwo().doAppend(eventObject);
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2017-present the original author or authors.
*
* Licensed 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.
*/
package org.springframework.geode.logging.slf4j.logback;
import java.util.Objects;
import java.util.Optional;
import org.slf4j.LoggerFactory;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.helpers.NOPAppender;
/**
* {@link DelegatingAppender} is an SLF4J {@link Appender} that delegates to the configured {@link Appender}.
*
* If no {@link Appender} was configured, then the {@link DelegatingAppender} delegates to the {@link NOPAppender}.
*
* @author John Blum
* @see ch.qos.logback.core.Appender
* @see ch.qos.logback.core.AppenderBase
* @see ch.qos.logback.core.helpers.NOPAppender
* @since 1.3.0
*/
public class DelegatingAppender<T> extends AppenderBase<T> {
@SuppressWarnings("rawtypes")
protected static final Appender DEFAULT_APPENDER = new NOPAppender<>();
protected static final String DEFAULT_NAME = "delegate";
public DelegatingAppender() {
Optional.ofNullable(LoggerFactory.getILoggerFactory())
.filter(it -> Objects.isNull(DEFAULT_APPENDER.getContext()))
.filter(Context.class::isInstance)
.map(Context.class::cast)
.ifPresent(DEFAULT_APPENDER::setContext);
this.name = DEFAULT_NAME;
}
private volatile Appender<T> appender;
public void setAppender(Appender<T> appender) {
this.appender = appender;
}
@SuppressWarnings("unchecked")
protected Appender<T> getAppender() {
Appender<T> configuredAppender = this.appender;
return configuredAppender != null
? configuredAppender
: DEFAULT_APPENDER;
}
@Override
protected void append(T eventObject) {
getAppender().doAppend(eventObject);
}
}

View File

@@ -0,0 +1,259 @@
/*
* Copyright 2017-present the original author or authors.
*
* Licensed 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.
*/
package org.springframework.geode.logging.slf4j.logback;
import java.util.Optional;
import org.springframework.geode.logging.slf4j.logback.support.LogbackSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.Context;
/**
* {@link StringAppender} is a {@link Appender} implementation that captures all log events/statements in-memory
* appended to a {@link String} using optionally either a builder or a buffer.
*
* @author John Blum
* @see java.lang.StringBuilder
* @see java.lang.StringBuffer
* @see ch.qos.logback.core.Appender
* @see ch.qos.logback.core.AppenderBase
* @see ch.qos.logback.core.Context
* @since 1.3.0
*/
@SuppressWarnings("unused")
public class StringAppender extends AppenderBase<ILoggingEvent> {
protected static final String DEFAULT_NAME = "string";
protected static final String NEWLINE = "\n";
@FunctionalInterface
interface StringAppenderWrapper {
void append(CharSequence charSequence);
default void clear() { }
}
protected static class StringBufferAppenderWrapper implements StringAppenderWrapper {
protected static StringBufferAppenderWrapper create() {
return new StringBufferAppenderWrapper();
}
private final StringBuffer stringBuffer = new StringBuffer();
@Override
public void append(CharSequence charSequence) {
this.stringBuffer.append(charSequence);
this.stringBuffer.append(NEWLINE);
}
@Override
public void clear() {
this.stringBuffer.delete(0, this.stringBuffer.length());
}
@Override
public java.lang.String toString() {
return this.stringBuffer.toString();
}
}
protected static class StringBuilderAppenderWrapper implements StringAppenderWrapper {
protected static StringBuilderAppenderWrapper create() {
return new StringBuilderAppenderWrapper();
}
private final StringBuilder stringBuilder = new StringBuilder();
@Override
public void append(CharSequence charSequence) {
this.stringBuilder.append(charSequence);
this.stringBuilder.append(NEWLINE);
}
@Override
public void clear() {
this.stringBuilder.delete(0, this.stringBuilder.length());
}
@Override
public java.lang.String toString() {
return this.stringBuilder.toString();
}
}
@SuppressWarnings({ "rawtypes", "unchecked", "unused" })
public static class Builder {
private static final boolean DEFAULT_REPLACE = false;
private boolean replace = false;
private boolean useSynchronization = false;
private Context context;
private DelegatingAppender delegate;
private ch.qos.logback.classic.Logger logger;
private String name;
public Builder applyTo(DelegatingAppender<?> delegate) {
return applyTo(delegate, DEFAULT_REPLACE);
}
public Builder applyTo(DelegatingAppender<?> delegate, boolean replace) {
this.delegate = delegate;
this.replace = replace;
return this;
}
public Builder applyTo(Logger logger) {
return LogbackSupport.toLogbackLogger(logger)
.map(this::applyTo)
.orElse(this);
}
public Builder applyTo(ch.qos.logback.classic.Logger logger) {
this.logger = logger;
return this;
}
public Builder setContext(Context context) {
this.context = context;
return this;
}
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder useSynchronization() {
this.useSynchronization = true;
return this;
}
private Optional<DelegatingAppender> getDelegate() {
return Optional.ofNullable(this.delegate);
}
private Optional<ch.qos.logback.classic.Logger> getLogger() {
return Optional.ofNullable(this.logger);
}
private Context resolveContext() {
return this.context != null
? this.context
: Optional.ofNullable(LoggerFactory.getILoggerFactory())
.filter(Context.class::isInstance)
.map(Context.class::cast)
.orElse(null);
}
private String resolveName() {
return this.name != null && !this.name.trim().isEmpty() ? this.name : DEFAULT_NAME;
}
private StringAppenderWrapper resolveStringAppenderWrapper() {
return this.useSynchronization
? StringBufferAppenderWrapper.create()
: StringBuilderAppenderWrapper.create();
}
public StringAppender build() {
StringAppender stringAppender =
new StringAppender(resolveStringAppenderWrapper());
stringAppender.setContext(resolveContext());
stringAppender.setName(resolveName());
getDelegate().ifPresent(delegate -> {
Appender appender = this.replace ? stringAppender
: CompositeAppender.compose(delegate.getAppender(), stringAppender);
delegate.setAppender(appender);
});
getLogger().ifPresent(logger -> logger.addAppender(stringAppender));
return stringAppender;
}
public StringAppender buildAndStart() {
StringAppender stringAppender = build();
stringAppender.start();
return stringAppender;
}
}
private final StringAppenderWrapper stringAppenderWrapper;
protected StringAppender(StringAppenderWrapper stringAppenderWrapper) {
if (stringAppenderWrapper == null) {
throw new IllegalArgumentException("StringAppenderWrapper must not be null");
}
this.stringAppenderWrapper = stringAppenderWrapper;
}
public String getLogOutput() {
return getStringAppenderWrapper().toString();
}
protected StringAppenderWrapper getStringAppenderWrapper() {
return this.stringAppenderWrapper;
}
@Override
protected void append(ILoggingEvent loggingEvent) {
Optional.ofNullable(loggingEvent)
.map(event -> preProcessLogMessage(toString(event)))
.filter(this::isValidLogMessage)
.ifPresent(getStringAppenderWrapper()::append);
}
protected boolean isValidLogMessage(String message) {
return message != null && !message.isEmpty();
}
protected String preProcessLogMessage(String message) {
return message != null ? message.trim() : null;
}
protected String toString(ILoggingEvent loggingEvent) {
return loggingEvent != null ? loggingEvent.getFormattedMessage() : null;
}
}

View File

@@ -0,0 +1,326 @@
/*
* Copyright 2017-present the original author or authors.
*
* Licensed 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.
*/
package org.springframework.geode.logging.slf4j.logback.support;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.impl.StaticLoggerBinder;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
/**
* Abstract utility class containing functionality for invoking SLF4J and Logback APIs.
*
* @author John Blum
* @see org.slf4j.ILoggerFactory
* @see org.slf4j.Logger
* @see org.slf4j.LoggerFactory
* @see ch.qos.logback.classic.LoggerContext
* @see ch.qos.logback.core.Appender
* @since 1.3.0
*/
@SuppressWarnings("unused")
public abstract class LogbackSupport {
protected static final Function<Logger, Optional<ch.qos.logback.classic.Logger>> slf4jLoggerToLogbackLoggerConverter =
logger -> Optional.ofNullable(logger)
.filter(ch.qos.logback.classic.Logger.class::isInstance)
.map(ch.qos.logback.classic.Logger.class::cast);
protected static final String CONSOLE_APPENDER_NAME = "console";
protected static final String DELEGATE_APPENDER_NAME = "delegate";
protected static final String ILLEGAL_LOGGER_TYPE_EXCEPTION_MESSAGE =
"[%1$s] Logger type [%2$s] is not a Logback Logger";
protected static final String ROOT_LOGGER_NAME = Logger.ROOT_LOGGER_NAME;
protected static final String SPRING_BOOT_LOGGING_SYSTEM_CLASS_NAME =
"org.springframework.boot.logging.LoggingSystem";
protected static final String UNRESOLVABLE_APPENDER_EXCEPTION_MESSAGE =
"Could not resolve Appender with name [%1$s] as type [%2$s] from Logger [%3$s]";
/**
* Disables Spring Boot's logging initialization, auto-configuration.
*/
public static void suppressSpringBootLogbackInitialization() {
requireLoggerContext().putObject(SPRING_BOOT_LOGGING_SYSTEM_CLASS_NAME, new Object());
}
/**
* Resets the state of the SLF4J Logback logging provider and system.
*/
public static void resetLogback() {
try {
resetLoggerContext();
resetLoggerFactory();
resetStaticLoggerBinder();
}
catch (Throwable cause) {
throw new IllegalStateException("Failed to reset Logback", cause);
}
}
private static void resetLoggerContext() {
resolveLoggerContext().ifPresent(loggerContext -> {
loggerContext.reset();
loggerContext.getStatusManager().clear();
});
}
private static void resetLoggerFactory() throws Exception {
Method loggerFactoryReset = LoggerFactory.class.getDeclaredMethod("reset");
loggerFactoryReset.setAccessible(true);
loggerFactoryReset.invoke(null);
}
private static void resetStaticLoggerBinder() throws Exception {
Method staticLoggerBinderReset = StaticLoggerBinder.class.getDeclaredMethod("reset");
staticLoggerBinderReset.setAccessible(true);
staticLoggerBinderReset.invoke(null);
}
/**
* Resolves the SLF4J, Logback {@link LoggerContext}.
*
* If the {@link LoggerContext} could not be resolve then the returned {@link Optional}
* will be {@link Optional#empty() empty}.
*
* @return an {@link Optional} {@link LoggerContext}.
* @see ch.qos.logback.classic.LoggerContext
*/
public static Optional<LoggerContext> resolveLoggerContext() {
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
LoggerContext resolvedLoggerContext = loggerFactory instanceof LoggerContext
? (LoggerContext) loggerFactory
: null;
return Optional.ofNullable(resolvedLoggerContext);
}
/**
* Requires a {@link LoggerContext} otherwise throws an {@link IllegalStateException}.
*
* @return the required {@link LoggerContext}.
* @throws IllegalStateException if the {@link LoggerContext} could not be resolved.
* @see #resolveLoggerContext()
*/
public static LoggerContext requireLoggerContext() {
return resolveLoggerContext()
.orElseThrow(() -> new IllegalStateException("LoggerContext is required"));
}
/**
* Resolves the {@link Logger#ROOT_LOGGER_NAME Root} {@link Logger}.
*
* @return an {@link Optional} {@link Logger} for the logging provider's {@literal ROOT} {@link Logger}.
* @see org.slf4j.Logger
*/
public static Optional<Logger> resolveRootLogger() {
return Optional.ofNullable(LoggerFactory.getLogger(ROOT_LOGGER_NAME));
}
/**
* Requires the SLF4J Logback {@literal ROOT} {@link Logger} otherwise throws an {@link IllegalStateException}.
*
* @return the configured SLF4J Logback {@literal ROOT} {@link Logger}.
* @throws IllegalStateException if the SLF4J Logback {@literal ROOT} {@link Logger} could not be resolved
* or the {@literal ROOT} {@link Logger} is not a SLF4J Logback {@literal ROOT} {@link Logger}.
* @see ch.qos.logback.classic.Logger
* @see #resolveRootLogger()
*/
public static ch.qos.logback.classic.Logger requireLogbackRootLogger() {
return resolveRootLogger()
.filter(ch.qos.logback.classic.Logger.class::isInstance)
.map(ch.qos.logback.classic.Logger.class::cast)
.orElseThrow(() -> new IllegalStateException(String.format(ILLEGAL_LOGGER_TYPE_EXCEPTION_MESSAGE,
ROOT_LOGGER_NAME, nullSafeTypeName(resolveRootLogger().orElse(null)))));
}
/**
* Finds an {@link Appender} with the given {@link String name} from the given {@link Logger}.
*
* @param <T> {@link Class type} of the {@link Appender}.
* @param <E> {@link Class type} of the logging event.
* @param logger SLF4J {@link Logger} from which to resolve the {@link Appender}.
* @param appenderName a {@link String} containing the name of the {@link Appender} to resolve.
* @param appenderType required {@link Class type} of the {@link Appender} to resolve.
* @return an {@link Optional} {@link Appender} with the given {@link String name} from the {@link Logger}.
* @see ch.qos.logback.core.Appender
* @see java.util.Optional
* @see org.slf4j.Logger
*/
public static <E, T extends Appender<E>> Optional<T> resolveAppender(ch.qos.logback.classic.Logger logger,
String appenderName, Class<T> appenderType) {
appenderType = nullSafeAppenderType(appenderType);
return Optional.ofNullable(logger)
.map(it -> it.getAppender(appenderName))
.filter(appenderType::isInstance)
.map(appenderType::cast);
}
/**
* Requires an {@link Appender} with the given {@link String name} having the specified {@link Class type}
* from the given {@link Logger}.
*
* @param <T> {@link Class type} of the {@link Appender}.
* @param <E> {@link Class type} of the {@link Object Objects} processed by the {@link Appender}.
* @param logger {@link Logger} from which to resolve the {@link Appender}.
* @param appenderName {@link String} containing the name of the {@link Appender}.
* @param appenderType required {@link Class type} of the {@link Appender}.
* @return the resolved {@link Appender}.
* @throws IllegalStateException if an {@link Appender} with {@link String name} and required {@link Class type}
* could not be resolved from the given {@link Logger}.
* @see ch.qos.logback.classic.Logger
* @see ch.qos.logback.core.Appender
*/
public static <E, T extends Appender<E>> T requireAppender(ch.qos.logback.classic.Logger logger,
String appenderName, Class<T> appenderType) {
return resolveAppender(logger, appenderName, appenderType)
.orElseThrow(() -> new IllegalStateException(String.format(UNRESOLVABLE_APPENDER_EXCEPTION_MESSAGE,
appenderName, nullSafeTypeName(appenderType), nullSafeLoggerName(logger))));
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static <E, T extends Appender<E>> Class<T> nullSafeAppenderType(Class<T> appenderType) {
return appenderType != null ? appenderType : (Class) Appender.class;
}
/**
* Adds the given {@link Appender} to the given {@link Logger}.
*
* @param logger {@link Logger} to add the {@link Appender} to.
* @param appender {@link Appender} to add to the {@link Logger}.
* @return a boolean value indicating whether the {@link Appender} was successfully added to the {@link Logger}.
* @see ch.qos.logback.classic.Logger
* @see ch.qos.logback.core.Appender
*/
public static boolean addAppender(ch.qos.logback.classic.Logger logger, Appender<ILoggingEvent> appender) {
return Optional.ofNullable(logger)
.filter(it -> Objects.nonNull(appender))
.map(it -> {
it.addAppender(appender);
return logger.getAppender(appender.getName());
})
.isPresent();
}
/**
* Removes the {@link Appender} with the specified {@link String name} from the given {@link Logger}.
*
* @param logger {@link Logger} from which to remove the {@link Appender}.
* @param appenderName {@link String name} of the {@link Appender} to remove from the {@link Logger}.
* @return a boolean value indicating whether the targeted {@link Appender} was removed from
* the given {@link Logger}.
* @see ch.qos.logback.classic.Logger
* @see ch.qos.logback.core.Appender
*/
@SuppressWarnings("all")
public static boolean removeAppender(ch.qos.logback.classic.Logger logger, String appenderName) {
return Optional.ofNullable(logger)
.map(it -> it.getAppender(appenderName))
.filter(appender -> appender.getName().equals(appenderName))
.map(appender -> { appender.stop(); return appender; })
.map(appender -> logger.detachAppender(appender))
.orElse(false);
}
/**
* Removes the {@literal console} {@link Appender} from the given {@link Logger}.
*
* @param logger {@link Logger} from which to remove the {@literal console} {@link Appender}.
* @return {@literal true} if the {@literal console} {@link Appender} was registered with
* and successfully remove from the given {@link Logger}.
* @see #removeAppender(ch.qos.logback.classic.Logger, String)
*/
public static boolean removeConsoleAppender(ch.qos.logback.classic.Logger logger) {
return removeAppender(logger, CONSOLE_APPENDER_NAME);
}
/**
* Removes the {@literal delegate} {@link Appender} from the given {@link Logger}.
*
* @param logger {@link Logger} from which to remove the {@literal delegate} {@link Appender}.
* @return {@literal true} if the {@literal delegate} {@link Appender} was registered with
* and successfully remove from the given {@link Logger}.
* @see #removeAppender(ch.qos.logback.classic.Logger, String)
*/
public static boolean removeDelegateAppender(ch.qos.logback.classic.Logger logger) {
return removeAppender(logger, DELEGATE_APPENDER_NAME);
}
/**
* Converts an SLF4J {@link Logger} to a Logback {@link ch.qos.logback.classic.Logger}.
*
* @param logger SLF4J {@link Logger} to convert.
* @return an {@link Optional} Logback {@link ch.qos.logback.classic.Logger} for the given SLF4J {@link Logger}
* iff the SLF4J {@link Logger} is {@literal not-null} and is a Logback {@link ch.qos.logback.classic.Logger}.
* @see java.util.Optional
* @see ch.qos.logback.classic.Logger
* @see org.slf4j.Logger
*/
public static Optional<ch.qos.logback.classic.Logger> toLogbackLogger(Logger logger) {
return slf4jLoggerToLogbackLoggerConverter.apply(logger);
}
private static String nullSafeLoggerName(Logger logger) {
return logger != null ? logger.getName() : null;
}
private static Class<?> nullSafeType(Object obj) {
return obj != null ? obj.getClass() : null;
}
private static String nullSafeTypeName(Class<?> type) {
return type != null ? type.getName() : null;
}
private static String nullSafeTypeName(Object obj) {
return nullSafeTypeName(nullSafeType(obj));
}
private static String nullSafeTypeSimpleName(Class<?> type) {
return type != null ? type.getSimpleName() : null;
}
private static String nullSafeTypeSimpleName(Object obj) {
return nullSafeTypeSimpleName(nullSafeType(obj));
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<included>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %5p %40.40c:%4L - %m%n</pattern>
</encoder>
</appender>
<appender name="delegate" class="org.springframework.geode.logging.slf4j.logback.DelegatingAppender"/>
<logger name="com.gemstone.gemfire" level="${spring.boot.data.gemfire.log.level:-INFO}"/>
<logger name="org.apache.geode" level="${spring.boot.data.gemfire.log.level:-INFO}"/>
<logger name="org.jgroups" level="${spring.boot.data.gemfire.jgroups.log.level:-ERROR}"/>
</included>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<statusListener class="ch.qos.logback.core.status.NopStatusListener"/>
<include resource="logback-include.xml"/>
<root level="${logback.root.log.level:-INFO}">
<appender-ref ref="console"/>
<appender-ref ref="delegate"/>
</root>
</configuration>

View File

@@ -0,0 +1,151 @@
/*
* Copyright 2017-present the original author or authors.
*
* Licensed 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.
*/
package org.springframework.geode.logging.slf4j.logback;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
import java.util.Optional;
import org.junit.After;
import org.junit.Before;
import org.springframework.data.gemfire.tests.integration.IntegrationTestsSupport;
import org.springframework.data.gemfire.tests.logging.slf4j.logback.TestAppender;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.util.ContextInitializer;
import ch.qos.logback.core.Appender;
/**
* Abstract base class for testing the spring-geode-starter-logging and spring-gemfire-starter modules.
*
* @author John Blum
* @see org.slf4j.Logger
* @see org.slf4j.LoggerFactory
* @see org.springframework.data.gemfire.tests.integration.IntegrationTestsSupport
* @see org.springframework.data.gemfire.tests.logging.slf4j.logback.TestAppender
* @see ch.qos.logback.classic.Level
* @see ch.qos.logback.classic.LoggerContext
* @see ch.qos.logback.classic.util.ContextInitializer
* @see ch.qos.logback.core.Appender
* @since 1.3.0
*/
public abstract class AbstractLoggingIntegrationTests extends IntegrationTestsSupport {
protected static final String APACHE_GEODE_LOGGER_NAME = "org.apache.geode";
protected static final String SPRING_BOOT_DATA_GEMFIRE_LOG_LEVEL_PROPERTY = "spring.boot.data.gemfire.log.level";
private Logger apacheGeodeLogger = LoggerFactory.getLogger(APACHE_GEODE_LOGGER_NAME);
private TestAppender testAppender;
protected TestAppender getTestAppender() {
assertThat(this.testAppender).describedAs("TestAppender could not be resolved").isNotNull();
return this.testAppender;
}
protected Level getTestLogLevel() {
return Level.INFO;
}
public void assertApacheGeodeLoggerLogLevel(Level logLevel) {
Optional.ofNullable(this.apacheGeodeLogger)
.filter(ch.qos.logback.classic.Logger.class::isInstance)
.map(ch.qos.logback.classic.Logger.class::cast)
.map(logger -> {
assertThat(logger.getLevel()).isEqualTo(logLevel);
return logger;
})
.orElseThrow(() -> newIllegalStateException("'org.apache.geode' Logger not found"));
}
@Before
public void setup() {
configureLogging();
configureRootLoggerDelegatingAppender();
logMessages();
}
private void configureLogging() {
System.setProperty(SPRING_BOOT_DATA_GEMFIRE_LOG_LEVEL_PROPERTY, getTestLogLevel().toString());
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
assertThat(loggerFactory).isInstanceOf(LoggerContext.class);
LoggerContext loggerContext = (LoggerContext) loggerFactory;
try {
new ContextInitializer(loggerContext).autoConfig();
}
catch (Exception cause) {
throw newIllegalStateException("Failed to configure and initialize SLF4J/Logback logging context", cause);
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void configureRootLoggerDelegatingAppender() {
Logger rootLogger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
assertThat(rootLogger).isInstanceOf(ch.qos.logback.classic.Logger.class);
ch.qos.logback.classic.Logger logbackRootLogger = (ch.qos.logback.classic.Logger) rootLogger;
Appender<?> delegateAppender = logbackRootLogger.getAppender("delegate");
assertThat(delegateAppender).isNotNull();
assertThat(delegateAppender.getName()).isEqualTo("delegate");
this.testAppender = new TestAppender();
this.testAppender.start();
((DelegatingAppender) delegateAppender).setAppender(this.testAppender);
assertThat(((DelegatingAppender) delegateAppender).getAppender()).isSameAs(this.testAppender);
}
public void logMessages() {
assertThat(this.apacheGeodeLogger).isNotNull();
this.apacheGeodeLogger.debug("DEBUG TEST");
this.apacheGeodeLogger.info("INFO TEST");
this.apacheGeodeLogger.error("ERROR TEST");
}
@After
public void tearDown() {
Optional.ofNullable(this.testAppender).ifPresent(it -> {
it.clear();
it.stop();
});
System.clearProperty(SPRING_BOOT_DATA_GEMFIRE_LOG_LEVEL_PROPERTY);
}
}

View File

@@ -0,0 +1,233 @@
/*
* Copyright 2017-present the original author or authors.
*
* Licensed 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.
*/
package org.springframework.geode.logging.slf4j.logback;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.Context;
/**
* Unit Tests for {@link CompositeAppender}.
*
* @author John Blum
* @see org.junit.Test
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see org.mockito.junit.MockitoJUnitRunner
* @see org.springframework.geode.logging.slf4j.logback.CompositeAppender
* @see ch.qos.logback.core.Appender
* @since 1.3.0
*/
@RunWith(MockitoJUnitRunner.class)
@SuppressWarnings({ "rawtypes", "unchecked" })
public class CompositeAppenderUnitTests {
@Mock
private Appender mockAppenderOne;
@Mock
private Appender mockAppenderTwo;
@Test
public void composeIsNullSafe() {
assertThat(CompositeAppender.compose(null, null)).isNull();
}
@Test
public void composeSingleAppenderReturnsTheAppender() {
assertThat(CompositeAppender.compose(this.mockAppenderOne, null)).isEqualTo(this.mockAppenderOne);
assertThat(CompositeAppender.compose(null, this.mockAppenderTwo)).isEqualTo(this.mockAppenderTwo);
}
@Test
public void composeTwoAppendersReturnsCompositeAppender() {
Appender<?> composite = CompositeAppender.compose(this.mockAppenderOne, this.mockAppenderTwo);
assertThat(composite).isInstanceOf(CompositeAppender.class);
assertThat((((CompositeAppender<?>) composite).getAppenderOne())).isEqualTo(this.mockAppenderOne);
assertThat((((CompositeAppender<?>) composite).getAppenderTwo())).isEqualTo(this.mockAppenderTwo);
assertThat(composite.getName()).isEqualTo(CompositeAppender.DEFAULT_NAME);
assertThat(composite.isStarted()).isTrue();
}
@Test
public void composeAppenderArray() {
Appender mockAppenderOne = mock(Appender.class);
Appender mockAppenderTwo = mock(Appender.class);
Appender mockAppenderThree = mock(Appender.class);
Appender composite = CompositeAppender.compose(mockAppenderOne, mockAppenderTwo, mockAppenderThree);
assertThat(composite).isInstanceOf(CompositeAppender.class);
Appender appenderOne = ((CompositeAppender) composite).getAppenderOne();
Appender appenderTwo = ((CompositeAppender) composite).getAppenderTwo();
assertThat(appenderOne).isInstanceOf(CompositeAppender.class);
assertThat(appenderTwo).isEqualTo(mockAppenderThree);
assertThat(((CompositeAppender) appenderOne).getAppenderOne()).isEqualTo(mockAppenderOne);
assertThat(((CompositeAppender) appenderOne).getAppenderTwo()).isEqualTo(mockAppenderTwo);
}
@Test
public void composeAppenderArrayWithOneAppender() {
assertThat(CompositeAppender.compose(this.mockAppenderOne)).isSameAs(this.mockAppenderOne);
}
@Test
public void composeAppenderArrayWithZeroAppenders() {
assertThat(CompositeAppender.compose()).isNull();
}
@Test
public void composeAppenderArrayIsNullSafe() {
assertThat(CompositeAppender.compose((Appender[]) null)).isNull();
}
@Test
public void composeAppenderIterable() {
Appender mockAppenderOne = mock(Appender.class);
Appender mockAppenderTwo = mock(Appender.class);
Appender mockAppenderThree = mock(Appender.class);
Appender composite =
CompositeAppender.compose(Arrays.asList(mockAppenderOne, mockAppenderTwo, mockAppenderThree));
assertThat(composite).isInstanceOf(CompositeAppender.class);
Appender appenderOne = ((CompositeAppender) composite).getAppenderOne();
Appender appenderTwo = ((CompositeAppender) composite).getAppenderTwo();
assertThat(appenderOne).isInstanceOf(CompositeAppender.class);
assertThat(appenderTwo).isEqualTo(mockAppenderThree);
assertThat(((CompositeAppender) appenderOne).getAppenderOne()).isEqualTo(mockAppenderOne);
assertThat(((CompositeAppender) appenderOne).getAppenderTwo()).isEqualTo(mockAppenderTwo);
}
@Test
public void composeAppenderIterableWithOneAppender() {
assertThat(CompositeAppender.compose(Collections.singletonList(this.mockAppenderTwo)))
.isSameAs(this.mockAppenderTwo);
}
@Test
public void composeAppenderIterableWithZeroAppenders() {
assertThat(CompositeAppender.compose(Collections.emptyList())).isNull();
}
@Test
public void composeAppenderIterableIsNullSafe() {
assertThat(CompositeAppender.compose((Iterable<Appender<Object>>) null)).isNull();
}
@Test
public void setContextConfiguresContextOnCompositeAppenderAndComposedAppenders() {
Context mockContext = mock(Context.class);
Appender compositeAppender = CompositeAppender.compose(this.mockAppenderOne, this.mockAppenderTwo);
assertThat(compositeAppender).isInstanceOf(CompositeAppender.class);
compositeAppender.setContext(mockContext);
assertThat(compositeAppender.getContext()).isEqualTo(mockContext);
verify(this.mockAppenderOne, times(1)).setContext(eq(mockContext));
verify(this.mockAppenderTwo, times(1)).setContext(eq(mockContext));
}
@Test
public void getContextReturnsConfiguredContext() {
Context mockContext = mock(Context.class);
Appender compositeAppender = CompositeAppender.compose(this.mockAppenderOne, this.mockAppenderTwo);
assertThat(compositeAppender).isInstanceOf(CompositeAppender.class);
compositeAppender.setContext(mockContext);
assertThat(compositeAppender.getContext()).isEqualTo(mockContext);
verify(this.mockAppenderOne, never()).getContext();
verify(this.mockAppenderTwo, never()).getContext();
}
@Test
public void getContextReturnsAppenderOneContext() {
Context mockContext = mock(Context.class);
doReturn(mockContext).when(this.mockAppenderOne).getContext();
Appender compositeAppender = CompositeAppender.compose(this.mockAppenderOne, this.mockAppenderTwo);
assertThat(compositeAppender).isInstanceOf(CompositeAppender.class);
assertThat(compositeAppender.getContext()).isEqualTo(mockContext);
verify(this.mockAppenderOne, times(1)).getContext();
verify(this.mockAppenderTwo, never()).getContext();
}
@Test
public void getContextReturnsAppenderTwoContext() {
Context mockContext = mock(Context.class);
doReturn(mockContext).when(this.mockAppenderTwo).getContext();
Appender compositeAppender = CompositeAppender.compose(this.mockAppenderOne, this.mockAppenderTwo);
assertThat(compositeAppender).isInstanceOf(CompositeAppender.class);
assertThat(compositeAppender.getContext()).isEqualTo(mockContext);
verify(this.mockAppenderOne, times(1)).getContext();
verify(this.mockAppenderTwo, times(1)).getContext();
}
@Test
public void appendCallsAppenderOneAppendAndAppenderTwoAppend() {
Appender compositeAppender = CompositeAppender.compose(this.mockAppenderOne, this.mockAppenderTwo);
assertThat(compositeAppender).isInstanceOf(CompositeAppender.class);
((CompositeAppender<Object>) compositeAppender).append("TEST");
verify(this.mockAppenderOne, times(1)).doAppend(eq("TEST"));
verify(this.mockAppenderTwo, times(1)).doAppend(eq("TEST"));
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2017-present the original author or authors.
*
* Licensed 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.
*/
package org.springframework.geode.logging.slf4j.logback;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.springframework.data.gemfire.tests.logging.slf4j.logback.TestAppender;
import ch.qos.logback.classic.Level;
/**
* Integration Tests testing the {@literal org.apache.geode} {@link org.slf4j.Logger}
* with log level {@link Level#DEBUG}.
*
* <code>
* -Dspring.boot.data.gemfire.log.level=DEBUG
* </code>
*
* @author John Blum
* @see org.junit.Test
* @see org.springframework.data.gemfire.tests.logging.slf4j.logback.TestAppender
* @see org.springframework.geode.logging.slf4j.logback.AbstractLoggingIntegrationTests
* @see ch.qos.logback.classic.Level#DEBUG
* @since 1.3.0
*/
public class DebugLoggingIntegrationTests extends AbstractLoggingIntegrationTests {
@Override
protected Level getTestLogLevel() {
return Level.DEBUG;
}
@Test
public void logLevelIsSetToDebug() {
assertApacheGeodeLoggerLogLevel(Level.DEBUG);
}
@Test
public void logMessagesAtDebug() {
TestAppender testAppender = getTestAppender();
assertThat(testAppender.lastLogMessage()).isEqualTo("ERROR TEST");
assertThat(testAppender.lastLogMessage()).isEqualTo("INFO TEST");
assertThat(testAppender.lastLogMessage()).isEqualTo("DEBUG TEST");
assertThat(testAppender.lastLogMessage()).isNull();
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2017-present the original author or authors.
*
* Licensed 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.
*/
package org.springframework.geode.logging.slf4j.logback;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import org.junit.Test;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.helpers.NOPAppender;
/**
* Unit Tests for {@link DelegatingAppender}.
*
* @author John Blum
* @see org.junit.Test
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see org.springframework.geode.logging.slf4j.logback.DelegatingAppender
* @since 1.3.0
*/
public class DelegatingAppenderUnitTests {
@Test
public void delegatingAppenderDefaultsNameToDelegate() {
assertThat(new DelegatingAppender<>().getName()).isEqualTo(DelegatingAppender.DEFAULT_NAME);
}
@Test
public void delegatingAppenderDefaultsToNoOpAppender() {
assertThat(new DelegatingAppender<>().getAppender()).isInstanceOf(NOPAppender.class);
}
@Test
@SuppressWarnings({ "rawtypes", "unchecked" })
public void delegatingAppenderDelegatesToMockAppender() {
Appender mockAppender = mock(Appender.class);
DelegatingAppender delegatingAppender = new DelegatingAppender<>();
delegatingAppender.setAppender(mockAppender);
assertThat(delegatingAppender.getAppender()).isSameAs(mockAppender);
delegatingAppender.append("TEST");
verify(mockAppender, times(1)).doAppend(eq("TEST"));
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2017-present the original author or authors.
*
* Licensed 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.
*/
package org.springframework.geode.logging.slf4j.logback;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.springframework.data.gemfire.tests.logging.slf4j.logback.TestAppender;
import ch.qos.logback.classic.Level;
/**
* Integration Tests testing the {@literal org.apache.geode} {@link org.slf4j.Logger}
* with the default log level {@link Level#INFO}.
*
* @author John Blum
* @see org.junit.Test
* @see org.springframework.data.gemfire.tests.logging.slf4j.logback.TestAppender
* @see org.springframework.geode.logging.slf4j.logback.AbstractLoggingIntegrationTests
* @see ch.qos.logback.classic.Level#INFO
* @since 1.3.0
*/
public class InfoLoggingIntegrationTests extends AbstractLoggingIntegrationTests {
@Test
public void logLevelIsSetToInfo() {
assertApacheGeodeLoggerLogLevel(Level.INFO);
}
@Test
public void logsMessagesAtInfo() {
TestAppender testAppender = getTestAppender();
assertThat(testAppender.lastLogMessage()).isEqualTo("ERROR TEST");
assertThat(testAppender.lastLogMessage()).isEqualTo("INFO TEST");
assertThat(testAppender.lastLogMessage()).isNull();
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2017-present the original author or authors.
*
* Licensed 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.
*/
package org.springframework.geode.logging.slf4j.logback;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.springframework.data.gemfire.tests.logging.slf4j.logback.TestAppender;
import ch.qos.logback.classic.Level;
/**
* Integration Tests testing the {@literal org.apache.geode} {@link org.slf4j.Logger}
* with no logging enabled, i.e. {@link Level#OFF}.
*
* <code>
* -Dspring.boot.data.gemfire.log.level=DEBUG
* </code>
*
* @author John Blum
* @see org.junit.Test
* @see org.springframework.data.gemfire.tests.logging.slf4j.logback.TestAppender
* @see org.springframework.geode.logging.slf4j.logback.AbstractLoggingIntegrationTests
* @see ch.qos.logback.classic.Level#OFF
* @since 1.3.0
*/
public class NoLoggingIntegrationTests extends AbstractLoggingIntegrationTests {
@Override
protected Level getTestLogLevel() {
return Level.OFF;
}
@Test
public void logLevelIsSetToOff() {
assertApacheGeodeLoggerLogLevel(Level.OFF);
}
@Test
public void logsNoMessages() {
TestAppender testAppender = getTestAppender();
assertThat(testAppender.lastLogMessage()).isNull();
}
}

View File

@@ -0,0 +1,266 @@
/*
* Copyright 2017-present the original author or authors.
*
* Licensed 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.
*/
package org.springframework.geode.logging.slf4j.logback;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.Context;
/**
* Unit Tests for {@link StringAppender}.
*
* @author John Blum
* @see org.junit.Test
* @see org.mockito.Mock
* @see org.mockito.Mockito
* @see org.mockito.junit.MockitoJUnitRunner
* @see org.springframework.geode.logging.slf4j.logback.StringAppender
* @see ch.qos.logback.classic.Logger
* @see ch.qos.logback.core.Appender
* @since 1.3.0
*/
@RunWith(MockitoJUnitRunner.class)
@SuppressWarnings({ "rawtypes", "unchecked" })
public class StringAppenderUnitTests {
@Mock
private StringAppender.StringAppenderWrapper mockWrapper;
@After
public void tearDown() {
((Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).detachAndStopAllAppenders();
}
@Test
public void buildStringAppender() {
StringAppender stringAppender = new StringAppender.Builder().build();
assertThat(stringAppender).isNotNull();
assertThat(stringAppender.isStarted()).isFalse();
assertThat(stringAppender.getContext()).isEqualTo(LoggerFactory.getILoggerFactory());
assertThat(stringAppender.getName()).isEqualTo(StringAppender.DEFAULT_NAME);
assertThat(stringAppender.getStringAppenderWrapper())
.isInstanceOf(StringAppender.StringBuilderAppenderWrapper.class);
}
@Test
public void buildStringAppenderApplyToDelegateUsingReplace() {
DelegatingAppender delegate = spy(new DelegatingAppender());
StringAppender stringAppender = new StringAppender.Builder()
.applyTo(delegate, true)
.build();
assertThat(stringAppender).isNotNull();
assertThat(stringAppender.isStarted()).isFalse();
assertThat(stringAppender.getName()).isEqualTo(StringAppender.DEFAULT_NAME);
verify(delegate, times(1)).setAppender(eq(stringAppender));
assertThat(delegate.getAppender()).isEqualTo(stringAppender);
}
@Test
public void buildAndStartStringAppender() {
Context mockContext = mock(Context.class);
DelegatingAppender delegate = spy(new DelegatingAppender());
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
StringAppender stringAppender = new StringAppender.Builder()
.applyTo(delegate)
.applyTo(rootLogger)
.setContext(mockContext)
.setName("TestStringAppender")
.useSynchronization()
.buildAndStart();
assertThat(stringAppender).isNotNull();
assertThat(stringAppender.isStarted()).isTrue();
assertThat(stringAppender.getContext()).isEqualTo(mockContext);
assertThat(stringAppender.getName()).isEqualTo("TestStringAppender");
assertThat(stringAppender.getStringAppenderWrapper())
.isInstanceOf(StringAppender.StringBufferAppenderWrapper.class);
assertThat(rootLogger.getAppender("TestStringAppender")).isEqualTo(stringAppender);
verify(delegate, times(1)).setAppender(isA(CompositeAppender.class));
Appender compositeAppender = delegate.getAppender();
assertThat(compositeAppender).isInstanceOf(CompositeAppender.class);
assertThat(((CompositeAppender) compositeAppender).getAppenderOne()).isEqualTo(DelegatingAppender.DEFAULT_APPENDER);
assertThat(((CompositeAppender) compositeAppender).getAppenderTwo()).isEqualTo(stringAppender);
}
@Test
public void constructStringAppender() {
StringAppender.StringAppenderWrapper mockWrapper = mock(StringAppender.StringAppenderWrapper.class);
StringAppender appender = new StringAppender(mockWrapper);
assertThat(appender).isNotNull();
assertThat(appender.getStringAppenderWrapper()).isEqualTo(mockWrapper);
}
@Test(expected = IllegalArgumentException.class)
public void constructStringAppenderWithNullWrapper() {
try {
new StringAppender(null);
}
catch (IllegalArgumentException expected) {
assertThat(expected).hasMessage("StringAppenderWrapper must not be null");
assertThat(expected).hasNoCause();
throw expected;
}
}
@Test
public void appendCallsConfiguredStringAppenderWrapper() {
ILoggingEvent mockEvent = mock(ILoggingEvent.class);
StringAppender appender = spy(new StringAppender(this.mockWrapper));
doReturn(this.mockWrapper).when(appender).getStringAppenderWrapper();
doReturn("TEST").when(appender).toString(eq(mockEvent));
appender.append(mockEvent);
verify(this.mockWrapper, times(1)).append(eq("TEST"));
}
@Test
public void appendIgnoresBlankEmptyAndNullStrings() {
ILoggingEvent mockEvent = mock(ILoggingEvent.class);
StringAppender appender = spy(new StringAppender(this.mockWrapper));
doReturn(this.mockWrapper).when(appender).getStringAppenderWrapper();
doReturn(" ").doReturn("").doReturn(null)
.when(appender).toString(eq(mockEvent));
appender.append(mockEvent);
appender.append(mockEvent);
appender.append(mockEvent);
verify(appender, times(3)).toString(eq(mockEvent));
verify(appender, times(1)).preProcessLogMessage(eq(" "));
verify(appender, times(1)).preProcessLogMessage(eq(""));
verify(appender, times(1)).preProcessLogMessage(eq(null));
verify(this.mockWrapper, never()).append(any());
}
@Test
public void appendIsNullSafe() {
StringAppender appender = spy(new StringAppender(this.mockWrapper));
doReturn(this.mockWrapper).when(appender).getStringAppenderWrapper();
appender.append(null);
verify(appender, never()).toString(any(ILoggingEvent.class));
verify(this.mockWrapper, never()).append(any());
}
@Test
public void emptyStringIsNotValidLogMessage() {
StringAppender appender = new StringAppender(this.mockWrapper);
assertThat(appender.isValidLogMessage("")).isFalse();
assertThat(appender.isValidLogMessage(null)).isFalse(); }
@Test
public void nonEmptyStringIsValidLogMessage() {
StringAppender appender = new StringAppender(this.mockWrapper);
assertThat(appender.isValidLogMessage("TEST")).isTrue();
assertThat(appender.isValidLogMessage("_")).isTrue();
assertThat(appender.isValidLogMessage(" ")).isTrue();
}
@Test
public void preProcessLogMessageIsNullSafe() {
assertThat(new StringAppender(this.mockWrapper).preProcessLogMessage(null)).isNull();
}
@Test
public void preProcessLogMessageTrimsMessage() {
StringAppender appender = new StringAppender(this.mockWrapper);
assertThat(appender.preProcessLogMessage("TEST")).isEqualTo("TEST");
assertThat(appender.preProcessLogMessage(" MOCK ")).isEqualTo("MOCK");
assertThat(appender.preProcessLogMessage("J UNK ")).isEqualTo("J UNK");
}
@Test
public void toStringCallsLoggingEventFormattedMessage() {
ILoggingEvent mockEvent = mock(ILoggingEvent.class);
when(mockEvent.getFormattedMessage()).thenReturn("TEST");
assertThat(new StringAppender(this.mockWrapper).toString(mockEvent)).isEqualTo("TEST");
verify(mockEvent, times(1)).getFormattedMessage();
}
@Test
public void toStringIsNullSafe() {
assertThat(new StringAppender(this.mockWrapper).toString(null)).isNull();
}
@Test
public void getLogOutputCallsStringAppenderWrapperToString() {
doReturn("TEST").when(this.mockWrapper).toString();
assertThat(new StringAppender(this.mockWrapper).getLogOutput()).isEqualTo("TEST");
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<include resource="logback-include.xml"/>
<root level="${logback.root.log.level:-ERROR}">
<appender-ref ref="delegate"/>
</root>
</configuration>