java日志规范及模板
日志规约
-
【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架(如SLF4J)中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理 方式统一。
-
【强制】日志文件至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。对于当天日志,以 “应用名.log”来保存,保存在/{统一目录}/logs/{应用名}目录下,过往日志文件名带有yyyy-MM-dd格式日期。
-
【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式。 说明:因为 String 字符串的拼接会使用 StringBuilder 的 append() 方式,有一定的性能损耗。使用占位符仅是替换动 作,可以有效提升性能。
正例:logger.debug("Processing trade with id : {} and symbol : {}", id, symbol);
-
【强制】生产环境禁止使用 System.out 或 System.err 输出或使用 e.printStackTrace() 打印异常堆栈。
-
【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。
注意:一定要携带最后一个参数e(java.lang.Throwable),禁止仅打印 e.getMessage()
正例:
logger.error("inputParams: {}", 各类参数或者对象 toString(), e);
-
【推荐】谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时 删除这些观察日志。
日志格式
统一的日志格式不仅对用户友好,也有利于日志收集等运维平台做进一步处理。因此,日志格式必须在系统内达成共识。
一条完整的日志由系统自动捕获的公共信息(由日志模板定义),和开发者手动记录的日志信息拼接组成,即一条日志=公共信息+日志体
。
格式定义
以下日志格式配置适用于log4j2与logback,对应xml中的pattern配置:
|%d{yyyy-MM-dd HH:mm:ss.SSS}|%-5level|%X{traceId}|%X{spanId}|${appName}|%t|%C|%M|%L|%m%n
分段释义:
|日期时间|日志级别|链路ID|链路跨度ID|应用名词|线程名称|类名|方法名|行号|日志体
其中traceId与spanId是链路追踪参数,需要基于链路追踪框架生成,可以结合链路追踪系统使用;appName由应用系统定义。
日志样例
-
INFO(DEBUG,WARN等)日志
|2023-01-30 14:15:26.220|INFO |trace0|span1|iot.spaceFence|http-nio-8080-exec-1|com.iot.spaceFence.controller.IotAlarmFenceController|findOne|31|正常日志:0e178fbb5907676c38959eb7bf1c5f2b
-
ERROR日志
|2023-01-30 14:15:26.221|ERROR|trace1|span1|iot.spaceFence|http-nio-8080-exec-1|com.iot.spaceFence.controller.IotAlarmFenceController|findOne|35|异常日志: 0e178fbb5907676c38959eb7bf1c5f2b java.lang.RuntimeException: 测试异常 at com.iot.spaceFence.controller.IotAlarmFenceController.findOne(IotAlarmFenceController.java:33) [classes/:?] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_351] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_351] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_351] at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_351]
ELK配置
filebeat
每台服务器运行一个filebeat即可,负责采集日志并发送到logstash:
注意tags表示所属项目,最终会对应到elasticsearch的不同索引。
#=========================== Agent name =============================
name: 100.127.6.231
#=========================== Filebeat inputs =============================
filebeat.inputs:
- type: log
# Change to true to enable this input configuration.
enabled: true
# Paths that should be crawled and fetched. Glob based paths.
paths:
- /apps/logs/iot/*/app.log
# The regexp Pattern that has to be matched. The example pattern matches all lines starting with [
multiline.pattern: ^\|
# Defines if the pattern set under pattern should be negated or not. Default is false.
multiline.negate: true
# Match can be set to "after" or "before". It is used to define if lines should be append to a pattern
# that was (not) matched before or after or as long as a pattern is not matched based on negate.
# Note: After is the equivalent to previous and before is the equivalent to to next in Logstash
multiline.match: after
tags: ['iot']
- type: log
enabled: true
paths:
- /apps/logs/lms/lms-*.log
multiline.pattern: ^\|
multiline.negate: true
multiline.match: after
tags: ['lms']
#============================= Filebeat modules ===============================
filebeat.config.modules:
# Glob pattern for configuration loading
path: ${path.config}/modules.d/*.yml
# Set to true to enable config reloading
reload.enabled: false
#==================== Elasticsearch template setting ==========================
setup.template.settings:
index.number_of_shards: 1
#============================== Dashboards =====================================
setup.kibana:
#----------------------------- Logstash output --------------------------------
output.logstash:
# The Logstash hosts
hosts: ["192.168.3.39:5044","192.168.3.214:5044"]
#================================ Processors =====================================
# Configure processors to enhance or manipulate events generated by the beat.
processors:
- add_host_metadata: ~
- add_cloud_metadata: ~
- add_docker_metadata: ~
- add_kubernetes_metadata: ~
logstash
# Sample Logstash configuration for creating a simple
# Beats -> Logstash -> Elasticsearch pipeline.
input {
beats {
port => 5044
}
}
filter {
if "iot" in [tags] {
mutate { add_field => { "[@metadata][target_index]" => "iot-%{+YYYY.MM.dd}" } }
} else if "lms" in [tags] {
mutate { add_field => { "[@metadata][target_index]" => "lms-%{+YYYY.MM.dd}" } }
} else {
mutate { add_field => { "[@metadata][target_index]" => "default-%{+YYYY.MM.dd}" } }
}
grok {
match => { "message" => "\|%{DATA:datetime}\|%{NOTSPACE:level}\s?\|%{DATA:traceId}\|%{DATA:spanId}\|%{DATA:app}\|%{NOTSPACE:thread}\|%{NOTSPACE:class}\|%{NOTSPACE:method}\|%{INT:line}\|(?<content>(.|\n)*)" }
# 移除一些不太有用的默认字段
remove_field => ["[agent][ephemeral_id]","[agent][id]","[agent][id]","[agent][type]","[agent][version]","[host][mac]","[host][ip]","[host][id]","[host][architecture]","[host][containerized]","[host][os][codename]","[host][os][family]","[host][os][name]","[host][os][kernel]","[ecs][version]"]
}
}
output {
elasticsearch {
hosts => ["http://192.168.0.184:9200","http://192.168.0.182:9200","http://192.168.0.59:9200"]
index => "%{[@metadata][target_index]}"
user => "elastic"
password => "*********"
}
}