aboutsummaryrefslogtreecommitdiffstats
path: root/vespalog
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /vespalog
Publish
Diffstat (limited to 'vespalog')
-rw-r--r--vespalog/.gitignore15
-rw-r--r--vespalog/CMakeLists.txt21
-rw-r--r--vespalog/INSTALL17
-rw-r--r--vespalog/OWNERS1
-rw-r--r--vespalog/README183
-rw-r--r--vespalog/README.events158
-rw-r--r--vespalog/README.server.protocol46
-rw-r--r--vespalog/pom.xml71
-rw-r--r--vespalog/src/.gitignore5
-rw-r--r--vespalog/src/Log.pm93
-rw-r--r--vespalog/src/logctl/.gitignore3
-rw-r--r--vespalog/src/logctl/CMakeLists.txt9
-rw-r--r--vespalog/src/logctl/logctl.cpp273
-rw-r--r--vespalog/src/logfmt/.gitignore1
-rw-r--r--vespalog/src/logfmt/logfilter.c78
-rw-r--r--vespalog/src/logfmt/logfmt.1218
-rw-r--r--vespalog/src/logfmt/logfmt.pl327
-rw-r--r--vespalog/src/logger/.gitignore5
-rw-r--r--vespalog/src/logger/CMakeLists.txt26
-rw-r--r--vespalog/src/logger/llreader.cpp113
-rw-r--r--vespalog/src/logger/llreader.h42
-rw-r--r--vespalog/src/logger/logger.cpp56
-rw-r--r--vespalog/src/logger/logreplay.c58
-rw-r--r--vespalog/src/logger/runserver.cpp470
-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
-rw-r--r--vespalog/src/test/.gitignore14
-rw-r--r--vespalog/src/test/CMakeLists.txt24
-rw-r--r--vespalog/src/test/bufferedlogskiptest.cpp90
-rw-r--r--vespalog/src/test/bufferedlogskiptest.skipped.log32
-rwxr-xr-xvespalog/src/test/bufferedlogskiptest_test.sh3
-rw-r--r--vespalog/src/test/bufferedlogtest.cpp408
-rw-r--r--vespalog/src/test/bufferedlogtest.expire.log110
-rw-r--r--vespalog/src/test/bufferedlogtest.highcountexpire.log108
-rw-r--r--vespalog/src/test/bufferedlogtest.highcountkept.log106
-rw-r--r--vespalog/src/test/bufferedlogtest.locationastoken.log5
-rw-r--r--vespalog/src/test/bufferedlogtest.logger1.cpp11
-rw-r--r--vespalog/src/test/bufferedlogtest.logger1.h8
-rw-r--r--vespalog/src/test/bufferedlogtest.logger2.cpp11
-rw-r--r--vespalog/src/test/bufferedlogtest.logger2.h8
-rw-r--r--vespalog/src/test/bufferedlogtest.messageastoken.log21
-rw-r--r--vespalog/src/test/bufferedlogtest.nostarve.log26
-rw-r--r--vespalog/src/test/bufferedlogtest.tokenacrossloggers.log5
-rw-r--r--vespalog/src/test/bufferedlogtest.trimcache.log4
-rwxr-xr-xvespalog/src/test/bufferedlogtest_test.sh3
-rw-r--r--vespalog/src/test/java/com/yahoo/log/FileLogTargetTest.java48
-rw-r--r--vespalog/src/test/java/com/yahoo/log/LogLevelTestCase.java78
-rw-r--r--vespalog/src/test/java/com/yahoo/log/LogSetupTestCase.java153
-rw-r--r--vespalog/src/test/java/com/yahoo/log/LogUtilTest.java22
-rw-r--r--vespalog/src/test/java/com/yahoo/log/RejectFilterTest.java32
-rw-r--r--vespalog/src/test/java/com/yahoo/log/TimeComparatorTestCase.java71
-rw-r--r--vespalog/src/test/java/com/yahoo/log/UncloseableOutputStreamTestCase.java28
-rw-r--r--vespalog/src/test/java/com/yahoo/log/UtilTestCase.java22
-rw-r--r--vespalog/src/test/java/com/yahoo/log/VespaFormatterTestCase.java264
-rw-r--r--vespalog/src/test/java/com/yahoo/log/VespaLevelControllerRepoTest.java107
-rw-r--r--vespalog/src/test/java/com/yahoo/log/VespaLogHandlerTestCase.java440
-rw-r--r--vespalog/src/test/java/com/yahoo/log/event/EventTestCase.java199
-rw-r--r--vespalog/src/test/java/com/yahoo/log/event/SingleHandler.java20
-rw-r--r--vespalog/src/test/logtest.cpp37
-rw-r--r--vespalog/src/test/rejectfiltertest.cpp57
-rw-r--r--vespalog/src/test/simple/.gitignore4
-rw-r--r--vespalog/src/test/simple/CMakeLists.txt8
-rw-r--r--vespalog/src/test/simple/expect.lctl5
-rw-r--r--vespalog/src/test/simple/logtest.cpp37
-rw-r--r--vespalog/src/test/threads/.gitignore2
-rw-r--r--vespalog/src/test/threads/CMakeLists.txt8
-rw-r--r--vespalog/src/test/threads/testthreads.cpp139
-rw-r--r--vespalog/src/vespa/log/.gitignore8
-rw-r--r--vespalog/src/vespa/log/CMakeLists.txt28
-rw-r--r--vespalog/src/vespa/log/bufferedlogger.cpp382
-rw-r--r--vespalog/src/vespa/log/bufferedlogger.h206
-rw-r--r--vespalog/src/vespa/log/component.cpp121
-rw-r--r--vespalog/src/vespa/log/component.h22
-rw-r--r--vespalog/src/vespa/log/control-file.cpp424
-rw-r--r--vespalog/src/vespa/log/control-file.h86
-rwxr-xr-xvespalog/src/vespa/log/create-multiarg-file.pl104
-rw-r--r--vespalog/src/vespa/log/event-count-multiarg.cpp274
-rw-r--r--vespalog/src/vespa/log/event-count-multiarg.h76
-rw-r--r--vespalog/src/vespa/log/event-value-multiarg.cpp274
-rw-r--r--vespalog/src/vespa/log/event-value-multiarg.h76
-rw-r--r--vespalog/src/vespa/log/internal.cpp23
-rw-r--r--vespalog/src/vespa/log/internal.h30
-rw-r--r--vespalog/src/vespa/log/llparser.cpp410
-rw-r--r--vespalog/src/vespa/log/llparser.h44
-rw-r--r--vespalog/src/vespa/log/lock.cpp89
-rw-r--r--vespalog/src/vespa/log/lock.h32
-rw-r--r--vespalog/src/vespa/log/log-assert.cpp37
-rw-r--r--vespalog/src/vespa/log/log-target-fd.cpp42
-rw-r--r--vespalog/src/vespa/log/log-target-fd.h25
-rw-r--r--vespalog/src/vespa/log/log-target-file.cpp72
-rw-r--r--vespalog/src/vespa/log/log-target-file.h32
-rw-r--r--vespalog/src/vespa/log/log-target.cpp48
-rw-r--r--vespalog/src/vespa/log/log-target.h28
-rw-r--r--vespalog/src/vespa/log/log.cpp409
-rw-r--r--vespalog/src/vespa/log/log.h308
-rwxr-xr-xvespalog/src/vespa/log/mknm.pl46
-rw-r--r--vespalog/src/vespa/log/reject-filter.cpp65
-rw-r--r--vespalog/src/vespa/log/reject-filter.h37
138 files changed, 11668 insertions, 0 deletions
diff --git a/vespalog/.gitignore b/vespalog/.gitignore
new file mode 100644
index 00000000000..ad5e60bcc0f
--- /dev/null
+++ b/vespalog/.gitignore
@@ -0,0 +1,15 @@
+.classpath
+.project
+archive
+bin
+build
+include
+javadoc
+javamake.pdb
+staging
+target
+testLogs
+vespalog.iml
+/pom.xml.build
+Makefile
+Testing
diff --git a/vespalog/CMakeLists.txt b/vespalog/CMakeLists.txt
new file mode 100644
index 00000000000..17a91b8bbee
--- /dev/null
+++ b/vespalog/CMakeLists.txt
@@ -0,0 +1,21 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_define_module(
+ DEPENDS
+ vespadefaults
+ fastos
+
+ LIBS
+ src/vespa/log
+
+ APPS
+ src/logctl
+ src/logger
+
+ TESTS
+ src/test
+ src/test/simple
+ src/test/threads
+)
+
+vespa_install_script(src/logfmt/logfmt.pl logfmt bin)
+install(FILES src/logfmt/logfmt.1 DESTINATION man/man1)
diff --git a/vespalog/INSTALL b/vespalog/INSTALL
new file mode 100644
index 00000000000..f148a0c18a4
--- /dev/null
+++ b/vespalog/INSTALL
@@ -0,0 +1,17 @@
+1. Make and install fastos with the correct options.
+
+2. cd src
+
+3. ./configure --fastos-dir <fastos-install-dir> [options]
+ Additional optons:
+ --install-dir <val> [..]
+
+4. make
+
+5. make install
+
+This will install the following:
+
+bin/logctl
+include/log/log.h
+lib/liblog.a
diff --git a/vespalog/OWNERS b/vespalog/OWNERS
new file mode 100644
index 00000000000..67cd2820bb8
--- /dev/null
+++ b/vespalog/OWNERS
@@ -0,0 +1 @@
+arnej27959
diff --git a/vespalog/README b/vespalog/README
new file mode 100644
index 00000000000..697e02c6dbc
--- /dev/null
+++ b/vespalog/README
@@ -0,0 +1,183 @@
+This is the unified logging library for Vespa.
+
+To use in a C++ program, make and install this module, and do the
+following in your program(s):
+
+1. In each .cpp file, add the following near the top, preferably
+before any other non-system include:
+
+#include "log/log.h"
+LOG_SETUP("name");
+
+Where "name" should be replaced with the proper COMPONENT NAME (see
+separate section about component names).
+
+NOTE: Naming convention: In LOG_SETUP, only use lower-case letters,
+and use "-" to separate words if necessary, ie:
+ LOG_SETUP(".my-lib.bla-bla")
+Do not use spaces.
+
+This will define a macro named LOG that performs logging. The reason
+for doing this before any other non-system includes, is that you will
+be able to use the LOG macro in your included .h files as well
+then. If you do not LOG from any of your included .h files, it doesn't
+matter.
+
+2. Optionally, add a LOG_RCSID("$Id$") line after LOG_SETUP. This will
+cause debug messages to be tagged with the rcs version of the file
+as well (in addition to the normal filename and line number).
+
+3. At each location where you want to make a log message, use the LOG
+ macro like this:
+
+LOG(<level>, "printf-format" [, optional printf arguments]);
+
+<level> can be one of :
+ fatal - deprecated, use "error"
+ error - An error condition
+ warning - A problem that is not severe enough to warrant an error message.
+
+ event - Information in machine-readable (fixed) format.
+ event should be used instead of info where possible
+
+ info - Information in non-machine-readable format (ie for humans only)
+ info should not be used for error conditions, but is fine for
+ progress reports and similar (unless an event is defined for it!)
+
+ debug - Debug messages, intented for developers (ie you!)
+ only. Debug messages are normally suppressed, and will use
+ very little resources unless they are enabled. Arguments
+ to suppressed log messages are _not_ evaluated.
+
+ Debug messages should contain whatever information you need
+ while debugging your code to make sure it behaves like it should,
+ and to track down any suspected problems.
+
+ spam - Spammy debug messages, intented for developers only. This is intended
+ for VERY spammy debug output, such as printing 100 lines per
+ document processed.
+
+4. When linking the program, link with -llog.
+
+
+COMPONENT NAMES
+
+Component names are hierarchical, a "." separates each level. The root
+component name must be IDENTICAL in all files for a particular
+binary. The root component name should be identical to the name of the
+final binary.
+
+There is some support in LOG_SETUP for simplifying this:
+
+1. An empty argument to LOG_SETUP will use whatever root component name
+ is defined in any other .cpp file.
+
+2. If the component name used in LOG_SETUP starts with a ".", it will be
+ appended to the root component name that is defined in any other
+ .cpp file.
+
+
+The normal way to use this is then:
+
+1. In applications
+
+ a) Find the .cpp file that defines the main() function, and use
+ LOG_SETUP("binary-name");
+ in that file.
+
+ b) For all other .cpp files in the application that only want to use
+ the root name, the following will then be equivalent, and the short
+ form is preferable if you are ever going to rename the program:
+
+ LOG_SETUP("");
+ or
+ LOG_SETUP("binary-name");
+
+
+2. In libraries or sub-modules
+
+ Decide on a name for the library or sub-module, and use this:
+ LOG_SETUP(".name") (where "name" is the name of the library
+ or sub-module)
+
+ If you want more levels you can also do
+ LOG_SETUP(".level1.level2");
+
+ And so on.
+
+The reason it is useful to have more than one component name in an
+application is that it is possible to change at _runtime_ which log
+levels are suppressed in each individual component. This is especially
+useful for enabling debug output only in parts of a program.
+
+Some hypothetical examples:
+
+In the application "fdispatch" there could be some files that have
+code primarily for query parsing. It also links with the library "fnet"
+for communicating.
+
+in main.cpp the following is done:
+LOG_SETUP("fdispatch");
+LOG_RCSID("$Id$");
+
+In the .cpp files that worry about query parsing, the following is done:
+LOG_SETUP(".query-parse");
+LOG_RCSID("$Id$");
+
+In fnet, all files do at least this:
+LOG_SETUP(".fnet");
+LOG_RCSID("$Id$");
+
+It is also possible to sub-divide fnet into more components, and do
+something like this in some of the fnet files:
+LOG_SETUP(".fnet.network");
+LOG_RCSID("$Id$");
+
+
+OUTPUT FORMAT
+
+The output log format is tab-separated fields, and the following
+fields exist, always in this order:
+
+<time> <host> <process/thread id> <service> <component> <level> <message>
+
+time: Seconds since 1970-01-01 UTC, optionally with fractional seconds.
+ Examples: "1088516763", "1088516763.808240"
+ Time is always output with a "neutral" time zone (UTC).
+
+host: Hostname as given by "uname -n", or "-" for no hostname.
+
+process/thread-id: pid[/tid] of the process/thread that procuded the log.
+ For systems where pid uniquely defines the logging thread, or where
+ there are no threads, only the pid will be logged. If a tid is logged
+ in addition, the format will be "pid/tid".
+
+service: Service name as given by Vespa configuration setup, or "-" for
+ no service name
+
+component: Application decided logging component name. Hierarchical with
+ "." between each level, top level should always be the name of the
+ binary that produced the log.
+
+level (see above)
+
+message: The log message itself. Any content is possible, but the following
+ quoting rules are enforced by the library:
+
+
+QUOTING
+
+The library will forcibly quote all characters that have an ASCII
+value < 32 (and also 92) according to the following table,
+
+9 -> \t
+10 -> \n
+13 -> \r
+92 -> \\ (ie \ is escaped as \\)
+
+All other values that must be quoted will be quoted as \xXX where X is
+a hexadecimal number, eg 1 -> \x01, 2 -> \x02, 17 -> \x11 and so on.
+
+Any code reading logs should be prepared to see \xXX style quoting also
+for the characters that have defined alphabetic quoting shortcuts (ie
+9, 10, 13 and 92)!
diff --git a/vespalog/README.events b/vespalog/README.events
new file mode 100644
index 00000000000..7d9e70eae97
--- /dev/null
+++ b/vespalog/README.events
@@ -0,0 +1,158 @@
+Quick Specification for VESPA Log Events
+========================================
+
+Events are a machine-readable form of log messages, intended for
+automated monitoring.
+
+Each event has an event type, a version and an optional payload.
+
+In the log format, event types are expressed as a single word,
+versions as a simple integer, and the payload as a set of key=value
+pairs.
+
+The event payload is backslash-quoted just like log messages are in
+general. This means that events may be double-quoted during
+transport. Double-quote delimiters are not supported.
+
+These are the supported event types and their usage. You can find
+more details about each of the event types further below.
+
+ Event Type Description
+============================================================================
+ starting Sent by a process starting another process
+ started Sent by a process when it has started
+ stopping Sent by a process that will exit
+ stopped Sent by the calling process when a sub-process has exited
+ reloading Sent when some form of reloading starts
+ reloaded Sent when the reload is complete
+ crash Sent by the calling process when a sub-process crashes
+ progress For tracking any type of progress
+ count For tracking any type of counter (queries served etc)
+ value For tracking any type of value (qps, queue-length, etc..)
+ collection For tracking events related to collections (collection out of sync etc)
+
+Event: starting
+---------------
+Payload: name=<name>
+
+This event is sent by processes when they are about to start another
+process. Typical for, but not limited to, shell scripts. This event
+is not required to track processes, but is useful in cases where a
+sub-process may fail during startup.
+
+Example:
+starting name=fdispatch
+
+
+Event: started
+--------------
+Payload: name=<name>
+The "started" event is sent by a process that just started up.
+
+Example:
+started name=fdispatch
+
+
+Event: stopping
+---------------
+Payload: name=<name> why=<why>
+The "stopping" event is sent by a process that is about to exit.
+
+Example:
+stopping name=fdispatch why="clean exit"
+
+
+Event: stopped
+--------------
+Payload: name=<name> pid=<pid> exitcode=<exitcode>
+
+This event is sent by a process monitoring when a sub-process exits.
+
+Example:
+stopped name=fdispatch pid=76512
+
+
+Event: reloading
+----------------
+Payload: name=<name>
+
+This event is used to signal that a process is about to start some
+kind of reloading operation.
+
+The "name" payload attribute says what kind of reload operation that
+is being done, and is specific for each library/application. The
+"reloading" event is optional for reloads, for a simple reload that
+doesn't fail, you can use "reloaded" directly.
+
+Example:
+reloading name=partitions
+
+
+Event: reloaded
+---------------
+The "reloaded" event is sent after the reload is complete (and
+successful). Any "reloading" event must have a matching "reloaded" event
+
+Example:
+
+reloaded name=crawler.configfile:$VESPA_HOME/var/mms/docproccfg.xml
+
+
+Event: crash
+------------
+Payload: name=<appname> pid=<pid> signal=<signal>
+
+Submitted by a process monitoring a sub-process when the sub-process
+crashes (dumps core etc.)
+
+Example:
+
+crash name=fastcrawler pid=12345 signal=11
+
+
+Event: progress
+---------------
+Payload: name=<name> value=<value> [total=<total>]
+
+Somewhat general event for tracking progress. The <name> is specific
+to each library/application.
+
+Example:
+progress name=indexing.autoindex value=17 total=90
+
+
+Event: count
+------------
+Payload: name=<name> value=<value>
+
+Somewhat general event for counting stuff. As with "progress", the
+<name> is specific to each library/application. Counters are asssumed
+to increase with time, counting the number of events since the program
+was started for example.
+
+
+Event: value
+------------
+Payload: name=<name> value=<value>
+
+General event for values. "Value is for values that cannot be counts"
+Typical values are queue lengths, transaction frequencies and so on.
+
+
+Examples:
+count name=queue-length value=2346235
+
+Event: collection
+-----------------
+
+Payload: collectionid=<id> name=<name> <params>
+
+Represents events related to collections. The name describes what
+happened to the collection ("syncrows", "created", etc). params
+is a string on the form param1=... param2=... that holds various
+parameters for the event. For "syncrows", the parameters are:
+column, badrow, goodrow.
+
+Examples:
+collection collectionid=1111111111 name=syncrows column=1 badrow=0 goodrow=1
+
diff --git a/vespalog/README.server.protocol b/vespalog/README.server.protocol
new file mode 100644
index 00000000000..7d89bf625c0
--- /dev/null
+++ b/vespalog/README.server.protocol
@@ -0,0 +1,46 @@
+Protocol for talking to the log server:
+
+Pure line-based text protocol. Tcp to some port on the log server:
+
+From log daemon (or anything, really) to server:
+
+service <service>
+component <service> <component>
+state <service> <component> <levels>
+
+
+This tells the log daemon that service <service> exists, and that it
+has a component named <component>.
+
+<levels> syntax:
+
+fatal=forward,error=forward,warning=store,info=store,event=forward,debug=off,spam=off
+
+ie: level=<status>[,...]*
+
+Where <status> is one of:
+forward : store and send to log server
+store : store locally only (to file)
+off : do not even generate the message in the program
+
+
+
+From server to whoever is interested:
+
+setstate <service> <component> <levels>
+
+Exactly same syntax. Levels that are not mentioned are not modified.
+
+list services
+list components <service>
+list states <service> <component>
+
+(list commands are replied to with service/component/state commands).
+
+
+
+
+
+
+
+
diff --git a/vespalog/pom.xml b/vespalog/pom.xml
new file mode 100644
index 00000000000..5a39f43b1c3
--- /dev/null
+++ b/vespalog/pom.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>parent</artifactId>
+ <version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
+ </parent>
+ <artifactId>vespalog</artifactId>
+ <version>6-SNAPSHOT</version>
+ <name>Vespa Log</name>
+ <packaging>container-plugin</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>vespajlib</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>annotations</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <redirectTestOutputToFile>${test.hide}</redirectTestOutputToFile>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <compilerArgs>
+ <arg>-Xlint:all</arg>
+ <arg>-Xlint:-serial</arg>
+ <arg>-Werror</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-install-plugin</artifactId>
+ <configuration>
+ <updateReleaseInfo>true</updateReleaseInfo>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/vespalog/src/.gitignore b/vespalog/src/.gitignore
new file mode 100644
index 00000000000..41357d5435e
--- /dev/null
+++ b/vespalog/src/.gitignore
@@ -0,0 +1,5 @@
+Makefile.ini
+config.h
+config_command.sh
+vespalog.mak
+project.dsw
diff --git a/vespalog/src/Log.pm b/vespalog/src/Log.pm
new file mode 100644
index 00000000000..06eda129238
--- /dev/null
+++ b/vespalog/src/Log.pm
@@ -0,0 +1,93 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package Log;
+
+require 5.006_001;
+use strict;
+use warnings;
+use Sys::Hostname;
+
+
+#initialize
+my $VESPA_LOG_TARGET = $ENV{VESPA_LOG_TARGET} || "fd:2";
+my $VESPA_LOG_LEVELS = $ENV{VESPA_LOG_LEVELS} || "all -debug -spam";
+
+my $SERVICE = $ENV{VESPA_SERVICE_NAME} || "-";
+my $HOST = hostname;
+
+my %LEVEL = ( error => 0x01,
+ warning => 0x02,
+ info => 0x04,
+ config => 0x08,
+ debug => 0x10,
+ spam => 0x20,
+ all => 0x3f);
+
+my %instance = ();
+
+my $VESPA_LOG_FILTER;
+foreach (split(/\s+/, $VESPA_LOG_LEVELS)) {
+ /(-?)(\S+)/ || die "Log level parse error: $_";
+ my ($inv, $value) = ($1, $LEVEL{$2});
+ die "Unknown level: $2" unless $value;
+ if ($inv) {
+ $VESPA_LOG_FILTER &= ~$value;
+ } else {
+ $VESPA_LOG_FILTER |= $value;
+ }
+}
+
+if ($VESPA_LOG_TARGET =~ /fd:(\d+)/) {
+ open(TARGET, ">&=$1") || die $!;
+} elsif ($VESPA_LOG_TARGET =~ /file:(.+)/) {
+ open(TARGET, ">>$1") || die $!;
+} else {
+ die "Illegal target $VESPA_LOG_TARGET";
+}
+select(TARGET); $| = 1;
+
+sub new {
+ my ($self, $name) = @_;
+ my $type = ref($self) || $self;
+ $instance{$name} = bless { component => $name } unless $instance{$name};
+ return $instance{$name};
+}
+
+sub open_target {
+ return unless $VESPA_LOG_TARGET =~ /file:(.+)/;
+ close(TARGET);
+ open(TARGET, ">>$1") || die $!;
+ select(TARGET); $| = 1;
+}
+
+sub escape_message {
+ $_ = shift;
+ s/\n/\\n/g;
+ s/\r/\\r/g;
+ s/\t/\\t/g;
+ s/([\x80-\xFF])/sprintf("\\x%x",ord($1))/eg;
+ return $_;
+}
+
+sub log {
+ my ($this, $level, $msg) = @_;
+ my $component = ref($this) ? $this->{component} : "-";
+ die "Unknown logging level: '$level'" unless $LEVEL{$level};
+ return unless $VESPA_LOG_FILTER & $LEVEL{$level};
+
+ open_target;
+ $msg = escape_message($msg);
+
+ # format: time host pid service component level message
+ print TARGET (time()."\t$HOST\t$$\t$SERVICE\t$component\t$level\t$msg\n");
+}
+
+sub error { &log(shift, "error", @_); }
+sub warning { &log(shift, "warning", @_); }
+sub info { &log(shift, "info", @_); }
+sub config { &log(shift, "config", @_); }
+sub debug { &log(shift, "debug", @_); }
+sub spam { &log(shift, "spam", @_); }
+
+
+$SIG{__DIE__} = sub { Log->log("error", @_); };
+$SIG{__WARN__} = sub { Log->log("warning", @_); };
diff --git a/vespalog/src/logctl/.gitignore b/vespalog/src/logctl/.gitignore
new file mode 100644
index 00000000000..6089704ed9a
--- /dev/null
+++ b/vespalog/src/logctl/.gitignore
@@ -0,0 +1,3 @@
+.depend
+Makefile
+logctl
diff --git a/vespalog/src/logctl/CMakeLists.txt b/vespalog/src/logctl/CMakeLists.txt
new file mode 100644
index 00000000000..666e840fe60
--- /dev/null
+++ b/vespalog/src/logctl/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalog_logctl_app
+ SOURCES
+ logctl.cpp
+ OUTPUT_NAME logctl
+ INSTALL bin
+ DEPENDS
+ vespalog
+)
diff --git a/vespalog/src/logctl/logctl.cpp b/vespalog/src/logctl/logctl.cpp
new file mode 100644
index 00000000000..8457fb944ce
--- /dev/null
+++ b/vespalog/src/logctl/logctl.cpp
@@ -0,0 +1,273 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <sys/types.h>
+#include <cstdio>
+#include <cstdlib>
+#include <unistd.h>
+#include <cstring>
+#include <limits.h>
+
+#include <memory>
+#include <vector>
+#include <string>
+
+#include <vespa/defaults.h>
+#include <vespa/log/control-file.h>
+#include <vespa/log/internal.h>
+#include <vespa/log/component.h>
+LOG_SETUP("logctl", "$Id$");
+
+using namespace ns_log;
+
+static void modifyLevels(const char *file, char *component, char *levels,
+ bool shouldCreateFile, bool shouldCreateEntry);
+static void readLevels(const char *file, char *component);
+
+
+static void
+usage(const char *name)
+{
+ fprintf(stderr, "Usage: %s [OPTION] <service>[:component-specification]\n"
+ " or: %s [OPTION] <service>[:component-spec] <level-mods>\n"
+ "Print or modify log levels for a VESPA service.\n\n"
+ " -c Create the control file if it does not exist (implies -n)\n"
+ " -a Update all .logcontrol files in <dir>\n"
+ " -r Reset to default levels\n"
+ " -n Create the component entry if it does not exist\n"
+ " -f <file> Use <file> as the log control file\n"
+ " -d <dir> Look in <dir> for log control files\n\n"
+ "<level-mods> are defined as follows:\n"
+ " <level>=<on|off>[,<level>=<on|off>]...\n"
+ "<level> is one of:\n"
+ " all, fatal, error, warning, info, event, config, debug or spam\n\n"
+ "component-specification specicies which sub-components of the\n"
+ "service should be controlled. If it is empty, all components\n"
+ "are controlled:\n"
+ " x. : Matches only component x\n"
+ " x : Matches component x and all its sub-components\n\n"
+ "Example: %s topleveldispatch:log all=on,spam=off,debug=off : For service\n"
+ "topleveldispatch, set log and all sub-components of log to enable all\n"
+ "except spam and debug.\n\n", name, name, name);
+}
+
+static std::vector<std::string>
+findAllFiles(const char *dir)
+{
+ std::vector<std::string> rv;
+ DIR *d = opendir(dir);
+ if (d == NULL) {
+ perror(dir);
+ return rv;
+ }
+ LOG(spam, "scanning %s", dir);
+
+ struct dirent *entry;
+ while ((entry = readdir(d)) != NULL) {
+ if (strcmp(entry->d_name, ".") == 0) continue;
+ if (strcmp(entry->d_name, "..") == 0) continue;
+
+ const char *suffix = ".logcontrol";
+
+ LOG(spam, "check %s", entry->d_name);
+
+ if (strlen(entry->d_name) > strlen(suffix)) {
+ char *cmp = entry->d_name + strlen(entry->d_name) - strlen(suffix);
+ if (strcmp(suffix, cmp) == 0) {
+ std::string fn = dir;
+ fn.append("/");
+ fn.append(entry->d_name);
+
+ struct stat sb;
+ if (stat(fn.c_str(), &sb) == 0) {
+ if (S_ISREG(sb.st_mode)) {
+ *cmp = 0;
+ rv.push_back(entry->d_name);
+ }
+ } else {
+ perror(fn.c_str());
+ }
+ }
+ }
+ }
+ closedir(d);
+ return rv;
+}
+
+
+int
+main(int argc, char **argv)
+{
+ vespa::Defaults::bootstrap(argv[0]);
+
+ const char *dir = getenv("VESPA_LOG_CONTROL_DIR");
+ const char *file = getenv("VESPA_LOG_CONTROL_FILE");
+ const char *root = getenv("ROOT");
+ if (!root) {
+ root = vespa::Defaults::vespaHome();
+ }
+ bool shouldCreateFile = false;
+ bool shouldCreateEntry = false;
+ bool doAllFiles = false;
+ bool doOnlyFile = false;
+ bool doResetLevels = false;
+
+ while (1) {
+ int c = getopt(argc, argv, "acnrf:d:h");
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'a':
+ doAllFiles = true;
+ break;
+ case 'r':
+ doResetLevels = true;
+ case 'c':
+ shouldCreateFile = true;
+ // fallthrough
+ case 'n':
+ shouldCreateEntry = true;
+ break;
+ case 'f':
+ file = strdup(optarg);
+ doOnlyFile = true;
+ break;
+ case 'd':
+ dir = strdup(optarg);
+ break;
+ case 'h':
+ usage(argv[0]);
+ exit(EXIT_SUCCESS);
+ }
+ }
+
+
+ char buf[PATH_MAX];
+ if (!dir && !file) {
+ snprintf(buf, sizeof buf, "%s/var/db/vespa/logcontrol", root);
+ dir = buf;
+ }
+
+ typedef std::vector<std::string> strlist_t;
+
+ strlist_t services;
+
+ char nullComponent[] = "default";
+ char *component = nullComponent;
+
+ if (doAllFiles) {
+ services = findAllFiles(dir);
+ if (doOnlyFile) {
+ fprintf(stderr, "-f and -a options cannot be used at the same time\n");
+ exit(EXIT_FAILURE);
+ }
+ // No log control files exist
+ if (services.empty()) {
+ exit(EXIT_SUCCESS);
+ }
+ } else {
+ if (optind >= argc) {
+ usage(argv[0]);
+ fprintf(stderr, "ERROR: Missing service argument!\n");
+ exit(EXIT_FAILURE);
+ }
+ char *service = strdup(argv[optind]);
+ ++optind;
+
+ char *delim = strchr(service, ':');
+ if (delim) {
+ *delim = 0;
+ services.push_back(service);
+ *delim = '.';
+ component = delim;
+ } else {
+ services.push_back(service);
+ }
+ }
+
+ char defLevels[] = "all=on,debug=off,spam=off";
+ char *levels = NULL;
+
+ if (doResetLevels) {
+ levels = defLevels;
+ } else {
+ if (argc > optind) {
+ levels = strdup(argv[optind]);
+ ++optind;
+ }
+ }
+
+ if (argc > optind) {
+ usage(argv[0]);
+ fprintf(stderr, "ERROR: Too many arguments!\n\n");
+ exit(EXIT_FAILURE);
+ }
+
+ bool hadFailure = false;
+ bool hadSuccess = false;
+
+ for (strlist_t::iterator it = services.begin();
+ it != services.end();
+ ++it)
+ {
+ const char *service = (*it).c_str();
+
+ char serviceFile[PATH_MAX];
+ if (! doOnlyFile) {
+ snprintf(serviceFile, sizeof serviceFile, "%s/%s.logcontrol", dir, service);
+ file = serviceFile;
+ }
+ // fprintf(stderr, "Log control file %s:\n", file);
+
+ try {
+ if (levels) {
+ modifyLevels(file, component, levels, shouldCreateFile, shouldCreateEntry);
+ } else {
+ readLevels(file, component);
+ }
+ hadSuccess = true;
+ } catch (InvalidLogException& x) {
+ fprintf(stderr, "Failed: %s\n", x.what());
+ hadFailure = true;
+ }
+ }
+ if (hadFailure) exit(EXIT_FAILURE);
+ if (! hadSuccess) {
+ fprintf(stderr, "no logcontrol files updates\n");
+ exit(EXIT_FAILURE);
+ }
+ exit(EXIT_SUCCESS);
+}
+
+static void
+modifyLevels(const char *file, char *componentPattern, char *levels,
+ bool shouldCreateFile, bool shouldCreateEntry)
+{
+ ControlFile cf(file, shouldCreateFile
+ ? ControlFile::CREATE : ControlFile::READWRITE);
+ Component *c;
+ if (shouldCreateEntry) {
+ cf.ensureComponent(componentPattern);
+ }
+ ComponentIterator iter(cf.getComponentIterator());
+ while ((c = iter.next()) != NULL) {
+ std::unique_ptr<Component> component(c);
+ if (component->matches(componentPattern)) {
+ component->modifyLevels(levels);
+ }
+ }
+ cf.flush();
+}
+
+static void
+readLevels(const char *file, char *componentPattern)
+{
+ ControlFile cf(file, ControlFile::READONLY);
+ Component *c;
+ ComponentIterator iter(cf.getComponentIterator());
+ while ((c = iter.next()) != NULL) {
+ std::unique_ptr<Component> component(c);
+ if (c->matches(componentPattern)) {
+ c->display();
+ }
+ }
+}
+
diff --git a/vespalog/src/logfmt/.gitignore b/vespalog/src/logfmt/.gitignore
new file mode 100644
index 00000000000..568d69eee3e
--- /dev/null
+++ b/vespalog/src/logfmt/.gitignore
@@ -0,0 +1 @@
+logfilter
diff --git a/vespalog/src/logfmt/logfilter.c b/vespalog/src/logfmt/logfilter.c
new file mode 100644
index 00000000000..709ce2c016e
--- /dev/null
+++ b/vespalog/src/logfmt/logfilter.c
@@ -0,0 +1,78 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <signal.h>
+
+int main(int argc, char *argv[])
+{
+ int lfpipe[2];
+ pid_t lfmtpid;
+ pid_t progpid;
+ int wstat;
+
+ if (argc < 3) {
+ fprintf(stderr, "Usage: %s logfmt prog [...]\n", argv[0]);
+ exit(1);
+ }
+
+ pipe(lfpipe);
+ lfmtpid = fork();
+ if (lfmtpid == 0) {
+ close(lfpipe[1]);
+ if (lfpipe[0] != 0) {
+ close(0);
+ dup(lfpipe[0]);
+ close(lfpipe[0]);
+ }
+ execlp(argv[1], argv[1], "-", (const char *)NULL);
+ perror("exec logfmt failed");
+ exit(1);
+ }
+ if (lfmtpid < 0) {
+ perror("fork failed");
+ exit(1);
+ }
+ close(lfpipe[0]);
+
+ progpid = fork();
+ if (progpid == 0) {
+ char buf[20];
+ int i=2;
+
+ sprintf(buf, "fd:%d", lfpipe[1]);
+ setenv("VESPA_LOG_TARGET", buf, 1);
+ for (; i<argc; i++) {
+ argv[i-2] = argv[i];
+ }
+ argv[i-2] = NULL;
+ execvp(argv[0], argv);
+ perror("exec program failed");
+ exit(1);
+ }
+ if (progpid < 0) {
+ perror("fork failed");
+ exit(1);
+ }
+ close(lfpipe[1]);
+
+ if (waitpid(lfmtpid, &wstat, 0) != lfmtpid) {
+ perror("bad waitpid for logfmt");
+ }
+ if (waitpid(progpid, &wstat, 0) != progpid) {
+ perror("bad waitpid for program");
+ }
+
+ if (WIFEXITED(wstat)) {
+ return WEXITSTATUS(wstat);
+ }
+ if (WIFSIGNALED(wstat)) {
+ int sig = WTERMSIG(wstat);
+ signal(sig, SIG_DFL);
+ kill(getpid(), sig);
+ }
+ /* should not get here */
+ exit(1);
+}
diff --git a/vespalog/src/logfmt/logfmt.1 b/vespalog/src/logfmt/logfmt.1
new file mode 100644
index 00000000000..a16b1ea2eb3
--- /dev/null
+++ b/vespalog/src/logfmt/logfmt.1
@@ -0,0 +1,218 @@
+.\" $Id: logfmt.1,v 1.12 2007-06-19 09:37:25 daljord Exp $
+.\"
+.Dd October 29, 2004
+.Dt LOGFMT \&1 "Vespa documentation"
+.Os "Yahoo! Vespa" "2.0"
+.Os
+.Sh NAME
+.Nm logfmt
+.Nd select and format messages from Vespa log files
+.Sh SYNOPSIS
+.Nm
+.Op Fl l Ar levellist
+.Op Fl s Ar fieldlist
+.Op Fl p Ar pid
+.Op Fl S Ar service
+.Op Fl H Ar host
+.Op Fl c Ar regex
+.Op Fl m Ar regex
+.Op Fl t
+.Op Fl f
+.Op Fl L
+.Op Fl N
+.Op Fl ts
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility reads Vespa log files, select messages and writes a formatted
+version of selected messages to the standard output.
+.Pp
+The options are as follows:
+.Bl -tag -width ".It Fl l Ar levellist"
+.It Fl l Ar levellist
+Select which log levels to select.
+The default is to select "fatal", "error", "warning" and "info"
+levels, and suppress "config", "event", "debug" and "spam" levels;
+but when using this option, only the named levels will be selected.
+The
+.Ar levellist
+is a comma separated list of level names.
+The name
+.Em all
+may be used to add all known levels.
+Prepending a minus sign will deselect the level named.
+Starting the list with a plus sign will add and remove levels
+from the current (or default) list of levels instead
+of replacing it.
+.It Fl s Ar fieldlist
+Select which fields of log messages to show.
+The order of the actual output fields is fixed.
+When using this option, only the named fields will be shown. The
+fieldlist is a comma separated list of field names. The name
+.Em all
+may be used to add all possible fields.
+Prepending a minus sign will turn off display of the named field.
+Starting the list with a plus sign will add and remove fields
+from the current (or default) list of fields instead
+of replacing it.
+.Pp
+The fields which may be named are:
+.Bl -tag -width component
+.It time
+Print the time in seconds since the epoch.
+Ignored if
+.Em fmttime
+is shown.
+.It fmttime
+Print the time in human-readable [YYYY-MM-DD HH:mm:ss] format.
+Note that the time is printed in the local timezone; to get GMT
+output use
+.Nm "\*[q]env TZ=GMT logfmt\*[q]"
+as your command.
+.It msecs
+Add milliseconds after the seconds in
+.Em time
+and
+.Em fmttime
+output. Ignored if
+.Em usecs
+is in effect.
+.It usecs
+Add microseconds after the seconds in
+.Em time
+and
+.Em fmttime
+output.
+.It host
+Print the hostname field.
+.It level
+Print the level field (uppercased).
+.It pid
+Print the pid field.
+.It service
+Print the service field.
+.It component
+Print the component field.
+.It message
+Print the message text field.
+You probably always want to add this.
+.El
+.Pp
+Using this option several times works as if the given
+.Ar fieldlist
+arguments had been concatenated into one comma-separated list.
+The default fields to show are as if
+.Bk
+.Op Fl s Ar fmttime,msecs,level,service,component,message
+.Ek
+had been given.
+.It Fl p Ar pid
+Select only messages where the pid field matches the
+.Ar pid
+string exactly.
+.It Fl S Ar service
+Select only messages where the service field matches the
+.Ar service
+string exactly.
+.It Fl H Ar host
+Select only messages where the hostname field matches the
+.Ar host
+string exactly.
+.It Fl c Ar regex
+Select only messages where the component field matches the
+.Ar regex
+given, using
+.Xr perlre
+regular expression matching.
+.It Fl m Ar regex
+Select only messages where the message text field matches the
+.Ar regex
+given, using
+.Xr perlre
+regular expression matching.
+.It Fl f
+Invoke tail -F to follow input file
+.It Fl L
+.It Fl -livestream
+Only useful for multi-machine setups:
+Invoke vespa-replicate-log-stream to follow live stream from log server,
+instead of reading any input file. Also implies
+.Bk
+.Fl "s +host"
+.Ek
+as default.
+.It Fl N
+Dequote quoted newlines in the message text field to an actual newline plus tab.
+.It Fl t
+Format the component field (if shown) as a fixed-with string,
+truncating if necessary.
+.It Fl ts
+Format the service field (if shown) as a fixed-with string,
+truncating if necessary.
+.El
+.Sh EXAMPLES
+The command:
+.Pp
+.Bd -literal -offset indent
+logfmt -l event -s service,message,fmttime,message
+.Ed
+.Pp
+will display only messages with log level "event",
+printing a human-readable time (without any fractional seconds),
+the service generating the event and the event message, like this:
+.Bd -literal -offset indent
+[2004-12-07 18:43:01] config-sentinel starting/1 name="logd"
+[2004-12-07 18:43:01] logd started/1 name="logdemon"
+[2004-12-07 18:45:51] rtc starting/1 name="rtc.index0"
+[2004-12-07 18:45:51] rtc.index0 started/1 name="flexindexer.index"
+[2004-12-07 18:45:51] rtc.index0 stopping/1 name="flexindexer.index" why="done"
+[2004-12-07 18:45:53] rtc stopped/1 name="rtc.index0" pid=50600 exitcode=0
+[2004-12-07 18:46:13] logd stopping/1 name="logdemon" why="done ok."
+[2004-12-07 18:46:13] config-sentinel stopped/1 name="logd" pid=49633 exitcode=0
+.Ed
+.Pp
+Note that the second "message" item in the fieldlist is redundant,
+and that order of printed field is fixed no matter what the fieldlist
+order is.
+.Pp
+The command:
+.Pp
+.Bd -literal -offset indent
+logfmt -l all-info,-debug -s level \e
+ -s time,usecs,component,message -t -l -event
+.Ed
+.Pp
+will display messages with log levels that are
+.Em not
+any of
+.Em info, debug,
+or
+.Em event,
+printing the time in seconds and microseconds, the log level, the
+component name, and the message text, possibly somewhat like this:
+.Bd -literal -offset indent
+1102441382.530423 CONFIG : nc Config handle: 'pandora.0-rtx'
+1102441551.471568 CONFIG : flexindexer.doc Adding document type typetest-0
+1102441573.148211 WARNING : logdemon stopping on signal 15
+1102441887.158000 WARNING : com.yahoo.fs4.m read exception
+1102441935.569567 WARNING : rtc Dispatch inherited job failed for dir dispatch0
+1102442115.746001 WARNING : fdispatch Search node 172.24.94.75:10124 down
+1102442474.205920 WARNING : rtx RTC (tcp/172.24.94.75:10161) : DOWN
+1102442474.515877 WARNING : fdispatch Search node localhost:10128 down
+1102442983.075669 ERROR : flexindexer.std Unable to find cluster map defaultcluster
+.Ed
+.Sh FILES
+If no file argument is given,
+.Nm
+will read the last vespa log file $VESPA_HOME/logs/vespa/vespa.log (this also works with the
+.Fl f
+option).
+Otherwise, reads only the files given as arguments.
+To read standard input, supply a single dash '-' as a file argument.
+.Sh SEE ALSO
+Documentation in the "log" module for input file format.
+.Sh HISTORY
+Developed as part of Vespa 1.1. The default output format
+reflects the old "fastlib" log formatting, with minor differences
+and is intended to be human-readable, not parsed.
diff --git a/vespalog/src/logfmt/logfmt.pl b/vespalog/src/logfmt/logfmt.pl
new file mode 100644
index 00000000000..9152d1fcfec
--- /dev/null
+++ b/vespalog/src/logfmt/logfmt.pl
@@ -0,0 +1,327 @@
+#!/usr/local/bin/perl
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+# BEGIN perl environment bootstrap section
+# Do not edit between here and END as this section should stay identical in all scripts
+
+use File::Basename;
+use File::Path;
+
+sub findpath {
+ my $myfullname = ${0};
+ my($myname, $mypath) = fileparse($myfullname);
+
+ return $mypath if ( $mypath && -d $mypath );
+ $mypath=`pwd`;
+
+ my $pwdfullname = $mypath . "/" . $myname;
+ return $mypath if ( -f $pwdfullname );
+ return 0;
+}
+
+# Returns the argument path if it seems to point to VESPA_HOME, 0 otherwise
+sub is_vespa_home {
+ my($VESPA_HOME) = shift;
+ my $COMMON_ENV="libexec/vespa/common-env.sh";
+ if ( $VESPA_HOME && -d $VESPA_HOME ) {
+ my $common_env = $VESPA_HOME . "/" . $COMMON_ENV;
+ return $VESPA_HOME if -f $common_env;
+ }
+ return 0;
+}
+
+# Returns the home of Vespa, or dies if it cannot
+sub findhome {
+ # Try the VESPA_HOME env variable
+ return $ENV{'VESPA_HOME'} if is_vespa_home($ENV{'VESPA_HOME'});
+ if ( $ENV{'VESPA_HOME'} ) { # was set, but not correctly
+ die "FATAL: bad VESPA_HOME value '" . $ENV{'VESPA_HOME'} . "'\n";
+ }
+
+ # Try the ROOT env variable
+ $ROOT = $ENV{'ROOT'};
+ return $ROOT if is_vespa_home($ROOT);
+
+ # Try the script location or current dir
+ my $mypath = findpath();
+ if ($mypath) {
+ while ( $mypath =~ s|/[^/]*$|| ) {
+ return $mypath if is_vespa_home($mypath);
+ }
+ }
+ die "FATAL: Missing VESPA_HOME environment variable\n";
+}
+
+BEGIN {
+ my $tmp = findhome();
+ if ( $tmp !~ m{[/]$} ) { $tmp .= "/"; }
+ $ENV{'VESPA_HOME'} = $tmp;
+}
+my $VESPA_HOME = $ENV{'VESPA_HOME'};
+
+# END perl environment bootstrap section
+
+use 5.006_001;
+use strict;
+use warnings;
+
+use Getopt::Long qw(:config no_ignore_case);
+
+my %showflags = (
+ time => 1,
+ fmttime => 1,
+ msecs => 1,
+ usecs => 0,
+ host => 0,
+ level => 1,
+ pid => 0,
+ service => 1,
+ component => 1,
+ message => 1
+);
+
+my %levelflags = (
+ fatal => 1,
+ error => 1,
+ warning => 1,
+ info => 1,
+ config => 0,
+ event => 0,
+ debug => 0,
+ spam => 0
+);
+
+# Do not buffer the output
+$| = 1;
+
+my $compore;
+my $msgtxre;
+my $onlypid;
+my $onlysvc;
+my $onlyhst;
+
+my $shortsvc;
+my $shortcmp;
+
+my @optlevels;
+my @optshow;
+my $optlevels;
+my $optfollow;
+my $optnldequote;
+my $optreplication;
+my $opthelp = '';
+
+my $bad = 0;
+
+GetOptions ('level|l=s' => \@optlevels,
+ 'service|S=s' => \$onlysvc,
+ 'show|s=s' => \@optshow,
+ 'pid|p=s' => \$onlypid,
+ 'component|c=s' => \$compore,
+ 'message|m=s' => \$msgtxre,
+ 'help|h' => \$opthelp,
+ 'follow|f' => \$optfollow,
+ 'livestream|L' => \$optreplication,
+ 'nldequote|N' => \$optnldequote,
+ 'host|H=s' => \$onlyhst,
+ 'truncateservice|ts' => \$shortsvc,
+ 'truncatecomponent|tc|t' => \$shortcmp,
+) or $bad=1;
+
+if ( $optreplication ) {
+ open(STDIN, "vespa-replicate-log-stream |");
+ $showflags{'host'} = 1;
+} elsif ( @ARGV == 0 and ! -p STDIN) {
+ push(@ARGV, "$VESPA_HOME/logs/vespa/vespa.log");
+}
+
+if ( $optfollow ) {
+ my $filearg = "";
+ if ( @ARGV > 1 ) {
+ print STDERR "ERROR: Cannot follow more than one file\n\n";
+ $bad=1;
+ } else {
+ $filearg = shift @ARGV if (@ARGV > 0);
+ open(STDIN, "tail -F $filearg |")
+ or die "cannot open 'tail -F $filearg' as input pipe\n";
+ }
+}
+
+if ( $opthelp || $bad ) {
+ print STDERR "Usage: $0 [options] [inputfile ...]\n",
+ "Options:\n",
+ " -l LEVELLIST\t--level=LEVELLIST\tselect levels to include\n",
+ " -s FIELDLIST\t--show=FIELDLIST\tselect fields to print\n",
+ " -p PID\t--pid=PID\t\tselect messages from given PID\n",
+ " -S SERVICE\t--service=SERVICE\tselect messages from given SERVICE\n",
+ " -H HOST\t--host=HOST\t\tselect messages from given HOST\n",
+ " -c REGEX\t--component=REGEX\tselect components matching REGEX\n",
+ " -m REGEX\t--message=REGEX\t\tselect message text matching REGEX\n",
+ " -f\t\t--follow\t\tinvoke tail -F to follow input file\n",
+ " -L\t\t--livestream\t\tfollow log stream from logserver\n",
+ " -N\t\t--nldequote\t\tdequote newlines in message text field\n",
+ " -t\t--tc\t--truncatecomponent\tchop component to 15 chars\n",
+ " --ts\t\t--truncateservice\tchop service to 9 chars\n",
+ "\n",
+ "FIELDLIST is comma separated, available fields:\n",
+ "\t time fmttime msecs usecs host level pid service component message\n",
+ "Available levels for LEVELLIST:\n",
+ "\t fatal error warning info event debug spam\n",
+ "for both lists, use 'all' for all possible values, and -xxx to disable xxx.\n";
+ exit $bad;
+}
+
+
+$optlevels = join(",", @optlevels );
+if ( $optlevels ) {
+ my $k;
+ unless ( $optlevels =~ s/^\+// or $optlevels =~ m/^-/ ) {
+ $levelflags{$_} = 0 foreach ( keys %levelflags );
+ }
+ my @l = split(/,|(?=-)/, $optlevels);
+ my $l;
+ foreach $l ( @l ) {
+ my $v = 1;
+ my $minus = "";
+ if ( $l =~ s/^-// ) { $v = 0; $minus = "-"; }
+ if ( $l eq "all" ) {
+ foreach $k ( keys %levelflags ) {
+ $levelflags{$k} = $v;
+ }
+ } elsif ( defined $levelflags{$l} ) {
+ $levelflags{$l} = $v;
+ } else {
+ print STDERR "bad level option '$minus$l'\n";
+ exit 1;
+ }
+ }
+# print STDERR "select level $_ => $levelflags{$_}\n"
+# foreach ( keys %levelflags );
+}
+
+my $optshow;
+$optshow = join(",", @optshow );
+if ( $optshow ) {
+ my $k;
+ unless ( $optshow =~ s/^\+// or $optshow =~ m/^-/ ) {
+ $showflags{$_} = 0 foreach ( keys %showflags );
+ }
+ my @l = split(/,|(?=-)/, $optshow);
+ my $l;
+ foreach $l ( @l ) {
+ my $v = 1;
+ my $minus = "";
+ if ( $l =~ s/^-// ) { $v = 0; $minus = "-"; }
+ if ( $l eq "all" ) {
+ foreach $k ( keys %showflags ) {
+ $showflags{$k} = $v;
+ }
+ } elsif ( defined $showflags{$l} ) {
+ $showflags{$l} = $v;
+ } else {
+ print STDERR "bad show option '$minus$l'\n";
+ exit 1;
+ }
+ }
+# print STDERR "show field $_ => $showflags{$_}\n"
+# foreach ( keys %showflags ) ;
+}
+
+while (<>) {
+ chomp;
+ if ( /^
+ (\d+)\.?(\d*) # seconds, optional fractional seconds
+ \t
+ ([^\t]*) # host
+ \t
+ (\d+\/?\d*|\-\/\d+) # pid, optional tid
+ \t
+ ([^\t]*) # servicename
+ \t
+ ([^\t]*) # componentname
+ \t
+ (\w+) # level
+ \t
+ (.*) # message text
+ $/x )
+ {
+ my $secs = $1;
+ my $usec = $2 . "000000"; # make sure we have atleast 6 digits
+ my $host = $3;
+ my $pidn = $4;
+ my $svcn = $5;
+ my $comp = $6;
+ my $levl = $7;
+ my $msgt = $8;
+
+ if ( ! defined $levelflags{$levl} ) {
+ print STDERR "Warning: unknown level '$levl' in input\n";
+ $levelflags{$levl} = 1;
+ }
+ next unless ( $levelflags{$levl} );
+
+ if ($compore && $comp !~ m/$compore/o) { next; }
+ if ($msgtxre && $msgt !~ m/$msgtxre/o) { next; }
+ if ($onlypid && $pidn ne $onlypid) { next; }
+ if ($onlysvc && $svcn ne $onlysvc) { next; }
+ if ($onlyhst && $host ne $onlyhst) { next; }
+
+ $levl = "\U$levl";
+
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday);
+ ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday)=localtime($secs);
+ my $datestr = sprintf("%04d-%02d-%02d",
+ 1900+$year, 1+$mon, $mday);
+ my $timestr = sprintf("%02d:%02d:%02d",
+ $hour, $min, $sec);
+
+ if ( $showflags{"time"} || $showflags{"fmttime"} ) {
+ if ($showflags{"fmttime"} ) {
+ print "[$datestr $timestr";
+ if ( $showflags{"usecs"} ) {
+ printf ".%.6s", $usec;
+ } elsif ( $showflags{"msecs"} ) {
+ printf ".%.3s", $usec;
+ }
+ print "] ";
+ } else {
+ printf "%s.%.6s ", $secs, $usec;
+ }
+ }
+ if ( $showflags{"host"} ) {
+ printf "%-8s ", $host;
+ }
+ if ( $showflags{"level"} ) {
+ printf "%-7s : ", $levl;
+ }
+ if ( $showflags{"pid"} ) {
+ printf "%5s ", $pidn;
+ }
+ if ( $showflags{"service"} ) {
+ if ( $shortsvc ) {
+ printf "%-9.9s ", $svcn;
+ } else {
+ printf "%-16s ", $svcn;
+ }
+ }
+ if ( $showflags{"component"} ) {
+ if ( $shortcmp ) {
+ printf "%-15.15s ", $comp;
+ } else {
+ printf "%s\t", $comp;
+ }
+ }
+ if ( $showflags{"message"} ) {
+ if ( $optnldequote ) {
+ my $did_dequote_1 = ( $msgt =~ s/\\n\\t/\n\t/g );
+ my $did_dequote_2 = ( $msgt =~ s/\\n/\n\t/g );
+ $msgt = "\n\t${msgt}" if ( $did_dequote_1 || $did_dequote_2 );
+ $msgt =~ s/\\t/\t/;
+ }
+ print $msgt;
+ }
+ print "\n";
+ } else {
+ print STDERR "bad log line: '$_'\n";
+ }
+}
diff --git a/vespalog/src/logger/.gitignore b/vespalog/src/logger/.gitignore
new file mode 100644
index 00000000000..7cd5c615ee3
--- /dev/null
+++ b/vespalog/src/logger/.gitignore
@@ -0,0 +1,5 @@
+.depend
+Makefile
+vespa-runserver
+vlogconv
+vlogreplay
diff --git a/vespalog/src/logger/CMakeLists.txt b/vespalog/src/logger/CMakeLists.txt
new file mode 100644
index 00000000000..b2327a1327a
--- /dev/null
+++ b/vespalog/src/logger/CMakeLists.txt
@@ -0,0 +1,26 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalog_vlogconv_app
+ SOURCES
+ logger.cpp
+ llreader.cpp
+ OUTPUT_NAME vlogconv
+ INSTALL bin
+ DEPENDS
+ vespalog
+)
+vespa_add_executable(vespalog_vlogreplay_app
+ SOURCES
+ logreplay.c
+ OUTPUT_NAME vlogreplay
+ INSTALL bin
+ DEPENDS
+)
+vespa_add_executable(vespalog_vespa-runserver_app
+ SOURCES
+ runserver.cpp
+ llreader.cpp
+ OUTPUT_NAME vespa-runserver
+ INSTALL bin
+ DEPENDS
+ vespalog
+)
diff --git a/vespalog/src/logger/llreader.cpp b/vespalog/src/logger/llreader.cpp
new file mode 100644
index 00000000000..82fa48777b0
--- /dev/null
+++ b/vespalog/src/logger/llreader.cpp
@@ -0,0 +1,113 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+#include "llreader.h"
+
+namespace ns_log {
+
+
+InputBuf::InputBuf(int fd)
+ : _inputfd(fd),
+ _size(1000),
+ _buf((char *)malloc(_size)),
+ _bp(_buf),
+ _left(_size)
+{ }
+
+
+InputBuf::~InputBuf()
+{
+ free(_buf);
+}
+
+
+bool
+InputBuf::hasInput()
+{
+ char *p = _buf;
+ while (p < _bp) {
+ if (*p == '\n') return true;
+ p++;
+ }
+ return false;
+
+}
+
+void
+InputBuf::doInput(LLParser& via)
+{
+ char *p = _buf;
+ while (p < _bp) {
+ if (*p == '\n') {
+ *p = '\0';
+ via.doInput(_buf);
+ ++p;
+ int len = p - _buf;
+ int movelen = _bp - p;
+ memmove(_buf, p, movelen);
+ _bp -= len;
+ _left += len;
+ p = _buf;
+ continue;
+ }
+ p++;
+ }
+}
+
+void
+InputBuf::extend()
+{
+ _size *= 2;
+ int pos = _bp - _buf;
+ char *nbuf = (char *)realloc(_buf, _size);
+ if (nbuf == NULL) {
+ free(_buf);
+ throw MsgException("realloc failed");
+ }
+ _buf = nbuf;
+ _bp = _buf + pos;
+ _left = _size - pos;
+}
+
+bool
+InputBuf::blockRead()
+{
+ if (_left < 80) {
+ extend();
+ }
+ ssize_t len = ::read(_inputfd, _bp, _left);
+ if (len > 0) {
+ _bp += len;
+ _left -= len;
+ // printf("read %d bytes: '%.*s'\n", len, len, _buf);
+ } else if (len < 0) {
+ throw MsgException("error reading");
+ } else {
+ // EOF on input
+ return false;
+ }
+ return true;
+}
+
+void
+InputBuf::doAllInput(LLParser &outputvia)
+{
+ while (blockRead()) {
+ while (hasInput()) {
+ doInput(outputvia);
+ }
+ }
+ if (_bp != _buf) {
+ if (_left < 1) extend();
+ *_bp++ = '\n';
+ doInput(outputvia);
+ }
+}
+
+} // namespace
diff --git a/vespalog/src/logger/llreader.h b/vespalog/src/logger/llreader.h
new file mode 100644
index 00000000000..7519f99959f
--- /dev/null
+++ b/vespalog/src/logger/llreader.h
@@ -0,0 +1,42 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <stdexcept>
+
+#include <vespa/log/log.h>
+#include <vespa/log/log-target.h>
+#include <vespa/log/llparser.h>
+
+namespace ns_log {
+
+class MsgException : public std::exception {
+private:
+ const char *_string;
+ MsgException& operator = (const MsgException &);
+public:
+ MsgException(const MsgException &x) : std::exception(), _string(x._string) {}
+ MsgException(const char *s) : _string(s) {}
+ ~MsgException() throw() {} // nothing to do
+ const char *what() const throw() { return _string; }
+};
+
+
+class InputBuf
+{
+private:
+ int _inputfd;
+ int _size;
+ char *_buf;
+ char *_bp;
+ int _left;
+ void extend();
+ InputBuf(const InputBuf& other);
+ InputBuf& operator= (const InputBuf& other);
+public:
+ InputBuf(int fd);
+ ~InputBuf();
+ bool blockRead();
+ bool hasInput();
+ void doInput(LLParser &via);
+ void doAllInput(LLParser &via);
+};
+
+} // namespace
diff --git a/vespalog/src/logger/logger.cpp b/vespalog/src/logger/logger.cpp
new file mode 100644
index 00000000000..fcd1ef9d57d
--- /dev/null
+++ b/vespalog/src/logger/logger.cpp
@@ -0,0 +1,56 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <vespa/log/llparser.h>
+#include "llreader.h"
+
+using namespace ns_log;
+
+int main(int argc, char *argv[])
+{
+ InputBuf input(0);
+ LLParser llparser;
+
+ int ch;
+ while ((ch = getopt(argc, argv, "s:c:l:p:h")) != -1) {
+ switch (ch) {
+ case 's':
+ llparser.setService(optarg);
+ break;
+ case 'c':
+ llparser.setComponent(optarg);
+ break;
+ case 'l': {
+ Logger::LogLevel level = Logger::parseLevel(optarg);
+ if (level == Logger::NUM_LOGLEVELS) {
+ fprintf(stderr, "Unknown loglevel %s - using info\n",
+ optarg);
+ level = Logger::info;
+ }
+ llparser.setDefaultLevel(level);
+ break;
+ }
+ case 'p':
+ llparser.setPid(atoi(optarg));
+ break;
+ default:
+ fprintf(stderr, "Usage: foo | %s [-s service] [-c component]"
+ "[-l level] [-p pid]\n", argv[0]);
+ exit(ch != 'h');
+ }
+ }
+
+ try {
+ input.doAllInput(llparser);
+ } catch (MsgException& ex) {
+ fprintf(stderr, "error: %s\n", ex.what());
+ return 1;
+ }
+ return 0;
+}
diff --git a/vespalog/src/logger/logreplay.c b/vespalog/src/logger/logreplay.c
new file mode 100644
index 00000000000..cc3ee798aa4
--- /dev/null
+++ b/vespalog/src/logger/logreplay.c
@@ -0,0 +1,58 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <sys/types.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+int
+main(int argc, char **argv)
+{
+ char line[10000];
+
+ double delta = 0;
+
+ if (argc != 1) {
+ fprintf(stderr, "Usage: %s < <vespa.log>\n"
+ "Replays a vespa log file with the same timing delta between each log message.\n"
+ "Reprints the log messages without timestamps.\n\n",
+ argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ while (fgets(line, sizeof line, stdin)) {
+ struct timeval tv;
+ char *s;
+ double log_time;
+ double now;
+ double delay;
+
+ gettimeofday(&tv, NULL);
+ now = tv.tv_sec + 1e-6 * tv.tv_usec;
+
+ log_time = strtod(line, NULL);
+ if (!delta) {
+ delta = now - log_time;
+ }
+
+ delay = log_time + delta - now;
+ if (delay > 0) {
+ tv.tv_sec = floor(delay);
+ tv.tv_usec = 1e6 * (delay - floor(delay));
+ /* fprintf(stderr, "Sleeping for %.3fs\n", delay); */
+ select(0, NULL, NULL, NULL, &tv);
+ }
+
+ s = strchr(line, '\t');
+ if (s) {
+ s = s + 1;
+ } else {
+ s = line;
+ }
+ fwrite(s, strlen(s), 1, stdout);
+ fflush(stdout);
+ }
+ exit(EXIT_SUCCESS);
+}
diff --git a/vespalog/src/logger/runserver.cpp b/vespalog/src/logger/runserver.cpp
new file mode 100644
index 00000000000..fde952720c7
--- /dev/null
+++ b/vespalog/src/logger/runserver.cpp
@@ -0,0 +1,470 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <signal.h>
+
+#include <sys/select.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/file.h>
+#include <fcntl.h>
+
+#include <vespa/defaults.h>
+#include <vespa/log/llparser.h>
+#include "llreader.h"
+#include <vespa/log/log.h>
+
+LOG_SETUP("runserver");
+
+// XXX should close all file descriptors,
+// XXX but that makes logging go haywire
+
+static volatile sig_atomic_t gotstopsig = 0;
+static volatile sig_atomic_t lastsig = 0;
+static volatile sig_atomic_t unhandledsig = 0;
+
+extern "C" {
+void termsig(int sig) {
+ lastsig = sig;
+ gotstopsig = 1;
+ unhandledsig = 1;
+}
+}
+
+class PidFile
+{
+private:
+ char *_pidfile;
+ int _fd;
+ PidFile(const PidFile&);
+ PidFile& operator= (const PidFile&);
+public:
+ PidFile(const char *pidfile) : _pidfile(strdup(pidfile)), _fd(-1) {}
+ ~PidFile() { free(_pidfile); if (_fd >= 0) close(_fd); }
+ int readPid();
+ void writePid();
+ bool writeOpen();
+ bool isRunning();
+ bool isMine();
+ void cleanUp();
+};
+
+void
+PidFile::cleanUp()
+{
+ if (isMine() || !isRunning()) remove(_pidfile);
+ if (_fd >= 0) close(_fd);
+ _fd = -1;
+}
+
+bool
+PidFile::writeOpen()
+{
+ if (_fd >= 0) close(_fd);
+ int flags = O_CREAT | O_WRONLY | O_NONBLOCK;
+ _fd = open(_pidfile, flags, 0644);
+ if (_fd < 0) {
+ fprintf(stderr, "could not create pidfile %s: %s\n", _pidfile,
+ strerror(errno));
+ return false;
+ }
+ // XXX should we use locking or not?
+ if (flock(_fd, LOCK_EX | LOCK_NB) != 0) {
+ fprintf(stderr, "could not lock pidfile %s: %s\n", _pidfile,
+ strerror(errno));
+ close(_fd);
+ _fd = -1;
+ return false;
+ }
+ fcntl(_fd, F_SETFD, FD_CLOEXEC);
+ return true;
+}
+
+void
+PidFile::writePid()
+{
+ if (_fd < 0) abort();
+ ftruncate(_fd, (off_t)0);
+ char buf[100];
+ sprintf(buf, "%d\n", getpid());
+ int l = strlen(buf);
+ ssize_t didw = write(_fd, buf, l);
+ if (didw != l) {
+ fprintf(stderr, "could not write pid to %s: %s\n",
+ _pidfile, strerror(errno));
+ exit(1);
+ }
+ LOG(debug, "wrote '%s' to %s (fd %d)", buf, _pidfile, _fd);
+}
+
+int
+PidFile::readPid()
+{
+ FILE *pf = fopen(_pidfile, "r");
+ if (pf == NULL) return 0;
+ char buf[100];
+ strcpy(buf, "0");
+ fgets(buf, 100, pf);
+ fclose(pf);
+ return atoi(buf);
+}
+
+bool
+PidFile::isRunning()
+{
+ int pid = readPid();
+ if (pid < 1) return false;
+ return (kill(pid, 0) == 0 || errno == EPERM);
+}
+
+bool
+PidFile::isMine()
+{
+ int pid = readPid();
+ return (pid == getpid());
+}
+
+using namespace ns_log;
+
+int loop(const char *svc, char * const * run)
+{
+ int pstdout[2];
+ int pstderr[2];
+
+ if (pipe(pstdout) < 0 || pipe(pstderr) < 0) {
+ LOG(error, "pipe: %s", strerror(errno));
+ exit(1);
+ }
+ LOG(debug, "stdout pipe %d <- %d; stderr pipe %d <- %d",
+ pstdout[0], pstdout[1],
+ pstderr[0], pstderr[1]);
+
+ int high = 1 + pstdout[0] + pstderr[0];
+
+ pid_t child = fork();
+
+ if (child == 0) {
+ // I am the child process
+ dup2(pstdout[1], 1);
+ dup2(pstderr[1], 2);
+ close(pstdout[0]);
+ close(pstderr[0]);
+ close(pstdout[1]);
+ close(pstderr[1]);
+ execvp(run[0], run);
+ LOG(error, "exec %s: %s", run[0], strerror(errno));
+ exit(1);
+ }
+ if (child < 0) {
+ LOG(error, "fork(): %s", strerror(errno));
+ exit(1);
+ }
+ // I am the parent process
+
+ LOG(debug, "started %s (pid %d)", run[0], (int)child);
+ std::string torun = run[0];
+ for (char * const *arg = (run + 1); *arg != NULL; ++arg) {
+ torun += " ";
+ torun += *arg;
+ }
+
+ {
+ torun += " (pid ";
+ char buf[20];
+ sprintf(buf, "%d", (int)child);
+ torun += buf;
+ torun += ")";
+ }
+ EV_STARTING(torun.c_str());
+
+ close(pstdout[1]);
+ close(pstderr[1]);
+
+ LLParser outvia;
+ LLParser errvia;
+ outvia.setDefaultLevel(Logger::info);
+ errvia.setDefaultLevel(Logger::warning);
+
+ outvia.setService(svc);
+ errvia.setService(svc);
+ outvia.setComponent("stdout");
+ errvia.setComponent("stderr");
+ outvia.setPid(child);
+ errvia.setPid(child);
+
+ InputBuf outReader(pstdout[0]);
+ InputBuf errReader(pstderr[0]);
+
+ bool outeof = false;
+ bool erreof = false;
+
+ int wstat = 0;
+
+ while (child || !outeof || !erreof) {
+ struct timeval timeout;
+
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 100000; // == 100 ms == 1/10 s
+
+ fd_set pipes;
+
+ FD_ZERO(&pipes);
+ if (!outeof) FD_SET(pstdout[0], &pipes);
+ if (!erreof) FD_SET(pstderr[0], &pipes);
+
+ int n = select(high, &pipes, NULL, NULL, &timeout);
+ if (n > 0) {
+ if (FD_ISSET(pstdout[0], &pipes)) {
+ LOG(debug, "out reader has input");
+ if (outReader.blockRead()) {
+ while (outReader.hasInput()) {
+ LOG(debug, "process out reader input");
+ outReader.doInput(outvia);
+ }
+ } else {
+ LOG(debug, "eof on stdout");
+ outeof = true; // EOF on stdout
+ close(pstdout[0]);
+ }
+ }
+ if (FD_ISSET(pstderr[0], &pipes)) {
+ LOG(debug, "err reader has input");
+ if (errReader.blockRead()) {
+ while (errReader.hasInput()) {
+ LOG(debug, "process err reader input");
+ errReader.doInput(errvia);
+ }
+ } else {
+ LOG(debug, "eof on stderr");
+ erreof = true; // EOF on stderr
+ close(pstderr[0]);
+ }
+ }
+ }
+
+ if (child != 0) {
+ int cpid = waitpid(child, &wstat, WNOHANG);
+ if (cpid == child) {
+ if (WIFSTOPPED(wstat)) {
+ LOG(info, "child %d stopped, waiting for it to continue",
+ cpid);
+ } else if (WIFEXITED(wstat)) {
+ // child terminated
+ LOG(debug, "child %d exit status: %d", cpid,
+ (int)WEXITSTATUS(wstat));
+ EV_STOPPED(torun.c_str(), (int)child, (int)WEXITSTATUS(wstat));
+ child = 0;
+ } else if (WIFSIGNALED(wstat)) {
+ if (WTERMSIG(wstat) != lastsig) {
+ LOG(warning, "child died from signal: %d", WTERMSIG(wstat));
+ if (WCOREDUMP(wstat)) {
+ LOG(info, "child %d dumped core", cpid);
+ }
+ }
+ child = 0;
+ } else {
+ LOG(error, "unexpected status %d from waidpit", wstat);
+ abort();
+ }
+ } else if (cpid < 0) {
+ LOG(error, "waitpid: %s", strerror(errno));
+ abort();
+ } else if (cpid != 0) {
+ LOG(warning, "unexpected status %d for pid %d",
+ wstat, cpid);
+ abort();
+ }
+ }
+ if (unhandledsig && child != 0) {
+ LOG(debug, "got signal %d, sending to pid %d",
+ (int)lastsig, (int)child);
+ char why[256];
+ sprintf(why, "got signal %d", (int)lastsig);
+ EV_STOPPING(torun.c_str(), why);
+ kill(child, lastsig);
+ unhandledsig = 0;
+ }
+ }
+ if (WIFSIGNALED(wstat))
+ return WTERMSIG(wstat);
+ return WEXITSTATUS(wstat);
+}
+
+void usage(char *prog, int es)
+{
+ fprintf(stderr, "Usage: %s\n"
+ " [-s service] [-r restartinterval] [-p pidfile]"
+ " program [args ...]\n"
+ "or: [-p pidfile] [-k killcmd] -S\n", prog);
+ exit(es);
+}
+
+int main(int argc, char *argv[])
+{
+ bool doStop = false;
+ int restart = 0;
+ const char *service = "runserver";
+ const char *pidfile = "vespa-runserver.pid"; // XXX bad default?
+ const char *killcmd = NULL;
+
+ int ch;
+ while ((ch = getopt(argc, argv, "k:s:r:p:Sh")) != -1) {
+ switch (ch) {
+ case 's':
+ service = optarg;
+ break;
+ case 'r':
+ restart = atoi(optarg);
+ break;
+ case 'p':
+ pidfile = optarg;
+ break;
+ case 'S':
+ doStop = true;
+ break;
+ case 'k':
+ killcmd = optarg;
+ break;
+ default:
+ usage(argv[0], ch != 'h');
+ }
+ }
+
+ const char *envROOT = getenv("ROOT");
+ if (envROOT == NULL || envROOT[0] == '\0') {
+ envROOT = vespa::Defaults::vespaHome();
+ setenv("ROOT", envROOT, 1);
+ }
+ if (chdir(envROOT) != 0) {
+ fprintf(stderr, "Cannot chdir to %s: %s\n", envROOT, strerror(errno));
+ exit(1);
+ }
+
+ PidFile mypf(pidfile);
+ if (doStop) {
+ if (mypf.isRunning()) {
+ int pid = mypf.readPid();
+ if (killcmd != NULL) {
+ fprintf(stdout, "%s was running with pid %d, running '%s' to stop it\n",
+ service, pid, killcmd);
+ if (system(killcmd) != 0) {
+ fprintf(stderr, "WARNING: stop command '%s' had some problem\n", killcmd);
+ }
+ } else {
+ fprintf(stdout, "%s was running with pid %d, sending SIGTERM\n",
+ service, pid);
+ if (killpg(pid, SIGTERM) != 0) {
+ fprintf(stderr, "could not signal %d: %s\n", pid,
+ strerror(errno));
+ exit(1);
+ }
+ }
+ fprintf(stdout, "Waiting for exit (up to 60 seconds)\n");
+ for (int cnt(0); cnt < 1800; cnt++) {
+ usleep(100000); // wait 0.1 seconds
+ if ((cnt > 300) && (cnt % 100 == 0)) {
+ killpg(pid, SIGTERM);
+ }
+ if (killpg(pid, 0) == 0) {
+ if (cnt%10 == 0) {
+ fprintf(stdout, ".");
+ fflush(stdout);
+ }
+ } else {
+ fprintf(stdout, "DONE\n");
+ break;
+ }
+ if (cnt == 900) {
+ printf("\ngiving up, sending KILL signal\n");
+ killpg(pid, SIGKILL);
+ }
+ }
+ } else {
+ fprintf(stdout, "%s not running according to %s\n",
+ service, pidfile);
+ }
+ mypf.cleanUp();
+ exit(0);
+ }
+ if (optind >= argc || killcmd != NULL) {
+ usage(argv[0], 1);
+ }
+
+ if (mypf.isRunning()) {
+ fprintf(stderr, "runserver already running with pid %d\n",
+ mypf.readPid());
+ exit(0);
+ }
+
+ if (!mypf.writeOpen()) {
+ perror(pidfile);
+ return 1;
+ }
+
+ pid_t rsp = fork();
+ if (rsp == 0) {
+ close(0);
+ if (open("/dev/null", O_RDONLY) != 0) {
+ perror("open /dev/null for reading failed");
+ exit(1);
+ }
+ close(1);
+ if (open("/dev/null", O_WRONLY) != 1) {
+ perror("open /dev/null for writing failed");
+ exit(1);
+ }
+ dup2(1, 2);
+ if (setsid() < 0) {
+ perror("setsid");
+ exit(1);
+ }
+ struct sigaction act;
+ struct sigaction oact;
+
+ memset(&act, 0, sizeof(act));
+
+ act.sa_handler = termsig;
+
+ sigaction(SIGINT, &act, &oact);
+ sigaction(SIGTERM, &act, &oact);
+
+ int stat = 0;
+ try {
+ mypf.writePid();
+ do {
+ time_t laststart = time(NULL);
+ stat = loop(service, argv+optind);
+ if (restart > 0 && !gotstopsig) {
+ int wt = restart + laststart - time(NULL);
+ if (wt < 0) wt = 0;
+ LOG(info, "will restart in %d seconds", wt);
+ }
+ while (!gotstopsig && time(NULL) - laststart < restart) {
+ sleep(1);
+ }
+ } while (!gotstopsig && restart > 0);
+ } catch (MsgException& ex) {
+ LOG(error, "exception: '%s'", ex.what());
+ exit(1);
+ }
+ if (restart > 0) {
+ LOG(debug, "final exit status: %d", stat);
+ }
+ mypf.cleanUp();
+ exit(stat);
+ }
+
+ if (rsp < 0) {
+ perror("fork");
+ return 1;
+ }
+ printf("runserver(%s) running with pid: %d\n", service, rsp);
+ return 0;
+}
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;
diff --git a/vespalog/src/test/.gitignore b/vespalog/src/test/.gitignore
new file mode 100644
index 00000000000..dde71217083
--- /dev/null
+++ b/vespalog/src/test/.gitignore
@@ -0,0 +1,14 @@
+.depend
+Makefile
+bufferedlogskiptest
+bufferedlogskiptest.log
+bufferedlogtest
+bufferedlogtest.log
+empty.*
+logtest
+testthreads
+vespa.log
+/rejectfiltertest
+vespalog_bufferedlogskiptest_app
+vespalog_bufferedlogtest_app
+vespalog_rejectfiltertest_app
diff --git a/vespalog/src/test/CMakeLists.txt b/vespalog/src/test/CMakeLists.txt
new file mode 100644
index 00000000000..f9b2f360fa8
--- /dev/null
+++ b/vespalog/src/test/CMakeLists.txt
@@ -0,0 +1,24 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalog_bufferedlogtest_app
+ SOURCES
+ bufferedlogtest.cpp
+ bufferedlogtest.logger1.cpp
+ bufferedlogtest.logger2.cpp
+ DEPENDS
+ vespalog
+)
+vespa_add_test(NAME vespalog_bufferedlogtest_app NO_VALGRIND COMMAND sh bufferedlogtest_test.sh)
+vespa_add_executable(vespalog_bufferedlogskiptest_app
+ SOURCES
+ bufferedlogskiptest.cpp
+ DEPENDS
+ vespalog
+)
+vespa_add_test(NAME vespalog_bufferedlogskiptest_app NO_VALGRIND COMMAND sh bufferedlogskiptest_test.sh)
+vespa_add_executable(vespalog_rejectfiltertest_app
+ SOURCES
+ rejectfiltertest.cpp
+ DEPENDS
+ vespalog
+)
+vespa_add_test(NAME vespalog_rejectfiltertest_app NO_VALGRIND COMMAND vespalog_rejectfiltertest_app)
diff --git a/vespalog/src/test/bufferedlogskiptest.cpp b/vespalog/src/test/bufferedlogskiptest.cpp
new file mode 100644
index 00000000000..5b386d18e90
--- /dev/null
+++ b/vespalog/src/test/bufferedlogskiptest.cpp
@@ -0,0 +1,90 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/log/log.h>
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+
+LOG_SETUP("bufferedlogskiptest",
+ "$Id$");
+
+std::string readFile(const std::string& file) {
+ std::ostringstream ost;
+ std::ifstream is(file.c_str());
+ std::string line;
+ while (std::getline(is, line)) {
+ std::string::size_type pos = line.find('\t');
+ if (pos == std::string::npos) continue;
+ std::string::size_type pos2 = line.find('\t', pos + 1);
+ if (pos2 == std::string::npos) continue;
+ std::string result = line.substr(0, pos)
+ + "\tlocalhost"
+ + line.substr(pos2);
+ // Ignore debug entries. (Log adds some itself with timestamp we
+ // can't control)
+ if (result.find("\tdebug\t") == std::string::npos) {
+ ost << result << "\n";
+ }
+ }
+ return ost.str();
+}
+
+void testSkipBufferOnDebug(const std::string& file, uint64_t & timer)
+{
+ std::cerr << "testSkipBufferOnDebug ...\n";
+ LOGBM(info, "Starting up, using logfile %s", file.c_str());
+
+ timer = 200 * 1000000;
+ for (uint32_t i=0; i<10; ++i) {
+ LOGBP(info, "Message");
+ timer += 1;
+ LOGBM(info, "Message");
+ timer += 1;
+ LOGBT(info, "Message", "Message");
+ timer += 1;
+ }
+
+ std::string result(readFile(file));
+ std::string expected(readFile("bufferedlogskiptest.skipped.log"));
+
+ if (result != expected) {
+ std::cerr << "Failed "
+ << "testSkipBufferOnDebug\n";
+ system(("diff -u " + file
+ + " bufferedlogskiptest.skipped.log").c_str());
+ exit(EXIT_FAILURE);
+ }
+ unlink(file.c_str());
+
+}
+
+void reset(uint64_t & timer) {
+ timer = 0;
+ ns_log::BufferedLogger::logger.setMaxCacheSize(10);
+ ns_log::BufferedLogger::logger.setMaxEntryAge(300);
+ ns_log::BufferedLogger::logger.setCountFactor(5);
+}
+
+int
+main(int argc, char **argv)
+{
+ if (argc != 2) {
+ std::cerr << "bufferedlogskiptest must be called with one argument\n";
+ exit(EXIT_FAILURE);
+ }
+ ns_log::Logger::fakePid = true;
+ uint64_t timer;
+ logger.setTimer(std::unique_ptr<ns_log::Timer>(new ns_log::TestTimer(timer)));
+ ns_log::BufferedLogger::logger.setTimer(std::unique_ptr<ns_log::Timer>(new ns_log::TestTimer(timer)));
+
+ reset(timer);
+ testSkipBufferOnDebug(argv[1], timer);
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/vespalog/src/test/bufferedlogskiptest.skipped.log b/vespalog/src/test/bufferedlogskiptest.skipped.log
new file mode 100644
index 00000000000..c0c2cc36309
--- /dev/null
+++ b/vespalog/src/test/bufferedlogskiptest.skipped.log
@@ -0,0 +1,32 @@
+1220016833.684527 example.yahoo.com 15527 - bufferedlogskiptest.log debug log.cpp:111 (1.77): prefix was set to 'bufferedlogskiptest'
+0.000000 example.yahoo.com -1/-1 - bufferedlogskiptest info Starting up, using logfile bufferedlogskiptest.log
+200.000000 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000001 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000002 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000003 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000004 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000005 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000006 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000007 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000008 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000009 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000010 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000011 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000012 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000013 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000014 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000015 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000016 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000017 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000018 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000019 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000020 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000021 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000022 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000023 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000024 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000025 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000026 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000027 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000028 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
+200.000029 example.yahoo.com -1/-1 - bufferedlogskiptest info Message
diff --git a/vespalog/src/test/bufferedlogskiptest_test.sh b/vespalog/src/test/bufferedlogskiptest_test.sh
new file mode 100755
index 00000000000..50887394bf6
--- /dev/null
+++ b/vespalog/src/test/bufferedlogskiptest_test.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+rm -f bufferedlogskiptest.log
+VESPA_LOG_TARGET="file:bufferedlogskiptest.log" VESPA_LOG_LEVEL="all -spam" ./vespalog_bufferedlogskiptest_app bufferedlogskiptest.log
diff --git a/vespalog/src/test/bufferedlogtest.cpp b/vespalog/src/test/bufferedlogtest.cpp
new file mode 100644
index 00000000000..0e27bd65988
--- /dev/null
+++ b/vespalog/src/test/bufferedlogtest.cpp
@@ -0,0 +1,408 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/log/log.h>
+
+#include "bufferedlogtest.logger1.h"
+#include "bufferedlogtest.logger2.h"
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+
+LOG_SETUP("bufferedlogtest",
+ "$Id$");
+
+std::string readFile(const std::string& file) {
+ std::ostringstream ost;
+ std::ifstream is(file.c_str());
+ std::string line;
+ while (std::getline(is, line)) {
+ std::string::size_type pos = line.find('\t');
+ if (pos == std::string::npos) continue;
+ std::string::size_type pos2 = line.find('\t', pos + 1);
+ if (pos2 == std::string::npos) continue;
+ std::string result = line.substr(0, pos)
+ + "\tlocalhost"
+ + line.substr(pos2);
+ ost << result << "\n";
+ }
+ return ost.str();
+}
+
+void spamLog1(uint64_t& time, int diff) {
+ for (int i=0; i<100; ++i) {
+ time += diff;
+ LOGBT(warning, "Failed to send to node 4",
+ "Failed to send message to node 4: NOT CONNECTED");
+ time += diff;
+ LOGBT(warning, "Failed to send to node 4",
+ "Failed to send message to node 4: NOT_READY");
+ time += diff;
+ LOGBT(warning, "Failed to send to node 4",
+ "Failed to send message to node 4: BAAAH");
+ time += diff;
+ LOGBT(warning, "Failed to send to node 4",
+ "Failed to send message to node 4: RPC FAILURE");
+ time += diff;
+ LOGBT(warning, "Failed to send to node 4",
+ "Failed to send message to node 4: COSMIC RADIATION");
+ time += diff;
+ LOGBT(warning, "Failed to send to node 4",
+ "Failed to send message to node 4: ITS SATURDAY");
+ time += diff;
+ LOGBT(warning, "Failed to send to node 4",
+ "Failed to send message to node 4: Yeah, Right!!");
+ time += diff;
+ LOGBT(error, "Failed to send to node 4",
+ "Failed to send message to node 4: NOT CONNECTED");
+ time += diff;
+ LOGBT(spam, "Failed to send to node 4",
+ "Failed to send message to node 4: NOT CONNECTED");
+ time += diff;
+ LOGBT(warning, "Failed to send to node 4",
+ "Failed to send message to node 4: NOT CONNECTED");
+ time += diff * 10;
+ LOGBT(warning, "Failed to send to node 3",
+ "Failed to send message to node 3: NOT CONNECTED");
+ }
+}
+
+void spamLog2(uint64_t& time, int diff) {
+ for (int i=0; i<100; ++i) {
+ time += diff;
+ std::ostringstream ost;
+ ost << "Message " << i;
+ LOGBT(info, ost.str(), ost.str().c_str());
+ }
+}
+
+void testThatEntriesWithHighCountIsKept(const std::string& file,
+ uint64_t& timer)
+{
+ std::cerr << "testThatEntriesWithHighCountIsKept ...\n";
+ timer = 10 * 1000000 + 4;
+
+ LOGBM(info, "Starting up, using logfile %s", file.c_str());
+ timer = 100 * 1000000 + 4;
+ LOGBT(warning, "Token", "message");
+
+ spamLog1(timer, 1);
+ spamLog2(timer, 1);
+ spamLog1(timer, 1);
+
+ LOGB_FLUSH();
+
+ std::string result(readFile(file));
+ std::string expected(readFile("bufferedlogtest.highcountkept.log"));
+
+ if (result != expected) {
+ std::cerr << "Failed testThatEntriesWithHighCountIsKept\n";
+ system(("diff -u " + file + " bufferedlogtest.highcountkept.log")
+ .c_str());
+ exit(EXIT_FAILURE);
+ }
+ unlink(file.c_str());
+}
+
+void testThatEntriesWithHighCountsAreEventuallyRemoved(
+ const std::string& file, uint64_t& timer)
+{
+ std::cerr << "testThatEntriesWithHighCountsAreEventuallyRemoved ...\n";
+ // Same as above, just increase timer more between each log entry.
+ // Should eventually throw out the entries with high count
+ timer = 10 * 1000000 + 4;
+ // Make sure we don't remove due to age.
+ ns_log::BufferedLogger::logger.setMaxEntryAge(1000000);
+ // Let each count, count for 5 seconds.
+ ns_log::BufferedLogger::logger.setCountFactor(5);
+
+ LOGBM(info, "Starting up, using logfile %s", file.c_str());
+ timer = 100 * 1000000 + 4;
+ LOGBT(warning, "Token", "message");
+
+ spamLog1(timer, 1);
+ spamLog2(timer, 10 * 1000000);
+ spamLog1(timer, 1);
+
+ LOGB_FLUSH();
+
+ std::string result(readFile(file));
+ std::string expected(readFile("bufferedlogtest.highcountexpire.log"));
+
+ if (result != expected) {
+ std::cerr << "Failed "
+ << "testThatEntriesWithHighCountsAreEventuallyRemoved\n";
+ system(("diff -u " + file + " bufferedlogtest.highcountexpire.log")
+ .c_str());
+ exit(EXIT_FAILURE);
+ }
+ unlink(file.c_str());
+}
+
+void testThatEntriesExpire(
+ const std::string& file, uint64_t& timer)
+{
+ std::cerr << "testThatEntriesExpire ...\n";
+ // Test that we don't keep entries longer than max age
+ timer = 10 * 1000000 + 4;
+ // Time out after 120 seconds
+ ns_log::BufferedLogger::logger.setMaxEntryAge(120);
+ // Let counts count much, so they expire due to time instead
+ ns_log::BufferedLogger::logger.setCountFactor(100000);
+
+ LOGBM(info, "Starting up, using logfile %s", file.c_str());
+ timer = 100 * 1000000 + 4;
+ LOGBT(warning, "Token", "message");
+
+ spamLog1(timer, 1);
+ spamLog2(timer, 10 * 1000000);
+ spamLog1(timer, 1);
+
+ LOGB_FLUSH();
+
+ std::string result(readFile(file));
+ std::string expected(readFile("bufferedlogtest.expire.log"));
+
+ if (result != expected) {
+ std::cerr << "Failed "
+ << "testThatEntriesExpire\n";
+ system(("diff -u " + file + " bufferedlogtest.expire.log").c_str());
+ exit(EXIT_FAILURE);
+ }
+ unlink(file.c_str());
+}
+
+// Spam 10+ different entries lots of times, to fill cache with high count
+// entries
+void spamLog3(uint64_t& time, int diff) {
+ for (int i=0; i<100; ++i) {
+ time += diff;
+ LOGBT(warning, "Failed to talk to node 1",
+ "Failed to send message to node 0x1: NOT CONNECTED");
+ time += diff;
+ LOGBT(warning, "Failed to talk to node 2",
+ "Failed to send message to node 0x2: NOT_READY");
+ time += diff;
+ LOGBT(warning, "Failed to talk to node 3",
+ "Failed to send message to node 0x3: BAAAH");
+ time += diff;
+ LOGBT(warning, "Failed to talk to node 4",
+ "Failed to send message to node 0x4: RPC FAILURE");
+ time += diff;
+ LOGBT(warning, "Failed to talk to node 5",
+ "Failed to send message to node 0x5: COSMIC RADIATION");
+ time += diff;
+ LOGBT(warning, "Failed to talk to node 6",
+ "Failed to send message to node 0x6: ITS SATURDAY");
+ time += diff;
+ LOGBT(warning, "Failed to talk to node 7",
+ "Failed to send message to node 0x7: Yeah, Right!!");
+ time += diff;
+ LOGBT(error, "Failed to talk to node 8",
+ "Failed to send message to node 0x8: NOT CONNECTED");
+ time += diff;
+ LOGBT(info, "Failed to talk to node 9",
+ "Failed to send message to node 0x9: NOT CONNECTED");
+ time += diff;
+ LOGBT(warning, "Failed to talk to node 10",
+ "Failed to send message to node 0xa: NOT CONNECTED");
+ }
+}
+
+void testThatHighCountEntriesDontStarveOthers(
+ const std::string& file, uint64_t& timer)
+{
+ std::cerr << "testThatHighCountEntriesDontStarveOthers ...\n";
+ timer = 10 * 1000000 + 4;
+ // Long time out, we don't want to rely on timeout to prevent starvation
+ ns_log::BufferedLogger::logger.setMaxEntryAge(12000000);
+ // Let counts count much, so they score high
+ ns_log::BufferedLogger::logger.setCountFactor(100000);
+
+ LOGBM(info, "Starting up, using logfile %s", file.c_str());
+ timer = 100 * 1000000;
+ LOGBT(warning, "Token", "message");
+
+ spamLog3(timer, 1);
+ spamLog1(timer, 1);
+
+ LOGB_FLUSH();
+
+ std::string result(readFile(file));
+ std::string expected(readFile("bufferedlogtest.nostarve.log"));
+
+ if (result != expected) {
+ std::cerr << "Failed "
+ << "testThatHighCountEntriesDontStarveOthers\n";
+ system(("diff -u " + file + " bufferedlogtest.nostarve.log").c_str());
+ exit(EXIT_FAILURE);
+ }
+ unlink(file.c_str());
+}
+
+void testNoTokenMatchAcrossComponents(const std::string& file,
+ uint64_t& timer)
+{
+ std::cerr << "testNoTokenMatchAcrossComponents ...\n";
+
+ LOGBP(info, "Starting up, using logfile %s", file.c_str());
+
+ timer = 200 * 1000000;
+ for (uint32_t i=0; i<100; ++i) {
+ std::ostringstream ost;
+ ost << "Message " << i;
+ logWithLogger1("Message", ost.str());
+ timer += 1;
+ logWithLogger2("Message", ost.str());
+ timer += 1;
+ }
+
+ LOGB_FLUSH();
+
+ std::string result(readFile(file));
+ std::string expected(readFile("bufferedlogtest.tokenacrossloggers.log"));
+
+ if (result != expected) {
+ std::cerr << "Failed "
+ << "testNoTokenMatchAcrossComponents\n";
+ system(("diff -u " + file
+ + " bufferedlogtest.tokenacrossloggers.log").c_str());
+ exit(EXIT_FAILURE);
+ }
+ unlink(file.c_str());
+}
+
+void testLogLocationAsToken(const std::string& file, uint64_t& timer)
+{
+ std::cerr << "testLogLocationAsToken ...\n";
+ LOGBP(info, "Starting up, using logfile %s", file.c_str());
+
+ timer = 200 * 1000000;
+ for (uint32_t i=0; i<100; ++i) {
+ LOGBP(info, "Message %i", i);
+ timer += 1;
+ LOGBP(info, "Message %i", i);
+ timer += 1;
+ }
+
+ LOGB_FLUSH();
+
+ std::string result(readFile(file));
+ std::string expected(readFile("bufferedlogtest.locationastoken.log"));
+
+ if (result != expected) {
+ std::cerr << "Failed "
+ << "testLogLocationAsToken\n";
+ system(("diff -u " + file
+ + " bufferedlogtest.locationastoken.log").c_str());
+ exit(EXIT_FAILURE);
+ }
+ unlink(file.c_str());
+}
+
+void testLogMessageAsToken(const std::string& file, uint64_t& timer)
+{
+ std::cerr << "testLogMessageAsToken ...\n";
+ LOGBM(info, "Starting up, using logfile %s", file.c_str());
+
+ timer = 200 * 1000000;
+ for (uint32_t i=0; i<100; ++i) {
+ LOGBM(info, "Message %i", i % 10);
+ timer += 1;
+ LOGBM(info, "Message %i", i % 10);
+ timer += 1;
+ }
+
+ LOGB_FLUSH();
+
+ std::string result(readFile(file));
+ std::string expected(readFile("bufferedlogtest.messageastoken.log"));
+
+ if (result != expected) {
+ std::cerr << "Failed "
+ << "testLogMessageAsToken\n";
+ system(("diff -u " + file
+ + " bufferedlogtest.messageastoken.log").c_str());
+ exit(EXIT_FAILURE);
+ }
+ unlink(file.c_str());
+}
+
+void testNonBufferedLoggerTriggersBufferedLogTrim(const std::string& file,
+ uint64_t& timer)
+{
+ std::cerr << "testNonBufferedLoggerTriggersBufferedLogTrim ...\n";
+ // Write a lot of buffered log.
+ LOGBM(info, "Starting up, using logfile %s", file.c_str());
+
+ timer = 200 * 1000000;
+ for (uint32_t i=0; i<100; ++i) {
+ LOGBP(info, "Message %i", i);
+ timer += 1;
+ }
+
+ // Advance time to time where we should have flushed long ago.
+ timer = 100000 * 1000000ull;
+ LOG(info, "This should cause buffered log flush");
+
+ // So flushing should have happened before this time.
+ timer = 200000 * 1000000ull;
+ LOGB_FLUSH();
+
+ std::string result(readFile(file));
+ std::string expected(readFile("bufferedlogtest.trimcache.log"));
+
+ if (result != expected) {
+ std::cerr << "Failed "
+ << "testNonBufferedLoggerTriggersBufferedLogTrim\n";
+ system(("diff -u " + file
+ + " bufferedlogtest.trimcache.log").c_str());
+ exit(EXIT_FAILURE);
+ }
+ unlink(file.c_str());
+
+}
+
+void reset(uint64_t& timer) {
+ timer = 0;
+ ns_log::BufferedLogger::logger.setMaxEntryAge(300);
+ ns_log::BufferedLogger::logger.setCountFactor(5);
+}
+
+int
+main(int argc, char **argv)
+{
+ if (argc != 2) {
+ std::cerr << "bufferedlogtest must be called with one argument\n";
+ exit(EXIT_FAILURE);
+ }
+ ns_log::Logger::fakePid = true;
+ ns_log::BufferedLogger::logger.setMaxCacheSize(10);
+ uint64_t timer;
+ logger.setTimer(std::unique_ptr<ns_log::Timer>(new ns_log::TestTimer(timer)));
+ ns_log::BufferedLogger::logger.setTimer(std::unique_ptr<ns_log::Timer>(new ns_log::TestTimer(timer)));
+
+ reset(timer);
+ testThatEntriesWithHighCountIsKept(argv[1], timer);
+ reset(timer);
+ testThatEntriesWithHighCountsAreEventuallyRemoved(argv[1], timer);
+ reset(timer);
+ testThatEntriesExpire(argv[1], timer);
+ reset(timer);
+ testThatHighCountEntriesDontStarveOthers(argv[1], timer);
+ reset(timer);
+ testNoTokenMatchAcrossComponents(argv[1], timer);
+ reset(timer);
+ testLogLocationAsToken(argv[1], timer);
+ reset(timer);
+ testLogMessageAsToken(argv[1], timer);
+ reset(timer);
+ testNonBufferedLoggerTriggersBufferedLogTrim(argv[1], timer);
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/vespalog/src/test/bufferedlogtest.expire.log b/vespalog/src/test/bufferedlogtest.expire.log
new file mode 100644
index 00000000000..b68b1c3422e
--- /dev/null
+++ b/vespalog/src/test/bufferedlogtest.expire.log
@@ -0,0 +1,110 @@
+10.000004 example.yahoo.com -1/-1 - bufferedlogtest info Starting up, using logfile bufferedlogtest.log
+100.000004 example.yahoo.com -1/-1 - bufferedlogtest warning message
+100.000005 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 4: NOT CONNECTED
+100.000024 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 3: NOT CONNECTED
+110.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 0
+120.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 1
+130.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 2
+140.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 3
+150.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 4
+160.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 5
+170.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 6
+180.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 7
+190.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 8
+200.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 9
+210.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 10
+220.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 11
+220.002004 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 4: NOT CONNECTED (Repeated 899 times since 100.000005)
+220.002004 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 3: NOT CONNECTED (Repeated 99 times since 100.000024)
+230.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 12
+240.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 13
+250.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 14
+260.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 15
+270.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 16
+280.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 17
+290.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 18
+300.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 19
+310.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 20
+320.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 21
+330.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 22
+340.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 23
+350.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 24
+360.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 25
+370.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 26
+380.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 27
+390.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 28
+400.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 29
+410.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 30
+420.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 31
+430.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 32
+440.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 33
+450.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 34
+460.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 35
+470.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 36
+480.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 37
+490.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 38
+500.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 39
+510.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 40
+520.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 41
+530.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 42
+540.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 43
+550.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 44
+560.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 45
+570.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 46
+580.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 47
+590.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 48
+600.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 49
+610.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 50
+620.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 51
+630.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 52
+640.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 53
+650.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 54
+660.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 55
+670.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 56
+680.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 57
+690.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 58
+700.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 59
+710.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 60
+720.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 61
+730.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 62
+740.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 63
+750.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 64
+760.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 65
+770.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 66
+780.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 67
+790.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 68
+800.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 69
+810.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 70
+820.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 71
+830.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 72
+840.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 73
+850.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 74
+860.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 75
+870.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 76
+880.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 77
+890.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 78
+900.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 79
+910.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 80
+920.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 81
+930.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 82
+940.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 83
+950.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 84
+960.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 85
+970.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 86
+980.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 87
+990.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 88
+1000.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 89
+1010.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 90
+1020.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 91
+1030.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 92
+1040.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 93
+1050.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 94
+1060.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 95
+1070.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 96
+1080.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 97
+1090.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 98
+1100.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 99
+1100.002005 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 4: NOT CONNECTED
+1100.002024 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 3: NOT CONNECTED
+1100.004004 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 4: NOT CONNECTED (Repeated 899 times since 1100.002005)
+1100.004004 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 3: NOT CONNECTED (Repeated 99 times since 1100.002024)
diff --git a/vespalog/src/test/bufferedlogtest.highcountexpire.log b/vespalog/src/test/bufferedlogtest.highcountexpire.log
new file mode 100644
index 00000000000..fb07d45d924
--- /dev/null
+++ b/vespalog/src/test/bufferedlogtest.highcountexpire.log
@@ -0,0 +1,108 @@
+10.000004 example.yahoo.com -1/-1 - bufferedlogtest info Starting up, using logfile bufferedlogtest.log
+100.000004 example.yahoo.com -1/-1 - bufferedlogtest warning message
+100.000005 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 4: NOT CONNECTED
+100.000024 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 3: NOT CONNECTED
+110.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 0
+120.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 1
+130.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 2
+140.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 3
+150.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 4
+160.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 5
+170.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 6
+180.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 7
+190.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 8
+200.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 9
+210.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 10
+220.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 11
+230.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 12
+240.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 13
+250.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 14
+260.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 15
+270.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 16
+280.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 17
+290.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 18
+300.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 19
+310.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 20
+320.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 21
+330.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 22
+340.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 23
+350.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 24
+360.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 25
+370.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 26
+380.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 27
+390.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 28
+400.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 29
+410.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 30
+420.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 31
+430.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 32
+440.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 33
+450.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 34
+460.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 35
+470.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 36
+480.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 37
+490.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 38
+500.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 39
+510.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 40
+520.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 41
+530.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 42
+540.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 43
+550.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 44
+560.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 45
+570.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 46
+580.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 47
+590.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 48
+600.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 49
+610.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 50
+620.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 51
+630.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 52
+640.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 53
+650.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 54
+660.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 55
+670.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 56
+680.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 57
+680.002004 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 3: NOT CONNECTED (Repeated 99 times since 100.000024)
+690.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 58
+700.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 59
+710.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 60
+720.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 61
+730.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 62
+740.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 63
+750.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 64
+760.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 65
+770.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 66
+780.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 67
+790.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 68
+800.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 69
+810.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 70
+820.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 71
+830.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 72
+840.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 73
+850.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 74
+860.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 75
+870.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 76
+880.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 77
+890.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 78
+900.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 79
+910.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 80
+920.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 81
+930.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 82
+940.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 83
+950.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 84
+960.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 85
+970.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 86
+980.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 87
+990.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 88
+1000.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 89
+1010.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 90
+1020.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 91
+1030.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 92
+1040.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 93
+1050.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 94
+1060.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 95
+1070.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 96
+1080.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 97
+1090.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 98
+1100.002004 example.yahoo.com -1/-1 - bufferedlogtest info Message 99
+1100.002024 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 3: NOT CONNECTED
+1100.004004 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 4: NOT CONNECTED (Repeated 1799 times since 100.000005)
+1100.004004 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 3: NOT CONNECTED (Repeated 99 times since 1100.002024)
diff --git a/vespalog/src/test/bufferedlogtest.highcountkept.log b/vespalog/src/test/bufferedlogtest.highcountkept.log
new file mode 100644
index 00000000000..68679497033
--- /dev/null
+++ b/vespalog/src/test/bufferedlogtest.highcountkept.log
@@ -0,0 +1,106 @@
+10.000004 localhost -1/-1 - bufferedlogtest info Starting up, using logfile bufferedlogtest.log
+100.000004 localhost -1/-1 - bufferedlogtest warning message
+100.000005 localhost -1/-1 - bufferedlogtest warning Failed to send message to node 4: NOT CONNECTED
+100.000024 localhost -1/-1 - bufferedlogtest warning Failed to send message to node 3: NOT CONNECTED
+100.002005 localhost -1/-1 - bufferedlogtest info Message 0
+100.002006 localhost -1/-1 - bufferedlogtest info Message 1
+100.002007 localhost -1/-1 - bufferedlogtest info Message 2
+100.002008 localhost -1/-1 - bufferedlogtest info Message 3
+100.002009 localhost -1/-1 - bufferedlogtest info Message 4
+100.002010 localhost -1/-1 - bufferedlogtest info Message 5
+100.002011 localhost -1/-1 - bufferedlogtest info Message 6
+100.002012 localhost -1/-1 - bufferedlogtest info Message 7
+100.002013 localhost -1/-1 - bufferedlogtest info Message 8
+100.002014 localhost -1/-1 - bufferedlogtest info Message 9
+100.002015 localhost -1/-1 - bufferedlogtest info Message 10
+100.002016 localhost -1/-1 - bufferedlogtest info Message 11
+100.002017 localhost -1/-1 - bufferedlogtest info Message 12
+100.002018 localhost -1/-1 - bufferedlogtest info Message 13
+100.002019 localhost -1/-1 - bufferedlogtest info Message 14
+100.002020 localhost -1/-1 - bufferedlogtest info Message 15
+100.002021 localhost -1/-1 - bufferedlogtest info Message 16
+100.002022 localhost -1/-1 - bufferedlogtest info Message 17
+100.002023 localhost -1/-1 - bufferedlogtest info Message 18
+100.002024 localhost -1/-1 - bufferedlogtest info Message 19
+100.002025 localhost -1/-1 - bufferedlogtest info Message 20
+100.002026 localhost -1/-1 - bufferedlogtest info Message 21
+100.002027 localhost -1/-1 - bufferedlogtest info Message 22
+100.002028 localhost -1/-1 - bufferedlogtest info Message 23
+100.002029 localhost -1/-1 - bufferedlogtest info Message 24
+100.002030 localhost -1/-1 - bufferedlogtest info Message 25
+100.002031 localhost -1/-1 - bufferedlogtest info Message 26
+100.002032 localhost -1/-1 - bufferedlogtest info Message 27
+100.002033 localhost -1/-1 - bufferedlogtest info Message 28
+100.002034 localhost -1/-1 - bufferedlogtest info Message 29
+100.002035 localhost -1/-1 - bufferedlogtest info Message 30
+100.002036 localhost -1/-1 - bufferedlogtest info Message 31
+100.002037 localhost -1/-1 - bufferedlogtest info Message 32
+100.002038 localhost -1/-1 - bufferedlogtest info Message 33
+100.002039 localhost -1/-1 - bufferedlogtest info Message 34
+100.002040 localhost -1/-1 - bufferedlogtest info Message 35
+100.002041 localhost -1/-1 - bufferedlogtest info Message 36
+100.002042 localhost -1/-1 - bufferedlogtest info Message 37
+100.002043 localhost -1/-1 - bufferedlogtest info Message 38
+100.002044 localhost -1/-1 - bufferedlogtest info Message 39
+100.002045 localhost -1/-1 - bufferedlogtest info Message 40
+100.002046 localhost -1/-1 - bufferedlogtest info Message 41
+100.002047 localhost -1/-1 - bufferedlogtest info Message 42
+100.002048 localhost -1/-1 - bufferedlogtest info Message 43
+100.002049 localhost -1/-1 - bufferedlogtest info Message 44
+100.002050 localhost -1/-1 - bufferedlogtest info Message 45
+100.002051 localhost -1/-1 - bufferedlogtest info Message 46
+100.002052 localhost -1/-1 - bufferedlogtest info Message 47
+100.002053 localhost -1/-1 - bufferedlogtest info Message 48
+100.002054 localhost -1/-1 - bufferedlogtest info Message 49
+100.002055 localhost -1/-1 - bufferedlogtest info Message 50
+100.002056 localhost -1/-1 - bufferedlogtest info Message 51
+100.002057 localhost -1/-1 - bufferedlogtest info Message 52
+100.002058 localhost -1/-1 - bufferedlogtest info Message 53
+100.002059 localhost -1/-1 - bufferedlogtest info Message 54
+100.002060 localhost -1/-1 - bufferedlogtest info Message 55
+100.002061 localhost -1/-1 - bufferedlogtest info Message 56
+100.002062 localhost -1/-1 - bufferedlogtest info Message 57
+100.002063 localhost -1/-1 - bufferedlogtest info Message 58
+100.002064 localhost -1/-1 - bufferedlogtest info Message 59
+100.002065 localhost -1/-1 - bufferedlogtest info Message 60
+100.002066 localhost -1/-1 - bufferedlogtest info Message 61
+100.002067 localhost -1/-1 - bufferedlogtest info Message 62
+100.002068 localhost -1/-1 - bufferedlogtest info Message 63
+100.002069 localhost -1/-1 - bufferedlogtest info Message 64
+100.002070 localhost -1/-1 - bufferedlogtest info Message 65
+100.002071 localhost -1/-1 - bufferedlogtest info Message 66
+100.002072 localhost -1/-1 - bufferedlogtest info Message 67
+100.002073 localhost -1/-1 - bufferedlogtest info Message 68
+100.002074 localhost -1/-1 - bufferedlogtest info Message 69
+100.002075 localhost -1/-1 - bufferedlogtest info Message 70
+100.002076 localhost -1/-1 - bufferedlogtest info Message 71
+100.002077 localhost -1/-1 - bufferedlogtest info Message 72
+100.002078 localhost -1/-1 - bufferedlogtest info Message 73
+100.002079 localhost -1/-1 - bufferedlogtest info Message 74
+100.002080 localhost -1/-1 - bufferedlogtest info Message 75
+100.002081 localhost -1/-1 - bufferedlogtest info Message 76
+100.002082 localhost -1/-1 - bufferedlogtest info Message 77
+100.002083 localhost -1/-1 - bufferedlogtest info Message 78
+100.002084 localhost -1/-1 - bufferedlogtest info Message 79
+100.002085 localhost -1/-1 - bufferedlogtest info Message 80
+100.002086 localhost -1/-1 - bufferedlogtest info Message 81
+100.002087 localhost -1/-1 - bufferedlogtest info Message 82
+100.002088 localhost -1/-1 - bufferedlogtest info Message 83
+100.002089 localhost -1/-1 - bufferedlogtest info Message 84
+100.002090 localhost -1/-1 - bufferedlogtest info Message 85
+100.002091 localhost -1/-1 - bufferedlogtest info Message 86
+100.002092 localhost -1/-1 - bufferedlogtest info Message 87
+100.002093 localhost -1/-1 - bufferedlogtest info Message 88
+100.002094 localhost -1/-1 - bufferedlogtest info Message 89
+100.002095 localhost -1/-1 - bufferedlogtest info Message 90
+100.002096 localhost -1/-1 - bufferedlogtest info Message 91
+100.002097 localhost -1/-1 - bufferedlogtest info Message 92
+100.002098 localhost -1/-1 - bufferedlogtest info Message 93
+100.002099 localhost -1/-1 - bufferedlogtest info Message 94
+100.002100 localhost -1/-1 - bufferedlogtest info Message 95
+100.002101 localhost -1/-1 - bufferedlogtest info Message 96
+100.002102 localhost -1/-1 - bufferedlogtest info Message 97
+100.002103 localhost -1/-1 - bufferedlogtest info Message 98
+100.002104 localhost -1/-1 - bufferedlogtest info Message 99
+100.004104 localhost -1/-1 - bufferedlogtest warning Failed to send message to node 4: NOT CONNECTED (Repeated 1799 times since 100.000005)
+100.004104 localhost -1/-1 - bufferedlogtest warning Failed to send message to node 3: NOT CONNECTED (Repeated 199 times since 100.000024)
diff --git a/vespalog/src/test/bufferedlogtest.locationastoken.log b/vespalog/src/test/bufferedlogtest.locationastoken.log
new file mode 100644
index 00000000000..c171ca2800a
--- /dev/null
+++ b/vespalog/src/test/bufferedlogtest.locationastoken.log
@@ -0,0 +1,5 @@
+0.000000 example.yahoo.com -1/-1 - bufferedlogtest info Starting up, using logfile bufferedlogtest.log
+200.000000 example.yahoo.com -1/-1 - bufferedlogtest info Message 0
+200.000001 example.yahoo.com -1/-1 - bufferedlogtest info Message 0
+200.000200 example.yahoo.com -1/-1 - bufferedlogtest info Message 0 (Repeated 99 times since 200.000000)
+200.000200 example.yahoo.com -1/-1 - bufferedlogtest info Message 0 (Repeated 99 times since 200.000001)
diff --git a/vespalog/src/test/bufferedlogtest.logger1.cpp b/vespalog/src/test/bufferedlogtest.logger1.cpp
new file mode 100644
index 00000000000..992de19fea5
--- /dev/null
+++ b/vespalog/src/test/bufferedlogtest.logger1.cpp
@@ -0,0 +1,11 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/log/log.h>
+#include "bufferedlogtest.logger1.h"
+
+LOG_SETUP(".logger1");
+
+void logWithLogger1(const std::string& token, const std::string message)
+{
+ LOGBT(info, token, message.c_str());
+}
diff --git a/vespalog/src/test/bufferedlogtest.logger1.h b/vespalog/src/test/bufferedlogtest.logger1.h
new file mode 100644
index 00000000000..e5336efec82
--- /dev/null
+++ b/vespalog/src/test/bufferedlogtest.logger1.h
@@ -0,0 +1,8 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <string>
+
+void logWithLogger1(const std::string& token, const std::string message);
+
diff --git a/vespalog/src/test/bufferedlogtest.logger2.cpp b/vespalog/src/test/bufferedlogtest.logger2.cpp
new file mode 100644
index 00000000000..688ecd153aa
--- /dev/null
+++ b/vespalog/src/test/bufferedlogtest.logger2.cpp
@@ -0,0 +1,11 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/log/log.h>
+#include "bufferedlogtest.logger2.h"
+
+LOG_SETUP(".logger2");
+
+void logWithLogger2(const std::string& token, const std::string message)
+{
+ LOGBT(info, token, message.c_str());
+}
diff --git a/vespalog/src/test/bufferedlogtest.logger2.h b/vespalog/src/test/bufferedlogtest.logger2.h
new file mode 100644
index 00000000000..0f18607a13d
--- /dev/null
+++ b/vespalog/src/test/bufferedlogtest.logger2.h
@@ -0,0 +1,8 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <string>
+
+void logWithLogger2(const std::string& token, const std::string message);
+
diff --git a/vespalog/src/test/bufferedlogtest.messageastoken.log b/vespalog/src/test/bufferedlogtest.messageastoken.log
new file mode 100644
index 00000000000..e98fcd6caea
--- /dev/null
+++ b/vespalog/src/test/bufferedlogtest.messageastoken.log
@@ -0,0 +1,21 @@
+0.000000 example.yahoo.com -1/-1 - bufferedlogtest info Starting up, using logfile bufferedlogtest.log
+200.000000 example.yahoo.com -1/-1 - bufferedlogtest info Message 0
+200.000002 example.yahoo.com -1/-1 - bufferedlogtest info Message 1
+200.000004 example.yahoo.com -1/-1 - bufferedlogtest info Message 2
+200.000006 example.yahoo.com -1/-1 - bufferedlogtest info Message 3
+200.000008 example.yahoo.com -1/-1 - bufferedlogtest info Message 4
+200.000010 example.yahoo.com -1/-1 - bufferedlogtest info Message 5
+200.000012 example.yahoo.com -1/-1 - bufferedlogtest info Message 6
+200.000014 example.yahoo.com -1/-1 - bufferedlogtest info Message 7
+200.000016 example.yahoo.com -1/-1 - bufferedlogtest info Message 8
+200.000018 example.yahoo.com -1/-1 - bufferedlogtest info Message 9
+200.000200 example.yahoo.com -1/-1 - bufferedlogtest info Message 0 (Repeated 19 times since 200.000000)
+200.000200 example.yahoo.com -1/-1 - bufferedlogtest info Message 1 (Repeated 19 times since 200.000002)
+200.000200 example.yahoo.com -1/-1 - bufferedlogtest info Message 2 (Repeated 19 times since 200.000004)
+200.000200 example.yahoo.com -1/-1 - bufferedlogtest info Message 3 (Repeated 19 times since 200.000006)
+200.000200 example.yahoo.com -1/-1 - bufferedlogtest info Message 4 (Repeated 19 times since 200.000008)
+200.000200 example.yahoo.com -1/-1 - bufferedlogtest info Message 5 (Repeated 19 times since 200.000010)
+200.000200 example.yahoo.com -1/-1 - bufferedlogtest info Message 6 (Repeated 19 times since 200.000012)
+200.000200 example.yahoo.com -1/-1 - bufferedlogtest info Message 7 (Repeated 19 times since 200.000014)
+200.000200 example.yahoo.com -1/-1 - bufferedlogtest info Message 8 (Repeated 19 times since 200.000016)
+200.000200 example.yahoo.com -1/-1 - bufferedlogtest info Message 9 (Repeated 19 times since 200.000018)
diff --git a/vespalog/src/test/bufferedlogtest.nostarve.log b/vespalog/src/test/bufferedlogtest.nostarve.log
new file mode 100644
index 00000000000..aa507e00921
--- /dev/null
+++ b/vespalog/src/test/bufferedlogtest.nostarve.log
@@ -0,0 +1,26 @@
+10.000004 example.yahoo.com -1/-1 - bufferedlogtest info Starting up, using logfile bufferedlogtest.log
+100.000000 example.yahoo.com -1/-1 - bufferedlogtest warning message
+100.000001 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 0x1: NOT CONNECTED
+100.000002 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 0x2: NOT_READY
+100.000003 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 0x3: BAAAH
+100.000004 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 0x4: RPC FAILURE
+100.000005 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 0x5: COSMIC RADIATION
+100.000006 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 0x6: ITS SATURDAY
+100.000007 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 0x7: Yeah, Right!!
+100.000008 example.yahoo.com -1/-1 - bufferedlogtest error Failed to send message to node 0x8: NOT CONNECTED
+100.000009 example.yahoo.com -1/-1 - bufferedlogtest info Failed to send message to node 0x9: NOT CONNECTED
+100.000010 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 0xa: NOT CONNECTED
+100.001001 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 4: NOT CONNECTED
+100.001001 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 0x1: NOT CONNECTED (Repeated 99 times since 100.000001)
+100.001020 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 3: NOT CONNECTED
+100.001020 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 0x2: NOT_READY (Repeated 99 times since 100.000002)
+100.003000 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 0x3: BAAAH (Repeated 99 times since 100.000003)
+100.003000 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 0x4: RPC FAILURE (Repeated 99 times since 100.000004)
+100.003000 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 0x5: COSMIC RADIATION (Repeated 99 times since 100.000005)
+100.003000 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 0x6: ITS SATURDAY (Repeated 99 times since 100.000006)
+100.003000 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 0x7: Yeah, Right!! (Repeated 99 times since 100.000007)
+100.003000 example.yahoo.com -1/-1 - bufferedlogtest error Failed to send message to node 0x8: NOT CONNECTED (Repeated 99 times since 100.000008)
+100.003000 example.yahoo.com -1/-1 - bufferedlogtest info Failed to send message to node 0x9: NOT CONNECTED (Repeated 99 times since 100.000009)
+100.003000 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 0xa: NOT CONNECTED (Repeated 99 times since 100.000010)
+100.003000 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 4: NOT CONNECTED (Repeated 899 times since 100.001001)
+100.003000 example.yahoo.com -1/-1 - bufferedlogtest warning Failed to send message to node 3: NOT CONNECTED (Repeated 99 times since 100.001020)
diff --git a/vespalog/src/test/bufferedlogtest.tokenacrossloggers.log b/vespalog/src/test/bufferedlogtest.tokenacrossloggers.log
new file mode 100644
index 00000000000..2c175701509
--- /dev/null
+++ b/vespalog/src/test/bufferedlogtest.tokenacrossloggers.log
@@ -0,0 +1,5 @@
+0.000000 example.yahoo.com -1/-1 - bufferedlogtest info Starting up, using logfile bufferedlogtest.log
+200.000000 example.yahoo.com -1/-1 - bufferedlogtest.logger1 info Message 0
+200.000001 example.yahoo.com -1/-1 - bufferedlogtest.logger2 info Message 0
+200.000200 example.yahoo.com -1/-1 - bufferedlogtest.logger1 info Message 0 (Repeated 99 times since 200.000000)
+200.000200 example.yahoo.com -1/-1 - bufferedlogtest.logger2 info Message 0 (Repeated 99 times since 200.000001)
diff --git a/vespalog/src/test/bufferedlogtest.trimcache.log b/vespalog/src/test/bufferedlogtest.trimcache.log
new file mode 100644
index 00000000000..d99e7357808
--- /dev/null
+++ b/vespalog/src/test/bufferedlogtest.trimcache.log
@@ -0,0 +1,4 @@
+0.000000 example.yahoo.com -1/-1 - bufferedlogtest info Starting up, using logfile bufferedlogtest.log
+200.000000 example.yahoo.com -1/-1 - bufferedlogtest info Message 0
+100000.000000 example.yahoo.com -1/-1 - bufferedlogtest info This should cause buffered log flush
+100000.000000 example.yahoo.com -1/-1 - bufferedlogtest info Message 0 (Repeated 99 times since 200.000000)
diff --git a/vespalog/src/test/bufferedlogtest_test.sh b/vespalog/src/test/bufferedlogtest_test.sh
new file mode 100755
index 00000000000..0ff927b946d
--- /dev/null
+++ b/vespalog/src/test/bufferedlogtest_test.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+rm -f bufferedlogtest.log
+VESPA_LOG_TARGET="file:bufferedlogtest.log" ./vespalog_bufferedlogtest_app bufferedlogtest.log
diff --git a/vespalog/src/test/java/com/yahoo/log/FileLogTargetTest.java b/vespalog/src/test/java/com/yahoo/log/FileLogTargetTest.java
new file mode 100644
index 00000000000..deecebe0b81
--- /dev/null
+++ b/vespalog/src/test/java/com/yahoo/log/FileLogTargetTest.java
@@ -0,0 +1,48 @@
+// 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 org.junit.Test;
+import org.junit.After;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class FileLogTargetTest {
+ @Test(expected = RuntimeException.class)
+ public void requireThatExceptionIsThrowIfFileNotFound() throws IOException {
+ File file = new File("mydir1");
+ file.delete();
+ assertTrue(file.mkdir());
+ new FileLogTarget(file).open();
+ }
+
+ @After
+ public void cleanup() { new File("mydir1").delete(); }
+
+ @Test
+ public void requireThatFileCanOpened() throws IOException {
+ FileLogTarget logTarget = new FileLogTarget(File.createTempFile("logfile", ".log"));
+ assertNotNull(logTarget.open());
+ }
+
+ @Test
+ public void requireThatFileIsReopened() throws IOException {
+ FileLogTarget logTarget = new FileLogTarget(File.createTempFile("logfile", ".log"));
+ OutputStream out1 = logTarget.open();
+ assertNotNull(out1);
+ OutputStream out2 = logTarget.open();
+ assertNotNull(out2);
+ assertThat(out1, is(not(out2)));
+ }
+}
diff --git a/vespalog/src/test/java/com/yahoo/log/LogLevelTestCase.java b/vespalog/src/test/java/com/yahoo/log/LogLevelTestCase.java
new file mode 100644
index 00000000000..cef2acd99da
--- /dev/null
+++ b/vespalog/src/test/java/com/yahoo/log/LogLevelTestCase.java
@@ -0,0 +1,78 @@
+// 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.Set;
+import java.util.HashSet;
+import java.util.logging.Level;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Testcases for LogLevel
+ *
+ * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a>
+ */
+public class LogLevelTestCase {
+
+ /**
+ * Ensure that all the log levels we need are present
+ * and that they are distinct.
+ */
+ @Test
+ public void testLogLevels () {
+ assertNotNull(LogLevel.UNKNOWN);
+ assertNotNull(LogLevel.FATAL);
+ assertNotNull(LogLevel.ERROR);
+ assertNotNull(LogLevel.WARNING);
+ assertNotNull(LogLevel.INFO);
+ assertNotNull(LogLevel.CONFIG);
+ assertNotNull(LogLevel.EVENT);
+ assertNotNull(LogLevel.DEBUG);
+ assertNotNull(LogLevel.SPAM);
+
+ // use a set to verify that all are distinct
+ Set<Level> seen = new HashSet<Level>();
+ assertTrue(seen.add(LogLevel.UNKNOWN));
+ assertTrue(seen.add(LogLevel.FATAL));
+ assertTrue(seen.add(LogLevel.ERROR));
+ assertTrue(seen.add(LogLevel.WARNING));
+ assertTrue(seen.add(LogLevel.INFO));
+ assertTrue(seen.add(LogLevel.CONFIG));
+ assertTrue(seen.add(LogLevel.EVENT));
+ assertTrue(seen.add(LogLevel.DEBUG));
+ assertTrue(seen.add(LogLevel.SPAM));
+
+ // verify that set would trigger error (not necessary)
+ assertTrue(! seen.add(LogLevel.SPAM));
+ }
+
+ /**
+ * Test that given the log level name we are able to
+ * map it to the correct static instance.
+ */
+ @Test
+ public void testNameToLevelMapping () {
+ assertEquals(LogLevel.UNKNOWN, LogLevel.parse("unknown"));
+ assertEquals(LogLevel.FATAL, LogLevel.parse("fatal"));
+ assertEquals(LogLevel.ERROR, LogLevel.parse("error"));
+ assertEquals(LogLevel.WARNING, LogLevel.parse("warning"));
+ assertEquals(LogLevel.INFO, LogLevel.parse("info"));
+ assertEquals(LogLevel.CONFIG, LogLevel.parse("config"));
+ assertEquals(LogLevel.EVENT, LogLevel.parse("event"));
+ assertEquals(LogLevel.DEBUG, LogLevel.parse("debug"));
+ assertEquals(LogLevel.SPAM, LogLevel.parse("spam"));
+ }
+
+ @Test
+ public void testJavaToLevelMapping () {
+ assertTrue(LogLevel.ERROR == LogLevel.getVespaLogLevel(Level.SEVERE));
+ assertTrue(LogLevel.WARNING == LogLevel.getVespaLogLevel(Level.WARNING));
+ assertTrue(LogLevel.INFO == LogLevel.getVespaLogLevel(Level.INFO));
+ assertTrue(LogLevel.CONFIG == LogLevel.getVespaLogLevel(Level.CONFIG));
+ assertTrue(LogLevel.DEBUG == LogLevel.getVespaLogLevel(Level.FINE));
+ assertTrue(LogLevel.DEBUG == LogLevel.getVespaLogLevel(Level.FINER));
+ assertTrue(LogLevel.SPAM == LogLevel.getVespaLogLevel(Level.FINEST));
+ }
+}
diff --git a/vespalog/src/test/java/com/yahoo/log/LogSetupTestCase.java b/vespalog/src/test/java/com/yahoo/log/LogSetupTestCase.java
new file mode 100644
index 00000000000..9b5eb0ed6ce
--- /dev/null
+++ b/vespalog/src/test/java/com/yahoo/log/LogSetupTestCase.java
@@ -0,0 +1,153 @@
+// 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.util.logging.LogRecord;
+import java.util.logging.Logger;
+import java.util.logging.Level;
+import java.io.File;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.rules.TemporaryFolder;
+
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.is;
+
+/**
+ * Make sure we can install the logging stuff properly.
+ *
+ * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a>
+ */
+public class LogSetupTestCase {
+ // For testing zookeeper log records
+ protected static LogRecord zookeeperLogRecord;
+ protected static LogRecord zookeeperLogRecordError;
+ protected static LogRecord curatorLogRecord;
+ protected static LogRecord curatorLogRecordError;
+ protected static String zookeeperLogRecordString;
+ protected static LogRecord notzookeeperLogRecord;
+
+ protected static String hostname;
+ protected static String pid;
+
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+
+ @Before
+ public void setUp() {
+ System.setProperty("config.id", "my-test-config-id");
+ System.setProperty("vespa.log.target", "file:test3");
+
+ zookeeperLogRecord = new LogRecord(Level.WARNING, "zookeeper log record");
+ zookeeperLogRecord.setLoggerName("org.apache.zookeeper.server.NIOServerCnxn");
+ zookeeperLogRecord.setMillis(1107011348029L);
+
+ curatorLogRecord = new LogRecord(Level.WARNING, "curator log record");
+ curatorLogRecord.setLoggerName("com.netflix.curator.utils.DefaultTracerDriver");
+ curatorLogRecord.setMillis(1107011348029L);
+
+ hostname = Util.getHostName();
+ pid = Util.getPID();
+
+ zookeeperLogRecordString = "1107011348.029\t"
+ + hostname
+ + "\t"
+ + pid
+ + "/" + zookeeperLogRecord.getThreadID() + "\t-\t.org.apache.zookeeper.server.NIOServerCnxn"
+ + "\twarning\tzookeeper log record";
+
+ zookeeperLogRecordError = new LogRecord(Level.SEVERE, "zookeeper error");
+ zookeeperLogRecordError.setLoggerName("org.apache.zookeeper.server.NIOServerCnxn");
+ zookeeperLogRecordError.setMillis(1107011348029L);
+
+ curatorLogRecordError = new LogRecord(Level.SEVERE, "curator log record");
+ curatorLogRecordError.setLoggerName("com.netflix.curator.utils.DefaultTracerDriver");
+ curatorLogRecordError.setMillis(1107011348029L);
+
+ notzookeeperLogRecord = new LogRecord(Level.WARNING, "not zookeeper log record");
+ notzookeeperLogRecord.setLoggerName("org.apache.foo.Bar");
+ notzookeeperLogRecord.setMillis(1107011348029L);
+ }
+
+ @Test
+ public void testSetup() throws IOException {
+ try {
+ final File zookeeperLogFile = folder.newFile("zookeeper.log");
+ System.setProperty("zookeeperlogfile", zookeeperLogFile.getAbsolutePath());
+ LogSetup.initVespaLogging("TST");
+ Logger.getLogger("").log(VespaLogHandlerTestCase.record2);
+ Logger.getLogger("").log(VespaLogHandlerTestCase.record1);
+ Logger.getLogger("").log(VespaLogHandlerTestCase.record2);
+ Logger.getLogger("").log(zookeeperLogRecord); // Should not be written to log, due to use of ZooKeeperFilter
+ Logger.getLogger("").log(curatorLogRecord); // Should not be written to log, due to use of ZooKeeperFilter
+ Logger.getLogger("").log(notzookeeperLogRecord);
+ String[] lines = VespaLogHandlerTestCase.readFile("test3");
+ assertEquals(2, lines.length);
+ assertEquals(VespaLogHandlerTestCase.record1String, lines[0]);
+ } finally {
+ LogSetup.clearHandlers();
+ LogSetup.getLogHandler().closeFileTarget();
+ assertTrue(new File("test3").delete());
+ }
+ }
+
+ // Note: This test will generate warnings about initVespaLogging being called twice, which can be ignored
+ @Test
+ public void testLogLevelSetting() {
+
+ // levels are ordered like:
+ // C++ has fatal, error, warning, config, info, event, debug, spam
+ // Java has fatal, error, warning, event, config, info, debug, spam
+
+ // logctl file values: fatal, error, warning, config, info, event, debug, spam
+
+ System.setProperty("vespa.log.target", "fd:2");
+ setupAndCheckLevels(null, " ON ON ON ON ON ON OFF OFF", LogLevel.INFO, LogLevel.DEBUG);
+ setupAndCheckLevels("", " ON ON ON ON ON ON OFF OFF", LogLevel.INFO, LogLevel.DEBUG);
+ setupAndCheckLevels("all", " ON ON ON ON ON ON ON ON", LogLevel.SPAM, null);
+ setupAndCheckLevels("all -spam", " ON ON ON ON ON ON ON OFF", LogLevel.DEBUG, LogLevel.SPAM);
+ setupAndCheckLevels("all -debug", " ON ON ON ON ON ON OFF ON", LogLevel.INFO, null);
+ setupAndCheckLevels("all -debug -spam", " ON ON ON ON ON ON OFF OFF", LogLevel.INFO, LogLevel.DEBUG);
+ // INFO is higher in level value than CONFIG, so one should rather use -info -config
+ setupAndCheckLevels("all -debug -spam -info", " ON ON ON ON OFF ON OFF OFF", LogLevel.WARNING, LogLevel.DEBUG);
+ setupAndCheckLevels("all -debug -spam -info -config", " ON ON ON OFF OFF ON OFF OFF", LogLevel.WARNING, LogLevel.INFO);
+ setupAndCheckLevels("all debug", " ON ON ON ON ON ON ON ON", LogLevel.SPAM, null);
+ setupAndCheckLevels("debug", " OFF OFF OFF OFF OFF OFF ON OFF", null, null);
+ setupAndCheckLevels("debug error", " OFF ON OFF OFF OFF OFF ON OFF", LogLevel.ERROR, LogLevel.SPAM);
+ }
+
+ @Test
+ public void testZooKeeperFilter() throws IOException {
+ final File file = folder.newFile("zookeeper.log");
+ LogSetup.ZooKeeperFilter filter = new LogSetup.ZooKeeperFilter(file.getAbsolutePath());
+ assertThat(filter.isLoggable(zookeeperLogRecord), is(false));
+ //assertThat(filter.isLoggable(zookeeperLogRecordError), is(true));
+ assertThat(filter.isLoggable(notzookeeperLogRecord), is(true));
+ String[] lines = VespaLogHandlerTestCase.readFile(file.getAbsolutePath());
+ assertThat(lines.length, is(1));
+ assertEquals(zookeeperLogRecordString, lines[0]);
+ }
+
+ private void setupAndCheckLevels(String levelString, String expectedOnOffString, Level shouldLog, Level shouldNotLog) {
+ try {
+ if (levelString != null) {
+ System.setProperty("vespa.log.level", levelString);
+ }
+ LogSetup.initVespaLogging("TST");
+ Logger.getLogger("TEST").log(LogLevel.DEBUG, "DEBUG");
+ LevelController levelController = LogSetup.getLogHandler().getLevelControl("TST");
+ assertNotNull(levelController);
+ assertThat(levelController.getOnOffString(), is(expectedOnOffString));
+ if (shouldLog != null) {
+ assertTrue(levelController.shouldLog(shouldLog));
+ }
+ if (shouldNotLog != null) {
+ assertFalse(levelController.shouldLog(shouldNotLog));
+ }
+ } finally {
+ LogSetup.clearHandlers();
+ }
+ }
+}
diff --git a/vespalog/src/test/java/com/yahoo/log/LogUtilTest.java b/vespalog/src/test/java/com/yahoo/log/LogUtilTest.java
new file mode 100644
index 00000000000..2a0b1e1caaa
--- /dev/null
+++ b/vespalog/src/test/java/com/yahoo/log/LogUtilTest.java
@@ -0,0 +1,22 @@
+// 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 org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class LogUtilTest {
+ @Test
+ public void testEmpty() {
+ assertTrue(LogUtil.empty(null));
+ assertTrue(LogUtil.empty(""));
+ assertFalse(LogUtil.empty("f"));
+ assertFalse(LogUtil.empty("fo"));
+ assertFalse(LogUtil.empty("foo"));
+ }
+}
diff --git a/vespalog/src/test/java/com/yahoo/log/RejectFilterTest.java b/vespalog/src/test/java/com/yahoo/log/RejectFilterTest.java
new file mode 100644
index 00000000000..a562aa1cb01
--- /dev/null
+++ b/vespalog/src/test/java/com/yahoo/log/RejectFilterTest.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;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class RejectFilterTest {
+ @Test
+ public void testBasicPatternMatching() {
+ RejectFilter rejectFilter = new RejectFilter();
+ assertFalse(rejectFilter.shouldReject("This is a test"));
+ rejectFilter.addRejectedMessage("This is a test");
+ assertTrue(rejectFilter.shouldReject("This is a test"));
+ rejectFilter.addRejectedMessage("This is not a test");
+ assertTrue(rejectFilter.shouldReject("This is a test"));
+ assertTrue(rejectFilter.shouldReject("This is not a test"));
+ assertFalse(rejectFilter.shouldReject("This is not not a test"));
+ assertFalse(rejectFilter.shouldReject(null));
+ }
+
+ @Test
+ public void testDefaultRejectPattern() {
+ RejectFilter filter = RejectFilter.createDefaultRejectFilter();
+ assertTrue(filter.shouldReject("E 23-235018.067240 14650 23/10/2012 23:50:18 yjava_preload.so: [preload.c:350] Using FILTER_NONE: This must be paranoid approved, and since you are using FILTER_NONE you must live with this error."));
+ }
+}
diff --git a/vespalog/src/test/java/com/yahoo/log/TimeComparatorTestCase.java b/vespalog/src/test/java/com/yahoo/log/TimeComparatorTestCase.java
new file mode 100644
index 00000000000..28d34d38a86
--- /dev/null
+++ b/vespalog/src/test/java/com/yahoo/log/TimeComparatorTestCase.java
@@ -0,0 +1,71 @@
+// 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.Iterator;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Unit test for the LogMessage TimeComparator
+ *
+ * @author vlarsen
+ *
+ */
+public class TimeComparatorTestCase {
+ private String msgstr1 = "1098709000\t"
+ + "nalle.puh.com\t"
+ + "23234\t"
+ + "serviceName\t"
+ + "tst\t"
+ + "info\t"
+ + "this is a test\n";
+ private LogMessage msg1;
+
+ private String msgstr2 = "1098709021\t"
+ + "nalle.puh.com\t"
+ + "23234\t"
+ + "serviceName\t"
+ + "tst\t"
+ + "info\t"
+ + "this is a test\n";
+ private LogMessage msg2;
+
+ @Test
+ public void testAscendingOrder() {
+ try {
+ msg1 = LogMessage.parseNativeFormat(msgstr1.trim());
+ msg2 = LogMessage.parseNativeFormat(msgstr2.trim());
+ } catch (InvalidLogFormatException e) {
+ fail();
+ }
+ Set<LogMessage> msglist = new TreeSet<LogMessage>(new LogMessageTimeComparator());
+ msglist.add(msg1);
+ msglist.add(msg2);
+
+ Iterator<LogMessage> it = msglist.iterator();
+ assertEquals(msg1, it.next());
+ assertEquals(msg2, it.next());
+ }
+
+ @Test
+ public void testDescendingOrder() {
+ try {
+ msg1 = LogMessage.parseNativeFormat(msgstr1.trim());
+ msg2 = LogMessage.parseNativeFormat(msgstr2.trim());
+ } catch (InvalidLogFormatException e) {
+ fail();
+ }
+ Set<LogMessage> msglist = new TreeSet<LogMessage>(new LogMessageTimeComparator(false));
+ msglist.add(msg1);
+ msglist.add(msg2);
+
+ // note, last gets fetched first
+ Iterator<LogMessage> it = msglist.iterator();
+ assertEquals(msg2, it.next());
+ assertEquals(msg1, it.next());
+ }
+}
diff --git a/vespalog/src/test/java/com/yahoo/log/UncloseableOutputStreamTestCase.java b/vespalog/src/test/java/com/yahoo/log/UncloseableOutputStreamTestCase.java
new file mode 100644
index 00000000000..7fa98791b86
--- /dev/null
+++ b/vespalog/src/test/java/com/yahoo/log/UncloseableOutputStreamTestCase.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 org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class UncloseableOutputStreamTestCase {
+
+ @Test
+ public void requireThatWriteIsProxied() throws IOException {
+ OutputStream out = Mockito.mock(OutputStream.class);
+ new UncloseableOutputStream(out).write(69);
+ Mockito.verify(out).write(69);
+ }
+
+ @Test
+ public void requireThatCloseIsIgnored() {
+ OutputStream out = Mockito.mock(OutputStream.class);
+ new UncloseableOutputStream(out).close();
+ Mockito.verifyNoMoreInteractions(out);
+ }
+}
diff --git a/vespalog/src/test/java/com/yahoo/log/UtilTestCase.java b/vespalog/src/test/java/com/yahoo/log/UtilTestCase.java
new file mode 100644
index 00000000000..3f3da8436bd
--- /dev/null
+++ b/vespalog/src/test/java/com/yahoo/log/UtilTestCase.java
@@ -0,0 +1,22 @@
+// 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 org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a>
+ */
+public class UtilTestCase {
+
+ /**
+ * Just make sure the static getHostName() method returns something
+ * that looks half sensible.
+ */
+ @Test
+ public void testSimple () {
+ String name = Util.getHostName();
+ assertNotNull(name);
+ }
+}
diff --git a/vespalog/src/test/java/com/yahoo/log/VespaFormatterTestCase.java b/vespalog/src/test/java/com/yahoo/log/VespaFormatterTestCase.java
new file mode 100644
index 00000000000..300514a4f3f
--- /dev/null
+++ b/vespalog/src/test/java/com/yahoo/log/VespaFormatterTestCase.java
@@ -0,0 +1,264 @@
+// 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.LogRecord;
+
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.Ignore;
+
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.is;
+
+/**
+ * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a>
+ */
+public class VespaFormatterTestCase {
+
+ private String hostname;
+ private String pid;
+ private String serviceName = "serviceName";
+ private String app = "tst";
+ private LogRecord testRecord1;
+ private LogRecord testRecord2;
+ private String expected1;
+ private String expected2;
+ private String expected3;
+ private String expected4;
+
+
+ @Before
+ public void setUp () {
+ hostname = Util.getHostName();
+ pid = Util.getPID();
+
+ testRecord1 = new LogRecord(Level.INFO, "this is a test");
+ testRecord1.setMillis(1098709021843L);
+ testRecord1.setThreadID(123);
+
+ expected1 = "1098709021.843\t"
+ + hostname + "\t"
+ + pid
+ + "/123" + "\t"
+ + "serviceName\t"
+ + "tst\t"
+ + "info\t"
+ + "this is a test\n";
+
+ expected2 = "1098709021.843\t"
+ + hostname + "\t"
+ + pid
+ + "/123" + "\t"
+ + "serviceName\t"
+ + "-\t"
+ + "info\t"
+ + "this is a test\n";
+
+
+ testRecord2 = new LogRecord(Level.INFO, "this is a test");
+ testRecord2.setMillis(1098709021843L);
+ testRecord2.setThreadID(123);
+ testRecord2.setLoggerName("org.foo");
+
+ expected3 = "1098709021.843\t"
+ + hostname + "\t"
+ + pid
+ + "/123" + "\t"
+ + "serviceName\t"
+ + ".org.foo\t"
+ + "info\t"
+ + "this is a test\n";
+
+ expected4 = "1098709021.843\t"
+ + hostname + "\t"
+ + pid
+ + "/123" + "\t"
+ + "serviceName\t"
+ + "tst.org.foo\t"
+ + "info\t"
+ + "this is a test\n";
+ }
+
+ /**
+ * Just make sure that the messages are parsed as expected.
+ */
+ @Test
+ public void testFormatting () {
+ VespaFormatter formatter = new VespaFormatter(serviceName, app);
+ assertEquals(expected1, formatter.format(testRecord1));
+ assertEquals(expected4, formatter.format(testRecord2));
+
+ VespaFormatter fmt2 = new VespaFormatter(serviceName, null);
+ assertEquals(expected2, fmt2.format(testRecord1));
+ assertEquals(expected3, fmt2.format(testRecord2));
+ }
+
+
+ /**
+ * test that {0} etc is replaced properly
+ */
+ @Test
+ public void testTextFormatting () {
+ VespaFormatter formatter = new VespaFormatter(serviceName, app);
+
+ LogRecord testRecord = new LogRecord(Level.INFO, "this {1} is {0} test");
+ testRecord.setMillis(1098709021843L);
+ testRecord.setThreadID(123);
+ testRecord.setLoggerName("org.foo");
+ Object[] params = { "a small", "message" };
+ testRecord.setParameters(params);
+
+ String expected = "1098709021.843\t"
+ + hostname + "\t"
+ + pid
+ + "/123" + "\t"
+ + "serviceName\t"
+ + app
+ + ".org.foo\t"
+ + "info\t"
+ + "this message is a small test\n";
+
+ assertEquals(expected, formatter.format(testRecord));
+ }
+
+ /**
+ * Make sure that the LogMessage parser used in the log server is
+ * able to parse these messages too.
+ */
+ @Test
+ public void testParsing () {
+ VespaFormatter formatter = new VespaFormatter(serviceName, app);
+ LogMessage m;
+ try {
+ m = LogMessage.parseNativeFormat(expected1.trim());
+ assertEquals("this is a test", m.getPayload());
+
+ m = LogMessage.parseNativeFormat(formatter.format(testRecord1).trim());
+ assertEquals("this is a test", m.getPayload());
+
+ assertTrue(true);
+ }
+ catch (InvalidLogFormatException e) {
+ System.out.println(e.toString());
+ fail();
+ }
+ }
+
+ /**
+ * This method was used for testing the speed of the formatter.
+ * We usually do not want to run this in normal testing, it was
+ * just added when trying to analyze the abysmal performance of
+ * double formatting.
+ */
+ @Test
+ @Ignore
+ public void testSpeed () {
+ VespaFormatter formatter = new VespaFormatter(serviceName, app);
+ int reps = 1000000;
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < reps; i++) {
+ formatter.format(testRecord1);
+ }
+ long diff = System.currentTimeMillis() - start;
+ double dd = (reps / (double) diff);
+ System.out.println("Took " + diff + " milliseconds, per ms=" + dd);
+ }
+
+
+ /** test backslash, newline and tab escaping. one instance of each */
+ @Test
+ public void testEscaping () {
+ String a = "This line contains a \\ backslash and \n newline and \t tab.";
+ String b = "This line contains a \\\\ backslash and \\n newline and \\t tab.";
+ assertEquals(b, VespaFormat.escape(a));
+ assertEquals(a, VespaFormatter.unEscape(b));
+ }
+
+ /** test multiple instances of backslash */
+ @Test
+ public void testMultipleEscaping () {
+ String a = "This line contains a \\ backslash and \\ more backslash.";
+ String b = "This line contains a \\\\ backslash and \\\\ more backslash.";
+ assertEquals(b, VespaFormat.escape(a));
+ assertEquals(a, VespaFormatter.unEscape(b));
+ }
+
+ @Test
+ public void testThrowable () {
+ // VespaFormatter formatter = new VespaFormatter(serviceName, app);
+ LogRecord r = new LogRecord(Level.INFO, "foo bar");
+ r.setThrown(new IllegalStateException("this was fun"));
+ }
+
+ /**
+ * Got a NullPointerException earlier when trying to format
+ * a null message. Added unit test to make sure this does
+ * not happen again. Makes sure that NullPointerException
+ * is not thrown and that the formatted string contains the
+ * expected message for saying that there was no log message.
+ */
+ @Test
+ public void testNullMessage () {
+ VespaFormatter formatter = new VespaFormatter(serviceName, app);
+ LogRecord r = new LogRecord(LogLevel.ERROR, null);
+ assertNotNull(r);
+ try {
+ String s = formatter.format(r);
+ assertNotNull(s);
+ assertTrue(s.indexOf("(empty)") > 0);
+ }
+ catch (NullPointerException e) {
+ fail("unable to handle null message!");
+ }
+ }
+
+ @Test
+ public void testLowLogLevelExceptionFormatting () {
+ VespaFormatter formatter = new VespaFormatter(serviceName, app);
+ LogRecord r = new LogRecord(LogLevel.INFO, "meldingen her");
+ r.setThrown(new IllegalStateException());
+ String result = formatter.format(r);
+ assertTrue(formatter.format(r).contains("meldingen her"));
+ }
+
+ /**
+ * This test doesn't really do that much. It is just here
+ * to ensure this doesn't crash. XXX TODO: make this test
+ * actually test something more than just the non-generation
+ * of runtime errors. -bb
+ */
+ @Test
+ public void testExceptionFormatting () {
+ StringBuilder sb = new StringBuilder(128);
+ Exception e = new Exception("testing", new Exception("nested"));
+ VespaFormat.formatException(e, sb);
+ }
+
+
+ @Test
+ public void testGeneralFormat() {
+ String[] expected = new String[] {
+ "54.321",
+ "hostname",
+ "26019/UnitTest-Thread-37",
+ "UnitTestRunner",
+ "com.UnitTest",
+ "INFO",
+ "Just check it looks OK\\nmsg=\"boom\"\\nname=\"java.lang.Throwable\"\\nstack=\"\\n" + this.getClass().getName() // Clover rewrites class names, get the current one to avoid test failure
+ };
+ String formatted = VespaFormat.format("INFO",
+ "UnitTest", "com", 54321L,
+ "UnitTest-Thread-37", "UnitTestRunner",
+ "Just check it looks OK",
+ new Throwable("boom"));
+ String[] split = formatted.split("\t");
+ assertEquals(expected[0], split[0]);
+ assertEquals(expected[2].split("/")[1], split[2].split("/")[1]);
+ assertEquals(expected[3], split[3]);
+ assertEquals(expected[4], split[4]);
+ assertEquals(expected[5], split[5]);
+ assertEquals(expected[6], split[6].substring(0, expected[6].length()));
+ assertEquals(expected.length, split.length);
+ }
+}
diff --git a/vespalog/src/test/java/com/yahoo/log/VespaLevelControllerRepoTest.java b/vespalog/src/test/java/com/yahoo/log/VespaLevelControllerRepoTest.java
new file mode 100644
index 00000000000..948931450f4
--- /dev/null
+++ b/vespalog/src/test/java/com/yahoo/log/VespaLevelControllerRepoTest.java
@@ -0,0 +1,107 @@
+// 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 org.junit.Test;
+import org.junit.Ignore;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class VespaLevelControllerRepoTest {
+
+ static int findControlString(RandomAccessFile f, String s) {
+ try {
+ String toFind = "\n." + s + ": ";
+ byte[] contents = new byte[(int) f.length()];
+ f.seek(0);
+ f.read(contents);
+ f.seek(0);
+ String c_as_s = new String(contents, "US-ASCII");
+ int off = c_as_s.indexOf(toFind);
+ if (off < 0) {
+ System.err.println("did not find control line for level '"+s+"' in logcontrol file:");
+ System.err.println(c_as_s);
+ throw new RuntimeException("bad state in logcontrol file");
+ }
+ off += toFind.length();
+ while ((off % 4) != 0) ++off;
+ return off;
+ } catch (IOException e) {
+ throw new RuntimeException("problem reading logcontrol file", e);
+ }
+ }
+
+ @Test
+ public void testLogCtl () throws InterruptedException, IOException {
+ File lcf = new File("./my-testilol-config-id.logcontrol");
+ try {
+ lcf.delete();
+ Logger.getLogger("com.yahoo.log.test").setLevel(null);
+ assertEquals(null,
+ Logger.getLogger("com.yahoo.log.test").getLevel());
+
+ LevelControllerRepo repo = new VespaLevelControllerRepo(lcf.getName(), "all -debug -spam", "TST");
+
+ long timeout = System.currentTimeMillis() + 60000;
+ while (System.currentTimeMillis() < timeout) {
+ if (Level.CONFIG == Logger.getLogger("com.yahoo.log.test").getLevel())
+ break;
+ Thread.sleep(100);
+ }
+
+ RandomAccessFile lcfile = new RandomAccessFile(lcf, "rw");
+
+ lcfile.seek(VespaLevelControllerRepo.controlFileHeaderLength);
+ assertEquals(lcfile.readByte(), 'd');
+ lcfile.seek(VespaLevelControllerRepo.controlFileHeaderLength + 7);
+ assertEquals(lcfile.readByte(), ':');
+ assertEquals(0, (VespaLevelControllerRepo.controlFileHeaderLength+9) % 4);
+ lcfile.seek(VespaLevelControllerRepo.controlFileHeaderLength + 9);
+ assertEquals(0x20204f4e, lcfile.readInt());
+
+ int off = findControlString(lcfile, "com.yahoo.log.test");
+ lcfile.seek(off);
+ assertEquals(0x20204f4e, lcfile.readInt());
+ assertEquals(0x20204f4e, lcfile.readInt());
+ assertEquals(0x20204f4e, lcfile.readInt());
+ assertEquals(0x20204f4e, lcfile.readInt());
+ assertEquals(0x20204f4e, lcfile.readInt());
+ assertEquals(0x20204f4e, lcfile.readInt());
+
+ assertEquals(0x204f4646, lcfile.readInt());
+ assertEquals(0x204f4646, lcfile.readInt());
+ lcfile.seek(off);
+ lcfile.writeInt(0x204f4646);
+ lcfile.writeInt(0x204f4646);
+ lcfile.writeInt(0x204f4646);
+ lcfile.writeInt(0x204f4646);
+ lcfile.writeInt(0x204f4646);
+ lcfile.writeInt(0x204f4646);
+
+ lcfile.writeInt(0x20204f4e);
+ lcfile.writeInt(0x20204f4e);
+ lcfile.close();
+ assertFalse(repo.getLevelController("com.yahoo.log.test").shouldLog(LogLevel.FATAL));
+ assertFalse(repo.getLevelController("com.yahoo.log.test").shouldLog(LogLevel.ERROR));
+ assertFalse(repo.getLevelController("com.yahoo.log.test").shouldLog(LogLevel.WARNING));
+ assertFalse(repo.getLevelController("com.yahoo.log.test").shouldLog(LogLevel.INFO));
+ assertFalse(repo.getLevelController("com.yahoo.log.test").shouldLog(LogLevel.EVENT));
+ assertFalse(repo.getLevelController("com.yahoo.log.test").shouldLog(LogLevel.CONFIG));
+ assertTrue(repo.getLevelController("com.yahoo.log.test").shouldLog(LogLevel.DEBUG));
+ assertTrue(repo.getLevelController("com.yahoo.log.test").shouldLog(LogLevel.SPAM));
+ } finally {
+ lcf.delete();
+ }
+ }
+}
diff --git a/vespalog/src/test/java/com/yahoo/log/VespaLogHandlerTestCase.java b/vespalog/src/test/java/com/yahoo/log/VespaLogHandlerTestCase.java
new file mode 100644
index 00000000000..c231efed2dd
--- /dev/null
+++ b/vespalog/src/test/java/com/yahoo/log/VespaLogHandlerTestCase.java
@@ -0,0 +1,440 @@
+// 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 org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.*;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a>
+ */
+public class VespaLogHandlerTestCase {
+ protected static String hostname;
+ protected static String pid;
+
+ protected static LogRecord record1;
+ protected static String record1String;
+
+ protected static LogRecord record2;
+ protected static String record2String;
+
+ protected static LogRecord record3;
+ protected static String record3String;
+
+ protected static LogRecord record4;
+ protected static String record4String;
+
+ static {
+ hostname = Util.getHostName();
+ pid = Util.getPID();
+
+ record1 = new LogRecord(Level.INFO, "This is a test");
+ record1.setMillis(1100011348029L);
+ record1String = "1100011348.029\t"
+ + hostname
+ + "\t"
+ + pid
+ + "/"
+ + record1.getThreadID()
+ + "\tmy-test-config-id\tTST\tinfo\tThis is a test";
+
+ record2 = new LogRecord(Level.FINE, "This is a test too");
+ record2.setMillis(1100021348029L);
+ record2.setLoggerName("com.yahoo.log.test");
+ record2String = "1100021348.029\t"
+ + hostname
+ + "\t"
+ + pid
+ + "/" + record2.getThreadID() + "\tmy-test-config-id\tTST.com.yahoo.log.test\tdebug\tThis is a test too";
+
+ record3 = new LogRecord(Level.WARNING, "another test");
+ record3.setLoggerName("com.yahoo.log.test");
+ record3.setMillis(1107011348029L);
+ record3String = "1107011348.029\t"
+ + hostname
+ + "\t"
+ + pid
+ + "/" + record3.getThreadID() + "\tmy-test-config-id\tTST.com.yahoo.log.test"
+ + "\twarning\tanother test";
+
+ record4 = new LogRecord(Level.WARNING, "unicode \u00E6\u00F8\u00E5 test \u7881 unicode");
+ record4.setLoggerName("com.yahoo.log.test");
+ record4.setMillis(1107011348029L);
+ record4String = "1107011348.029\t"
+ + hostname
+ + "\t"
+ + pid
+ + "/" + record4.getThreadID() + "\tmy-test-config-id\tTST.com.yahoo.log.test"
+ + "\twarning\tunicode \u00E6\u00F8\u00E5 test \u7881 unicode";
+ }
+
+ @Before
+ public void setUp() {
+ // System.setProperty("vespa.log.level", "all");
+ // System.setProperty("vespa.log.control.dir", ".");
+ // System.setProperty("config.id", "my-test-config-id");
+ }
+
+ @After
+ public void tearDown() {
+ new File("./my-test-config-id.logcontrol").delete();
+ }
+
+ /**
+ * Perform simple test
+ */
+ @Test
+ public void testSimple () throws IOException {
+ File logfile = new File("./test1");
+ File ctlfile = new File("./my-test-config-id.logcontrol");
+ try {
+ logfile.delete();
+ ctlfile.delete();
+ VespaLogHandler h
+ = new VespaLogHandler(new FileLogTarget(new File("test1")),
+ new VespaLevelControllerRepo(ctlfile.getName(), "all", "TST"),
+ "my-test-config-id", "TST");
+ h.publish(record1);
+ h.cleanup();
+ h.close();
+
+ File f = new File("test1");
+ assertTrue(f.exists());
+
+ String[] lines = readFile(f.getName());
+
+ // make sure there is only one line in the file
+ assertEquals(1, lines.length);
+
+ // make sure that ine line matches what is expected
+ assertEquals(record1String, lines[0]);
+ }
+ finally {
+ logfile.delete();
+ ctlfile.delete();
+ }
+ }
+
+ @Test
+ public void testFallback() throws FileNotFoundException {
+ File file = new File("mydir2");
+ file.delete();
+ assertTrue(file.mkdir());
+ try {
+ new VespaLogHandler(new FileLogTarget(file), new VespaLevelControllerRepo("my-test-config-id.logcontrol", "all", "TST"), "my-test-config-id", "TST");
+ } catch (Exception e) {
+ fail("Should not throw exception: " + e.getMessage());
+ }
+ }
+
+ @After
+ public void cleanup() { new File("mydir2").delete(); }
+
+ /**
+ * Perform simple test
+ */
+ @Test
+ public void testLogCtl () throws InterruptedException, FileNotFoundException {
+ MockLevelController ctl = new MockLevelController();
+ MockLevelControllerRepo ctlRepo = new MockLevelControllerRepo(ctl);
+ MockLogTarget target = new MockLogTarget();
+ VespaLogHandler h = new VespaLogHandler(target,
+ ctlRepo,
+ "my-test-config-id", "TST");
+ ctl.setShouldLog(Level.INFO);
+ h.publish(record1);
+ h.publish(record2);
+ h.publish(record3);
+ h.publish(record4);
+
+ ctl.setShouldLog(Level.CONFIG);
+ h.publish(record1);
+ h.publish(record2);
+ h.publish(record3);
+ h.publish(record4);
+
+ ctl.setShouldLog(Level.WARNING);
+ h.publish(record1);
+ h.publish(record2);
+ h.publish(record3);
+ h.publish(record4);
+
+ ctl.setShouldLog(Level.FINE);
+ h.publish(record1);
+ h.publish(record2);
+ h.publish(record3);
+ h.publish(record4);
+
+ h.close();
+ String [] lines = target.getLines();
+ assertThat(lines.length, is(4));
+ assertEquals(record1String, lines[0]);
+ assertEquals(record3String, lines[1]);
+ //assertEquals(record4String, lines[2]);
+ assertEquals(record2String, lines[3]);
+ }
+
+ /**
+ * Make sure rotation works
+ */
+ @Test
+ public void testRotate () throws IOException {
+ // Doesn't work in Windows. TODO: Fix the logging stuff
+ if (System.getProperty("os.name").toLowerCase().indexOf("win")>=0)
+ return;
+ try {
+ VespaLogHandler h
+ = new VespaLogHandler(new FileLogTarget(new File("test2")),
+ new VespaLevelControllerRepo("my-test-config-id.logcontrol", "all", "TST"), "my-test-config-id",
+ "TST"
+ );
+ h.publish(record1);
+
+ // before rename
+ assertTrue(new File("test2").exists());
+ assertFalse(new File("test2-rotated").exists());
+
+ // rename file
+ assertTrue("Could rename test2 to test2-rotated",
+ new File("test2").renameTo(new File("test2-rotated")));
+
+ // log next entry
+ h.publish(record2);
+ h.cleanup();
+ h.close();
+
+ // now make sure both files exist
+ assertTrue(new File("test2").exists());
+ assertTrue(new File("test2-rotated").exists());
+
+ // read both files
+ String[] lines1 = readFile("test2");
+ String[] lines2 = readFile("test2-rotated");
+
+ // make sure they have the correct number of lines
+ assertEquals(1, lines1.length);
+ assertEquals(1, lines2.length);
+
+ // make sure they match the correct strings
+ assertEquals(record2String, lines1[0]);
+ assertEquals(record1String, lines2[0]);
+ }
+ finally {
+ new File("test2").delete();
+ new File("test2-rotated").delete();
+ }
+ }
+
+
+ /**
+ * This test was made in order to look for a race condition
+ * which occurs when file rotation in the VespaLogHandler
+ * occurs concurrently (which it should never do).
+ */
+ @Test
+ public void testRaceCondition () throws FileNotFoundException {
+ int numThreads = 10;
+ Thread[] t = new Thread[numThreads];
+ final CyclicBarrier barrier = new CyclicBarrier(numThreads);
+ final int numLogEntries = 100;
+
+ try {
+ final VespaLogHandler h =
+ new VespaLogHandler(new FileLogTarget(new File("test4")),
+ new VespaLevelControllerRepo("my-test-config-id.logcontrol", "all", "TST"), "my-test-config-id",
+ "TST"
+ );
+
+ class LogRacer implements Runnable {
+ private int n;
+
+ public LogRacer (int n) {
+ this.n = n;
+ }
+
+ public void run () {
+ try {
+ barrier.await();
+ logLikeCrazy();
+ }
+ catch (BrokenBarrierException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void logLikeCrazy () {
+ for (int j = 0; j < numLogEntries; j++) {
+ try {
+ h.publish(record1);
+ h.publish(record2);
+ }
+ catch (Throwable t) {
+ fail(t.getMessage());
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < numThreads; i++) {
+ t[i] = new Thread(new LogRacer(i));
+ t[i].start();
+ }
+
+ for (int i = 0; i < numThreads; i++) {
+ t[i].join();
+ }
+
+ String[] lines = readFile("test4");
+ assertTrue("num lines was " + lines.length
+ + " should be " + (2 * numLogEntries * numThreads),
+ (lines.length == (2 * numLogEntries * numThreads)));
+
+ h.cleanup();
+ h.close();
+ }
+ catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ finally {
+ new File("test4").delete();
+ }
+
+ }
+
+ /**
+ * Make sure unicode characters in log message works
+ */
+ @Test
+ public void testUnicodeLog () throws IOException {
+ try {
+ VespaLogHandler h
+ = new VespaLogHandler(new FileLogTarget(new File("test4")),
+ new VespaLevelControllerRepo("my-test-config-id.logcontrol", "all", "TST"), "my-test-config-id",
+ "TST"
+ );
+ h.publish(record4);
+ h.cleanup();
+ h.close();
+
+ // check that file exists
+ assertTrue(new File("test4").exists());
+
+ // read file
+ String[] lines = readFile("test4");
+
+ // make sure the file have the correct number of lines
+ assertEquals(1, lines.length);
+
+ // make sure the read lines match the correct string
+ assertEquals(record4String, lines[0]);
+ }
+ finally {
+ new File("test4").delete();
+ }
+ }
+
+ /**
+ * read a text file into a string array
+ *
+ */
+ protected static String[] readFile (String fileName) {
+ BufferedReader br = null;
+ List<String> lines = new LinkedList<String>();
+ try {
+ br = new BufferedReader(
+ new InputStreamReader(new FileInputStream(new File(fileName)), "UTF-8"));
+ for (String line = br.readLine();
+ line != null;
+ line = br.readLine())
+ {
+ lines.add(line);
+ }
+ return lines.toArray(new String[lines.size()]);
+ }
+ catch (Throwable e) {
+ return new String[0];
+ }
+ finally {
+ if (br != null) {
+ try {
+ br.close();
+ }
+ catch (IOException e) {}
+ }
+ }
+ }
+
+ private static class MockLevelControllerRepo implements LevelControllerRepo {
+ private LevelController levelController;
+ public MockLevelControllerRepo(LevelController controller) {
+ this.levelController = controller;
+ }
+
+ @Override
+ public LevelController getLevelController(String component) {
+ return levelController;
+ }
+
+ @Override
+ public void close() { }
+ }
+
+ private static class MockLevelController implements LevelController {
+
+ private Level logLevel = Level.ALL;
+
+ @Override
+ public boolean shouldLog(Level level) {
+ return (level.equals(logLevel));
+ }
+
+ public void setShouldLog(Level level) {
+ this.logLevel = level;
+ }
+
+
+ @Override
+ public String getOnOffString() { return ""; }
+
+ @Override
+ public void checkBack() { }
+
+ @Override
+ public Level getLevelLimit() {
+ return logLevel;
+ }
+ }
+
+ private static class MockLogTarget implements LogTarget {
+ private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ public String[] getLines() {
+ return baos.toString().split("\n");
+ }
+ @Override
+ public OutputStream open() {
+ return baos;
+ }
+
+ @Override
+ public void close() {
+ try {
+ baos.close();
+ } catch (IOException e) {
+ fail("Test failed: " + e.getMessage());
+ }
+ }
+ }
+}
diff --git a/vespalog/src/test/java/com/yahoo/log/event/EventTestCase.java b/vespalog/src/test/java/com/yahoo/log/event/EventTestCase.java
new file mode 100644
index 00000000000..de116bc2e74
--- /dev/null
+++ b/vespalog/src/test/java/com/yahoo/log/event/EventTestCase.java
@@ -0,0 +1,199 @@
+// 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 java.util.logging.Logger;
+
+import com.yahoo.log.VespaFormatter;
+import com.yahoo.log.event.Collection;
+import com.yahoo.log.event.Count;
+import com.yahoo.log.event.Crash;
+import com.yahoo.log.event.Event;
+import com.yahoo.log.event.MalformedEventException;
+import com.yahoo.log.event.Progress;
+import com.yahoo.log.event.Reloaded;
+import com.yahoo.log.event.Reloading;
+import com.yahoo.log.event.Started;
+import com.yahoo.log.event.Starting;
+import com.yahoo.log.event.Stopped;
+import com.yahoo.log.event.Stopping;
+import com.yahoo.log.event.Unknown;
+import com.yahoo.log.event.Value;
+
+public class EventTestCase extends junit.framework.TestCase {
+ Count countEvent;
+ Count floatCountEvent;
+ Value valueEvent;
+ Value floatValueEvent;
+ Crash crashEvent;
+ Progress progressEvent1;
+ Progress progressEvent2;
+ Reloading reloadingEvent;
+ Reloaded reloadedEvent;
+ Starting startingEvent;
+ Started startedEvent;
+ Stopping stoppingEvent;
+ Stopped stoppedEvent;
+ Collection collectionEvent;
+ Unknown unknownEvent;
+
+ public EventTestCase (String name) {
+ super(name);
+ }
+
+ public void setUp() {
+ countEvent = new Count("thecounter", 1234);
+ floatCountEvent= new Count("thecounter", 1234.23);
+ valueEvent = new Value("thevalue", 4566);
+ floatValueEvent= new Value("thevalue", 4566.23);
+ crashEvent = new Crash("appname", 1234, 11);
+ progressEvent1 = new Progress("thename", 1);
+ progressEvent2 = new Progress("thename", 1.0, 2.0);
+ reloadingEvent = new Reloading("thefilewereloading");
+ reloadedEvent = new Reloaded("thefilewereloaded");
+ startingEvent = new Starting("startingName");
+ startedEvent = new Started("startedName");
+ stoppingEvent = new Stopping("stoppingName", "because we want to");
+ stoppedEvent = new Stopped("stoppedName", 1234, 1);
+ collectionEvent=new Collection(123456, "thename");
+ unknownEvent = new Unknown();
+ }
+
+ // make sure we can make the test instances okay
+ public void testExists () {
+ assertNotNull(countEvent);
+ assertNotNull(floatCountEvent);
+ assertNotNull(valueEvent);
+ assertNotNull(floatValueEvent);
+ assertNotNull(crashEvent);
+ assertNotNull(progressEvent1);
+ assertNotNull(progressEvent2);
+ assertNotNull(reloadingEvent);
+ assertNotNull(reloadedEvent);
+ assertNotNull(startingEvent);
+ assertNotNull(startedEvent);
+ assertNotNull(stoppingEvent);
+ assertNotNull(stoppedEvent);
+ assertNotNull(collectionEvent);
+ assertNotNull(unknownEvent);
+ }
+
+ public void testEvents () {
+ assertEquals("count/1 name=thecounter value=1234",
+ countEvent.toString());
+
+ assertEquals("count/1 name=thecounter value=1234",
+ floatCountEvent.toString());
+
+ assertEquals("crash/1 name=appname pid=1234 signal=11",
+ crashEvent.toString());
+
+ assertEquals("progress/1 name=thename value=1.0 total=",
+ progressEvent1.toString());
+
+ assertEquals("progress/1 name=thename value=1.0 total=2.0",
+ progressEvent2.toString());
+
+ assertEquals("reloaded/1 name=thefilewereloaded",
+ reloadedEvent.toString());
+
+ assertEquals("reloading/1 name=thefilewereloading",
+ reloadingEvent.toString());
+
+ assertEquals("started/1 name=startedName",
+ startedEvent.toString());
+
+ assertEquals("starting/1 name=startingName",
+ startingEvent.toString());
+
+ assertEquals("stopping/1 name=stoppingName why=\"because we want to\"",
+ stoppingEvent.toString());
+
+ assertEquals("collection/1 collectionId=123456 name=thename",
+ collectionEvent.toString());
+
+ assertEquals("stopped/1 name=stoppedName pid=1234 exitcode=1",
+ stoppedEvent.toString());
+ }
+
+ /**
+ * do the dirty work for testParser
+ */
+ private void parseEvent (Event e) throws MalformedEventException {
+ assertEquals(e.toString(), Event.parse(e.toString()).toString());
+ }
+
+ public void testParser () throws MalformedEventException {
+ parseEvent(countEvent);
+ parseEvent(floatCountEvent);
+ parseEvent(crashEvent);
+ parseEvent(progressEvent1);
+ parseEvent(progressEvent2);
+ parseEvent(reloadingEvent);
+ parseEvent(reloadedEvent);
+ parseEvent(startingEvent);
+ parseEvent(startedEvent);
+ parseEvent(stoppingEvent);
+ parseEvent(stoppedEvent);
+ }
+
+ public void testUnknownEvents () throws MalformedEventException {
+ String s = "crappyevent/2 first=\"value one\" second=more third=4";
+ Event event = Event.parse(s);
+ assertEquals(s, event.toString());
+
+ String s2 = "notinventedhere/999";
+ assertEquals(s2, Event.parse(s2).toString());
+ }
+
+ /**
+ * This test makes sure that the static event logging methods
+ * successfully manage to find the right name on the calling
+ * stack -- it should find the name of the calling class.
+ */
+ public void testFindingCallingClassLogger() {
+ SingleHandler sh = new SingleHandler();
+ assertNull(sh.lastRecord());
+
+ VespaFormatter formatter = new VespaFormatter();
+ Logger log = Logger.getLogger(EventTestCase.class.getName());
+ synchronized(log) {
+ log.setUseParentHandlers(false);
+ log.addHandler(sh);
+ Event.starting("mintest");
+
+ assertTrue(formatter.format(sh.lastRecord()).
+ indexOf("\t.com.yahoo.log.event.EventTestCase\tevent\tstarting/1 name=mintest") > -1);
+
+ Event.starting("startingName");
+ Event.started("startedName");
+ Event.stopping("stoppingName", "whyParam");
+ Event.stopped("stoppedName", 1, 2);
+ Event.reloading("reloadingName");
+ Event.reloaded("reloadedName");
+ Event.count("countName", 1);
+ Event.progress("progressName", 1, 2);
+ Event.crash("crashName", 1, 2);
+ }
+ }
+
+ public void testFunnyEvent () {
+ String funnyEvent = "collection/1 collectionId=1111111111 name=\"syncrows\" params=\"column=0 badrow=1 goodrow=0\"";
+ try {
+ Event e = Event.parse(funnyEvent);
+ }
+ catch (MalformedEventException e) {
+ fail();
+ }
+ }
+
+ public void testFullParse () {
+ try {
+ Event event = Event.parse("count/1 name=\"data_searched_mb\" value=15115168.3940149993");
+ assertTrue(event instanceof Count);
+ assertEquals("data_searched_mb", event.getValue("name"));
+ assertEquals("15115168", event.getValue("value"));
+ } catch (MalformedEventException e) {
+ fail("Malformed Event Exception on parsing");
+ }
+ }
+}
diff --git a/vespalog/src/test/java/com/yahoo/log/event/SingleHandler.java b/vespalog/src/test/java/com/yahoo/log/event/SingleHandler.java
new file mode 100644
index 00000000000..47e342c2523
--- /dev/null
+++ b/vespalog/src/test/java/com/yahoo/log/event/SingleHandler.java
@@ -0,0 +1,20 @@
+// 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 java.util.logging.Handler;
+import java.util.logging.LogRecord;
+
+public class SingleHandler extends Handler {
+ private LogRecord lastRecord;
+
+ public void flush() {}
+ public void close() {}
+
+ public void publish (LogRecord r) {
+ lastRecord = r;
+ }
+
+ public LogRecord lastRecord () {
+ return lastRecord;
+ }
+}
diff --git a/vespalog/src/test/logtest.cpp b/vespalog/src/test/logtest.cpp
new file mode 100644
index 00000000000..29902e418cc
--- /dev/null
+++ b/vespalog/src/test/logtest.cpp
@@ -0,0 +1,37 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <sys/types.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <vespa/log/log.h>
+
+LOG_SETUP("logtest",
+ "$Id$");
+
+int
+main(int, char **argv)
+{
+ EV_STARTING("logtest");
+ LOG(info, "Starting up, called as %s", argv[0]);
+ EV_STARTED("logtest");
+ EV_RELOADING("something");
+ EV_RELOADED("something");
+ EV_CRASH("something", getpid(), SIGTERM);
+ EV_PROGRESS("batch-index", 7, 100);
+ EV_PROGRESS("unbounded-batch-index", 9);
+ EV_COUNT("hits", 3);
+ EV_VALUE("some value", 1./3);
+
+ LOG(info, "backslash: \\");
+ int n;
+ LOG(info, "Will log 20 spam messages now every 500ms");
+ for (n = 1; n <= 20; n++) {
+ LOG(spam, "log message %d/%d", n, 100);
+ usleep(500000);
+ }
+ LOG(info, "Exiting.");
+ EV_STOPPING("logtest", "clean exit");
+ EV_STOPPED("logtest", getpid(), 0);
+ exit(EXIT_SUCCESS);
+}
diff --git a/vespalog/src/test/rejectfiltertest.cpp b/vespalog/src/test/rejectfiltertest.cpp
new file mode 100644
index 00000000000..a11c25e58e1
--- /dev/null
+++ b/vespalog/src/test/rejectfiltertest.cpp
@@ -0,0 +1,57 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/log/reject-filter.h>
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <iostream>
+
+using ns_log::RejectFilter;
+using ns_log::Logger;
+
+void
+assertShouldNotReject(RejectFilter & filter, Logger::LogLevel level, const char * msg)
+{
+ std::cerr << "Filter should not reject level '" << Logger::levelName(level) << "' message '" << (msg == NULL ? "NULL" : msg) << "' ...: ";
+ if (filter.shouldReject(level, msg)) {
+ std::cerr << "Failed!\n";
+ exit(EXIT_FAILURE);
+ }
+ std::cerr << "Success!\n";
+}
+
+void
+assertShouldReject(RejectFilter & filter, Logger::LogLevel level, const char * msg)
+{
+ std::cerr << "Filter should reject level '" << Logger::levelName(level) << "' message '" << (msg == NULL ? "NULL" : msg) << "' ...: ";
+ if (!filter.shouldReject(level, msg)) {
+ std::cerr << "Failed!\n";
+ exit(EXIT_FAILURE);
+ }
+ std::cerr << "Success!\n";
+}
+
+int
+main(int argc, char **argv)
+{
+ (void) argc;
+ (void) argv;
+
+ ns_log::RejectFilter filter;
+ filter.addRejectRule(Logger::warning, "bar");
+ assertShouldNotReject(filter, Logger::warning, NULL);
+ assertShouldNotReject(filter, Logger::warning, "");
+ assertShouldNotReject(filter, Logger::warning, "foo");
+ assertShouldReject(filter, Logger::warning, "bar");
+ assertShouldReject(filter, Logger::warning, "barfoo");
+ assertShouldReject(filter, Logger::warning, "foobar");
+ assertShouldReject(filter, Logger::warning, "foobarbaz");
+
+ ns_log::RejectFilter defaultFilter = RejectFilter::createDefaultFilter();
+ assertShouldReject(defaultFilter, Logger::warning, "E 23-235018.067240 14650 23/10/2012 23:50:18 yjava_preload.so: [preload.c:350] Using FILTER_NONE: This must be paranoid approved, and since you are using FILTER_NONE you must live with this error.");
+ assertShouldReject(defaultFilter, Logger::warning, "");
+ assertShouldNotReject(defaultFilter, Logger::warning, "foobar");
+ assertShouldNotReject(defaultFilter, Logger::event, NULL);
+ assertShouldReject(defaultFilter, Logger::warning, "E 18-140313.398540 10727 18/11/2012 14:03:13 yjava_preload.so: [preload.c:670] Accept failed: -1 (4)");
+ exit(EXIT_SUCCESS);
+}
diff --git a/vespalog/src/test/simple/.gitignore b/vespalog/src/test/simple/.gitignore
new file mode 100644
index 00000000000..d3c5aa01c49
--- /dev/null
+++ b/vespalog/src/test/simple/.gitignore
@@ -0,0 +1,4 @@
+/simple.lctl
+/simple.logf
+/simple_test
+vespalog_simple_test_app
diff --git a/vespalog/src/test/simple/CMakeLists.txt b/vespalog/src/test/simple/CMakeLists.txt
new file mode 100644
index 00000000000..f915772d80f
--- /dev/null
+++ b/vespalog/src/test/simple/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalog_simple_test_app
+ SOURCES
+ logtest.cpp
+ DEPENDS
+ vespalog
+)
+vespa_add_test(NAME vespalog_simple_test_app COMMAND vespalog_simple_test_app)
diff --git a/vespalog/src/test/simple/expect.lctl b/vespalog/src/test/simple/expect.lctl
new file mode 100644
index 00000000000..5256fbb2821
--- /dev/null
+++ b/vespalog/src/test/simple/expect.lctl
@@ -0,0 +1,5 @@
+Vespa log control file version 1
+Prefix: logtest
+
+default: ON ON ON ON ON ON OFF OFF
+.log: ON ON ON ON ON ON OFF OFF
diff --git a/vespalog/src/test/simple/logtest.cpp b/vespalog/src/test/simple/logtest.cpp
new file mode 100644
index 00000000000..b532b2a4b8f
--- /dev/null
+++ b/vespalog/src/test/simple/logtest.cpp
@@ -0,0 +1,37 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <sys/types.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <vespa/log/log.h>
+
+LOG_SETUP("logtest",
+ "$Id: logtest.cpp 119439 2011-04-19 09:32:27Z arnej $");
+
+int
+main(int, char **argv)
+{
+ EV_STARTING("logtest");
+ LOG(info, "Starting up, called as %s", argv[0]);
+ EV_STARTED("logtest");
+ EV_RELOADING("something");
+ EV_RELOADED("something");
+ EV_CRASH("something", getpid(), SIGTERM);
+ EV_PROGRESS("batch-index", 7, 100);
+ EV_PROGRESS("unbounded-batch-index", 9);
+ EV_COUNT("hits", 3);
+ EV_VALUE("some value", 1./3);
+
+ LOG(info, "backslash: \\");
+ int n;
+ LOG(info, "Will log 20 spam messages now every 50ms");
+ for (n = 1; n <= 5; n++) {
+ LOG(spam, "log message %d/%d", n, 100);
+ usleep(50000);
+ }
+ LOG(info, "Exiting.");
+ EV_STOPPING("logtest", "clean exit");
+ EV_STOPPED("logtest", getpid(), 0);
+ exit(EXIT_SUCCESS);
+}
diff --git a/vespalog/src/test/threads/.gitignore b/vespalog/src/test/threads/.gitignore
new file mode 100644
index 00000000000..238de5943e7
--- /dev/null
+++ b/vespalog/src/test/threads/.gitignore
@@ -0,0 +1,2 @@
+/threads_test
+vespalog_threads_test_app
diff --git a/vespalog/src/test/threads/CMakeLists.txt b/vespalog/src/test/threads/CMakeLists.txt
new file mode 100644
index 00000000000..77928ed3213
--- /dev/null
+++ b/vespalog/src/test/threads/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalog_threads_test_app
+ SOURCES
+ testthreads.cpp
+ DEPENDS
+ vespalog
+)
+vespa_add_test(NAME vespalog_threads_test_app COMMAND vespalog_threads_test_app vespa.log ENVIRONMENT "VESPA_LOG_TARGET=file:vespa.log")
diff --git a/vespalog/src/test/threads/testthreads.cpp b/vespalog/src/test/threads/testthreads.cpp
new file mode 100644
index 00000000000..6e5f72da524
--- /dev/null
+++ b/vespalog/src/test/threads/testthreads.cpp
@@ -0,0 +1,139 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+#include <iostream>
+#include <string>
+
+using std::string;
+
+
+LOG_SETUP(".threadtest");
+
+class FileThread : public FastOS_Runnable
+{
+ bool _done;
+ string _file;
+public:
+ FileThread(string file) : _done(false), _file(file) {}
+ virtual void Run(FastOS_ThreadInterface *thread, void *arg);
+ void stop() {_done = true; }
+};
+
+class LoggerThread : public FastOS_Runnable
+{
+ bool _done;
+public:
+ bool _useLogBuffer;
+ LoggerThread() : _done(false), _useLogBuffer(false) {}
+ virtual void Run(FastOS_ThreadInterface *thread, void *arg);
+ void stop() {_done = true; }
+};
+
+
+
+
+void
+FileThread::Run(FastOS_ThreadInterface *, void *)
+{
+ unlink(_file.c_str());
+ while (!_done) {
+ int fd = open(_file.c_str(), O_RDWR | O_CREAT | O_APPEND, 0644);
+ if (fd == -1) {
+ fprintf(stderr, "open failed: %s\n", strerror(errno));
+ exit(1);
+ }
+ FastOS_Thread::Sleep(5);
+ struct stat buf;
+ fstat(fd, &buf);
+ if (buf.st_size != 0) {
+ fprintf(stderr, "%s isn't empty anymore\n", _file.c_str());
+ exit(1);
+ }
+ if (close(fd) != 0) {
+ fprintf(stderr, "close of %d failed: %s\n", fd, strerror(errno));
+ exit(1);
+ }
+ }
+}
+
+
+void
+LoggerThread::Run(FastOS_ThreadInterface *, void *)
+{
+ int counter = 0;
+ while (!_done) {
+ if (_useLogBuffer) {
+ LOGBM(info, "bla bla bla %u", ++counter);
+ } else {
+ LOG(info, "bla bla bla");
+ }
+ }
+}
+
+
+class ThreadTester : public FastOS_Application
+{
+public:
+ int Main();
+};
+
+int
+ThreadTester::Main()
+{
+ std::cerr << "Testing that logging is threadsafe. 30 sec test.\n";
+ FastOS_ThreadPool pool(128 * 1024);
+
+ const int numWriters = 30;
+ const int numLoggers = 10;
+
+ auto writers = std::array<std::unique_ptr<FileThread>, numWriters>();
+ auto loggers = std::array<std::unique_ptr<LoggerThread>, numLoggers>();
+
+ for (int i = 0; i < numWriters; i++) {
+ char filename[100];
+ sprintf(filename, "empty.%d", i);
+ writers[i] = std::make_unique<FileThread>(filename);
+ pool.NewThread(writers[i].get());
+ }
+ for (int i = 0; i < numLoggers; i++) {
+ loggers[i] = std::make_unique<LoggerThread>();
+ pool.NewThread(loggers[i].get());
+ }
+
+ FastOS_Time start;
+ start.SetNow();
+ // Reduced runtime to half as the test now repeats itself to test with
+ // buffering. (To avoid test taking a minute)
+ while (start.MilliSecsToNow() < 15 * 1000) {
+ unlink(_argv[1]);
+ FastOS_Thread::Sleep(1);
+ }
+ // Then set to use logbuffer and continue
+ for (int i = 0; i < numLoggers; i++) {
+ loggers[i]->_useLogBuffer = true;
+ }
+ start.SetNow();
+ while (start.MilliSecsToNow() < 15 * 1000) {
+ unlink(_argv[1]);
+ FastOS_Thread::Sleep(1);
+ }
+
+ for (int i = 0; i < numLoggers; i++) {
+ loggers[i]->stop();
+ }
+ for (int i = 0; i < numWriters; i++) {
+ writers[i]->stop();
+ }
+
+ pool.Close();
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ ThreadTester app;
+ return app.Entry(argc, argv);
+}
+
+
diff --git a/vespalog/src/vespa/log/.gitignore b/vespalog/src/vespa/log/.gitignore
new file mode 100644
index 00000000000..7b45206e869
--- /dev/null
+++ b/vespalog/src/vespa/log/.gitignore
@@ -0,0 +1,8 @@
+*.So
+*.so.*
+.depend
+Makefile
+logctl
+loglevelnames.cpp
+logtest
+logtest.logcontrol
diff --git a/vespalog/src/vespa/log/CMakeLists.txt b/vespalog/src/vespa/log/CMakeLists.txt
new file mode 100644
index 00000000000..e36a55b1f8a
--- /dev/null
+++ b/vespalog/src/vespa/log/CMakeLists.txt
@@ -0,0 +1,28 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_library(vespalog
+ SOURCES
+ ${CMAKE_CURRENT_BINARY_DIR}/loglevelnames.cpp
+ log.cpp
+ bufferedlogger.cpp
+ log-target-fd.cpp
+ log-target-file.cpp
+ log-target.cpp
+ lock.cpp
+ internal.cpp
+ control-file.cpp
+ component.cpp
+ llparser.cpp
+ log-assert.cpp
+ reject-filter.cpp
+ INSTALL lib64
+ DEPENDS
+ fastos
+)
+
+add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/loglevelnames.cpp
+ COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/mknm.pl < ${CMAKE_CURRENT_SOURCE_DIR}/log.h > lln.NEW && mv lln.NEW ${CMAKE_CURRENT_BINARY_DIR}/loglevelnames.cpp
+ MAIN_DEPENDENCY log.h
+ DEPENDENCIES mknm.pl
+)
+
diff --git a/vespalog/src/vespa/log/bufferedlogger.cpp b/vespalog/src/vespa/log/bufferedlogger.cpp
new file mode 100644
index 00000000000..76fdcf1f66a
--- /dev/null
+++ b/vespalog/src/vespa/log/bufferedlogger.cpp
@@ -0,0 +1,382 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/log/bufferedlogger.h>
+
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include <vector>
+#include <stdarg.h>
+#include <sys/time.h>
+
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/identity.hpp>
+#include <boost/multi_index/member.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+
+namespace ns_log {
+
+// implementation details for BufferedLogger
+class BackingBuffer {
+ BackingBuffer(const BackingBuffer & rhs);
+ BackingBuffer & operator = (const BackingBuffer & rhs);
+public:
+ std::unique_ptr<Timer> _timer;
+ /** Lock needed to access cache. */
+ mutable FastOS_Mutex _mutex;
+
+ static uint64_t _countFactor;
+
+ /** Struct keeping information about log message. */
+ struct Entry {
+ Logger::LogLevel _level;
+ std::string _file;
+ int _line;
+ std::string _token;
+ std::string _message;
+ uint32_t _count;
+ uint64_t _timestamp;
+ Logger* _logger;
+
+ Entry(Logger::LogLevel level, const char* file, int line,
+ const std::string& token, const std::string& message,
+ uint64_t timestamp, Logger&);
+
+ bool operator==(const Entry& entry) const;
+ bool operator<(const Entry& entry) const;
+
+ uint64_t getAgeFactor() const;
+
+ std::string toString() const;
+ };
+
+ typedef boost::multi_index_container<
+ Entry,
+ boost::multi_index::indexed_by<
+ boost::multi_index::sequenced<>, // Timestamp sorted
+ boost::multi_index::ordered_unique<
+ boost::multi_index::identity<Entry>
+ >
+ >
+ > LogCacheFront;
+ typedef boost::multi_index_container<
+ Entry,
+ boost::multi_index::indexed_by<
+ boost::multi_index::sequenced<>, // Timestamp sorted
+ boost::multi_index::ordered_unique<
+ boost::multi_index::identity<Entry>
+ >,
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::const_mem_fun<
+ Entry, uint64_t, &Entry::getAgeFactor
+ >
+ >
+ >
+ > LogCacheBack;
+
+ /** Entry container indexes on insert order and token. */
+ LogCacheFront _cacheFront;
+ /** Entry container indexed on insert order, token and age function. */
+ LogCacheBack _cacheBack;
+
+ uint32_t _maxCacheSize;
+ uint64_t _maxEntryAge;
+
+ /** Empty buffer and write all log entries in it. */
+ void flush();
+
+ /** Gives all current content of log buffer. Useful for debugging. */
+ std::string toString() const;
+
+ /**
+ * Flush parts of cache, so we're below max size and only have messages of
+ * acceptable age. Calling this, _mutex should already be locked.
+ */
+ void trimCache(uint64_t currentTime);
+
+ /**
+ * Trim the cache up to current time. Used externally to check if we
+ * need to empty buffer before new log messages arive.
+ */
+ void trimCache() {
+ _mutex.Lock();
+ trimCache(_timer->getTimestamp());
+ _mutex.Unlock();
+ }
+
+ /**
+ * Log a given entry to underlying logger. Used when removing from cache.
+ * Calling this, _mutex should already be locked.
+ */
+ void log(const Entry& e) const;
+
+ BackingBuffer();
+ ~BackingBuffer();
+
+ void logImpl(Logger& l, Logger::LogLevel level,
+ const char *file, int line,
+ const std::string& token,
+ const std::string& message);
+
+};
+
+// Let each hit count for 5 seconds
+uint64_t BackingBuffer::_countFactor = VESPA_LOG_COUNTAGEFACTOR * 1000 * 1000;
+
+BackingBuffer::Entry::Entry(Logger::LogLevel level, const char* file, int line,
+ const std::string& token, const std::string& msg,
+ uint64_t timestamp, Logger& l)
+ : _level(level),
+ _file(file),
+ _line(line),
+ _token(token),
+ _message(msg),
+ _count(1),
+ _timestamp(timestamp),
+ _logger(&l)
+{
+}
+
+bool
+BackingBuffer::Entry::operator==(const Entry& entry) const
+{
+ return (_token == entry._token);
+}
+
+bool
+BackingBuffer::Entry::operator<(const Entry& entry) const
+{
+ // Don't let tokens from different loggers match each other
+ if (_logger != entry._logger) {
+ return _logger < entry._logger;
+ }
+ // If in the same logger, you should have full control. Overlapping
+ // tokens if you want is a feature.
+ return (_token < entry._token);
+}
+
+std::string
+BackingBuffer::Entry::toString() const
+{
+ std::ostringstream ost;
+ ost << "Entry(" << _level << ", " << _file << ":" << _line << ": "
+ << _message << " [" << _token << "], count " << _count
+ << ", timestamp " << _timestamp << ")";
+ return ost.str();
+}
+
+uint64_t
+BackingBuffer::Entry::getAgeFactor() const
+{
+ return _timestamp + _countFactor * _count;
+}
+
+BufferedLogger BufferedLogger::logger;
+
+BackingBuffer::BackingBuffer()
+ : _timer(new Timer),
+ _mutex(),
+ _cacheFront(),
+ _cacheBack(),
+ _maxCacheSize(VESPA_LOG_LOGBUFFERSIZE),
+ _maxEntryAge(VESPA_LOG_LOGENTRYMAXAGE * 1000 * 1000)
+{
+}
+
+BackingBuffer::~BackingBuffer()
+{
+}
+
+BufferedLogger::BufferedLogger()
+{
+ _backing = new BackingBuffer();
+}
+
+BufferedLogger::~BufferedLogger()
+{
+ delete _backing; _backing = NULL;
+}
+
+namespace {
+ typedef boost::multi_index::nth_index<
+ BackingBuffer::LogCacheFront, 0>::type LogCacheFrontTimestamp;
+ typedef boost::multi_index::nth_index<
+ BackingBuffer::LogCacheFront, 1>::type LogCacheFrontToken;
+ typedef boost::multi_index::nth_index<
+ BackingBuffer::LogCacheBack, 0>::type LogCacheBackTimestamp;
+ typedef boost::multi_index::nth_index<
+ BackingBuffer::LogCacheBack, 1>::type LogCacheBackToken;
+ typedef boost::multi_index::nth_index<
+ BackingBuffer::LogCacheBack, 2>::type LogCacheBackAge;
+}
+
+void
+BufferedLogger::doLog(Logger& l, Logger::LogLevel level,
+ const char *file, int line,
+ const std::string& mytoken, const char *fmt, ...)
+{
+ std::string token(mytoken);
+ va_list args;
+ va_start(args, fmt);
+
+ const size_t sizeofPayload(4000);
+ std::vector<char> buffer(sizeofPayload);
+ vsnprintf(&buffer[0], buffer.capacity(), fmt, args);
+ std::string message(&buffer[0]);
+ // Empty token means to use message itself as token
+ if (token.empty()) token = message;
+
+ _backing->logImpl(l, level, file, line, token, message);
+}
+
+void
+BackingBuffer::logImpl(Logger& l, Logger::LogLevel level,
+ const char *file, int line,
+ const std::string& token,
+ const std::string& message)
+{
+ Entry entry(level, file, line, token, message, _timer->getTimestamp(), l);
+
+ _mutex.Lock();
+ LogCacheFrontToken::iterator it1 = _cacheFront.get<1>().find(entry);
+ LogCacheBackToken::iterator it2 = _cacheBack.get<1>().find(entry);
+ if (it1 != _cacheFront.get<1>().end()) {
+ Entry copy(*it1);
+ ++copy._count;
+ _cacheFront.get<1>().replace(it1, copy);
+ } else if (it2 != _cacheBack.get<1>().end()) {
+ Entry copy(*it2);
+ ++copy._count;
+ _cacheBack.get<1>().replace(it2, copy);
+ } else {
+ // If entry didn't already exist, add it to the cache and log it
+ l.doLogCore(entry._timestamp, level, file, line, message.c_str(), message.size());
+ _cacheFront.push_back(entry);
+ }
+ trimCache(entry._timestamp);
+ _mutex.Unlock();
+}
+
+void
+BackingBuffer::flush()
+{
+ _mutex.Lock();
+ for (LogCacheBack::const_iterator it = _cacheBack.begin();
+ it != _cacheBack.end(); ++it)
+ {
+ log(*it);
+ }
+ _cacheBack.clear();
+ for (LogCacheFront::const_iterator it = _cacheFront.begin();
+ it != _cacheFront.end(); ++it)
+ {
+ log(*it);
+ }
+ _cacheFront.clear();
+ _mutex.Unlock();
+}
+
+void
+BufferedLogger::flush() {
+ _backing->flush();
+}
+
+void
+BackingBuffer::trimCache(uint64_t currentTime)
+{
+ // Remove entries that have been in here too long.
+ while (!_cacheBack.empty() &&
+ _cacheBack.front()._timestamp + _maxEntryAge < currentTime)
+ {
+ log(_cacheBack.front());
+ _cacheBack.pop_front();
+ }
+ while (!_cacheFront.empty() &&
+ _cacheFront.front()._timestamp + _maxEntryAge < currentTime)
+ {
+ log(_cacheFront.front());
+ _cacheFront.pop_front();
+ }
+ // If cache front is larger than half max size, move to back.
+ for (uint32_t i = _cacheFront.size(); i > _maxCacheSize / 2; --i) {
+ Entry e(_cacheFront.front());
+ _cacheFront.pop_front();
+ _cacheBack.push_back(e);
+ }
+ // Remove entries from back based on count modified age.
+ for (uint32_t i = _cacheFront.size() + _cacheBack.size();
+ i > _maxCacheSize; --i)
+ {
+ log(*_cacheBack.get<2>().begin());
+ _cacheBack.get<2>().erase(_cacheBack.get<2>().begin());
+ }
+}
+
+void
+BufferedLogger::trimCache()
+{
+ _backing->trimCache();
+}
+
+void
+BackingBuffer::log(const Entry& e) const
+{
+ if (e._count > 1) {
+ std::ostringstream ost;
+ ost << e._message << " (Repeated " << (e._count - 1)
+ << " times since " << (e._timestamp / 1000000) << "."
+ << std::setw(6) << std::setfill('0') << (e._timestamp % 1000000)
+ << ")";
+ e._logger->doLogCore(_timer->getTimestamp(), e._level, e._file.c_str(),
+ e._line, ost.str().c_str(), ost.str().size());
+ }
+}
+
+std::string
+BackingBuffer::toString() const
+{
+ std::ostringstream ost;
+ ost << "Front log cache content:\n";
+ _mutex.Lock();
+ for (LogCacheFront::const_iterator it = _cacheFront.begin();
+ it != _cacheFront.end(); ++it)
+ {
+ ost << " " << it->toString() << "\n";
+ }
+ ost << "Back log cache content:\n";
+ for (LogCacheBack::const_iterator it = _cacheBack.begin();
+ it != _cacheBack.end(); ++it)
+ {
+ ost << " " << it->toString() << "\n";
+ }
+ _mutex.Unlock();
+ return ost.str();
+}
+
+
+void
+BufferedLogger::setMaxCacheSize(uint32_t size) {
+ _backing->_maxCacheSize = size;
+}
+
+void
+BufferedLogger::setMaxEntryAge(uint64_t seconds) {
+ _backing->_maxEntryAge = seconds * 1000000;
+}
+
+void
+BufferedLogger::setCountFactor(uint64_t factor) {
+ _backing->_countFactor = factor * 1000000;
+}
+
+/** Set a fake timer to use for log messages. Used in unit testing. */
+void
+BufferedLogger::setTimer(std::unique_ptr<Timer> timer)
+{
+ _backing->_timer = std::move(timer);
+}
+
+
+} // ns_log
diff --git a/vespalog/src/vespa/log/bufferedlogger.h b/vespalog/src/vespa/log/bufferedlogger.h
new file mode 100644
index 00000000000..4f20f46af4c
--- /dev/null
+++ b/vespalog/src/vespa/log/bufferedlogger.h
@@ -0,0 +1,206 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * \class ns_log::BufferedLogger
+ * \ingroup log
+ *
+ * \brief Utility class for writing log less spammy.
+ *
+ * Uses string tokens to identify what messages are equal. Equal tokens means
+ * similar message. Use tokens similar to the messages, excluding the variable
+ * parts you don't want to mess up equality.
+ *
+ * Keeps a cache of log entries. When something is logged, check if the message
+ * is already in the log cache. If so, increase the count of the log entry. If
+ * it wasn't in the cache, log it to file and add it to the log cache.
+ *
+ * Trim the log cache if it is above maximum size or contains entries that are
+ * older than the max age they can live.
+ *
+ * There are no separate threads polling buffer to see if log entries are older
+ * than max age. Each doLog() call to the logbuffer will trigger a check.
+ *
+ * The log cache is a single static object, common for all loggers. This has
+ * the following advantages:
+ * - Only one instance to trigger to check for old entries.
+ * - We can keep a bigger cache, preventing issues with allocating large
+ * caches to lots of loggers or having to small of a cache for heavily used
+ * loggers.
+ * The disadvantage is that the cache itself needs to be able to use little
+ * resources even though there's many entries in the cache.
+ *
+ * For the log cache to be efficient, even if it gets large, it is indexed 3
+ * ways. This means that there's no operation (except flushing) that needs to
+ * go through all log entries. Currently, ordered indexes accessed through
+ * binary search is used to give log N complexity.
+ *
+ * - We need to look up entries by token to figure out if entry already exist.
+ * - We need to get oldest entries to see whether they are older than max age.
+ * - We need to identify entry to remove when cache is too large.
+ *
+ * Deciding on what entry to remove when the cache is too large is not straight
+ * forward. We don't want a burst of unique log entries to clear the whole
+ * buffer. We also need to make sure that the low count entries aren't starved
+ * out by the higher count entries, such that no new high count entries can
+ * come in.
+ *
+ * Suggested algorithm for deciding what entry to remove next:
+ * - Split the cache in two by timestamp. The first half of the cache
+ * contains the newest entries added. When entries are added, the last
+ * entry in the first half is moved into the last half (if first half is
+ * full). This ensures that new entries are never starved, and will always
+ * be able to build up their counts.
+ * - In the second half, when an entry needs to be removed, calculate each
+ * entry's timestamp + X * count, and remove the entry with the lowest
+ * score. This will let higher count entries live longer, but
+ * will prevent them from living forever. X is the age factor, saying
+ * how much each count counts for. For instance, if X counts for 5 seconds,
+ * this means that a log entry that you will rather remove an entry seen
+ * once 10 seconds ago, than an entry seen twice less than 15 seconds ago.
+ *
+ * For efficient access of the log buffer itself, boost' multi_index is used.
+ * This is a container that keeps separate indexes. In the log buffer case it
+ * will keep 3 indexes.
+ * - Index 0 will be the insert order. This will also be the timestamp order
+ * of the log entries, such that new entries goes in the back, and if we
+ * need to remove an entry due to old age, it will always be the first.
+ * - Index 1 will be the token. This is used for efficient lookup to find
+ * whether we have seen this entry before.
+ * - Index 2 will be the timestamp + X * count, used to find which entry to
+ * remove next if the buffer is too full. Note that this index is only
+ * needed in the second half of the buffer.
+ *
+ * The buffer itself is split into two parts, because this was the only way I
+ * could think of to keep it log N, as the sequenced index don't have random
+ * access, such that finding the middle element has N complexity, also, the
+ * first half of the entries would pollute the timestamp + X * count index, such
+ * that one potentially would have to check many entries before finding the
+ * correct one to remove.
+ */
+#pragma once
+
+#define VESPA_LOG_LOGBUFFERSIZE 1000 // Max log entries cached.
+#define VESPA_LOG_LOGENTRYMAXAGE 300 // Max seconds an entry can be cached
+#define VESPA_LOG_COUNTAGEFACTOR 5 // How many seconds each count counts for
+
+#include <vespa/fastos/mutex.h>
+#include <vespa/log/log.h>
+#include <sstream>
+#include <string>
+
+// Define LOG if using log buffer for regular LOG calls
+// If logger logs debug messages, bypass buffer.
+#ifdef VESPA_LOG_USELOGBUFFERFORREGULARLOG
+#define LOG(level, ...) \
+ do { \
+ if (logger.wants(ns_log::Logger::level)) { \
+ if (logger.wants(ns_log::Logger::debug)) { \
+ logger.doLog(ns_log::Logger::level, \
+ __FILE__, __LINE__, __VA_ARGS__); \
+ ns_log::BufferedLogger::logger.trimCache(); \
+ } else { \
+ ns_log::BufferedLogger::logger.doLog(logger, \
+ ns_log::Logger::level, __FILE__, __LINE__, \
+ "", __VA_ARGS__); \
+ } \
+ } \
+ } while (0)
+#endif
+
+// Define LOGBM macro for logging buffered, using the message itself as a
+// token. This is the same as LOG defined above if
+// VESPA_LOG_USELOGBUFFERFORREGULARLOG is defined.
+#define LOGBM(level, ...) \
+ do { \
+ if (logger.wants(ns_log::Logger::level)) { \
+ if (logger.wants(ns_log::Logger::debug)) { \
+ logger.doLog(ns_log::Logger::level, \
+ __FILE__, __LINE__, __VA_ARGS__); \
+ ns_log::BufferedLogger::logger.trimCache(); \
+ } else { \
+ ns_log::BufferedLogger::logger.doLog(logger, \
+ ns_log::Logger::level, __FILE__, __LINE__, \
+ "", __VA_ARGS__); \
+ } \
+ } \
+ } while (0)
+
+// Define LOGBP macro for logging buffered, using the call point as token.
+// (File/line of macro caller)
+#define LOGBP(level, ARGS...) \
+ do { \
+ if (logger.wants(ns_log::Logger::level)) { \
+ if (logger.wants(ns_log::Logger::debug)) { \
+ logger.doLog(ns_log::Logger::level, \
+ __FILE__, __LINE__, ##ARGS); \
+ ns_log::BufferedLogger::logger.trimCache(); \
+ } else { \
+ std::ostringstream ost123; \
+ ost123 << __FILE__ << ":" << __LINE__; \
+ ns_log::BufferedLogger::logger.doLog(logger, \
+ ns_log::Logger::level, \
+ __FILE__, __LINE__, ost123.str(), ##ARGS); \
+ } \
+ } \
+ } while (0)
+
+// Define LOGT calls for using the buffer specifically stating token
+#define LOGBT(level, token, ...) \
+ do { \
+ if (logger.wants(ns_log::Logger::level)) { \
+ if (logger.wants(ns_log::Logger::debug)) { \
+ logger.doLog(ns_log::Logger::level, \
+ __FILE__, __LINE__, __VA_ARGS__); \
+ ns_log::BufferedLogger::logger.trimCache(); \
+ } else { \
+ ns_log::BufferedLogger::logger.doLog(logger, \
+ ns_log::Logger::level, \
+ __FILE__, __LINE__, token, __VA_ARGS__); \
+ } \
+ } \
+ } while (0)
+
+#define LOGB_FLUSH() \
+ ns_log::BufferedLogger::logger.flush()
+
+namespace ns_log {
+
+class BackingBuffer;
+
+class BufferedLogger {
+ BackingBuffer *_backing;
+ BufferedLogger(const BufferedLogger & buf);
+ BufferedLogger & operator = (const BufferedLogger & buf);
+
+public:
+ static BufferedLogger logger;
+
+ BufferedLogger();
+ ~BufferedLogger();
+
+ // Not intended to be used within real applications. Unit tests use these,
+ // to easier be able to test all aspects of the buffer, and be independent
+ // of the default settings for applications
+ void setMaxCacheSize(uint32_t size);
+ void setMaxEntryAge(uint64_t seconds);
+ void setCountFactor(uint64_t factor);
+
+ void doLog(Logger&, Logger::LogLevel level, const char *file, int line,
+ const std::string& token,
+ const char *fmt, ...) __attribute__((format(printf,7,8)));
+
+ /** Empty buffer and write all log entries in it. */
+ void flush();
+
+ /** Gives all current content of log buffer. Useful for debugging. */
+ std::string toString() const;
+
+ /** Set a fake timer to use for log messages. Used in unit testing. */
+ void setTimer(std::unique_ptr<Timer> timer);
+
+ /** Trim the buffer. Removing old messages if wanted. */
+ void trimCache();
+
+};
+
+} // ns_log
+
diff --git a/vespalog/src/vespa/log/component.cpp b/vespalog/src/vespa/log/component.cpp
new file mode 100644
index 00000000000..5582366ef8d
--- /dev/null
+++ b/vespalog/src/vespa/log/component.cpp
@@ -0,0 +1,121 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <cstdio>
+
+#include "log.h"
+LOG_SETUP_INDIRECT(".log.control", "$Id$");
+#undef LOG
+#define LOG LOG_INDIRECT
+
+#include "component.h"
+#include "control-file.h"
+#include "internal.h"
+
+namespace ns_log {
+
+bool
+Component::matches(const char *pattern)
+{
+ // Return true if pattern matches the name of this component,
+ // false otherwise.
+ int pLen = strlen(pattern);
+ bool matched;
+
+ if (strcmp(pattern, "default") == 0) {
+ return true;
+ }
+
+ if (strcmp(pattern, ".") == 0) {
+ return matches("default.");
+ }
+
+ if (pattern[pLen - 1] == '.') {
+ LOG(spam, "Component::matches -- exact match of '%s' vs name '%s'",
+ pattern, _name);
+ matched = (strncmp(pattern, _name, pLen - 1) == 0)
+ && _name[pLen - 1] == ':';
+ } else {
+ LOG(spam, "Component::matches -- prefix match of '%s' vs name '%s'",
+ pattern, _name);
+ matched = strncmp(pattern, _name, pLen) == 0
+ && (_name[pLen] == '.' || _name[pLen] == ':');
+ }
+ LOG(spam, "Component::matches: Pattern '%s' %s match name '%s'",
+ pattern, matched ? "did" : "did not", _name);
+ return matched;
+}
+
+void
+Component::modifyLevels(char *levels)
+{
+ // levels is a comma-separated list of level={on|off} pairs.
+
+ // the levels string can always be converted to a
+ // AND bitmask -- for all levels to be removed
+ // and an OR bitmask -- for all levels to be added
+ char *s = levels;
+
+ LOG(spam, "Will modify levels for '%.*s' according to \"%s\"",
+ (int)strcspn(_name, " :\n"), _name, levels);
+
+ while (s && *s) {
+ char *eq = strchr(s, '=');
+ if (!eq) {
+ throwInvalid("Missing \"=\" in levels string at \"%s\"", s);
+ }
+ *eq = 0;
+ Logger::LogLevel level = Logger::parseLevel(s);
+ if (level == Logger::NUM_LOGLEVELS) {
+ if (strcmp(s, "all") != 0) {
+ throwInvalid("Level name at \"%s\" is not valid", s);
+ }
+ }
+
+ *eq = '=';
+ char *mod = eq + 1;
+ unsigned int newValue;
+
+ if (strcmp(mod, "on") == 0 || strncmp(mod, "on,", 3) == 0) {
+ s = mod + 2;
+ newValue = CHARS_TO_UINT(' ', ' ', 'O', 'N');
+ } else if (strcmp(mod, "off") == 0 || strncmp(mod, "off,", 4) == 0) {
+ s = mod + 3;
+ newValue = CHARS_TO_UINT(' ', 'O', 'F', 'F');
+ } else {
+ throwInvalid("Invalid modification string at \"%s\", expected "
+ "\"on\" or \"off\"", mod);
+ }
+ if (*s == ',') {
+ ++s;
+ }
+
+ if (level != Logger::NUM_LOGLEVELS) {
+ _intLevels[level] = newValue;
+ } else {
+ for (int n = 0; n != Logger::NUM_LOGLEVELS; ++n) {
+ _intLevels[n] = newValue;
+ }
+ }
+ }
+ display();
+}
+
+void
+Component::display()
+{
+ int nlen = strcspn(_name, ":\n ");
+ printf("%-30.*s %.*s\n", nlen, _name,
+ (int)(sizeof(unsigned int) * Logger::NUM_LOGLEVELS), _charLevels);
+}
+
+Component::Component(char *s)
+ : _name(s),
+ _charLevels(ControlFile::alignLevels(strchr(s, ':') + 2)),
+ _intLevels(reinterpret_cast<unsigned int *>(_charLevels))
+{
+ if (_charLevels == reinterpret_cast<char *>(4)) {
+ throwInvalid("Invalid component instantiated");
+ }
+
+}
+
+} // end namesmace ns_log
diff --git a/vespalog/src/vespa/log/component.h b/vespalog/src/vespa/log/component.h
new file mode 100644
index 00000000000..9dd364af456
--- /dev/null
+++ b/vespalog/src/vespa/log/component.h
@@ -0,0 +1,22 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+namespace ns_log {
+
+class Component {
+ Component();
+ Component& operator = (const Component &);
+ Component(const Component &);
+ char *_name;
+ char *_charLevels;
+ unsigned int *_intLevels;
+
+public:
+ bool matches(const char *pattern);
+ void modifyLevels(char *levels);
+ void display();
+ explicit Component(char *);
+};
+
+} // end namespace ns_log
+
diff --git a/vespalog/src/vespa/log/control-file.cpp b/vespalog/src/vespa/log/control-file.cpp
new file mode 100644
index 00000000000..3b9fe2834a9
--- /dev/null
+++ b/vespalog/src/vespa/log/control-file.cpp
@@ -0,0 +1,424 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <sys/types.h>
+#include <cstdlib>
+#include <cstring>
+#include <ctype.h>
+#include <cstdio>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <memory>
+
+#include "log.h"
+LOG_SETUP_INDIRECT(".log.control", "$Id$");
+#undef LOG
+#define LOG LOG_INDIRECT
+
+#include "control-file.h"
+#include "internal.h"
+#include "component.h"
+
+namespace ns_log {
+
+ControlFile::ControlFile(const char *file, Mode mode)
+ : _fileBacking(file,
+ O_NOCTTY | ( (mode == READONLY) ? O_RDONLY
+ : (mode == READWRITE) ? O_RDWR
+ : (O_RDWR | O_CREAT))),
+ _mode(mode),
+ _fileName(strdup(file)),
+ _prefix(0),
+ _mapBase(0),
+ _mappedSize(0),
+ _firstComponent(0)
+{
+ _fileBacking.lock(mode != READONLY);
+ ensureHeader(); // Create the header if it doesn't exist
+ ensureMapping(); // mmap the file
+ _fileBacking.unlock();
+}
+
+ControlFile::~ControlFile()
+{
+ freeMapping();
+ free(_fileName);
+}
+
+void
+ControlFile::ensureHeader()
+{
+ // Make sure the file has a valid header. If it doesn't, or is
+ // empty, truncate it and write a new fresh header.
+ static const char fileHeader[] = "Vespa log control file version 1\n";
+ const int fd = _fileBacking.fd();
+ char buf[sizeof fileHeader];
+
+ int wantsLen = strlen(fileHeader);
+ int len = read(fd, &buf, wantsLen);
+ if (len != wantsLen || memcmp(fileHeader, buf, wantsLen) != 0) {
+ if (len) {
+ ftruncate(fd, 0);
+ }
+ lseek(fd, 0, SEEK_SET);
+ write(fd, fileHeader, strlen(fileHeader));
+
+ char spaces[_maxPrefix + 1];
+ memset(spaces, ' ', sizeof spaces);
+ spaces[sizeof(spaces) - 1] = '\0';
+
+ char buf2[sizeof(spaces) + 100];
+ snprintf(buf2, sizeof buf2, "Prefix: \n%s\n", spaces);
+ write(fd, buf2, strlen(buf2));
+ }
+}
+
+void
+ControlFile::flush()
+{
+ if (_mapBase != NULL) {
+ if (msync(_mapBase, 0, MS_SYNC) != 0) {
+ LOG(warning, "msync of log control file failed: %s",
+ strerror(errno));
+ }
+ }
+}
+
+void
+ControlFile::ensureMapping()
+{
+ // If the file is not mmaped yet, first do a huge anonymous mmap
+ // so that we never have to change the address, then mmap the
+ // start of the file.
+ if (!_mapBase) {
+ int prot = PROT_READ;
+ int flags = MAP_PRIVATE | MAP_ANON;
+ int fd = -1;
+ size_t length = pageAlign(_maxMapSize + 1);
+
+ void *addr = mmap(NULL, length, prot, flags, fd, 0);
+ if (!addr) {
+ throwInvalid("Failed to get anonymous memory for control file: %s",
+ strerror(errno));
+ }
+ _mapBase = static_cast<char *>(addr);
+ extendMapping();
+
+ char *s = strstr(_mapBase, "Prefix: ");
+ if (!s) {
+ throwInvalid("Bad format of mapped file. bleh.");
+ }
+ _prefix = s + strlen("Prefix: ");
+ _firstComponent = _mapBase + _maxPrefix + 25;
+ _firstComponent = strchr(_firstComponent, '\n') + 1;
+ }
+}
+
+void
+ControlFile::freeMapping()
+{
+ // If the file is mmapped, release all resource used
+ if (_mapBase && (munmap(_mapBase, pageAlign(_maxMapSize + 1)) < 0)) {
+ LOG(warning, "munmapping of loglevel settings failed: %s",
+ strerror(errno));
+ }
+ _mapBase = NULL;
+}
+
+unsigned int
+ControlFile::pageAlign(unsigned int len)
+{
+ static const int pageMask = getpagesize() - 1;
+ return (len + pageMask) & ~pageMask;
+}
+
+char *
+ControlFile::alignLevels(char *addr)
+{
+ unsigned long x = reinterpret_cast<unsigned long>(addr);
+ x = (x + 3) & ~3;
+ return reinterpret_cast<char *>(x);
+}
+
+bool
+ControlFile::extendMapping()
+{
+ int fileLen = _fileBacking.size();
+
+ if (fileLen == -1) {
+ _fileBacking.unlock();
+ LOG(error, "Cannot get file size of '%s': %s", _fileName,
+ strerror(errno));
+ return false;
+ }
+
+ if (fileLen >= _maxMapSize) {
+ _fileBacking.unlock();
+ LOG(error, "Log control file is too big at %d bytes (max "
+ "size is %d). Ignoring it for further log components.",
+ fileLen, _maxMapSize - 1);
+ return false;
+ }
+
+ off_t size = pageAlign(fileLen);
+
+ int prot = PROT_READ | (_mode == READONLY ? 0 : PROT_WRITE);
+ int flags = MAP_FIXED | MAP_SHARED;
+ int fd = _fileBacking.fd();
+ if (mmap(_mapBase, size, prot, flags, fd, 0) != _mapBase) {
+ _fileBacking.unlock();
+ _mappedSize = -1;
+ LOG(error, "failed to mmap lock file: %s", strerror(errno));
+ return false;
+ }
+ _mappedSize = size;
+
+ return true;
+}
+
+void
+ControlFile::setPrefix(const char *prefix)
+{
+ if (prefix && !hasPrefix() && _prefix) {
+ char buf[_maxPrefix + 1];
+ sprintf(buf, "%.*s\n", _maxPrefix - 1, prefix);
+ memcpy(_prefix, buf, strlen(buf));
+ msync(_mapBase, pageAlign(1), MS_ASYNC | MS_INVALIDATE);
+ }
+}
+
+
+unsigned int *
+ControlFile::getLevels(const char *name)
+{
+ // these comments are all wrong...
+
+ // See if there already is some info for name. If not,
+ // lock the file, stat it, remap if necessary, scan again,
+ // and if still not there, append to it. Return pointer to mapped area.
+
+ // ### call getComponent() instead,
+ // ### if it does not exist, create it and return the thing
+ // ### get default value from pre-existing levels, or from
+ // ### the default level if nothing exists.
+ // ### (if default level does not exist, create it?)
+
+ _fileBacking.lock(_mode != READONLY);
+ static const char *padSpaces = " "; // 3 spaces
+ char buf[2000];
+ if (strcmp(name, "") == 0) {
+ name = "default";
+ }
+ // Leave space for 3 spaces and a full levels string (200 bytes should
+ // be enough)
+ snprintf(buf, sizeof buf - 200, "\n%s: ", name);
+
+ char *levels = strstr(_mapBase, buf);
+ if (levels) {
+ _fileBacking.unlock();
+ char *addr = levels + strlen(buf);
+ addr = alignLevels(addr);
+ return reinterpret_cast<unsigned int *>(addr);
+ }
+
+ char *inheritLevels = reinterpret_cast<char *>(defaultLevels());
+ const char *chop = strrchr(name, '.');
+ if (chop != NULL) {
+ char shorterName[2000];
+ strncpy(shorterName, name, chop - name);
+ shorterName[chop-name] = '\0';
+ unsigned int *inherit = getLevels(shorterName);
+ if (inherit != NULL) {
+ inheritLevels = reinterpret_cast<char *>(inherit);
+ }
+ }
+
+ // Append whatever is in buf, excluding the initial newline, and
+ // up to 3 more spaces to get the entire file length to be aligned.
+ int fileLength = _fileBacking.size();
+ char *appendedString = buf + 1;
+ int newLength = fileLength + strlen(appendedString);
+ unsigned int padding = static_cast<unsigned int>(-newLength) & 3u;
+ strcat(appendedString, &padSpaces[3 - padding]);
+
+ char *baseAddr = _mapBase + fileLength + strlen(appendedString);
+
+ strncat(appendedString, inheritLevels, Logger::NUM_LOGLEVELS*sizeof(int));
+ strcat(appendedString, "\n");
+
+ int len = strlen(appendedString);
+
+ int fd = _fileBacking.fd();
+ lseek(fd, (off_t)fileLength, SEEK_SET);
+ int wlen = write(_fileBacking.fd(), appendedString, len);
+ if (wlen != len) {
+ _fileBacking.unlock();
+ LOG(error, "Writing to control file '%s' fails (%d/%d bytes): %s",
+ _fileName, wlen, len, strerror(errno));
+ return reinterpret_cast<unsigned int *>(inheritLevels);
+ } else {
+ }
+
+ if (fileLength + wlen > _mappedSize) {
+ if (!extendMapping()) {
+ _fileBacking.unlock(); // just for sure
+ LOG(error, "Failed to extend mapping of '%s', losing runtime "
+ "configurability of component '%s'", _fileName, name);
+ return defaultLevels();
+ }
+ }
+
+ _fileBacking.unlock();
+
+ return reinterpret_cast<unsigned int *>(baseAddr);
+}
+
+
+unsigned int *
+ControlFile::defaultLevels()
+{
+ static unsigned int levels[Logger::NUM_LOGLEVELS + 1];
+ if (levels[0] == 0) {
+ const char *env = getenv("VESPA_LOG_LEVEL");
+ if (!env) {
+ env = "all -debug -spam";
+ }
+ makeLogLevelArray(levels, sizeof levels, env);
+ levels[Logger::NUM_LOGLEVELS] = 0;
+ }
+ return levels;
+}
+
+
+unsigned int
+ControlFile::findOnOffStatus(Logger::LogLevel level, const char *levelsString)
+{
+ const char *name = Logger::levelName(level);
+ if (hasWord(name, levelsString)
+ || (!hasNegWord(name, levelsString)
+ && hasWord("all", levelsString))) {
+ return CHARS_TO_UINT(' ', ' ', 'O', 'N');
+ } else {
+ return CHARS_TO_UINT(' ', 'O', 'F', 'F');
+ }
+}
+
+void
+ControlFile::makeLogLevelArray(unsigned int *levels, unsigned int size,
+ const char *env)
+{
+ int n;
+ for (n = 0; n < Logger::NUM_LOGLEVELS
+ && (n * sizeof levels[0] < size); ++n) {
+ levels[n] = findOnOffStatus(static_cast<Logger::LogLevel>(n), env);
+ }
+}
+
+bool
+ControlFile::hasWord(const char *word, const char *haystack)
+{
+ int len = strlen(word);
+ const char *start = strstr(haystack, word);
+ const char *end = start + len;
+ if (!start)
+ return false;
+
+ return ((start == haystack) || isspace(start[-1]))
+ && (!*end || isspace(*end));
+}
+
+bool
+ControlFile::hasNegWord(const char *word, const char *haystack)
+{
+ int len = strlen(word);
+ const char *start = strstr(haystack, word);
+ const char *end = start + len;
+ if (!start || start == haystack)
+ return false;
+ return start[-1] == '-' && (!*end || isspace(*end));
+}
+
+void
+ControlFile::ensureComponent(const char *pattern)
+{
+ // Make sure at least one entry exists matching a pattern, if not,
+ // create it. Wildcard patterns cannot be created though!
+ ComponentIterator iter(getComponentIterator());
+ bool wasSeen = false;
+ Component *c;
+ while ((c = iter.next()) != NULL) {
+ std::unique_ptr<Component> component(c);
+ if (c->matches(pattern)) {
+ wasSeen = true;
+ break;
+ }
+ }
+ if (!wasSeen) {
+ (void) getLevels(pattern); // Creates it ((### ugly ###))
+ }
+}
+
+bool
+ControlFile::makeName(const char *service, char *buf, int bufLen)
+{
+ static const char *file = getenv("VESPA_LOG_CONTROL_FILE");
+ static const char *dir = getenv("VESPA_LOG_CONTROL_DIR");
+
+ bool result;
+
+ if (file) {
+ // mostly used for manual testing, so allow this even
+ // with empty service name
+ result = (snprintf(buf, bufLen, "%s", file) < bufLen);
+ } else if (dir) {
+ // We can't make control files for empty services here
+ if (!*service || strcmp(service, "-") == 0) {
+ return false;
+ }
+ if (strchr(service, '/') != NULL) {
+ LOG(debug, "service name '%s' should not contain /", service);
+ return false;
+ }
+ int req = snprintf(buf, bufLen, "%s/%s.logcontrol", dir, service);
+ result = (req < bufLen);
+ } else {
+ result = false;
+ }
+ return result;
+}
+
+
+
+ComponentIterator
+ControlFile::getComponentIterator()
+{
+ return ComponentIterator(this);
+}
+
+ComponentIterator::ComponentIterator(const ComponentIterator& ci)
+ : _cf(ci._cf),
+ _next(ci._next)
+{
+}
+
+ComponentIterator::ComponentIterator(ControlFile *cf)
+ : _cf(cf),
+ _next(cf->_firstComponent)
+{
+}
+
+Component *
+ComponentIterator::next()
+{
+ Component *ret = NULL;
+ if (_next && _next[0]) {
+ ret = new Component(_next);
+ _next = strchr(_next, '\n');
+ if (_next) {
+ ++_next;
+ }
+ }
+ return ret;
+}
+
+} // end namespace ns_log
diff --git a/vespalog/src/vespa/log/control-file.h b/vespalog/src/vespa/log/control-file.h
new file mode 100644
index 00000000000..d0fb211e202
--- /dev/null
+++ b/vespalog/src/vespa/log/control-file.h
@@ -0,0 +1,86 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "log.h"
+#include "control-file.h"
+#include "lock.h"
+
+namespace ns_log {
+
+class Component;
+class ComponentIterator;
+
+class ControlFile {
+ friend class ComponentIterator;
+ friend class Component;
+public:
+ enum Mode { READONLY, READWRITE, CREATE };
+private:
+ Lock _fileBacking;
+ enum Mode _mode;
+ char *_fileName;
+ void ensureHeader();
+ bool hasPrefix() { return (_prefix != NULL &&
+ _prefix[0] != '\0' &&
+ _prefix[0] != ' ' &&
+ _prefix[0] != '\n'); }
+ void ensureMapping();
+ void freeMapping();
+ bool extendMapping();
+ static unsigned int pageAlign(unsigned int len);
+
+ char *_prefix;
+ char *_mapBase;
+ int _mappedSize;
+ char *_firstComponent;
+
+ static const int _maxMapSize = 200000;
+ static const int _maxPrefix = 64;
+
+ ControlFile(const ControlFile &);
+ ControlFile& operator = (const ControlFile &);
+
+ static bool hasWord(const char *word, const char *haystack);
+ static bool hasNegWord(const char *word, const char *haystack);
+
+ static void makeLogLevelArray(unsigned int *levels, unsigned int size,
+ const char *env);
+ static char *alignLevels(char *addr);
+ static unsigned int findOnOffStatus(Logger::LogLevel level,
+ const char *levelsString);
+
+public:
+ ComponentIterator getComponentIterator();
+ explicit ControlFile(const char *file, enum Mode);
+ void setPrefix(const char *prefix);
+ ~ControlFile();
+ unsigned int *getLevels(const char *name);
+ void ensureComponent(const char *pattern);
+
+ static unsigned int *defaultLevels();
+
+ // make sure in-memory changes are synchronized to disk
+ void flush();
+
+ // Construct the name of the control file for <serviceName>
+ // returns true if successful, false if not.
+ // Makes the name into <buf> which is of length <bufLen>.
+ static bool makeName(const char *serviceName, char *buf, int bufLen);
+};
+
+class ComponentIterator {
+private:
+ ControlFile *_cf;
+ char *_next;
+
+ ComponentIterator& operator = (const ComponentIterator &);
+
+public:
+ ComponentIterator(const ComponentIterator &ci);
+ ComponentIterator(ControlFile *cf);
+ ~ComponentIterator() {}
+ Component *next();
+};
+
+} // end namespace ns_log
+
diff --git a/vespalog/src/vespa/log/create-multiarg-file.pl b/vespalog/src/vespa/log/create-multiarg-file.pl
new file mode 100755
index 00000000000..3a937e3e0d1
--- /dev/null
+++ b/vespalog/src/vespa/log/create-multiarg-file.pl
@@ -0,0 +1,104 @@
+#!/usr/bin/env perl
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+# This script generates multi-argument functions for sending some events.
+
+require 5.006_001;
+use warnings;
+use strict;
+
+die "Usage: $0 <event-name> <event-version> <number of functions> <name>...\n"
+ unless $#ARGV > 2;
+
+my $event = $ARGV[0];
+my $event_version = $ARGV[1];
+my $noof_fns = $ARGV[2];
+
+my @variables = @ARGV;
+splice(@variables, 0, 3);
+
+
+open(H, ">event-${event}-multiarg.h")
+ or die "Cannot open event-${event}-multiarg.h: $!\n";
+open(CPP, ">event-${event}-multiarg.cpp")
+ or die "Cannot open event-${event}-multiarg.cpp: $!\n";
+
+my $called_as = "$0 $ARGV[0] $ARGV[1] $ARGV[2]";
+foreach my $x (@variables) {
+ $called_as .= " \"$x\"";
+}
+
+print H << "EOF";
+// This file was generated like this:
+// $called_as
+// Don not modify this file manually!
+EOF
+
+print CPP << "EOF";
+// This file was generated like this:
+// $called_as
+// Do not modify this file manually!
+EOF
+
+
+my $i;
+
+for ($i = 2; $i <= $noof_fns; $i++) {
+ print H "void doEvent\u${event}(";
+ print CPP "void\nLogger::doEvent\u${event}(";
+ my $n;
+ for ($n = 1; $n <= $i; $n++) {
+ print H "," unless $n == 1;
+ print H "\n\t";
+ print CPP "," unless $n == 1;
+ print CPP "\n\t";
+ my $first = 1;
+ foreach my $var (@variables) {
+ print H ", " unless $first;
+ print H "${var}${n}";
+ print CPP ", " unless $first;
+ print CPP "${var}${n}";
+ $first = 0;
+ }
+ }
+ print H ");\n\n";
+ print CPP ")\n{\n";
+
+ print CPP "\tdoLog(event, \"\", 0, \"${event}/${event_version}\"";
+ for ($n = 1; $n <= $i; $n++) {
+ foreach my $var (@variables) {
+ my $type;
+ my $quot = "";
+ if ($var =~ m=double =) {
+ $type = "%lf";
+ } elsif ($var =~ m=int =) {
+ $type = "%d";
+ } elsif ($var =~ m=const char ?\*=) {
+ $type = "%s";
+ $quot = "\\\"";
+ } else {
+ die "Don't know printf format for variable $var\n";
+ }
+ my $name = $var;
+ $name =~ s=.*[ *&]==;
+ print CPP "\n\t\t\" ${name}=${quot}${type}${quot}\"";
+ }
+ }
+ print CPP ",";
+ for ($n = 1; $n <= $i; $n++) {
+ my $first = 1;
+ print CPP "," unless $n == 1;
+ print CPP "\n\t\t";
+ foreach my $var (@variables) {
+ print CPP ", " unless $first;
+ $first = 0;
+ my $name = $var;
+ $name =~ s=.*[ *&]==;
+ print CPP "${name}${n}";
+ }
+ }
+
+ print CPP ");\n";
+
+ print CPP "}\n\n";
+}
diff --git a/vespalog/src/vespa/log/event-count-multiarg.cpp b/vespalog/src/vespa/log/event-count-multiarg.cpp
new file mode 100644
index 00000000000..9a8d176bcf3
--- /dev/null
+++ b/vespalog/src/vespa/log/event-count-multiarg.cpp
@@ -0,0 +1,274 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// This file was generated like this:
+// ./create-multiarg-file.pl count 2 10 "const char *name" "double value"
+// Do not modify this file manually!
+void
+Logger::doEventCount(
+ const char *name1, double value1,
+ const char *name2, double value2)
+{
+ doLog(event, "", 0, "count/2"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf",
+ name1, value1,
+ name2, value2);
+}
+
+void
+Logger::doEventCount(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3)
+{
+ doLog(event, "", 0, "count/2"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf",
+ name1, value1,
+ name2, value2,
+ name3, value3);
+}
+
+void
+Logger::doEventCount(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4)
+{
+ doLog(event, "", 0, "count/2"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf",
+ name1, value1,
+ name2, value2,
+ name3, value3,
+ name4, value4);
+}
+
+void
+Logger::doEventCount(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5)
+{
+ doLog(event, "", 0, "count/2"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf",
+ name1, value1,
+ name2, value2,
+ name3, value3,
+ name4, value4,
+ name5, value5);
+}
+
+void
+Logger::doEventCount(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6)
+{
+ doLog(event, "", 0, "count/2"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf",
+ name1, value1,
+ name2, value2,
+ name3, value3,
+ name4, value4,
+ name5, value5,
+ name6, value6);
+}
+
+void
+Logger::doEventCount(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6,
+ const char *name7, double value7)
+{
+ doLog(event, "", 0, "count/2"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf",
+ name1, value1,
+ name2, value2,
+ name3, value3,
+ name4, value4,
+ name5, value5,
+ name6, value6,
+ name7, value7);
+}
+
+void
+Logger::doEventCount(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6,
+ const char *name7, double value7,
+ const char *name8, double value8)
+{
+ doLog(event, "", 0, "count/2"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf",
+ name1, value1,
+ name2, value2,
+ name3, value3,
+ name4, value4,
+ name5, value5,
+ name6, value6,
+ name7, value7,
+ name8, value8);
+}
+
+void
+Logger::doEventCount(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6,
+ const char *name7, double value7,
+ const char *name8, double value8,
+ const char *name9, double value9)
+{
+ doLog(event, "", 0, "count/2"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf",
+ name1, value1,
+ name2, value2,
+ name3, value3,
+ name4, value4,
+ name5, value5,
+ name6, value6,
+ name7, value7,
+ name8, value8,
+ name9, value9);
+}
+
+void
+Logger::doEventCount(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6,
+ const char *name7, double value7,
+ const char *name8, double value8,
+ const char *name9, double value9,
+ const char *name10, double value10)
+{
+ doLog(event, "", 0, "count/2"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf",
+ name1, value1,
+ name2, value2,
+ name3, value3,
+ name4, value4,
+ name5, value5,
+ name6, value6,
+ name7, value7,
+ name8, value8,
+ name9, value9,
+ name10, value10);
+}
+
diff --git a/vespalog/src/vespa/log/event-count-multiarg.h b/vespalog/src/vespa/log/event-count-multiarg.h
new file mode 100644
index 00000000000..d2c7f60ec4a
--- /dev/null
+++ b/vespalog/src/vespa/log/event-count-multiarg.h
@@ -0,0 +1,76 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// This file was generated like this:
+// ./create-multiarg-file.pl count 2 10 "const char *name" "double value"
+// Don not modify this file manually!
+void doEventCount(
+ const char *name1, double value1,
+ const char *name2, double value2);
+
+void doEventCount(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3);
+
+void doEventCount(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4);
+
+void doEventCount(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5);
+
+void doEventCount(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6);
+
+void doEventCount(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6,
+ const char *name7, double value7);
+
+void doEventCount(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6,
+ const char *name7, double value7,
+ const char *name8, double value8);
+
+void doEventCount(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6,
+ const char *name7, double value7,
+ const char *name8, double value8,
+ const char *name9, double value9);
+
+void doEventCount(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6,
+ const char *name7, double value7,
+ const char *name8, double value8,
+ const char *name9, double value9,
+ const char *name10, double value10);
+
diff --git a/vespalog/src/vespa/log/event-value-multiarg.cpp b/vespalog/src/vespa/log/event-value-multiarg.cpp
new file mode 100644
index 00000000000..c676ba44efd
--- /dev/null
+++ b/vespalog/src/vespa/log/event-value-multiarg.cpp
@@ -0,0 +1,274 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// This file was generated like this:
+// ./create-multiarg-file.pl value 2 10 "const char *name" "double value"
+// Do not modify this file manually!
+void
+Logger::doEventValue(
+ const char *name1, double value1,
+ const char *name2, double value2)
+{
+ doLog(event, "", 0, "value/2"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf",
+ name1, value1,
+ name2, value2);
+}
+
+void
+Logger::doEventValue(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3)
+{
+ doLog(event, "", 0, "value/2"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf",
+ name1, value1,
+ name2, value2,
+ name3, value3);
+}
+
+void
+Logger::doEventValue(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4)
+{
+ doLog(event, "", 0, "value/2"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf",
+ name1, value1,
+ name2, value2,
+ name3, value3,
+ name4, value4);
+}
+
+void
+Logger::doEventValue(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5)
+{
+ doLog(event, "", 0, "value/2"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf",
+ name1, value1,
+ name2, value2,
+ name3, value3,
+ name4, value4,
+ name5, value5);
+}
+
+void
+Logger::doEventValue(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6)
+{
+ doLog(event, "", 0, "value/2"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf",
+ name1, value1,
+ name2, value2,
+ name3, value3,
+ name4, value4,
+ name5, value5,
+ name6, value6);
+}
+
+void
+Logger::doEventValue(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6,
+ const char *name7, double value7)
+{
+ doLog(event, "", 0, "value/2"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf",
+ name1, value1,
+ name2, value2,
+ name3, value3,
+ name4, value4,
+ name5, value5,
+ name6, value6,
+ name7, value7);
+}
+
+void
+Logger::doEventValue(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6,
+ const char *name7, double value7,
+ const char *name8, double value8)
+{
+ doLog(event, "", 0, "value/2"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf",
+ name1, value1,
+ name2, value2,
+ name3, value3,
+ name4, value4,
+ name5, value5,
+ name6, value6,
+ name7, value7,
+ name8, value8);
+}
+
+void
+Logger::doEventValue(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6,
+ const char *name7, double value7,
+ const char *name8, double value8,
+ const char *name9, double value9)
+{
+ doLog(event, "", 0, "value/2"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf",
+ name1, value1,
+ name2, value2,
+ name3, value3,
+ name4, value4,
+ name5, value5,
+ name6, value6,
+ name7, value7,
+ name8, value8,
+ name9, value9);
+}
+
+void
+Logger::doEventValue(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6,
+ const char *name7, double value7,
+ const char *name8, double value8,
+ const char *name9, double value9,
+ const char *name10, double value10)
+{
+ doLog(event, "", 0, "value/2"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf"
+ " name=\"%s\""
+ " value=%lf",
+ name1, value1,
+ name2, value2,
+ name3, value3,
+ name4, value4,
+ name5, value5,
+ name6, value6,
+ name7, value7,
+ name8, value8,
+ name9, value9,
+ name10, value10);
+}
+
diff --git a/vespalog/src/vespa/log/event-value-multiarg.h b/vespalog/src/vespa/log/event-value-multiarg.h
new file mode 100644
index 00000000000..baf68683347
--- /dev/null
+++ b/vespalog/src/vespa/log/event-value-multiarg.h
@@ -0,0 +1,76 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// This file was generated like this:
+// ./create-multiarg-file.pl value 2 10 "const char *name" "double value"
+// Don not modify this file manually!
+void doEventValue(
+ const char *name1, double value1,
+ const char *name2, double value2);
+
+void doEventValue(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3);
+
+void doEventValue(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4);
+
+void doEventValue(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5);
+
+void doEventValue(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6);
+
+void doEventValue(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6,
+ const char *name7, double value7);
+
+void doEventValue(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6,
+ const char *name7, double value7,
+ const char *name8, double value8);
+
+void doEventValue(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6,
+ const char *name7, double value7,
+ const char *name8, double value8,
+ const char *name9, double value9);
+
+void doEventValue(
+ const char *name1, double value1,
+ const char *name2, double value2,
+ const char *name3, double value3,
+ const char *name4, double value4,
+ const char *name5, double value5,
+ const char *name6, double value6,
+ const char *name7, double value7,
+ const char *name8, double value8,
+ const char *name9, double value9,
+ const char *name10, double value10);
+
diff --git a/vespalog/src/vespa/log/internal.cpp b/vespalog/src/vespa/log/internal.cpp
new file mode 100644
index 00000000000..3ff4c9cbec1
--- /dev/null
+++ b/vespalog/src/vespa/log/internal.cpp
@@ -0,0 +1,23 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <sys/types.h>
+#include <stdarg.h>
+#include <cstdio>
+
+#include "internal.h"
+
+namespace ns_log {
+
+void
+throwInvalid(const char *format, ...)
+{
+ char buf[4000];
+ va_list args;
+
+ va_start(args, format);
+ vsnprintf(buf, sizeof buf, format, args);
+ va_end(args);
+
+ throw InvalidLogException(buf);
+}
+
+} // end namespace ns_log
diff --git a/vespalog/src/vespa/log/internal.h b/vespalog/src/vespa/log/internal.h
new file mode 100644
index 00000000000..5b1e4b2deaf
--- /dev/null
+++ b/vespalog/src/vespa/log/internal.h
@@ -0,0 +1,30 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <cstring>
+#include <cstdlib>
+
+#if !__GNUC__ && !defined(__attribute__)
+#define __attribute__(x)
+#endif
+
+namespace ns_log {
+
+void throwInvalid(const char *fmt, ...)
+ __attribute__((format(printf, 1, 2))) __attribute__((noreturn));
+
+class InvalidLogException {
+private:
+ char *_what;
+ InvalidLogException& operator = (const InvalidLogException&);
+
+public:
+ InvalidLogException(const InvalidLogException &x) :
+ _what(strdup(x._what)) {}
+ InvalidLogException(const char *s) : _what(strdup(s)) {}
+ ~InvalidLogException() { free(_what); }
+ const char *what() const { return _what; }
+};
+
+} // end namespace ns_log
+
diff --git a/vespalog/src/vespa/log/llparser.cpp b/vespalog/src/vespa/log/llparser.cpp
new file mode 100644
index 00000000000..88d181e2b2c
--- /dev/null
+++ b/vespalog/src/vespa/log/llparser.cpp
@@ -0,0 +1,410 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+#include "log-target.h"
+#include "llparser.h"
+#include "internal.h"
+
+namespace ns_log {
+
+static const char *defcomponent = "logger";
+static const char *defservice = "-";
+static char defhostname[256] = "localhost";
+
+LLParser::LLParser()
+ : _defHostname(defhostname),
+ _defService(defservice),
+ _defComponent(defcomponent),
+ _defLevel(Logger::info),
+ _target(LogTarget::defaultTarget()),
+ _rejectFilter(RejectFilter::createDefaultFilter())
+{
+ const char *envServ = getenv("VESPA_SERVICE_NAME");
+ if (envServ != NULL) {
+ _defService = envServ;
+ }
+ char *name = getenv("VESPA_LOG_TARGET");
+ if (name) {
+ LogTarget *target;
+ try {
+ target = LogTarget::makeTarget(name);
+ } catch (InvalidLogException& ex) {
+ // If we catch an exception, use default target
+ target = LogTarget::defaultTarget();
+ }
+ delete _target;
+ _target = target;
+ }
+ if (gethostname(defhostname, 256) == 0) {
+ _defHostname = defhostname;
+ }
+ snprintf(_defPid, 10, "%d", (int)getpid());
+}
+
+LLParser::~LLParser()
+{
+ delete _target;
+}
+
+const char LLParser::_hexdigit[17] = "0123456789abcdef";
+
+void
+LLParser::sendMessage(const char *totalMessage)
+{
+ _target->write(totalMessage, strlen(totalMessage));
+}
+
+static inline bool validLevel(Logger::LogLevel level)
+{
+ return (level >= 0 && level < Logger::NUM_LOGLEVELS);
+}
+
+static bool isValidPid(const char *field)
+{
+ char *eol;
+ long pidnum = strtol(field, &eol, 10);
+ if (pidnum > 0 && pidnum < 18*1000*1000) {
+ char endbyte = *eol;
+ if (endbyte == '\0' || endbyte == '\t' || endbyte == '/') {
+ return true;
+ }
+ return false;
+ }
+ // too big to be a valid pid, maybe a timestamp?
+ if (pidnum >= 18*1000*1000)
+ return false;
+
+ // stupid java logging...
+ if (field[0] == '-' && field[1] == '\0') {
+ return true;
+ }
+ if (field[0] == '-' && field[1] == '/') {
+ if (field[2] == '-' && field[3] == '\0') {
+ return true;
+ }
+ // thread id
+ pidnum = strtol(field+2, &eol, 10);
+ if (eol > field+2) {
+ char endbyte = *eol;
+ if (endbyte == '\0' || endbyte =='\t') {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void
+LLParser::doInput(char *line)
+{
+ double logTime = 0.0;
+ bool timefield = false;
+ int pidfield = 0;
+ char *eod = NULL;
+
+ char *first = line;
+ char *tab = strchr(first, '\t'); // time?
+ char empty[1] = "";
+
+ if (tab) {
+ *tab = '\0';
+ logTime = strtod(first, &eod);
+ if (eod == tab && logTime > 900*1000*1000) {
+ timefield = true;
+ } else if (isValidPid(first)) {
+ pidfield = 1;
+ }
+ char *second = tab+1;
+ tab = strchr(second, '\t'); // host?
+ if (tab) {
+ *tab = '\0';
+ if (pidfield == 0) {
+ if (isValidPid(second)) {
+ pidfield = 2;
+ }
+ }
+ char *third = tab+1;
+ tab = strchr(third, '\t'); // pid?
+ if (tab) {
+ *tab = '\0';
+ if (pidfield == 0) {
+ if (isValidPid(third)) {
+ pidfield = 3;
+ }
+ }
+ char *fourth = tab+1;
+ tab = strchr(fourth, '\t'); // service ?
+ if (tab) {
+ *tab = '\0';
+ char *fifth = tab+1;
+ tab = strchr(fifth, '\t'); // component ?
+ if (tab) {
+ *tab = '\0';
+ char *sixth = tab+1;
+ tab = strchr(sixth, '\t'); // level?
+ if (tab && timefield) {
+ *tab = '\0';
+ char *seventh = tab+1; // message
+ Logger::LogLevel l = Logger::parseLevel(sixth);
+ if (validLevel(l)) {
+ makeMessage(first, second, third, fourth,
+ fifth, l, seventh);
+ return;
+ }
+ // pretend 6 fields
+ *tab = '\t';
+ }
+ Logger::LogLevel l = Logger::parseLevel(fifth);
+ if (validLevel(l)) {
+ // missing one field - which one?
+ if (timefield && pidfield == 2) {
+ // missing host
+ makeMessage(first, empty, second, third,
+ fourth, l, sixth);
+ return;
+ }
+ if (timefield && pidfield == 3) {
+ // missing service
+ makeMessage(first, second, third, empty,
+ fourth, l, sixth);
+ return;
+ }
+ if (!timefield && pidfield == 2) {
+ // missing time
+ makeMessage(empty, first, second, third,
+ fourth, l, sixth);
+ return;
+ }
+ if (timefield && pidfield == 0) {
+ // missing pid
+ makeMessage(first, second, empty, third,
+ fourth, l, sixth);
+ return;
+ }
+ // fprintf(stderr, "bad 6-field\n");
+ // no idea what is going on
+ }
+ // pretend 5 fields
+ tab = sixth-1;
+ *tab = '\t';
+ }
+ Logger::LogLevel l = Logger::parseLevel(fourth);
+ if (validLevel(l)) {
+ // missing two fields
+ if (!timefield && pidfield == 0) {
+ // missing time and pid
+ makeMessage(empty, first, empty, second,
+ third, l, fifth);
+ return;
+ }
+ if (!timefield && pidfield == 1) {
+ // missing time and host
+ makeMessage(empty, empty, first, second,
+ third, l, fifth);
+ return;
+ }
+ if (!timefield && pidfield == 2) {
+ // missing time and service
+ makeMessage(empty, first, second, empty,
+ third, l, fifth);
+ return;
+ }
+ if (timefield && pidfield == 2) {
+ // missing host and service
+ makeMessage(first, empty, second, empty,
+ third, l, fifth);
+ return;
+ }
+ if (timefield && pidfield == 3) {
+ // missing service and component
+ makeMessage(first, second, third, empty,
+ empty, l, fifth);
+ return;
+ }
+ if (timefield && pidfield == 0) {
+ // missing pid and (hostname or service)
+ if (_defService == second) {
+ // it's the service, assume missing hostname
+ makeMessage(first, empty, empty, second,
+ third, l, fifth);
+ return;
+ }
+ makeMessage(first, second, empty, empty,
+ third, l, fifth);
+ return;
+ }
+ // fprintf(stderr, "bad 5-field\n");
+ // no idea what is going on
+ }
+ // pretend 4 fields
+ tab = fifth-1;
+ *tab = '\t';
+ }
+ Logger::LogLevel l = Logger::parseLevel(third);
+ if (validLevel(l)) {
+ // three fields + message
+ if (timefield && pidfield == 2) {
+ // missing host, service, component
+ makeMessage(first, empty, second, empty,
+ empty, l, fourth);
+ return;
+ }
+ if (timefield && pidfield == 0) {
+ // missing host, pid, service
+ makeMessage(first, empty, empty, empty,
+ second, l, fourth);
+ return;
+ }
+ if (!timefield && pidfield == 1) {
+ // missing time, host, service
+ makeMessage(empty, empty, first, empty,
+ second, l, fourth);
+ return;
+ }
+ if (!timefield && pidfield == 0) {
+ // missing time, pid, and (host or service)
+ if (_defService == first) {
+ // it's the service, assume missing hostname
+ makeMessage(empty, empty, empty, first,
+ second, l, fourth);
+ return;
+ }
+ // missing service
+ makeMessage(empty, first, empty, empty,
+ second, l, fourth);
+ return;
+ }
+ // fprintf(stderr, "bad 4-field\n");
+ // no idea what is going on
+
+ }
+ // pretend 3 fields
+ tab = fourth-1;
+ *tab = '\t';
+ }
+ Logger::LogLevel l = Logger::parseLevel(second);
+ if (validLevel(l)) {
+ if (timefield) {
+ // time, level, message
+ makeMessage(first, empty, empty, empty,
+ empty, l, third);
+ return;
+ }
+ if (pidfield) {
+ // pid, level, message
+ makeMessage(empty, empty, first, empty,
+ empty, l, third);
+ return;
+ }
+ // component, level, message
+ makeMessage(empty, empty, empty, empty,
+ first, l, third);
+ return;
+ }
+ // pretend 2 fields
+ tab = third-1;
+ *tab = '\t';
+ }
+ Logger::LogLevel l = Logger::parseLevel(first);
+
+ if (validLevel(l)) {
+ makeMessage(empty, empty, empty, empty,
+ empty, l, second);
+ return;
+ }
+ // pretend 1 field
+ tab = second-1;
+ *tab = '\t';
+ }
+ makeMessage(empty, empty, empty, empty,
+ empty, _defLevel, line);
+}
+
+
+static char escaped[16000];
+static char totalMessage[17000];
+
+void
+LLParser::makeMessage(const char *tmf, const char *hsf, const char *pdf,
+ const char *svf, const char *cmf, Logger::LogLevel level,
+ char *src)
+{
+ char tmbuffer[24];
+ if (tmf[0] == '\0') {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ snprintf(tmbuffer, 24, "%u.%u",
+ static_cast<unsigned int>(tv.tv_sec),
+ static_cast<unsigned int>(tv.tv_usec));
+ tmf = tmbuffer;
+ }
+
+ if (hsf[0] == '\0') hsf = _defHostname.c_str();
+
+ if (pdf[0] == '\0') pdf = _defPid;
+
+ if (svf[0] == '\0') svf = _defService.c_str();
+
+ if (cmf[0] == '\0') cmf = _defComponent.c_str();
+
+ char *dst = escaped;
+ unsigned char c;
+
+ int len = strlen(src);
+ if (len > 3999) {
+ src[3997] = '.';
+ src[3998] = '.';
+ src[3999] = '.';
+ src[4000] = '\0';
+ }
+ do {
+ c = static_cast<unsigned char>(*src++);
+ if ((c == '\\' && src[0] == 't')
+ || (c >= 32 && c < '\\')
+ || (c > '\\' && c < 128)
+ || c == 0
+ || c > 160)
+ {
+ *dst++ = static_cast<char>(c);
+ } else {
+ *dst++ = '\\';
+ if (c == '\\') {
+ *dst++ = '\\';
+ } else if (c == '\r') {
+ *dst++ = 'r';
+ } else if (c == '\n') {
+ *dst++ = 'n';
+ } else if (c == '\t') {
+ *dst++ = 't';
+ } else {
+ *dst++ = 'x';
+ *dst++ = _hexdigit[c >> 4];
+ *dst++ = _hexdigit[c & 0xf];
+ }
+ }
+ } while (c);
+
+ snprintf(totalMessage, sizeof totalMessage,
+ "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
+ tmf, hsf, pdf, svf, cmf,
+ Logger::logLevelNames[level], escaped);
+
+ if (_rejectFilter.shouldReject(level, escaped))
+ return;
+ sendMessage(totalMessage);
+}
+
+
+void
+LLParser::setPid(int p)
+{
+ snprintf(_defPid, 10, "%d", p);
+}
+
+} // namespace
diff --git a/vespalog/src/vespa/log/llparser.h b/vespalog/src/vespa/log/llparser.h
new file mode 100644
index 00000000000..e8d35bc9777
--- /dev/null
+++ b/vespalog/src/vespa/log/llparser.h
@@ -0,0 +1,44 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/log/log.h>
+#include <vespa/log/reject-filter.h>
+#include <string>
+
+namespace ns_log {
+
+class LogTarget;
+class LLParser
+{
+private:
+ static const char _hexdigit[17];
+
+ char _defPid[10];
+ std::string _defHostname;
+ std::string _defService;
+ std::string _defComponent;
+ Logger::LogLevel _defLevel;
+
+ LogTarget *_target;
+ RejectFilter _rejectFilter;
+ void makeMessage(Logger::LogLevel l, char *msg);
+ void makeMessage(const char *tmf, const char *hsf, const char *pdf,
+ const char *svf, const char *cmf, Logger::LogLevel l,
+ char *msg);
+ void sendMessage(const char *msg);
+
+ LLParser(const LLParser &);
+ LLParser& operator = (const LLParser &);
+
+public:
+ void doInput(char *line);
+ LLParser();
+ ~LLParser();
+ void setService(const char *s) { _defService = s; }
+ void setComponent(const char *c) { _defComponent = c; }
+ void setPid(int p);
+ void setDefaultLevel(Logger::LogLevel level) { _defLevel = level; }
+};
+
+} // namespace
+
diff --git a/vespalog/src/vespa/log/lock.cpp b/vespalog/src/vespa/log/lock.cpp
new file mode 100644
index 00000000000..be83a745fd7
--- /dev/null
+++ b/vespalog/src/vespa/log/lock.cpp
@@ -0,0 +1,89 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/file.h>
+#include <cstring>
+#include <unistd.h>
+#include <limits.h>
+#include <cstdio>
+
+#include "log.h"
+LOG_SETUP_INDIRECT(".log.lock", "$Id$");
+#include "lock.h"
+#include "internal.h"
+
+#undef LOG
+#define LOG LOG_INDIRECT
+
+namespace ns_log {
+
+Lock::Lock(int arg_fd)
+ : _fd(dup(arg_fd)),
+ _isLocked(false)
+{
+ if (_fd == -1) {
+ throwInvalid("Cannot duplicate fd %d for lock: %s", arg_fd,
+ strerror(errno));
+ }
+}
+
+
+Lock::Lock(const char *filename, int mode)
+ : _fd(open(filename, mode, 0666)),
+ _isLocked(false)
+{
+ if (_fd == -1) {
+ if (mode & O_CREAT) {
+ unlink(filename);
+ _fd = open(filename, mode, 0666);
+ }
+ if (_fd == -1) {
+ throwInvalid("Cannot open lockfile '%s': %s", filename,
+ strerror(errno));
+ }
+ }
+ fcntl(_fd, F_SETFD, FD_CLOEXEC);
+}
+
+void
+Lock::lock(bool exclusive)
+{
+ // Early escape to avoid double locking which would of course block forever
+ if (_isLocked) return;
+
+ if (flock(_fd, exclusive ? LOCK_EX : LOCK_SH) == -1) {
+ int err = errno;
+ close(_fd);
+ LOG(error, "Cannot lock fd %d: %s", _fd, strerror(err));
+ throwInvalid("Cannot lock fd %d: %s", _fd, strerror(err));
+ }
+ _isLocked = true;
+}
+
+void
+Lock::unlock() {
+ if (_isLocked) {
+ flock(_fd, LOCK_UN);
+ _isLocked = false;
+ }
+}
+
+int Lock::size()
+{
+ struct stat st;
+ int err = fstat(_fd, &st);
+ if (err == -1) {
+ return -1;
+ }
+ return st.st_size;
+}
+
+Lock::~Lock()
+{
+ unlock();
+ close(_fd);
+}
+
+} // end namespace ns_log
diff --git a/vespalog/src/vespa/log/lock.h b/vespalog/src/vespa/log/lock.h
new file mode 100644
index 00000000000..48af938fc53
--- /dev/null
+++ b/vespalog/src/vespa/log/lock.h
@@ -0,0 +1,32 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <sys/types.h>
+#include <fcntl.h>
+#include <limits.h>
+
+namespace ns_log {
+
+class Lock {
+private:
+ Lock();
+ Lock(const Lock &);
+ Lock& operator =(const Lock &);
+ int _fd;
+ bool _isLocked;
+
+
+public:
+ int fd() const { return _fd; }
+ int size();
+ explicit Lock(const char *filename, int mode = O_RDONLY | O_NOCTTY);
+ explicit Lock(int fd);
+ ~Lock();
+ void lock(bool isExclusive);
+ void unlock();
+};
+
+
+} // end namespace ns_log
+
+
diff --git a/vespalog/src/vespa/log/log-assert.cpp b/vespalog/src/vespa/log/log-assert.cpp
new file mode 100644
index 00000000000..2c4106816a1
--- /dev/null
+++ b/vespalog/src/vespa/log/log-assert.cpp
@@ -0,0 +1,37 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <sys/types.h>
+#include <cstring>
+#include <cstdlib>
+#include <cstdio>
+#include <unistd.h>
+
+#include "log.h"
+LOG_SETUP("");
+
+namespace ns_log {
+
+void log_assert_fail(const char *assertion,
+ const char *file,
+ uint32_t line)
+{
+ LOG(error, "%s:%d: Failed assertion: '%s'",
+ file, line, assertion);
+ fprintf(stderr, "%s:%d: Failed assertion: '%s'\n",
+ file, line, assertion);
+ abort();
+}
+
+
+void log_abort(const char *message,
+ const char *file,
+ unsigned int line)
+{
+ LOG(error, "%s:%d: Abort called. Reason: %s",
+ file, line, message);
+ fprintf(stderr, "%s:%d: Abort called. Reason: %s\n",
+ file, line, message);
+ abort();
+}
+
+} // end namespace ns_log
diff --git a/vespalog/src/vespa/log/log-target-fd.cpp b/vespalog/src/vespa/log/log-target-fd.cpp
new file mode 100644
index 00000000000..ac28293d3bc
--- /dev/null
+++ b/vespalog/src/vespa/log/log-target-fd.cpp
@@ -0,0 +1,42 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <sys/types.h>
+#include <unistd.h>
+#include <cstring>
+#include <cstdlib>
+
+#include "log.h"
+LOG_SETUP(".log");
+#include "log-target-fd.h"
+#include "internal.h"
+
+namespace ns_log {
+
+LogTargetFd::LogTargetFd(const char *target)
+ : LogTarget(target),
+ _fd(-1), _istty(false)
+{
+ if (strncmp(target, "fd:", 3) != 0) {
+ throwInvalid("Bad target for LogTargetFd: '%s'", target);
+ }
+ _fd = strtol(target + 3, NULL, 0);
+ if (isatty(_fd) == 1) {
+ _istty = true;
+ }
+}
+
+LogTargetFd::~LogTargetFd()
+{
+ // Must not close _fd, we did not open it!
+}
+
+
+// When writing to file descriptors, there is really not much to do.
+// No log rotation is supported (at least not directly).
+int
+LogTargetFd::write(const char *buf, int bufLen)
+{
+ return ::write(_fd, buf, bufLen);
+}
+
+} // end namespace ns_log
+
diff --git a/vespalog/src/vespa/log/log-target-fd.h b/vespalog/src/vespa/log/log-target-fd.h
new file mode 100644
index 00000000000..f75ff9b44e1
--- /dev/null
+++ b/vespalog/src/vespa/log/log-target-fd.h
@@ -0,0 +1,25 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "log-target.h"
+
+namespace ns_log {
+
+class LogTargetFd : public LogTarget {
+private:
+ int _fd;
+ bool _istty;
+ LogTargetFd();
+ LogTargetFd(const LogTargetFd&);
+ LogTargetFd& operator = (const LogTargetFd);
+
+public:
+ explicit LogTargetFd(const char *target);
+ virtual int write(const char *buf, int len);
+ virtual ~LogTargetFd();
+ virtual bool makeHumanReadable() const { return _istty; }
+};
+
+
+} // end namespace log
+
diff --git a/vespalog/src/vespa/log/log-target-file.cpp b/vespalog/src/vespa/log/log-target-file.cpp
new file mode 100644
index 00000000000..07b638d7f84
--- /dev/null
+++ b/vespalog/src/vespa/log/log-target-file.cpp
@@ -0,0 +1,72 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <sys/types.h>
+#include <unistd.h>
+#include <cstring>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <cstdlib>
+#include <errno.h>
+#include <cstdio>
+
+#include "log.h"
+LOG_SETUP(".log");
+LOG_RCSID("$Id$");
+#include "log-target-file.h"
+#include "internal.h"
+
+namespace ns_log {
+
+#ifndef O_LARGEFILE
+#define O_LARGEFILE 0
+#endif
+
+LogTargetFile::LogTargetFile(const char *target)
+ : LogTarget(target),
+ _failstate(FS_OK)
+{
+ memset(_fname, 0, sizeof(_fname));
+ const char *fname = target + strlen("file:");
+ assert(strlen(fname) < sizeof(_fname));
+ // NOTE: This function cannot LOG()
+ if (strncmp(target, "file:", strlen("file:")) != 0) {
+ throwInvalid("Illegal log target '%s'", target);
+ }
+ strcpy(_fname, fname);
+ int fd = open(_fname, O_WRONLY | O_CREAT | O_APPEND | O_NOCTTY | O_LARGEFILE,
+ 0666);
+ if (fd == -1) {
+ throwInvalid("Cannot open log target file '%s': %s",
+ target + strlen("file:"), strerror(errno));
+ }
+ close(fd);
+}
+
+LogTargetFile::~LogTargetFile()
+{
+}
+
+// Here we must support log rotation. We do this by reopening the filename.
+int
+LogTargetFile::write(const char *buf, int bufLen)
+{
+ int fd = open(_fname,
+ O_WRONLY | O_CREAT | O_APPEND | O_NOCTTY | O_LARGEFILE,
+ 0666);
+ if (fd < 0) {
+ if (_failstate == FS_OK) {
+ _failstate = FS_FAILED;
+ LOG(warning, "cannot create %s: %s", _fname, strerror(errno));
+ LOG(warning, "Log file failed, falling back to stderr logging");
+ }
+ fd = dup(STDERR_FILENO);
+ } else {
+ if (_failstate != FS_OK) {
+ _failstate = FS_OK;
+ }
+ }
+ int retVal = ::write(fd, buf, bufLen);
+ close(fd);
+ return retVal;
+}
+
+} // end namespace ns_log
diff --git a/vespalog/src/vespa/log/log-target-file.h b/vespalog/src/vespa/log/log-target-file.h
new file mode 100644
index 00000000000..4ac71ff65ec
--- /dev/null
+++ b/vespalog/src/vespa/log/log-target-file.h
@@ -0,0 +1,32 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <vespa/fastos/fastos.h>
+
+#include "log-target.h"
+
+namespace ns_log {
+
+class LogTargetFile : public LogTarget {
+private:
+ char _fname[256];
+ enum FailState {
+ FS_OK, FS_CHECKING, FS_ROTATING, FS_FAILED
+ } _failstate;
+
+ LogTargetFile();
+ LogTargetFile(const LogTargetFile&);
+ LogTargetFile& operator =(const LogTargetFile&);
+
+public:
+ explicit LogTargetFile(const char *target);
+ virtual ~LogTargetFile();
+ virtual int write(const char *buf, int len);
+};
+
+
+} // end namespace log
+
diff --git a/vespalog/src/vespa/log/log-target.cpp b/vespalog/src/vespa/log/log-target.cpp
new file mode 100644
index 00000000000..d34e0067b04
--- /dev/null
+++ b/vespalog/src/vespa/log/log-target.cpp
@@ -0,0 +1,48 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <sys/types.h>
+#include <cstring>
+#include <cstdlib>
+
+#include "log.h"
+LOG_SETUP(".log");
+LOG_RCSID("$Id$");
+
+#include "log-target-fd.h"
+#include "log-target-file.h"
+#include "internal.h"
+
+namespace ns_log {
+
+LogTarget::LogTarget(const char *target)
+{
+ memset(_name, 0, sizeof(_name));
+ assert(strlen(target) < sizeof(_name));
+ strcpy(_name, target);
+}
+
+LogTarget::~LogTarget() {
+}
+
+LogTarget *
+LogTarget::defaultTarget()
+{
+ // Note! This function cannot LOG().
+ return new LogTargetFd("fd:2");
+}
+
+LogTarget *
+LogTarget::makeTarget(const char *const target)
+{
+ LogTarget *res = NULL;
+ if (strncmp(target, "fd:", 3) == 0) {
+ res = new LogTargetFd(target);
+ } else if (strncmp(target, "file:", 5) == 0) {
+ res = new LogTargetFile(target);
+ } else {
+ throwInvalid("Log target '%s' is invalid.", target);
+ }
+ return res;
+}
+
+
+} // end namespace ns_log
diff --git a/vespalog/src/vespa/log/log-target.h b/vespalog/src/vespa/log/log-target.h
new file mode 100644
index 00000000000..5a27bec7e4d
--- /dev/null
+++ b/vespalog/src/vespa/log/log-target.h
@@ -0,0 +1,28 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+/* This is a virtual base class for all log targets.
+ A log target has a constructor with the log target in string form,
+ and a write() method to write log messages. that's it. */
+
+namespace ns_log {
+
+class LogTarget {
+private:
+ char _name[256];
+ LogTarget();
+ LogTarget& operator =(const LogTarget &);
+ LogTarget(const LogTarget&);
+
+public:
+ LogTarget(const char *name);
+ virtual int write(const char *buf, int bufLen) = 0;
+ virtual ~LogTarget();
+ static LogTarget *makeTarget(const char *target);
+ static LogTarget *defaultTarget();
+ virtual const char *name() const { return _name; }
+ virtual bool makeHumanReadable() const { return false; }
+};
+
+} // end namespace ns_log
+
diff --git a/vespalog/src/vespa/log/log.cpp b/vespalog/src/vespa/log/log.cpp
new file mode 100644
index 00000000000..6ba386ba04d
--- /dev/null
+++ b/vespalog/src/vespa/log/log.cpp
@@ -0,0 +1,409 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/fastos/fastos.h>
+#include <sys/types.h>
+#include <cstring>
+#include <cstdlib>
+#include <stdarg.h>
+#include <cstdio>
+#include <sys/time.h>
+#include <unistd.h>
+#include <errno.h>
+#include <memory>
+
+#include <algorithm>
+
+#include "log.h"
+LOG_SETUP_INDIRECT(".log", "$Id$");
+#undef LOG
+#define LOG LOG_INDIRECT
+
+#include "lock.h"
+#include "log-target.h"
+#include "internal.h"
+#include "control-file.h"
+
+namespace ns_log {
+
+uint64_t Timer::getTimestamp() const {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ uint64_t timestamp = tv.tv_sec;
+ timestamp *= 1000000;
+ timestamp += tv.tv_usec;
+ return timestamp;
+}
+
+LogTarget *Logger::_target = 0;
+int Logger::_numInstances = 0;
+bool Logger::fakePid = false;
+char Logger::_prefix[64] = { '\0' };
+const char Logger::_hexdigit[17] = "0123456789abcdef";
+char Logger::_controlName[1024] = { '\0' };
+char Logger::_hostname[1024] = { '\0'};
+char Logger::_serviceName[1024] = {'\0' };
+ControlFile *Logger::_controlFile = 0;
+
+void
+Logger::ensureControlName()
+{
+ if (_controlName[0] == '\0') {
+ if (!ControlFile::makeName(_serviceName, _controlName,
+ sizeof _controlName)) {
+ LOG(spam, "Neither $VESPA_LOG_CONTROL_FILE nor "
+ "$VESPA_LOG_CONTROL_DIR + $VESPA_SERVICE_NAME are set, "
+ "runtime log-control is therefore disabled.");
+ strcpy(_controlName, "///undefined///");
+ }
+ }
+}
+
+
+void
+Logger::ensureServiceName()
+{
+ if (_serviceName[0] == '\0') {
+ const char *name = getenv("VESPA_SERVICE_NAME");
+ snprintf(_serviceName, sizeof _serviceName, "%s", name ? name : "-");
+ }
+}
+
+void
+Logger::setTarget()
+{
+ try {
+ char *name = getenv("VESPA_LOG_TARGET");
+ if (name) {
+ LogTarget *target = LogTarget::makeTarget(name);
+ delete _target;
+ _target = target;
+ } else {
+ LOG(spam, "$VESPA_LOG_TARGET is not set, logging to stderr");
+ }
+ } catch (InvalidLogException& x) {
+ LOG(error, "Log target problem: %s. Logging to stderr. "
+ "($VESPA_LOG_TARGET=\"%s\")",
+ x.what(), getenv("VESPA_LOG_TARGET"));
+ }
+}
+
+
+void
+Logger::ensurePrefix(const char *name)
+{
+ const char *start = name;
+ if (name[0] != '\0' && name[0] != '.') {
+ const char *end = strchr(name, '.');
+ int len = end ? end - name : strlen(name);
+ start += len;
+
+ if (_prefix[0]) {
+ // Make sure the prefix already set is identical to this one
+ if ((len != int(strlen(_prefix)))
+ || memcmp(name, _prefix, len) != 0) {
+ LOG(error, "Fatal: Tried to set log component name '%s' which "
+ "conflicts with existing root component '%s'. ABORTING",
+ name, _prefix);
+ throwInvalid("Bad config component name '%s' conflicts "
+ "with existing name '%s'", name, _prefix);
+ }
+ } else {
+ // No one has set the prefix yet, so we do it
+ snprintf(_prefix, sizeof _prefix, "%.*s", len, name);
+ LOG(debug, "prefix was set to '%s'", _prefix);
+ }
+ }
+}
+
+void
+Logger::ensureHostname()
+{
+ if (_hostname[0] == '\0') {
+ char hn[257];
+ snprintf(_hostname, sizeof _hostname, "%s",
+ (gethostname(hn, sizeof hn) == 0) ? hn : "-");
+ }
+}
+
+
+Logger::Logger(const char *name, const char *rcsId)
+ : _logLevels(ControlFile::defaultLevels()),
+ _timer(new Timer())
+{
+ _numInstances++;
+ memset(_rcsId, 0, sizeof(_rcsId));
+ memset(_appendix, 0, sizeof(_appendix));
+ const char *app(strchr(name, '.') ? strchr(name, '.') : "");
+ assert(strlen(app) < sizeof(_appendix));
+ strncpy(_appendix, app, sizeof(_appendix));
+ if (!_target) {
+ // Set up stderr first as a target so we can log even if target
+ // cannot be found
+ _target = LogTarget::defaultTarget();
+ setTarget();
+ }
+ ensureServiceName();
+ if (rcsId) {
+ setRcsId(rcsId);
+ }
+ ensureControlName();
+ ensurePrefix(name);
+ ensureHostname();
+
+ // Only read log levels from a file if we are using a file!
+ if (strcmp(_controlName, "///undefined///") != 0) {
+ try {
+ if (!_controlFile) {
+ _controlFile = new ControlFile(_controlName, ControlFile::CREATE);
+ }
+ _logLevels = _controlFile->getLevels(_appendix);
+ _controlFile->setPrefix(_prefix);
+ } catch (InvalidLogException& x) {
+ LOG(error, "Problems initialising logging: %s.", x.what());
+ LOG(warning, "Log control disabled, using default levels.");
+ }
+ }
+}
+
+
+Logger::~Logger()
+{
+ _numInstances--;
+ if ((_numInstances == 1)) {
+ if (logger != NULL) {
+ logger->~Logger();
+ free(logger);
+ logger = NULL;
+ }
+ } else if (_numInstances == 0) {
+ delete _controlFile;
+ logInitialised = false;
+ delete _target;
+ _target = NULL;
+ }
+}
+
+
+int
+Logger::setRcsId(const char *id)
+{
+ const char *start = strchr(id, ',');
+ if (start) {
+ start += std::min((size_t)3, strlen(start)); // Skip 3 chars
+ } else {
+ start = id;
+ }
+
+ int len = strlen(start);
+ const char *end = strchr(start, ' ');
+ if (!end) {
+ end = start + len;
+ }
+
+ assert(size_t(len + 8) < sizeof(_rcsId));
+ sprintf(_rcsId, "(%.*s): ", (int)(end - start), start);
+ LOG(spam, "rcs id was set to '%s'", _rcsId);
+ return 0;
+}
+
+int
+Logger::tryLog(int sizeofPayload, LogLevel level, const char *file, int line, const char *fmt, va_list args)
+{
+ char * payload(new char[sizeofPayload]);
+ const int actualSize = vsnprintf(payload, sizeofPayload, fmt, args);
+
+ if (actualSize < sizeofPayload) {
+ uint64_t timestamp = _timer->getTimestamp();
+ doLogCore(timestamp, level, file, line, payload, actualSize);
+ }
+ delete[] payload;
+ return actualSize;
+}
+
+void
+Logger::doLog(LogLevel level, const char *file, int line, const char *fmt, ...)
+{
+ int sizeofPayload(0x400);
+ int actualSize(sizeofPayload-1);
+ do {
+ sizeofPayload = actualSize+1;
+ va_list args;
+ va_start(args, fmt);
+ actualSize = tryLog(sizeofPayload, level, file, line, fmt, args);
+ va_end(args);
+ } while (sizeofPayload < actualSize);
+}
+
+void Logger::doLogCore(uint64_t timestamp, LogLevel level,
+ const char *file, int line, const char *msg, size_t msgSize)
+{
+ const size_t sizeofEscapedPayload(msgSize*4+1);
+ const size_t sizeofTotalMessage(sizeofEscapedPayload + 1000);
+ char * bigBuffer(new char[sizeofEscapedPayload+sizeofTotalMessage]);
+ char * escapedPayload(bigBuffer);
+ char * totalMessage(escapedPayload + sizeofEscapedPayload);
+
+ char *dst = escapedPayload;
+ for (size_t i(0); (i < msgSize) && msg[i]; i++) {
+ unsigned char c = static_cast<unsigned char>(msg[i]);
+ if ((c >= 32) && (c != '\\') && (c != 127)) {
+ *dst++ = static_cast<char>(c);
+ } else {
+ *dst++ = '\\';
+ if (c == '\\') {
+ *dst++ = '\\';
+ } else if (c == '\r') {
+ *dst++ = 'r';
+ } else if (c == '\n') {
+ *dst++ = 'n';
+ } else if (c == '\t') {
+ *dst++ = 't';
+ } else {
+ *dst++ = 'x';
+ *dst++ = _hexdigit[c >> 4];
+ *dst++ = _hexdigit[c & 0xf];
+ }
+ }
+ }
+ *dst = 0;
+
+ // The only point of tracking thread id is to be able to distinguish log
+ // from multiple threads from each other. As one aren't using that many
+ // threads, only showing the least significant bits will hopefully
+ // distinguish between all threads in your application. Alter later if
+ // found to be too inaccurate.
+ int32_t tid = (fakePid ? -1 : FastOS_Thread::GetCurrentThreadId() % 0xffff);
+
+ if (_target->makeHumanReadable()) {
+ time_t secs = static_cast<time_t>(timestamp / 1000000);
+ struct tm tmbuf;
+ localtime_r(&secs, &tmbuf);
+ char timebuf[100];
+ strftime(timebuf, 100, "%Y-%m-%d %H:%M:%S", &tmbuf);
+ snprintf(totalMessage, sizeofTotalMessage,
+ "[%s.%06u] %d/%d (%s%s) %s: %s\n",
+ timebuf, static_cast<unsigned int>(timestamp % 1000000),
+ fakePid ? -1 : getpid(), tid,
+ _prefix, _appendix,
+ levelName(level), msg);
+ } else if (level == debug || level == spam) {
+ snprintf(totalMessage, sizeofTotalMessage,
+ "%u.%06u\t%s\t%d/%d\t%s\t%s%s\t%s\t%s:%d %s%s\n",
+ static_cast<unsigned int>(timestamp / 1000000),
+ static_cast<unsigned int>(timestamp % 1000000),
+ _hostname, fakePid ? -1 : getpid(), tid,
+ _serviceName, _prefix,
+ _appendix, levelName(level), file, line,
+ _rcsId,
+ escapedPayload);
+ } else {
+ snprintf(totalMessage, sizeofTotalMessage,
+ "%u.%06u\t%s\t%d/%d\t%s\t%s%s\t%s\t%s\n",
+ static_cast<unsigned int>(timestamp / 1000000),
+ static_cast<unsigned int>(timestamp % 1000000),
+ _hostname, fakePid ? -1 : getpid(), tid,
+ _serviceName, _prefix,
+ _appendix, levelName(level), escapedPayload);
+ }
+
+ _target->write(totalMessage, strlen(totalMessage));
+ delete [] bigBuffer;
+}
+
+const char *
+Logger::levelName(LogLevel level)
+{
+ switch (level) {
+ case fatal: return "fatal"; // Deprecated, remove this later.
+ case error: return "error";
+ case warning: return "warning";
+ case event: return "event";
+ case config: return "config";
+ case info: return "info";
+ case debug: return "debug";
+ case spam: return "spam";
+ case NUM_LOGLEVELS: break;
+ }
+ return "--unknown--";
+}
+
+void
+Logger::doEventStarting(const char *name)
+{
+ doLog(event, "", 0, "starting/1 name=\"%s\"", name);
+}
+
+void
+Logger::doEventStopping(const char *name, const char *why)
+{
+ doLog(event, "", 0, "stopping/1 name=\"%s\" why=\"%s\"", name, why);
+}
+
+void
+Logger::doEventStarted(const char *name)
+{
+ doLog(event, "", 0, "started/1 name=\"%s\"", name);
+}
+
+void
+Logger::doEventStopped(const char *name, pid_t pid, int exitCode)
+{
+ doLog(event, "", 0, "stopped/1 name=\"%s\" pid=%d exitcode=%d", name,
+ static_cast<int>(pid), exitCode);
+}
+
+void
+Logger::doEventReloading(const char *name)
+{
+ doLog(event, "", 0, "reloading/1 name=\"%s\"", name);
+}
+
+void
+Logger::doEventReloaded(const char *name)
+{
+ doLog(event, "", 0, "reloaded/1 name=\"%s\"", name);
+}
+
+void
+Logger::doEventCrash(const char *name, pid_t pid, int signal)
+{
+ doLog(event, "", 0, "crash/1 name=\"%s\" pid=%d signal=\"%s\"", name, pid,
+ strsignal(signal));
+}
+
+void
+Logger::doEventProgress(const char *name, double value, double total)
+{
+ if (total > 0) {
+ doLog(event, "", 0, "progress/1 name=\"%s\" value=%.18g total=%.18g",
+ name, value, total);
+ } else {
+ doLog(event, "", 0, "progress/1 name=\"%s\" value=%.18g", name, value);
+ }
+}
+
+void
+Logger::doEventCount(const char *name, uint64_t value)
+{
+ doLog(event, "", 0, "count/1 name=\"%s\" value=%" PRIu64, name, value);
+}
+
+void
+Logger::doEventValue(const char *name, double value)
+{
+ doLog(event, "", 0, "value/1 name=\"%s\" value=%.18g", name, value);
+}
+
+void
+Logger::doEventCollection(uint64_t collectionId, const char* name, const char* params)
+{
+ doLog(event, "", 0, "collection/1 collectionId=%" PRIu64 " name=\"%s\" %s",
+ collectionId, name, params);
+}
+
+void
+Logger::doEventState(const char *name, const char *value)
+{
+ doLog(event, "", 0, "state/1 name=\"%s\" value=\"%s\"", name, value);
+}
+
+} // end namespace ns_log
diff --git a/vespalog/src/vespa/log/log.h b/vespalog/src/vespa/log/log.h
new file mode 100644
index 00000000000..827d615c4e8
--- /dev/null
+++ b/vespalog/src/vespa/log/log.h
@@ -0,0 +1,308 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <memory>
+#include <inttypes.h>
+#include <sys/types.h> // for pid_t
+#include <new> // for placement new
+#include <cstdlib> // for malloc
+#include <cstring> // for memset
+
+/**
+ * If this macro is defined, the regular LOG calls will go through the
+ * buffered logger, using the whole messages as tokens.
+ *
+ * If this macro is not defined, only LOGB calls will go through log buffer
+ */
+//#define VESPA_LOG_USELOGBUFFERFORREGULARLOG 1
+
+// Used to use anonymous namespaces, but they fail miserably in gdb 5.3
+
+#define LOG_SETUP(...) \
+static ns_log::Logger logger(__VA_ARGS__)
+
+#define LOG_SETUP_INDIRECT(x, id) \
+static ns_log::Logger *logger=NULL; \
+static bool logInitialised = false; \
+static const char *logName = x; \
+static const char *rcsId = id
+
+
+#define LOG_RCSID(x) \
+static int log_dummmy __attribute__((unused)) = logger.setRcsId(x)
+
+
+// Define LOG if not using log buffer. Otherwise log buffer will define them
+#ifndef VESPA_LOG_USELOGBUFFERFORREGULARLOG
+#define LOG(level, ...) \
+do { \
+ if (logger.wants(ns_log::Logger::level)) { \
+ logger.doLog(ns_log::Logger::level, __FILE__, __LINE__, __VA_ARGS__); \
+ ns_log::BufferedLogger::logger.trimCache(); \
+ } \
+} while (0)
+#define VLOG(level, ...) \
+do { \
+ if (logger.wants(level)) { \
+ logger.doLog(level, __FILE__, __LINE__, __VA_ARGS__); \
+ ns_log::BufferedLogger::logger.trimCache(); \
+ } \
+} while (0)
+#endif
+
+// Must use placement new in the following definition, since the variable
+// "logger" must be a valid logger object DURING the construction of the
+// logger object itself.
+#define LOG_INDIRECT_MUST \
+ if (!logInitialised) { \
+ logInitialised = true; \
+ logger = static_cast<Logger *>(malloc(sizeof *logger)); \
+ memset(logger, 0, sizeof *logger); \
+ new (logger) Logger(logName, rcsId); \
+ }
+#define LOG_INDIRECT(level, ...) \
+do { \
+ LOG_INDIRECT_MUST \
+ if (logger->wants(ns_log::Logger::level)) { \
+ logger->doLog(ns_log::Logger::level, __FILE__, __LINE__, __VA_ARGS__); \
+ } \
+} while (0)
+
+#define LOG_WOULD_LOG(level) logger.wants(ns_log::Logger::level)
+#define LOG_WOULD_VLOG(level) logger.wants(level)
+
+#define EV_STARTING(name) \
+do { \
+ if (logger.wants(ns_log::Logger::event)) { \
+ logger.doEventStarting(name); \
+ } \
+} while (0)
+
+#define EV_STOPPING(name,why) \
+do { \
+ if (logger.wants(ns_log::Logger::event)) { \
+ logger.doEventStopping(name, why); \
+ } \
+} while (0)
+
+#define EV_STARTED(name) \
+do { \
+ if (logger.wants(ns_log::Logger::event)) { \
+ logger.doEventStarted(name); \
+ } \
+} while (0)
+
+#define EV_STOPPED(name,pid,exitcode) \
+do { \
+ if (logger.wants(ns_log::Logger::event)) { \
+ logger.doEventStopped(name, pid, exitcode); \
+ } \
+} while (0)
+
+#define EV_RELOADING(name) \
+do { \
+ if (logger.wants(ns_log::Logger::event)) { \
+ logger.doEventReloading(name); \
+ } \
+} while (0)
+
+#define EV_RELOADED(name) \
+do { \
+ if (logger.wants(ns_log::Logger::event)) { \
+ logger.doEventReloaded(name); \
+ } \
+} while (0)
+
+#define EV_CRASH(name,pid,signal) \
+do { \
+ if (logger.wants(ns_log::Logger::event)) { \
+ logger.doEventCrash(name, pid, signal); \
+ } \
+} while (0)
+
+#define EV_PROGRESS(name, ...) \
+do { \
+ if (logger.wants(ns_log::Logger::event)) { \
+ logger.doEventProgress(name, __VA_ARGS__); \
+ } \
+} while (0)
+
+#define EV_COUNT(name,value) \
+do { \
+ if (logger.wants(ns_log::Logger::event)) { \
+ logger.doEventCount(name, value); \
+ } \
+} while (0)
+
+#define EV_VALUE(name,value) \
+do { \
+ if (logger.wants(ns_log::Logger::event)) { \
+ logger.doEventValue(name, value); \
+ } \
+} while (0)
+
+#define EV_COLLECTION(collectionId,name,params) \
+do { \
+ if (logger.wants(ns_log::Logger::event)) { \
+ logger.doEventCollection(collectionId, name, params); \
+ } \
+} while (0)
+
+#define EV_STATE(name,value) \
+do { \
+ if (logger.wants(ns_log::Logger::event)) { \
+ logger.doEventState(name, value); \
+ } \
+} while (0)
+
+namespace ns_log {
+
+class LogTarget;
+class ControlFile;
+
+// XXX this is way too complicated, must be some simpler way to do this
+/** Timer class used to retrieve timestamp, such that we can override in test */
+struct Timer {
+ virtual ~Timer() {}
+ virtual uint64_t getTimestamp() const;
+};
+
+/** Test timer returning just a given time. Used in tests to fake time. */
+struct TestTimer : public Timer {
+ uint64_t & _time;
+ TestTimer(uint64_t & timeVar) : _time(timeVar) { }
+ virtual ~TestTimer() {}
+ virtual uint64_t getTimestamp() const { return _time; }
+};
+
+class Logger {
+public:
+ enum LogLevel {
+ fatal, error, warning, config, info, event, debug, spam, NUM_LOGLEVELS
+ };
+ static const char *logLevelNames[];
+ static enum LogLevel parseLevel(const char *lname);
+
+ /**
+ * Set by unit tests to avoid needing to match different pids. Will be
+ * false by default.
+ */
+ static bool fakePid;
+
+private:
+ Logger();
+ Logger(const Logger &);
+ Logger& operator =(const Logger &);
+
+ unsigned int *_logLevels;
+
+ static char _prefix[64];
+ static LogTarget *_target;
+ char _rcsId[256];
+ static int _numInstances;
+
+ static char _controlName[1024];
+ static char _hostname[1024];
+ static char _serviceName[1024];
+ static const char _hexdigit[17];
+ static ControlFile *_controlFile;
+
+ static void setTarget();
+ void makeLockFile();
+
+ char _appendix[256];
+
+ std::unique_ptr<Timer> _timer;
+
+ void ensurePrefix(const char *name);
+ void ensureHostname();
+ void ensureControlName();
+ void ensureServiceName();
+
+ int tryLog(int sizeofPayload, LogLevel level, const char *file, int line,
+ const char *fmt, va_list args);
+public:
+ ~Logger();
+ explicit Logger(const char *name, const char *rcsId = 0);
+ static Logger& getLogger(const char *name);
+ int setRcsId(const char *rcsId);
+ static const char *levelName(LogLevel level);
+
+ inline bool wants(LogLevel level);
+ void doLog(LogLevel level, const char *file, int line,
+ const char *fmt, ...) __attribute__((format(printf,5,6)));
+ /**
+ * The log buffer creates timestamp and creates the log message itself.
+ * Thus the core log functionality has been moved here to avoid doing it
+ * twice. doLogCore is called from doLog and from the log buffer.
+ *
+ * @param timestamp Time in microseconds.
+ */
+ void doLogCore(uint64_t timestamp, LogLevel level,
+ const char *file, int line, const char *msg, size_t msgSize);
+ void doEventStarting(const char *name);
+ void doEventStopping(const char *name, const char *why);
+ void doEventStarted(const char *name);
+ void doEventStopped(const char *name, pid_t pid, int exitCode);
+ void doEventReloading(const char *name);
+ void doEventReloaded(const char *name);
+ void doEventCrash(const char *name, pid_t pid, int signal);
+ void doEventProgress(const char *name, double value, double total = 0);
+ void doEventCount(const char *name, uint64_t value);
+ void doEventValue(const char *name, double value);
+ void doEventCollection(uint64_t collectionId, const char *name,
+ const char *params);
+ void doEventState(const char *name, const char *value);
+
+ // Only for unit testing
+ void setTimer(std::unique_ptr<Timer> timer) { _timer = std::move(timer); }
+};
+
+
+#define CHARS_TO_UINT(a,b,c,d) \
+(static_cast<unsigned char>(a) \
+ | static_cast<unsigned int>(static_cast<unsigned char>(b) << 8u) \
+ | static_cast<unsigned int>(static_cast<unsigned char>(c) << 16u) \
+ | static_cast<unsigned int>(static_cast<unsigned char>(d) << 24u))
+
+inline bool Logger::wants(LogLevel level)
+{
+ return _logLevels[level] == CHARS_TO_UINT(' ', ' ', 'O', 'N');
+}
+
+#define LOG_noreturn __attribute__((__noreturn__))
+
+extern void log_assert_fail(const char *assertion,
+ const char *file,
+ uint32_t line) LOG_noreturn;
+
+extern void log_abort(const char *message,
+ const char *file,
+ uint32_t line) LOG_noreturn;
+
+#undef LOG_noreturn
+
+} // end namespace log
+
+//======================================//
+// LOG_ASSERT and LOG_ABORT definitions //
+//======================================//
+
+#ifndef __STRING
+#define __STRING(x) #x
+#endif
+
+#define LOG_ABORT(msg) \
+ (ns_log::log_abort(msg, __FILE__, __LINE__))
+
+#ifndef NDEBUG
+#define LOG_ASSERT(expr) \
+ ((void) ((expr) ? 0 : \
+ (ns_log::log_assert_fail(__STRING(expr), \
+ __FILE__, __LINE__), 0)))
+#else
+#define LOG_ASSERT(expr)
+#endif // #ifndef NDEBUG
+
+#include <vespa/log/bufferedlogger.h>
+
diff --git a/vespalog/src/vespa/log/mknm.pl b/vespalog/src/vespa/log/mknm.pl
new file mode 100755
index 00000000000..126fe0ba6ff
--- /dev/null
+++ b/vespalog/src/vespa/log/mknm.pl
@@ -0,0 +1,46 @@
+#!/usr/bin/perl
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+while (<>) {
+ if ( s/.*\benum\s+LogLevel\s*\{// ) {
+ chomp;
+ $t = $_;
+ while (<>) {
+ if ( s/\}.*// ) {
+ $t .= $_;
+ $t =~ s/,/ /g;
+ @t = split(" ", $t);
+ if ( $t[$#t] ne "NUM_LOGLEVELS" ) {
+ die "expected NUM_LOGLEVELS got '$t[$#t]'\n";
+ }
+ pop @t;
+ makecpp();
+ }
+ $t .= $_;
+ }
+ }
+}
+die "did not find enum\n";
+
+sub makecpp
+{
+ print "#include <string.h>\n";
+ print '#include <vespa/log/log.h>';
+ print "\n\n" . "namespace ns_log {" . "\n\n";
+
+ print "enum Logger::LogLevel\n";
+ print "Logger::parseLevel(const char *lname)\n{\n";
+ foreach $l ( @t ) {
+ print " if (strcmp(lname, \"$l\") == 0) return $l;\n";
+ }
+ print " // bad level name signaled by NUM_LOGLEVELS\n";
+ print " return NUM_LOGLEVELS;\n";
+ print "}\n\n";
+
+ print "const char *Logger::logLevelNames[] = {" . "\n ";
+ foreach $l ( @t ) { $l = "\"$l\""; }
+ push @t, "0 // converting NUM_LOGLEVELS gives null pointer\n";
+ print join(",\n ", @t);
+ print "};\n\n} // namespace\n";
+ exit(0);
+}
diff --git a/vespalog/src/vespa/log/reject-filter.cpp b/vespalog/src/vespa/log/reject-filter.cpp
new file mode 100644
index 00000000000..59155a44a6e
--- /dev/null
+++ b/vespalog/src/vespa/log/reject-filter.cpp
@@ -0,0 +1,65 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <sys/types.h>
+#include <cstring>
+#include <cstdlib>
+
+#include "reject-filter.h"
+
+namespace ns_log {
+
+void
+RejectFilter::addRejectRule(Logger::LogLevel level, const std::string & message)
+{
+ _rejectRules.push_back(RejectRule(level, message, false));
+}
+
+void
+RejectFilter::addExactRejectRule(Logger::LogLevel level, const std::string & message)
+{
+ _rejectRules.push_back(RejectRule(level, message, true));
+}
+
+bool
+RejectFilter::shouldReject(Logger::LogLevel level, const char * message)
+{
+ if (message == NULL) {
+ return false;
+ }
+ for (size_t i = 0; i < _rejectRules.size(); i++) {
+ if (_rejectRules[i].shouldReject(level, message)) {
+ return true;
+ }
+ }
+
+
+ return false;
+}
+
+bool
+RejectFilter::RejectRule::shouldReject(Logger::LogLevel level, const char * message)
+{
+ if (_level == level) {
+ if (_exact) {
+ if (strlen(message) == _message.length() && _message.compare(message) == 0) {
+ return true;
+ }
+ } else {
+ if (strstr(message, _message.c_str()) != NULL) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+RejectFilter
+RejectFilter::createDefaultFilter()
+{
+ RejectFilter filter;
+ filter.addRejectRule(Logger::warning, "Using FILTER_NONE: This must be paranoid approved, and since you are using FILTER_NONE you must live with this error.");
+ filter.addExactRejectRule(Logger::warning, "");
+ filter.addRejectRule(Logger::warning, "yjava_preload.so: [preload.c:670] Accept failed: -1 (4)");
+ return filter;
+}
+
+} // end namespace ns_log
diff --git a/vespalog/src/vespa/log/reject-filter.h b/vespalog/src/vespa/log/reject-filter.h
new file mode 100644
index 00000000000..82c6d975d6d
--- /dev/null
+++ b/vespalog/src/vespa/log/reject-filter.h
@@ -0,0 +1,37 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/log/log.h>
+
+#include <vector>
+#include <string>
+
+namespace ns_log {
+
+/**
+ * A reject filter is simply a list of log messages that should be rejected and
+ * not logged.
+ */
+class RejectFilter {
+private:
+ class RejectRule {
+ private:
+ Logger::LogLevel _level;
+ std::string _message;
+ bool _exact;
+ public:
+ RejectRule(Logger::LogLevel level, const std::string & message, bool exact)
+ : _level(level), _message(message), _exact(exact)
+ { }
+ bool shouldReject(Logger::LogLevel level, const char * message);
+ };
+ std::vector<RejectRule> _rejectRules;
+public:
+ void addRejectRule(Logger::LogLevel level, const std::string & rejectedMessage);
+ void addExactRejectRule(Logger::LogLevel level, const std::string & rejectedMessage);
+ bool shouldReject(Logger::LogLevel level, const char * message);
+ static RejectFilter createDefaultFilter();
+};
+
+} // end namespace ns_log
+