package com.zillabyte.motherbrain.flow.operations; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; import java.util.UUID; import org.apache.log4j.FileAppender; import org.apache.log4j.Layout; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.PatternLayout; import org.apache.log4j.Priority; import com.zillabyte.motherbrain.universe.Universe; import com.zillabyte.motherbrain.utils.Utils; public abstract class OperationLogger implements Serializable { private static final long serialVersionUID = -4995010296782665869L; private static final SimpleDateFormat rfc3339 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); private static final Logger _internalLog = Utils.getLogger(OperationLogger.class); static { rfc3339.setTimeZone(TimeZone.getTimeZone("GMT")); } protected String _procId; protected OperationLogger(String procId) { _procId = procId; } public String procId() { return _procId; } protected static final String toRFC3339(Date d) { return rfc3339.format(d).replaceAll("(\\d\\d)(\\d\\d)$", "$1:$2"); } public void writeLog(String message, LogPriority priority) { try { writeLogInternal(message, priority); } catch (OperationLoggerException | InterruptedException e) { _internalLog.error("internal logging error: " + e); } } public void logError(Exception e) { String message = e.getMessage(); if(message != null) { message = message.replace("\\n", "\n"); for(String s : message.split("\n")) { // Apparently we need that many slashes to escape \n writeLog(s, LogPriority.ERROR); } } for(StackTraceElement s : e.getStackTrace()) { writeLog(s.toString(), LogPriority.ERROR); } if(e.getCause() != null) { writeLog("Caused by:", LogPriority.ERROR); logError((Exception) e.getCause()); } } protected abstract void writeLogInternal(String message, LogPriority priority) throws InterruptedException, OperationLoggerException; public abstract String absoluteFilename(); public void error(String message) { writeLog(message, LogPriority.ERROR); } public void info(String message) { writeLog(message, LogPriority.RUN); } public void startUp(String message) { writeLog(message, LogPriority.STARTUP); } public void hidden(String message) { writeLog(message, LogPriority.HIDDEN); } public enum LogPriority { STARTUP, ERROR, HIDDEN, SYSTEM, IPC, RUN, KILL } public static abstract class Base extends OperationLogger { /** * Serialization ID */ private static final long serialVersionUID = 9203738033367330437L; protected String _flowId; protected static Logger log = Logger.getLogger(OperationLogger.class); public Base(String flowId, String opName) { super(opName); _flowId = flowId; log.info("initializing an operation logger for: "+_procId); } @SuppressWarnings("deprecation") @Override public void writeLog(String message, LogPriority priority) { log.log(OperationLogger.Base.class.getName(), Priority.INFO, "[f" + _flowId + "][" + _procId.toString() + "][" + priority + "] " + message, null); try { writeLogInternal(message, priority); } catch (OperationLoggerException | InterruptedException e) { log.error("internal logging error:" + e); } } } public static class noOp extends OperationLogger { /*** * * @param defaultProcId */ public noOp() { super(""); } /** * Serialization ID */ private static final long serialVersionUID = 9221643914582167040L; @Override protected void writeLogInternal(String message, LogPriority priority) { // System.err.println("noop:" + message); } @Override public String absoluteFilename() { return null; } } public static class Local extends Base { private static final long serialVersionUID = -3651914779884984185L; private transient org.apache.log4j.Logger _log = null; // do NOT make static private static final int MAX_LENGTH = 80 * 5; // Lines in display public Local(String flowId, String opName) { super(flowId, opName); } public String filename() { return "flow_"+_flowId+".log"; } public File root() { return Universe.instance().fileFactory().getFlowLoggingRoot(_flowId); } public File logFile() { return new File(root(), filename()); } @Override public String absoluteFilename() { return logFile().getAbsolutePath(); } @Override protected synchronized void writeLogInternal(final String msg, LogPriority priority) throws OperationLoggerException { String message = msg; if (msg == null) { // Do nothing... return; } if (message.length() > MAX_LENGTH && !priority.equals(LogPriority.ERROR)) { // don't truncate errors message = message.substring(0, MAX_LENGTH) + "[...truncated]"; } message = message.replace("\n", "\\n").replace("\r", "\\r"); try { if (_log == null) { // NOTE: this is the logger that the USER sees. Don't change unless you know what you're doing. _log = Logger.getLogger(UUID.randomUUID().toString()); _log.setAdditivity(false); _log.removeAllAppenders(); _log.setLevel(Level.ALL); logFile().getParentFile().mkdirs(); String filename = absoluteFilename(); Layout layout = new PatternLayout("%m%n"); FileAppender appender = new FileAppender(layout, filename, true); _log.addAppender(appender); } _log.info( toRFC3339(new Date()) + " flow_" + _flowId + "[" + _procId + "] - [" + priority + "] " + message); } catch(IOException e) { throw new OperationLoggerException(e); } } } }