blob: 4575259d31ffd7c91e09b44d53352eaf391867da [file] [log] [blame]
////
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
////
#set($dollar = '$')
= JSON Template Layout
Volkan Yazıcı <vy@apache.org>
`JsonTemplateLayout` is a customizable, efficient, and garbage-free JSON
emitting layout. It encodes ``LogEvent``s according to the structure described
by the JSON template provided. In a nutshell, it shines with its
* Customizable JSON structure (see `eventTemplate[Uri]` and
`stackTraceElementTemplate[Uri]` parameters)
* Customizable timestamp formatting (see `timestamp` parameter)
[#usage]
== Usage
Adding `log4j-layout-template-json` artifact to your list of dependencies is
enough to enable access to `JsonTemplateLayout` in your Log4j configuration:
[source,xml]
----
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-layout-template-json</artifactId>
<version>${Log4jReleaseVersion}</version>
</dependency>
----
For instance, given the following JSON template modelling the
https://github.com/logstash/log4j-jsonevent-layout[the official Logstash
`JSONEventLayoutV1`] (accessible via `classpath:LogstashJsonEventLayoutV1.json`)
[source,json]
----
{
"mdc": {
"$resolver": "mdc"
},
"exception": {
"exception_class": {
"$resolver": "exception",
"field": "className"
},
"exception_message": {
"$resolver": "exception",
"field": "message"
},
"stacktrace": {
"$resolver": "exception",
"field": "stackTrace",
"stackTrace": {
"stringified": true
}
}
},
"line_number": {
"$resolver": "source",
"field": "lineNumber"
},
"class": {
"$resolver": "source",
"field": "className"
},
"@version": 1,
"source_host": "${hostName}",
"message": {
"$resolver": "message",
"stringified": true
},
"thread_name": {
"$resolver": "thread",
"field": "name"
},
"@timestamp": {
"$resolver": "timestamp"
},
"level": {
"$resolver": "level",
"field": "name"
},
"file": {
"$resolver": "source",
"field": "fileName"
},
"method": {
"$resolver": "source",
"field": "methodName"
},
"logger_name": {
"$resolver": "logger",
"field": "name"
}
}
----
in combination with the below `log4j2.xml` configuration:
[source,xml]
----
<JsonTemplateLayout eventTemplateUri="classpath:LogstashJsonEventLayoutV1.json"/>
----
or with the below `log4j2.properties` configuration:
[source,ini]
----
appender.console.json.type = JsonTemplateLayout
appender.console.json.eventTemplateUri = classpath:LogstashJsonEventLayoutV1.json
----
`JsonTemplateLayout` emits JSON strings as follows:
[source,json]
----
{
"exception": {
"exception_class": "java.lang.RuntimeException",
"exception_message": "test",
"stacktrace": "java.lang.RuntimeException: test\n\tat org.apache.logging.log4j.JsonTemplateLayoutDemo.main(JsonTemplateLayoutDemo.java:11)\n"
},
"line_number": 12,
"class": "org.apache.logging.log4j.JsonTemplateLayoutDemo",
"@version": 1,
"source_host": "varlik",
"message": "Hello, error!",
"thread_name": "main",
"@timestamp": "2017-05-25T19:56:23.370+02:00",
"level": "ERROR",
"file": "JsonTemplateLayoutDemo.java",
"method": "main",
"logger_name": "org.apache.logging.log4j.JsonTemplateLayoutDemo"
}
----
[#layout-config]
== Layout Configuration
`JsonTemplateLayout` is configured with the following parameters:
.`JsonTemplateLayout` parameters
[cols="1m,1m,4"]
|===
| Parameter Name
| Type
| Description
| charset
| Charset
| `Charset` used for `String` encoding
| locationInfoEnabled
| boolean
| toggles access to the `LogEvent` source; file name, line number, etc.
(defaults to `false` set by `log4j.layout.jsonTemplate.locationInfoEnabled`
property)
| stackTraceEnabled
| boolean
| toggles access to the stack traces (defaults to `true` set by
`log4j.layout.jsonTemplate.stackTraceEnabled` property)
| eventTemplate
| String
| inline JSON template for rendering ``LogEvent``s (has priority over
`eventTemplateUri`, defaults to `null` set by
`log4j.layout.jsonTemplate.eventTemplate` property)
| eventTemplateUri
| String
| URI pointing to the JSON template for rendering ``LogEvent``s (defaults to
`classpath:EcsLayout.json` set by `log4j.layout.jsonTemplate.eventTemplateUri`
property)
| eventTemplateRootObjectKey
| String
| if given, puts the event template into a JSON object composed of a single
member with the given key (defaults to `null` set by
`log4j.layout.jsonTemplate.eventTemplateRootObjectKey`
property)
| eventTemplateAdditionalFields
| EventTemplateAdditionalField[]
| additional key-value pairs appended to the root of the event template
| stackTraceElementTemplate
| String
| inline JSON template for rendering ``StackTraceElement``s (has priority over
`stackTraceElementTemplateUri`, defaults to `null` set by
`log4j.layout.jsonTemplate.stackTraceElementTemplate` property)
| stackTraceElementTemplateUri
| String
| JSON template for rendering ``StackTraceElement``s (defaults to
`classpath:StackTraceElementLayout.json` set by
`log4j.layout.jsonTemplate.stackTraceElementTemplateUri` property)
| eventDelimiter
| String
| delimiter used for separating emitted ``LogEvent``s (defaults to
`System.lineSeparator()` set by `log4j.layout.jsonTemplate.eventDelimiter`
property)
| nullEventDelimiterEnabled
| boolean
| append `\0` (`null`) character to the end of every emitted `eventDelimiter`
(defaults to `false` set by
`log4j.layout.jsonTemplate.nullEventDelimiterEnabled` property)
| maxStringLength
| int
| truncate string values longer than the specified limit (defaults to 16384 set
by `log4j.layout.jsonTemplate.maxStringLength` property)
| truncatedStringSuffix
| String
| suffix to append to strings truncated due to exceeding `maxStringLength`
(defaults to `…` set by `log4j.layout.jsonTemplate.truncatedStringSuffix`
property)
| recyclerFactory
| RecyclerFactory
| recycling strategy that can either be `dummy`, `threadLocal`, or `queue`
(set by `log4j.layout.jsonTemplate.recyclerFactory` property)
|===
[#additional-event-template-fields]
=== Additonal event template fields
Additional event template fields are a convenient short-cut to add custom fields
to a template or override the existing ones. Following configuration overrides
the `host` field of the `GelfLayout.json` template and adds two new custom
fields:
.XML configuration with additional fields
[source,xml]
----
<JsonTemplateLayout eventTemplateUri="classpath:GelfLayout.json">
<EventTemplateAdditionalField key="host" value="www.apache.org"/>
<EventTemplateAdditionalField key="_serviceName" value="auth-service"/>
<EventTemplateAdditionalField key="_containerId" value="6ede3f0ca7d9"/>
</JsonTemplateLayout>
----
The default `format` for the added new fields are `String`.
One can also provide JSON-formatted additional fields:
.XML-formatted configuration with JSON-formatted additional fields
[source,xml]
----
<JsonTemplateLayout eventTemplateUri="classpath:GelfLayout.json">
<EventTemplateAdditionalField
key="marker"
format="JSON"
value='{"$resolver": "marker", "field": "name"}'/>
<EventTemplateAdditionalField
key="aNumber"
format="JSON"
value="1"/>
<EventTemplateAdditionalField
key="aList"
format="JSON"
value='[1, 2, "three"]'/>
</JsonTemplateLayout>
----
Additional event template fields can also be introduced using properties- and
YAML-formatted configuration:
.Properties-formatted configuration with JSON-formatted additional fields
[source, properties]
----
appender.console.layout.type = JsonTemplateLayout
appender.console.layout.eventTemplateUri = classpath:GelfLayout.json
appender.console.layout.eventTemplateAdditionalField[0].type = EventTemplateAdditionalField
appender.console.layout.eventTemplateAdditionalField[0].key = marker
appender.console.layout.eventTemplateAdditionalField[0].value = {"$resolver": "marker", "field": "name"}
appender.console.layout.eventTemplateAdditionalField[0].format = JSON
appender.console.layout.eventTemplateAdditionalField[1].type = EventTemplateAdditionalField
appender.console.layout.eventTemplateAdditionalField[1].key = aNumber
appender.console.layout.eventTemplateAdditionalField[1].value = 1
appender.console.layout.eventTemplateAdditionalField[1].format = JSON
appender.console.layout.eventTemplateAdditionalField[2].type = EventTemplateAdditionalField
appender.console.layout.eventTemplateAdditionalField[2].key = aList
appender.console.layout.eventTemplateAdditionalField[2].value = [1, 2, "three"]
appender.console.layout.eventTemplateAdditionalField[2].format = JSON
----
.YAML-formatted configuration with JSON-formatted additional fields
[source,yaml]
----
JsonTemplateLayout:
eventTemplateAdditionalField:
- key: "marker"
value: '{"$resolver": "marker", "field": "name"}'
format: "JSON"
- key: "aNumber"
value: "1"
format: "JSON"
- key: "aList"
value: '[1, 2, "three"]'
format: "JSON"
----
[#recycling-strategy]
=== Recycling strategy
`RecyclerFactory` plays a crucial role for determining the memory footprint of
the layout. Template resolvers employ it to create recyclers for objects that
they can reuse. The function of each `RecyclerFactory` and when one should
prefer one over another is explained below:
* `dummy` performs no recycling, hence each recycling attempt will result in a
new instance. This will obviously create a load on the garbage-collector. It
is a good choice for applications with low and medium log rate.
* `threadLocal` performs the best, since every instance is stored in
``ThreadLocal``s and accessed without any synchronization cost. Though this
might not be a desirable option for applications running with hundreds of
threads or more, e.g., a web servlet.
* `queue` is the best of both worlds. It allows recycling of objects up to a
certain number (`capacity`). When this limit is exceeded due to excessive
concurrent load (e.g., `capacity` is 50 but there are 51 threads concurrently
trying to log), it starts allocating. `queue` is a good strategy where
`threadLocal` is not desirable.
+
`queue` also accepts optional `supplier` (of type `java.util.Queue`, defaults to
`org.jctools.queues.MpmcArrayQueue.new` if JCTools is in the classpath;
otherwise `java.util.concurrent.ArrayBlockingQueue.new`) and `capacity` (of
type `int`, defaults to `max(8,2*cpuCount+1)`) parameters:
+
[source]
----
queue:supplier=org.jctools.queues.MpmcArrayQueue.new
queue:capacity=10
queue:supplier=java.util.concurrent.ArrayBlockingQueue.new,capacity=50
----
The default `RecyclerFactory` is `threadLocal`, if
`log4j2.enable.threadlocals=true`; otherwise, `queue`.
[#template-config]
== Template Configuration
Templates are configured by means of the following `JsonTemplateLayout`
parameters:
- `eventTemplate[Uri]` (for serializing ``LogEvent``s)
- `stackTraceElementTemplate[Uri]` (for serializing ``StackStraceElement``s)
- `eventTemplateAdditionalFields` (for extending the used event template)
[#event-templates]
=== Event Templates
`eventTemplate[Uri]` describes the JSON structure `JsonTemplateLayout` uses to
serialize ``LogEvent``s. The default configuration (accessible by
`log4j.layout.jsonTemplate.eventTemplate[Uri]` property) is set to
`classpath:EcsLayout.json` provided by the `log4j-layout-template-json`
artifact:
[source,json]
----
{
"@timestamp": {
"$resolver": "timestamp",
"pattern": {
"format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
"timeZone": "UTC"
}
},
"log.level": {
"$resolver": "level",
"field": "name"
},
"message": {
"$resolver": "message",
"stringified": true
},
"process.thread.name": {
"$resolver": "thread",
"field": "name"
},
"log.logger": {
"$resolver": "logger",
"field": "name"
},
"labels": {
"$resolver": "mdc",
"flatten": true,
"stringified": true
},
"tags": {
"$resolver": "ndc"
},
"error.type": {
"$resolver": "exception",
"field": "className"
},
"error.message": {
"$resolver": "exception",
"field": "message"
},
"error.stack_trace": {
"$resolver": "exception",
"field": "stackTrace",
"stackTrace": {
"stringified": true
}
}
}
----
`log4j-layout-template-json` artifact contains the following predefined event
templates:
- https://github.com/apache/logging-log4j2/tree/master/log4j-layout-template-json/src/main/resources/EcsLayout.json[`EcsLayout.json`]
described by https://www.elastic.co/guide/en/ecs/current/ecs-reference.html[the Elastic Common Schema (ECS) specification]
- https://github.com/apache/logging-log4j2/tree/master/log4j-layout-template-json/src/main/resources/LogstashJsonEventLayoutV1.json[`LogstashJsonEventLayoutV1.json`]
described in https://github.com/logstash/log4j-jsonevent-layout[Logstash
`json_event` pattern for log4j]
- https://github.com/apache/logging-log4j2/tree/master/log4j-layout-template-json/src/main/resources/GelfLayout.json[`GelfLayout.json`]
described by https://docs.graylog.org/en/3.1/pages/gelf.html#gelf-payload-specification[the
Graylog Extended Log Format (GELF) payload specification] with additional
`_thread` and `_logger` fields. (Here it is advised to override the obligatory
`host` field with a user provided constant via `eventTemplateAdditionalFields`
to avoid `hostName` property lookup at runtime, which incurs an extra cost.)
- https://github.com/apache/logging-log4j2/tree/master/log4j-layout-template-json/src/main/resources/JsonLayout.json[`JsonLayout.json`]
providing the exact JSON structure generated by link:layouts.html#JSONLayout[`JsonLayout`]
with the exception of `thrown` field. (`JsonLayout` serializes the `Throwable`
as is via Jackson `ObjectMapper`, whereas `JsonLayout.json` template of
`JsonTemplateLayout` employs the `StackTraceElementLayout.json` template
for stack traces to generate a document-store-friendly flat structure.)
[#event-template-resolvers]
==== Event Template Resolvers
[#event-template-resolver-endOfBatch]
===== `endOfBatch`
Resolves `logEvent.isEndOfBatch()` boolean flag:
[source,json]
----
{
"$resolver": "endOfBatch"
}
----
[#event-template-resolver-exception]
===== `exception`
[source]
----
config = field , [ stringified ] , [ stackTrace ]
field = "field" -> ( "className" \| "message" \| "stackTrace" )
stackTrace = "stackTrace" -> stringified
stringified = "stringified" -> ( boolean \| truncation )
truncation = "truncation" -> (
[ suffix ]
, [ pointMatcherStrings ]
, [ pointMatcherRegexes ]
)
suffix = "suffix" -> string
pointMatcherStrings = "pointMatcherStrings" -> string[]
pointMatcherRegexes = "pointMatcherRegexes" -> string[]
----
Resolves fields of the `Throwable` returned by `logEvent.getThrown()`.
`stringified` is set to `false` by default. `stringified` at the root level is
*deprecated* in favor of `stackTrace.stringified`, which has precedence if both
are provided.
`pointMatcherStrings` and `pointMatcherRegexes` enable the truncation of
stringified stack traces after the given matching point. If both parameters are
provided, `pointMatcherStrings` will be checked first.
If a stringified stack trace truncation takes place, it will be indicated with
`suffix`, which by default is set to the configured `truncatedStringSuffix` in
the layout, unless explicitly provided.
Note that this resolver is toggled by
`log4j.layout.jsonTemplate.stackTraceEnabled` property.
[WARNING]
====
Since `Throwable#getStackTrace()` clones the original `StackTraceElement[]`,
access to (and hence rendering of) stack traces are not garbage-free.
Each `pointMatcherRegexes` item triggers a `Pattern#matcher()` call, which is
not garbage-free either.
====
Resolve `logEvent.getThrown().getClass().getCanonicalName()`:
[source,json]
----
{
"$resolver": "exception",
"field": "className"
}
----
Resolve the stack trace into a list of `StackTraceElement` objects:
[source,json]
----
{
"$resolver": "exception",
"field": "stackTrace"
}
----
Resolve the stack trace into a string field:
[source,json]
----
{
"$resolver": "exception",
"field": "stackTrace",
"stackTrace": {
"stringified": true
}
}
----
Resolve the stack trace into a string field such that the content will be
truncated by the given point matcher:
[source,json]
----
{
"$resolver": "exception",
"field": "stackTrace",
"stackTrace": {
"stringified": {
"truncation": {
"suffix": ">",
"pointMatcherStrings": ["at javax.servlet.http.HttpServlet.service"]
}
}
}
}
----
[#event-template-resolver-exceptionRootCause]
===== `exceptionRootCause`
Resolves the fields of the innermost `Throwable` returned by
`logEvent.getThrown()`. Its syntax and garbage-footprint are identical to the
link:#event-template-exception[`exception`] resolver.
[#event-template-resolver-level]
===== `level`
[source]
----
config = field , [ severity ]
field = "field" -> ( "name" | "severity" )
severity = severity-field
severity-field = "field" -> ( "keyword" | "code" )
----
Resolves the fields of the `logEvent.getLevel()`.
Resolve the level name:
[source,json]
----
{
"$resolver": "level",
"field": "name"
}
----
Resolve the https://en.wikipedia.org/wiki/Syslog#Severity_levels[Syslog severity]
keyword:
[source,json]
----
{
"$resolver": "level",
"field": "severity",
"severity": {
"field": "keyword"
}
}
----
Resolve the https://en.wikipedia.org/wiki/Syslog#Severity_levels[Syslog severity]
code:
[source,json]
----
{
"$resolver": "level",
"field": "severity",
"severity": {
"field": "code"
}
}
----
[#event-template-resolver-logger]
===== `logger`
[source]
----
config = "field" -> ( "name" | "fqcn" )
----
Resolves `logEvent.getLoggerFqcn()` and `logEvent.getLoggerName()`.
Resolve the logger name:
[source,json]
----
{
"$resolver": "logger",
"field": "name"
}
----
Resolve the logger's fully qualified class name:
[source,json]
----
{
"$resolver": "logger",
"field": "fqcn"
}
----
[#event-template-resolver-main]
===== `main`
[source]
----
config = ( index | key )
index = "index" -> number
key = "key" -> string
----
Performs link:lookups.html#AppMainArgsLookup[Main Argument Lookup] for the
given `index` or `key`.
Resolve the 1st `main()` method argument:
[source,json]
----
{
"$resolver": "main",
"index": 0
}
----
Resolve the argument coming right after `--userId`:
[source,json]
----
{
"$resolver": "main",
"key": "--userId"
}
----
[#event-template-resolver-map]
===== `map`
Resolves ``MapMessage``s. See link:#map-resolver-template[Map Resolver Template]
for details.
[#event-template-resolver-mdc]
===== `mdc`
Resolves Mapped Diagnostic Context (MDC), aka. Thread Context Data. See
link:#map-resolver-template[Map Resolver Template] for details.
[WARNING]
====
`log4j2.garbagefreeThreadContextMap` flag needs to be turned on to iterate
the map without allocations.
====
[#event-template-resolver-message]
===== `message`
[source]
----
config = [ stringified ] , [ fallbackKey ]
stringified = "stringified" -> boolean
fallbackKey = "fallbackKey" -> string
----
Resolves `logEvent.getMessage()`.
[WARNING]
====
For simple string messages, the resolution is performed without allocations.
For ``ObjectMessage``s and ``MultiformatMessage``s, it depends.
====
Resolve the message into a string:
[source,json]
----
{
"$resolver": "message",
"stringified": true
}
----
Resolve the message such that if it is an `ObjectMessage` or a
`MultiformatMessage` with JSON support, its type (string, list, object, etc.)
will be retained:
[source,json]
----
{
"$resolver": "message"
}
----
Given the above configuration, a `SimpleMessage` will generate a `"sample log
message"`, whereas a `MapMessage` will generate a `{"action": "login",
"sessionId": "87asd97a"}`. Certain indexed log storage systems (e.g.,
https://www.elastic.co/elasticsearch/[Elasticsearch]) will not allow both values
to coexist due to type mismatch: one is a `string` while the other is an `object`.
Here one can use a `fallbackKey` to work around the problem:
[source,json]
----
{
"$resolver": "message",
"fallbackKey": "formattedMessage"
}
----
Using this configuration, a `SimpleMessage` will generate a
`{"formattedMessage": "sample log message"}` and a `MapMessage` will generate a
`{"action": "login", "sessionId": "87asd97a"}`. Note that both emitted JSONs are
of type `object` and have no type-conflicting fields.
[#event-template-resolver-messageParameter]
===== `messageParameter`
[source]
----
config = [ stringified ] , [ index ]
stringified = "stringified" -> boolean
index = "index" -> number
----
Resolves `logEvent.getMessage().getParameters()`.
[WARNING]
====
Regarding garbage footprint, `stringified` flag translates to
`String.valueOf(value)`, hence mind not-`String`-typed values. Further,
`logEvent.getMessage()` is expected to implement `ParameterVisitable` interface,
which is the case if `log4j2.enableThreadLocals` property set to true.
====
Resolve the message parameters into an array:
[source,json]
----
{
"$resolver": "messageParameter"
}
----
Resolve the string representation of all message parameters into an array:
[source,json]
----
{
"$resolver": "messageParameter",
"stringified": true
}
----
Resolve the first message parameter:
[source,json]
----
{
"$resolver": "messageParameter",
"index": 0
}
----
Resolve the string representation of the first message parameter:
[source,json]
----
{
"$resolver": "messageParameter",
"index": 0,
"stringified": true
}
----
[#event-template-resolver-ndc]
===== `ndc`
[source]
----
config = [ pattern ]
pattern = "pattern" -> string
----
Resolves the Nested Diagnostic Context (NDC), aka. Thread Context Stack,
`String[]` returned by `logEvent.getContextStack()`.
Resolve all NDC values into a list:
[source,json]
----
{
"$resolver": "ndc"
}
----
Resolve all NDC values matching with the `pattern` regex:
[source,json]
----
{
"$resolver": "ndc",
"pattern": "user(Role|Rank):\\w+"
}
----
[#event-template-resolver-pattern]
===== `pattern`
[source]
----
config = pattern , [ stackTraceEnabled ]
pattern = "pattern" -> string
stackTraceEnabled = "stackTraceEnabled" -> boolean
----
Resolver delegating to link:layouts.html#PatternLayout[`PatternLayout`].
The default value of `stackTraceEnabled` is inherited from the parent
`JsonTemplateLayout`.
Resolve the string produced by `%p %c{1.} [%t] %X{userId} %X %m%ex` pattern:
[source,json]
----
{
"$resolver": "pattern",
"pattern": "%p %c{1.} [%t] %X{userId} %X %m%ex"
}
----
[#event-template-resolver-source]
===== `source`
[source]
----
config = "field" -> (
"className" |
"fileName" |
"methodName" |
"lineNumber" )
----
Resolves the fields of the `StackTraceElement` returned by
`logEvent.getSource()`.
Note that this resolver is toggled by
`log4j.layout.jsonTemplate.locationInfoEnabled` property.
Resolve the line number:
[source,json]
----
{
"$resolver": "source",
"field": "lineNumber"
}
----
[#event-template-resolver-thread]
===== `thread`
[source]
----
config = "field" -> ( "name" | "id" | "priority" )
----
Resolves `logEvent.getThreadId()`, `logEvent.getThreadName()`,
`logEvent.getThreadPriority()`.
Resolve the thread name:
[source,json]
----
{
"$resolver": "thread",
"field": "name"
}
----
[#event-template-resolver-timestamp]
===== `timestamp`
[source]
----
config = [ patternConfig | epochConfig ]
patternConfig = "pattern" -> ( [ format ] , [ timeZone ] , [ locale ] )
format = "format" -> string
timeZone = "timeZone" -> string
locale = "locale" -> (
language |
( language , "_" , country ) |
( language , "_" , country , "_" , variant )
)
epochConfig = "epoch" -> ( unit , [ rounded ] )
unit = "unit" -> (
"nanos" |
"millis" |
"secs" |
"millis.nanos" |
"secs.nanos" |
)
rounded = "rounded" -> boolean
----
Resolves `logEvent.getInstant()` in various forms.
.`timestamp` template resolver examples
[cols="5,2m"]
|===
| Configuration
| Output
a|
[source,json]
----
{
"$resolver": "timestamp"
}
----
| 2020-02-07T13:38:47.098+02:00
a|
[source,json]
----
{
"$resolver": "timestamp",
"pattern": {
"format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
"timeZone": "UTC",
"locale": "en_US"
}
}
----
| 2020-02-07T13:38:47.098Z
a|
[source,json]
----
{
"$resolver": "timestamp",
"epoch": {
"unit": "secs"
}
}
----
| 1581082727.982123456
a|
[source,json]
----
{
"$resolver": "timestamp",
"epoch": {
"unit": "secs",
"rounded": true
}
}
----
| 1581082727
a|
[source,json]
----
{
"$resolver": "timestamp",
"epoch": {
"unit": "secs.nanos"
}
}
----
| 982123456
a|
[source,json]
----
{
"$resolver": "timestamp",
"epoch": {
"unit": "millis"
}
}
----
| 1581082727982.123456
a|
[source,json]
----
{
"$resolver": "timestamp",
"epoch": {
"unit": "millis",
"rounded": true
}
}
----
| 1581082727982
a|
[source,json]
----
{
"$resolver": "timestamp",
"epoch": {
"unit": "millis.nanos"
}
}
----
| 123456
a|
[source,json]
----
{
"$resolver": "timestamp",
"epoch": {
"unit": "nanos"
}
}
----
| 1581082727982123456
|===
[#map-resolver-template]
==== Map Resolver Template
`ReadOnlyStringMap` is Log4j's `Map<String, Object>` equivalent with
garbage-free accessors and heavily employed throughout the code base. It is the
data structure backing both Mapped Diagnostic Context (MDC), aka. Thread Context
Data and `MapMessage` implementations. Hence template resolvers for both of
these are provided by a single backend: `ReadOnlyStringMapResolver`. Put another
way, both `mdc` and `map` resolvers support identical configuration, behaviour,
and garbage footprint, which are detailed below.
[source]
----
config = singleAccess | multiAccess
singleAccess = key , [ stringified ]
key = "key" -> string
stringified = "stringified" -> boolean
multiAccess = [ pattern ] , [ flatten ] , [ stringified ]
pattern = "pattern" -> string
flatten = "flatten" -> ( boolean | flattenConfig )
flattenConfig = [ flattenPrefix ]
flattenPrefix = "prefix" -> string
----
`singleAccess` resolves a single field, whilst `multiAccess` resolves a
multitude of fields. If `flatten` is provided, `multiAccess` merges the fields
with the parent, otherwise creates a new JSON object containing the values.
[WARNING]
====
Regarding garbage footprint, `stringified` flag translates to
`String.valueOf(value)`, hence mind not-`String`-typed values.
====
`"${dollar}resolver"` is left out in the following examples, since it is to be
defined by the actual resolver, e.g., `map`, `mdc`.
Resolve the value of the field keyed with `userRole`:
[source,json]
----
{
"$resolver": "…",
"key": "userRole"
}
----
Resolve the string representation of the `userRank` field value:
[source,json]
----
{
"$resolver": "…",
"key": "userRank",
"stringified": true
}
----
Resolve all fields into an object:
[source,json]
----
{
"$resolver": "…"
}
----
Resolve all fields into an object such that values are converted to string:
[source,json]
----
{
"$resolver": "…",
"stringified": true
}
----
Merge all fields whose keys are matching with the `user(Role|Rank)` regex into
the parent:
[source,json]
----
{
"$resolver": "…",
"flatten": true,
"pattern": "user(Role|Rank)"
}
----
After converting the corresponding field values to string, merge all fields to
parent such that keys are prefixed with `_`:
[source,json]
----
{
"$resolver": "…",
"stringified": true,
"flatten": {
"prefix": "_"
}
}
----
[#stack-trace-element-templates]
=== Stack Trace Element Templates
`stackTraceElement[Uri]` describes the JSON structure `JsonTemplateLayout` uses
to format ``StackTraceElement``s. The default configuration (accessible by
`log4j.layout.jsonTemplate.stackTraceElementTemplate[Uri]` property) is set to
`classpath:StackTraceElementLayout.json` provided by the
`log4j-layout-template-json` artifact:
[source,json]
----
{
"class": {
"$resolver": "stackTraceElement",
"field": "className"
},
"method": {
"$resolver": "stackTraceElement",
"field": "methodName"
},
"file": {
"$resolver": "stackTraceElement",
"field": "fileName"
},
"line": {
"$resolver": "stackTraceElement",
"field": "lineNumber"
}
}
----
The allowed template configuration syntax is as follows:
[source]
----
config = "field" -> (
"className" |
"fileName" |
"methodName" |
"lineNumber" )
----
All above accesses to `StackTraceElement` is garbage-free.
[#features]
== Features
Below is a feature comparison matrix between `JsonTemplateLayout` and
alternatives.
.Feature comparison matrix
[cols="3,1,1,1,1"]
|===
| Feature
| `JsonTemplateLayout`
| link:layouts.html#JSONLayout[`JsonLayout`]
| link:layouts.html#GELFLayout[`GelfLayout`]
| https://github.com/elastic/java-ecs-logging/tree/master/log4j2-ecs-layout[`EcsLayout`]
| Java version
| 8
| 8
| 8
| 6
| Dependencies
| None
| Jackson
| None
| None
| Schema customization?
| ✓
| ✕
| ✕
| ✕
| Timestamp customization?
| ✓
| ✕
| ✕
| ✕
| (Almost) garbage-free?
| ✓
| ✕
| ✓
| ✓
| Custom typed `Message` serialization?
| ✓
| ✕
| ✕
| ?footnote:[Only for ``ObjectMessage``s and if Jackson is in the classpath.]
| Custom typed `MDC` value serialization?
| ✓
| ✕
| ✕
| ✕
| Rendering stack traces as array?
| ✓
| ✓
| ✕
| ✓
| Stack trace truncation?
| ✓
| ✕
| ✕
| ✕
| JSON pretty print?
| ✕
| ✓
| ✕
| ✕
| Additional string fields?
| ✓
| ✓
| ✓
| ✓
| Additional JSON fields?
| ✓
| ✕
| ✕
| ✕
|===
[#faq]
== F.A.Q.
[#faq-lookups]
=== Are lookups supported in templates?
Yes, link:lookups.html[lookups] (e.g., `${dollar}{java:version}`,
`${dollar}{env:USER}`, `${dollar}{date:MM-dd-yyyy}`) are supported in string
literals of templates. Though note that they are not garbage-free.
=== Are recursive collections supported?
No. Consider a `Message` containing a recursive value as follows:
[source,java]
----
Object[] recursiveCollection = new Object[1];
recursiveCollection[0] = recursiveCollection;
----
While the exact exception might vary, you will most like get a
`StackOverflowError` while trying to render `recursiveCollection` into a
`String`. Note that this is also the default behaviour for other Java standard
library methods, e.g., `Arrays.toString()`. Hence mind self references while
logging.
[#faq-garbage-free]
=== Is `JsonTemplateLayout` garbage-free?
Yes, if the garbage-free layout behaviour toggling properties
`log4j2.enableDirectEncoders` and `log4j2.garbagefreeThreadContextMap` are
enabled. Take into account the following caveats:
* The configured link:#recycling-strategy[recycling strategy] might not be
garbage-free.
* Since `Throwable#getStackTrace()` clones the original `StackTraceElement[]`,
access to (and hence rendering of) stack traces are not garbage-free.
* Serialization of ``MapMessage``s and ``ObjectMessage``s are mostly
garbage-free except for certain types (e.g., `BigDecimal`, `BigInteger`,
``Collection``s with the exception of `List`).
* link:lookups.html[Lookups] (that is, `${...}` variables) are not garbage-free.
Don't forget to checkout link:#event-template-resolvers[the notes on garbage footprint of resolvers]
you employ in templates.