/* ************************************************************************ # # DivConq # # http://divconq.com/ # # Copyright: # Copyright 2014 eTimeline, LLC. All rights reserved. # # License: # See the license.txt file in the project's top-level directory for details. # # Authors: # * Andy White # ************************************************************************ */ package divconq.log; import io.netty.util.ResourceLeakDetector; import io.netty.util.ResourceLeakDetector.Level; import io.netty.util.internal.logging.InternalLoggerFactory; import java.io.File; import java.io.PrintWriter; import java.util.concurrent.locks.ReentrantLock; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormat; import divconq.lang.Memory; import divconq.lang.op.OperationContext; import divconq.lang.op.OperationResult; import divconq.locale.Tr; import divconq.util.HexUtil; import divconq.xml.XElement; /** * When logging messages to the debug log each message has a debug level. * The logger has a filter level and messages of lower priority than the * current debug level will not be logged. * * Note that 99% of the time the "current" debug level is determined by * the current TaskContext. The preferred way to log messages is through * the TaskContext or through an OperationResult. Ultimately a filter * is used to determine what should go in the log. * * In fact, when you call "void error(String message, String... tags)" * and other logging methods, theses methods will lookup the current * task context. So it is more efficient to work directly with task * context, however, occasional calls to these global logger methods * are fine. * * @author Andy * */ public class HubLog { static protected DebugLevel globalLevel = DebugLevel.Info; static protected boolean debugEnabled = false; // typically task logging is handled by a service on the bus, but on occasions // we want it to log to the file as well, from settings change this to 'true' static protected boolean toFile = true; static protected boolean toConsole = true; static protected PrintWriter logWriter = null; static protected ReentrantLock writeLock = new ReentrantLock(); static protected long filestart = 0; static protected ILogHandler handler = null; static protected XElement config = null; static public DebugLevel getGlobalLevel() { return HubLog.globalLevel; } static public void setGlobalLevel(DebugLevel v) { HubLog.globalLevel = v; HubLog.debugEnabled = ((v == DebugLevel.Trace) || (v == DebugLevel.Trace)); // keep hub context up to date OperationContext.updateHubContext(); // TODO replace with a debug context provider } static public void setLogHandler(ILogHandler v) { HubLog.handler = v; } static public void setToConsole(boolean v) { HubLog.toConsole = v; } /* * return true if debugging is even an option on this setup, * if not this saves a lot of overhead on the Logger.debug and Logger.trace calls */ static public boolean getDebugEnabled() { return HubLog.debugEnabled; } /** * Called from Hub.start this method configures the logging features. * * @param config xml holding the configuration */ static public void init(XElement config) { HubLog.config = config; // TODO return operation result // TODO load levels, path etc // include a setting for startup logging - if present set the TC log level directly HubLog.startNewLogFile(); // set by operation context init //Logger.locale = LocaleUtil.getDefaultLocale(); // From here on we can use netty and so we need the logger setup InternalLoggerFactory.setDefaultFactory(new divconq.log.netty.LoggerFactory()); if (HubLog.config != null) { // set by operation context init //if (Logger.config.hasAttribute("Level")) // Logger.globalLevel = DebugLevel.parse(Logger.config.getAttribute("Level")); if (HubLog.config.hasAttribute("Level")) HubLog.setGlobalLevel(DebugLevel.parse(HubLog.config.getAttribute("Level"))); if (HubLog.config.hasAttribute("EnableDebugger")) HubLog.debugEnabled = "True".equals(HubLog.config.getAttribute("EnableDebugger")); if (HubLog.config.hasAttribute("NettyLevel")) { ResourceLeakDetector.setLevel(Level.valueOf(HubLog.config.getAttribute("NettyLevel"))); Logger.debug("Netty Level set to: " + ResourceLeakDetector.getLevel()); } else if (!"none".equals(System.getenv("dcnet"))) { // TODO anything more we should do here? maybe paranoid isn't helpful? } // set by operation context init //if (Logger.config.hasAttribute("Locale")) // Logger.locale = Logger.config.getAttribute("Locale"); } } static protected void startNewLogFile() { try { File logfile = new File("./logs/" + DateTimeFormat.forPattern("yyyyMMdd'_'HHmmss").print(new DateTime(DateTimeZone.UTC)) + ".log"); if (!logfile.getParentFile().exists()) if (!logfile.getParentFile().mkdirs()) Logger.error("Unable to create logs folder."); logfile.createNewFile(); if (HubLog.logWriter != null) { HubLog.logWriter.flush(); HubLog.logWriter.close(); } Logger.trace("Opening log file: " + logfile.getCanonicalPath()); HubLog.logWriter = new PrintWriter(logfile, "utf-8"); HubLog.filestart = System.currentTimeMillis(); } catch (Exception x) { Logger.error("Unable to create log file: " + x); } } /* * In a distributed setup, DivConq may route logging to certain Hubs and * bypass the local log file. During shutdown logging returns to local * log file so that the dcBus can shutdown and stop routing the messages. * @param or */ static public void stop(OperationResult or) { // TODO return operation result HubLog.toFile = true; // go back to logging to file // TODO say no to database } /* * Insert a (string) translated message into the log * * @param ctx context for log settings, null for none * @param level message level * @param code to translate * @param params for the translation */ static public void log(DebugLevel level, long code, Object... params) { HubLog.log(level, Tr.tr("_code_" + code, params), "Code", code + ""); } /* * Insert a (string) message into the log * * @param ctx context for log settings, null for none * @param level message level * @param message text to store in log * @param tags searchable values associated with the message, key-value pairs can be created by putting two tags adjacent */ static public void log(DebugLevel level, String message, String... tags) { // do not log, is being filtered if (HubLog.globalLevel.getCode() < level.getCode()) return; HubLog.logWr(null, level, message, tags); } /* * Insert a (string) translated message into the log * * @param ctx context for log settings, null for none * @param level message level * @param code to translate * @param params for the translation */ static public void logWr(String opid, DebugLevel level, long code, Object... params) { HubLog.logWr(opid, level, Tr.tr("_code_" + code, params), "Code", code + ""); } /* * don't check, just write * * @param taskid * @param level * @param message * @param tags */ static public void logWr(String opid, DebugLevel level, String message, String... tags) { String indicate = "M" + level.getIndicator(); /* TODO if (Logger.toDatabase) { Message lmsg = new Message("Logger"); lmsg.addHeader("Op", "Log"); lmsg.addHeader("Indicator", indicate); lmsg.addHeader("Occurred", occur); lmsg.addHeader("Tags", tagvalue); lmsg.addStringAttachment(message); Hub.instance.getBus().sendMessage(lmsg); } */ // write to file if not a Task or if File Tasks is flagged if (HubLog.toFile || HubLog.toConsole) { if (message != null) message = message.replace("\n", "\n\t"); // tab sub-lines HubLog.write(opid, indicate, message, tags); } } /* * Insert a chunk of hex encoded memory into the log * * @param ctx context for log settings, null for none * @param level message level * @param data memory to hex encode and store * @param tags searchable values associated with the message, key-value pairs can be created by putting two tags adjacent */ static public void log(String opid, DebugLevel level, Memory data, String... tags) { // do not log, is being filtered if (HubLog.globalLevel.getCode() < level.getCode()) return; String indicate = "H" + level.getIndicator(); /* TODO if (tc != null) { Message lmsg = new Message("Logger"); lmsg.addHeader("Op", "Log"); lmsg.addHeader("Indicator", indicate); lmsg.addHeader("Occurred", occur); lmsg.addHeader("Tags", tagvalue); lmsg.addAttachment(data); Hub.instance.getBus().sendMessage(lmsg); } */ // write to file if not a Task or if File Tasks is flagged if (HubLog.toFile || HubLog.toConsole) HubLog.write(opid, indicate, HexUtil.bufferToHex(data), tags); } /* * A boundary delineates in section of a task log from another, making it * easier for a log viewer to organize the content. Boundary's are treated * like "info" messages, if only errors or warnings are being logged then * the boundary entry will be skipped. * * @param ctx context for log settings, null for none * @param tags searchable values associated with the message, key-value pairs can be created by putting two tags adjacent */ static public void boundary(String opid, String... tags) { // do not log, is being filtered if (HubLog.globalLevel.getCode() < DebugLevel.Info.getCode()) return; HubLog.boundaryWr(opid, tags); } /* * Don't check, just write * * @param taskid * @param tags */ static public void boundaryWr(String opid, String... tags) { /* TODO if (tc != null) { Message lmsg = new Message("Logger"); lmsg.addHeader("Op", "Log"); lmsg.addHeader("Indicator", "B"); lmsg.addHeader("Occurred", occur); lmsg.addHeader("Tags", tagvalue); Hub.instance.getBus().sendMessage(lmsg); } */ // write to file if not a Task or if File Tasks is flagged if (HubLog.toFile || HubLog.toConsole) HubLog.write(opid, "B ", "", tags); } static protected void write(String opid, String indicator, String message, String... tags) { if (opid == null) opid = "00000_19700101T000000000Z_000000000000000"; DateTime occur = new DateTime(DateTimeZone.UTC); String tagvalue = ""; if ((tags != null) && tags.length > 0) { tagvalue = "|"; for (String tag : tags) tagvalue += tag + "|"; } if (HubLog.handler != null) HubLog.handler.write(occur.toString(), opid, indicator, tagvalue, message); if (tagvalue.length() > 0) tagvalue += " "; HubLog.write(occur + " " + opid + " " + indicator + " " + tagvalue + message); } static protected void write(String msg) { if (HubLog.toConsole) System.out.println(msg); if (!HubLog.toFile || (HubLog.logWriter == null)) return; HubLog.writeLock.lock(); // start a new log file every 24 hours if (System.currentTimeMillis() - HubLog.filestart > 86400000) HubLog.startNewLogFile(); try { HubLog.logWriter.println(msg); HubLog.logWriter.flush(); } catch (Exception x) { // ignore, logger is broken } HubLog.writeLock.unlock(); } }