package org.apache.logging.log4j.status;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.MessageFactory;
import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory;
import org.apache.logging.log4j.simple.SimpleLogger;
import org.apache.logging.log4j.simple.SimpleLoggerContext;
import org.apache.logging.log4j.spi.AbstractLogger;
import org.apache.logging.log4j.util.Constants;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.apache.logging.log4j.util.Strings;
public final class StatusLogger extends AbstractLogger {
public static final String MAX_STATUS_ENTRIES = "log4j2.status.entries";
public static final String DEFAULT_STATUS_LISTENER_LEVEL = "log4j2.StatusLogger.level";
public static final String STATUS_DATE_FORMAT = "log4j2.StatusLogger.DateFormat";
private static final long serialVersionUID = 2L;
private static final String NOT_AVAIL = "?";
private static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties");
private static final int MAX_ENTRIES = PROPS.getIntegerProperty(MAX_STATUS_ENTRIES, 200);
private static final String DEFAULT_STATUS_LEVEL = PROPS.getStringProperty(DEFAULT_STATUS_LISTENER_LEVEL);
private static final StatusLogger STATUS_LOGGER = new StatusLogger(StatusLogger.class.getName(),
ParameterizedNoReferenceMessageFactory.INSTANCE);
private final SimpleLogger logger;
private final Collection<StatusListener> listeners = new CopyOnWriteArrayList<>();
@SuppressWarnings("NonSerializableFieldInSerializableClass")
private final ReadWriteLock listenersLock = new ReentrantReadWriteLock();
private final Queue<StatusData> messages = new BoundedQueue<>(MAX_ENTRIES);
@SuppressWarnings("NonSerializableFieldInSerializableClass")
private final Lock msgLock = new ReentrantLock();
private int listenersLevel;
private StatusLogger(final String name, final MessageFactory messageFactory) {
super(name, messageFactory);
final String dateFormat = PROPS.getStringProperty(STATUS_DATE_FORMAT, Strings.EMPTY);
final boolean showDateTime = !Strings.isEmpty(dateFormat);
this.logger = new SimpleLogger("StatusLogger", Level.ERROR, false, true, showDateTime, false,
dateFormat, messageFactory, PROPS, System.err);
this.listenersLevel = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
if (isDebugPropertyEnabled()) {
logger.setLevel(Level.TRACE);
}
}
private boolean isDebugPropertyEnabled() {
return PropertiesUtil.getProperties().getBooleanProperty(Constants.LOG4J2_DEBUG, false, true);
}
public static StatusLogger getLogger() {
return STATUS_LOGGER;
}
public void setLevel(final Level level) {
logger.setLevel(level);
}
public void registerListener(final StatusListener listener) {
listenersLock.writeLock().lock();
try {
listeners.add(listener);
final Level lvl = listener.getStatusLevel();
if (listenersLevel < lvl.intLevel()) {
listenersLevel = lvl.intLevel();
}
} finally {
listenersLock.writeLock().unlock();
}
}
public void removeListener(final StatusListener listener) {
closeSilently(listener);
listenersLock.writeLock().lock();
try {
listeners.remove(listener);
int lowest = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
for (final StatusListener statusListener : listeners) {
final int level = statusListener.getStatusLevel().intLevel();
if (lowest < level) {
lowest = level;
}
}
listenersLevel = lowest;
} finally {
listenersLock.writeLock().unlock();
}
}
public void updateListenerLevel(final Level status) {
if (status.intLevel() > listenersLevel) {
listenersLevel = status.intLevel();
}
}
public Iterable<StatusListener> getListeners() {
return listeners;
}
public void reset() {
listenersLock.writeLock().lock();
try {
for (final StatusListener listener : listeners) {
closeSilently(listener);
}
} finally {
listeners.clear();
listenersLock.writeLock().unlock();
clear();
}
}
private static void closeSilently(final Closeable resource) {
try {
resource.close();
} catch (final IOException ignored) {
}
}
public List<StatusData> getStatusData() {
msgLock.lock();
try {
return new ArrayList<>(messages);
} finally {
msgLock.unlock();
}
}
public void clear() {
msgLock.lock();
try {
messages.clear();
} finally {
msgLock.unlock();
}
}
@Override
public Level getLevel() {
return logger.getLevel();
}
@Override
public void logMessage(final String fqcn, final Level level, final Marker marker, final Message msg,
final Throwable t) {
StackTraceElement element = null;
if (fqcn != null) {
element = getStackTraceElement(fqcn, Thread.currentThread().getStackTrace());
}
final StatusData data = new StatusData(element, level, msg, t, null);
msgLock.lock();
try {
messages.add(data);
} finally {
msgLock.unlock();
}
if (isDebugPropertyEnabled()) {
logger.logMessage(fqcn, level, marker, msg, t);
} else {
if (listeners.size() > 0) {
for (final StatusListener listener : listeners) {
if (data.getLevel().isMoreSpecificThan(listener.getStatusLevel())) {
listener.log(data);
}
}
} else {
logger.logMessage(fqcn, level, marker, msg, t);
}
}
}
private StackTraceElement getStackTraceElement(final String fqcn, final StackTraceElement[] stackTrace) {
if (fqcn == null) {
return null;
}
boolean next = false;
for (final StackTraceElement element : stackTrace) {
final String className = element.getClassName();
if (next && !fqcn.equals(className)) {
return element;
}
if (fqcn.equals(className)) {
next = true;
} else if (NOT_AVAIL.equals(className)) {
break;
}
}
return null;
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) {
return isEnabled(level, marker);
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message) {
return isEnabled(level, marker);
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object... params) {
return isEnabled(level, marker);
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0) {
return isEnabled(level, marker);
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
final Object p1) {
return isEnabled(level, marker);
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
final Object p1, final Object p2) {
return isEnabled(level, marker);
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
final Object p1, final Object p2, final Object p3) {
return isEnabled(level, marker);
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
final Object p1, final Object p2, final Object p3,
final Object p4) {
return isEnabled(level, marker);
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
final Object p1, final Object p2, final Object p3,
final Object p4, final Object p5) {
return isEnabled(level, marker);
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
final Object p1, final Object p2, final Object p3,
final Object p4, final Object p5, final Object p6) {
return isEnabled(level, marker);
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
final Object p1, final Object p2, final Object p3,
final Object p4, final Object p5, final Object p6,
final Object p7) {
return isEnabled(level, marker);
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
final Object p1, final Object p2, final Object p3,
final Object p4, final Object p5, final Object p6,
final Object p7, final Object p8) {
return isEnabled(level, marker);
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
final Object p1, final Object p2, final Object p3,
final Object p4, final Object p5, final Object p6,
final Object p7, final Object p8, final Object p9) {
return isEnabled(level, marker);
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final CharSequence message, final Throwable t) {
return isEnabled(level, marker);
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable t) {
return isEnabled(level, marker);
}
@Override
public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable t) {
return isEnabled(level, marker);
}
@Override
public boolean isEnabled(final Level level, final Marker marker) {
if (isDebugPropertyEnabled()) {
return true;
}
if (listeners.size() > 0) {
return listenersLevel >= level.intLevel();
}
return logger.isEnabled(level, marker);
}
private class BoundedQueue<E> extends ConcurrentLinkedQueue<E> {
private static final long serialVersionUID = -3945953719763255337L;
private final int size;
BoundedQueue(final int size) {
this.size = size;
}
@Override
public boolean add(final E object) {
super.add(object);
while (messages.size() > size) {
messages.poll();
}
return size > 0;
}
}
}