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

io.github.awidesky.guiUtil.LoggerThread Maven / Gradle / Ivy

/*
 * Copyright (c) 2023 Eugene Hong
 *
 * This software is distributed under license. Use of this software
 * implies agreement with all terms and conditions of the accompanying
 * software license.
 * Please refer to LICENSE
 * */

package io.github.awidesky.guiUtil;

import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.function.Consumer;


/**
 * A Thread that manages a set of {@code TaskLogger}s that external log destination is the same.
 * It's purpose is to provide a thread-safe logging mechanism where tasks from multiple threads produce logs,
 * but all of them should saved into single destination({@code OutputStream} connected to file, console, web socket... etc.)
 * 
 * 

Each Thread or task can have their {@code TaskLogger} created by LoggerThread#getLogger or * LoggerThread#getBufferedLogger methods, and queued log tasks are executed in the {@code LoggerThread}. * {@code LoggerThread} constantly checks the queue and executes log tasks. If autoFlush in * {@code LoggerThread#setLogDestination(OutputStream, boolean, Charset)} is {@code true}, backing {@code PrintWriter} * of this logger thread's autoFlush will also be true. * *

{@code LoggerThread#getLogger()} method family will create a normal {@code TaskLogger}; logs will queued every time you call * {@code log} methods. On the other hand, {@code LoggerThread#getBufferedLogger()} method family will create {@code TaskBufferedLogger}; * logs will not queued to {@code LoggerThread} unless {@code TaskBufferedLogger#flush()} is called. * *

{@code LoggerThread} is not a Daemon Thread; since external output destination({@code OutputStream}) is not closed until * {@code LoggerThread#shutdown(int)} is called. {@code LoggerThread#shutdown(int)} must be called before the main application terminates. * */ public class LoggerThread extends Thread { private PrintWriter logTo = null; private LinkedBlockingQueue> loggerQueue = new LinkedBlockingQueue<>(); private Set children = Collections.synchronizedSet(new HashSet()); public volatile boolean isStop = false; private boolean verbose = false; private DateFormat datePrefix = null; /** Creates a new logger thread. */ public LoggerThread() { super("LoggerThread"); } /** * Set destination of log to given {@code OutputStream}. Log Strings will be encoded in {@code Charset#defaultCharset()}. * Auto flush is set to {@code true}. * */ public void setLogDestination(OutputStream os) throws IllegalArgumentException { setLogDestination(os, true, Charset.defaultCharset()); } /** * Set destination of log to given {@code OutputStream}. Log Strings will be encoded in given {@code Charset}. * Auto flush is set to {@code true}. * */ public void setLogDestination(OutputStream os, Charset cs) throws IllegalArgumentException { setLogDestination(os, true, cs); } /** * Set destination of log to given {@code OutputStream}. Log Strings will be encoded in {@code Charset#defaultCharset()}. * * @param autoFlush A boolean; if true, the logs will flushed to the output buffer when printed. * */ public void setLogDestination(OutputStream os, boolean autoFlush) throws IllegalArgumentException { setLogDestination(os, autoFlush, Charset.defaultCharset()); } /** * Set destination of log to given {@code OutputStream}. Log Strings will be encoded in given {@code Charset}. * * @param autoFlush A boolean; if true, the logs will flushed to the output buffer when printed. * */ public void setLogDestination(OutputStream os, boolean autoFlush, Charset cs) throws IllegalArgumentException { if(logTo != null) { throw new IllegalArgumentException("log output stream is already set, cannot modify!"); } logTo = new PrintWriter(new OutputStreamWriter(os, cs), autoFlush); } /** * Returns a new {@code TaskLogger} with no initial prefix. * Returned {@code TaskLogger}'s verbosity is same as value of {@code LoggerThread#isVerbose()} * */ public TaskLogger getLogger() { return getLogger(verbose, null); } /** * Returns a new {@code TaskLogger} with given prefix. * Returned {@code TaskLogger}'s verbosity is same as value of {@code LoggerThread#isVerbose()} * */ public TaskLogger getLogger(String prefix) { return getLogger(verbose, prefix); } /** * Returns a new {@code TaskLogger} with given prefix and verbosity. * */ public TaskLogger getLogger(boolean verbose, String prefix) { TaskLogger newLogger = new TaskLogger(verbose, prefix) { @Override public void queueLogTask(Consumer logTask) { try { loggerQueue.put(logTask); } catch (InterruptedException e) { if (!isStop) log(e); } } @Override public boolean runLogTask(Consumer logTask) { return loggerQueue.offer(logTask); } @Override public void close() { children.remove(this); } }; newLogger.setDatePrefix(datePrefix); children.add(newLogger); return newLogger; } /** * Returns a new {@code TaskBufferedLogger} with no initial prefix. * Returned {@code TaskBufferedLogger}'s verbosity is same as value of {@code LoggerThread#isVerbose()} * */ public TaskBufferedLogger getBufferedLogger() { return getBufferedLogger(verbose, null); } /** * Returns a new {@code TaskBufferedLogger} with given prefix. * Returned {@code TaskBufferedLogger}'s verbosity is same as value of {@code LoggerThread#isVerbose()} * */ public TaskBufferedLogger getBufferedLogger(String prefix) { return getBufferedLogger(verbose, prefix); } /** * Returns a new {@code TaskBufferedLogger} with given prefix and verbosity. * */ public TaskBufferedLogger getBufferedLogger(boolean verbose, String prefix) { TaskBufferedLogger newLogger = new TaskBufferedLogger(verbose, prefix) { @Override public void queueLogTask(Consumer logTask) { try { loggerQueue.put(logTask); } catch (InterruptedException e) { loggerQueue.offer(logTask); if (!isStop) log(e); } } @Override public void close() { flush(); children.remove(this); } }; newLogger.setDatePrefix(datePrefix); children.add(newLogger); return newLogger; } @Override public void run() { logTo.println("LoggerThread started at [" + new SimpleDateFormat("yyyy/MM/dd-kk:mm:ss").format(new Date()) + "]"); while (true) { if (loggerQueue.isEmpty() && isStop) { break; } try { loggerQueue.take().accept(logTo); } catch (InterruptedException e) { logTo.println("LoggerThread Interrupted! : " + e.getMessage()); logTo.println("Closing LoggerThread.."); break; } } logTo.close(); } /** * Set verbosity of this {@code LoggerThread}. * Verbosity of child {@code TaskLogger} generated after this call will set to {@code verbose}. * Children {@code TaskLogger}s who created previously is not effected. * */ public void setVerbose(boolean verbose) { this.verbose = verbose; } /** * Set verbosity of this {@code LoggerThread} and all existing children {@code TaskLogger}s. * Verbosity of child {@code TaskLogger} generated after this call will set to {@code verbose}. * Children {@code TaskLogger}s who created previously is also effected. * */ public void setVerboseAllChildren(boolean verbose) { this.verbose = verbose; children.stream().forEach(l -> l.setVerbose(verbose)); } /** * Get verbosity of this {@code LoggerThread}. * It means verbosity of child {@code TaskLogger} generated in this point will have. * */ public boolean isVerbose() { return verbose; } /** * Set date prefix of this {@code LoggerThread}. * date prefix of child {@code TaskLogger} generated after this call will set to {@code datePrefix}. * Children {@code TaskLogger}s who created previously is not effected. * */ public void setDatePrefix(DateFormat datePrefix) { this.datePrefix = datePrefix; } /** * Set date prefix of this {@code LoggerThread} and all existing children {@code TaskLogger}s. * date prefix of child {@code TaskLogger} generated after this call will set to {@code datePrefix}. * Children {@code TaskLogger}s who created previously is also effected. * */ public void setDatePrefixAllChildren(DateFormat datePrefix) { this.datePrefix = datePrefix; children.stream().forEach(l -> l.setDatePrefix(datePrefix)); } /** * Waits at most timeOut milliseconds for this LoggerThread to die. * A timeout of {@code 0} means to wait forever. * If the time has past and this LoggerThread is not dead, interrupt. * */ public void shutdown(int timeOut) { isStop = true; new HashSet<>(children).stream().forEach(TaskLogger::close); try { this.join(timeOut); } catch (InterruptedException e) { logTo.println("Failed to join logger thread!"); e.printStackTrace(logTo); } this.interrupt(); if(logTo != null) logTo.close(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy