最近要把日志导入ELK管理, 需要把java日志打印成json格式, 谷歌了一通, 主要都是通过logback配置的方式来实现
配置方式一
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="stash" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>info</level>
</filter>
<file>/some/path/to/your/file.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/some/path/to/your/file.log.%d{yyyy-MM-dd}</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LogstashEncoder" />
</appender>
<root level="all">
<appender-ref ref="stash" />
</root>
</configuration>
这种配置输出的日志格式:
{
"@timestamp": "2018-09-04T10:14:40.215+08:00",
"@version": "1",
"message": "invoke getSchedules request:{\r\n \"token\": \"JI1mVZxAjLTLLIp4em1slPWGDWwrAPRqLQh2rC7rAmsXQg==\",\r\n \"timeZone\": \"Asia/Shanghai\",\r\n \"accountID\": \"1000009\",\r\n \"uuid\": \"f7dda551-84a5-45ec-8b68-4e9803101dc7\"\r\n}",
"logger_name": "com.etekcity.airpurifier131.controller.v1.ScheduleController",
"thread_name": "http-nio-8080-exec-1",
"level": "INFO",
"level_value": 20000
}
通过这种配置, 该json的key都是无法自定义的, 我们想要的信息是message里的各个信息, 但是输出的日志message对应的是字符串, 只能通过logstash后期处理解决. 在message里, 由于json字符串前面又有打印的文字信息, 增大了解析的难度. 当然, 解析处理应该是可行的.
配置方式二
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 配置文件轮转 -->
<appender name="logfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>/var/log/tomcat/AirPurifier131/AirPurifier131.%d{yyyy-MM-dd-HH}.log</FileNamePattern>
<maxHistory>48</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="com.etekcity.airpurifier131.config.MyLogLayout" />
</encoder>
</appender>
<root level="all">
<appender-ref ref="logfile"/>
</root>
</configuration>
此配置需要自己写一个自定义的LayOut类继承LayOutBase类, 可以在该类里自定义日志输出的格式, 自由度较高, 我采用的是这种方式
MyLogLayout类:
package com.etekcity.airpurifier131.config;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.LayoutBase;
import java.sql.Timestamp;
public class MyLogLayout extends LayoutBase<ILoggingEvent> {
private static final String PROJECT_NAME = "AirPurifier131";
@Override
public String doLayout(ILoggingEvent event) {
StringBuilder sb = new StringBuilder();
sb.append("{");
sb.append("\"P\":");
sb.append("\""+PROJECT_NAME+"\", ");
sb.append("\"T\":");
sb.append("\"").append(new Timestamp(event.getTimeStamp())).append("\"");
sb.append(", \"L\":");
sb.append("\"").append(event.getLevel()).append("\"");
sb.append(", \"THREAD\":");
sb.append("\"").append(event.getThreadName()).append("\"");
sb.append(", \"CLASS\": ");
sb.append("\"").append(event.getLoggerName()).append("\"");
sb.append(",\"message\": ");
String message = event.getFormattedMessage();
if (event.getThrowableProxy()!=null){
ExtendedThrowableProxyConverter throwableConverter = new ExtendedThrowableProxyConverter();
throwableConverter.start();
message = event.getFormattedMessage() + "\n" + throwableConverter.convert(event);
throwableConverter.stop();
}
int beginIndex = message.indexOf("{");
int endIndex = message.lastIndexOf("}")+1;
if (beginIndex >= 0 && endIndex >= 0) {
String Additional = message.substring(0, beginIndex);
String jsonMsg = message.substring(beginIndex, endIndex);
sb.append(jsonMsg);
sb.append(",\"Additional\": ");
sb.append("\"").append(Additional).append("\"");
}else {
sb.append("\"").append(message).append("\"");
}
sb.append("}");
sb.append(CoreConstants.LINE_SEPARATOR);
return sb.toString();
}
}
可以看到我在该类里重写的doLayout方法, 自定义了日志的格式, 还初步解析了message, 拿出了较为完整的json, 本来可以继续解析, 将message里的json键值全部移至外层, 但是logstash解析json格式已经很方便了, 就放在了logstash中去解析. 这是java项目输出的日志:
{
"P": "AirPurifier131",
"T": "2018-09-07 09:20:49.966",
"L": "INFO",
"THREAD": "http-nio-8080-exec-2",
"CLASS": "com.etekcity.airpurifier131.controller.v1.ScheduleController",
"message": {
"token": "JI1mVZxAjLTLLIp4em1slPWGDWwrAPRqLQh2rC7rAmsXQg==",
"timeZone": "Asia/Shanghai",
"accountID": "1000009",
"uuid": "f7dda551-84a5-45ec-8b68-4e9803101dc7"
},
"Additional": "invoke getSchedules request:"
}
可见, 自定义之后日志结构清晰了很多.