Twig

Minimal bunyan-style logging for Kotlin and the JVM

Download as .zip Download as .tar.gz View on GitHub

Twig

Opinionated minimal logging inspired by and compatible with node-bunyan


Features

Basic

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}

Custom Fields

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}

Auto-expanded Throwables

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}

Bunyan CLI

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)

Configuration

Note: You may configure global options by referencing either Logger.* or Logger.global.*.

The configuration is logged on startup; by default

{"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.

Per-logger settings:

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() }

Exception Behaviour

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

Notes

This project adheres to the semantic versioning and change log guidelines.

Author

Manuel Weidmann (@vyo)

License

ISC License