package net.sourceforge.pain.util; import java.io.*; import java.text.*; import java.util.*; /** * PAiN Date: 02.03.2003 Time: 22:19:31 * this class could be changed to Log4j as soon as any reasons will be found * today I just do not want any additional jar files in distribution */ public final class Log { private static long lastTime; private static final char[] lastTimeString = new char[23]; private static final GregorianCalendar calendar = new GregorianCalendar(); private static SimpleDateFormat fileFormat = new SimpleDateFormat("dd_MM_yyyy_HH'h'_mm'm'"); public static int rollPeriod = 60 * 60 * 1000; public static long nextRollTime = -1; public static String dirPath = ""; public static final String activeLogFileName = "pain.log"; public static Writer writer; private static final int flushPeriod = 1 * 1000; private static long nextFlushTime = -1; private static Flusher flusher; private static ShutdownFlusher shutdownFlusher; private Log() { } public static void debug(final String message) { roll(); log(" [DEBUG] ", message); } public static void info(final String message) { roll(); log(" [INFO] ", message); } public static void warn(final String message) { roll(); log(" [WARN] ", message); } public static void error(final String message) { roll(); log(" [ERROR] ", message); } public static void error(final Throwable e) { error("", e); } public static void error(final String message, final Throwable e) { roll(); log(" [ERROR] ", message + " / " + e.getClass().getName() + " : " + e.getMessage()); log("\r\n"); final StackTraceElement[] stack = e.getStackTrace(); final int catchDeep = stack.length - (new Throwable().getStackTrace().length - 1); for (int i = 0; i < stack.length; i++) { if (i == catchDeep) { log("[log]\t"); } else { log("\t"); } log(stack[i].toString()); log("\r\n"); } log(".\r\n"); } private static void log(final String message) { try { out(message); } catch (Exception e) { handleException(message, e); } } private static void log(final char[] message) { try { out(message); } catch (Exception e) { handleException(new String(message), e); } } private static void handleException(final String message, final Exception e) { if (writer == null || writer instanceof FileWriter) { switchToSystemOut(e, message); } } private static void renameFile(final File file, final String name) { File toFile = new File(name); for (int i = 1; toFile.exists(); i++) { toFile = new File(name + i); } file.renameTo(toFile); } private static void log(final String prefix, final String message) { log(getTime()); log(prefix); log(message); log("\r\n"); } private static void switchToSystemOut(final Exception e, final String message) { writer = new OutputStreamWriter(System.out); error(e); info("Switching output to system out"); log(message); } private static void flush() { if (nextFlushTime > System.currentTimeMillis()) { return; } synchronized (Log.class) { try { writer.flush(); } catch (Exception e) { handleException(e.getMessage(), e); } } nextFlushTime = System.currentTimeMillis() + flushPeriod; } private static void out(String message) throws IOException { message = message == null ? "null" : message; writer.write(message, 0, message.length()); } private static void out(final char[] message) throws IOException { writer.write(message, 0, message.length); } /** i just had nothing to do :) */ private static char[] getTime() { // code from log4j package (ISO8601DateFormat with a minimal changes) final long now = System.currentTimeMillis(); final int millis = (int) (now % 1000); if ((now - millis) != lastTime) { // We reach this point at most once per second // across all threads instead of each time format() // is called. This saves considerable CPU time. calendar.setTimeInMillis(now); final int year = calendar.get(Calendar.YEAR); lastTimeString[0] = (char) ('1' + (year / 1000) - 1); lastTimeString[1] = (char) ('1' + (year % 1000) / 100 - 1); lastTimeString[2] = (char) ('1' + (year % 100) / 10 - 1); lastTimeString[3] = (char) ('1' + (year % 10) - 1); lastTimeString[4] = '-'; final char m1; final char m2; switch (calendar.get(Calendar.MONTH)) { case Calendar.JANUARY: m1 = '0'; m2 = '1'; break; case Calendar.FEBRUARY: m1 = '0'; m2 = '2'; break; case Calendar.MARCH: m1 = '0'; m2 = '3'; break; case Calendar.APRIL: m1 = '0'; m2 = '4'; break; case Calendar.MAY: m1 = '0'; m2 = '5'; break; case Calendar.JUNE: m1 = '0'; m2 = '6'; break; case Calendar.JULY: m1 = '0'; m2 = '7'; break; case Calendar.AUGUST: m1 = '0'; m2 = '8'; break; case Calendar.SEPTEMBER: m1 = '0'; m2 = '9'; break; case Calendar.OCTOBER: m1 = '1'; m2 = '0'; break; case Calendar.NOVEMBER: m1 = '1'; m2 = '1'; break; case Calendar.DECEMBER: m1 = '1'; m2 = '2'; break; default: m1 = 'N'; m2 = 'A'; break; } lastTimeString[5] = m1; lastTimeString[6] = m2; lastTimeString[7] = '-'; final int day = calendar.get(Calendar.DAY_OF_MONTH); lastTimeString[8] = (char) ('1' + day / 10 - 1); lastTimeString[9] = (char) ('1' + day % 10 - 1); lastTimeString[10] = ' '; final int hour = calendar.get(Calendar.HOUR_OF_DAY); lastTimeString[11] = (char) ('1' + hour / 10 - 1); lastTimeString[12] = (char) ('1' + hour % 10 - 1); lastTimeString[13] = ':'; final int mins = calendar.get(Calendar.MINUTE); lastTimeString[14] = (char) ('1' + mins / 10 - 1); lastTimeString[15] = (char) ('1' + mins % 10 - 1); lastTimeString[16] = ':'; final int secs = calendar.get(Calendar.SECOND); lastTimeString[17] = (char) ('1' + secs / 10 - 1); lastTimeString[18] = (char) ('1' + secs % 10 - 1); lastTimeString[19] = ','; lastTime = now - millis; } lastTimeString[20] = (char) ('1' + (millis / 100) - 1); lastTimeString[21] = (char) ('1' + (millis % 100) / 10 - 1); lastTimeString[22] = (char) ('1' + (millis % 10) - 1); return lastTimeString; } private static void roll() { if (nextRollTime > System.currentTimeMillis()) { return; } synchronized (Log.class) { if (flusher == null) { flusher = new Flusher(); Thread worker = new Thread(flusher); worker.setDaemon(true); worker.start(); } if (shutdownFlusher == null) { shutdownFlusher = new ShutdownFlusher(); } if (nextRollTime > System.currentTimeMillis()) { return;//DCL } try { nextRollTime = System.currentTimeMillis() + rollPeriod; if (writer != null && !(writer instanceof FileWriter)) { return; } if (writer != null) { writer.close(); } createNewLogFile(); } catch (Exception e) { handleException("roll error", e); } } } private static void createNewLogFile() throws Exception { final File file = new File(dirPath + activeLogFileName); if (file.exists()) { // check first line for name: String line = null; final BufferedReader r = new BufferedReader(new FileReader(file)); try { line = r.readLine(); } finally { r.close(); } if (line == null) { line = fileFormat.format(new Date(file.lastModified())) + "_before.log"; } renameFile(file, dirPath + line); } writer = new FileWriter(file); writer.write(fileFormat.format(new Date())); //first line in file is its creation date, and future name during roll writer.write(".log"); writer.write("\r\n"); } private static final class ShutdownFlusher extends Thread { public ShutdownFlusher() { this.setDaemon(true); Runtime.getRuntime().addShutdownHook(this); } public void run() { try { if (writer != null) { writer.flush(); } } catch (Exception e) { e.printStackTrace(); } } } private static final class Flusher implements Runnable { public void run() { while (true) { synchronized (this) { try { this.wait(flushPeriod); flush(); } catch (InterruptedException e) { Log.error(e); } } } } } public static boolean isDebugEnabled() { return true; } public static boolean isInfoEnabled() { return true; } public static boolean isWarnEnabled() { return true; } }