shax is a minimal (30KB, zero dependencies), opinionated (see below) JSON structured logging backend for SLF4j.
It is inspired by and combines heavily modified parts from minimal-json and the slf4j-simple reference backend implementation.
Pull the following dependency into your project (available in Maven Central):
implementation("io.vacco.shax:shax:<VERSION>")
Note: All
shax
releases align to theslf4j-api
version they were compiled against. Make sure to exclude other SLF4J bindings in your class path, otherwiseslf4j
will complain.
Your regular SLF4J logging statements are exactly the same, but will now look like this (single-line version):
log.info("Let's see some cats and owners");
Which produces:
{"utc":"2020-08-21T13:42:42.312455","utc_ms":1598017362312,"level":"INFO","level_value":20,"logger_name":"io.vacco.shax.test.ShLoggingSpec","thread_name":"Test worker","message":"Let's see some cats and owners"}
Harsh on the eyes? Make it pretty with io.vacco.shax.prettyprint
:
{
"utc": "2020-08-21T13:43:20.788755",
"utc_ms": 1598017400788,
"level": "INFO",
"level_value": 20,
"logger_name": "io.vacco.shax.test.ShLoggingSpec",
"thread_name": "Test worker",
"message": "Let's see some cats and owners"
}
Still harsh on the eyes? Switch to dev mode with io.vacco.shax.devmode
:
INFO [1598017441508] (Test worker): Let's see some cats and owners
Now add more keys to a log record with io.vacco.shax.logging.ShArgument.kv(String, Object)
Map<String, String> catOwners = new TreeMap<>();
catOwners.put("Garfield", "Jon");
catOwners.put("Arlene", "Jon");
catOwners.put("Azrael", "Gargamel");
catOwners.put("Chi", "Youhei");
log.info("Cats and Owners [{}]", kv("catOwners", catOwners));
To get:
{
"utc": "2020-08-21T13:45:44.956641",
"utc_ms": 1598017544956,
"level": "INFO",
"level_value": 20,
"logger_name": "io.vacco.shax.test.ShLoggingSpec",
"thread_name": "Test worker",
"message": "Cats and Owners [{catOwners=java.util.TreeMap}]",
"catOwners": {
"Arlene": "Jon",
"Azrael": "Gargamel",
"Chi": "Youhei",
"Garfield": "Jon"
}
}
Or in dev mode:
INFO [1598017595726] (Test worker): Cats and Owners [{catOwners=java.util.TreeMap}]
{
"Arlene": "Jon",
"Azrael": "Gargamel",
"Chi": "Youhei",
"Garfield": "Jon"
}
Not happy with the output format? Place a record transformer on the target Logger
you intend to modify.
For example, let's say we want to make the logger io.vacco.shax.test
align more to the
logstash-logback-encoder fields
(which it kind of already does :P).
Logger log = ShLogger.withTransformer(
LoggerFactory.getLogger(ShLoggingSpec.class),
r -> {
r.put("@timestamp", r.get(ShLogRecord.ShLrField.utc.name()));
r.put("@version", 1);
r.remove(ShLogRecord.ShLrField.utc.name());
r.remove(ShLogRecord.ShLrField.utc_ms.name());
return r;
}
);
Which produces:
{
"level": "INFO",
"level_value": 20,
"logger_name": "io.vacco.shax.test.ShLoggingSpec",
"thread_name": "Test worker",
"message": "Cats and Owners [{catOwners=java.util.TreeMap}]",
"catOwners": {
"Arlene": "Jon",
"Azrael": "Gargamel",
"Chi": "Youhei",
"Garfield": "Jon"
},
"@timestamp": "2020-08-21T13:57:59.74028",
"@version": 1
}
Neat!
Pass in the following Environment
or System
properties to configure:
IO_VACCO_SHAX_DEVMODE
orio.vacco.shax.devmode
to display messages like pino-pretty would. Defaults tofalse
.IO_VACCO_SHAX_SHOWDATETIME
orio.vacco.shax.showdatetime
to display or hide UTC times. Defaults totrue
.IO_VACCO_SHAX_LOGLEVEL
orio.vacco.shax.loglevel
to set the root logger level. Defaults toINFO
.IO_VACCO_SHAX_PRETTYPRINT
orio.vacco.shax.prettyprint
,true
to output formatted JSON,false
to output a single line. Defaults tofalse
.IO_VACCO_SHAX_LOGGER_X_Y_Z
orio.vacco.shax.logger.x.y.z
(multiple times with different values) to set individual logger namespace levels.OTEL_EXPORTER_OTLP_ENDPOINT
orotel.exporter.otlp.endpoint
to forward JSON logs and traces to an OTEL collector. Example:https://otel.example.io
.
Note:
shax
will search forEnvironment
variables, thenSystem
properties to load these values.
shax
is opinionated. It will:
- Output only to
stderr
, no Files or TCP/UDP forwarding. So plug your favorite log forwarding agent at the process level to capture log output. - Display time:
- In the UTC timezone only (the entire planet lives there).
- As
ISO-8601
extended offset date-time format. - As a Unix millisecond timestamp.
- Not support
slf4j
's MDC logging.
If you're not okay with these, then shax
is not for you.
Go back to logstash-logback-encoder as it may suit your use case better.
If you use log record transformer functions in your code, be aware that:
- A record transformer can only be assigned only once to a
Logger
. Reassignments will result in an error. - In
dev
mode, the only mandatory fields you must preserve in your record transformer are:[level, thread_name, message]
. - Any transformer function MAY be stateless and MUST be thread-safe, since many threads will be calling your code.
- Don't nest super complex objects in
ShArgument
s.