package com.ibm.jactors.logging; import java.io.PrintStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Date; import javax.swing.JTextArea; import com.ibm.jactors.utils.Utils; /** * Default Logger implementation. * * Intended only for tracing, not core to the Actor article. * More functional than needed for this context. * * @author bfeigenb * */ public class DefaultLogger implements Logger { private static final String MTP_PACKAGE_PREFIX = "com.ibm.haac.mtp."; private static final String myCname = DefaultLogger.class.getName(); private static final String MTP_EMAIL_SUBJECT = "Mobile Topic Pods ERROR Notification"; private static final String MTP_EMAIL = "mtp@us.ibm.com"; private static final String SMTP_SERVER = "smtp.server"; private static final String LOGGER_RESOURCE = "logger.properties"; private static DefaultLogger instance; public static DefaultLogger getDefaultInstance() { if (instance == null) { instance = new DefaultLogger(); } return instance; } protected static String getDefaultContext() { return null; } public DefaultLogger() { this(getDefaultContext()); } public DefaultLogger(Object context) { this.context = context; } @Override public String toString() { return getClass().getName() + "[context=" + context + ", includeStacktrace=" + includeStacktrace + ", logToConsole=" + logToConsole + "]"; } // private String dateTimePattern = "%1$tm-%<td-%<tY %<tH:%<tM:%<tS.%<tL"; private String datePattern = "%1$tm-%<td-%<tY"; private String timePattern = "%1$tH:%<tM:%<tS.%<tL"; public String getDatePattern() { return timePattern; } public void setDatePattern(String datePattern) { this.timePattern = datePattern; } private Object context; public Object getContext() { return context; } /** * Defines a context (used to qualify logging messages). Some * implementations use the context to filter logging. * * @param context * generally a String or a Class. */ public void setContext(Object context) { this.context = context; } protected boolean logToFile = true; public boolean isLogToFile() { return logToFile; } public void setLogToFile(boolean logToFile) { this.logToFile = logToFile; } private boolean includeThread = true; public boolean isIncludeThread() { return includeThread; } public void setIncludeThread(boolean includeThread) { this.includeThread = includeThread; } private boolean includeCaller = true; public boolean isIncludeCaller() { return includeCaller; } public void setIncludeCaller(boolean includeCaller) { this.includeCaller = includeCaller; } private boolean includeDate = true; public boolean isIncludeDate() { return includeDate; } public void setIncludeDate(boolean includeDate) { this.includeDate = includeDate; } private boolean includeContext = true; public boolean isIncludeContext() { return includeContext; } public void setIncludeContext(boolean includeContext) { this.includeContext = includeContext; } private LogLevel logLevel = LogLevel.INFO; public LogLevel getLogLevel() { return logLevel; } /** * Controls what level of logging occurs. * * @param logLevel * only this and higher levels will be recorded * @see LogLevel */ public void setLogLevel(LogLevel logLevel) { this.logLevel = logLevel; } protected int callerFieldWidth = 60; public int getCallerFieldWidth() { return callerFieldWidth; } public void setCallerFieldWidth(int callerFieldWidth) { this.callerFieldWidth = callerFieldWidth; } protected int threadFieldWidth = 20; public int getThreadFieldWidth() { return threadFieldWidth; } public void setThreadFieldWidth(int threadFieldWidth) { this.threadFieldWidth = threadFieldWidth; } protected synchronized String log(LogLevel level, String message, Object... values) { String res = null; if (level.ordinal() >= logLevel.ordinal()) { Date now = new Date(); String nowDate = Utils.safeFormat(datePattern, now); String nowTime = Utils.safeFormat(timePattern, now); PrintStream ps = getPrintStream(level); try { StringBuilder sb = new StringBuilder(); if (includeDate) { sb.append(nowTime); } if (includeContext && context != null) { String text = context instanceof Class ? ((Class<?>) context) .getName() : context.toString(); sb.append(Utils.safeFormat(" (%s)", text)); } // sb.append(Utils.safeFormat(" %-10s ", level.toString())); sb.append(Utils.safeFormat(" %c ", level.toString().charAt(0))); if (includeThread) { String tname = Thread.currentThread().getName(); if (!Utils.isEmpty(tname)) { if (tname.length() > threadFieldWidth) { tname = "<" + tname.substring(tname.length() - 19); } sb.append(Utils.safeFormat("[%-" + threadFieldWidth + "s] ", tname)); } } if (includeCaller) { StackTraceElement ste = getCaller(); if (ste != null) { String cname = ste.getClassName(); if (cname.startsWith(MTP_PACKAGE_PREFIX)) { cname = "<." + cname.substring(MTP_PACKAGE_PREFIX .length()); } String calledFrom = Utils.safeFormat("%s#%s:%d", cname, ste.getMethodName(), ste.getLineNumber()); int length = calledFrom.length(); if (length > callerFieldWidth) { calledFrom = "..." + calledFrom.substring(length - callerFieldWidth + 3, length); } sb.append(Utils.safeFormat("(%-" + callerFieldWidth + "s)", calledFrom)); } } if (includeThread || includeCaller) { sb.append(Utils.safeFormat("- ")); } sb.append(Utils.isEmpty(values) ? message : Utils.safeFormat( message, values)); sb.append('\n'); String text = sb.toString(); res = text; if (logToConsole) { syncPrint(ps, text); } if (logArea != null) { logArea.append(text); logArea.setCaretPosition(logArea.getText().length()); } } catch (Exception e) { syncPrint(ps, Utils.safeFormat("log exception %s, %s: %s%n", level, message, e)); } } return res; } protected JTextArea logArea; public JTextArea getLogArea() { return logArea; } public void setLogArea(JTextArea logArea) { this.logArea = logArea; } protected static StackTraceElement getCaller() { StackTraceElement res = null; StackTraceElement[] stea = Thread.currentThread().getStackTrace(); for (int i = 0; res == null && i < stea.length; i++) { StackTraceElement ste = stea[i]; if (ste.getClassName().equals(myCname)) { continue; } if (ste.getClassName().endsWith("Utils")) { continue; } if (ste.getMethodName().indexOf("getStackTrace") == 0) { continue; } res = ste; } return res; } protected void syncPrint(PrintStream ps, String text) { synchronized (ps) { ps.print(text); ps.flush(); } } @Override public void info(String message, Object... values) { log(LogLevel.INFO, message, values); } @Override public void trace(String message, Object... values) { log(LogLevel.TRACE, message, values); } @Override public void warning(String message, Object... values) { log(LogLevel.WARNING, message, values); } protected String logSevere(LogLevel level, String message, Object... values) { String res = null; try { if (lastIsThrowable(values)) { res = log(level, Utils.safeFormat(message, values) + ": %s", (Throwable) values[values.length - 1]); String trace = logStackTrace(level, (Throwable) values[values.length - 1]); if (!Utils.isEmpty(trace)) { res += "\n\n" + trace; } } else { res = log(level, message, values); } } catch (Exception e) { System.out.printf("logSevere exception %s, %s: %s%n", level, message, e); } return res; } @Override public void error(String message, Object... values) { logSevere(LogLevel.ERROR, message, values); } protected String notifyRecepients; public String getNotifyRecepients() { return notifyRecepients; } public void setNotifyRecepients(String notifyRecepients) { this.notifyRecepients = notifyRecepients; } @Override public void notify(String message, Object... values) { String msg = logSevere(LogLevel.NOTIFY, message, values); } protected boolean lastIsThrowable(Object... values) { return values != null && values.length > 0 && values[values.length - 1] instanceof Throwable; } protected String logStackTrace(LogLevel level, Throwable t) { String res = null; if (t != null && includeStacktrace) { StringWriter sw = new StringWriter(); t.printStackTrace(new PrintWriter(sw)); String text = sw.toString(); res = text; if (logToConsole) { getPrintStream(level).print(text); } } return res; } protected PrintStream getPrintStream(LogLevel level) { // PrintStream out = level == LogLevel.NOTIFY ? System.err : System.out; PrintStream out = System.out; return out; } private boolean includeStacktrace = true; public boolean getIncludeStacktrace() { return includeStacktrace; } /** * Determines if ERROR or NOTIFY level logging with an Exception also * includes the stack trace. * * @param includeStacktrace */ public void setIncludeStacktrace(boolean includeStacktrace) { this.includeStacktrace = includeStacktrace; } private boolean logToConsole = true; /* * // test case public static void main(String[] args) { DefaultLogger lu = * new DefaultLogger(); lu.setIncludeStacktrace(true); lu.setFileLogger(new * FileLogger("/temp/log")); * * lu.info("info message: %s", "sub"); lu.warning("warn message: %s", * "sub"); lu.error("error message: %s", "sub"); * lu.notify("notify message: %s", "sub"); * * Exception e = new Exception("test exception"); lu.error("error message", * e); lu.notify("notify message", e); * * lu.setContext(DefaultLogger.class); lu.info("info message: %s", "sub"); } */ }