package org.ggp.base.util.logging; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Date; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; import org.ggp.base.util.match.Match; /** * GamerLogger is a customized logger designed for long-running game players. * Logs are written to directories on a per-game basis. Each logfile represents * a single logical component of the game playing program, identified whenever * the logger is called. * * TODO: More details about specific use examples. * * @author Sam Schreiber */ public class GamerLogger { // Public Interface public static void emitToConsole(String s) { // TODO: fix this hack! if(!writeLogsToFile && !suppressLoggerOutput) { System.out.print(s); } } public static void stopFileLogging() { log("Logger", "Stopped logging to files at: " + new Date()); log("Logger", "LOG SEALED"); writeLogsToFile = false; } public static void setSpilloverLogfile(String spilloverFilename) { spilloverLogfile = spilloverFilename; } public static void startFileLogging(Match m, String roleName) { writeLogsToFile = true; myDirectory = "logs/" + m.getMatchId() + "-" + roleName; new File(myDirectory).mkdirs(); log("Logger", "Started logging to files at: " + new Date()); log("Logger", "Game rules: " + m.getGame().getRules()); log("Logger", "Start clock: " + m.getStartClock()); log("Logger", "Play clock: " + m.getPlayClock()); } public static void setFileToDisplay(String toFile) { filesToDisplay.add(toFile); } public static void setMinimumLevelToDisplay(int nLevel) { minLevelToDisplay = nLevel; } public static void setSuppressLoggerOutput(boolean bSuppress) { suppressLoggerOutput = bSuppress; } public static final int LOG_LEVEL_DATA_DUMP = 0; public static final int LOG_LEVEL_ORDINARY = 3; public static final int LOG_LEVEL_IMPORTANT = 6; public static final int LOG_LEVEL_CRITICAL = 9; public static void logError(String toFile, String message) { logEntry(System.err, toFile, message, LOG_LEVEL_CRITICAL); if(writeLogsToFile) { logEntry(System.err, "Errors", "(in " + toFile + ") " + message, LOG_LEVEL_CRITICAL); } } public static void log(String toFile, String message) { log(toFile, message, LOG_LEVEL_ORDINARY); } public static void log(String toFile, String message, int nLevel) { logEntry(System.out, toFile, message, nLevel); } public static void logStackTrace(String toFile, Exception ex) { StringWriter s = new StringWriter(); ex.printStackTrace(new PrintWriter(s)); logError(toFile, s.toString()); } public static void logStackTrace(String toFile, Error ex) { StringWriter s = new StringWriter(); ex.printStackTrace(new PrintWriter(s)); logError(toFile, s.toString()); } // Private Implementation private static boolean writeLogsToFile = false; private static final Set<String> filesToSkip = new HashSet<String>(); private static final long maximumLogfileSize = 25 * 1024 * 1024; private static void logEntry(PrintStream ordinaryOutput, String toFile, String message, int logLevel) { if(suppressLoggerOutput) return; // When we're not writing to a particular directory, and we're not spilling over into // a general logfile, write directly to the standard output unless it is really unimportant. if(!writeLogsToFile && spilloverLogfile == null) { if (logLevel >= LOG_LEVEL_ORDINARY) { ordinaryOutput.println("[" + toFile + "] " + message); } return; } try { String logMessage = logFormat(logLevel, ordinaryOutput == System.err, message); // If we are also displaying this file, write it to the standard output. if(filesToDisplay.contains(toFile) || logLevel >= minLevelToDisplay) { ordinaryOutput.println("[" + toFile + "] " + message); } // When constructing filename, if we are not writing to a particular directory, // go directly to the spillover file if one exists. String myFilename = myDirectory + "/" + toFile; if(!writeLogsToFile && spilloverLogfile != null) { myFilename = spilloverLogfile; } // Periodically check to make sure we're not writing TOO MUCH to this file. if(!filesToSkip.isEmpty() && filesToSkip.contains(myFilename)) { return; } if(ThreadLocalRandom.current().nextInt(1000) == 0) { // Verify that the file is not too large. if(new File(myFilename).length() > maximumLogfileSize) { System.err.println("Adding " + myFilename + " to filesToSkip."); filesToSkip.add(myFilename); logLevel = 9; logMessage = logFormat(logLevel, ordinaryOutput == System.err, "File too long; stopping all writes to this file."); } } // Finally, write the log message to the file. BufferedWriter out = new BufferedWriter(new FileWriter(myFilename, true)); out.write(logMessage); out.close(); } catch (IOException e) { e.printStackTrace(); } } private static String logFormat(int logLevel, boolean isError, String message) { String logMessage = "LOG " + System.currentTimeMillis() + " [L" + logLevel + "]: " + (isError ? "<ERR> " : "") + message; if(logMessage.charAt(logMessage.length() - 1) != '\n') { logMessage += '\n'; // All log lines must end with a newline. } return logMessage; } private static String myDirectory; private static HashSet<String> filesToDisplay = new HashSet<String>(); private static int minLevelToDisplay = Integer.MAX_VALUE; private static boolean suppressLoggerOutput; private static String spilloverLogfile; }