👉 这是一个或许对你有用的社群
🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料:
《项目实战(视频)》:从书中学,往事中“练” 《互联网高频面试题》:面朝简历学习,春暖花开 《架构 x 系统设计》:摧枯拉朽,掌控面试高频场景题 《精进 Java 学习指南》:系统学习,互联网主流技术栈 《必读 Java 源码专栏》:知其然,知其所以然
👉这是一个或许对你有用的开源项目
国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。
功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号等等功能:
Boot 仓库:https://gitee.com/zhijiantianya/ruoyi-vue-pro Cloud 仓库:https://gitee.com/zhijiantianya/yudao-cloud 视频教程:https://doc.iocoder.cn 【国内首批】支持 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 双版本
来源:juejin.cn/post/
7214068367607070781
日志服务是作为软件开发架构的必备服务之一,一直都是我们所关注,所考虑的服务关键点,一个优秀的日志服务可以为项目的维护提供有力的支持,提高了系统的可靠性。
前言
如何设计一个优秀而又可靠的日志服务,是一直以来都是一个很难的课题,写本文的目的起源于大学时期做的一个项目,那时候后台部署到阿里云上,每次遇到Error的问题的时候都需要登陆服务器查看,非常的麻烦,于是我就突发奇想为什么不对Spring的日志进行拦截,展示到前端界面呢?于是就产生了这个项目。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/ruoyi-vue-pro 视频教程:https://doc.iocoder.cn/video/
开工
在开始之前,我们需要捋清楚,我们的需求是什么?通过思考,我们得知,我们的需求是能够实时获取系统输出日志,并展示到我们的前端,用于运维和问题排查。
在明确了目的之后,我们可以开始了开发工作,首先是技术选型,我们所选择的是SpringBoot + Vue来搭配。鉴于写文本的时候Vue3已经发布了,所以才用的是最新的Vue3来进行开发。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/yudao-cloud 视频教程:https://doc.iocoder.cn/video/
代码部分
前端
前端所选用的通信技术为:Vue3 + Websocket,日志通过Websocket实时同步到前端,具体实现如下。
mounted() {
this.res = document.getElementById('terminal-console');
this.websocket = new WebSocket("ws://127.0.0.1:8080/websocket/logger");
this.renderConsole()
this.connect()
},
1.渲染代码架子。
2.建立Websocket链接。
3.渲染控制台
4.建立链接,开始接收日志,这里由于前端篇幅问题,我就直接放核心代码了,后续如果需要进一步的可以前往github进行下载。
connect() {
this.res.appendChild(this.createDivElement('通道连接成功,静默等待....'))
const _this = this;
if (this.websocket != null) {
this.websocket.open = function () {
this.websocket.send("数据发送");
};
this.websocket.onmessage = function (event) {
// 处理日志
const content = JSON.parse(event.data);
let levelHtml = _this.createSpanElement(content.level, '#90ad2b');
const className = _this.createSpanElement(content.className, '#229379')
switch (content.level) {
case 'DEBUG':
levelHtml = _this.createSpanElement(content.level, '#A8C023');
break;
case 'WARN':
levelHtml = _this.createSpanElement(content.level, '#fffa1c');
break;
case 'ERROR':
levelHtml = _this.createSpanElement(content.level, '#e3270e');
break;
}
const logger = _this.createDivElement(content.timestamp)
logger.appendChild(levelHtml)
logger.innerHTML = logger.innerHTML + " --- [" + content.threadName + "] "
logger.appendChild(className)
logger.innerHTML = logger.innerHTML + " :" + content.body
_this.res.appendChild(logger)
}
}
}
后台
后台部分实现思路如下:
1.在logger.xml配置文件中配置日志拦截器,同时实现拦截器类,下方的filter就是我们的实现的过滤器。
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
2.紧接着就是实现过滤的能力,这块就不用说了,就是实现了logger的过滤能力,其思路就是继承logback的Filter,实现过滤方法,在过滤过程中提取关键字,并写入队列。
@Service
public class LogFilter extends Filter {
@Override
public FilterReply decide(ILoggingEvent event) {
String exception = "";
IThrowableProxy iThrowableProxy1 = event.getThrowableProxy();
if (iThrowableProxy1 != null) {
exception = "" + iThrowableProxy1.getClassName() + " " + iThrowableProxy1.getMessage() + "
";
for (int i = 0; i < iThrowableProxy1.getStackTraceElementProxyArray().length; i++) {
exception += "" + iThrowableProxy1.getStackTraceElementProxyArray()[i].toString() + "
";
}
}
// 创建日志消息
LoggerMessage loggerMessage = new LoggerMessage(
event.getMessage()
, DateFormat.getDateTimeInstance().format(new Date(event.getTimeStamp())),
event.getThreadName(),
event.getLoggerName(),
event.getLevel().levelStr,
exception,
""
);
// 写入非阻塞队列
LoggerQueue.getInstance().push(loggerMessage);
return FilterReply.ACCEPT;
}
}
3.写入过后呢,就由websocket类,来实时提取返回给前端,核心代码如下,其实就是建立一个线程池,不停的去获取队列的内容,如果有的话就返回给前端,如果没有就跳过(当然这样实现很挫,最好呢是吧消息丢进消息队列,然后有一个服务端去异步消费个前端)。
public void send() {
ExecutorService executorService= Executors.newFixedThreadPool(2);
Runnable runnable= () -> {
while (true) {
try {
LoggerMessage log = LoggerQueue.getInstance().poll();
if(log!=null){
sendInfo(JSON.toJSONString(log), "logger");
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
executorService.submit(runnable);
}
结束
通过上面一顿操作,我们基本完成了,一个日志服务的搭建过程,不过这里还是有不足的,比如利用消息队列来中转日志消息,这样可以避免开线程给服务器造成无关的压力。
代码仓库
“
https://github.com/bertguan96/zeus
欢迎加入我的知识星球,全面提升技术能力。
👉 加入方式,“长按”或“扫描”下方二维码噢:
星球的内容包括:项目实战、面试招聘、源码解析、学习路线。
文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)