org.apache.logging.log4j.status.StatusLogger Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache license, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the license for the specific language governing permissions and
* limitations under the license.
*/
package org.apache.logging.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.spi.AbstractLogger;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.apache.logging.log4j.util.Strings;
/**
* Records events that occur in the logging system.
*/
public final class StatusLogger extends AbstractLogger {
/**
* System property that can be configured with the number of entries in the queue. Once the limit is reached older
* entries will be removed as new entries are added.
*/
public static final String MAX_STATUS_ENTRIES = "log4j2.status.entries";
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("log4j2.StatusLogger.level");
// LOG4J2-1176: normal parameterized message remembers param object, causing memory leaks.
private static final StatusLogger STATUS_LOGGER = new StatusLogger(StatusLogger.class.getName(),
ParameterizedNoReferenceMessageFactory.INSTANCE);
private final SimpleLogger logger;
private final Collection listeners = new CopyOnWriteArrayList<>();
@SuppressWarnings("NonSerializableFieldInSerializableClass")
// ReentrantReadWriteLock is Serializable
private final ReadWriteLock listenersLock = new ReentrantReadWriteLock();
private final Queue messages = new BoundedQueue<>(MAX_ENTRIES);
@SuppressWarnings("NonSerializableFieldInSerializableClass")
// ReentrantLock is Serializable
private final Lock msgLock = new ReentrantLock();
private int listenersLevel;
private StatusLogger(String name, MessageFactory messageFactory) {
super(name, messageFactory);
this.logger = new SimpleLogger("StatusLogger", Level.ERROR, false, true, false, false, Strings.EMPTY,
messageFactory, PROPS, System.err);
this.listenersLevel = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel();
}
/**
* Retrieve the StatusLogger.
*
* @return The StatusLogger.
*/
public static StatusLogger getLogger() {
return STATUS_LOGGER;
}
public void setLevel(final Level level) {
logger.setLevel(level);
}
/**
* Registers a new listener.
*
* @param listener The StatusListener to register.
*/
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();
}
}
/**
* Removes a StatusListener.
*
* @param listener The StatusListener to remove.
*/
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();
}
}
/**
* Returns a thread safe Iterable for the StatusListener.
*
* @return An Iterable for the list of StatusListeners.
*/
public Iterable getListeners() {
return listeners;
}
/**
* Clears the list of status events and listeners.
*/
public void reset() {
listenersLock.writeLock().lock();
try {
for (final StatusListener listener : listeners) {
closeSilently(listener);
}
} finally {
listeners.clear();
listenersLock.writeLock().unlock();
// note this should certainly come after the unlock to avoid unnecessary nested locking
clear();
}
}
private static void closeSilently(final Closeable resource) {
try {
resource.close();
} catch (final IOException ignored) {
// ignored
}
}
/**
* Returns a List of all events as StatusData objects.
*
* @return The list of StatusData objects.
*/
public List getStatusData() {
msgLock.lock();
try {
return new ArrayList<>(messages);
} finally {
msgLock.unlock();
}
}
/**
* Clears the list of status events.
*/
public void clear() {
msgLock.lock();
try {
messages.clear();
} finally {
msgLock.unlock();
}
}
@Override
public Level getLevel() {
return logger.getLevel();
}
/**
* Adds an event.
*
* @param marker The Marker
* @param fqcn The fully qualified class name of the caller
* @param level The logging level
* @param msg The message associated with the event.
* @param t A Throwable or null.
*/
@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 (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 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 (listeners.size() > 0) {
return listenersLevel >= level.intLevel();
}
return logger.isEnabled(level, marker);
}
/**
* Queues for status events.
*
* @param Object type to be stored in the queue.
*/
private class BoundedQueue extends ConcurrentLinkedQueue {
private static final long serialVersionUID = -3945953719763255337L;
private final int size;
public 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;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy