ApiBoot Logging 和Logging Admin使用总结

1. 说明

ApiBoot Logging是ApiBoot提供单应用、微服务应用下的请求日志分析框架。在微服务的链路调用过程中,可以记录下每次调用的链路信息。信息可以以日志的形式保存在数据库中,或者也可以自己通过代码将日志保存在其他地方,比如kafka中。

日志的格式大致如下:

[
    {
        "endTime":1564368219907,
        "httpStatus":200,
        "parentSpanId":"d9ed5130-b72c-4282-8d18-4a6f7a08275a",
        "requestBody":"",
        "requestHeaders":{
            "api-boot-x-trace-id":"855b0f4d-7667-4e14-ac8d-6b63fb4ef64e",
            "host":"localhost:9099",
            "connection":"keep-alive",
            "api-boot-x-parent-span-id":"d9ed5130-b72c-4282-8d18-4a6f7a08275a",
            "accept":"*/*",
            "user-agent":"Java/1.8.0_201"
        },
        "requestIp":"127.0.0.1",
        "requestMethod":"GET",
        "requestUri":"/levelone",
        "responseBody":"levelone",
        "responseHeaders":{},
        "serviceId":"ahhx_jcpt",
        "serviceIp":"192.168.21.101",
        "servicePort":"9099",
        "spanId":"d1597b46-ec21-4a37-8f40-d39d95d533a8",
        "startTime":1564368219904,
        "timeConsuming":3,
        "traceId":"855b0f4d-7667-4e14-ac8d-6b63fb4ef64e"
    }
]

1.1 traceId(链路ID)和spanId(跨度ID)

  • 如果一个请求的header信息内包含traceId(链路ID)则加入该链路,如果不存在则生成新的链路信息
  • 如果一个请求的header信息内包含spanId(跨度ID),则使用该spanId作为parent spanId,对两个请求进行上下级关联。

简单的理解,通过traceId,可以分析出来,如果几个日志的traceId是一样的,那么这几个调用是属于一条链路的。然后再通过spanId和parentSpanId就可以分析出来这个几个请求的上下级关系,是哪个调用的哪个。

1.2 体系

项目结构主要分为两个部分,一个是ApiBoot Logging,另一个是ApiBoot Logging Admin。

ApiBoot Logging是日志采集端,就是集成了ApiBoot Logging的项目,这个项目中的接口被请求时,就会采集到日志。

ApiBoot Logging Admin是日志收集端,所有ApiBoot Logging的项目采集到的日志都会上报到ApiBoot Logging Admin。

2. 项目中导入ApiBoot Logging

2.1 引入依赖

        
        
            org.minbox.framework
            api-boot-starter-logging
            2.1.2.RELEASE
        

2.2 修改配置文件

logging:
  level:
    org.minbox.framework.api.boot.plugin.logging: debug
    root: info
    com.base.web: info
api:
  boot:
    logging:
      admin:
        server-address: 127.0.0.1:20004
        # 格式化上报日志
        format-console-log-json: true
        number-of-request-log: 2
        # 显示上报日志
        show-console-report-log: true

server-address是Logging Admin的地址,Logging Admin是独立的项目,是Logging项目的日志上报地址。这里可以先不管,等到Logging Admin创建完毕,在修改为对于的ip和port

2.3 测试接口

@Controller
public class ApiBootContorller {

    @ResponseBody
    @GetMapping("/levelone")
    public String levelone(){
        return "levelone";
    }
}

通过postman调用这个接口,就可以在控制台中看到打印的日志。

集成ApiBoot Logging 的步骤只有这些,但是可能会遇到一些问题

spring boot版本问题:
我的项目使用的是spring boot 2.0.1 但是ApiBoot 需要2.1.6,直接引入,启动就会报错。

10:16:58 [localhost-startStop-1] ERROR o.a.c.c.C.[Tomcat].[localhost].[/] - Exception starting filter [apiBootLoggingFilter]
java.lang.AbstractMethodError: null
    at org.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:285)
    at org.apache.catalina.core.ApplicationFilterConfig.(ApplicationFilterConfig.java:112)
    at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4598)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5241)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1421)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1411)
    at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
    at java.util.concurrent.FutureTask.run(FutureTask.java)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

于是我将项目升级到2.1.6版本,之后还是无法启动,因为项目原先依赖的kafka无法启动,之后又去升级kafka依赖,以及修改kafka接收数据的代码。
总之,如果一个项目比较复杂,升级spring boot版本还是比较容易碰到坑的。

3. 创建Logging Admin 项目

Logging Admin 项目用来接口日志采集端发送过来的数据,而且一个Logging Admin可以接受多个数据采集端,可以理解为一对多的关系。

3.1 导入依赖

        
        
            org.minbox.framework
            api-boot-starter-logging-admin
        

        ApiBoot Mybatis Enhance
        
            org.minbox.framework
            api-boot-starter-mybatis-enhance
        
        MySQL驱动
        
            mysql
            mysql-connector-java
        
        Hikari数据源
        
            com.zaxxer
            HikariCP
        

如果不指定依赖的version的话,需要添加依赖管理,如果指定了依赖的具体版本,就不需要添加依赖管理了,上面的ApiBoot Logging引入时也是一样。

依赖管理:

    
    
        
            
                org.minbox.framework
                api-boot-dependencies
                2.1.2.RELEASE
                import
                pom
            
        
    

3.2 配置文件

server.port=20004
logging.level.org.minbox.framework.api.boot.plugin.logging=debug
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.username=root
spring.datasource.password=ahhx@123
spring.datasource.url=jdbc:mysql://192.168.220.46:3306/apiboot
# 格式化上报日志
api.boot.logging.admin.format-console-log-json=true
# 显示上报日志
api.boot.logging.admin.show-console-report-log=true

3.3 在数据库中运行sql文件

上面的配置文件中用到了数据库,需要初始化数据库,创建一些表。
表结构如下,将如下语句在对应的数据库中跑一次,即可。

 SET NAMES utf8mb4 ;

--
-- Table structure for table `logging_request_logs`
--

DROP TABLE IF EXISTS `logging_request_logs`;
 SET character_set_client = utf8mb4 ;
CREATE TABLE `logging_request_logs` (
  `lrl_id` varchar(36) NOT NULL COMMENT '主键,UUID',
  `lrl_service_detail_id` varchar(36) DEFAULT NULL COMMENT '服务详情编号,关联logging_service_details主键',
  `lrl_trace_id` varchar(36) DEFAULT NULL COMMENT '链路ID',
  `lrl_parent_span_id` varchar(36) DEFAULT NULL COMMENT '上级跨度ID',
  `lrl_span_id` varchar(36) DEFAULT NULL COMMENT '跨度ID',
  `lrl_start_time` mediumtext COMMENT '请求开始时间',
  `lrl_end_time` mediumtext COMMENT '请求结束时间',
  `lrl_http_status` int(11) DEFAULT NULL COMMENT '请求响应状态码',
  `lrl_request_body` longtext COMMENT '请求主体内容',
  `lrl_request_headers` text COMMENT '请求头信息',
  `lrl_request_ip` varchar(30) DEFAULT NULL COMMENT '发起请求客户端的IP地址',
  `lrl_request_method` varchar(10) DEFAULT NULL COMMENT '请求方式',
  `lrl_request_uri` varchar(200) DEFAULT NULL COMMENT '请求路径',
  `lrl_response_body` longtext COMMENT '响应内容',
  `lrl_response_headers` text COMMENT '响应头信息',
  `lrl_time_consuming` int(11) DEFAULT NULL COMMENT '请求耗时',
  `lrl_create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '日志保存时间',
  PRIMARY KEY (`lrl_id`),
  KEY `logging_request_logs_LRL_SERVICE_DETAIL_ID_index` (`lrl_service_detail_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='请求日志信息表';

--
-- Table structure for table `logging_service_details`
--

DROP TABLE IF EXISTS `logging_service_details`;
 SET character_set_client = utf8mb4 ;
CREATE TABLE `logging_service_details` (
  `lsd_id` varchar(36) NOT NULL,
  `lsd_service_id` varchar(200) DEFAULT NULL COMMENT '上报服务的ID,对应spring.application.name配置值',
  `lsd_service_ip` varchar(50) DEFAULT NULL COMMENT '上报服务的IP地址',
  `lsd_service_port` int(11) DEFAULT NULL COMMENT '上报服务的端口号',
  `lsd_last_report_time` timestamp NULL DEFAULT NULL COMMENT '最后一次上报时间,每次上报更新',
  `lsd_create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '首次上报时创建时间',
  PRIMARY KEY (`lsd_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='上报日志的客户端服务详情';

3.4 测试

将ApiBoot Logging配置文件中的对应的server-address: 127.0.0.1:20004指定为Logging Admin的ip和port。

使用postman调用ApiBoot Logging中的接口,可以看到的结果是ApiBoot Logging本地的控制台打印出来了日志,同时Logging Admin也收到了日志打印在控制台上并且存储在了数据库中。

4. 链式调用

之前引入ApiBoot Logging 的项目叫做ahhx-jcpt

spring:
  application:
    name: ahhx-jcpt

再创建一个ApiBoot Logging项目叫做apiboot,项目中提供一个接口

    @ResponseBody
    @GetMapping(value = "/index")
    public String hello(){
        return getStringApi.getString();
    }

在apiboot中的接口中调用ahhx-jcpt,就达成了链式调用。

需要注意的问题是,目前最新版2.1.2,只支持spring cloud openfeign调用,对于RestTemplate等方式调用,虽然也可以采集到日志,但是并不能正确得到traceId、spanId和parentSpanId。那么就无法分析出链路的调用关系。

关于spring cloud openfeign的简单使用将在后文描述。

使用postman调用apiboot的\index接口,查看ahhx-jcpt和apiboot的控制台,可以看到他们的traceId是一样的,并且ahhx-jcpt的parentSpanId就是apiboot的spanId。

5. spring cloud openfeign最简单使用

5.1 引入依赖

        
            org.springframework.cloud
            spring-cloud-starter-openfeign
            2.1.0.RELEASE
        

5.2 代码

5.2.1 创建一个接口

@FeignClient(name = "ahhx-jcpt", url = "http://localhost:9099")
public interface GetStringApi {
    @RequestMapping(value = "/levelone", method = RequestMethod.GET)
    String getString();
}

表示这个getString方法会调用http://localhost:9099/levelone接口

5.2.2 调用

@Controller
public class IndexController {

    @Autowired
    private GetStringApi getStringApi;

    @ResponseBody
    @GetMapping(value = "/index")
    public String hello(){
        return getStringApi.getString();
    }
}

当/index被调用,就会调用GetStringApi 的getString,而getString就会去调用http://localhost:9099/levelone接口

6. 在Logging Admin端用kafka存储日志,并关闭自带的存储数据库操作

6.1 关闭存储数据库操作

  • 删除依赖
        
        
            
            
        
        
        
            
            
        
        
        
            
            
        
  • 删除配置
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#spring.datasource.type=com.zaxxer.hikari.HikariDataSource
#spring.datasource.username=root
#spring.datasource.password=ahhx@123
#spring.datasource.url=jdbc:mysql://192.168.220.46:3306/apiboot

这样admin端,在收到数据以后就只会在控制台打印,不会存数据库

6.2 存储kafka

继承SmartApplicationListener

@Component
public class LocalNoticeSample implements SmartApplicationListener {

    @Autowired
    private KafkaSender kafkaSender;

    @Override
    public boolean supportsEventType(Class eventType) {
        return eventType == ReportLogEvent.class;
    }

    @Override
    public boolean supportsSourceType(Class sourceType) {
        return sourceType == LoggingEndpoint.class;
    }

    /**
     * order 值越小执行越靠前
     *
     * @return
     */
    @Override
    public int getOrder() {
        return 2;
    }

    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        System.out.println("onApplicationEvent");
        ReportLogEvent reportLogEvent = (ReportLogEvent) applicationEvent;
        ApiBootLogClientNotice notice = reportLogEvent.getLogClientNotice();
        kafkaSender.sendMessage(notice.getLoggers().toString());
    }
}

kafka发送代码:

@EnableBinding(Source.class)
@Component
public class KafkaSender {

    private final Logger logger = LoggerFactory.getLogger(KafkaSender.class);

    @Autowired
    private Source source;

    public void sendMessage(String msg){
        try {
            logger.info("准备发送数据到kafka->数据:【message:"+msg+"】");
            source.output().send(MessageBuilder.withPayload(msg).build());
        }catch (Exception e){
            logger.info("准备发送数据到kafka->出错");
            e.printStackTrace();
        }
    }

}

kafka接收代码

@Component
@EnableBinding(Sink.class)
public class KafkaReceive {
    private final Logger logger = LoggerFactory.getLogger(KafkaReceive.class);

    @StreamListener(Sink.INPUT)
    public void process(Message message) throws  Exception {
        logger.info("监听到kafka数据->输入参数【message:"+message+"】");
        String receivedString = message.getPayload();
        logger.info("received kafka message : value:"+receivedString);
    }
}

7. 追诉

7.1 提取日志

在admin端想要提取日志,需要写一个类集成SmartApplicationListener 即可。

在Logging端目前无法直接提取日志(2.1.2版本),根据作者说明,在下一版本将可以直接提取日志。

并且据说下一个版本admin端将提供可视界面,以及日志上报到admin可以关闭,让日志不上报。

8. 相关链接

还有很多的功能这里并没有介绍,具体可查看如下地址:
http://apiboot.minbox.io/zh-cn/docs/api-boot-logging-admin.html
https://github.com/hengboy/api-boot
https://gitee.com/hengboy/api-boot

非常感谢作者
@恒宇少年https://github.com/hengboy提供的帮助

你可能感兴趣的:(ApiBoot Logging 和Logging Admin使用总结)