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

de.carne.boot.logging.LogBuffer Maven / Gradle / Ivy

/*
 * Copyright (c) 2018-2019 Holger de Carne and contributors, All Rights Reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package de.carne.boot.logging;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Handler;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import org.eclipse.jdt.annotation.Nullable;

/**
 * {@linkplain Handler} implementation used to add/remove {@linkplain Handler} instances programmatically during
 * application runtime. This class keeps a buffer of published {@linkplain LogRecord}s to make them available to added
 * {@linkplain Handler} instances (e.g. to display log messages issued during application startup in UI).
 */
public class LogBuffer extends Handler {

	private final int limit;
	private final Queue buffer;
	private final Set handlers = new HashSet<>();
	private final AtomicBoolean locked = new AtomicBoolean();

	/**
	 * Constructs a new {@linkplain LogBuffer} instance.
	 */
	public LogBuffer() {
		LogManager manager = LogManager.getLogManager();
		String propertyBase = getClass().getName();

		this.limit = Logs.getIntProperty(manager, propertyBase + ".limit", 1000);
		this.buffer = new ArrayDeque<>(this.limit);
		setLevel(Logs.getLevelProperty(manager, propertyBase + ".level", LogLevel.LEVEL_WARNING));
		setFilter(Logs.getFilterProperty(manager, propertyBase + ".filter", null));
	}

	/**
	 * Gets the {@linkplain LogBuffer} attached to the submitted {@linkplain Log} (if any).
	 *
	 * @param log the {@linkplain Log} to get the {@linkplain LogBuffer} for.
	 * @return the found {@linkplain LogBuffer} or {@code null} if none has been configured.
	 */
	@Nullable
	public static LogBuffer get(Log log) {
		return get(log.logger());
	}

	/**
	 * Gets the {@linkplain LogBuffer} attached to the submitted {@linkplain Logger} (if any).
	 *
	 * @param logger the {@linkplain Logger} to get the {@linkplain LogBuffer} for.
	 * @return the found {@linkplain LogBuffer} or {@code null} if none has been configured.
	 */
	@Nullable
	public static LogBuffer get(Logger logger) {
		LogBuffer logBuffer = null;
		Logger currentLogger = logger;

		while (logBuffer == null && currentLogger != null) {
			for (Handler handler : currentLogger.getHandlers()) {
				if (handler instanceof LogBuffer) {
					logBuffer = (LogBuffer) handler;
					break;
				}
			}
			currentLogger = currentLogger.getParent();
		}
		return logBuffer;
	}

	/**
	 * Adds a {@linkplain Handler} to this {@linkplain LogBuffer} instance for {@linkplain LogRecord} consuming.
	 * 

* If the submitted {@linkplain Log} has no {@linkplain LogBuffer} attached the call is ignored. * * @param log the {@linkplain Log} identifying the {@linkplain LogBuffer} to add to. * @param handler the {@linkplain Handler} to add. * @param republishBuffer whether to republish buffered {@linkplain LogRecord}s to the {@linkplain Handler}. * @see #get(Log) * @see #addHandler(Handler, boolean) */ public static void addHandler(Log log, Handler handler, boolean republishBuffer) { addHandler(log.logger(), handler, republishBuffer); } /** * Adds a {@linkplain Handler} to this {@linkplain LogBuffer} instance for {@linkplain LogRecord} consuming. *

* If the submitted {@linkplain Logger} has no {@linkplain LogBuffer} attached the call is ignored. * * @param logger the {@linkplain Logger} identifying the {@linkplain LogBuffer} to add to. * @param handler the {@linkplain Handler} to add. * @param republishBuffer whether to republish buffered {@linkplain LogRecord}s to the {@linkplain Handler}. * @see #get(Logger) * @see #addHandler(Handler, boolean) */ public static void addHandler(Logger logger, Handler handler, boolean republishBuffer) { LogBuffer logBuffer = get(logger); if (logBuffer != null) { logBuffer.addHandler(handler, republishBuffer); } } /** * Adds a {@linkplain Handler} to this {@linkplain LogBuffer} instance for {@linkplain LogRecord} consuming. *

* Any already buffered {@linkplain LogRecord} is sent to the {@linkplain Handler} during this operation. * * @param handler the {@linkplain Handler} to add. * @param republishBuffer whether to republish buffered {@linkplain LogRecord}s to the {@linkplain Handler}. */ public synchronized void addHandler(Handler handler, boolean republishBuffer) { for (LogRecord record : this.buffer) { handler.publish(record); } this.handlers.add(handler); } /** * Gets a previously registered {@linkplain Handler} of a specific type. If the submitted {@linkplain Log} has no * {@linkplain LogBuffer} attached the call is ignored. * * @param the actual type of the requested {@linkplain Handler}. * @param log the {@linkplain Log} identifying the {@linkplain LogBuffer} to search. * @param handlerType the type of {@linkplain Handler} to get. * @return the found {@linkplain Handler} or {@code null}. */ @Nullable public static T getHandler(Log log, Class handlerType) { return getHandler(log.logger(), handlerType); } /** * Gets a previously registered {@linkplain Handler} of a specific type. If the submitted {@linkplain Logger} has no * {@linkplain LogBuffer} attached the call is ignored. * * @param the actual type of the requested {@linkplain Handler}. * @param logger the {@linkplain Logger} identifying the {@linkplain LogBuffer} to search. * @param handlerType the type of {@linkplain Handler} to get. * @return the found {@linkplain Handler} or {@code null}. */ @Nullable public static T getHandler(Logger logger, Class handlerType) { LogBuffer logBuffer = get(logger); return (logBuffer != null ? logBuffer.getHandler(handlerType) : null); } /** * Gets a previously registered {@linkplain Handler} of a specific type. * * @param the actual type of the requested {@linkplain Handler}. * @param handlerType the type of {@linkplain Handler} to get. * @return the found {@linkplain Handler} or {@code null}. */ @Nullable public synchronized T getHandler(Class handlerType) { @Nullable T found = null; for (Handler handler : this.handlers) { if (handler.getClass().equals(handlerType)) { found = handlerType.cast(handler); break; } } return found; } /** * Removes a previously added {@linkplain Handler} from this {@linkplain LogBuffer} instance. *

* If the submitted {@linkplain Log} has no {@linkplain LogBuffer} attached the call is ignored. * * @param log the {@linkplain Log} identifying the {@linkplain LogBuffer} to remove from. * @param handler the {@linkplain Handler} to remove. * @see #addHandler(Log, Handler, boolean) * @see #get(Log) * @see #removeHandler(Handler) */ public static void removeHandler(Log log, Handler handler) { removeHandler(log.logger(), handler); } /** * Removes a previously added {@linkplain Handler} from this {@linkplain LogBuffer} instance. *

* If the submitted {@linkplain Logger} has no {@linkplain LogBuffer} attached the call is ignored. * * @param logger the {@linkplain Logger} identifying the {@linkplain LogBuffer} to remove from. * @param handler the {@linkplain Handler} to remove. * @see #addHandler(Logger, Handler, boolean) * @see #get(Logger) * @see #removeHandler(Handler) */ public static void removeHandler(Logger logger, Handler handler) { LogBuffer logBuffer = get(logger); if (logBuffer != null) { logBuffer.removeHandler(handler); } } /** * Removes a previously added {@linkplain Handler} from this {@linkplain LogBuffer} instance. *

* After removal the {@linkplain Handler} will no longer receive any {@linkplain LogRecord}s. * * @param handler the {@linkplain Handler} to remove. * @see #addHandler(Handler, boolean) */ public synchronized void removeHandler(Handler handler) { this.handlers.remove(handler); } /** * Exports the buffered {@linkplain LogRecord}s to a {@linkplain File}. *

* If the submitted {@linkplain Log} has no {@linkplain LogBuffer} attached the call is ignored. * * @param log the {@linkplain Log} identifying the {@linkplain LogBuffer} to export from. * @param file the {@linkplain File} to export to. * @param append whether to append ({@code true}) in case of an existing file or not ({@code false}). * @throws IOException if an I/O error occurs during export. */ public static void exportTo(Log log, File file, boolean append) throws IOException { exportTo(log.logger(), file, append); } /** * Exports the buffered {@linkplain LogRecord}s to a {@linkplain File}. *

* If the submitted {@linkplain Logger} has no {@linkplain LogBuffer} attached the call is ignored. * * @param logger the {@linkplain Logger} identifying the {@linkplain LogBuffer} to export from. * @param file the {@linkplain File} to export to. * @param append whether to append ({@code true}) in case of an existing file or not ({@code false}). * @throws IOException if an I/O error occurs during export. */ public static void exportTo(Logger logger, File file, boolean append) throws IOException { LogBuffer logBuffer = get(logger); if (logBuffer != null) { logBuffer.exportTo(file, append); } } /** * Exports the buffered {@linkplain LogRecord}s to a {@linkplain File}. * * @param file the {@linkplain File} to export to. * @param append whether to append ({@code true}) in case of an existing file or not ({@code false}). * @throws IOException if an I/O error occurs during export. */ public synchronized void exportTo(File file, boolean append) throws IOException { try (Writer writer = new FileWriter(file, append)) { LogLineFormatter formatter = new LogLineFormatter(); for (LogRecord record : this.buffer) { writer.write(formatter.format(record)); } } } /** * Performs a flush operation on this {@linkplain LogBuffer} instance. *

* The flush request is forwarded to any currently registered {@linkplain Handler} and any buffered * {@linkplain LogRecord} will be discarded. If the submitted {@linkplain Logger} has no {@linkplain LogBuffer} * attached the call is ignored. * * @param log The {@linkplain Log} identifying the {@linkplain LogBuffer} to flush. * @see #get(Log) */ public static void flush(Log log) { flush(log.logger()); } /** * Performs a flush operation on this {@linkplain LogBuffer} instance. *

* The flush request is forwarded to any currently registered {@linkplain Handler} and any buffered * {@linkplain LogRecord} will be discarded. If the submitted {@linkplain Logger} has no {@linkplain LogBuffer} * attached the call is ignored. * * @param logger the {@linkplain Logger} identifying the {@linkplain LogBuffer} to flush. * @see #get(Logger) */ public static void flush(Logger logger) { LogBuffer logBuffer = get(logger); if (logBuffer != null) { logBuffer.flush(); } } @Override public void publish(@Nullable LogRecord record) { if (record != null && isLoggable(record) && this.locked.compareAndSet(false, true)) { try { synchronized (this) { while (this.buffer.size() >= this.limit) { this.buffer.remove(); } this.buffer.add(record); this.handlers.forEach(handler -> handler.publish(record)); } } finally { this.locked.set(false); } } } @Override public synchronized void flush() { this.handlers.forEach(Handler::flush); this.buffer.clear(); } @Override public synchronized void close() { this.handlers.forEach(Handler::close); this.handlers.clear(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy