diff options
Diffstat (limited to 'vespalog/src/main/java/com/yahoo/log')
42 files changed, 2743 insertions, 0 deletions
diff --git a/vespalog/src/main/java/com/yahoo/log/DefaultLevelController.java b/vespalog/src/main/java/com/yahoo/log/DefaultLevelController.java new file mode 100644 index 00000000000..eb55939b3be --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/DefaultLevelController.java @@ -0,0 +1,66 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +import java.util.logging.Level; + +/** + * a levelcontroller that just implements a simple default + * (possibly controlled by a system property or environment) + **/ +class DefaultLevelController implements LevelController { + private String levelstring; + private Level levelLimit = LogLevel.EVENT; + + DefaultLevelController(String env) { + if (LogUtil.empty(env)) { + env = "all -debug -spam"; + } + + //level string is: fatal, error, warning, config, info, event, debug, spam + if (env.equals("all")) { + levelLimit = LogLevel.ALL; + levelstring = " ON ON ON ON ON ON ON ON"; + } else { + StringBuilder sb = new StringBuilder(); + for (Level level : LogLevel.getLevels().values()) { + String levelName = level.getName(); + if (hasNegWord(levelName, env) || (!hasWord("all", env) && !hasWord(levelName, env))) { + sb.append(" OFF"); + } else { + sb.append(" ON"); + if ((level.intValue() < levelLimit.intValue())) { + levelLimit = level; + } + } + } + levelstring = sb.toString(); + } + // System.err.println("default level controller levelstring: "+levelstring); + } + + private boolean hasWord(String levelName, String inputLevels) { + return inputLevels.contains(levelName.toLowerCase()); + } + + private boolean hasNegWord(String levelName, String inputLevels) { + int pos = inputLevels.indexOf(levelName.toLowerCase()); + if (pos > 0) { + String c = inputLevels.substring(pos - 1, pos); + return (c != null && c.equals("-")); + } else { + return false; + } + } + + public String getOnOffString() { + return levelstring; + } + + public Level getLevelLimit() { + return levelLimit; + } + public boolean shouldLog(Level level) { + return (level.intValue() >= levelLimit.intValue()); + } + public void checkBack() { } +} diff --git a/vespalog/src/main/java/com/yahoo/log/FileLogTarget.java b/vespalog/src/main/java/com/yahoo/log/FileLogTarget.java new file mode 100644 index 00000000000..73087daaeb1 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/FileLogTarget.java @@ -0,0 +1,44 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +import java.io.*; + +/** + * @author lulf + * @since 5.1 + */ +public class FileLogTarget implements LogTarget { + private final File file; + private FileOutputStream fileOutputStream; + + public FileLogTarget(File target) throws FileNotFoundException { + this.file = target; + this.fileOutputStream = null; + } + + public synchronized OutputStream open() { + try { + close(); + fileOutputStream = new FileOutputStream(file, true); + } catch (FileNotFoundException e) { + throw new RuntimeException("Unable to open output stream", e); + } + return fileOutputStream; + } + + public synchronized void close() { + try { + if (fileOutputStream != null) { + fileOutputStream.close(); + fileOutputStream = null; + } + } catch (IOException e) { + throw new RuntimeException("Unable to close output stream", e); + } + } + + @Override + public String toString() { + return file.getAbsolutePath(); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/InvalidLogFormatException.java b/vespalog/src/main/java/com/yahoo/log/InvalidLogFormatException.java new file mode 100644 index 00000000000..a005f5098c8 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/InvalidLogFormatException.java @@ -0,0 +1,18 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +/** + * This (checked) exception is used to flag invalid log messages, + * primarily for use in the factory methods of LogMessage. + * + * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a> + */ +public class InvalidLogFormatException extends Exception +{ + public InvalidLogFormatException (String msg) { + super(msg); + } + + public InvalidLogFormatException () { + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/LevelController.java b/vespalog/src/main/java/com/yahoo/log/LevelController.java new file mode 100644 index 00000000000..d0220ca4105 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/LevelController.java @@ -0,0 +1,38 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * This is the interface for controlling the log level of a + * component logger. This hides the actual controlling + * mechanism. + * + * @author arnej27959 + * + */ + +/** + * @author arnej27959 + **/ +package com.yahoo.log; + +import java.util.logging.Level; + +public interface LevelController { + + /** + * should we actually publish a log message with the given Level now? + **/ + public boolean shouldLog(Level level); + + /** + * return a string suitable for printing in a logctl file. + * the string must be be 4 * 8 characters, where each group + * of 4 characters is either " ON" or " OFF". + **/ + public String getOnOffString(); + + /** + * check the current state of logging and reflect it into the + * associated Logger instance, if available. + **/ + public void checkBack(); + public Level getLevelLimit(); +} diff --git a/vespalog/src/main/java/com/yahoo/log/LevelControllerRepo.java b/vespalog/src/main/java/com/yahoo/log/LevelControllerRepo.java new file mode 100644 index 00000000000..65d6a6341f3 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/LevelControllerRepo.java @@ -0,0 +1,23 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +/** + * The level controller repository is an interface towards something that is able to provide level + * controllers for a given component. + * + * @author lulf + * @since 5.1 + */ +public interface LevelControllerRepo { + /** + * Return the level controller for a given component. + * @param component The component name string. + * @return The LevelController corresponding to that component. Return null if not found. + */ + public LevelController getLevelController(String component); + + /** + * Close down the level controller repository. Cleanup should be done here. + */ + public void close(); +} diff --git a/vespalog/src/main/java/com/yahoo/log/LogLevel.java b/vespalog/src/main/java/com/yahoo/log/LogLevel.java new file mode 100644 index 00000000000..032f6e009fd --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/LogLevel.java @@ -0,0 +1,167 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +import java.util.HashMap; +import java.util.Map; +import java.util.LinkedHashMap; +import java.util.logging.Level; + + +/** + * Note that the log levels defined in VESPA applications are the + * following. + * + * <UL> + * <LI> LogLevel.EVENT (1201) + * <LI> LogLevel.FATAL (1151) + * <LI> LogLevel.ERROR (1101) + * <LI> <em>LogLevel.SEVERE (1000)</em> + * <LI> LogLevel.WARNING (900) + * <LI> LogLevel.INFO (800) + * <LI> LogLevel.CONFIG (700) + * <LI> LogLevel.DEBUG (501) + * <LI> LogLevel.SPAM (299) + * </UL> + * + * <P> + * Note that the EVENT level is somewhat special and you must + * <b>never</b> log one of these messages manually, but use + * the {@link com.yahoo.log.event.Event} class for this. + * + * @author Bjorn Borud + * @author arnej27959 + */ + +public class LogLevel extends Level { + /** A map from the name of the log level to the instance */ + private static LinkedHashMap<String, Level> nameToLevel; + + /** A map from the java.util.logging loglevel to VESPA's loglevel */ + private static Map<Level, Level> javaToVespa; + + public static final int IntValEVENT = 1201; + public static final int IntValFATAL = 1161; + public static final int IntValERROR = 1101; + public static final int IntValUNKNOWN = 1001; + public static final int IntValSEVERE = 1000; + public static final int IntValWARNING = 900; + public static final int IntValINFO = 800; + public static final int IntValCONFIG = 700; + public static final int IntValDEBUG = 501; + public static final int IntValFINE = 500; + public static final int IntValFINER = 400; + public static final int IntValFINEST = 300; + public static final int IntValSPAM = 299; + + // these define the ordering of the Vespa levels logcontrol files. + // it must match the values of the LogLevel enum in <log/log.h> + // for the C++ framework: + // fatal, error, warning, config, info, event, debug, spam, NUM_LOGLEVELS + + public static final int LogCtlFATAL = 0; + public static final int LogCtlERROR = 1; + public static final int LogCtlWARNING = 2; + public static final int LogCtlCONFIG = 3; + public static final int LogCtlINFO = 4; + public static final int LogCtlEVENT = 5; + public static final int LogCtlDEBUG = 6; + public static final int LogCtlSPAM = 7; + public static final int LogCtlNumLevels = 8; + + // ordinary log levels + public static LogLevel UNKNOWN = new LogLevel("UNKNOWN", IntValUNKNOWN); + public static LogLevel EVENT = new LogLevel("EVENT", IntValEVENT); + public static LogLevel FATAL = new LogLevel("FATAL", IntValFATAL); + public static LogLevel ERROR = new LogLevel("ERROR", IntValERROR); + public static LogLevel DEBUG = new LogLevel("DEBUG", IntValDEBUG); + public static LogLevel SPAM = new LogLevel("SPAM", IntValSPAM); + + // overlapping ones, only mentioned for illustration + // + // public static LogLevel WARNING = new LogLevel("WARNING",900); + // public static LogLevel INFO = new LogLevel("INFO",800); + // public static LogLevel CONFIG = new LogLevel("CONFIG",700); + + static { + // define mapping from Java log levels to VESPA log + // levels. + javaToVespa = new HashMap<Level, Level>(); + javaToVespa.put(Level.SEVERE, ERROR); + javaToVespa.put(Level.WARNING, WARNING); + javaToVespa.put(Level.INFO, INFO); + javaToVespa.put(Level.CONFIG, CONFIG); + javaToVespa.put(Level.FINE, DEBUG); + javaToVespa.put(Level.FINER, DEBUG); + javaToVespa.put(Level.FINEST, SPAM); + + // need the VESPA ones too + javaToVespa.put(UNKNOWN, UNKNOWN); + javaToVespa.put(FATAL, FATAL); + javaToVespa.put(ERROR, ERROR); + javaToVespa.put(EVENT, EVENT); + javaToVespa.put(DEBUG, DEBUG); + javaToVespa.put(SPAM, SPAM); + + // manually enter the valid log levels we shall recognize + // in VESPA + nameToLevel = new LinkedHashMap<String, Level>(15); + nameToLevel.put("fatal", FATAL); + nameToLevel.put("error", ERROR); + nameToLevel.put("warning", WARNING); + nameToLevel.put("config", CONFIG); + nameToLevel.put("info", INFO); + nameToLevel.put("event", EVENT); + nameToLevel.put("debug", DEBUG); + nameToLevel.put("spam", SPAM); + } + + private LogLevel(String name, int value) { + super(name, value); + } + + /** + * Semi-Case sensitive parsing of log levels. <b>Log levels are + * in either all upper case or all lower case. Not mixed + * case. </b>. Returns static instance representing log level or + * the UNKNOWN LogLevel instance. + * + * @param name Name of loglevel in uppercase or lowercase. + * @return Returns the static (immutable) LogLevel instance + * equivalent to the name given. + * + */ + public static Level parse(String name) { + Level l = nameToLevel.get(name); + if (l == null) { + return UNKNOWN; + } + return l; + } + + /** + * Static method for mapping Java log level to VESPA log level. + * + * @param level The Java loglevel we want mapped to its VESPA + * counterpart + * @return The VESPA LogLevel instance representing the corresponding + * log level or the UNKNOWN instance if the log level was + * unknown (ie. not contained in the mapping. Should never + * happen). + */ + public static Level getVespaLogLevel(Level level) { + Level ll = javaToVespa.get(level); + if (ll != null) { + return ll; + } + return UNKNOWN; + } + + /** + * Static method returning a map from Vespa level name to Level + * + * @return a map from Vespa level name to Level + */ + public static HashMap<String, Level> getLevels() { + return nameToLevel; + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/LogMessage.java b/vespalog/src/main/java/com/yahoo/log/LogMessage.java new file mode 100644 index 00000000000..123c10d0d95 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/LogMessage.java @@ -0,0 +1,151 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.yahoo.log.event.Event; +import com.yahoo.log.event.MalformedEventException; + +/** + * This class implements the common ground log message used by + * the logserver. A LogMessage is immutable. Note that we have + * chosen the name LogMessage to avoid confusion with LogRecord + * which is used in java.util.logging. + * + * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a> + */ +public class LogMessage +{ + private static Logger log = Logger.getLogger(LogMessage.class.getName()); + + private static Pattern nativeFormat = + Pattern.compile("^(\\d[^\t]+)\t" + // time + "([^\t]+)\t" + // host + "([^\t]+)\t" + // threadProcess + "([^\t]+)\t" + // service + "([^\t]+)\t" + // component + "([^\t]+)\t" + // level + "(.+)$" // payload + ); + + private long time; + private String timeStr; + private String host; + private String threadProcess; + private String service; + private String component; + private Level level; + private String payload; + private Event event; + + /** + * Private constructor. Log messages should never be instantiated + * directly; only as the result of a static factory method. + */ + private LogMessage (String timeStr, Long time, String host, String threadProcess, + String service, String component, Level level, + String payload) + { + this.timeStr = timeStr; + this.time = time; + this.host = host; + this.threadProcess = threadProcess; + this.service = service; + this.component = component; + this.level = level; + this.payload = payload; + } + + public long getTime () {return time;} + public long getTimeInSeconds () {return time / 1000;} + public String getHost () {return host;} + public String getThreadProcess () {return threadProcess;} + public String getService () {return service;} + public String getComponent () {return component;} + public Level getLevel () {return level;} + public String getPayload () {return payload;} + + /** + * Make a log message from the native format of the logging + * package. + * + * @param msg The log message + * @return Returns a LogMessage instance + * @throws InvalidLogFormatException if the log message + * can not be parsed, ie. is invalid, we throw this + * exception. + */ + public static LogMessage parseNativeFormat(String msg) throws InvalidLogFormatException { + Matcher m = nativeFormat.matcher(msg); + if (! m.matches()) { + throw new InvalidLogFormatException(msg); + } + + Level msgLevel = LogLevel.parse(m.group(6)); + Long timestamp = parseTimestamp(m.group(1)); + + return new LogMessage(m.group(1), timestamp, m.group(2), m.group(3), + m.group(4), m.group(5), msgLevel, + m.group(7)); + } + + private static long parseTimestamp(String timeStr) throws InvalidLogFormatException { + try { + return (long) (Double.parseDouble(timeStr) * 1000); + } catch (NumberFormatException e) { + throw new InvalidLogFormatException("Invalid time string:" + timeStr); + } + } + + /** + * If the LogMessage was an EVENT then this method can + * be used to get the Event instance representing the + * event. The event instance created the first time + * this method is called and then cached. + * + * TODO: make sure this throws exception! + * + * @return Returns Event instance if this is an event message + * and the payload is correctly formatted. Otherwise + * it will return <code>null</code>. + * + */ + public Event getEvent () throws MalformedEventException { + if ((level == LogLevel.EVENT) && (event == null)) { + try { + event = Event.parse(getPayload()); + event.setTime(time); + } + catch (MalformedEventException e) { + log.log(LogLevel.DEBUG, "Got malformed event: " + getPayload()); + throw e; + } + } + return event; + } + + /** + * Return valid representation of log message. + */ + public String toString () { + return new StringBuilder(timeStr.length() + + host.length() + + threadProcess.length() + + service.length() + + component.length() + + level.toString().length() + + payload.length() + + 1) + .append(timeStr).append("\t") + .append(host).append("\t") + .append(threadProcess).append("\t") + .append(service).append("\t") + .append(component).append("\t") + .append(level.toString().toLowerCase()).append("\t") + .append(payload).append("\n") + .toString(); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/LogMessageTimeComparator.java b/vespalog/src/main/java/com/yahoo/log/LogMessageTimeComparator.java new file mode 100644 index 00000000000..f11dc9ec139 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/LogMessageTimeComparator.java @@ -0,0 +1,45 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +import java.io.Serializable; +import java.util.Comparator; + +/** + * Order LogMessage instances based on timestamp. The default ordering is ascending. + * This may be reversed by the constructor argument. + * + * Note: this comparator imposes orderings that are inconsistent with equals. + * This is due to only looking at the timestamp, so two different messages with + * the same timestamp would appear "equal" to this comparator. + * + * @author vlarsen + * + */ +public class LogMessageTimeComparator implements Comparator<LogMessage>, Serializable { + private static final long serialVersionUID = 1L; + + /** Indicate the ordering of the timecomparison */ + private boolean ascending = true; + + /** + * Create a Time comparator for logmessages. Order is ascending. + * + */ + public LogMessageTimeComparator() {} + + /** + * Create a Time comparator for logmessages. The chronological order is dependent + * on the argument to the constructor. + * + * @param ascending true if you want LogMessages ordered ascending according to timestamp. + */ + public LogMessageTimeComparator(boolean ascending) { + this.ascending = ascending; + } + + public int compare(LogMessage message1, LogMessage message2) { + return ascending ? + Long.valueOf(message1.getTime()).compareTo(Long.valueOf(message2.getTime())) + : Long.valueOf(message2.getTime()).compareTo(Long.valueOf(message1.getTime())); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/LogSetup.java b/vespalog/src/main/java/com/yahoo/log/LogSetup.java new file mode 100644 index 00000000000..581a71e2b2b --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/LogSetup.java @@ -0,0 +1,198 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Enumeration; +import java.util.logging.*; +import java.util.Timer; + +/** + * Sets up Vespa logging. Call a setup method to set up this. + * + * @author Bjorn Borud + * @author arnej27959 + */ +public class LogSetup { + + private static Timer taskRunner = new Timer(true); + /** A global task thread */ + public static Timer getTaskRunner() { return taskRunner; } + + /** The log handler used by this */ + private static VespaLogHandler logHandler; + + public static void clearHandlers () { + Enumeration<String> names = LogManager.getLogManager().getLoggerNames(); + while (names.hasMoreElements()) { + String name = names.nextElement(); + Logger logger = Logger.getLogger(name); + + Handler[] handlers = logger.getHandlers(); + for (Handler handler : handlers) { + logger.removeHandler(handler); + } + } + } + + private static boolean isInitialized = false; + + /** + * Every Vespa application should call initVespaLogging exactly + * one time. This should be done from the main() method or from a + * static initializer in the main class. The library will pick up + * the environment variables usually set by the Vespa + * config-sentinel (VESPA_LOG_LEVEL, VESPA_LOG_TARGET, + * VESPA_SERVICE_NAME, VESPA_LOG_CONTROL_DIR) but it's possible to + * override these by setting system properties before calling + * initVespaLogging. This may be useful for unit testing etc: + * <br> + * System.setProperty("vespa.log.level", "all") + * <br> + * System.setProperty("vespa.log.target", "file:foo.log") + * <br> + * System.setProperty("vespa.service.name", "my.name") + * <br> + * System.setProperty("vespa.log.control.dir", ".") + * <br> + * System.setProperty("vespa.log.control.file", "my.logcontrol") + * <br> + * vespa.log.control.file is used if it's set, otherwise it's + * vespa.log.control.dir + "/" + vespa.service.name + ".logcontrol" + * if both of those variables are set, otherwise there will be no + * runtime log control. + * + * @param programName the name of the program that is running; + * this is added as a prefix to the logger name to form the + * "component" part of the log message. (Usually the logger name + * is the name of the class that logs something, so the + * programName should be kept short and simple.) + **/ + public static void initVespaLogging(String programName) { + if (isInitialized) { + System.err.println("WARNING: initVespaLogging called twice"); + } + isInitialized = true; + + // prefer Java system properties + String logLevel = System.getProperty("vespa.log.level"); + String logTarget = System.getProperty("vespa.log.target"); + String logService = System.getProperty("vespa.service.name"); + String logControlDir = System.getProperty("vespa.log.control.dir"); + String logControlFile = System.getProperty("vespa.log.control.file"); + if (programName == null || programName.equals("")) { + throw new RuntimeException("invalid programName: "+programName); + } + + // then try environment values + if (logTarget == null) logTarget = System.getenv("VESPA_LOG_TARGET"); + if (logService == null) logService = System.getenv("VESPA_SERVICE_NAME"); + if (logControlDir == null) logControlDir = System.getenv("VESPA_LOG_CONTROL_DIR"); + if (logControlFile == null) logControlFile = System.getenv("VESPA_LOG_CONTROL_FILE"); + if (logLevel == null) logLevel = System.getenv("VESPA_LOG_LEVEL"); + + // then hardcoded defaults + if (logTarget == null) logTarget = "fd:2"; + if (logLevel == null) logLevel = "all -debug -spam"; + + if (logControlFile == null && + logControlDir != null && + logService != null && + !logService.equals("") && + !logService.equals("-")) + { + logControlFile = logControlDir + "/" + logService + ".logcontrol"; + } + + // for backwards compatibility - XXX should be removed + if (logService == null) logService = System.getProperty("config.id"); + if (logService == null) logService = "-"; + + System.setProperty("vespa.service.name", logService); + System.setProperty("vespa.program.name", programName); + + try { + initInternal(logTarget, logService, logControlFile, programName, logLevel); + } catch (FileNotFoundException e) { + throw new RuntimeException("Unable to initialize logging", e); + } + } + + private static LogTarget getLogTargetFromString(String target) throws FileNotFoundException { + if ("fd:2".equals(target)) { + return new StderrLogTarget(); + } else if ("fd:1".equals(target)) { + return new StdoutLogTarget(); + } else if (target.startsWith("file:")) { + return new FileLogTarget(new File(target.substring(5))); + } + throw new IllegalArgumentException("Target '" + target + "' is not a valid target"); + } + + private static void initInternal(String target, + String service, + String logCtlFn, + String app, + String lev) throws FileNotFoundException { + clearHandlers(); + + if (app != null && app.length() > 64) app = app.substring(0, 63); + + if (logHandler != null) { + logHandler.cleanup(); + Logger.getLogger("").removeHandler(logHandler); + } + Logger.getLogger("").setLevel(Level.ALL); + logHandler = new VespaLogHandler(getLogTargetFromString(target), new VespaLevelControllerRepo(logCtlFn, lev, app), service, app); + String zookeeperLogFile = System.getProperty("zookeeperlogfile"); + if (zookeeperLogFile != null) { + logHandler.setFilter(new ZooKeeperFilter(zookeeperLogFile)); + } + Logger.getLogger("").addHandler(logHandler); + } + + /** Returns the log handler set up by this class */ + public static VespaLogHandler getLogHandler() { + return logHandler; + } + + /** + * Class that has an isLoggable methods that handles log records that + * start with "org.apache.zookeeper." in + * a special way (writing them to the logfile specified in the system property + * zookeeperlogfile, "/tmp/zookeeper.log" if not set) and returning false. + * For other log records, isLoggable returns true + */ + static class ZooKeeperFilter implements Filter { + private FileHandler fileHandler; + + ZooKeeperFilter(String logFile) { + try { + fileHandler = new FileHandler(logFile, true); + fileHandler.setFormatter(new VespaFormatter()); + } catch (IOException e) { + System.out.println("Not able to create " + logFile); + fileHandler = null; + } + } + + /** + * Return true if loggable (ordinary log record), returns false if this filter + * logs it itself + * @param record a #{@link LogRecord} + * @return true if loggable, false otherwise + */ + @Override + public boolean isLoggable(LogRecord record) { + if (record.getLoggerName() == null) return true; + if (!record.getLoggerName().startsWith("org.apache.zookeeper.") && + !record.getLoggerName().startsWith("com.netflix.curator")) { + return true; + } + fileHandler.publish(record); + return false; + } + } + +} diff --git a/vespalog/src/main/java/com/yahoo/log/LogTarget.java b/vespalog/src/main/java/com/yahoo/log/LogTarget.java new file mode 100644 index 00000000000..5ba5ba7769f --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/LogTarget.java @@ -0,0 +1,21 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +import java.io.OutputStream; + +/** + * @author lulf + * @since 5.1 + */ +public interface LogTarget { + /** + * Opens an output stream for the target. If already open, the stream should be reopened. + * @return a new outputstream for the log target. + */ + public OutputStream open(); + + /** + * Close the log target, ensuring that all data is written. + */ + public void close(); +} diff --git a/vespalog/src/main/java/com/yahoo/log/LogUtil.java b/vespalog/src/main/java/com/yahoo/log/LogUtil.java new file mode 100644 index 00000000000..e803ee48156 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/LogUtil.java @@ -0,0 +1,12 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +/** + * @author lulf + * @since 5.1 + */ +class LogUtil { + static boolean empty(String s) { + return (s == null || s.equals("")); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/MappedLevelController.java b/vespalog/src/main/java/com/yahoo/log/MappedLevelController.java new file mode 100644 index 00000000000..9cfc25a2dae --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/MappedLevelController.java @@ -0,0 +1,120 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +import com.yahoo.text.Utf8; + +import java.nio.MappedByteBuffer; +import java.util.logging.Level; + +/** + * a level controller that does lookup in a file via a memory-mapped + * buffer for realtime logging control. + **/ +class MappedLevelController implements LevelController { + private static final int ONVAL = 0x20204f4e; // equals " ON" in file + private static final int OFFVAL = 0x204f4646; // equals " OFF" in file + + private MappedByteBuffer mapBuf; + private int offset; + private java.util.logging.Logger associate; + public MappedLevelController(MappedByteBuffer buf, + int firstoffset, + String name) + { + this.mapBuf = buf; + this.offset = firstoffset; + this.associate = java.util.logging.Logger.getLogger(name); + } + + /** + * return the current state as a string + * (directly fetched from the file via the mapping buffer) + **/ + public String getOnOffString() { + byte[] levels = new byte[4 * VespaLevelControllerRepo.numLevels]; + for (int i = 0; i < levels.length; i++) { + levels[i] = mapBuf.get(offset + i); + } + return Utf8.toString(levels); + } + + /** + * check that each controlled level is either ON or OFF. + **/ + public static boolean checkOnOff(MappedByteBuffer mapBuf, + int offset) + { + for (int i = 0; i < VespaLevelControllerRepo.numLevels; i++) { + int off = offset + 4 * i; + int val = mapBuf.getInt(off); + if (val != ONVAL && val != OFFVAL) { + System.err.println("bad on/off value: "+val); + return false; + } + } + return true; + } + + /** + * make sure our associated java.util.Logger instance + * gets the correct logging level so it can avoid sending + * us lots of debug and spam log messages that will + * be discarded in the usual case. + **/ + public void checkBack() { + associate.setLevel(getLevelLimit()); + } + public Level getLevelLimit() { + Level lvl; + if (isOn(LogLevel.LogCtlSPAM)) { + lvl = LogLevel.ALL; + } else if (isOn(LogLevel.LogCtlDEBUG)) { + lvl = LogLevel.FINE; + } else if (isOn(LogLevel.LogCtlCONFIG)) { + lvl = LogLevel.CONFIG; + } else if (isOn(LogLevel.LogCtlINFO)) { + lvl = LogLevel.INFO; + } else if (isOn(LogLevel.LogCtlWARNING)) { + lvl = LogLevel.WARNING; + } else { + lvl = LogLevel.SEVERE; + } + return lvl; + } + + /** + * is a specific Vespa level ON or OFF in the file? + **/ + private boolean isOn(int num) { + int off = offset + num*4; + int val = mapBuf.getInt(off); + if (val == OFFVAL) + return false; + return true; + } + + /** + * should we publish a log messages on the given java Level? + **/ + public boolean shouldLog(Level level) { + int val = level.intValue(); + + // event is special and handled first: + if (val == LogLevel.IntValEVENT) { return isOn(LogLevel.LogCtlEVENT); } + + // all other levels are handled in "severity order": + + if (val >= LogLevel.IntValFATAL) { return isOn(LogLevel.LogCtlFATAL); } + // LogLevel.ERROR between here + if (val >= LogLevel.IntValSEVERE) { return isOn(LogLevel.LogCtlERROR); } + if (val >= LogLevel.IntValWARNING) { return isOn(LogLevel.LogCtlWARNING); } + if (val >= LogLevel.IntValINFO) { return isOn(LogLevel.LogCtlINFO); } + if (val >= LogLevel.IntValCONFIG) { return isOn(LogLevel.LogCtlCONFIG); } + // LogLevel.DEBUG between here + // LogLevel.FINE between here + if (val >= LogLevel.IntValFINER) { return isOn(LogLevel.LogCtlDEBUG); } + // LogLevel.FINEST and + // LogLevel.SPAM: + return isOn(LogLevel.LogCtlSPAM); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/MappedLevelControllerRepo.java b/vespalog/src/main/java/com/yahoo/log/MappedLevelControllerRepo.java new file mode 100644 index 00000000000..eb37ca47835 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/MappedLevelControllerRepo.java @@ -0,0 +1,114 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +import com.yahoo.text.Utf8; + +import java.nio.MappedByteBuffer; +import java.util.HashMap; +import java.util.Map; + +/** + * Contains a repository of mapped log level controllers. + * + * @author lulf + * @since 5.1 + */ +public class MappedLevelControllerRepo { + private final Map<String, LevelController> levelControllerMap = new HashMap<>(); + private final MappedByteBuffer mapBuf; + private final int controlFileHeaderLength; + private final int numLevels; + private final String logControlFilename; + + public MappedLevelControllerRepo(MappedByteBuffer mapBuf, int controlFileHeaderLength, int numLevels, String logControlFilename) { + this.mapBuf = mapBuf; + this.controlFileHeaderLength = controlFileHeaderLength; + this.numLevels = numLevels; + this.logControlFilename = logControlFilename; + buildMap(); + } + + private void buildMap() { + int len = mapBuf.capacity(); + int startOfLine = controlFileHeaderLength; + + int numLine = 1; + int i = 0; + while (i < len) { + if (mapBuf.get(i) == '\n') { + startOfLine = ++i; + ++numLine; + } else if (i < controlFileHeaderLength) { + ++i; + } else if (mapBuf.get(i) == ':') { + int endOfName = i; + int levels = i; + levels += 2; + while ((levels % 4) != 0) { + levels++; + } + int endLine = levels + 4*numLevels; + + if (checkLine(startOfLine, endOfName, levels, endLine)) { + int l = endOfName - startOfLine; + if (l > 1 && mapBuf.get(startOfLine) == '.') { + ++startOfLine; + --l; + } + byte[] namebytes = new byte[l]; + for (int j = 0; j < l; j++) { + namebytes[j] = mapBuf.get(startOfLine + j); + } + String name = Utf8.toString(namebytes); + if (name.equals("default")) { + name = ""; + } + MappedLevelController ctrl = new MappedLevelController(mapBuf, levels, name); + levelControllerMap.put(name, ctrl); + i = endLine; + continue; // good line + } + // bad line, skip + while (i < len && mapBuf.get(i) != '\n') { + i++; + } + int bll = i - startOfLine; + byte[] badline = new byte[bll]; + for (int j = 0; j < bll; j++) { + badline[j] = mapBuf.get(startOfLine + j); + } + System.err.println("bad loglevel line "+numLine+" in " + + logControlFilename + ": " + Utf8.toString(badline)); + } else { + i++; + } + } + } + + private boolean checkLine(int sol, int endnam, int levstart, int eol) { + if (eol >= mapBuf.capacity()) { + System.err.println("line would end after end of file"); + return false; + } + if (mapBuf.get(eol) != '\n') { + System.err.println("line must end with newline, was: "+mapBuf.get(eol)); + return false; + } + if (endnam < sol + 1) { + System.err.println("name must be at least one character after start of line"); + return false; + } + return MappedLevelController.checkOnOff(mapBuf, levstart); + } + + public LevelController getLevelController(String suffix) { + + return levelControllerMap.get(suffix); + } + + public void checkBack() { + for (LevelController ctrl : levelControllerMap.values()) { + ctrl.checkBack(); + } + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/RejectFilter.java b/vespalog/src/main/java/com/yahoo/log/RejectFilter.java new file mode 100644 index 00000000000..980ce52ecb1 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/RejectFilter.java @@ -0,0 +1,37 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +import java.util.ArrayList; +import java.util.List; + +/** + * A RejectFilter can be queried to see if a log message should be rejected. + * + * @author lulf + * @since 5.1 + */ +public class RejectFilter { + private final List<String> rejectedMessages = new ArrayList<>(); + + public boolean shouldReject(String message) { + if (message == null) + return false; + + for (String rejectedMessage : rejectedMessages) { + if (message.contains(rejectedMessage)) { + return true; + } + } + return false; + } + + public void addRejectedMessage(String rejectedMessage) { + rejectedMessages.add(rejectedMessage); + } + + public static RejectFilter createDefaultRejectFilter() { + RejectFilter reject = new RejectFilter(); + reject.addRejectedMessage("Using FILTER_NONE: This must be paranoid approved, and since you are using FILTER_NONE you must live with this error."); + return reject; + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/StderrLogTarget.java b/vespalog/src/main/java/com/yahoo/log/StderrLogTarget.java new file mode 100644 index 00000000000..fa52cedbb2b --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/StderrLogTarget.java @@ -0,0 +1,21 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +import java.io.OutputStream; + +/** + * @author lulf + * @since 5.1 + */ +public class StderrLogTarget implements LogTarget { + + @Override + public OutputStream open() { + return new UncloseableOutputStream(System.err); + } + + @Override + public void close() { + // ignore + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/StdoutLogTarget.java b/vespalog/src/main/java/com/yahoo/log/StdoutLogTarget.java new file mode 100644 index 00000000000..e10cf1c6792 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/StdoutLogTarget.java @@ -0,0 +1,21 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +import java.io.OutputStream; + +/** + * @author lulf + * @since 5.1 + */ +public class StdoutLogTarget implements LogTarget { + + @Override + public OutputStream open() { + return new UncloseableOutputStream(System.out); + } + + @Override + public void close() { + // ignore + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/UncloseableOutputStream.java b/vespalog/src/main/java/com/yahoo/log/UncloseableOutputStream.java new file mode 100644 index 00000000000..67bd48f0654 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/UncloseableOutputStream.java @@ -0,0 +1,28 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + * @since 5.1.14 + */ +class UncloseableOutputStream extends OutputStream { + + private final OutputStream out; + + public UncloseableOutputStream(OutputStream out) { + this.out = out; + } + + @Override + public void write(int b) throws IOException { + out.write(b); + } + + @Override + public void close() { + // ignore + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/Util.java b/vespalog/src/main/java/com/yahoo/log/Util.java new file mode 100644 index 00000000000..062d6acf960 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/Util.java @@ -0,0 +1,61 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.InetAddress; + +/** + * + * @author Bjorn Borud + * @author arnej27959 + * + */ +public class Util { + + /** + * We do not have direct access to the <code>gethostname</code> + * system call, so we have to fake it. + */ + public static String getHostName () { + String hostname = "-"; + try { + Process p = Runtime.getRuntime().exec("hostname"); + BufferedReader r = new BufferedReader( + new InputStreamReader(p.getInputStream(), "UTF-8")); + hostname = r.readLine(); + if (hostname != null) { + return hostname; + } + } + catch (java.io.IOException e) {} + try { + hostname = InetAddress.getLocalHost().getHostName(); + return hostname; + } + catch (java.net.UnknownHostException e) {} + return "-"; + } + + /** + * Emulate the getpid() system call + **/ + public static String getPID() { + try { + Process p = Runtime.getRuntime().exec( + new String[] {"perl", "-e", "print getppid().\"\\n\";"} + ); + BufferedReader r = new BufferedReader( + new InputStreamReader(p.getInputStream(), "UTF-8")); + String line = r.readLine(); + p.destroy(); + int pid = Integer.parseInt(line); + if (pid > 0) { + return Integer.toString(pid); + } + } catch(Exception e) { + // any problem handled by return below + } + return "-"; + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/VespaFormat.java b/vespalog/src/main/java/com/yahoo/log/VespaFormat.java new file mode 100644 index 00000000000..a1efd02513e --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/VespaFormat.java @@ -0,0 +1,196 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Vespa log formatting utility methods. + * Contains some code based on Util.java in Cloudname https://github.com/Cloudname/cloudname + * written by Bjørn Borud, licensed under the Apache 2.0 license. + * + * @author arnej27959 + */ +public class VespaFormat { + + private static final Pattern special = Pattern.compile("[\r\\\n\\\t\\\\]+"); + private static final Pattern newLine = Pattern.compile("\n"); + private static final Pattern carriage = Pattern.compile("\r"); + private static final Pattern tab = Pattern.compile("\t"); + private static final Pattern backSlash = Pattern.compile("\\\\"); + + private static final String hostname; + private static final String processID; + + static { + hostname = Util.getHostName(); + processID = Util.getPID(); + } + + /** + * This static method is used to detect if a message needs + * to be escaped, and if so, performs the escaping. Since the + * common case is most likely that escaping is <em>not</em> + * needed, the code is optimized for this case. The forbidden + * characters are: + * + * <UL> + * <LI> newline + * <LI> tab + * <LI> backslash + * </UL> + * + * <P> + * Also handles the case where the message is <code>null</code> + * and replaces the null message with a tag saying that the + * value was "(empty)". + * + * @param s String that might need escaping + * @return Returns escaped string + * + */ + public static String escape (String s) { + if (s == null) { + return "(empty)"; + } + + Matcher m = special.matcher(s); + if (! m.find()) { + return s; + } + + // invariant: we had special characters + + m = backSlash.matcher(s); + if (m.find()) { + s = m.replaceAll("\\\\\\\\"); + } + + m = newLine.matcher(s); + if (m.find()) { + s = m.replaceAll("\\\\n"); + } + + m = carriage.matcher(s); + if (m.find()) { + s = m.replaceAll("\\\\r"); + } + + m = tab.matcher(s); + if (m.find()) { + s = m.replaceAll("\\\\t"); + } + + return s; + } + + + /** + * It is easier to slice and dice strings in Java than formatting + * numbers... + */ + public static void formatTime (long time, StringBuilder sbuffer) { + String timeString = Long.toString(time); + int len = timeString.length(); + + // something wrong. handle it by just returning the input + // long as a string. we prefer this to just crashing in + // the substring handling. + if (len < 3) { + sbuffer.append(timeString); + return; + } + sbuffer.append(timeString.substring(0, len - 3)); + sbuffer.append('.'); + sbuffer.append(timeString.substring(len - 3)); + } + + public static String format(String levelName, + String component, + String componentPrefix, + long millis, + String threadId, + String serviceName, + String formattedMessage, + Throwable t) + { + StringBuilder sbuf = new StringBuilder(300); // initial guess + + // format the time + formatTime(millis, sbuf); + sbuf.append("\t"); + + sbuf.append(hostname).append("\t"); + + sbuf.append(processID); + if (threadId != null) { + sbuf.append("/").append(threadId); + } + sbuf.append("\t"); + + sbuf.append(serviceName).append("\t"); + + if (component == null && componentPrefix == null) { + sbuf.append("-"); + } else if (component == null) { + sbuf.append(componentPrefix); + } else if (componentPrefix == null) { + sbuf.append(".").append(component); + } else { + sbuf.append(componentPrefix).append(".").append(component); + } + sbuf.append("\t"); + + sbuf.append(levelName).append("\t"); + + sbuf.append(escape(formattedMessage)); + if (t != null) { + formatException(t, sbuf); + } + sbuf.append("\n"); + return sbuf.toString(); + } + + /** + * Format throwable into given StringBuffer. + * + * @param t The Throwable we want to format + * @param sbuf The stringbuffer into which we wish to + * format the Throwable + */ + public static void formatException (Throwable t, StringBuilder sbuf) { + Throwable last = t; + int depth = 0; + while (last != null) { + sbuf.append("\\nmsg=\""); + sbuf.append(escape(last.getMessage())); + sbuf.append("\"\\nname=\""); + sbuf.append(escape(last.getClass().getName())); + sbuf.append("\"\\nstack=\"\\n"); + + // loop through stack frames and format them + StackTraceElement[] st = last.getStackTrace(); + int stopAt = Math.min(st.length, 15); + boolean first = true; + for (int i = 0; i < stopAt; i++) { + if (first) { + first = false; + } else { + sbuf.append("\\n"); + } + sbuf.append(escape(st[i].toString())); + } + + // tell the reader if we chopped off part of the stacktrace + if (stopAt < st.length) { + sbuf.append("\\n[...]"); + } + sbuf.append("\\n\""); + + last = last.getCause(); + depth++; + } + sbuf.append(" nesting=").append(depth); + } + +} diff --git a/vespalog/src/main/java/com/yahoo/log/VespaFormatter.java b/vespalog/src/main/java/com/yahoo/log/VespaFormatter.java new file mode 100644 index 00000000000..ee0f6e90b7c --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/VespaFormatter.java @@ -0,0 +1,172 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// $Id$ +package com.yahoo.log; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.logging.LogRecord; +import java.util.logging.SimpleFormatter; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.yahoo.log.event.Event; + +/** + * This class implements a log formatter which takes care of + * formatting messages according to the VESPA common log format. + * + * @author Bjorn Borud + * @author arnej27959 + * + */ +public class VespaFormatter extends SimpleFormatter { + + private static final Pattern backSlash = Pattern.compile("\\\\"); + + // other way around + private static final Pattern backSlashN = Pattern.compile("\\\\n"); + private static final Pattern backSlashT = Pattern.compile("\\\\t"); + private static final Pattern backSlash2 = Pattern.compile("\\\\\\\\"); + + private static final String hostname; + private static final String processID; + + public static final String serviceNameUnsetValue = "-"; + + static { + hostname = Util.getHostName(); + processID = Util.getPID(); + } + + private String serviceName; + private String componentPrefix; + + /** + * Default constructor + */ + public VespaFormatter() { + this.serviceName = serviceNameUnsetValue; + } + + /** + * @param serviceName The VESPA service name. + * @param componentPrefix The application name. + */ + public VespaFormatter(String serviceName, String componentPrefix) { + if (serviceName == null) { + this.serviceName = serviceNameUnsetValue; + } else { + this.serviceName = serviceName; + } + this.componentPrefix = componentPrefix; + } + + /** + * Un-escapes previously escaped string. + * note: look at com.yahoo.config.StringNode.unescapeQuotedString() + * + * @param s String that might need un-escaping + * @return Returns un-escaped string + */ + public static String unEscape(String s) { + Matcher m = backSlash.matcher(s); + if (! m.find()) { + return s; + } + m = backSlashN.matcher(s); + if (m.find()) { + s = m.replaceAll("\n"); + } + m = backSlashT.matcher(s); + if (m.find()) { + s = m.replaceAll("\t"); + } + m = backSlash2.matcher(s); + if (m.find()) { + s = m.replaceAll("\\\\"); + } + return s; + } + + public String format(LogRecord r) { + StringBuilder sbuf = new StringBuilder(300); // initial guess + + String levelName = LogLevel.getVespaLogLevel(r.getLevel()) + .toString() + .toLowerCase(); + + String component = r.getLoggerName(); + + // format the time + VespaFormat.formatTime(r.getMillis(), sbuf); + sbuf.append("\t"); + + sbuf.append(hostname).append("\t") + .append(processID).append("/") + .append(r.getThreadID()).append("\t") + .append(serviceName).append("\t"); + + if (component == null && componentPrefix == null) { + sbuf.append("-"); + } else if (component == null) { + sbuf.append(componentPrefix); + } else if (componentPrefix == null) { + sbuf.append(".").append(component); + } else { + sbuf.append(componentPrefix).append(".").append(component); + } + + sbuf.append("\t").append(levelName).append("\t"); + + // for events, there is no ordinary message string; + // instead we render a string represantion of the event object: + if (r.getLevel() == LogLevel.EVENT) { + Event event = (Event) r.getParameters()[0]; + sbuf.append(VespaFormat.escape(event.toString())); + } else { + // otherwise, run standard java text formatting on the message + sbuf.append(VespaFormat.escape(formatMessage(r))); + } + appendException(r.getThrown(), sbuf); + + + sbuf.append("\n"); + return sbuf.toString(); + } + + private void appendException(Throwable throwable, StringBuilder builder) { + if (throwable == null) + return; + + String escapedStackTrace = VespaFormat.escape(stackTrace(throwable)); + builder.append("\\n").append("exception=").append("\\n").append(escapedStackTrace); + } + + private String stackTrace(Throwable throwable) { + StringWriter writer = new StringWriter(); + PrintWriter wrappedWriter = new PrintWriter(writer); + throwable.printStackTrace(wrappedWriter); + wrappedWriter.close(); + return writer.toString(); + } + + + /** + * Set the service name (usually the VESPA config-id) of this + * formatter. + * + * @param serviceName The service name + */ + public void setServiceName (String serviceName) { + this.serviceName = serviceName; + } + + /** + * Get the service name for this formatter. + * + * @return Returns the service name. + */ + public String getServiceName () { + return serviceName; + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/VespaLevelControllerRepo.java b/vespalog/src/main/java/com/yahoo/log/VespaLevelControllerRepo.java new file mode 100644 index 00000000000..f2542437e5f --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/VespaLevelControllerRepo.java @@ -0,0 +1,236 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +import com.yahoo.text.Utf8; + +import java.io.RandomAccessFile; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.Enumeration; +import java.util.TimerTask; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +/** + * @author lulf + * @since 5.1 + */ +public class VespaLevelControllerRepo implements LevelControllerRepo { + + private RandomAccessFile ctlFile; + private MappedByteBuffer mapBuf; + private MappedLevelControllerRepo levelControllerRepo; + private final String logControlFilename; + private final String appPrefix; + + private static final int maxPrefix = 64; + private static final String CFHEADER = "Vespa log control file version 1\n"; + private static final String CFPREPRE = "Prefix: "; + + /** + * length of fixed header content of a control file, constant: + **/ + public static final int controlFileHeaderLength; + /** + * number of distinctly controlled levels (in logctl files), + * must be compatible with C++ Vespa logging + **/ + public static final int numLevels = 8; + + static { + controlFileHeaderLength = CFHEADER.length() + + CFPREPRE.length() + + 1 // newline + + maxPrefix + + 1; // newline + } + /** + * level controller for default levels (first-time logging + * or logging without a logcontrol file) + **/ + private LevelController defaultLevelCtrl; + private String defaultLogLevel; + + public VespaLevelControllerRepo(String logCtlFn, String logLevel, String applicationPrefix) { + this.logControlFilename = logCtlFn; + this.defaultLogLevel = logLevel; + this.appPrefix = applicationPrefix; + defaultLevelCtrl = new DefaultLevelController(defaultLogLevel); + openCtlFile(); + } + + private void resetCtlFile() { + // System.err.println("reset limit to: "+defaultLevelCtrl.getLevelLimit()); + Logger.getLogger("").setLevel(defaultLevelCtrl.getLevelLimit()); + try { + if (ctlFile != null) { + ctlFile.close(); + } + } catch (java.io.IOException ign) {} + ctlFile = null; + mapBuf = null; + levelControllerRepo = null; + } + + private void openCtlFile() { + if (ctlFile != null) { + // already done this + return; + } + if (logControlFilename == null) { + // System.err.println("initialize limit to: "+defaultLevelCtrl.getLevelLimit()); + Logger.getLogger("").setLevel(defaultLevelCtrl.getLevelLimit()); + // only default level controller, very little can be done + return; + } + + try { + ctlFile = new RandomAccessFile(logControlFilename, "rw"); + ensureHeader(); + extendMapping(); + + if (checkBackRunner == null) { + checkBackRunner = new CheckBackRunner(); + LogSetup.getTaskRunner().schedule(checkBackRunner, 1000, 9999); + } + } catch (java.io.IOException e) { + System.err.println("problem opening logcontrol file " + +logControlFilename+": "+e); + resetCtlFile(); + } + } + + private void ensureHeader() throws java.io.IOException { + byte[] hbytes = Utf8.toBytes(CFHEADER); + byte[] rbytes = new byte[hbytes.length]; + + ctlFile.seek(0); + int l = ctlFile.read(rbytes); + if (l != hbytes.length + || !java.util.Arrays.equals(hbytes, rbytes)) + { + ctlFile.seek(0); + ctlFile.write(hbytes); + ctlFile.writeBytes(CFPREPRE); + int appLen = 0; + if (appPrefix != null) { + appLen = appPrefix.length(); + ctlFile.writeBytes(appPrefix); + } + ctlFile.writeBytes("\n"); + for (int i = appLen; i < maxPrefix; i++) { + byte space = ' '; + ctlFile.write(space); + } + ctlFile.writeBytes("\n"); + ctlFile.setLength(ctlFile.getFilePointer()); + if (ctlFile.getFilePointer() != controlFileHeaderLength) { + System.err.println("internal error, bad header length: " + + ctlFile.getFilePointer() + + " (should have been: " + + controlFileHeaderLength + + ")"); + } + } + } + + private void extendMapping() throws java.io.IOException { + if (ctlFile == null) return; + long pos = 0; + long len = ctlFile.length(); + if (mapBuf == null || mapBuf.capacity() != len) { + mapBuf = ctlFile.getChannel().map(FileChannel.MapMode.READ_ONLY, pos, len); + } + levelControllerRepo = new MappedLevelControllerRepo(mapBuf, controlFileHeaderLength, numLevels, logControlFilename); + } + + public LevelController getLevelControl(String suffix) { + LevelController ctrl = null; + if (levelControllerRepo != null) { + if (suffix == null || suffix.equals("default")) { + suffix = ""; + } + ctrl = levelControllerRepo.getLevelController(suffix); + if (ctrl != null) { + return ctrl; + } + synchronized(this) { + LevelController inherit = null; + + int lastdot = suffix.lastIndexOf('.'); + if (lastdot != -1) { + // inherit from level above + inherit = getLevelControl(suffix.substring(0, lastdot)); + } else if (suffix.equals("")) { + // the one and only toplevel inherits from other mechanism + inherit = defaultLevelCtrl; + } else { + // everything else inherits from toplevel + inherit = getLevelControl(""); + } + try { + long len = ctlFile.length(); + String append; + if (suffix.equals("")) { + append = "default" + ": "; + } else { + append = "." + suffix + ": "; + } + while ((len + append.length()) % 4 != 0) { + append = append + " "; + } + append = append + inherit.getOnOffString() + "\n"; + ctlFile.seek(ctlFile.length()); + ctlFile.writeBytes(append); + extendMapping(); + ctrl = levelControllerRepo.getLevelController(suffix); + } catch(java.nio.channels.ClosedByInterruptException e) { + // happens during shutdown, ignore + // System.err.println("interrupt, reset logcontrol file: "+e); + resetCtlFile(); + } catch(java.io.IOException e) { + System.err.println("error extending logcontrol file: "+e); + e.printStackTrace(); + resetCtlFile(); + } + } + } + if (ctrl == null) { + return defaultLevelCtrl; + } else { + return ctrl; + } + } + + + private void checkBack() { + if (levelControllerRepo != null) { + Enumeration<String> e = LogManager.getLogManager().getLoggerNames(); + while (e.hasMoreElements()) { + String name = e.nextElement(); + LevelController ctrl = getLevelControl(name); + ctrl.checkBack(); + } + levelControllerRepo.checkBack(); + } + } + + @Override + public LevelController getLevelController(String component) { + return getLevelControl(component); + } + + private class CheckBackRunner extends TimerTask { + public void run() { + checkBack(); + } + } + private CheckBackRunner checkBackRunner; + + @Override + public void close() { + if (checkBackRunner != null) { + checkBackRunner.cancel(); + } + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/VespaLogHandler.java b/vespalog/src/main/java/com/yahoo/log/VespaLogHandler.java new file mode 100644 index 00000000000..26208340132 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/VespaLogHandler.java @@ -0,0 +1,136 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log; + +import java.io.UnsupportedEncodingException; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.StreamHandler; + +/** + * @author Bjorn Borud + * @author arnej27959 + */ +class VespaLogHandler extends StreamHandler { + + private final LogTarget logTarget; + private final String serviceName; + private final String appPrefix; + private final LevelControllerRepo repo; + private final RejectFilter logRejectFilter; + + /** + * Construct handler which logs to specified logTarget. The logTarget + * may be of the following formats: + * + * <DL> + * <DT> <code>fd:<number></code> + * <DD> Log to specified file descriptor number. Only "fd:2" + * is supported. + * + * <DT> <code>file:<filename></code> + * <DD> Log to specified file in append mode + * </DL> + */ + public VespaLogHandler(LogTarget logTarget, + LevelControllerRepo levelControllerRepo, String serviceName, + String applicationPrefix) { + this.logTarget = logTarget; + this.serviceName = serviceName; + this.appPrefix = applicationPrefix; + this.repo = levelControllerRepo; + this.logRejectFilter = RejectFilter.createDefaultRejectFilter(); + initialize(); + } + + /** + * Publish a log record into the Vespa log target. + */ + public synchronized void publish (LogRecord record) { + Level level = record.getLevel(); + String component = record.getLoggerName(); + + LevelController ctrl = getLevelControl(component); + if (!ctrl.shouldLog(level)) { + return; + } + + if (logRejectFilter.shouldReject(record.getMessage())) { + return; + } + + try { + // provokes rotation of target + setOutputStream(logTarget.open()); + } catch (RuntimeException e) { + LogRecord r = new LogRecord(Level.SEVERE, + "Unable to open file target"); + r.setThrown(e); + emergencyLog(r); + setOutputStream(System.err); + } + super.publish(record); + flush(); + closeFileTarget(); + } + + public LevelController getLevelControl(String component) { + return repo.getLevelController(component); + } + + /** + * Initalize the handler. The main invariant is that + * outputStream is always set to something valid when this method + * returns. + */ + private void initialize () { + try { + setFormatter(new VespaFormatter(serviceName, appPrefix)); + setLevel(LogLevel.ALL); + setEncoding("UTF-8"); + // System.err.println("initialize vespa logging, default level: "+defaultLogLevel); + setOutputStream(logTarget.open()); + } + catch (UnsupportedEncodingException uee) { + LogRecord r = new LogRecord(Level.SEVERE, "Unable to set log encoding to UTF-8"); + r.setThrown(uee); + emergencyLog(r); + } + catch (RuntimeException e) { + LogRecord r = new LogRecord(Level.SEVERE, + "Unable to open file target"); + r.setThrown(e); + emergencyLog(r); + setOutputStream(System.err); + } + } + + + /** Closes the target log file, if there is one */ + public void closeFileTarget() { + try { + logTarget.close(); + } + catch (RuntimeException e) { + LogRecord r = new LogRecord(Level.WARNING, "Unable to close log"); + r.setThrown(e); + emergencyLog(r); + } + } + + /** + * If the logging system experiences problems we can't be expected + * to log it through normal channels, so we have an emergency log + * method which just uses STDERR for formatting the log messages. + * (Which might be right, and might be wrong). + * + * @param record The log record to be logged + */ + private void emergencyLog(LogRecord record) { + record.setLoggerName(VespaLogHandler.class.getName()); + System.err.println(getFormatter().format(record)); + } + + public void cleanup() { + repo.close(); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/Collection.java b/vespalog/src/main/java/com/yahoo/log/event/Collection.java new file mode 100644 index 00000000000..da5c1180721 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/Collection.java @@ -0,0 +1,16 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log.event; + +/** + * + * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a> + */ +public class Collection extends Event { + public Collection () { + } + + public Collection (long collectionId, String name) { + setValue("collectionId", Long.toString(collectionId)); + setValue("name", name); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/Count.java b/vespalog/src/main/java/com/yahoo/log/event/Count.java new file mode 100644 index 00000000000..f4a61f85de6 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/Count.java @@ -0,0 +1,32 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log.event; + +/** + * + * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a> + */ +public class Count extends Event { + public Count () { + } + + public Count (String name, double value) { + setValue("name", name); + setValue("value", Double.toString(value)); + } + + /** + * Set a property. + * + * @param name The name of the property + * @param value The value of the property + */ + @Override + public Event setValue (String name, String value) { + if (name.equals("value")) { + super.setValue(name, Long.toString((new Double(value)).longValue())); + } else { + super.setValue(name , value); + } + return this; + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/CountGroup.java b/vespalog/src/main/java/com/yahoo/log/event/CountGroup.java new file mode 100755 index 00000000000..65d94670680 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/CountGroup.java @@ -0,0 +1,16 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log.event; + +public class CountGroup extends Event { + public CountGroup () { + } + + public CountGroup (String name, String values) { + init(name, values); + } + + private void init (String name, String counts) { + setValue("name", name); + setValue("values", counts); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/Crash.java b/vespalog/src/main/java/com/yahoo/log/event/Crash.java new file mode 100644 index 00000000000..a86124c1d9e --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/Crash.java @@ -0,0 +1,17 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log.event; + +/** + * + * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a> + */ +public class Crash extends Event { + public Crash () { + } + + public Crash (String name, int pid, int signal) { + setValue("name", name); + setValue("pid", Integer.toString(pid)); + setValue("signal", Integer.toString(signal)); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/Event.java b/vespalog/src/main/java/com/yahoo/log/event/Event.java new file mode 100644 index 00000000000..46caea4fc20 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/Event.java @@ -0,0 +1,494 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log.event; + +import com.yahoo.log.LogLevel; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * The Event class is the base class for all VESPA defined events. + * All specific Event classes extend this abstract class. An event + * is more or less a type and a set of properties. For convenience + * we use the logging system to transport Event instances, so the + * typical use is to serialize events into the payload of log + * messages. + * + * <P> + * Note that the static methods started(), stopped() etc are for use + * with this class so using them in the subclasses isn't really + * sanctioned. These methods are what the user wants to use for + * logging events, rather than making events him/herself and stuffing + * them through the logging API. + * + * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a> + */ +public abstract class Event implements Serializable { + private static Logger log = Logger.getLogger(Event.class.getName()); + + // states for the event parameters + private static final int INITIAL = 0; + private static final int IN_NAME = 1; + private static final int IN_UNQUOTED = 2; + private static final int IN_QUOTED = 3; + private static final int EQUALS = 4; + + private static Pattern whitespace = Pattern.compile("\\s"); + private static Pattern eventFmt = Pattern.compile("^([^/]+)/(\\d+)(.*)$"); + + // stash the package name for the instantiation + private static String packageName = Event.class.getPackage().getName(); + + private Map<String,String> values = new LinkedHashMap<String,String>(5); + + // default version number is always 1 + private int version = 1; + + private long time = -1; + + protected Event () {} + + /** + * Set a property. + * + * @param name The name of the property + * @param value The value of the property + */ + public Event setValue (String name, String value) { + values.put(name, value); + return this; + } + + /** + * Get a property value. + */ + public String getValue (String name) { + return values.get(name); + } + + /** + * Set the timestamp of this event. + */ + public void setTime (long time) { + this.time = time; + } + + /** + * Get the timestamp of this event + * + * @return returns the timestamp of this event + */ + public long getTime () { + return time; + } + + /** + * Set event version + * + * @param version The version of the event. + */ + public Event setVersion (int version) { + this.version = version; + return this; + } + + /** + * Get the event version. + * + * @return event version + */ + public int getVersion () { + return version; + } + + /** + * Convenience method which returns a property. If the + * property contains whitespace the property will be + * enclosed in quotes. + * + * FIXME: quotes inside the value are not quoted + * + */ + public String getValuePossiblyQuote (String name) { + String tmp = values.get(name); + if (tmp == null) { + return ""; + } + + Matcher m = whitespace.matcher(tmp); + if (m.find()) { + return new StringBuffer(tmp.length() + 2) + .append("\"") + .append(tmp) + .append("\"") + .toString(); + } + + return tmp; + } + + /** + * Get the name of the event instance. + * + * @return the name of the event instance. + */ + public String getName() { + String tmp = this.getClass().getName(); + int last = tmp.lastIndexOf("."); + if (last == -1) { + return tmp.toLowerCase(); + } + + return tmp.substring(last+1).toLowerCase(); + } + + /** + * This method returns the string representation of the + * event and must return something that can be parsed + * by the parse method. + */ + public String toString () { + StringBuilder buff = new StringBuilder(128) + .append(getName()) + .append("/") + .append(version); + + for (String name : values.keySet()) { + buff.append(" ") + .append(name) + .append("=") + .append(getValuePossiblyQuote(name)); + } + + return buff.toString(); + } + + + /** + * Okay, so I am good at making state machine based parsers, + * but every time I do it it feels uncomfortable because it + * isn't the sort of thing one ought to do by freehand :-). + * + * <P> + * Enjoy the graphic + * + * <PRE> + * ___ + * ',_`""\ .---, + * \ :~""``/` | + * `;' //`\ / + * / __ | ('. + * |_ ./O)\ \ `) \ + * _/-. ` `"` |`-. + * .-=: ` / `-. + * /o o \ ,_, . '. + * L._._;_.-' . `'-. + * `'-.` ' `'-. + * `. ' `-._ + * '-._. -' '. + * \ `\ + * + * + * + * </PRE> + */ + private static void parseValuePairs (String s, Event event) { + int state = INITIAL; + int i = 0; + int mark = 0; + String name = null; + + while (i < s.length()) { + switch(s.charAt(i)) { + + case ' ': + if (state == IN_UNQUOTED) { + state = INITIAL; + event.setValue(name, s.substring(mark, i)); + } + + if (state == INITIAL) { + mark = -1; + break; + } + + if (state == IN_QUOTED) { + break; + } + + throw new IllegalStateException("space not allowed at " + i); + + case '=': + if (state == IN_NAME) { + name = s.substring(mark, i); + state = EQUALS; + break; + } + if (state == IN_QUOTED) { + break; + } + + throw new IllegalStateException("'=' not allowed at " + i); + + case '"': + if (state == EQUALS) { + state = IN_QUOTED; + mark = i; + break; + } + + if (state == IN_QUOTED) { + // skip escaped + if (s.charAt(i-1) == '\\') { + break; + } + event.setValue(name, s.substring(mark+1, i)); + state = INITIAL; + break; + } + + throw new IllegalStateException("'\"' not allowed at " + i); + + // ordinary characters + default: + if (state == INITIAL) { + state = IN_NAME; + mark = i; + break; + } + + if (state == EQUALS) { + state = IN_UNQUOTED; + mark = i; + break; + } + } + i++; + } + + // mopping up. when there is no more input to be processed + // we need to take action if we are in one of the below states + switch (state) { + case IN_UNQUOTED: + event.setValue(name, s.substring(mark, i)); + break; + + case IN_QUOTED: + event.setValue(name, s.substring(mark+1, i)); + break; + + case IN_NAME: + throw new IllegalStateException("ended in name"); + + case EQUALS: + event.setValue(name, null); + break; + } + } + + + /** + * Parse string representation of Event and emit correct Event + * subtype. + * + * @param s A string containing an event + * @return Event represented by <code>s</code>. + * @throws MalformedEventException if unable to deciper Event + * from string. + */ + public static Event parse (String s) throws MalformedEventException { + Matcher m1 = eventFmt.matcher(s); + if (! m1.matches()) { + throw new MalformedEventException(s); + } + String eventName = m1.group(1); + String eventVersion = m1.group(2); + String rest = m1.group(3); + + String className = new StringBuffer(eventName.length() + + packageName.length() + + 1) + .append(packageName) + .append(".") + .append(eventName.substring(0,1).toUpperCase()) + .append(eventName.substring(1).toLowerCase()) + .toString(); + + Event event; + try { + event = (Event) Class.forName(className).newInstance(); + } + catch (ClassNotFoundException e) { + event = new Unknown().setName(eventName); + } + catch (Exception e) { + log.log(Level.WARNING, "Event instantiation problem", e); + return null; + } + + event.setVersion(Integer.parseInt(eventVersion)); + + try { + parseValuePairs(rest, event); + } + catch (IllegalStateException | NumberFormatException e) { + throw new MalformedEventException(e); + } + + return event; + } + + /** + * Find the stack frame of the last called before we entered + * the Event class and get the Logger belonging to that class. + * If for some reason we fail to do this, just return the + * default Logger. + * + * <P> + * Beware, if you come here for cleverness and enlightenment + * then know that this technique might not be exceptionally fast + * so don't abuse this mechanism blindly. (Do what I do: abuse + * it with both eyes open :-). + * + */ + private static final Logger getCallerLogger() { + StackTraceElement stack[] = (new Throwable()).getStackTrace(); + int i = 0; + while (i < stack.length) { + StackTraceElement frame = stack[i]; + String cname = frame.getClassName(); + if (cname.equals("com.yahoo.log.event.Event")) { + break; + } + i++; + } + + while (i < stack.length) { + StackTraceElement frame = stack[i]; + String cname = frame.getClassName(); + if (!cname.equals("com.yahoo.log.event.Event")) { + return Logger.getLogger(cname); + } + i++; + } + + return Logger.getLogger(""); + } + + /** + * Internal method which prepares Event log messages. Not + * the prettiest way to do it... + */ + private static final void log(Logger logger, Object param) { + LogRecord r = new LogRecord(LogLevel.EVENT, null); + r.setParameters(new Object[] {param}); + r.setLoggerName(logger.getName()); + logger.log(r); + } + + /** + * Static method for logging the <b>starting</b> event. + */ + public static final void starting (String name) { + log(getCallerLogger(), new Starting(name)); + } + + /** + * Static method for logging the <b>started</b> event. + */ + public static final void started (String name) { + log(getCallerLogger(), new Started(name)); + } + + /** + * Static method for logging the <b>stopping</b> event. + */ + public static final void stopping (String name, String why) { + log(getCallerLogger(), new Stopping(name, why)); + } + + /** + * Static method for logging the <b>stopped</b> event. + */ + public static final void stopped (String name, int pid, int exitcode) { + log(getCallerLogger(), new Stopped(name, pid, exitcode)); + } + + /** + * Static method for logging the <b>reloading</b> event. + */ + public static final void reloading (String name) { + log(getCallerLogger(), new Reloading(name)); + } + + /** + * Static method for logging the <b>reloaded</b> event. + */ + public static final void reloaded (String name) { + log(getCallerLogger(), new Reloaded(name)); + } + + /** + * Static method for logging the <b>count</b> event. + */ + public static final void count (String name, long value) { + log(getCallerLogger(), new Count(name, value)); + } + + /** + * Static method for logging the <b>value</b> event. + */ + public static final void value (String name, double value) { + log(getCallerLogger(), new Value(name, value)); + } + + /** + * Static method for logging the <b>histogram</b> event. + */ + public static final void histogram (String name, String value, + String representation) { + log(getCallerLogger(), new Histogram(name, value, + representation)); + } + + /** + * Static method for logging a set of <b>value</b> events. + */ + public static final void valueGroup (String name, String value) { + log(getCallerLogger(), new ValueGroup(name, value)); + } + + /** + * Static method for logging a set of <b>count</b> events. + */ + public static final void countGroup (String name, String value) { + log(getCallerLogger(), new CountGroup(name, value)); + } + + /** + * Static method for logging the <b>progress</b> event. + */ + public static final void progress (String name, long value, long total) { + log(getCallerLogger(), new Progress(name, value, total)); + } + + /** + * Static method for logging the <b>state</b> event. + */ + public static final void state (String name, String value) { + log(getCallerLogger(), new State(name, value)); + } + + /** + * Static method for logging the <b>crash</b> event. + */ + public static final void crash (String name, int pid, int signal) { + log(getCallerLogger(), new Crash(name, pid, signal)); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/Histogram.java b/vespalog/src/main/java/com/yahoo/log/event/Histogram.java new file mode 100755 index 00000000000..219f801deb1 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/Histogram.java @@ -0,0 +1,17 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log.event; + +public class Histogram extends Event { + public Histogram () { + } + + public Histogram (String name, String value, String representation) { + init(name, value, representation); + } + + private void init (String name, String value, String representation) { + setValue("name", name); + setValue("counts", value); + setValue("representation", representation); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/MalformedEventException.java b/vespalog/src/main/java/com/yahoo/log/event/MalformedEventException.java new file mode 100644 index 00000000000..3b67a19c916 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/MalformedEventException.java @@ -0,0 +1,15 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log.event; + +public class MalformedEventException extends Exception { + public MalformedEventException (Throwable cause) { + super(cause); + } + + public MalformedEventException (String msg) { + super(msg); + } + + public MalformedEventException () { + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/Progress.java b/vespalog/src/main/java/com/yahoo/log/event/Progress.java new file mode 100644 index 00000000000..70df650764c --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/Progress.java @@ -0,0 +1,33 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log.event; + +/** + * + * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a> + */ +public class Progress extends Event { + public Progress () { + } + + public Progress (String name, String value, String total) { + init(name, value, total); + } + + public Progress (String name, double value, double total) { + init(name, Double.toString(value), Double.toString(total)); + } + + public Progress (String name, String value) { + init(name, value, ""); + } + + public Progress (String name, double value) { + init(name, Double.toString(value), ""); + } + + private void init (String name, String value, String total) { + setValue("name", name); + setValue("value", value); + setValue("total", total); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/Reloaded.java b/vespalog/src/main/java/com/yahoo/log/event/Reloaded.java new file mode 100644 index 00000000000..0919b847065 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/Reloaded.java @@ -0,0 +1,15 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log.event; + +/** + * + * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a> + */ +public class Reloaded extends Event { + public Reloaded () { + } + + public Reloaded (String name) { + setValue("name", name); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/Reloading.java b/vespalog/src/main/java/com/yahoo/log/event/Reloading.java new file mode 100644 index 00000000000..001eb00522c --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/Reloading.java @@ -0,0 +1,15 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log.event; + +/** + * + * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a> + */ +public class Reloading extends Event { + public Reloading () { + } + + public Reloading (String name) { + setValue("name", name); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/Started.java b/vespalog/src/main/java/com/yahoo/log/event/Started.java new file mode 100644 index 00000000000..a955dc0b2b2 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/Started.java @@ -0,0 +1,15 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log.event; + +/** + * + * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a> + */ +public class Started extends Event { + public Started () { + } + + public Started (String name) { + setValue("name", name); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/Starting.java b/vespalog/src/main/java/com/yahoo/log/event/Starting.java new file mode 100644 index 00000000000..beb0740323f --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/Starting.java @@ -0,0 +1,15 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log.event; + +/** + * + * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a> + */ +public class Starting extends Event { + public Starting () { + } + + public Starting (String name) { + setValue("name", name); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/State.java b/vespalog/src/main/java/com/yahoo/log/event/State.java new file mode 100755 index 00000000000..869178cb435 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/State.java @@ -0,0 +1,16 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log.event; + +public class State extends Event { + public State () { + } + + public State (String name, String value) { + init(name, value); + } + + private void init (String name, String value) { + setValue("name", name); + setValue("value", value); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/Stopped.java b/vespalog/src/main/java/com/yahoo/log/event/Stopped.java new file mode 100644 index 00000000000..e61239b4b67 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/Stopped.java @@ -0,0 +1,17 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log.event; + +/** + * + * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a> + */ +public class Stopped extends Event { + public Stopped () { + } + + public Stopped (String name, int pid, int exitcode) { + setValue("name", name); + setValue("pid", Integer.toString(pid)); + setValue("exitcode", Integer.toString(exitcode)); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/Stopping.java b/vespalog/src/main/java/com/yahoo/log/event/Stopping.java new file mode 100644 index 00000000000..49ad9d94db2 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/Stopping.java @@ -0,0 +1,16 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log.event; + +/** + * + * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a> + */ +public class Stopping extends Event { + public Stopping () { + } + + public Stopping (String name, String why) { + setValue("name", name); + setValue("why", why); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/Unknown.java b/vespalog/src/main/java/com/yahoo/log/event/Unknown.java new file mode 100644 index 00000000000..864aa2adf0f --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/Unknown.java @@ -0,0 +1,23 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log.event; + +/** + * + * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud </a> + */ +public class Unknown extends Event { + public Unknown() { + } + + private String name; + + public Unknown setName(String name) { + this.name = name; + return this; + } + + @Override + public String getName() { + return this.name.toLowerCase(); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/Value.java b/vespalog/src/main/java/com/yahoo/log/event/Value.java new file mode 100644 index 00000000000..ac6b80aad40 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/Value.java @@ -0,0 +1,16 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log.event; + +/** + * + * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a> + */ +public class Value extends Event { + public Value () { + } + + public Value (String name, double value) { + setValue("name", name); + setValue("value", Double.toString(value)); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/ValueGroup.java b/vespalog/src/main/java/com/yahoo/log/event/ValueGroup.java new file mode 100755 index 00000000000..d201d643159 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/ValueGroup.java @@ -0,0 +1,16 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.log.event; + +public class ValueGroup extends Event { + public ValueGroup () { + } + + public ValueGroup (String name, String values) { + init(name, values); + } + + private void init (String name, String value) { + setValue("name", name); + setValue("values", value); + } +} diff --git a/vespalog/src/main/java/com/yahoo/log/event/package-info.java b/vespalog/src/main/java/com/yahoo/log/event/package-info.java new file mode 100644 index 00000000000..8e69bd447fc --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/event/package-info.java @@ -0,0 +1,7 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +@PublicApi +package com.yahoo.log.event; + +import com.yahoo.api.annotations.PublicApi; +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/vespalog/src/main/java/com/yahoo/log/package-info.java b/vespalog/src/main/java/com/yahoo/log/package-info.java new file mode 100644 index 00000000000..ca784ea8283 --- /dev/null +++ b/vespalog/src/main/java/com/yahoo/log/package-info.java @@ -0,0 +1,7 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +@PublicApi +package com.yahoo.log; + +import com.yahoo.api.annotations.PublicApi; +import com.yahoo.osgi.annotation.ExportPackage; |