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

de.carne.boot.logging.LogRecorder 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.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import org.eclipse.jdt.annotation.Nullable;

/**
 * This class is used to record {@linkplain LogRecord}s in a selective manner (e.g. to display detailed results of long
 * running operations).
 */
public final class LogRecorder {

	private final List> includeRecords = new ArrayList<>();
	private final List> excludeRecords = new ArrayList<>();

	private final List loggers = new ArrayList<>();

	/**
	 * Construct {@linkplain LogRecorder}.
	 *
	 * @param level The {@linkplain Level} to use for {@linkplain LogRecord} filtering (all {@linkplain LogRecord}s
	 * below this level are ignored).
	 */
	public LogRecorder(Level level) {
		excludeRecord(record -> record.getLevel().intValue() < level.intValue());
	}

	/**
	 * Add an include {@linkplain Predicate} to the recorder.
	 * 

* To be recorded an issued {@linkplain LogRecord} must match at least one of the include {@linkplain Predicate}s. * * @param include The include {@linkplain Predicate} to apply. * @return The updated {@linkplain LogRecorder}. */ public LogRecorder includeRecord(Predicate include) { this.includeRecords.add(include); return this; } /** * Add an exclude {@linkplain Predicate} to the recorder. *

* To be recorded an issued {@linkplain LogRecord} must not match any of the exclude {@linkplain Predicate}s. * * @param exclude The exclude {@linkplain Predicate} to apply. * @return The updated {@linkplain LogRecorder}. */ public LogRecorder excludeRecord(Predicate exclude) { this.excludeRecords.add(exclude); return this; } /** * Add a {@linkplain Log} instance to the {@linkplain LogRecorder} for recording. * * @param log The {@linkplain Log} instance to add to the recording. * @return The updated {@linkplain LogRecorder}. */ public LogRecorder addLog(Log log) { return addLogger(log.logger()); } /** * Add a {@linkplain Logger} instance to the {@linkplain LogRecorder} for recording. * * @param logger The {@linkplain Logger} instance to add to the recording. * @return The updated {@linkplain LogRecorder}. */ public LogRecorder addLogger(Logger logger) { this.loggers.add(logger); return this; } /** * Start the recording {@linkplain Session}. *

* After this method has been invoked the {@linkplain LogRecorder} collects all {@linkplain LogRecord}s that *

    *
  • issued via one of the added loggers
  • *
  • match the setup filtering
  • *
* until the {@linkplain Session}'s {@linkplain Session#close()} method is called. * * @param currentThreadOnly Whether to include only {@linkplain LogRecord}s issued by the current * {@linkplain Thread}. * @return The started {@linkplain Session}. */ public Session start(boolean currentThreadOnly) { return new Session(currentThreadOnly); } void startSession(Session session) { this.loggers.stream().forEach(logger -> logger.addHandler(session)); } void stopSession(Session session) { this.loggers.stream().forEach(logger -> logger.removeHandler(session)); } boolean testRecord(LogRecord record) { return this.excludeRecords.stream().noneMatch(exclude -> exclude.test(record)) && (this.includeRecords.isEmpty() || this.includeRecords.stream().anyMatch(include -> include.test(record))); } /** * This class represents a running recording session. * * @see LogRecorder#start(boolean) * @see #close() */ public class Session extends Handler implements AutoCloseable { private final List> includeThreads = new ArrayList<>(); private final List> excludeThreads = new ArrayList<>(); private final Queue buffer = new ConcurrentLinkedQueue<>(); private final AtomicBoolean locked = new AtomicBoolean(); Session(boolean currentThreadOnly) { if (currentThreadOnly) { Thread currentThread = Thread.currentThread(); excludeThread(thread -> !thread.equals(currentThread)); } startSession(this); } /** * Add an include {@linkplain Predicate} to the session. *

* To be recorded an issued {@linkplain LogRecord}'s thread must match at least one of the include * {@linkplain Predicate}s. * * @param include The include {@linkplain Predicate} to apply. * @return The updated {@linkplain Session}. */ public Session includeThread(Predicate include) { this.includeThreads.add(include); return this; } /** * Add an exclude {@linkplain Predicate} to the {@linkplain Session}. *

* To be recorded an issued {@linkplain LogRecord}'s thread must not match any of the exclude * {@linkplain Predicate}s. * * @param exclude The exclude {@linkplain Predicate} to apply. * @return The updated {@linkplain Session}. */ public Session excludeThread(Predicate exclude) { this.excludeThreads.add(exclude); return this; } /** * Get the {@linkplain LogRecord}s that have been recorded by this {@linkplain Session} so far. * * @return The recorded {@linkplain LogRecord}s. */ public Collection getRecords() { return Collections.unmodifiableCollection(this.buffer); } private boolean testThread(Thread thread) { return this.excludeThreads.stream().noneMatch(exclude -> exclude.test(thread)) && (this.includeThreads.isEmpty() || this.includeThreads.stream().anyMatch(include -> include.test(thread))); } @Override public void publish(@Nullable LogRecord record) { if (record != null && this.locked.compareAndSet(false, true) && testThread(Thread.currentThread()) && testRecord(record)) { this.buffer.add(record); this.locked.set(false); } } @Override public void flush() { // Nothing to do } @Override public void close() { stopSession(this); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy