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 引入依赖

        <!--ApiBoot Logging-->
        <dependency>
            <groupId>org.minbox.framework</groupId>
            <artifactId>api-boot-starter-logging</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>

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.<init>(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 导入依赖

        <!--ApiBoot Logging Admin-->
        <dependency>
            <groupId>org.minbox.framework</groupId>
            <artifactId>api-boot-starter-logging-admin</artifactId>
        </dependency>

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

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

依赖管理:

    <!--ApiBoot 版本依赖-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.minbox.framework</groupId>
                <artifactId>api-boot-dependencies</artifactId>
                <version>2.1.2.RELEASE</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

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 引入依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>2.1.0.RELEASE</version>
        </dependency>

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 关闭存储数据库操作

  • 删除依赖
        <!--ApiBoot Mybatis Enhance-->
        <!--<dependency>-->
            <!--<groupId>org.minbox.framework</groupId>-->
            <!--<artifactId>api-boot-starter-mybatis-enhance</artifactId>-->
        <!--</dependency>-->
        <!--MySQL驱动-->
        <!--<dependency>-->
            <!--<groupId>mysql</groupId>-->
            <!--<artifactId>mysql-connector-java</artifactId>-->
        <!--</dependency>-->
        <!--Hikari数据源-->
        <!--<dependency>-->
            <!--<groupId>com.zaxxer</groupId>-->
            <!--<artifactId>HikariCP</artifactId>-->
        <!--</dependency>-->
  • 删除配置
#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<? extends ApplicationEvent> 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<String> 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提供的帮助

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容