summaryrefslogtreecommitdiffstats
path: root/vespalog/src/main/java/com/yahoo/log
diff options
context:
space:
mode:
Diffstat (limited to 'vespalog/src/main/java/com/yahoo/log')
-rw-r--r--vespalog/src/main/java/com/yahoo/log/DefaultLevelController.java66
-rw-r--r--vespalog/src/main/java/com/yahoo/log/FileLogTarget.java44
-rw-r--r--vespalog/src/main/java/com/yahoo/log/InvalidLogFormatException.java18
-rw-r--r--vespalog/src/main/java/com/yahoo/log/LevelController.java38
-rw-r--r--vespalog/src/main/java/com/yahoo/log/LevelControllerRepo.java23
-rw-r--r--vespalog/src/main/java/com/yahoo/log/LogLevel.java167
-rw-r--r--vespalog/src/main/java/com/yahoo/log/LogMessage.java151
-rw-r--r--vespalog/src/main/java/com/yahoo/log/LogMessageTimeComparator.java45
-rw-r--r--vespalog/src/main/java/com/yahoo/log/LogSetup.java198
-rw-r--r--vespalog/src/main/java/com/yahoo/log/LogTarget.java21
-rw-r--r--vespalog/src/main/java/com/yahoo/log/LogUtil.java12
-rw-r--r--vespalog/src/main/java/com/yahoo/log/MappedLevelController.java120
-rw-r--r--vespalog/src/main/java/com/yahoo/log/MappedLevelControllerRepo.java114
-rw-r--r--vespalog/src/main/java/com/yahoo/log/RejectFilter.java37
-rw-r--r--vespalog/src/main/java/com/yahoo/log/StderrLogTarget.java21
-rw-r--r--vespalog/src/main/java/com/yahoo/log/StdoutLogTarget.java21
-rw-r--r--vespalog/src/main/java/com/yahoo/log/UncloseableOutputStream.java28
-rw-r--r--vespalog/src/main/java/com/yahoo/log/Util.java61
-rw-r--r--vespalog/src/main/java/com/yahoo/log/VespaFormat.java196
-rw-r--r--vespalog/src/main/java/com/yahoo/log/VespaFormatter.java172
-rw-r--r--vespalog/src/main/java/com/yahoo/log/VespaLevelControllerRepo.java236
-rw-r--r--vespalog/src/main/java/com/yahoo/log/VespaLogHandler.java136
-rw-r--r--vespalog/src/main/java/com/yahoo/log/event/Collection.java16
-rw-r--r--vespalog/src/main/java/com/yahoo/log/event/Count.java32
-rwxr-xr-xvespalog/src/main/java/com/yahoo/log/event/CountGroup.java16
-rw-r--r--vespalog/src/main/java/com/yahoo/log/event/Crash.java17
-rw-r--r--vespalog/src/main/java/com/yahoo/log/event/Event.java494
-rwxr-xr-xvespalog/src/main/java/com/yahoo/log/event/Histogram.java17
-rw-r--r--vespalog/src/main/java/com/yahoo/log/event/MalformedEventException.java15
-rw-r--r--vespalog/src/main/java/com/yahoo/log/event/Progress.java33
-rw-r--r--vespalog/src/main/java/com/yahoo/log/event/Reloaded.java15
-rw-r--r--vespalog/src/main/java/com/yahoo/log/event/Reloading.java15
-rw-r--r--vespalog/src/main/java/com/yahoo/log/event/Started.java15
-rw-r--r--vespalog/src/main/java/com/yahoo/log/event/Starting.java15
-rwxr-xr-xvespalog/src/main/java/com/yahoo/log/event/State.java16
-rw-r--r--vespalog/src/main/java/com/yahoo/log/event/Stopped.java17
-rw-r--r--vespalog/src/main/java/com/yahoo/log/event/Stopping.java16
-rw-r--r--vespalog/src/main/java/com/yahoo/log/event/Unknown.java23
-rw-r--r--vespalog/src/main/java/com/yahoo/log/event/Value.java16
-rwxr-xr-xvespalog/src/main/java/com/yahoo/log/event/ValueGroup.java16
-rw-r--r--vespalog/src/main/java/com/yahoo/log/event/package-info.java7
-rw-r--r--vespalog/src/main/java/com/yahoo/log/package-info.java7
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:&lt;number&gt;</code>
+ * <DD> Log to specified file descriptor number. Only "fd:2"
+ * is supported.
+ *
+ * <DT> <code>file:&lt;filename&gt;</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;