All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.valfirst.slf4jtest.LoggingEvent Maven / Gradle / Ivy

There is a newer version: 3.0.1
Show newest version
package com.github.valfirst.slf4jtest;

import static java.util.Objects.requireNonNull;
import static java.util.Optional.empty;
import static java.util.Optional.ofNullable;

import java.io.PrintStream;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.SortedMap;
import java.util.TreeMap;
import org.slf4j.Marker;
import org.slf4j.event.KeyValuePair;
import org.slf4j.event.Level;
import org.slf4j.helpers.MessageFormatter;

/**
 * Representation of a call to a logger for test assertion purposes. The contract of {@link
 * #equals(Object)} and {@link #hashCode} is that they compare the results of:
 *
 * 
    *
  • {@link #getLevel()} *
  • {@link #getMdc()} *
  • {@link #getMarkers()} *
  • {@link #getKeyValuePairs()} *
  • {@link #getThrowable()} *
  • {@link #getMessage()} *
  • {@link #getArguments()} *
* *

They do NOT compare the results of {@link #getTimestamp()}, {@link #getCreatingLogger()} or * {@link #getThreadContextClassLoader()} as this would render it impractical to create appropriate * expected {@link LoggingEvent}s to compare against. * *

Constructors and convenient static factory methods exist to create {@link LoggingEvent}s with * appropriate defaults. These are not documented further as they should be self-evident. */ @SuppressWarnings({"PMD.ExcessivePublicCount", "PMD.TooManyMethods"}) public class LoggingEvent { private static final DateTimeFormatter ISO_FORMAT = new DateTimeFormatterBuilder().appendInstant(3).toFormatter(); private static final Object[] emptyObjectArray = {}; private final Level level; private final SortedMap mdc; private final List markers; private final List keyValuePairs; private final Optional throwable; private final String message; private final List arguments; private final Optional creatingLogger; private final Instant timestamp = Instant.now(); private final String threadName = Thread.currentThread().getName(); private final ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader(); public static LoggingEvent trace(final String message, final Object... arguments) { return new LoggingEvent(Level.TRACE, message, arguments); } public static LoggingEvent trace( final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.TRACE, throwable, message, arguments); } public static LoggingEvent trace( final Marker marker, final String message, final Object... arguments) { return new LoggingEvent(Level.TRACE, marker, message, arguments); } public static LoggingEvent trace( final Marker marker, final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.TRACE, marker, throwable, message, arguments); } public static LoggingEvent trace( final Map mdc, final String message, final Object... arguments) { return new LoggingEvent(Level.TRACE, mdc, message, arguments); } public static LoggingEvent trace( final Map mdc, final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.TRACE, mdc, throwable, message, arguments); } public static LoggingEvent trace( final Map mdc, final Marker marker, final String message, final Object... arguments) { return new LoggingEvent(Level.TRACE, mdc, marker, message, arguments); } public static LoggingEvent trace( final Map mdc, final Marker marker, final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.TRACE, mdc, marker, throwable, message, arguments); } public static LoggingEvent debug(final String message, final Object... arguments) { return new LoggingEvent(Level.DEBUG, message, arguments); } public static LoggingEvent debug( final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.DEBUG, throwable, message, arguments); } public static LoggingEvent debug( final Marker marker, final String message, final Object... arguments) { return new LoggingEvent(Level.DEBUG, marker, message, arguments); } public static LoggingEvent debug( final Marker marker, final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.DEBUG, marker, throwable, message, arguments); } public static LoggingEvent debug( final Map mdc, final String message, final Object... arguments) { return new LoggingEvent(Level.DEBUG, mdc, message, arguments); } public static LoggingEvent debug( final Map mdc, final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.DEBUG, mdc, throwable, message, arguments); } public static LoggingEvent debug( final Map mdc, final Marker marker, final String message, final Object... arguments) { return new LoggingEvent(Level.DEBUG, mdc, marker, message, arguments); } public static LoggingEvent debug( final Map mdc, final Marker marker, final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.DEBUG, mdc, marker, throwable, message, arguments); } public static LoggingEvent info(final String message, final Object... arguments) { return new LoggingEvent(Level.INFO, message, arguments); } public static LoggingEvent info( final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.INFO, throwable, message, arguments); } public static LoggingEvent info( final Marker marker, final String message, final Object... arguments) { return new LoggingEvent(Level.INFO, marker, message, arguments); } public static LoggingEvent info( final Marker marker, final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.INFO, marker, throwable, message, arguments); } public static LoggingEvent info( final Map mdc, final String message, final Object... arguments) { return new LoggingEvent(Level.INFO, mdc, message, arguments); } public static LoggingEvent info( final Map mdc, final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.INFO, mdc, throwable, message, arguments); } public static LoggingEvent info( final Map mdc, final Marker marker, final String message, final Object... arguments) { return new LoggingEvent(Level.INFO, mdc, marker, message, arguments); } public static LoggingEvent info( final Map mdc, final Marker marker, final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.INFO, mdc, marker, throwable, message, arguments); } public static LoggingEvent warn(final String message, final Object... arguments) { return new LoggingEvent(Level.WARN, message, arguments); } public static LoggingEvent warn( final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.WARN, throwable, message, arguments); } public static LoggingEvent warn( final Marker marker, final String message, final Object... arguments) { return new LoggingEvent(Level.WARN, marker, message, arguments); } public static LoggingEvent warn( final Marker marker, final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.WARN, marker, throwable, message, arguments); } public static LoggingEvent warn( final Map mdc, final String message, final Object... arguments) { return new LoggingEvent(Level.WARN, mdc, message, arguments); } public static LoggingEvent warn( final Map mdc, final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.WARN, mdc, throwable, message, arguments); } public static LoggingEvent warn( final Map mdc, final Marker marker, final String message, final Object... arguments) { return new LoggingEvent(Level.WARN, mdc, marker, message, arguments); } public static LoggingEvent warn( final Map mdc, final Marker marker, final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.WARN, mdc, marker, throwable, message, arguments); } public static LoggingEvent error(final String message, final Object... arguments) { return new LoggingEvent(Level.ERROR, message, arguments); } public static LoggingEvent error( final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.ERROR, throwable, message, arguments); } public static LoggingEvent error( final Marker marker, final String message, final Object... arguments) { return new LoggingEvent(Level.ERROR, marker, message, arguments); } public static LoggingEvent error( final Marker marker, final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.ERROR, marker, throwable, message, arguments); } public static LoggingEvent error( final Map mdc, final String message, final Object... arguments) { return new LoggingEvent(Level.ERROR, mdc, message, arguments); } public static LoggingEvent error( final Map mdc, final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.ERROR, mdc, throwable, message, arguments); } public static LoggingEvent error( final Map mdc, final Marker marker, final String message, final Object... arguments) { return new LoggingEvent(Level.ERROR, mdc, marker, message, arguments); } public static LoggingEvent error( final Map mdc, final Marker marker, final Throwable throwable, final String message, final Object... arguments) { return new LoggingEvent(Level.ERROR, mdc, marker, throwable, message, arguments); } /** * Create a {@link LoggingEvent} from an SLF4J {@link org.slf4j.event.LoggingEvent}. * * @since 3.0.0 */ public static LoggingEvent fromSlf4jEvent(org.slf4j.event.LoggingEvent event) { return fromSlf4jEvent(event, Collections.emptyMap()); } /** * Create a {@link LoggingEvent} with an MDC from an SLF4J {@link org.slf4j.event.LoggingEvent}. * * @since 3.0.0 */ public static LoggingEvent fromSlf4jEvent( org.slf4j.event.LoggingEvent event, Map mdc) { List markers = event.getMarkers(); List keyValuePairs = event.getKeyValuePairs(); Object[] arguments = event.getArgumentArray(); return new LoggingEvent( empty(), event.getLevel(), mdc, markers == null ? Collections.emptyList() : Collections.unmodifiableList(new ArrayList<>(markers)), keyValuePairs == null ? Collections.emptyList() : Collections.unmodifiableList(new ArrayList<>(keyValuePairs)), ofNullable(event.getThrowable()), event.getMessage(), arguments == null ? emptyObjectArray : arguments); } public LoggingEvent(final Level level, final String message, final Object... arguments) { this(level, Collections.emptySortedMap(), empty(), empty(), message, arguments); } public LoggingEvent( final Level level, final Throwable throwable, final String message, final Object... arguments) { this(level, Collections.emptySortedMap(), empty(), ofNullable(throwable), message, arguments); } public LoggingEvent( final Level level, final Marker marker, final String message, final Object... arguments) { this(level, Collections.emptySortedMap(), ofNullable(marker), empty(), message, arguments); } public LoggingEvent( final Level level, final Marker marker, final Throwable throwable, final String message, final Object... arguments) { this( level, Collections.emptySortedMap(), ofNullable(marker), ofNullable(throwable), message, arguments); } public LoggingEvent( final Level level, final Map mdc, final String message, final Object... arguments) { this(level, mdc, empty(), empty(), message, arguments); } public LoggingEvent( final Level level, final Map mdc, final Throwable throwable, final String message, final Object... arguments) { this(level, mdc, empty(), ofNullable(throwable), message, arguments); } public LoggingEvent( final Level level, final Map mdc, final Marker marker, final String message, final Object... arguments) { this(level, mdc, ofNullable(marker), empty(), message, arguments); } public LoggingEvent( final Level level, final Map mdc, final Marker marker, final Throwable throwable, final String message, final Object... arguments) { this(level, mdc, ofNullable(marker), ofNullable(throwable), message, arguments); } private LoggingEvent( final Level level, final Map mdc, final Optional marker, final Optional throwable, final String message, final Object... arguments) { this( empty(), level, mdc, marker.map(Collections::singletonList).orElseGet(Collections::emptyList), Collections.emptyList(), throwable, message, arguments); } LoggingEvent( final Optional creatingLogger, final Level level, final Map mdc, final List markers, final List keyValuePairs, final Optional throwable, final String message, final Object... arguments) { super(); this.creatingLogger = creatingLogger; this.level = requireNonNull(level); this.mdc = requireNonNull(mdc).isEmpty() ? Collections.emptySortedMap() : Collections.unmodifiableSortedMap(new TreeMap<>(mdc)); this.markers = markers; this.keyValuePairs = keyValuePairs; this.throwable = requireNonNull(throwable); this.message = message; this.arguments = arguments.length == 0 ? Collections.emptyList() : Collections.unmodifiableList(new ArrayList<>(Arrays.asList(arguments))); } public Level getLevel() { return level; } /** * Get the MDC of the event. For events created by {@link TestLogger}, this is an unmodifiable * copy of the MDC of the thread when the event was created. For events constructed directly, this * is unmodifiable copy of the MDC passed to the constructor, if any. If no MDC was used for * construction, the copy is an empty map. The copy is a {@link SortedMap}, in order to make it * easier to spot discrepancies in case an assertion fails. Natural ordering of the keys is used. */ public SortedMap getMdc() { return mdc; } /** * Get the marker of the event. * * @deprecated As events created using the SLF4J fluent API can contain multiple markers, this * method is deprecated in favor of {@link #getMarkers}. * @throws IllegalStateException if the event has more than one marker. */ @Deprecated public Optional getMarker() { if (markers.isEmpty()) return empty(); if (markers.size() == 1) return Optional.of(markers.get(0)); throw new IllegalStateException("LoggingEvent has more than one marker"); } /** * Get the markers of the event. If the event has no markers, an empty list is returned. * * @return an unmodifiable copy of the markers when the event was created. * @since 3.0.0 */ public List getMarkers() { return markers; } /** * Get the key/value pairs of the event. If the event has no key/value pairs, an empty list is * returned. * * @return an unmodifiable copy of the key/value pairs when the event was created. * @since 3.0.0 */ public List getKeyValuePairs() { return keyValuePairs; } public String getMessage() { return message; } /** * Get the arguments to the event. * * @return an unmodifiable copy of the arguments when the event was created. */ public List getArguments() { return arguments; } public Optional getThrowable() { return throwable; } /** * @return the logger that created this logging event. * @throws IllegalStateException if this logging event was not created by a logger */ public TestLogger getCreatingLogger() { return creatingLogger.get(); } /** * @return the time at which this logging event was created */ public Instant getTimestamp() { return timestamp; } /** * @return the name of the thread that created this logging event */ public String getThreadName() { return threadName; } /** * @return the Thread Context Classloader used when this logging event was created */ public ClassLoader getThreadContextClassLoader() { return threadContextClassLoader; } void print() { final PrintStream output = printStreamForLevel(); output.println(formatLogStatement()); throwable.ifPresent(throwableToPrint -> throwableToPrint.printStackTrace(output)); } private String formatLogStatement() { return ISO_FORMAT.format(getTimestamp()) + " [" + getThreadName() + "] " + getLevel() + safeLoggerName() + " - " + getFormattedMessage(); } private String safeLoggerName() { return creatingLogger.map(logger -> " " + logger.getName()).orElse(""); } public String getFormattedMessage() { Object[] argumentsWithNulls = getArguments().toArray(); return MessageFormatter.arrayFormat(getMessage(), argumentsWithNulls).getMessage(); } private PrintStream printStreamForLevel() { switch (level) { case ERROR: case WARN: return System.err; default: return System.out; } } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } LoggingEvent that = (LoggingEvent) o; return level == that.level && Objects.equals(mdc, that.mdc) && Objects.equals(markers, that.markers) && Objects.equals(keyValuePairs, that.keyValuePairs) && Objects.equals(throwable, that.throwable) && Objects.equals(message, that.message) && Objects.equals(arguments, that.arguments); } @Override public int hashCode() { return Objects.hash(level, mdc, markers, keyValuePairs, throwable, message, arguments); } @Override public String toString() { return "LoggingEvent{" + "level=" + level + ", mdc=" + mdc + ", markers=" + markers + ", keyValuePairs=" + keyValuePairs + ", throwable=" + throwable + ", message=" + (message == null ? "null" : '\'' + message + '\'') + ", arguments=" + arguments + '}'; } }