⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7214068367607070781 「幻丶城」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

日志服务是作为软件开发架构的必备服务之一,一直都是我们所关注,所考虑的服务关键点,一个优秀的日志服务可以为项目的维护提供有力的支持,提高了系统的可靠性。

前言

如何设计一个优秀而又可靠的日志服务,是一直以来都是一个很难的课题,写本文的目的起源于大学时期做的一个项目,那时候后台部署到阿里云上,每次遇到Error的问题的时候都需要登陆服务器查看,非常的麻烦,于是我就突发奇想为什么不对Spring的日志进行拦截,展示到前端界面呢?于是就产生了这个项目。

开工

在开始之前,我们需要捋清楚,我们的需求是什么?通过思考,我们得知,我们的需求是能够实时获取系统输出日志,并展示到我们的前端,用于运维和问题排查。

在明确了目的之后,我们可以开始了开发工作,首先是技术选型,我们所选择的是SpringBoot + Vue来搭配。鉴于写文本的时候Vue3已经发布了,所以才用的是最新的Vue3来进行开发。

代码部分

前端

前端所选用的通信技术为: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

文章目录
  1. 1. 前言
  2. 2. 开工
  3. 3. 代码部分
    1. 3.0.1. 前端
    2. 3.0.2. 后台
  • 4. 结束
  • 5. 代码仓库