7人参与 • 2025-06-10 • ar
为了适配现有日志平台,java项目应用日志需要添加自定义字段:
日志关键字段:
格式需要改编成json
{“app”:“formula”,“namespace”:“main”,“host”:“127.0.0.1”,“env”:“dev”,“createdon”:“2025-04-23t13:47:08.726+08:00”,“level”:“info”,“message”:“(♥◠‿◠)ノ゙启动成功 ლ(´ڡ`ლ)゙”}
logback-starter/ │ ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ └── lf │ │ │ └── logbackstarter │ │ │ ├── config │ │ │ │ ├── mdcinterceptor.java │ │ │ │ ├── loginitializer.java │ │ │ │ └── logbackinterceptorautoconfiguration.java │ │ │ │ └── logbackproperties │ │ │ └── logbackautoconfiguration.java │ │ └── resources │ │ │ └── logback.xml │ │ │ └── meta-inf │ │ │ └── spring.factories └── pom.xml
<?xml version="1.0" encoding="utf-8"?> <project xmlns="http://maven.apache.org/pom/4.0.0" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xsi:schemalocation="http://maven.apache.org/pom/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelversion>4.0.0</modelversion> <groupid>com.kayou</groupid> <artifactid>java-logs-starter</artifactid> <version>1.0-snapshot</version> <name>java-logs-starter</name> <!-- fixme change it to the project's website --> <url>http://www.example.com</url> <properties> <spring-boot.version>2.6.3</spring-boot.version> </properties> <!-- 只声明依赖,不引入依赖 --> <dependencymanagement> <dependencies> <!-- 声明springboot版本 --> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-dependencies</artifactid> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencymanagement> <dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-autoconfigure</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-logging</artifactid> </dependency> <dependency> <groupid>net.logstash.logback</groupid> <artifactid>logstash-logback-encoder</artifactid> <version>6.6</version> </dependency> <!-- logback classic --> <dependency> <groupid>ch.qos.logback</groupid> <artifactid>logback-classic</artifactid> </dependency> </dependencies> <build> <plugins> <plugin> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-maven-plugin</artifactid> <version>2.6.3</version> <!-- <configuration>--> <!-- </configuration>--> <!-- <executions>--> <!-- <execution>--> <!-- <goals>--> <!-- <goal>repackage</goal>--> <!-- </goals>--> <!-- </execution>--> <!-- </executions>--> </plugin> <plugin> <groupid>org.apache.maven.plugins</groupid> <artifactid>maven-compiler-plugin</artifactid> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> </plugins> </build> </project>
import org.slf4j.mdc; import org.springframework.context.annotation.configuration; import org.springframework.core.annotation.order; import org.springframework.stereotype.component; import javax.annotation.postconstruct; import java.net.inetaddress; import java.net.unknownhostexception; @configuration @order public class loginitializer { private final logbackproperties properties; public loginitializer(logbackproperties properties) { this.properties = properties; } @postconstruct public void init() { mdc.put("app", properties.getapp()); mdc.put("env", properties.getenv()); mdc.put("namespace", properties.getnamespace()); mdc.put("host", resolvelocalhostip()); } private string resolvelocalhostip() { // 获取 linux 系统下的主机名/ip inetaddress inetaddress = null; try { inetaddress = inetaddress.getlocalhost(); } catch (unknownhostexception e) { return "unknown"; } return inetaddress.gethostaddress(); } }
mdcinterceptor 用于在每个请求的生命周期中设置 mdc。
package com.lf; import org.slf4j.mdc; import org.springframework.web.servlet.handlerinterceptor; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; public class mdcinterceptor implements handlerinterceptor { private final logbackproperties properties; public mdcinterceptor(logbackproperties properties) { this.properties = properties; } @override public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) { mdc.put("app", properties.getapp()); mdc.put("env", properties.getenv()); mdc.put("namespace", properties.getnamespace()); mdc.put("host", properties.gethost()); return true; } }
@configuration public class logbackinterceptorautoconfiguration { @bean @conditionalonmissingbean(mdcinterceptor.class) public mdcinterceptor mdcinterceptor(logbackproperties properties) { return new mdcinterceptor(properties); } @bean public webmvcconfigurer logbackwebmvcconfigurer(mdcinterceptor mdcinterceptor) { return new webmvcconfigurer() { @override public void addinterceptors(interceptorregistry registry) { registry.addinterceptor(mdcinterceptor).addpathpatterns("/**"); } }; } }
@configurationproperties(prefix = "log.context") public class logbackproperties { private string app = "default-app"; private string env = "default-env"; private string namespace = "default-namespace"; private string host = ""; // getter & setter public string getapp() { return app; } public void setapp(string app) { this.app = app; } public string getenv() { return env; } public void setenv(string env) { this.env = env; } public string getnamespace() { return namespace; } public void setnamespace(string namespace) { this.namespace = namespace; } public string gethost() { if (host != null && !host.isempty()) { return host; } return resolvelocalhostip(); } public void sethost(string host) { this.host = host; } private string resolvelocalhostip() { // 获取 linux 系统下的主机名/ip inetaddress inetaddress = null; try { inetaddress = inetaddress.getlocalhost(); } catch (unknownhostexception e) { return "unknown"; } return inetaddress.gethostaddress(); } }
@configuration @enableconfigurationproperties(logbackproperties.class) public class logbackautoconfiguration { }
logback.xml
<included> <property name="log_path" value="/home/logs"/> <!-- 控制台输出 --> <appender name="console" class="ch.qos.logback.core.consoleappender"> <encoder class="net.logstash.logback.encoder.loggingeventcompositejsonencoder"> <providers> <mdc> <includemdckeyname>app</includemdckeyname> <includemdckeyname>env</includemdckeyname> <includemdckeyname>namespace</includemdckeyname> <includemdckeyname>host</includemdckeyname> <includemdckeyname>createdon</includemdckeyname> </mdc> <timestamp> <fieldname>timestamp</fieldname> <pattern>unix_millis</pattern> <timezone>asia/shanghai</timezone> </timestamp> <loglevel fieldname="level"/> <message fieldname="message"/> <stacktrace fieldname="stack_trace"/> </providers> </encoder> </appender> <!-- 文件输出 --> <appender name="jsonlog" class="ch.qos.logback.core.rolling.rollingfileappender"> <file>${log_path}/${app_name}.log</file> <rollingpolicy class="ch.qos.logback.core.rolling.timebasedrollingpolicy"> <filenamepattern>${log_path}/${app_name}.%d{yyyy-mm-dd}.log</filenamepattern> <maxhistory>15</maxhistory> </rollingpolicy> <encoder class="net.logstash.logback.encoder.loggingeventcompositejsonencoder"> <providers> <mdc> <includemdckeyname>app</includemdckeyname> <includemdckeyname>env</includemdckeyname> <includemdckeyname>namespace</includemdckeyname> <includemdckeyname>host</includemdckeyname> <includemdckeyname>createdon</includemdckeyname> </mdc> <!-- 显式指定毫秒时间戳的类 --> <timestamp> <fieldname>timestamp</fieldname> <pattern>unix_millis</pattern> <timezone>asia/shanghai</timezone> </timestamp> <loglevel fieldname="level"/> <message fieldname="message"/> <stacktrace fieldname="stack_trace"/> </providers> </encoder> </appender> <root level="info"> <appender-ref ref="console"/> <appender-ref ref="jsonlog"/> </root> </included>
meta-inf
spring.factories
org.springframework.boot.autoconfigure.enableautoconfiguration=\ com.lf.logbackautoconfiguration,\ com.lf.logbackinterceptorautoconfiguration,\ com.lf.loginitializer
在其他项目中添加依赖:(需要install本地仓库或deploy远程仓库)
<dependency> <groupid>com.kayou</groupid> <artifactid>java-logs-starter</artifactid> <version>1.0-snapshot</version> </dependency>
<configuration scan="true"> <!-- 添加自动意logback配置 --> <property name="app_name" value="java-demo"/> <!-- 引入公共的logback配置 --> <include resource="logback-default.xml"/> </configuration>
{"app":"java-demo","namespace":"default-namespace","host":"10.2.3.130","env":"dev","createdon":"2025-04-23t14:41:57.981+08:00","level":"info","message":"exposing 13 endpoint(s) beneath base path '/actuator'"} {"app":"java-demo","namespace":"default-namespace","host":"10.2.3.130","env":"dev","createdon":"2025-04-23t14:41:58.014+08:00","level":"info","message":"tomcat started on port(s): 8090 (http) with context path ''"} {"app":"java-demo","namespace":"default-namespace","host":"10.2.3.130","env":"dev","createdon":"2025-04-23t14:41:58.125+08:00","level":"info","message":"started application in 4.303 seconds (jvm running for 5.293)"}
平台日志需要日志level 为首字母大写,时间createdon 需要为时间戳,并且为long数字, logback原生 mdc支持string 不支持其他类型
import ch.qos.logback.classic.spi.iloggingevent; import com.fasterxml.jackson.core.jsongenerator; import net.logstash.logback.composite.abstractjsonprovider; import org.springframework.context.annotation.configuration; import java.io.ioexception; import java.util.map; import java.util.hashset; import java.util.set; @configuration public class mdctypeawareprovider extends abstractjsonprovider<iloggingevent> { private final set<string> longfields = new hashset<>(); public mdctypeawareprovider() { longfields.add("createdon"); // 指定需要转成 long 类型的字段 } @override public void writeto(jsongenerator generator, iloggingevent event) throws ioexception { map<string, string> mdcproperties = event.getmdcpropertymap(); if (mdcproperties == null || mdcproperties.isempty()) { return; } for (map.entry<string, string> entry : mdcproperties.entryset()) { string key = entry.getkey(); string value = entry.getvalue(); // 处理 level 字段,将首字母大写 if ("level".equalsignorecase(key)) { value = value.substring(0, 1).touppercase() + value.substring(1).tolowercase(); } if (longfields.contains(key)) { try { generator.writenumberfield(key, long.parselong(value)); } catch (numberformatexception e) { generator.writestringfield(key, value); // fallback } } else { generator.writestringfield(key, value); } } // 将 level 作为日志的一个字段来写入 string level = event.getlevel().tostring(); level = level.substring(0, 1).touppercase() + level.substring(1).tolowercase(); // 首字母大写 generator.writestringfield("level", level); } }
org.springframework.boot.autoconfigure.enableautoconfiguration=\ com.kayou.logbackautoconfiguration,\ com.kayou.logbackinterceptorautoconfiguration,\ com.kayou.loginitializer,\ com.kayou.mdctypeawareprovider
去除引用的mdc,新增自定义mdc provider
<!-- 控制台输出 --> <appender name="console" class="ch.qos.logback.core.consoleappender"> <encoder class="net.logstash.logback.encoder.loggingeventcompositejsonencoder"> <providers> <provider class="com.kayou.mdctypeawareprovider"/> <!-- 显式指定毫秒时间戳的类 --> <timestamp> <fieldname>createdtime</fieldname> <pattern>yyyy-mm-dd hh:mm:ss.sss</pattern> <timezone>asia/shanghai</timezone> </timestamp> <message fieldname="message"/> <stacktrace fieldname="stack_trace"/> </providers> </encoder> </appender> <!-- 文件输出 --> <appender name="jsonlog" class="ch.qos.logback.core.rolling.rollingfileappender"> <file>${log_path}/${app_name}.log</file> <rollingpolicy class="ch.qos.logback.core.rolling.timebasedrollingpolicy"> <filenamepattern>${log_path}/${app_name}.%d{yyyy-mm-dd}.log</filenamepattern> <maxhistory>15</maxhistory> </rollingpolicy> <encoder class="net.logstash.logback.encoder.loggingeventcompositejsonencoder"> <providers> <provider class="com.kayou.mdctypeawareprovider"/> <timestamp> <fieldname>createdtime</fieldname> <pattern>yyyy-mm-dd hh:mm:ss.sss</pattern> <timezone>asia/shanghai</timezone> </timestamp> <message fieldname="message"/> <stacktrace fieldname="stack_trace"/> </providers> </encoder> </appender>
{“app”:“java-demo”,“namespace”:“default-namespace”,“host”:“10.2.3.130”,“env”:“dev”,“createdon”:1745820638113,“level”:“info”,“createdtime”:“2025-04-28 14:10:38.596”,“message”:“(♥◠‿◠)ノ゙启动成功 ლ(´ڡ`ლ)゙”}
如过在web请求处理中,使用了异步线程,web线程就直接返回了。后续子线程是不会被intercetor切到的。改成日志格式不匹配
在mdctypeawareprovider 去填充这些字段就可以了
@configuration public class logbackpropertiesholder { private static logbackproperties properties; public logbackpropertiesholder(logbackproperties properties) { logbackpropertiesholder.properties = properties; } public static logbackproperties getproperties() { return properties; } }
@configuration public class mdctypeawareprovider extends abstractjsonprovider<iloggingevent> { private final set<string> longfields = new hashset<>(); public mdctypeawareprovider() { longfields.add("createdon"); } @override public void writeto(jsongenerator generator, iloggingevent event) throws ioexception { map<string, string> mdcproperties = event.getmdcpropertymap(); logbackproperties properties = logbackpropertiesholder.getproperties(); ensuremdcproperty(mdcproperties, "app", properties.getapp()); ensuremdcproperty(mdcproperties, "env", properties.getenv()); ensuremdcproperty(mdcproperties, "namespace", properties.getnamespace()); ensuremdcproperty(mdcproperties, "host", resolvelocalhostip()); ensuremdcproperty(mdcproperties, "createdon", string.valueof(system.currenttimemillis())); for (map.entry<string, string> entry : mdcproperties.entryset()) { string key = entry.getkey(); string value = entry.getvalue(); if (longfields.contains(key)) { try { generator.writenumberfield(key, long.parselong(value)); } catch (numberformatexception e) { generator.writestringfield(key, value); } } else { generator.writestringfield(key, value); } } string level = event.getlevel().tostring(); generator.writestringfield("level", level.substring(0, 1).touppercase() + level.substring(1).tolowercase()); } private void ensuremdcproperty(map<string, string> mdcproperties, string key, string defaultvalue) { if (!mdcproperties.containskey(key)) { mdc.put(key, defaultvalue); } } private string resolvelocalhostip() { try { return inetaddress.getlocalhost().gethostaddress(); } catch (unknownhostexception e) { return "127.0.0.1"; } } }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论