You only log once but you can easily parse your data as many ways as you want.
A general log tailer and parser tool written in Java, inspired by Parsible and Logster. It is easily extensible with new parsers and processors. The main purpose was to build a simple tool to get metrics values and errors from log files and report to StatsD (and Graphite), but everyone can write custom modules in minutes.
- Flexible configuration: the configuration is in JSON format for easier management. You can define your parsers and processors and bind them as you want.
- Real time: the tool tails the log file realtime
- Scriptable: you can write parsers in other languages (currently only JavaScript is tested and allowed)
- Whole file reading: with a cli parameter you can read your logfile from the beginning
- Handle dynamic filenames: you can use wildcards in filename, all matching files will be tailed
- Logrotate friendly: works easily with logrotate or other log rotating tools
- Easily debuggable: debug mode writes verbose logs, and you can use built-in parsers and processors for debugging purposes
- Tailing gzip files: handles continuously written gzip output streams
Because we tail the log file and the application can stop anytime therefore it is not guaranteed that the tool will parse all the lines. In the near future we plan to implement a secure reader which stores the read offset and handles even log rotate events.
The file handler watches the given path's root directory for every file matching the given filename pattern (like 'gc*.log'). If a new file created it will be read from the beginning. Deleted files will be no more tailed and released.
In parsers you don't have to worry about concurrency, the file handler sends only one line at a time.
If you have trouble passing * or ? in shell to the file parameter, just use \* or ?.
If your application recreates log files by deleting and creating them, the application can hold wrong file references. In this case always use the reopen cli flag.
Each module is stateless, has a description and predefined configuration.
- Parser: the parser parses a log line and returns with parameters (a map).
- Processor: the processor gets the parser's parameters and process them, like send key/values to StatsD
- the file tailer reads a new line from the file
- the handler iterates through all the parsers and finds the first which returns with a non-null value
- the output value is passed to the given processors with the separate configs for each processor
- the processors process the data
- the handler runs all the parsers (regardless the first match) which runs always. (currently it is only the passthru parser)
The project uses Gradle and it is embedded with a Gradle wrapper.
# run tests, if you want
./gradlew test
# create runnable jar file
./gradlew jar
# check if it works
java -jar build/libs/yolo.jar -help
./gradlew debian
The .deb file will be placed in the build/linux-package directory.
The package contains the runnable jar file, which will be copied to /usr/lib/yolo when installed.
./gradlew uploadDebianArchives -PyoloMavenRepoUrl=... -PyoloMavenRepoUsername=... -PyoloMavenRepoPassword=...
An example config file can be found in example.json.
Simply run the jar with "-help" option.
$ java -jar build/libs/yolo.jar -help
usage: yolo
-config <path> path to config file
-debug turn on debug mode
-file <path> path to logfile, wildcards are accepted
-gzip read tailed file as GZIP formatted.
-help print this message
-hostname <short hostname> overwrite hostname
-listModules list available modules
-log <path> log to file
-reopen reopen file between reading the chunks
-verbose print verbose messages to console
-version show version
-watchConfigInterval <second> check config file periodically and update
without stopping, default: 5 sec
-whole tail file from the beginning
$ java -jar build/libs/yolo.jar -config /YOURPATH/src/main/config/example.json -file /YOURPATH/foo.log
Simply run the jar with "-listModules" option.
$ java -jar build/libs/yolo.jar -listModules
Available processors
--------------------
* tv.ustream.yolo.module.processor.CompositeProcessor - runs multiple processors
- params: Map {
class: String, required
processors: List, required
}
* tv.ustream.yolo.module.processor.ConsoleProcessor - writes parameters to console, use it for debug purposes
- params: Map {
class: String, required
}
* tv.ustream.yolo.module.processor.GraphiteProcessor - sends metrics to Graphite
- params: Map {
port: Number, default: 2003
host: String, required
prefix: String
class: String, required
flushTimeMs: Number, default: 1000
}
- parser params: Map {
keys: List [
Map {
timestamp: String, pattern allowed
value: String|Number, required, pattern allowed
multiplier: Number, default: 1
key: String, required, pattern allowed
}
]
}
* tv.ustream.yolo.module.processor.StatsDProcessor - sends metrics to StatsD, handles counter, gauge and timing values
- params: Map {
port: Number, default: 8125
host: String, required
prefix: String, required
class: String, required
}
- parser params: Map {
keys: List [
Map {
value: String|Number, required, pattern allowed
type: String, required, allowed values: [counter, gauge, timer]
multiplier: Number, default: 1
key: String, required, pattern allowed
}
]
}
Available parsers
-----------------
* tv.ustream.yolo.module.parser.PassThruParser - forwards all lines to processor (map: 'line' -> 'content'), runs always
- params: Map {
enabled: Boolean, default: true
class: String, required
processors: Map, required
}
* tv.ustream.yolo.module.parser.RegexpParser - parses lines via regular expression and returns with matches
- params: Map {
enabled: Boolean, default: true
regex: String, required
class: String, required
processors: Map, required
}
* tv.ustream.yolo.module.parser.JsonParser - parses JSON strings
- params: Map {
flatten: Boolean, default: true
processors: Map, required
filters: List [
Map {
value: Object, default: java.lang.Object@6f2b958e
key: String, required
}
]
class: String, required
enabled: Boolean, default: true
}
* tv.ustream.yolo.module.parser.ScriptEngineParser - parses data with an external script file
- params: Map {
enabled: Boolean, default: true
engine: String, required
file: String, required
class: String, required
processors: Map, required
}
We use the built-in ScriptEngine to run script files in other languages. Currently only JavaScript is supported, but we plan to test other engines as well.
Check examples/scriptengine directory for example.
Check RegexpParser for a compact example.
If you are ready, add your parser class to ModuleFactory.availableParsers.
public class TestParser implements IParser
{
@Override
public Map<String, Object> parse(String line)
{
// parse a line here and return with null if no match happened or with a Map if you want to process it
}
@Override
public boolean runAlways()
{
// return with true if you want the module to always run for every line regardless of the first match
}
@Override
public List<String> getOutputKeys()
{
// return with the keys you will return in parse
}
@Override
public void setUpModule(Map<String, Object> parameters)
{
// read the config and set up your parser
}
@Override
public ConfigMap getModuleConfig()
{
// build your module's config
}
@Override
public String getModuleDescription()
{
// return with a simple description for your module
}
}
Check StatsDProcessor for a compact example.
If you are ready, add your processor class to ModuleFactory.availableProcessors.
public class TestProcessor implements IProcessor
{
@Override
public ConfigMap getProcessParamsConfig()
{
// build the processParams config for parsers
}
@Override
public void process(Map<String, Object> parserOutput, Map<String, Object> processParams)
{
// process the parser's output. The processParams map can contain ConfigPattern objects where you can subtitute your own values
}
@Override
public void setUpModule(Map<String, Object> parameters)
{
// read configuration and set up your processor
}
@Override
public ConfigMap getModuleConfig()
{
// build your module's config
}
@Override
public String getModuleDescription()
{
// return with a simple description for your module
}
@Override
public void stop()
{
// here you can stop your processor gracefully
}
}
The configuration is validated with ConfigMap objects which can be built the following way.
ConfigMap config = new ConfigMap();
// add "key1" key with string type, the value will be required
config.addConfigValue("key1", String.class);
// add "key2" key with number type, the value will be optional, and the default value will be 5
config.addConfigValue("key1", Number.class, false, 5);
// add "key3" key with string|number type
ConfigValue<Object> configValue = new ConfigValue<Object>("key3", Object.class);
configValue.setAllowedTypes(Arrays.<Class>asList(String.class, Number.class));
config.addConfigValue(configValue);
// add "key4" key as an enumeration
ConfigValue<String> configValue = new ConfigValue<String>("key4", String.class);
configValue.setAllowedValues(Arrays.asList("value1", "value2"));
config.addConfigValue(configValue);
// add "key5" key which allows a config pattern
ConfigValue<String> configValue = new ConfigValue<String>("key5", String.class);
configValue.allowConfigPattern();
config.addConfigValue(configValue);
Config pattern means a string value with key placeholders, like 'this is #type#'. This can be used in the process parameters configuration, when you want to substitute values from the parser output in the process config.
If you check the configuration example, you can see that the statsd process parameters contain expressions like #exceptionName# or #val#, these values will be substituted from the regexp matches.
The tool uses SLF4J AND log4j 1.2 for logging.
To log debug messages use the -debug option.
To log messages to a file, use the -log option.
To show messages on the console, use the -verbose option.
For a detailed dependency list please check the "dependencies" block in build.gradle.
Compile dependencies:
Testing dependencies:
- JUnit
- Mockito
- [Awaitility] (http://code.google.com/p/awaitility/)
Feel free to fork this project and send pull requests if you want to help us improve the tool or add new parsers/processors. But before sending us new modules, please think through if the module serves a general purpose and whether it will be useful for others, not just for you.
Please see CHANGELOG.md.
This project is licensed under the terms of the MIT License (MIT).