| //// |
| 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. |