【Agent无侵入式日志采集方案解决】

本文介绍了在SpringBoot项目中,随着业务发展和日志量增长带来的问题,以及如何通过BPM插件log-agent-framework进行日志结构化、关联性增强、异常监控,以及如何配置日志系统、选择日志级别和使用AOP切面。同时,文章还涵盖了ClassLoader的作用和项目接入步骤。
摘要由CSDN通过智能技术生成

背景

企业以springboot框架作为一个业务开发的脚手架,随着业务的迭代,以及访问量提升,服务实例递增,会导致海量日志产生;无规则的海量日志会存在以下问题 1、日志与日志之间缺少关联关系 (如果找到用户一次请求的所有日志)2、日志信息不全(该打印的时候没有打印,不该打印的打印)如果业务发生异常,开发者第一时间很难从海量的日志,第一时间定位相关问题,3、日志难以清洗,BPM插件就是解决以上问题,并且可以提供以开发视角的定义业务异常从而业务报警

什么是bpm

bpm 是 Business Performance Management 缩写,log-agent-framework是bpm 的实现,它是一款日志中间是一款基于java agent 的日志插件,用于结构化输出日志数据,便于后期的问题排查,以及业务的报警;通过maven接入,将spring 项目的日志以json输出。

产品定位

类型用途代表
NPM用于监控设施的基础信息,如网络、io、cpu 等,一般运维使用prometheus
APM用于监控应用的数据链路,日志数据大,且详细,一般运维使用pinpoint、SkyWalking
BPM用于监控重要业务,日志量少,且精确,一般开发人员使用

优点

  • 对于项目维护:采用无侵入方式,较少冗余的代码
  • 对于程序开发:提供公共位置的日志收集,加快开发进度
  • 对于问题排查:采用标准的日志格式,可根据类型快速过滤排查
  • 对于项目预警:采用模块化+api,可自定操作

什么是日志数据

它是刻意产生,并且有目的搬运、清洗、存贮,最终消费的信息流,为用户提供更好的服务;并不是所有的日志都是日志数据,对用户有意义的日志,并且耗费精力维护,才能称为日志数据。

什么时候用

  • 后端开发人员排查bug,定位问题
  • 前端人员开发优化资源加载性能
  • leader 需要重要报表数据,提供决策
  • 异常报警

如何构建日志系统

  • 日志生产:规定日志规范json,以及实现
  • 日志搬运:sdk、filebeat、日志汇总、阿里日志服务、oss
  • 日志清洗:logstash、json、脚印、柯南系统、es
  • 日志存贮:hive、es、mysql
  • 日志消费:kibana、grafa、报警、报表

日志级别选型

建议日志级别保留时间
不建立,或者自建elk小型或者微型日志保留时间建议
自建elk,或者阿里 elk 或者阿里日志服务中型小于1天
阿里日志服务大型小于3天

Bpm日志数据结构

日志层次划分

层次例子日志type
入口日志controller、httpServletACCESS
出口日志http、hessionTHIRD
用户自定义日志service、自定义logSERVICE

日志aop 切面

切面例子
类上的注解切面@RestController
方法上的注解切面@RequestMapping
@PostMapping
@GetMapping
@DeleteMapping
@PutMapping
正则切面“^com.bb.bbfff.code.util.HttpUtils$:THIRD”: !!seq
  • “[a-zA-Z].*” | |
    | 精确切面 | | |

精确切面 配置
@HOOk
instruments:array【injectionClass】
skipNestedCalls:boolean【跳过嵌套切点,默认true】

eg:@Hook(instruments = {“javax.servlet.http.HttpServlet”})

@Before
method:array【injectionMethod】
备注:默认还通过参数的类型,和个数匹配

eg:@Before(method = {“addRequestHeaders”})
public void before(HessianConnection conn) {
LogUtils.insertEmptyAccessId();
conn.addHeader(LogConstants.HEADER_REQUEST_ID, LogUtils.getAccessId());
}

@After:
method:array【injectionMethod】
@Return :Object【返回值】
@Thrown:Thrown【方法异常】
备注:默认还通过参数的类型,和个数匹配

eg:@After(method = {“invoke”})
public void after(Object proxy, Method method, Object[] args, @Returned Object ret, @Thrown Throwable t) {
String executeTime = LogUtils.getContext(method.toString());
executeTime = String.valueOf(System.currentTimeMillis() - Long.parseLong(executeTime));
LogUtils.hessianLog(executeTime, t, ret, method, args);
}


callbackMethodName和callbackFrameErrorMethodName传递参数类型

| 参数名      | 参数类型  | 含义     |
| ----------- | --------- | -------- |
| executeTime | String    | 执行时间 |
| throwable   | Throwable | 方法异常 |
| ret         | Object    | 返回的值 |
| method      | Method    | 执行方法 |
| arguments   | Object[]  | 方法参数 |

hook配置

key说明默认值
promagent.fastHooks.scheduledPack默认收集定时任务日志收集该包下的@schedulednull
promagent.agent.appName项目名appName
promagent.agent.callClass回调classcom.cyou.agent.core.Logger
promagent.agent.debugclassLoader 调试false
promagent.agent.headers收集headers,all:收集全部header,none:不收集,headerA:headerB:headerC:收集header列表Arrays.asList<"none">
promagent.agent.ignoreSignatures不收集方法集合日志new ArrayList<String>
promagent.agent.mdcLogId传递到Mdc 中的 LogIdaccess_id
promagent.agent.retMaxLength收集ret 最大长度默认20480
promagent.agent.skipRetSignatures不收集放回的值的方法签名集合new ArrayList<String>
promagent.agent.traceId传递到其他项目的LogIdX-REQUEST-ID
promagent.fastHooks.controllerPack默认收集controller日志,收集该包下的@RequestMapping、@PostMapping、@GetMapping、@DeleteMapping、@PutMapping
日志null
promagent.fastHooks.scheduledPack默认收集定时任务日志,收集该包下的@scheduled null
promagent.hooks.annClassHook类注解回调@Controller new HashMap<String,List<String>>
promagent.hooks.annMethodHook方法注解回调Hook 例如:@RequestMapping new HashMap<String,List<String>>
promagent.hooks.annRegHook正则回调,详细例子见 com.cyou:log-agent-load中的 hook.yml 配置文件new HashMap<String,List<String>>
promagent.load.agentJar加载agentJar 路径null
promagent.load.result加载结果,true:加载成功,false:加载失败false
promagent.load.time加载agent 花费时间-1

正则收集例子

# hook 文件模板
#annMethodHook:                                                               # 方法注解日志打印
#      "^com.bb.*": !!seq                                                     # 正则表达式:^com.bb.*的package
#           - "org.springframework.web.bind.annotation.RequestMapping:ACCESS" # 日志打印 @RequestMapping 修饰的方法
#           - "org.springframework.web.bind.annotation.PostMapping:ACCESS"    # 日志打印 @PostMapping 修饰的方法
#           - "org.springframework.web.bind.annotation.GetMapping:ACCESS"     # 日志打印 @GetMapping 修饰的方法
#           - "org.springframework.web.bind.annotation.DeleteMapping:ACCESS"  # 日志打印 @DeleteMapping 修饰的方法
#           - "org.springframework.web.bind.annotation.PutMapping:ACCESS"     # 日志打印 @PutMapping 修饰的方法
#           - "org.springframework.scheduling.annotation.Scheduled:CRON"      # 日志打印 @Scheduled 修饰的方法
#
#annClassHook:                                                                # 类注解日志打印
#      "^com.bb.*": !!seq                                                     # 正则表达式:^com.bb.*的package
#           - "org.springframework.web.bind.annotation.RestController:ACCESS" #日志打印 @RestController 修饰的方法
#regHook:                                                                     # 正则日志打印
#      "^com.bb.bbfff.code.util.HttpUtils$:THIRD": !!seq                      # 正则表达式:^com.bb.bbfff.code.util.HttpUtils$ 的package,日志类型为 THIRD,默认为SYSTEM
#           - ".*post.*"                                                      # 正则表达式:.*post.* 的所有方法
#      "^com.bb.bbfff.code.plpay.common.BaasHttpUtil$:THIRD": !!seq
#           - "[a-zA-Z].*"                                                    # 正则表达式:[a-zA-Z].* 的所有方法,不能直接使用.*,需要加限定词[a-zA-Z].*
#      "^com.bb.common.util.HttpClientProxy$:THIRD": !!seq
#           - "[a-zA-Z].*:1"                                                  # 正则表达式:[a-zA-Z].*:1 的所有方法,方法参数个数为1
#      "^com.bb.openplatform.http.HttpUtil$:THIRD": !!seq
#           - ".*Response.*"                                                  # 正则表达式:.*Response.* 的所有方法

ClasssLoader 加载

  • 对于agent,他的平级目录class文件是被appClassLoader引用,promagent-agent下面的四个class,可被全局的class所访问
  • ClassLoaderCache作为单例,不同的classLoader,例如webappClassLoader回调的时候,ClassLoaderCache会将共享的jar分享出去
  • 因为ClassLoaderCache先查询 sharedClassLoader,ClassLoaderCache单例,所以sharedClassLoader对象唯一,所以全局会共享一个Delegator【Delegator用于精确匹配回调方法】

项目接入:

  • 添加一个maven插件 和依赖
<dependency>
    <groupId>io.promagent</groupId>
    <artifactId>log-agent-load</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <executions>
        <execution>
            <id>copy</id>
            <phase>process-resources</phase>
            <goals>
                <goal>copy</goal>
            </goals>
            <configuration>
                <artifactItems>
                    <artifactItem>
                        <groupId>io.promagent</groupId>
                        <artifactId>log-agent-builder</artifactId>
                        <version>1.0-SNAPSHOT</version>
                        <overWrite>true</overWrite>
                        <destFileName>log-agent.jar</destFileName>
                        <outputDirectory>${project.build.directory}/classes/</outputDirectory>
                    </artifactItem>
                </artifactItems>
                <overWriteReleases>false</overWriteReleases>
                <overWriteSnapshots>false</overWriteSnapshots>
                <overWriteIfNewer>true</overWriteIfNewer>
                <stripVersion>true</stripVersion>
             </configuration>
        </execution>
    </executions>
</plugin>
  • 添加hook.yml 配置文件
promagent:
  agent:
    appName: testAgent
    headers: !!seq
      - "all"
  fastHooks:
     controllerPackage: io.promagent.agent.test.controller
  load:
    agentJar: "E:\\study\\promagent\\promagent-log\\log-agent-builder\\target\\log-agent.jar"

备注:更多配置文件先hook 说明
启动方式

  • spring 启动:添加 @ComponentScan(basePackages={“io.promagent.agent”})
  • 非spring 启动:new AgentBootstrap().init()

建议

为了是用户无感知升级,建议通过jvm参数,统一配置管理,以及升级

后期规划

  • spring boot 自动加载
  • 配置文件动态更新

问题反馈

  • 微信:javazhangyi
  • 邮件:javazhangyi@163.com
  • 10
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在HTTP请求头中,User-Agent是一个标识客户端应用程序、操作系统、设备厂商和版本等信息的字符串。通过分析User-Agent字符串,可以确定请求是来自PC还是移动设备。 以下是一些常用的解决方案: 1. 根据User-Agent字符串进行分析 通过分析User-Agent字符串,可以识别请求来自哪种设备。例如,常见的PC浏览器的User-Agent字符串可能包含“Windows”或“Macintosh”等关键词,而移动设备的User-Agent字符串则可能包含“iPhone”、“Android”等关键词。可以使用服务器端的脚本语言(如PHP、Python、Java等)来获取User-Agent并分析它,从而确定请求来自哪种设备。 2. 使用响应设计 使用响应设计的网站可以自动适应不同的设备尺寸和屏幕分辨率。无论用户是在PC上还是在移动设备上访问网站,网站都可以自适应调整布局和样,以适应不同的设备。 3. 使用CSS媒体查询 CSS媒体查询可以根据屏幕尺寸和分辨率来设置不同的CSS样。通过使用CSS媒体查询,可以根据设备屏幕大小和分辨率的不同,为PC和移动设备设置不同的样和布局。 4. 使用JavaScript 使用JavaScript可以获取设备的屏幕宽度和高度,并根据这些信息来动态调整布局和样。可以使用JavaScript编写代码来检测设备屏幕大小,并根据需要加载不同的CSS文件或JavaScript文件。 需要注意的是,User-Agent字符串并不是100%可靠的方来区分PC和移动设备。例如,某些移动设备的浏览器可能会伪装成PC浏览器来访问网站。因此,在设计响应网站或应用程序时,应该考虑到这种情况,使用多种方来适应不同的设备。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值