看过我之前的文章的就可以一步一步搭建起日志传输到搜索引擎 不知道的 看下之前的文章
(1) 记一次logback传输日志到logstash根据自定义设置动态创建ElasticSearch索引
(2)关于” 记一次logback传输日志到logstash根据自定义设置动态创建ElasticSearch索引” 这篇博客相关的优化采坑记录
(3)日志收集(ElasticSearch)串联查询 MDC
这里我们结合sleuth 可以降服务之间的调用使用唯一标识串联起来已达到我们通过一个标识可以查看所有跨服务调用的串联日志,与上一篇 的MDC不同
sleuth 简单原理说下
就是在最初发起调用者的时候在请求头head中添加唯一标识传递到直接调用的服务上面
然后之后的服务做类似的操作
好了 不多比比了
上代码
首先所有的服务或spring boot项目都引入以下包
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency> <dependency> <groupId>com.cwbase</groupId> <artifactId>logback-redis-appender</artifactId> <version>1.1.5</version> </dependency>
一个是传输redis使用一个是调用链跟踪使用
下面是logback配置文件
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="false" scan="true" scanPeriod="1 seconds"> <include resource="org/springframework/boot/logging/logback/base.xml" /> <!-- <jmxConfigurator/> --> <contextName>logback</contextName> <property name="log.path" value="logslogback.log" /> <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} -%5p ${PID} --- traceId:[%X{mdc_trace_id}] [%15.15t] %-40.40logger{39} : %m%n" /> <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${log.path}</file> <encoder> <pattern>${log.pattern}</pattern> </encoder> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>info-%d{yyyy-MM-dd}-%i.log </fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>10MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <maxHistory>10</maxHistory> </rollingPolicy> </appender> <appender name="redis" class="com.cwbase.logback.RedisAppender"> <tags>test</tags> <host>IP</host><!--redis IP--> <port>6379</port><!--redis端口--> <key>test</key><!--redis队列名称--> <!-- <mdc>true</mdc> --> <callerStackIndex>0</callerStackIndex> <location>true</location> <additionalField> <key>X-B3-ParentSpanId</key> <value>@{X-B3-ParentSpanId}</value> </additionalField> <additionalField> <key>X-B3-SpanId</key> <value>@{X-B3-SpanId}</value> </additionalField> <additionalField> <key>X-B3-TraceId</key> <value>@{X-B3-TraceId}</value> </additionalField> </appender> <root level="info"> <!-- <appender-ref ref="CONSOLE" /> --> <!-- <appender-ref ref="file" /> --> <!-- <appender-ref ref="UdpSocket" /> --> <!-- <appender-ref ref="TcpSocket" /> --> <appender-ref ref="redis" /> </root> <!-- <logger name="com.example.logback" level="warn" /> --> </configuration>
与之前的logback.xml配置文件相比主要更改一下内容
<additionalField> <key>X-B3-ParentSpanId</key> <value>@{X-B3-ParentSpanId}</value> </additionalField> <additionalField> <key>X-B3-SpanId</key> <value>@{X-B3-SpanId}</value> </additionalField> <additionalField> <key>X-B3-TraceId</key> <value>@{X-B3-TraceId}</value> </additionalField>
一会在详细解释上述三个字段含义 下面先看项目目录结构
一个父工程(pom工程)三个spring boot子项目 子项目调用关系如下
三个子项目代码如下
spring-cloud-client-test工程结构及代码
1 package application; 2 3 import org.slf4j.Logger; 4 import org.slf4j.LoggerFactory; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.boot.SpringApplication; 7 import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 8 import org.springframework.boot.autoconfigure.SpringBootApplication; 9 import org.springframework.cloud.netflix.feign.EnableFeignClients; 10 import org.springframework.web.bind.annotation.GetMapping; 11 import org.springframework.web.bind.annotation.RestController; 12 13 @EnableAutoConfiguration 14 @EnableFeignClients 15 @RestController 16 @SpringBootApplication 17 public class ClientTestApplication { 18 protected final static Logger logger = LoggerFactory.getLogger(ClientTestApplication.class); 19 20 public static void main(String[] args) { 21 SpringApplication.run(ClientTestApplication.class, args); 22 } 23 24 @Autowired 25 servertest server; 26 27 @GetMapping("/client") 28 public String getString(){ 29 logger.info("开始调用服务端"); 30 return server.getString(); 31 } 32 @GetMapping("/client1") 33 public String getString1(){ 34 logger.info("开始调用服务端1"); 35 return server.getString1(); 36 } 37 }
1 package application; 2 3 import org.springframework.cloud.netflix.feign.FeignClient; 4 import org.springframework.http.MediaType; 5 import org.springframework.web.bind.annotation.RequestMapping; 6 import org.springframework.web.bind.annotation.RequestMethod; 7 8 @FeignClient("SPRING-CLOUD-SERVER-TEST") 9 public interface servertest { 10 @RequestMapping(value = "/server", method = RequestMethod.GET, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) 11 public String getString(); 12 @RequestMapping(value = "/server1", method = RequestMethod.GET, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) 13 public String getString1(); 14 }
spring-cloud-server-test工程及代码结构
1 package application; 2 3 import org.slf4j.Logger; 4 import org.slf4j.LoggerFactory; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.boot.SpringApplication; 7 import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 8 import org.springframework.boot.autoconfigure.SpringBootApplication; 9 import org.springframework.cloud.netflix.feign.EnableFeignClients; 10 import org.springframework.web.bind.annotation.GetMapping; 11 import org.springframework.web.bind.annotation.RestController; 12 13 @EnableAutoConfiguration 14 @EnableFeignClients 15 @RestController 16 @SpringBootApplication 17 public class ServerTestApplication { 18 protected final static Logger logger = LoggerFactory.getLogger(ServerTestApplication.class); 19 20 public static void main(String[] args) { 21 SpringApplication.run(ServerTestApplication.class, args); 22 } 23 24 @Autowired 25 servertest server; 26 27 @GetMapping("/server") 28 public String getString(){ 29 logger.info("接收客户端的调用"); 30 return server.getString(); 31 } 32 @GetMapping("/server1") 33 public String getString1(){ 34 logger.info("接收客户端的调用1"); 35 return server.getString1(); 36 } 37 }
1 package application; 2 3 import org.springframework.cloud.netflix.feign.FeignClient; 4 import org.springframework.http.MediaType; 5 import org.springframework.web.bind.annotation.RequestMapping; 6 import org.springframework.web.bind.annotation.RequestMethod; 7 8 @FeignClient("SPRING-CLOUD-SERVER1-TEST") 9 public interface servertest { 10 @RequestMapping(value = "/server", method = RequestMethod.GET, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) 11 public String getString(); 12 @RequestMapping(value = "/server1", method = RequestMethod.GET, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) 13 public String getString1(); 14 }
spring-cloud-server1-test工程及代码结构
1 package application; 2 3 import org.slf4j.Logger; 4 import org.slf4j.LoggerFactory; 5 import org.springframework.boot.SpringApplication; 6 import org.springframework.boot.autoconfigure.SpringBootApplication; 7 import org.springframework.web.bind.annotation.GetMapping; 8 import org.springframework.web.bind.annotation.RestController; 9 10 11 @RestController 12 @SpringBootApplication 13 public class Server1TestApplication { 14 protected final static Logger logger = LoggerFactory.getLogger(Server1TestApplication.class); 15 16 public static void main(String[] args) { 17 SpringApplication.run(Server1TestApplication.class, args); 18 } 19 20 21 @GetMapping("/server") 22 public String getString(){ 23 logger.info("接收客户端的调用"); 24 return "My is server"; 25 } 26 @GetMapping("/server1") 27 public String getString1(){ 28 logger.info("接收客户端的调用1"); 29 return "My is server1"; 30 } 31 }
好了 全部代码就是以上这些 下面看日志传输之后的效果
上图就是最后的结果
我们可以通过 X-B3-TraceId 串联所有的服务 这个值每次请求都不一样但是会随着调用链一直传递下去
X-B3-SpanId 这个值属于方法级别的值 也就是说 方法调用方法是父子级别的传递(方便调用跟踪)
X-B3-ParentSpanId 这个值就是上一个方法的X-B3-SpanId 我说的不是很明白大家可以查阅相关资料了解
好了到这里就基本完成了
总结思考
使用sleuth我们可以很好的串联快服务的日志,结合MDC就可以出现很完美的调用流水查询但是我们要做到一次查询 要么做表达式筛选要么查询两次 。我们有没有办法将二者结合那,我想并不困难自己重写sleuth相关方法可以做到,但是我们要考虑这是有问题的,什么问题那 就是 同样的MDC key-value 调用 sleuth会变 但是MDC值不变 我们要融合成什么样子才能达到想要的目的的,这个就不好说了 ,通过表达式筛选已经很方便了还有么有必要这样做那,做了之后怎么避免副作用那!有待考究