Opinionated minimal logging inspired by and compatible with node-bunyan
- JSON formatted, bunyan-conformant log entries
- asynchronous logging, powered by Kovenant and Disruptor
- console only by default
- easily extendable and customisable via simple Appender interface
- startup configuration via optional environment variables
Having a TestObject
object TestObject {
val logger: Logger = Logger(this)
fun dummyFunction() {
logger.info("dummy")
}
}
and calling its dummyFunction
will result in a log entry similar to
{"hostname":"vyo-pc","pid":6484,"thread":"Thread[main,5,]","time":"2015-11-30T18:55:35.092Z","level":30,"name":"io.github.vyo.twig.TestObject@12aba81","msg":"dummy","v":0}
You may pass in an arbitrary number of Pair<String, Any>
to create additional custom log entry fields
logger.info("dummy", Pair("my custom field", "my custom content"), Pair("my logger", logger))
{"hostname":"vyo-pc","pid":6156,"thread":"Thread[main,5,]","time":"2015-11-30T19:09:58.507Z","level":30,"name":"io.github.vyo.twig.TestObject@16e898f","msg":"dummy","my custom field":"my custom content","my logger":"io.github.vyo.twig.logger.Logger@1a97992","v":0}
You may pass throwables, i.e. exceptions and errors, and they will be automatically expanded to both a short message and an accompanying stacktrace.
Stacktraces will only be logged out at DEBUG level (configurable) or below. They will be put into a custom field called 'stacktrace' in the form of an array.
val logger = Logger("twig")
try {
Integer.parseInt("not an int")
} catch (e: Exception) {
logger.error(e)
logger.debug(e)
}
will lead to
{"hostname":"vyo-pc"","pid":6580,"thread":"Thread[main,5,]","time":"2015-11-30T19:38:45.037Z","level":50,"name":"twig","msg":"java.lang.NumberFormatException: For input string: \"not an int\"","v":0}
{"hostname":"vyo-pc"","pid":6580,"thread":"Thread[main,5,]","time":"2015-11-30T19:38:45.037Z","level":20,"name":"twig","msg":"java.lang.NumberFormatException: For input string: \"not an int\"","stacktrace":["java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)","java.lang.Integer.parseInt(Integer.java:580)","java.lang.Integer.parseInt(Integer.java:615)"],"v":0}
Piped into the bunyan cli the preceding log entry will be pretty-printed like this
[2015-11-30T19:09:58.507Z] INFO: io.github.vyo.twig.TestObject@16e898f/6156 on vyo-pc: dummy (thread=Thread[main,5,], my custom field="my custom content", my logger=io.github.vyo.twig.logger.Logger@1a97992)
Note: You may configure global options by referencing either Logger.*
or Logger.global.*
.
The configuration is logged on startup; by default
- the global log level will be INFO,
- the worker amount will be set to the system's available processors,
- the log queue size will be set to 1024,
- throwables will be auto-expanded at DEBUG level or below,
- and auto-expanded throwables will come with a stacktrace of 50 lines at most.
{"hostname":"vyo-pc","pid":1144,"thread":"Thread[main,5,]","time":"2015-11-30T19:36:28.220Z","level":30,"name":"twig","msg":"logging worker count: 4","v":0}
{"hostname":"vyo-pc","pid":1144,"thread":"Thread[main,5,]","time":"2015-11-30T19:36:28.220Z","level":30,"name":"twig","msg":"logging work queue size: 1024","v":0}
{"hostname":"vyo-pc","pid":1144,"thread":"Thread[main,5,]","time":"2015-11-30T19:36:28.220Z","level":30,"name":"twig","msg":"global log level: INFO","v":0}
{"hostname":"vyo-pc"","pid":6580,"thread":"Thread[main,5,]","time":"2015-11-30T19:38:45.037Z","level":30,"name":"twig","msg":"throwable expansion level: DEBUG","v":0}
{"hostname":"vyo-pc"","pid":6580,"thread":"Thread[main,5,]","time":"2015-11-30T19:38:45.037Z","level":30,"name":"twig","msg":"throwable expansion depth: 50","v":0}
Setting up the following environment variables in advance
TWIG_LEVEL=TRACE
TWIG_QUEUE=64
TWIG_WORKERS=1
TWIG_EXPANSION_LEVEL=TRACE
TWIG_EXPANSION_DEPTH=100
{"hostname":"vyo-pc"","pid":6580,"thread":"Thread[main,5,]","time":"2015-11-30T19:38:45.037Z","level":30,"name":"twig","msg":"global log level TRACE","v":0}
{"hostname":"vyo-pc"","pid":6580,"thread":"Thread[main,5,]","time":"2015-11-30T19:38:45.037Z","level":30,"name":"twig","msg":"logging work queue size: 64","v":0}
{"hostname":"vyo-pc"","pid":6580,"thread":"Thread[main,5,]","time":"2015-11-30T19:38:45.037Z","level":30,"name":"twig","msg":"logging worker count: 1","v":0}
{"hostname":"vyo-pc"","pid":6580,"thread":"Thread[main,5,]","time":"2015-11-30T19:38:45.037Z","level":30,"name":"twig","msg":"throwable expansion level: TRACE","v":0}
{"hostname":"vyo-pc"","pid":6580,"thread":"Thread[main,5,]","time":"2015-11-30T19:38:45.037Z","level":30,"name":"twig","msg":"throwable expansion depth: 100","v":0}
Re-assigning the global log level or appender will also be logged
Logger.global.appender = ConsoleAppender()
Logger.global.level = Level.WARN
{"hostname":"vyo-pc"","pid":6364,"thread":"Thread[main,5,]","time":"2015-11-30T19:40:36.876Z","level":30,"name":"twig","msg":"global appender io.github.vyo.twig.appender.ConsoleAppender@1ae6ba4","v":0}
{"hostname":"vyo-pc"","pid":6364,"thread":"Thread[main,5,]","time":"2015-11-30T19:40:36.876Z","level":30,"name":"twig","msg":"global log level WARN","v":0}
You may also specify your own serialiser function if the built in JSON-serialiser does not fit your needs.
####Default (built in):
Logger.global.serialiser = Logger.global.simpleSerialiser
####Jodd:
val jodd = JsonSerializer()
Logger.global.serialiser = { any: Any -> jodd.serialize(any) }
####Jackson:
val jackson = ObjectMapper()
Logger.global.serialiser = { any: Any -> jackson.writeValueAsString(any) }
####Custom function:
fun customToJson( any: Any) : String {
return any.toString()
}
Logger.global.serialiser = { any: Any -> customToJson(any) }
// or:
val lambdaJson = { any: Any -> any.toString() }
Logger.global.serialiser = { any: Any -> lambdaJson(any) }
// or:
Logger.global.serialiser = { any: Any -> any.toString() }
Note: You should ensure that your custom function produces valid JSON. You may also want to have arrays and collections be automatically expanded, especially when making use of Twig's auto-expanded throwables.
Note: Google's GSON seems to be unable to handle cyclic references, making it unsuitable for Twig for the time being.
Log level, appender and serialiser can be set globally, as well as individually for each logger:
val loggerOne = Logger("one")
val loggerTwo = Logger("two")
loggerOne.level = Level.WARN
loggerTwo.level = Level.INFO
loggerOne.appender = FileAppender()
loggerTwo.appender = ConsoleAppender()
loggerOne.serialiser = Logger.simpleSerialiser
loggerTwo.serialiser = { any: Any -> any.toString() }
If an exception occurs during the actual logging process, e.g. because the underlying Appender fails, we try to log a diagnostic entry to STDERR
logger.fatal("exceptional")
{"hostname":"vyo-pc","pid":3092,"thread":"Thread[main,5,]","time":"2015-11-30T19:47:15.128Z","level":60"name":"io.github.vyo.twig.logger.Logger@50ea2a","msg":"logging failed: null","original level":30,"original name":"io.github.vyo.twig.TestObject@970b10","original message":"exceptional","v":0
This project adheres to the semantic versioning and change log guidelines.
Manuel Weidmann (@vyo)
ISC License