脑洞:字节码加强 (1) 日志收集方案
背景
需求:
java技术栈,要接入目前所有的项目到日志中心,需求看似比较简单
但是实施的过程中各种问题,项目所属不同部门,使用开发框架不同,人员能力水平不同,
-
方案选择:
-
方案1 各个项目接入logstash,
各类日志框架都有,log4j logback log4j2(apache.logging.log4j),十分混乱,而且开发人员需要引入jar,有可能会与现有的jar版本冲突,有一定的开发成本.
-
方案2 基于现有的文件日志,filebeat收集之后,再进行分析,发送到消息队列,然后转储到elastic search.
可是日志格式不统一,kao
-
方案3 基于字节码加强,
重写log4j logback log4j2中关于打印日志的方法,进行拦截. 加入我们自己的逻辑,将日志以可控的形式进行记录,将消息直接入mq,再由消费端进行消费(可以是logstash或者自研的处理程序),然后将此程序集成至基础docker镜像,JAVA_OPTS参数设定javaagent路径即可
-
- 实施:
字节码加强框架选择:asm bytebuddy jvm-sandbox,前两个坚决不用,原因:我不相信我自己写的代码,烂+懒
所以选jvm-sandbox github地址
还有另一个原因,类隔离,对加强的项目没有影响
项目里面有个比较好理解的demo,拦截异常的, 可以在这里查看
上代码,再解释(log4j)
new EventWatchBuilder(moduleEventWatcher) .onClass("org.apache.log4j.Category") .onBehavior("callAppenders") .onWatch(new AdviceListener() { @Override public void afterReturning(Advice advice) { try { //定义一个错误级别(默认保持与ERROR一致 int errorLevel = 40000; //获取event变量 Object event = advice.getParameterArray()[0]; // 获取event对应的日志级别 int level = invokeMethod(invokeMethod(event, "getLevel"), "toInt"); // 获取日志的打印时间 long timeStamp = invokeField(event, "timeStamp"); // 获取日志格式化后的字符串 String msg = invokeMethod(event, "getRenderedMessage"); // 获取logger name String loggerName = invokeMethod(event, "getLoggerName"); // 获取线程名 String threadName = invokeMethod(event, "getThreadName"); // 如果小于默认的错误级别 if (level < errorLevel) { // 将日志信息发送到本地队列,等待(异步)发送 offerAppLog(timeStamp, msg, level, loggerName, threadName, null); } else { // 如果是错误级别,定义throwable变量 Throwable throwable = null; // 获取ThrowableInformation信息 Object throwProxy = invokeMethod(event, "getThrowableInformation"); if (throwProxy != null) { // 从throwable代理类中获取真实错误信息 throwable = invokeMethod(throwProxy, "getThrowable"); } // 将带有错误信息的消息发送到本地消息队列,待发送 offerAppLog(timeStamp, msg, level, loggerName, threadName, throwable); // 接入点评的CAT,将错误信息输出到CAT大盘,用于报警 Cat.logError("[ERROR] " + msg, throwable); // 设定当前的context有错误信息,做后续处理 Cat.getManager().setHasError(true); } } catch (Exception ex) { //黑洞 } } });
org.apache.log4j.Category.callAppenders 这个方法是log4j框架,在write message之前调用的方法,是将符合设置的level的message写入各个配置中定义的appender.我们加强这段代码,相当于增加了一个自定义的 appender,把数据输入进去.
说明一下,里面用了反射,是使用了缓存的反射,是jvm-sandbox的机制,因为classloader的类加载策略,目前只能使用反射,经测试,并不会对性能有明显损失,后续文章会将性能测试贴出.
接下来 logback加强,一样的类似,基本和log4j没有区别
new EventWatchBuilder(moduleEventWatcher) .onClass("ch.qos.logback.classic.Logger") .onBehavior("callAppenders") .onWatch(new AdviceListener() { @Override public void afterReturning(Advice advice) { try { int errorLevel = 40000; Object event = advice.getParameterArray()[0]; int level = invokeMethod(invokeMethod(event, "getLevel"), "toInt"); long timeStamp = invokeMethod(event, "getTimeStamp"); String msg = invokeMethod(event, "getFormattedMessage"); String loggerName = invokeMethod(event, "getLoggerName"); String threadName = invokeMethod(event, "getThreadName"); if (level < errorLevel) { offerAppLog(timeStamp, msg, level, loggerName, threadName, null); } else { Throwable throwable = null; Object throwProxy = invokeMethod(event, "getThrowableProxy"); if (throwProxy != null) { throwable = invokeMethod(throwProxy, "getThrowable"); } offerAppLog(timeStamp, msg, level, loggerName, threadName, throwable); Cat.logError("[ERROR] " + msg, throwable); Cat.getManager().setHasError(true); } } catch (Exception ex) { //黑洞 } } });
不解释 ,接下来 log4j2 ,比较类似 ,区别是,log4j2的level值,和logback log4j不同, 是反过来的,而且值也不同,所以做了一个转换
new EventWatchBuilder(moduleEventWatcher) .onClass("org.apache.logging.log4j.core.config.LoggerConfig") .onBehavior("callAppenders") .onWatch(new AdviceListener() { @Override public void afterReturning(Advice advice) { try { int errorLevel = 40000; Object event = advice.getParameterArray()[0]; int level = invokeMethod(invokeMethod(event, "getLevel"), "intLevel"); if (level >= 500) { level = 10000; } else if (level >= 400) { level = 20000; } else if (level >= 300) { level = 30000; } else if (level >= 200) { level = 40000; } else if (level >= 100) { level = 40000; } else { level = 40000; } long timeStamp = invokeMethod(event, "getTimeMillis"); String msg = invokeMethod(invokeMethod(event, "getMessage"), "getFormattedMessage"); String loggerName = invokeMethod(event, "getLoggerName"); String threadName = invokeMethod(event, "getThreadName"); if (level < errorLevel) { offerAppLog(timeStamp, msg, level, loggerName, threadName, null); } else { Throwable throwable = invokeMethod(event, "getThrown"); offerAppLog(timeStamp, msg, level, loggerName, threadName, throwable); Cat.logError("[ERROR] " + msg, throwable); Cat.getManager().setHasError(true); } } catch (Exception ex) { //黑洞 } } });
ok ,以上就是第三种日志收集方案的核心代码,本系列文章完成之前会开放源码供参考.
脑洞:字节码加强 (2) 动态日志level
脑洞:字节码加强 (3) APM方案埋点解析
脑洞:字节码加强 (4) tomcat访问日志收集
脑洞:字节码加强 (5) 业务问题排查方案
脑洞:字节码加强 (6) 性能测试
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
微服务架构案例(06):通过业务、应用、技术、存储方面,聊聊架构
更新进度(共6节): 章节 文章标题 01 项目技术选型简介,架构图解说明 02 业务架构设计,系统分层管理 03 数据库选型,业务数据设计规划 04 中间件集成,公共服务管理 05 SpringCloud 基础组件应用设计 06 通过业务、应用、技术、存储方面,聊聊架构 一、架构的概念 架构分类可细化的分为业务架构、应用架构、技术选型、代码规划、部署环境架构等。业务架构是核心的驱动力,应用架构是实现的思路,技术选型落地是结果。根据用户需求,设计合理的业务架构,做出相应的应用架构流程,最后落地实施,完成项目。如何在架构的初期,预判业务发展的速度,保证架构可以稳定快速的扩展,支撑起业务发展,这个是软件开发者,特别是架构师,需要长期积累和修炼的核心能力。 二、业务架构 业务架构中包括业务规划、功能模块、流程设计,微服务架构模式中对整个系统的业务进行服务化拆分设计,把实际的业务抽象化,进而进行封装,优化服务结构。不需要最好的架构,只选则合适的架构,系统架构的原则都要以解决业务问题为核心目标,任何不基于业务做天马行空的架构都是对公司的不负责任。 三、应用架构 应用架构流程是基于业务架构来设计的...
- 下一篇
HTTPS双向认证研究
作者:楚骧 研究HTTPS的双向认证实现与原理,踩了不少坑,终于整个流程都跑通了,现在总结出一篇文档来,把一些心得,特别是容易踩坑的地方记录下来。 1.原理 双向认证,顾名思义,客户端和服务器端都需要验证对方的身份,在建立Https连接的过程中,握手的流程比单向认证多了几步。单向认证的过程,客户端从服务器端下载服务器端公钥证书进行验证,然后建立安全通信通道。双向通信流程,客户端除了需要从服务器端下载服务器的公钥证书进行验证外,还需要把客户端的公钥证书上传到服务器端给服务器端进行验证,等双方都认证通过了,才开始建立安全通信通道进行数据传输。 1.1 单向认证流程 单向认证流程中,服务器端保存着公钥证书和私钥两个文件,整个握手过程如下: 客户端发起建立HTTPS连接请求,将SSL协议版本的信息发送给服务器端; 服务器端将本机的公钥证书(server
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8编译安装MySQL8.0.19
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Docker安装Oracle12C,快速搭建Oracle学习环境