ApiBoot 文档

ApiBoot 零侵入、链路式请求日志分析框架

ApiBoot提供单应用、微服务应用下的请求日志分析框架ApiBoot Logging,特性列表如下所示:

  • 链路:为每一个请求生成一个链路的单条或者多条请求日志信息,精准定位程序出现问题的位置。
  • 耗时:可以分析出每一个请求的耗时,改善服务的性能瓶颈
  • 异常信息:请求遇到异常,提供请求日志的异常堆栈信息记录
  • 请求参数:可以获取RequestBodyPathParam两种方式的参数
  • 响应内容:可以获取本次请求响应的内容信息。
  • 服务信息:可获取提供服务的ID、IP、Port等信息。
  • 异步通知:请求日志数据采集后通过Event/Listener方式异步通知给RPCMQRESTLocal
  • 数据分析:阈值警告、异常通知等(短信、邮件等方式,2.1.1.RELEASE版本未实现.)

ApiBoot Logging内部采用拦截器Filter组合完成一系列的操作。

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

架构设计图

ApiBoot Logging架构设计图

1. 添加依赖组件

pom.xml配置文件内添加依赖,如下所示:

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

内部封装的组件

ApiBoot2.1.3.RELEASE版本开始陆续会将api-boot-plugins模块下的组件从api-boot项目分离到minbox-projects开源组织内作为独立的项目进行升级维护,ApiBoot Logging内部通过封装minbox-projects/minbox-logging组件的minbox-logging-client实现。

minbox-logging源码地址:https://gitee.com/minbox-projects/minbox-logging

注意:如果未添加ApiBoot版本依赖,请访问版本依赖查看添加方式。

2. 修改采集日志路径前缀

ApiBoot Logging默认采集/**下的所有请求路径,通过修改application.yml配置来变更采集日志路径的前缀:

api:
  boot:
    logging:
      # 修改日志路径前缀,可配置多个
      logging-path-prefix:
        - /user/**
        - /order/**

在上面配置中,ApiBoot Logging只会采集/user/**/order/**下所有路径的请求,并将日志上报到ApiBoot Logging Admin

3. 排除不采集日志的路径

ApiBoot Logging可以指定某些路径的请求日志不进行采集、上报,比如:/actuator/health,通过修改application.yml配置来排除采集日志的路径列表,如下所示:

api:
  boot:
    logging:
      # 排除采集请求日志的路径列表
      ignore-paths:
        - /actuator/health

4. 修改日志等级

如果想让ApiBoot Logging在控制台输出日志信息,需要修改application.yml配置文件内的日志等级,如下所示:

logging:
  level:
    org.minbox.framework.api.boot.plugin.logging: debug

5. 美化控制台打印日志信息

为了方便查看控制台打印的请求日志信息,ApiBoot Logging支持美化日志json信息,配置文件如下所示:

api:
  boot:
    logging:
      format-console-log-json: true

效果如下所示:

2019-07-24 12:56:11.231 DEBUG 3833 --- [nio-8080-exec-2] f.a.b.p.l.n.ApiBootLoggingNoticeListener : Request Uri:/index, Logging:
{
	"endTime":1563944171115,
	"httpStatus":200,
	"requestBody":"{\n\t\"name\":\"测试\",\n\t\"email\":\"jnyuqy@gmail.com\"\n}",
	"requestHeaders":{
		"accept":"*/*",
		"accept-encoding":"gzip, deflate",
		"cache-control":"no-cache",
		"connection":"keep-alive",
		"content-length":"52",
		"content-type":"application/json",
		"cookie":"JSESSIONID=8E206A652D76A5DF1775FC8988549DF4",
		"host":"localhost:8080",
		"postman-token":"cc60ceb7-32de-46f1-a7a4-86d694edd073",
		"user-agent":"PostmanRuntime/7.15.2"
	},
	"requestIp":"0:0:0:0:0:0:0:1",
	"requestMethod":"POST",
	"requestUri":"/index",
	"responseBody":"测试",
	"responseHeaders":{},
	"serviceId":"api-boot-sample-logging",
	"serviceIp":"192.168.10.156",
	"servicePort":"8080",
	"spanId":"81c0016e-9bf7-4e86-bcad-98574f0df14c",
	"startTime":1563944171049,
	"timeConsuming":66,
	"traceId":"409b6f2c-f70a-4648-9673-915534bc5aa9"
}

6. 日志上报方式

ApiBoot Logging内部提供了两种上报方式,分别是justtiming

6.1 just方式

just顾名思义是直接方式上报,也是ApiBoot Logging内默认的方式,当一个请求日志产生之后会实时上报Logging Admin,利弊分析:

  • 优点
    • 数据分析延迟低
    • 提高数据有效性,防止丢失
  • 弊端
    • 增加Logging Admin访问压力

6.2 timing方式

timing方式是定时上报,ApiBoot Logging通过配置参数api.boot.logging.report-away=timing启用,默认间隔5秒执行一次上报,每次上报10条请求日志。

6.2.1 修改上报间隔时间

ApiBoot Logging默认间隔5秒发起一次上报请求到Logging Admin,修改配置如下所示:

api:
  boot:
    logging:
      # 修改每间隔10秒执行一次上报日志
      report-interval-second: 10

6.2.2 修改每次上报条数

ApiBoot Logging默认每次发送缓存内的10条日志到Logging Admin,修改配置如下所示:

api:
  boot:
    logging:
      # 修改每次上报2条请求日志
      number-of-request-log: 2

7. 日志上报到指定Admin

ApiBoot Logging 支持上报请求日志到指定Admin服务节点,配置如下所示:

api:
  boot:
    logging:
      admin:
        # Logging Admin 服务地址
        server-address: 127.0.0.1:9090

注意:Logging Admin服务地址不需要配置http://路径前缀。

8. 日志上报到服务注册中心Admin

ApiBoot Logging支持从服务注册中心(Eureka Server、Nacos Discovery、Consul...)中获取指定serviceID的服务列表,并且自动通过LoadBalance方式上报请求日志。

api:
  boot:
    logging:
      discovery:
        # Logging Admin ServiceID
        service-id: sample-logging-admin

Logging Admin ServiceID对应Logging Admin服务的spring.application.name属性配置。

9. 日志缓存

ApiBoot Logging内部默认通过memory(内存方式)进行缓存请求日志,目前版本无法修改缓存方式

10. 支持SpringSecurity安全上报日志

安全性是日志上报的基础能力之一,ApiBoot Logging内部集成Spring SecurityBasic安全认证方式来完成上报鉴权。

10.1 指定Admin的安全配置

ApiBoot Logging指定Admin服务节点的方式跟Eureka Client ServerUrl类似,在路径上添加Logging Admin配置的Spring Security用户名密码,配置如下所示:

api:
  boot:
    logging:
      admin:
        # 用户名:user,密码:123456
        server-address: user:123456@127.0.0.1:9090

10.2 服务注册中心Admin的安全配置

服务注册中心方式配置Logging AdminSpring Security用户名、密码方式如下所示:

api:
  boot:
    logging:
      discovery:
        # Logging Admin ServiceID
        service-id: sample-logging-admin
        # 用户名
        username: user
        # 密码
        password: 123456

11. 日志通知

ApiBoot Logging提供了日志的通知功能,利用该功能可以对每一条请求日志进行输出、存储、分析等,通过实现LoggingNotice接口使用通知功能,示例如下所示:

@Component
public class LocalNoticeSample implements LoggingNotice {
    /**
     * order 值越小执行越靠前
     *
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }

    /**
     * 请求日志通知执行方法
     * MinBoxLog为一次请求日志对象基本信息
     *
     * @param minBoxLog ApiBoot Log
     */
    @Override
    public void notice(MinBoxLog minBoxLog) {
        System.out.println(minBoxLog);
    }
}

ApiBoot Logging提供的LoggingNotice支持多个实现类配置,执行顺序根据getOrder()方法的返回值来定义,getOrder()方法返回值越小越靠前执行MinBoxLog对象定义如下所示:


/**
 * ApiBoot Log Object
 *
 * @author:恒宇少年 - 于起宇
 */
@Data
public class MinBoxLog implements Serializable {
    /**
     * trace id
     */
    private String traceId;
    /**
     * span id
     */
    private String spanId;
    /**
     * parent span id
     */
    private String parentSpanId;
    /**
     * request uri
     */
    private String requestUri;
    /**
     * request method
     */
    private String requestMethod;
    /**
     * http status code
     */
    private int httpStatus;
    /**
     * request ip
     */
    private String requestIp;
    /**
     * service ip address
     */
    private String serviceIp;
    /**
     * service port
     */
    private String servicePort;
    /**
     * start time
     */
    private Long startTime;
    /**
     * end time
     */
    private Long endTime;
    /**
     * this request time consuming
     */
    private long timeConsuming;
    /**
     * service id
     */
    private String serviceId;
    /**
     * request headers
     */
    private Map<String, String> requestHeaders;
    /**
     * request param
     */
    private String requestParam;
    /**
     * request body
     */
    private String requestBody;
    /**
     * response headers
     */
    private Map<String, String> responseHeaders;
    /**
     * response body
     */
    private String responseBody;
    /**
     * exception stack
     */
    private String exceptionStack;
    /**
     * Global method log list
     */
    private List<GlobalLog> globalLogs;
}

12. 无缝支持Openfeign

ApiBoot Logging支持Spring Cloud Openfeign的方式请求,在SpringCloud微服务应用中如果你发起一个Http请求,而该请求在服务端通过openfeign访问其他服务,这时ApiBoot Logging会通过openfeignInterceptor携带TraceIdSpanId到下一个服务,完成请求日志的链路信息透传。

13. GlobalLogging

GlobalLogging是全局日志,可以用来采集业务代码中的不同等级的日志,支持采集异常堆栈日志产生类日志产生源码行日志产生方法等信息。

ApiBoot Logging提供了一个配置参数,用于修改GlobalLogging的临时存储方式,默认为内存方式(memory),如下所示:

api:
  boot:
    logging:
      global-logging-storage-away: memory

该配置目前只提供了这一种存储方式,所以我们直接使用默认值即可,可以不用在application.yml文件内添加。

13.1 Debug等级日志

GlobalLogging提供了两种debug方法,方法定义源码如下所示:

/**
  * Collect Debug Level Logs
  *
  * @param msg log content
  */
void debug(String msg);

/**
  * Collect Debug Level Logs
  *
  * @param format    Unformatted log content
  * @param arguments List of parameters corresponding to log content
  */
void debug(String format, Object... arguments);

使用示例如下所示:

@Autowired
private GlobalLogging logging;

// debug(String msg)
logging.debug("这是一个debug等级的日志");

// debug(String format,Object... arguments)
logging.debug("这是一个debug等级的日志,执行时间:{}" , System.currentTimeMillis());

// debug(String format, Object... arguments)
logging.debug("多参数演示,第一个参数值:{},第二个参数值:{},第三个参数值:{}",16.7,"b",10);

由于arguments参数采用的可变长度参数传递,所以这里允许传递多个不同类型的填充{}占位符的内容。

13.2 Info等级日志

GlobalLogging接口中同样为info级别的日志采集提供了两个方法,方法定义源码如下所示:

/**
  * Collect Info Level Logs
  *
  * @param msg log content
  */
void info(String msg);

/**
  * Collect Info Level Logs
  *
  * @param format    Unformatted log content
  * @param arguments List of parameters corresponding to log content
  */
void info(String format, Object... arguments);

使用示例如下所示:

@Autowired
private GlobalLogging logging;

// info(String msg)
logging.info("这是一个info等级的日志");

// info(String format, Object... arguments)
logging.info("这是一个info等级的日志,记录时间:{}" , System.currentTimeMillis());

// info(String format, Object... arguments)
logging.info("多参数演示,第一个参数值:{},第二个参数值:{},第三个参数值:{}","a","b",10);

由于arguments参数采用的可变长度参数传递,所以这里允许传递多个不同类型的填充{}占位符的内容。

13.3 Error等级日志

GlobalLogging接口中为error等级的日志则是提供了3个方法,比info/debug多出了一个采集异常堆栈信息的方法,源码如下所示:

/**
  * Collect Error Level Logs
  *
  * @param msg log content
  */
void error(String msg);

/**
  * Collect Error Level Logs
  *
  * @param msg       log content
  * @param throwable Exception object instance
  */
void error(String msg, Throwable throwable);

/**
  * Collect Error Level Logs
  *
  * @param format    Unformatted log content
  * @param arguments List of parameters corresponding to log content
  */
void error(String format, Object... arguments);

其中error(String msg)以及error(String format, Object... arguments)方法使用方式与debug/info一致。

13.4 异常堆栈信息

/**
  * Collect Error Level Logs
  *
  * @param msg       log content
  * @param throwable Exception object instance
  */
void error(String msg, Throwable throwable);

采集异常堆栈信息的error方法,需要我们把异常实例作为参数传递,在内部通过printStackTrace()方法进行转换堆栈信息。

使用示例如下所示:

try {
  int a = 5 / 0;
} catch (Exception e) {
  logging.error("出现了异常.", e);
}