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

org.jtrim2.logs.LogCollector Maven / Gradle / Ivy

There is a newer version: 2.0.7
Show newest version
package org.jtrim2.logs;

import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.jtrim2.collections.CollectionsEx;

/**
 * Defines a class which is able to collect logs written to a
 * {@code java.util.logging.Logger}. This class is generally useful when testing
 * if a particular code emitted log message or not.
 * 

* To instantiate this class call the {@link #startCollecting(String)} factory * method. *

* Warning: Instances of this class need to be closed in order to * unregister log handlers. Failing to call the {@link #close() close} method * will cause a memory leak. * *

Thread safety

* Methods of this class are safe to be used by multiple threads concurrently. * *

Synchronization transparency

* Methods of this class are synchronization transparent unless otherwise * noted. * * @see #startCollecting(String) */ public final class LogCollector implements AutoCloseable { /** * Creats a new {@code LogCollector} which immediately starts listening * for logs passed to the specified logger. *

* This method will add a log handler to the specified logger and to * remove this handler you have to call the {@link #close() close} method * of the returned {@code LogCollector}. * * @param loggerName the name of the logger whose logs are to be collected. * This argument cannot be {@code null}. * @return the {@code LogCollector} which will collect logs passed to the * given logger. This method never returns {@code null}. */ public static LogCollector startCollecting(String loggerName) { return new LogCollector(loggerName); } private static void extractThrowables( Class cl, Throwable[] exceptions, Set result) { for (Throwable ex: exceptions) { if (ex != null) { extractThrowables(cl, new Throwable[]{ex.getCause()}, result); extractThrowables(cl, ex.getSuppressed(), result); if (cl.isAssignableFrom(ex.getClass())) { result.add(ex); } } } } /** * Collects all the throwable extending the given class from the given * {@code Throwable} instances. This method will also find exceptions within * the causes and the suppressed exceptions of the passed exceptions * (recursively). * * @param cl the class of the exceptions to be returned. This method will * also return exceptions with exactly the same type. That is, calling * this method with {@code Throwable.class} will return every possible * exception. This argument cannot be {@code null}. * @param exceptions the exceptions to be searched for the specified type * of exceptions. This array cannot be {@code null} but its elements * can be {@code null}, {@code null} elements will be ignored. * @return the exceptions implementing the given type. The returned array * will not contain the same (reference comparison) exception multiple * times. This method never returns {@code null}. */ public static Throwable[] extractThrowables( Class cl, Throwable... exceptions) { Objects.requireNonNull(cl, "cl"); Objects.requireNonNull(exceptions, "exceptions"); Set result = CollectionsEx.newIdentityHashSet(exceptions.length); extractThrowables(cl, exceptions, result); return result.toArray(new Throwable[result.size()]); } @SuppressWarnings("NonConstantLogger") private final Logger logger; private final CollectorHandler handler; private LogCollector(String loggerName) { Objects.requireNonNull(loggerName, "loggerName"); this.handler = new CollectorHandler(); this.logger = Logger.getLogger(loggerName); this.logger.addHandler(handler); } /** * Returns the number of log records collected. This is effectively * equivalent to {@code getLogs().length} but is more efficient. * * @return the number of log records collected. This method always returns * a value greater than or equal to zero. */ public int getNumberOfLogs() { return handler.getNumberOfLogs(); } /** * Returns the number of log records collected having the specified log * level. The level must have an exact match, so passing * {@code Level.SEVERE} will not count {@code Level.WARNING} logs. * * @param level the level of the logs to be counted. This argument cannot * be {@code null}. * @return the number of log records collected having the specified log * level. This method always returns a value greater than or equal to * zero. */ public int getNumberOfLogs(Level level) { return handler.getNumberOfLogs(level); } /** * Returns all the collected logs. This method returns the log records in * the order they were collected. * * @return all the collected logs. This method never returns {@code null}. */ public LogRecord[] getLogs() { return handler.getLogs(); } /** * Returns all the exceptions attached to the collected log records having * the specified log level. The exceptions are extracted from the * {@code thrown} property of the log records. The level must have an exact * match, so passing {@code Level.SEVERE} will not consider * {@code Level.WARNING} logs. * * @param level the level of the logs to be checked. This argument cannot * be {@code null}. * @return all the exceptions attached to the collected log records having * the specified log level. This method never returns {@code null}. */ public Throwable[] getExceptions(Level level) { return handler.getExceptions(level); } /** * Removes the log handler the {@code LogCollector} uses and stops * collecting logs. Logging after this method call will have no effect on * this {@code LogCollector}. *

* This method is idempotent, so calling it multiple times has no further * effect. */ @Override public void close() { handler.close(); logger.removeHandler(handler); } private static class CollectorHandler extends Handler { private final Lock mainLock; private final List records; private boolean closed; public CollectorHandler() { this.mainLock = new ReentrantLock(); this.records = new LinkedList<>(); this.closed = false; } @Override public void publish(LogRecord record) { mainLock.lock(); try { if (!closed) { records.add(record); } } finally { mainLock.unlock(); } } @Override public void flush() { } public int getNumberOfLogs() { mainLock.lock(); try { return records.size(); } finally { mainLock.unlock(); } } public int getNumberOfLogs(Level level) { Objects.requireNonNull(level, "level"); int result = 0; mainLock.lock(); try { for (LogRecord record: records) { if (level.equals(record.getLevel())) { result++; } } } finally { mainLock.unlock(); } return result; } public LogRecord[] getLogs() { mainLock.lock(); try { return records.toArray(new LogRecord[records.size()]); } finally { mainLock.unlock(); } } public Throwable[] getExceptions(Level level) { Objects.requireNonNull(level, "level"); List errors = new LinkedList<>(); mainLock.lock(); try { for (LogRecord record: records) { if (level.equals(record.getLevel())) { Throwable error = record.getThrown(); if (error != null) { errors.add(error); } } } } finally { mainLock.unlock(); } return errors.toArray(new Throwable[errors.size()]); } @Override public void close() { mainLock.lock(); try { closed = true; } finally { mainLock.unlock(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy