SpringBoot 轻量级一站式日志可视化与JVM监控

一、项目初衷

Java 应用开发的同学都知道,项目上线后,日志的可视化查询与 JVM 的可视化监控是一件非常重要的事。

市面上成熟方案一般是采用 ELK/EFK 实现日志可视化,采用 Actuator + Prometheus + Grafana 实现 JVM 监控。

这两套都是非常优秀的解决方案,不过对于很多开发者来说,这中间存在大量的繁琐的配置过程。

而对于大多数中小型企业来说,很多都是一个简单的单体项目,并不想要多余的运维和部署成本!

为此我们希望通过一些低门槛的方式,实现日志的自动采集与日志可视化查询。

二、软件介绍

我们的目标:zero-observer + zero-log = actuator + prometheus + granfana + elk / efk

1. 主要功能

  • 应用日志查询:

    提供控制台面板与列表两种方式查询应用日志;

  • 慢接口监控:

    监控 Java 服务中请求超过阈值的接口,支持通用配置与自定义配置;

  • JVM 监控:

    监控 JVM 进程中的各种指标,包括堆内存、非堆内存、Eden、Survivor、OldGen、Metaspace、线程、GC、物理内存、CPU 等。

2. 系统架构

本项目主要分为客户端和服务端两个部分。

3. Java 日志采集客户端【zero-log】

采集客户端旨在提供低门槛、少配置、轻量级、无侵入的方式实现日志、JVM 指标的自动采集与发送。

zero-log 基于 logback 实现将代码中通过 log.error、log.warn、log.info、log.trace 等方式输出的日志与 JVM 运行时的各项指标自动采集并发送到远程服务器上

原系统代码无需任何改动,只需要引入 zero-log 的依赖,同时在 logback-spring.xml 中添加 HttpBatchAppender,配置采集数据接收的接口即可实现自动采集与传输。

4. 服务端【zero-observer】

收集客户端采集插件采集的客户端数据,包括应用日志、JVM 指标等,并提供开箱即用的可视化检索功能,极大降低了 Java 应用日志与 JVM 指标监控可视化检索的门槛。

5. 功能介绍

++仪表盘++
++应用日志++
++控制台日志++
++慢接口日志++
++日志查询++
++日志详情++
++JVM 监控++

三、服务端安装

zero-observer 数据存储使用的是 mysql 与 elasticsearch,mysql 存储的是系统数据,elasticsearch 存储的是日志数据。 所以需要自行安装 mysql 与 elasticsearch。

1. Mysql 初始化脚本

创建数据库:zero_observer

sql 复制代码
CREATE TABLE `app_log_growth_trend` (
  `id` bigint(20) NOT NULL,
  `create_time` datetime NOT NULL,
  `app` varchar(255) NOT NULL,
  `env` varchar(50) NOT NULL,
  `level` varchar(10) NOT NULL,
  `statistic_time` datetime NOT NULL,
  `log_count` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `app_log_total_growth_trend` (
  `id` bigint(20) NOT NULL,
  `create_time` datetime NOT NULL,
  `statistic_time` datetime NOT NULL,
  `log_count` bigint(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `app_log_level_statistic` (
   `id` bigint(20) NOT NULL,
   `create_time` datetime NOT NULL,
   `app` varchar(255) NOT NULL,
   `env` varchar(50) NOT NULL,
   `level` varchar(10) NOT NULL,
   `statistic_time` datetime NOT NULL,
   `log_count` int(11) NOT NULL,
   PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `app_env_instance` (
    `id` bigint(20) NOT NULL,
    `create_time` datetime NOT NULL,
    `app` varchar(255) NOT NULL,
    `env` varchar(50) NOT NULL,
    `ip` varchar(50) NOT NULL,
    `port` varchar(5) NOT NULL,
    `hostname` varchar(255) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `app_log_statistic` (
    `id` bigint(20) NOT NULL,
    `create_time` datetime NOT NULL,
    `app_log_statistic_counter_id` bigint(20) NOT NULL,
    `app` varchar(255) NOT NULL,
    `env` varchar(50) NOT NULL,
    `statistic_time` datetime NOT NULL,
    `log_count` bigint(20) NOT NULL DEFAULT '0',
    `slow_request_count` bigint(20) NOT NULL DEFAULT '0',
    `error_count` bigint(20) NOT NULL DEFAULT '0',
    `warn_count` bigint(20) NOT NULL DEFAULT '0',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `app_log_statistic_counter` (
    `id` bigint(20) NOT NULL,
    `create_time` datetime NOT NULL,
    `statistic_time` datetime NOT NULL,
    `statistic_status` int(11) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `system_config` (
    `id` bigint(20) NOT NULL COMMENT '主键',
    `create_time` datetime NOT NULL COMMENT '创建时间',
    `key_code` varchar(50) NOT NULL COMMENT 'key编码',
    `key_name` varchar(50) DEFAULT NULL COMMENT 'key 名称',
    `key_value` varchar(255) NOT NULL COMMENT 'key值',
    `enabled` int(11) NOT NULL DEFAULT '1' COMMENT '是否启用',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO `system_config` (`id`, `create_time`, `key_code`, `key_name`, `key_value`, `enabled`)
VALUES (1, '2025-07-19 23:56:06', 'app_log_storage_days', '应用日志存储天数', '3', 1);

CREATE TABLE `users` (
    `id` bigint(20) NOT NULL,
    `create_time` datetime NOT NULL,
    `account` varchar(100) NOT NULL,
    `pwd` varchar(255) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `token_info` (
    `id` bigint(20) NOT NULL,
    `create_time` datetime NOT NULL,
    `subject` varchar(100) NOT NULL,
    `token` varchar(255) NOT NULL,
    `expire_time` datetime NOT NULL,
    `expire_timestamp` bigint(20) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `license` (
    `id` bigint(20) NOT NULL,
    `create_time` datetime NOT NULL,
    `content` text NOT NULL,
    `remark` text DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2. Docker 部署

bash 复制代码
# 拉取镜像
docker pull registry.cn-hangzhou.aliyuncs.com/kuafucv/zero-observer:1.1.0
# 启动容器
docker run -itd -p 8080:8080 --name zero-observer \
 -e TZ=Asia/Shanghai
 -e ES_IP=127.0.0.1 \
 -e ES_PORT=9200 \
 -e ES_USERNAME=es \
 -e ES_PASSWORD=es \
 -e MYSQL_IP=127.0.0.1 \
 -e MYSQL_PORT=3306 \
 -e MYSQL_USERNAME=root \
 -e MYSQL_PASSWORD=123456 \
 registry.cn-hangzhou.aliyuncs.com/kuafucv/zero-observer:1.1.0

参数解析:

  • TZ:时区,默认 Asia/Shanghai

  • ES_IP:elasticsearch 的 ip

  • ES_PORT:elasticsearch restapi 的端口

  • ES_USERNAME:elasticsearch 用户名,无则不填即可

  • ES_PASSWORD:elasticsearch 密码,无则不填即可

  • MYSQL_IP:mysql 的ip

  • MYSQL_PORT:mysql 端口

  • MYSQL_USERNAME 用户名

  • MYSQL_PASSWORD 密码

启动成功后,浏览器访问:http://127.0.0.1:8080/zero-observer/
默认用户密码:admin/123456

四、Java 应用日志快速接入

1. 引入 maven 依赖

XML 复制代码
<dependency>
    <groupId>io.github.kuafucv</groupId>
    <artifactId>zero-log-spring-boot-starter</artifactId>
    <version>1.1.0</version>
</dependency>

2. 配置 logback-spring.xml

XML 复制代码
<configuration debug="false">

    <springProperty name="app_name" source="spring.application.name"/>
    <springProperty name="env" source="spring.profiles.active"/>
    <springProperty name="port" source="server.port"/>

    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />

    <property name="FILE_LOG_PATTERN" value="${FILE_LOG_PATTERN:-[%X{TRACE_ID}] %d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%15.15t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    <property name="FILE_LOG_CHARSET" value="${FILE_LOG_CHARSET:-${file.encoding:-UTF-8}}"/>

    <appender name="BATCH_HTTP" class="io.github.kuafucv.zero.log.core.HttpBatchAppender">
        <!-- 必需:参照 logback 的 encoder 配置 -->
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>${FILE_LOG_CHARSET}</charset>
        </encoder>
        <!--请求的地址-->
        <!-- 废弃:日志接收接口URL,该配置已过时,后续版本将删除该配置,建议使用 serverUrl 进行配置 -->
        <endpointUrl>http://ip:port/zero-observer/appLog/report</endpointUrl>
        <!-- 必需:日志接收接口URL -->
        <serverUrl>http://ip:port/zero-observer/</serverUrl>
        <!-- 必填:应用名称 -->
        <appName>${app_name}</appName>
        <!-- 可选:环境,默认值:env -->
        <env>${env}</env>
        <!-- 可选:服务端口,默认值:0 -->
        <appServerPort>${port}</appServerPort>
        <!-- 可选:是否包含MDC上下文 -->
        <includeMDC>true</includeMDC>
        <!-- 可选:日志传输频率,单位秒,默认值:3 秒 -->
        <flushIntervalInSeconds>3</flushIntervalInSeconds>
        <!-- 可选:请求超时时间,默认值:1000 毫秒 -->
        <httpReadTimeoutInMillis>1000</httpReadTimeoutInMillis>
        <!-- 可选:请求超时时间,默认值:1000 毫秒 -->
        <httpConnectionTimeoutInMillis>1000</httpConnectionTimeoutInMillis>
        <!-- 可选:日志存储滚筒最大数量,默认值:8 -->
        <maxNumberOfBuckets>8</maxNumberOfBuckets>
        <!-- 可选:每个日志存储滚筒大小(KB),默认值:1024 -->
        <maxBucketSizeInKilobytes>1024</maxBucketSizeInKilobytes>
    </appender>

    <!-- 日志输出级别 -->
    <root level="INFO">
        <appender-ref ref="BATCH_HTTP" />
    </root>
</configuration>

注意:

  • 这里仅仅展示 springboot 下的 logback-spring-xml 的配置,非 springboot 项目自行配置。

  • 主要是新增 io.github.kuafucv.zero.log.core.HttpBatchAppender 日志 Appender,该 Appender 中的 encoder 建议与原项目中输出到日志文件的 encoder 一致。

  • endpointUrl(过时) 为日志接收的远程日志中心接口,zero-log 会自动调用该接口,并发送日志数据

  • 推荐使用 serverUrl,该属性值为 http://ip:port/zero-observer/

3. 全量配置列表

4. 日志观测

启动 Java 服务,等待日志自动上报至 zero-observer 即可。

五、Java 慢接口监控

1. 快速接入

SpringBoot 工程

springboot 工程引入zero-log-spring-boot-starter包,则自动开启慢接口监控。可以通过配置zero.log.slow-request.enabled=false进行关闭。

XML 复制代码
zero:
  log:
    slow-request:
      # 是否开启慢接口健康
      enabled: true
      # 默认时间阈值(毫秒)
      default-time-threshold: 1000
非 SpringBoot 工程

非 springboot 工程引入的是zero-log-core包,无法使用 starter 的自动装配,所以需要手

java 复制代码
@Bean
public FilterRegistrationBean<SlowRequestFilter> slowRequestFilterFilterRegistrationBean() {
    FilterRegistrationBean<SlowRequestFilter> bean = new FilterRegistrationBean<>();
    bean.setFilter(new SlowRequestFilter());
    bean.setOrder(Integer.MIN_VALUE);
    bean.setUrlPatterns(Collections.singletonList("/*"));
    return bean;
}

其他更古老的项目,如使用 servlet 的 web.xml 配置的方式,请自行配置。

2. 高阶配置

同一系统不同的接口,慢的时间阈值可能存在一定差别,zero-log 支持高阶的自定义配置,实现不同接口的不同阈值配置能力。

java 复制代码
@Component
public class SlowRequestThresholdFactoryImpl implements SlowRequestThresholdFactory {
    @Override
    public List<SlowRequestThreshold> build() {
        List<SlowRequestThreshold> list = new ArrayList<>();
        SlowRequestThreshold s0 = new SlowRequestThreshold();
        s0.setUrlPattern("/**/user/**");
        s0.setTimeThreshold(500);
        list.add(s1);
        SlowRequestThreshold s1 = new SlowRequestThreshold();
        s1.setUrlPattern("/**");
        s1.setTimeThreshold(2000);
        list.add(s1);
        return list;
    }
}

不论是否是 springboot 工程,本质都是通过实现 SlowRequestThresholdFactory 接口的 build 方法,返回 List 集合,实现自定义不同接口的不同阈值能力。

注意事项:

接口匹配是通过 spring 的 AntPathMatcher 实现的,所以 SlowRequestThreshold.urlPattern 的值配置规则参考 spring AntPathMatcher 匹配规则。接口匹配根据放回的 List 从上到下逐个匹配,匹配到了则不再继续匹配,所以 urlPattern 范围更大的配置需要发放在 List 集合的的后面。如果匹配不到,则使用默认阈值。默认阈值与 SlowRequestThreshold 里设置的阈值,如果 小于等于 0,则默认该接口不做慢接口监控,不会进行日志输出。

开源地址:

https://gitee.com/kuafucv/zero-observer

架构设计之道在于在不同的场景采用合适的架构设计,架构设计没有完美,只有合适。

在代码的路上,我们一起砥砺前行。用代码改变世界!

相关推荐
凡梦千华2 小时前
Django时区感知
后端·python·django
Boop_wu2 小时前
[数据结构] 队列 (Queue)
java·jvm·算法
Chan163 小时前
JVM从入门到实战:从字节码组成、类生命周期到双亲委派及打破双亲委派机制
java·jvm·spring boot·后端·intellij-idea
烈风4 小时前
004 Rust控制台打印输出
开发语言·后端·rust
星梦清河4 小时前
宋红康 JVM 笔记 Day17|垃圾回收器
java·jvm·笔记
用户21411832636024 小时前
用 AI 一键搞定!中医药科普短视频制作升级版
后端
yvya_5 小时前
JVM介绍
java·开发语言·jvm
秋难降5 小时前
零基础学习SQL(十一):SQL 索引结构|从 B+Tree 到 Hash,面试常问的 “为啥选 B+Tree” 有答案了
数据库·后端·mysql