This is basically a logger which I use/wrote. It has convenient features like including one-line stack traces with print statements, making the text easier to find, and piping all output to either standard out or standard error to make it so that no print statements will be out of order.
This is part of my LogAppTester. I am looking for any reviews or suggestions.
package Utilities;
import java.io.*
import java.text.*
import java.util.*
/**
* Use this for various testing/debugging purposes including multi-threaded
* print statements with built in stack trace, assertions that stop the entire
* application rather than just the current thread, and application termination
* with a full stack trace for terminal error conditions. Can also check and
* respond to background events.
*
* @author johnmichaelreed2
*/
public class AppTester {
// <editor-fold defaultstate="collapsed" desc="Enums">
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="Vars, Getters, and Setters">
/**
* The system independent line separator, shortened to two letters for
* convenience.
*/
public static final String ls = System.getProperty("line.separator");
static {
AppTester.check(ls != null);
}
/**
* The number of important rows in a stack trace.
*/
private static int numberOfRowsIDisplayInStackTraces_ = 6;
public static int getNumberOfRowsIDisplayInStackTraces_() {
return numberOfRowsIDisplayInStackTraces_;
}
public static void setNumberOfRowsIDisplayInStackTraces_(int aNumberOfRowsIDisplayInStackTraces_) {
AppTester.check(aNumberOfRowsIDisplayInStackTraces_ >= 0, "You can't display a negative number of rows in a stack trace.");
numberOfRowsIDisplayInStackTraces_ = aNumberOfRowsIDisplayInStackTraces_;
}
/**
* When this variable is true, the log file will be printed to. If it is false,
* the log file will not be used even if it can be written to.
*/
private static boolean printToLogFile_ = true;
/**
* When this variable is true, the terminal will be printed to. If it is false,
* the terminal will not be printed to.
*/
private static boolean printToTerminal_ = true;
/**
* All messages that are at this debug level or higher are printed. By
* default set to {@link #NORMAL}
*/
private static Rank myRank_ = NORMAL;
/**
* Specified where print stream messages should go to. By default set to
* {@link #EITHER_STD_OUT_OR_STD_ERROR}
*/
private static DefaultPrintStream myTargetPrintStream_ = DefaultPrintStream.ONLY_STANDARD_OUT;
/**
* This thread polls for a variety of background events, where each
* background events is an materialization of the
* {@link Utilities.BackgroundEvent_Interface} interface. Its name is
* "Event_Checker". Its initialization is deferred until its first use.
*/
private static ScheduledExecutorService myScheduler_
= null; //Executors.newSingleThreadScheduledExecutor();
/**
* This represents the main thread. If this thread is dead,
* {@link #myScheduler_} thread must die as well, otherwise, the application
* won't exit at the end of the main method.
*/
private static final Thread myMainThread_;
static {
myMainThread_ = Thread.currentThread();
}
public static Thread getMyMainThread() {
return myMainThread_;
}
/**
* @return the variable {@link #printToLogFile_}
*/
public static boolean getGenerateLogFiles() {
return printToLogFile_;
}
/**
* Determines whether or not the log file should be printed to.
* @param useLogFileOrNot true for printing to the log file and false
* for no printing to the log file.
*/
public static void setPrintToLogFile(boolean useLogFileOrNot) {
printToLogFile_ = useLogFileOrNot;
}
/**
* @return the variable {@link #printToTerminal_}
*/
public static boolean getPrintToTerminal() {
return printToTerminal_;
}
/**
* Determines whether of not the terminal should be printed to.
* @param toPrint true for printing to the terminal or false for no printing
* to the terminal.
*/
public static void setPrintToTerminal(boolean toPrint) {
printToTerminal_ = toPrint;
}
/**
* @return the currently in use {@link Utilities.AppTester.Rank}
*/
public static Rank getMyDebugLevel() {
return myRank_;
}
/**
* @param level all messages that are at this debug level or higher are
* printed. Messages that are below (less important than) this debug level
* are not printed.
*/
public static void setMyDebugLevel(Rank level) {
myRank_ = level;
}
/**
* @return the currently in use
* {@link Utilities.AppTester.DefaultPrintStream}
*/
public static DefaultPrintStream getMyDefaultPrintStream() {
return myTargetPrintStream_;
}
/**
* All terminal error messages will go through this
* {@link Utilities.AppTester.DefaultPrintStream}
*
* @param streamType
*/
public static void setMyDefaultPrintStream(DefaultPrintStream streamType) {
myTargetPrintStream_ = streamType;
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="Delayed Initialization Vars">
/**
* The name of the folder that holds all the log files, or null if there are
* no log files.
*/
private static final String myLogFolderNameOrNull_;// = "Log_Files";
static {
// initialize the above variable in the static block.
final String intendedFolderName = "Log_Files";
final File logFilesFolder = new File(FileFinder.WORKING_DIRECTORY
+ FileFinder.FILE_SEPARATOR + intendedFolderName);
if (!logFilesFolder.exists()) {
// No folder exists, so the folder needs to be created.
boolean madeFolder = false;
try {
madeFolder = logFilesFolder.mkdir();
} catch (SecurityException se) {
// madeFolder remains false.
}
if (madeFolder) {
//Tester.print("A log file folder was created.", NORMAL);
myLogFolderNameOrNull_ = intendedFolderName;
} else {
//Tester.print("The log file folder could not be created.", NORMAL);
myLogFolderNameOrNull_ = null;
}
} else {
// The folder already exists.
//Tester.print("The log file folder already exists.", NORMAL);
myLogFolderNameOrNull_ = intendedFolderName;
}
}
/**
* The name of the log file if log file creation was successful, or null if
* no log file was created. Perform null checks before use.
*/
private static final String myLogFileNameOrNull_;
/**
* @return Either the name of the log file or empty string if the log file
* was not successfully created.
*/
public static String getLogFileNameOrEmptyString() {
if(myLogFileNameOrNull_ != null) {
return myLogFileNameOrNull_;
} else {
return "";
}
}
/**
* If initialization was successful, this variable will be non-null,
* otherwise it will remain null. Perform null checks before use.
*/
private static FileOutputStream myLogFileOutputStreamOrNull_ = null;
/**
* If initialization was successful, this variable will be non-null,
* otherwise it will remain null. Perform null checks before use.
*/
private static OutputStreamWriter myLogOutputStreamWriterOrNull_ = null;
/**
* If initialization was successful, this variable will be non-null,
* otherwise it will by null. Perform null checks before use.
*/
private static final BufferedWriter myLogBufferedWriterOrNull_; // this does the actual writing
static {
// initialize myLogFileNameOrNull_ and the writers.
if (!(myLogFolderNameOrNull_ == null)) {
final DateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd___HH:mm:ss");
final Calendar cal = Calendar.getInstance();
final String dateTimeForLogFile = dateFormat.format(cal.getTime()); /* 2014_08_06___16:00:22 */
final String expectedFileName = dateTimeForLogFile + ".txt";
final String fullPath = FileFinder.WORKING_DIRECTORY
+ FileFinder.FILE_SEPARATOR + myLogFolderNameOrNull_
+ FileFinder.FILE_SEPARATOR + expectedFileName;
final File logFile = new File(fullPath);
boolean wasFileCreated = false;
try {
wasFileCreated = logFile.createNewFile();
} catch (IOException ioe) {
// wasFileCreated remains false.
} catch (SecurityException se) {
// wasFileCreated remains false.
}
boolean success = false;
if (wasFileCreated == true) {
final String wasFileFound = FileFinder.tryFindPathToFileWhoseNameIs(expectedFileName);
AppTester.check(wasFileFound != null, "I made the file so I should be able to find it.");
//Tester.printEx("Managed to create log file.", AppTester.IMPORTANT);
try {
myLogFileOutputStreamOrNull_ = new FileOutputStream(logFile, true); // File not found exception???
myLogOutputStreamWriterOrNull_ = new OutputStreamWriter(myLogFileOutputStreamOrNull_, "utf-8"); // unsupported encoding exception
} catch (FileNotFoundException fnfe) {
AppTester.killApplication("It's impossible for a newly created file to not be found.", fnfe);
} catch (UnsupportedEncodingException uee) {
AppTester.killApplication("It's impossible for UTF-8 to be an unsupported text format.", uee);
} catch (Exception e) {
//just ignore it, the things we are trying to initialize will be null.
myLogFileOutputStreamOrNull_ = null;
myLogOutputStreamWriterOrNull_ = null;
}
if (myLogOutputStreamWriterOrNull_ != null) {
myLogBufferedWriterOrNull_ = new BufferedWriter(myLogOutputStreamWriterOrNull_);
try {
myLogBufferedWriterOrNull_.write("Starting log file" + ls);
success = true;
} catch (IOException ioe) {
// Just ignore it - don't kill the thread.
}
} else {
myLogBufferedWriterOrNull_ = null;
}
} else {
//Tester.printEx("Failed to create log file.", AppTester.IMPORTANT);
myLogFileOutputStreamOrNull_ = null;
myLogOutputStreamWriterOrNull_ = null;
myLogBufferedWriterOrNull_ = null;
}
if (success == true) {
myLogFileNameOrNull_ = expectedFileName;
} else {
myLogFileNameOrNull_ = null;
}
} else {
// No log folder, so definetely no log files.
AppTester.printerr("Could not create log file because log folder does not exist.");
myLogFileNameOrNull_ = null;
myLogBufferedWriterOrNull_ = null;
}
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="Functions">
/**
* Closes the AppTester by killing any background threads it may be running
* and closing its log file writer. Must be called at or before termination.
*/
public static void close() {
try {
if (myScheduler_ != null) {
myScheduler_.shutdownNow();
printlnToReadout("\n" + "The scheduler has been shut down", ReadoutCondition.NON_ERROR, UNIMPORTANT);
}
} catch (Exception e) {
// ignore it.
//e.printStackTraceNoLeadingLineNumber();
}
try {
if (myLogBufferedWriterOrNull_ != null) {
printlnToReadout("\n" + "The log file is being shut down.", ReadoutCondition.NON_ERROR, NORMAL);
myLogBufferedWriterOrNull_.close();
}
} catch (Exception ioe) {
// ioe.printStackTraceNoLeadingLineNumber();
// ignore the error. myLogBufferedWriterOrNull_ is already closed.
}
}
/**
* Tries to write text to log file. Replaces all "\n" newline
* characters that may have been sent to the terminal with OS specific
* end of line characters before printing to the text file.
*
* @param text the text to be written to the log file.
* @return false if no text is written or true if text is successfully
* written.
*/
private static boolean tryWritingSomethingToLogFileNoNewline(String text) {
if (myLogBufferedWriterOrNull_ == null) {
return false;
} else {
try {
String updatedText = text.replaceAll("\n", ls);
myLogBufferedWriterOrNull_.write(updatedText);
myLogBufferedWriterOrNull_.flush();
return true;
} catch (IOException ioe) {
// means the writer was closed
return false;
}
}
}
public static void printlnToReadout(final String message, ReadoutCondition condition, Rank severity) {
printToReadout((message + "\n"), condition, severity);
}
/**
* Prints to log/terminal without newline.
* @param message the message to be printed
* @param condition whether the message is an error or non-error message
*/
public static synchronized void printToReadout(final String message, ReadoutCondition condition, Rank severity) {
// Logging happens regardless of severity.
if (printToLogFile_) {
// log stuff
if (!(AppTester.myLogFileNameOrNull_ == null)) {
final String logFilesPathString = FileFinder.tryFindPathToFileWhoseNameIs(myLogFileNameOrNull_);
AppTester.check(logFilesPathString != null, "The log file name is non-null, so the log file must exist.");
boolean success = AppTester.
tryWritingSomethingToLogFileNoNewline(message.replaceAll("\n", ls));
// this success is being silently ignored if it doesn't write to log file,
// I'm not doing anything about it.
} else {
// Don't bother, it won't work.
}
}
if(! printToTerminal_) {
// Do not print anything.
return;
} else if (myRank_.getImportance() > severity.getImportance()) {
// This message is not important enough to be printed.
return; // return without printing to terminal.
} else {
// This message is important enough to be printed to terminal.
if (myTargetPrintStream_ == ONLY_STANDARD_OUT) {
System.out.print(message);
} else if (myTargetPrintStream_ == ONLY_STANDARD_ERROR) {
System.err.print(message);
} else {
// myTargetPrintStream_ == EITHER_STD_OUT_OR_STD_ERROR
if (condition == ReadoutCondition.ERROR) {
System.err.print(message);
} else if (condition == ReadoutCondition.NON_ERROR) {
System.out.print(message);
} else {
AppTester.killApplication("This condition is logically impossible");
}
}
}
}
/**
* Kills the entire application and leaves a stack trace.
*
* @param message - message to print before the application terminates.
*/
public static void killApplication(String message) {
AppTester.check(false, message, 3);
}
/**
* Kills the application, prints a message, and also print the throwable who
* is responsible for the crash. Prints the entire throwable.
*
* @param message message to print before the application terminates.
* @param t throwable whose stack trace is to be included in the
* termination.
*/
public static void killApplication(String message, Throwable t) {
StackTraceElement[] ste = t.getStackTrace();
String concatenation = message + "\n";
concatenation += t.toString() + "\n";
for (int i = 0; i < ste.length; ++i) {
concatenation += (ste[i].toString() + "\n");
}
AppTester.killApplicationNoStackTrace(concatenation);
}
/**
* Alternate form of {@link #killApplication(java.lang.String, java.lang.Throwable) }.
* Prints the entire stack trace.
*/
public static void killApplication(Throwable t, String messsage) {
killApplication(messsage, t);
}
/**
* Kills the entire application and prints out the stack trace elements of
* the throwable responsible
*
* @param t the throwable responsible.
*/
public static void killApplication(Throwable t) {
killApplication("", t);
}
/**
* Kills the entire application without leaving a stack trace.
*
* @param message - message to print before the application terminates.
*/
public static void killApplicationNoStackTrace(String message) {
printlnToReadout("\n" + message, ReadoutCondition.ERROR, Rank.IMPORTANT);
close();
System.exit(-1);
}
/**
* Checks to see if an assertion is true. Prints stack trace and crashes the
* program if not true. USE THIS INSTEAD OF REGULAR "assert" STATEMENTS. For
* information on how to use an assert statement, see:
* <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/language/assert.html">Programming
* With Assertions</a>
*
* @param assertion - assertion to be checked
*/
public static void check(boolean assertion) {
check(assertion, "Empty_Assertion", 3); // nomally it would be 2 but the indirections bumps it up to 3.
}
/**
* Checks to see if an assertion is true and prints an error message if
* false. Also prints a stack trace and crashes the program if false. For
* information on how to use an assert statement, see:
* <a href="http://docs.oracle.com/javase/7/docs/technotes/guides/language/assert.html">Programming
* With Assertions</a>
*
* @param assertion - assertion to be checked
* @param message - error message to print
*/
public static void check(final boolean assertion, final String message) {
check(assertion, message, 3); // nomally it would be 2 but the indirections bumps it up to 3.
}
private static void check(final boolean assertion, final String message, int firstRowOfStackTrace) {
if (!assertion) {
String toBePrinted = "\n" + "Assertion failed in Thread: \""
+ Thread.currentThread().getName() + "\"" + "\n" + message;
// length of stack trace is 5 (thread called by check called by check called by handle called by main)
//printlnToReadout(toBePrinted, ReadoutCondition.ERROR, Rank.IMPORTANT); // print rows 3 & 4
final StackTraceElement[] stackTraceArray = Thread.currentThread().getStackTrace();
// This should print the stack trace from firstRowOfStackTrace down.
printStackTraceNoLeadingLineNumberWithLeadingMessageAndNewline(toBePrinted, stackTraceArray, firstRowOfStackTrace);
close();
System.exit(-1);
}
}
private static void print(String message, Rank severityLevel, int stackTraceStart) {
final String thread_name = Thread.currentThread().getName();
final String location_of_print_statement = Thread.currentThread().getStackTrace()[stackTraceStart].toString();
printlnToReadout("\n" + "Thread \"" + thread_name + "\": "
+ location_of_print_statement + "\n" + message, ReadoutCondition.NON_ERROR, severityLevel);
}
/**
* Prints the name of the current thread and where the print statement comes
* from to standard output or to the stream specified by
* {@link #myTargetPrintStream_}. The {@link #myTargetPrintStream_} variable
* acts as a global override for the preferred output stream of the
* application.
*
* @param message - message to be printed
*/
private static void print(String message, Rank severityLevel) {
print(message, severityLevel, 3);
}
/**
* Prints an unimportant non-error message.
* Short for "unimportant print".
*/
public static void uPrint(String message) {
print(message, UNIMPORTANT, 3);
}
/**
* Same as {@link #print(java.lang.String, Utilities.AppTester.Rank) },
* but with the {@link #NORMAL} severity level specified by default.
*
* @param message the message to be printed as if it were a regular
* non-error message.
*/
public static void print(String message) {
print(message, NORMAL, 3);
}
/**
* Prints an important non-error message.
* Short for "important print."
*/
public static void iPrint(String message) {
print(message, IMPORTANT, 3);
}
/**
* Prints an error message to terminal and/or log file including
* accompanying stack trace element.
*
* @param message The error message
* @param severityLevel How important the message is
* @param stackTraceStart The position of the current line on the stack
* trace
*/
private static void printerr(String message, Rank severityLevel, int stackTraceStart) {
final String thread_name = Thread.currentThread().getName();
final String location_of_print_statement = Thread.currentThread().getStackTrace()[stackTraceStart].toString();
printlnToReadout("\n" + "Thread \"" + thread_name + "\": "
+ location_of_print_statement + "\n" + message, ReadoutCondition.ERROR, severityLevel);
}
/**
* Prints the name of the current thread and where the print statement comes
* from to the error output stream or to the stream specified by
* {@link #myTargetPrintStream_}. The {@link #myTargetPrintStream_} variable
* acts as a global override for the preferred output stream of the
* application.
*
* @param message - message to be printed
*/
private static void printerr(String message, Rank severityLevel) {
printerr(message, severityLevel, 3);
}
/**
* Short for print error not important. Prints an unimportant error.
* Short for "unimportant print error".
*/
public static void uPrinterr(String message) {
printerr(message, UNIMPORTANT, 3);
}
/**
* Same as {@link #printerr(java.lang.String, Utilities.AppTester.Rank)
* }, but with the {@link #NORMAL} severity level specified by default.
*
* @param message the message to be printed as if it were an error.
*/
public static void printerr(String message) {
printerr(message, NORMAL, 3);
}
/**
* Short for print error important. Prints an important error.
* Short for "important print error".
*/
public static void iPrinterr(String message) {
printerr(message, IMPORTANT, 3);
}
/**
* Prints out a throwable as if it were and error and logs it appropriately.
* Short for "print exception".
*
* @param t Exception or error to be printed as an error.
*/
public static void printEx(Throwable t) {
printThrowableNoLeadingLineNumber(t);
}
/**
* Prints out a message and a stack trace caused by a throwable as an error.
* Message cannot be an empty string or null.
*
* @param message message to be printed
* @param t throwable that caused the message to be printed
*/
public static void printEx(String message, Throwable t) {
printThrowableNoLeadingLineNumber(message, t);
}
/**
* Same as {@link #printEx(java.lang.String, java.lang.Throwable) }, but
* with a leading message.
*/
public static void printEx(Throwable t, String message) {
printThrowableNoLeadingLineNumber(message, t);
}
/**
* Prints a stack trace starting from firstRow preceded by a leading message and a newline.
* @param message the message which appears just before the stack trace.
* @param stackTrace The stack trace to be printed.
* @param firstRow The first row of the stack trace, between zero and length.
*/
private static void printStackTraceNoLeadingLineNumberWithLeadingMessageAndNewline(final String message, StackTraceElement[] stackTrace, int firstRow) {
// Truncated for brevity. Basically prints a message and the stack trace, making the first n lines important and the rest uninimportant.
}
private static void printThrowableNoLeadingLineNumber(String nonNullMessage, Throwable t) {
AppTester.check(nonNullMessage != null, "The message is not supposed to be null");
String toPrint = "\n" + nonNullMessage + "\n" + t.toString();
// printlnToReadout(toPrint, ReadoutCondition.ERROR, IMPORTANT);
printStackTraceNoLeadingLineNumberWithLeadingMessageAndNewline(toPrint, t.getStackTrace(), 0);
printlnToReadout(Thread.currentThread().getStackTrace()[3].toString() + "\n", ReadoutCondition.ERROR, IMPORTANT);
}
private static void printThrowableNoLeadingLineNumber(Throwable t) {
String toPrint = "\n" + t.toString();
//printlnToReadout(toPrint, ReadoutCondition.ERROR, IMPORTANT);
printStackTraceNoLeadingLineNumberWithLeadingMessageAndNewline(toPrint, t.getStackTrace(), 0);
printlnToReadout(Thread.currentThread().getStackTrace()[3].toString() + "\n", ReadoutCondition.ERROR, IMPORTANT);
}
// </editor-fold>
}