文章目录
前言
日志在任何一个Web应用中都是不可忽视的存在,它已经成为大部分系统的标准组成部分。搭建日志可视化的主要目的是为了更好地理解和管理应用程序产生的大量日志数据。日志数据对于了解系统的运行状态、诊断问题以及优化性能至关重要。ELK日志可视化系统(Elasticsearch + Logstash + Kibana)太重太麻烦,安装的东西很多,本文介绍一款新的 轻量级日志系统
,基本组成部分为:SpringBoot + Loki4j + Loki + Grafana,下面分别介绍Loki4j、Loki、Grafana这几个组件,然后创建一个简单的SpringBoot项目,来演示如何集成使用这几个组件搭建一个轻量级的日志系统。
本文源码仓库地址:Gitee
一、组件介绍
(一)Loki
Loki 是一个开源的日志聚合系统,由 Grafana Labs 开发和维护。Loki 的设计目标是提供一种高效、可扩展的方式来收集、索引和查询结构化日志数据
。Loki 专注于日志数据,与传统的日志管理系统(如 ELK Stack)相比,它采用了不同的方法来处理日志数据。
特点
标签查询
:- Loki 使用类似于 Prometheus 的标签查询模型,允许用户通过标签来过滤和聚合日志数据。
- 这种模型非常适合大规模的日志数据查询和分析。
无索引日志存储
:- Loki 不对原始日志数据进行全文索引,而是将日志数据按流存储,并使用标签来标识每个流。
- 这种方法降低了存储成本,并提高了查询性能。
水平可扩展性
:- Loki 能够水平扩展,通过增加更多的节点来处理更多的日志数据。
- 这使得 Loki 非常适合处理大规模的日志数据集。
高可用性
:- Loki 支持高可用性部署,可以配置多个实例来确保数据的持久性和服务的连续性。
多租户支持
:- Loki 支持多租户部署,不同的组织或团队可以在同一套基础设施上独立管理自己的日志数据。
与 Grafana 集成
:- Loki 与 Grafana 紧密集成,可以直接在 Grafana 中查询和可视化 Loki 中的日志数据。
- 这使得 Loki 成为了 Grafana 生态系统的一个重要组成部分。
低资源消耗
:- 相比于 ELK Stack,Loki 对资源的需求较低,更适合中小规模的团队使用。
架构
Loki Server
:主服务器,负责接收日志数据、存储数据和处理查询。Alloy / Promtail
:一个轻量级的日志代理,用于收集日志文件并将日志数据发送给 Loki。- Alloy VS Promtail:基于 Loki 的日志采集架构对比与选型指南
- 本文使用
Loki4j
代替了Alloy / Promtail
Loki4j
是一个Java 日志库(支持Logback \ Log4j
),它允许 Spring Boot 应用直接将日志推送到 Loki 服务器,无需通过日志代理转发。-
Alloy / Promtail
的定位是日志采集代理,主要用于从日志文件中提取数据(如容器日志、系统日志),而Loki4j
直接在应用层完成日志发送,属于两种不同的数据采集方式。
Grafana
:用于查询和显示 Loki 中的日志数据的界面。Storage
:Loki 使用对象存储(如 S3 或 GCS)来持久化日志数据,以降低存储成本。
适用场景
大规模日志收集
:适用于需要处理大量日志数据的环境。实时日志查询
:需要快速查询和分析日志数据的场景。低成本日志存储
:希望降低日志存储成本的项目。
总结
Loki 是一个现代的日志管理系统,它简化了日志数据的收集、存储和查询过程,特别适合那些需要高性能、可扩展性和成本效益的日志管理解决方案。
(二)Loki4j
Loki4j 是一个基于 Java 的日志框架
,它提供了一种简单的方式来将日志数据发送到 Loki。Loki4j 旨在与 Loki 日志聚合系统无缝集成,使得 Java 应用程序能够轻松地将日志数据发送到 Loki 以进行集中管理和分析。
特点
易于集成
:- Loki4j 提供了一个简单的 API,可以很容易地与现有的 Java 应用程序集成。
- 支持 SLF4J 和 Logback 等流行的 Java 日志框架。
标签支持
:- Loki4j 支持 Loki 的标签查询模型,允许用户在日志消息中添加标签,以便更好地组织和查询日志数据。
异步日志记录
:- Loki4j 支持异步日志记录,可以提高应用程序的性能,因为它不会阻塞应用程序线程。
配置灵活
:- Loki4j 提供了灵活的配置选项,可以根据需要定制日志级别、日志格式等。
错误处理
:- Loki4j 包含了错误处理机制,可以处理网络问题或其他异常情况,确保日志数据能够可靠地发送到 Loki。
(三)Grafana
Grafana 是一款开源的数据可视化和分析平台,主要用于可视化时间和序列数据,如监控指标、日志文件、应用程序跟踪等
。它支持多种数据源,包括Prometheus、InfluxDB、MySQL、PostgreSQL、Elasticsearch、CloudWatch、Graphite等,以及自定义数据源。Grafana被广泛应用于IT基础架构监控、应用性能监控(APM)、物联网(IoT)数据分析、业务指标监控等多个领域。
特点
直观的界面
:Grafana提供了一个用户友好的界面,用于创建和编辑仪表盘,使得数据可视化变得非常简单,无需编程知识。丰富的图表类型
:支持多种图表类型,如折线图、柱状图、饼图、热力图、仪表盘、表格等,适合不同类型的视觉展示需求。动态和交互式面板
:面板可以配置为具有时间选择器、下拉菜单、查询编辑器等交互元素,使用户能更灵活地探索数据。报警功能
:Grafana支持基于规则的报警系统,当数据达到预设的阈值时,可以通过电子邮件、Slack、PagerDuty等多种方式发送通知。注释和事件
:用户可以在图表上添加注释,记录特定时间点发生的重要事件,有助于理解数据波动的原因。模板变量
:通过使用模板变量,可以创建动态仪表盘,允许用户在不修改仪表盘的情况下,快速切换查看不同数据源或视角。插件生态
:Grafana拥有强大的社区支持,提供了大量的数据源插件、面板插件和应用插件,大大扩展了其功能和适用场景。API和脚本支持
:Grafana提供了完善的API接口,允许用户通过脚本和外部应用自动化创建和管理仪表盘、数据源和用户权限。团队协作和权限管理
:支持多用户访问,可以为不同用户或团队分配不同的权限,促进团队间的协作。
适用场景
IT运维监控
:实时监控服务器性能、网络流量、应用程序健康状况等。业务分析
:展现用户行为、销售数据、营销活动效果等业务指标。物联网数据可视化
:分析设备传感器数据,监控物联网系统运行状态。日志分析
:结合Loki等日志管理系统,进行日志数据的实时查询和可视化展示。
Grafana凭借其灵活性和易用性,成为了数据分析和监控领域的首选工具之一,帮助企业更好地理解数据、发现问题和制定决策。
二、组件配置
操作系统:Windows 11 家庭中文版
(一)Loki
下载地址:下载 Loki
官网:Loki 官网
注意是loki
,不要下载logcli
开头的
这里使用的版本为 v2.9.9- 如果下载其他更高的版本,比如:v3.5.1,可能会在启动Loki时报错在配置文件中有不被识别的字段,这是版本兼容性导致的问题,若要升级为更新的版本,请参考版本发行说明自行修改下面的
loki-config.yaml
配置文件
- 如果下载其他更高的版本,比如:v3.5.1,可能会在启动Loki时报错在配置文件中有不被识别的字段,这是版本兼容性导致的问题,若要升级为更新的版本,请参考版本发行说明自行修改下面的
创建一个名为
loki-windows-amd64
的文件夹,将压缩包中的loki-windows-amd64.exe
解压到该文件夹中(不用点击exe文件,点了也会闪退)
创建一个
config
文件夹,进入config
文件夹中,创建loki-config.yaml
配置文件,文件内容如下:server: # Loki 服务监听的 HTTP 端口号 http_listen_port: 3100 schema_config: configs: - from: 2024-07-01 # 使用 BoltDB 作为索引存储 store: boltdb # 使用文件系统作为对象存储 object_store: filesystem # 使用 v11 版本的 schema schema: v11 index: # 索引前缀 prefix: index_ # 索引周期为 24 小时 period: 24h ingester: lifecycler: # 设置本地 IP 地址 address: 127.0.0.1 ring: kvstore: # 使用内存作为 kvstore store: inmemory # 复制因子设置为 1 replication_factor: 1 # 生命周期结束后的休眠时间 final_sleep: 0s # chunk 的空闲期为 5 分钟 chunk_idle_period: 5m # chunk 的保留期为 30 秒 chunk_retain_period: 30s storage_config: boltdb: # BoltDB 的存储路径【自行替换】 directory: C:\log system\loki-windows-amd64\BoltDB filesystem: # 文件系统的存储路径【自行替换】 directory: C:\log system\loki-windows-amd64\fileStore limits_config: # 不强制执行指标名称 enforce_metric_name: false # 拒绝旧样本 reject_old_samples: true # 最大拒绝旧样本的年龄为 168 小时 reject_old_samples_max_age: 168h # 每个用户每秒的采样率限制为 32 MB ingestion_rate_mb: 32 # 每个用户允许的采样突发大小为 64 MB ingestion_burst_size_mb: 64 chunk_store_config: # 最大可查询历史日期为 28 天(672 小时),这个时间必须是 schema_config 中 period 的倍数,否则会报错 max_look_back_period: 672h table_manager: # 启用表的保留期删除功能 retention_deletes_enabled: true # 表的保留期为 28 天(672 小时) retention_period: 672h
注意:
- 第三行“Loki 服务监听的 HTTP 端口号”要记好,下面配置
Grafana
数据源时会用到,如果不使用上面给定的3100端口号,那么在下面配置Grafana
数据源时也需要一并修改 - yaml文件中的
BoltDB
的存储路径和文件系统
的存储路径自行替换为自己的路径
- 第三行“Loki 服务监听的 HTTP 端口号”要记好,下面配置
使用
cmd
进入loki-windows-amd64.exe
所在目录下,执行启动命令# 这个启动命令是以后启动Loki组件的专用命令,指定了配置文件 loki-windows-amd64.exe --config.file=config/loki-config.yaml
执行结果如下
下面的这几个文件夹都是根据配置自动生成的
(二)Grafana
下载地址:Download Grafana,这里使用 11.1.0 版本
解压
grafana-enterprise-11.1.0.windows-amd64.zip
得到grafana-v11.1.0
文件夹进入
bin
目录找到grafana-server.exe
程序双击运行
运行报错请参考:
- Failed to get renderer plugin sources
- 目前还有一个关于
xychart
插件的报错未找到解决方案,报错信息:plugin xychart is already registered
,因其不影响使用,暂不深究,若大家有好的解决方案则可以在博文下留言,我会将解决方案及时更新博客中
程序运行成功后访问:http://localhost:3000,初始账户:
admin/admin
(登录后可以视个人情况选择是否重置密码)汉化教程请参考:Grafana 汉化调试
添加Loki数据源:选择
Data sources
,点击Add data source
,会出现很多数据源,找到Loki
数据源并单击设置监听的数据源地址和HTTP请求头
数据源地址(Loki配置文件里设置的):http://localhost:3100
Header:X-Scope-OrgID
Value:user1
设置
Header
的作用:理解Grafana中X-Scope-OrgID
的作用与配置
保存并测试:点击
save & test
,出现下面红框中的提示即说明数据源连接成功
注意:如果
save&test
报错可能是Loki服务初始化还未完成,验证Loki初始化是否完成访问:http://localhost:3100/ready,如果显示ready
即初始化完成
三、项目搭建
创建SpringBoot项目:Java 17 + SpringBoot 3.0.2
慎选 Java8 + SpringBoot2,可能会出现依赖问题或配置错误导致的Loki4jAppender类无法被正确加载,从而导致项目启动报错
添加POM依赖
<!--Loki 日志收集--> <dependency> <groupId>com.github.loki4j</groupId> <artifactId>loki-logback-appender</artifactId> <version>1.5.1</version> </dependency> <!--Loki 日志发送http请求和响应工具--> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency> <!--提供 @Slf4j 注解的依赖库--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.32</version> <scope>provided</scope> </dependency>
在
resources
目录下创建logback-spring.xml
文件<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- 彩色控制台控制 --> <substitutionProperty name="log.pattern" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) ${PID:-} %clr(---){faint} %clr(%-80.80logger{79}){cyan} %clr(:){faint} %m%n%wEx"/> <substitutionProperty name="log.pattern.no" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) ${PID:-} %clr(---){faint} %clr(%-80.80logger{79}){cyan} %clr(:){faint} %m%n%wEx"/> <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"/> <springProperty scope="context" name="LOG_FILE_DIR" source="logback.log-file-dir" defaultValue="log"/> <!-- 控制台输出 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${log.pattern}</pattern> </encoder> </appender> <springProperty scope="context" name="url" source="loki.url" defaultValue="http://localhost:3100/loki/api/v1/push"/> <springProperty scope="context" name="env" source="loki.label.env" defaultValue="dev"/> <springProperty scope="context" name="jobName" source="loki.label.job-name" defaultValue="my-app"/> <springProperty scope="context" name="hostIp" source="loki.label.host-ip" defaultValue="localhost"/> <springProperty scope="context" name="orgId" source="loki.org-id" defaultValue="default-org"/> <appender name="LOKI" class="com.github.loki4j.logback.Loki4jAppender"> <http class="com.github.loki4j.logback.ApacheHttpSender"> <url>${url}</url> <tenantId>${orgId}</tenantId> </http> <format> <label> <pattern>application=${jobName},env=${env},host=${hostIp},level=%level</pattern> </label> <message> <pattern> {"timestamp": "%d{yyyy-MM-dd HH:mm:ss.SSS}", "level": "%level", "logger": "%logger{36}", "thread": "%thread", "message": "%msg%n"} </pattern> </message> <sortByTime>true</sortByTime> </format> </appender> <!-- 使用异步方式将日志推送至Loki --> <appender name="ASYNC_LOKI" class="ch.qos.logback.classic.AsyncAppender"> <!-- 队列大小设置,根据实际需要调整 --> <queueSize>512</queueSize> <!-- 丢弃策略,当队列满时采取的操作 --> <discardingThreshold>0</discardingThreshold> <neverBlock>true</neverBlock> <!-- 实际的Loki Appender --> <appender-ref ref="LOKI" /> </appender> <appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>DENY</onMatch> <onMismatch>ACCEPT</onMismatch> </filter> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${log.pattern.no}</pattern> </encoder> <!--滚动策略--> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!--路径--> <fileNamePattern>${LOG_FILE_DIR}/info.%d.log</fileNamePattern> <!--保留30天日志--> <maxHistory>30</maxHistory> </rollingPolicy> </appender> <appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level> </filter> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${log.pattern.no}</pattern> </encoder> <!--滚动策略--> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!--路径--> <fileNamePattern>${LOG_FILE_DIR}/error.%d.log</fileNamePattern> <!--保留30天日志--> <maxHistory>30</maxHistory> </rollingPolicy> </appender> <root level="info"> <appender-ref ref="STDOUT" /> <appender-ref ref="fileInfoLog" /> <appender-ref ref="fileErrorLog" /> <appender-ref ref="ASYNC_LOKI" /> </root> </configuration>
配置
application.yml
文件spring: application: name: grafana-project # Loki 日志配置 loki: # Loki 服务的 URL,用于推送日志数据 url: http://localhost:3100/loki/api/v1/push # 标签配置,用于标识日志来源的额外信息 label: # 环境标签,标识当前运行的环境,例如开发环境 env: dev # 服务名称标签,标识日志来源的服务名称 job-name: my-service # 主机 IP 标签,标识日志来源的主机 IP 地址 host-ip: localhost # 组织 ID,用于多租户环境中标识日志所属的组织 org-id: user1
启动类添加日志注解和日志打印代码
import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @Slf4j @SpringBootApplication public class GrafanaProjectApplication { public static void main(String[] args) { SpringApplication.run(GrafanaProjectApplication.class, args); log.info("GrafanaProjectApplication started"); System.out.println("GrafanaProjectApplication started"); } }
启动SpringBoot项目,然后去 Grafana 查询日志信息:选择
Explore
,在Label filters
中下拉选择level=INFO
,然后点击右上角的Run query
,就能看到查询到的日志数据了
注意:
- 只有log方式记录的日志才会发送到Grafana,控制台输出的不会发送
- 刚启动组件时,后台需要一定时间加载,这时候是无法记录日志的
参考文章
springboot+Loki+Loki4j+Grafana搭建轻量级日志系统
【监控仪表系统】Grafana 中文入门教程 | 构建你的第一个仪表盘