<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-layout-template-json</artifactId>
<version>2.23.1</version>
</dependency>
JSON 模板布局
用法
将 log4j-layout-template-json 工件添加到您的依赖项列表中,就足以在您的 Log4j 配置中启用对 JsonTemplateLayout 的访问
例如,假设以下 JSON 模板对 Elastic Common Schema (ECS) 规范 进行建模(可通过 classpath:EcsLayout.json 访问)
{
"@timestamp": {
"$resolver": "timestamp",
"pattern": {
"format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
"timeZone": "UTC"
}
},
"ecs.version": "1.2.0",
"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
}
}
}
结合以下 log4j2.xml 配置
<JsonTemplateLayout eventTemplateUri="classpath:EcsLayout.json"/>
或以下 log4j2.properties 配置
appender.console.layout.type = JsonTemplateLayout
appender.console.layout.eventTemplateUri = classpath:EcsLayout.json
JsonTemplateLayout 生成如下 JSON
{
"@timestamp": "2017-05-25T19:56:23.370Z",
"ecs.version": "1.2.0",
"log.level": "ERROR",
"message": "Hello, error!",
"process.thread.name": "main",
"log.logger": "org.apache.logging.log4j.JsonTemplateLayoutDemo",
"error.type": "java.lang.RuntimeException",
"error.message": "test",
"error.stack_trace": "java.lang.RuntimeException: test\n\tat org.apache.logging.log4j.JsonTemplateLayoutDemo.main(JsonTemplateLayoutDemo.java:11)\n"
}
布局配置
JsonTemplateLayout 使用以下参数进行配置
|
|
描述 |
|
|
用于 |
|
|
切换对 |
|
|
切换对堆栈跟踪的访问(默认为 |
|
|
用于渲染 |
|
|
指向用于渲染 |
|
|
如果存在,则将事件模板放入一个 JSON 对象中,该对象由一个具有提供键的成员组成(默认为 |
|
|
附加到事件模板根部的附加键值对 |
|
|
用于渲染 |
|
|
指向用于渲染 |
|
|
用于分隔渲染的 |
|
|
将 |
|
|
截断长度超过指定限制的字符串值(默认为 16384,由 |
|
|
追加到由于超过 |
|
|
回收策略,可以是 |
附加事件模板字段
附加事件模板字段是将自定义字段添加到模板或覆盖现有字段的便捷快捷方式。以下配置将覆盖 GelfLayout.json 模板的 host 字段,并添加两个新的自定义字段
<JsonTemplateLayout eventTemplateUri="classpath:GelfLayout.json">
<EventTemplateAdditionalField key="host" value="www.apache.org"/>
<EventTemplateAdditionalField key="_serviceName" value="auth-service"/>
<EventTemplateAdditionalField key="_containerId" value="6ede3f0ca7d9"/>
</JsonTemplateLayout>
添加的新字段的默认 format 为 String。也可以提供 JSON 格式的附加字段
<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>
可以使用属性、YAML 和 JSON 格式的配置引入附加事件模板字段
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
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"
{
"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"
}
]
}
}
回收策略
RecyclerFactory 在确定布局的内存占用方面起着至关重要的作用。模板解析器使用它来创建它们可以重复使用的对象的回收器。每个 RecyclerFactory 的行为以及何时应该优先选择一个而不是另一个将在下面解释
-
dummy不执行任何回收,因此每次回收尝试都会导致一个新实例。这显然会给垃圾收集器带来负担。对于日志速率低和中等应用程序来说,这是一个不错的选择。 -
threadLocal的性能最佳,因为每个实例都存储在ThreadLocal中,并且可以无任何同步成本地访问。尽管对于运行数百个或更多线程的应用程序(例如,Web servlet)来说,这可能不是一个理想的选择。 -
queue是两全其美的选择。它允许回收一定数量的对象(capacity)。当由于过度的并发负载而超过此限制时(例如,capacity为 50,但有 51 个线程同时尝试记录),它将开始分配。queue是一种很好的策略,在threadLocal不理想的情况下使用。queue还接受可选的supplier(类型为java.util.Queue,如果 JCTools 在类路径中,则默认为org.jctools.queues.MpmcArrayQueue.new;否则为java.util.concurrent.ArrayBlockingQueue.new)和capacity(类型为int,默认为max(8,2*cpuCount+1))参数queue回收策略的示例配置queue:supplier=org.jctools.queues.MpmcArrayQueue.new queue:capacity=10 queue:supplier=java.util.concurrent.ArrayBlockingQueue.new,capacity=50
如果 log4j2.enable.threadlocals=true,则默认的 RecyclerFactory 为 threadLocal;否则为 queue。
有关如何引入自定义 RecyclerFactory 实现的详细信息,请参见 扩展回收器工厂。
模板配置
模板通过以下 JsonTemplateLayout 参数进行配置
-
eventTemplate[Uri](用于序列化LogEvent) -
stackTraceElementTemplate[Uri](用于序列化StackStraceElement) -
eventTemplateAdditionalField(用于扩展使用的事件模板)
事件模板
eventTemplate[Uri] 描述了 JsonTemplateLayout 用于序列化 LogEvent 的 JSON 结构。默认配置(可通过 log4j.layout.jsonTemplate.eventTemplate[Uri] 属性访问)设置为 log4j-layout-template-json 工件提供的 classpath:EcsLayout.json,其中包含以下预定义的事件模板
-
LogstashJsonEventLayoutV1.json在 Logstashjson_event模式 for log4j 中描述 -
GelfLayout.json由 Graylog Extended Log Format (GELF) 负载规范 描述,并包含附加的_thread和_logger字段。(建议使用 附加事件模板字段 覆盖强制性的host字段,以避免在运行时查找hostName属性,这会带来额外的成本。) -
GcpLayout.json由 Google Cloud Platform 结构化日志记录 描述,并添加了_thread、_logger和_exception字段。异常跟踪(如果有)将写入_exception字段以及message字段 - 前者对于显式搜索/分析结构化异常信息很有用,而后者是 Google 预期的异常位置,并与 Google 错误报告 集成。 -
JsonLayout.json提供了由JsonLayout生成的确切 JSON 结构,但thrown字段除外。(JsonLayout通过 JacksonObjectMapper按原样序列化Throwable,而JsonLayout.json模板的JsonTemplateLayout使用StackTraceElementLayout.json模板来生成堆栈跟踪,以生成对文档存储友好的扁平结构。)
事件模板解析器
事件模板解析器使用 LogEvent 并渲染其在 JSON 中声明位置的特定属性。例如,marker 解析器渲染事件的标记,level 解析器渲染级别,等等。事件模板解析器用一个包含 $resolver 键的特殊对象表示
level 解析器用法的示例事件模板{
"version": "1.0",
"level": {
"$resolver": "level",
"field": "name"
}
}
这里 version 字段将按原样渲染,而 level 字段将由 level 解析器填充。也就是说,此模板将生成类似于以下内容的 JSON
{
"version": "1.0",
"level": "INFO"
}
下面将详细提供所有可用事件模板解析器的完整列表。
counter
config = [ start ] , [ overflowing ] , [ stringified ]
start = "start" -> number
overflowing = "overflowing" -> boolean
stringified = "stringified" -> boolean
从内部计数器解析一个数字。
除非提供,否则 start 和 overflowing 分别默认设置为零和 true。
当 stringified 启用时,默认情况下设置为 false,解析的数字将转换为字符串。
|
警告
|
当 |
示例
解析从 0 开始的数字序列。一旦达到 Long.MAX_VALUE,计数器将溢出到 Long.MIN_VALUE。
{
"$resolver": "counter"
}
解析从 1000 开始的数字序列。一旦达到 Long.MAX_VALUE,计数器将溢出到 Long.MIN_VALUE。
{
"$resolver": "counter",
"start": 1000
}
解析从 0 开始的数字序列,并只要 JVM 堆允许就一直进行下去。
{
"$resolver": "counter",
"overflowing": false
}
caseConverter
config = case , input , [ locale ] , [ errorHandlingStrategy ]
input = JSON
case = "case" -> ( "upper" | "lower" )
locale = "locale" -> (
language |
( language , "_" , country ) |
( language , "_" , country , "_" , variant )
)
errorHandlingStrategy = "errorHandlingStrategy" -> (
"fail" |
"pass" |
"replace"
)
replacement = "replacement" -> JSON
转换字符串值的案例。
input 可以是任何可用的模板值;例如,JSON 文字、查找字符串、指向另一个解析器的对象。
除非提供,否则 locale 指向由 JsonTemplateLayoutDefaults.getLocale() 返回的区域设置,该区域设置由 log4j.layout.jsonTemplate.locale 系统属性配置,默认情况下设置为默认系统区域设置。
errorHandlingStrategy 确定输入无法解析为字符串值或大小写转换引发异常时的行为
-
fail传播失败 -
pass导致解析的值按原样传递 -
replace抑制失败并将其替换为replacement,默认情况下设置为null
errorHandlingStrategy 默认情况下设置为 replace。
大多数情况下,JSON 日志会持久化到存储解决方案(例如 Elasticsearch)中,该解决方案在字段上保留静态类型索引。因此,如果始终期望字段为字符串类型,则在 errorHandlingStrategy 中使用非字符串 replacement 或 pass 可能会导致存储级别出现类型不兼容问题。
|
警告
|
除非输入值被完整 |
示例
将解析的日志级别字符串转换为大写
{
"$resolver": "caseConverter",
"case": "upper",
"input": {
"$resolver": "level",
"field": "name"
}
}
使用 nl_NL 区域设置将解析的 USER 环境变量转换为小写
{
"$resolver": "caseConverter",
"case": "lower",
"locale": "nl_NL",
"input": "${env:USER}"
}
将解析的 sessionId 线程上下文数据 (MDC) 转换为小写
{
"$resolver": "caseConverter",
"case": "lower",
"input": {
"$resolver": "mdc",
"key": "sessionId"
}
}
在上面,如果 sessionId MDC 解析为一个数字,例如,大小写转换将失败。由于 errorHandlingStrategy 设置为 replace,并且替换默认情况下设置为 null,因此解析的值将为 null。可以抑制此行为,并让解析的 sessionId 数字保持原样
{
"$resolver": "caseConverter",
"case": "lower",
"input": {
"$resolver": "mdc",
"key": "sessionId"
},
"errorHandlingStrategy": "pass"
}
或将其替换为自定义字符串
{
"$resolver": "caseConverter",
"case": "lower",
"input": {
"$resolver": "mdc",
"key": "sessionId"
},
"errorHandlingStrategy": "replace",
"replacement": "unknown"
}
endOfBatch
{
"$resolver": "endOfBatch"
}
解析 logEvent.isEndOfBatch() 布尔标志。
exception
config = field , [ stringified ] , [ stackTrace ]
field = "field" -> ( "className" | "message" | "stackTrace" )
stackTrace = "stackTrace" -> (
[ stringified ]
, [ elementTemplate ]
)
stringified = "stringified" -> ( boolean | truncation )
truncation = "truncation" -> (
[ suffix ]
, [ pointMatcherStrings ]
, [ pointMatcherRegexes ]
)
suffix = "suffix" -> string
pointMatcherStrings = "pointMatcherStrings" -> string[]
pointMatcherRegexes = "pointMatcherRegexes" -> string[]
elementTemplate = "elementTemplate" -> object
解析由 logEvent.getThrown() 返回的 Throwable 的字段。
stringified 默认情况下设置为 false。根级别的 stringified 已被 stackTrace.stringified 弃用,如果两者都提供,则 stackTrace.stringified 优先。
pointMatcherStrings 和 pointMatcherRegexes 允许在给定匹配点后截断字符串化的堆栈跟踪。如果两个参数都提供,则首先检查 pointMatcherStrings。
如果发生字符串化的堆栈跟踪截断,则将使用 suffix 指示,默认情况下设置为布局中配置的 truncatedStringSuffix,除非显式提供。每个截断后缀都以换行符为前缀。
字符串化的堆栈跟踪截断在 Caused by: 和 Suppressed: 标签块中进行。也就是说,匹配器在每个标签中独立执行。
elementTemplate 是一个对象,描述在解析 StackTraceElement 数组时要使用的模板。如果 stringified 设置为 true,则 elementTemplate 将被丢弃。默认情况下,elementTemplate 设置为 null,而是从布局配置中填充。也就是说,堆栈跟踪元素模板也可以使用 stackTraceElementTemplate[Uri] 布局配置参数提供。要使用的模板将按以下顺序确定
-
解析器配置中提供的
elementTemplate -
布局配置中的
stackTraceElementTemplate参数(默认情况下从log4j.layout.jsonTemplate.stackTraceElementTemplate系统属性填充) -
布局配置中的
stackTraceElementTemplateUri参数(默认情况下从log4j.layout.jsonTemplate.stackTraceElementTemplateUri系统属性填充)
有关堆栈跟踪元素模板中可用解析器的列表,请参见 堆栈跟踪元素模板。
请注意,此解析器由 log4j.layout.jsonTemplate.stackTraceEnabled 属性切换。
|
警告
|
由于 每个 |
示例
解析 logEvent.getThrown().getClass().getCanonicalName()
{
"$resolver": "exception",
"field": "className"
}
将堆栈跟踪解析为 StackTraceElement 对象列表
{
"$resolver": "exception",
"field": "stackTrace"
}
将堆栈跟踪解析为字符串字段
{
"$resolver": "exception",
"field": "stackTrace",
"stackTrace": {
"stringified": true
}
}
将堆栈跟踪解析为字符串字段,以便在给定点匹配器后截断内容
{
"$resolver": "exception",
"field": "stackTrace",
"stackTrace": {
"stringified": {
"truncation": {
"suffix": "... [truncated]",
"pointMatcherStrings": ["at javax.servlet.http.HttpServlet.service"]
}
}
}
}
将堆栈跟踪解析为由提供的堆栈跟踪元素模板描述的对象
{
"$resolver": "exception",
"field": "stackTrace",
"stackTrace": {
"elementTemplate": {
"class": {
"$resolver": "stackTraceElement",
"field": "className"
},
"method": {
"$resolver": "stackTraceElement",
"field": "methodName"
},
"file": {
"$resolver": "stackTraceElement",
"field": "fileName"
},
"line": {
"$resolver": "stackTraceElement",
"field": "lineNumber"
}
}
}
}
有关 StackTraceElement 模板中可用解析器的更多详细信息,请参见 堆栈跟踪元素模板。
exceptionRootCause
解析由 logEvent.getThrown() 返回的最内部 Throwable 的字段。其语法和垃圾占用与 exception 解析器相同。
level
config = field , [ severity ]
field = "field" -> ( "name" | "severity" )
severity = severity-field
severity-field = "field" -> ( "keyword" | "code" )
解析 logEvent.getLevel() 的字段。
示例
解析级别名称
{
"$resolver": "level",
"field": "name"
}
解析 Syslog 严重性 关键字
{
"$resolver": "level",
"field": "severity",
"severity": {
"field": "keyword"
}
}
解析 Syslog 严重性 代码
{
"$resolver": "level",
"field": "severity",
"severity": {
"field": "code"
}
}
logger
config = "field" -> ( "name" | "fqcn" )
解析 logEvent.getLoggerFqcn() 和 logEvent.getLoggerName()。
示例
解析记录器名称
{
"$resolver": "logger",
"field": "name"
}
解析记录器的完全限定类名
{
"$resolver": "logger",
"field": "fqcn"
}
main
config = ( index | key )
index = "index" -> number
key = "key" -> string
对给定的 index 或 key 执行 主参数查找。
示例
解析第一个 main() 方法参数
{
"$resolver": "main",
"index": 0
}
解析紧随 --userId 后的参数
{
"$resolver": "main",
"key": "--userId"
}
map
解析 MapMessage。有关详细信息,请参见 地图解析器模板。
marker
config = "field" -> ( "name" | "parents" )
解析 logEvent.getMarker()。
示例
解析标记名称
{
"$resolver": "marker",
"field": "name"
}
解析标记父级的名称
{
"$resolver": "marker",
"field": "parents"
}
mdc
解析映射诊断上下文 (MDC),也称为线程上下文数据。有关详细信息,请参见 地图解析器模板。
|
警告
|
需要打开 |
message
config = [ stringified ] , [ fallbackKey ]
stringified = "stringified" -> boolean
fallbackKey = "fallbackKey" -> string
解析 logEvent.getMessage()。
|
警告
|
对于简单的字符串消息,解析在没有分配的情况下执行。对于 |
示例
将消息解析为字符串
{
"$resolver": "message",
"stringified": true
}
解析消息,以便如果它是 ObjectMessage 或具有 JSON 支持的 MultiformatMessage,则会保留其类型(字符串、列表、对象等)
{
"$resolver": "message"
}
鉴于上述配置,SimpleMessage 将生成 "sample log message",而 MapMessage 将生成 {"action": "login", "sessionId": "87asd97a"}。某些索引日志存储系统(例如 Elasticsearch)不允许这两个值共存,因为类型不匹配:一个是 string,另一个是 object。这里可以使用 fallbackKey 来解决此问题
{
"$resolver": "message",
"fallbackKey": "formattedMessage"
}
使用此配置,SimpleMessage 将生成 {"formattedMessage": "sample log message"},而 MapMessage 将生成 {"action": "login", "sessionId": "87asd97a"}。请注意,两个发出的 JSON 都是 object 类型,并且没有类型冲突的字段。
messageParameter
config = [ stringified ] , [ index ]
stringified = "stringified" -> boolean
index = "index" -> number
解析 logEvent.getMessage().getParameters()。
|
警告
|
关于垃圾占用, |
示例
将消息参数解析为数组
{
"$resolver": "messageParameter"
}
将所有消息参数的字符串表示形式解析为数组
{
"$resolver": "messageParameter",
"stringified": true
}
解析第一个消息参数
{
"$resolver": "messageParameter",
"index": 0
}
解析第一个消息参数的字符串表示形式
{
"$resolver": "messageParameter",
"index": 0,
"stringified": true
}
ndc
config = [ pattern ]
pattern = "pattern" -> string
解析嵌套诊断上下文 (NDC),也称为线程上下文堆栈,由 logEvent.getContextStack() 返回的 String[]。
示例
将所有 NDC 值解析为列表
{
"$resolver": "ndc"
}
解析与 pattern 正则表达式匹配的所有 NDC 值
{
"$resolver": "ndc",
"pattern": "user(Role|Rank):\\w+"
}
pattern
config = pattern , [ stackTraceEnabled ]
pattern = "pattern" -> string
stackTraceEnabled = "stackTraceEnabled" -> boolean
委托给 PatternLayout 的解析器。
stackTraceEnabled 的默认值从父 JsonTemplateLayout 继承。
示例
解析由 %p %c{1.} [%t] %X{userId} %X %m%ex 模式生成的字符串
{
"$resolver": "pattern",
"pattern": "%p %c{1.} [%t] %X{userId} %X %m%ex"
}
source
config = "field" -> (
"className" |
"fileName" |
"methodName" |
"lineNumber" )
解析由 logEvent.getSource() 返回的 StackTraceElement 的字段。
请注意,此解析器由 log4j.layout.jsonTemplate.locationInfoEnabled 属性切换。
示例
解析行号
{
"$resolver": "source",
"field": "lineNumber"
}
thread
config = "field" -> ( "name" | "id" | "priority" )
解析 logEvent.getThreadId()、logEvent.getThreadName()、logEvent.getThreadPriority()。
示例
解析线程名称
{
"$resolver": "thread",
"field": "name"
}
timestamp
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
解析各种形式的 logEvent.getInstant()。
示例
配置 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
映射解析器模板
ReadOnlyStringMap 是 Log4j 的 Map<String, Object> 等效项,具有无垃圾访问器,并在整个代码库中广泛使用。它是映射诊断上下文 (MDC)(也称为线程上下文数据)和 MapMessage 实现的底层数据结构。因此,这两个的模板解析器由单个后端提供:ReadOnlyStringMapResolver。换句话说,mdc 和 map 解析器都支持相同的配置、行为和垃圾占用,这些将在下面详细说明。
config = singleAccess | multiAccess
singleAccess = key , [ stringified ]
key = "key" -> string
stringified = "stringified" -> boolean
multiAccess = [ pattern ] , [ replacement ] , [ flatten ] , [ stringified ]
pattern = "pattern" -> string
replacement = "replacement" -> string
flatten = "flatten" -> ( boolean | flattenConfig )
flattenConfig = [ flattenPrefix ]
flattenPrefix = "prefix" -> string
singleAccess 解析单个字段,而 multiAccess 解析多个字段。如果提供 flatten,multiAccess 会将字段与父级合并,否则会创建一个包含这些值的新 JSON 对象。
启用 stringified 标志会将每个值转换为其字符串表示形式。
pattern 中提供的正则表达式用于与键匹配。如果提供,replacement 将用于替换匹配的键。这两个实际上等效于 Pattern.compile(pattern).matcher(key).matches() 和 Pattern.compile(pattern).matcher(key).replaceAll(replacement) 调用。
|
警告
|
关于垃圾占用,
将某些非基本类型的值(例如, |
在以下示例中省略了 "$resolver",因为它将由实际解析器定义,例如,map、mdc。
解析键为 user:role 的字段的值
{
"$resolver": "…",
"key": "user:role"
}
解析 user:rank 字段值的字符串表示形式
{
"$resolver": "…",
"key": "user:rank",
"stringified": true
}
将所有字段解析为一个对象
{
"$resolver": "…"
}
将所有字段解析为一个对象,以便将值转换为字符串
{
"$resolver": "…",
"stringified": true
}
将所有键与 user:(role|rank) 正则表达式匹配的字段解析为一个对象
{
"$resolver": "…",
"pattern": "user:(role|rank)"
}
在从键中删除 user: 前缀后,将所有键与 user:(role|rank) 正则表达式匹配的字段解析为一个对象
{
"$resolver": "…",
"pattern": "user:(role|rank)",
"replacement": "$1"
}
将所有键与 user:(role|rank) 正则表达式匹配的字段合并到父级中
{
"$resolver": "…",
"flatten": true,
"pattern": "user:(role|rank)"
}
在将相应字段值转换为字符串后,将所有字段合并到父级中,以便键以 _ 为前缀
{
"$resolver": "…",
"stringified": true,
"flatten": {
"prefix": "_"
}
}
堆栈跟踪元素模板
exception 和 exceptionRootCause 事件模板解析器可以将异常堆栈跟踪(即 Throwable#getStackTrace() 返回的 StackTraceElement[])序列化为 JSON 数组。在此过程中,再次使用 JSON 模板基础设施。
stackTraceElement[Uri] 描述了 JsonTemplateLayout 用于格式化 StackTraceElement 的 JSON 结构。默认配置(可以通过 log4j.layout.jsonTemplate.stackTraceElementTemplate[Uri] 属性访问)设置为 log4j-layout-template-json 工件提供的 classpath:StackTraceElementLayout.json
{
"class": {
"$resolver": "stackTraceElement",
"field": "className"
},
"method": {
"$resolver": "stackTraceElement",
"field": "methodName"
},
"file": {
"$resolver": "stackTraceElement",
"field": "fileName"
},
"line": {
"$resolver": "stackTraceElement",
"field": "lineNumber"
}
}
允许的模板配置语法如下
config = "field" -> (
"className" |
"fileName" |
"methodName" |
"lineNumber" )
以上所有对 StackTraceElement 的访问都是无垃圾的。
扩展
JsonTemplateLayout 依赖于 Log4j 插件系统 来构建其提供的功能。这使得用户可以轻松地自定义功能。截至目前,以下功能是通过插件实现的
-
事件模板解析器(例如,
exception、message、level事件模板解析器) -
事件模板拦截器(例如,注入
eventTemplateAdditionalField) -
回收器工厂
以下部分将详细介绍这些内容。
插件预备知识
Log4j 插件系统是各种 Log4j 组件(包括 JsonTemplateLayout)采用的事实上的扩展机制。插件使可扩展组件能够接收功能实现,而无需两者之间有任何显式链接。它类似于 依赖注入 框架,但针对 Log4j 特定的需求进行了调整。
简而言之,您使用 @Plugin 注释您的类,并使用 @PluginFactory 注释其(static)创建器方法。最后,您通知 Log4j 插件系统发现这些自定义类。这可以通过在 Log4j 配置中声明的 packages 或 插件系统文档 中描述的各种其他方式来完成。
扩展事件解析器
所有可用的 事件模板解析器 都是 JsonTemplateLayout 使用的简单插件。要添加新的解析器,只需创建自己的 EventResolver 并通过 @Plugin 注释的 EventResolverFactory 类指示其注入。
出于演示目的,下面我们将创建一个 randomNumber 事件解析器。让我们从实际的解析器开始
package com.acme.logging.log4j.layout.template.json;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.layout.template.json.resolver.EventResolver;
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
/**
* Resolves a random floating point number.
*
* <h3>Configuration</h3>
*
* <pre>
* config = ( [ range ] )
* range = number[]
* </pre>
*
* {@code range} is a number array with two elements, where the first number
* denotes the start (inclusive) and the second denotes the end (exclusive).
* {@code range} is optional and by default set to {@code [0, 1]}.
*
* <h3>Examples</h3>
*
* Resolve a random number between 0 and 1:
*
* <pre>
* {
* "$resolver": "randomNumber"
* }
* </pre>
*
* Resolve a random number between -0.123 and 0.123:
*
* <pre>
* {
* "$resolver": "randomNumber",
* "range": [-0.123, 0.123]
* }
* </pre>
*/
public final class RandomNumberResolver implements EventResolver {
private final double loIncLimit;
private final double hiExcLimit;
RandomNumberResolver(final TemplateResolverConfig config) {
final List<Number> rangeArray = config.getList("range", Number.class);
if (rangeArray == null) {
this.loIncLimit = 0D;
this.hiExcLimit = 1D;
} else if (rangeArray.size() != 2) {
throw new IllegalArgumentException(
"range array must be of size two: " + config);
} else {
this.loIncLimit = rangeArray.get(0).doubleValue();
this.hiExcLimit = rangeArray.get(1).doubleValue();
if (loIncLimit > hiExcLimit) {
throw new IllegalArgumentException("invalid range: " + config);
}
}
}
static String getName() {
return "randomNumber";
}
@Override
public void resolve(
final LogEvent value,
final JsonWriter jsonWriter) {
final double randomNumber =
loIncLimit + (hiExcLimit - loIncLimit) * Math.random();
jsonWriter.writeNumber(randomNumber);
}
}
接下来,创建一个 EventResolverFactory 类,将 RandomNumberResolver 注册到 Log4j 插件系统中。
RandomNumberResolver 注册到 Log4j 插件系统中package com.acme.logging.log4j.layout.template.json;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext;
import org.apache.logging.log4j.layout.template.json.resolver.EventResolverFactory;
import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolver;
import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverConfig;
import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverFactory;
/**
* {@link RandomNumberResolver} factory.
*/
@Plugin(name = "RandomNumberResolverFactory", category = TemplateResolverFactory.CATEGORY)
public final class RandomNumberResolverFactory implements EventResolverFactory {
private static final RandomNumberResolverFactory INSTANCE =
new RandomNumberResolverFactory();
private RandomNumberResolverFactory() {}
@PluginFactory
public static RandomNumberResolverFactory getInstance() {
return INSTANCE;
}
@Override
public String getName() {
return RandomNumberResolver.getName();
}
@Override
public RandomNumberResolver create(
final EventResolverContext context,
final TemplateResolverConfig config) {
return new RandomNumberResolver(config);
}
}
几乎完成了。最后,我们需要通知 Log4j 插件系统发现这些自定义类
randomNumber 解析器的 Log4j 配置<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<!-- ... -->
<JsonTemplateLayout>
<EventTemplateAdditionalField
key="id"
format="JSON"
value='{"$resolver": "randomNumber", "range": [0, 1000000]}'/>
</JsonTemplateLayout>
<!-- ... -->
</Configuration>
所有可用的事件模板解析器都位于 org.apache.logging.log4j.layout.template.json.resolver 包中。在实现新的解析器时,这是一个相当丰富的资源,可以从中获得灵感。
拦截模板解析器编译器
JsonTemplateLayout 允许拦截模板解析器编译,这是将模板转换为执行 JSON 序列化的 Java 函数的过程。这种拦截机制在内部用于实现 eventTemplateRootObjectKey 和 eventTemplateAdditionalField 功能。简而言之,您需要创建一个扩展 EventResolverInterceptor 接口的 @Plugin 注释的类。
要查看拦截的实际操作,请查看 EventRootObjectKeyInterceptor 类,该类负责实现 eventTemplateRootObjectKey 功能
eventTemplateRootObjectKey(如果存在)import org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext;
import org.apache.logging.log4j.layout.template.json.resolver.EventResolverInterceptor;
import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverInterceptor;
/**
* Interceptor to add a root object key to the event template.
*/
@Plugin(name = "EventRootObjectKeyInterceptor", category = TemplateResolverInterceptor.CATEGORY)
public class EventRootObjectKeyInterceptor implements EventResolverInterceptor {
private static final EventRootObjectKeyInterceptor INSTANCE =
new EventRootObjectKeyInterceptor();
private EventRootObjectKeyInterceptor() {}
@PluginFactory
public static EventRootObjectKeyInterceptor getInstance() {
return INSTANCE;
}
@Override
public Object processTemplateBeforeResolverInjection(
final EventResolverContext context,
final Object node) {
String eventTemplateRootObjectKey = context.getEventTemplateRootObjectKey();
return eventTemplateRootObjectKey != null
? Collections.singletonMap(eventTemplateRootObjectKey, node)
: node;
}
}
在这里,processTemplateBeforeResolverInjection() 方法检查用户是否提供了 eventTemplateRootObjectKey。如果是,它会用一个新对象包装根 node;否则,按原样返回 node。请注意,node 指的是由 JsonReader 读取的事件模板的根 Java 对象。
扩展回收器工厂
从布局配置中读取的 recyclerFactory 输入 String 会使用扩展 TypeConverter<RecyclerFactory> 的默认 RecyclerFactoryConverter 转换为 RecyclerFactory。如果要更改此行为,只需添加自己的 TypeConverter<RecyclerFactory> 实现 Comparable<TypeConverter<?>> 以优先考虑您的自定义转换器。
RecyclerFactory 的自定义 TypeConverterpackage com.acme.logging.log4j.layout.template.json;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.convert.TypeConverter;
import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters;
@Plugin(name = "AcmeRecyclerFactoryConverter", category = TypeConverters.CATEGORY)
public final class AcmeRecyclerFactoryConverter
implements TypeConverter<RecyclerFactory>, Comparable<TypeConverter<?>> {
@Override
public RecyclerFactory convert(final String recyclerFactorySpec) {
return AcmeRecyclerFactory.ofSpec(recyclerFactorySpec);
}
@Override
public int compareTo(final TypeConverter<?> ignored) {
return -1;
}
}
请注意,compareTo() 始终返回 -1,以使其排名高于其他匹配的转换器。
功能
以下是 JsonTemplateLayout 与替代方案的功能比较矩阵。
功能 |
|
|||
Java 版本 |
8 |
8 |
8 |
6 |
依赖项 |
无 |
Jackson |
无 |
无 |
架构自定义? |
✓ |
✕ |
✕ |
✕ |
时间戳自定义? |
✓ |
✕ |
✕ |
✕ |
(几乎)无垃圾? |
✓ |
✕ |
✓ |
✓ |
自定义类型 |
✓ |
✕ |
✕ |
?[1] |
自定义类型 |
✓ |
✕ |
✕ |
✕ |
将堆栈跟踪呈现为数组? |
✓ |
✓ |
✕ |
✓ |
堆栈跟踪截断? |
✓ |
✕ |
✕ |
✕ |
JSON 美化打印? |
✕ |
✓ |
✕ |
✕ |
其他字符串字段? |
✓ |
✓ |
✓ |
✓ |
其他 JSON 字段? |
✓ |
✕ |
✕ |
✕ |
自定义解析器? |
✓ |
✕ |
✕ |
✕ |
常见问题解答
模板中是否支持查找?
是的,模板的字符串文字中支持 查找(例如,${java:version}、${env:USER}、${date:MM-dd-yyyy})。但请注意,它们不是无垃圾的。
是否支持递归集合?
否。考虑一个包含递归值的 Message,如下所示
Object[] recursiveCollection = new Object[1];
recursiveCollection[0] = recursiveCollection;
虽然确切的异常可能有所不同,但您很可能会在尝试将 recursiveCollection 渲染为 String 时收到 StackOverflowError。请注意,这也是其他 Java 标准库方法(例如,Arrays.toString())的默认行为。因此,在记录时请注意自引用。
JsonTemplateLayout 是否无垃圾?
是的,如果启用了无垃圾布局行为切换属性 log4j2.enableDirectEncoders 和 log4j2.garbagefreeThreadContextMap。请考虑以下注意事项
不要忘记查看您在模板中使用的 解析器垃圾占用说明。
ObjectMessage,如果类路径中存在 Jackson。

