使用 Loki、Loki4j、Grafana 和 Spring Boot 搭建一个轻量级、简单、易用的 Java 日志系统

news2024/11/15 17:59:49

要使用 LokiLoki4jGrafanaSpring Boot 搭建一个轻量级、简单、易用的 Java 日志系统,您可以按以下步骤进行。这个系统将利用 Loki 作为日志存储和聚合系统,Loki4j 作为 Java 的日志插件,Grafana 用于日志的可视化。

1.工具介绍:

  1. Loki:类似于 Prometheus 的日志系统,但 Loki 并不索引日志的内容,而是通过标签(label)进行索引,使用 PromQL 进行查询,轻量级且高效。
  2. Loki4j:一个适用于 Java 项目的日志库,可以将 Spring Boot 项目的日志直接推送到 Loki。
  3. Grafana:用于可视化 Loki 中存储的日志,并提供查询、分析和展示日志的功能。
  4. Spring Boot:作为 Java 应用框架,用于生成日志并集成 Loki4j 进行日志传输。

2.系统架构:

  • Spring Boot 应用程序通过 Loki4j 生成和发送日志。
  • Loki 作为日志聚合和存储系统接收日志。
  • Grafana 用于展示和可视化 Loki 中存储的日志。

3. 使用步骤

3.1.步骤 1:搭建 Loki 和 Grafana

可以通过 Docker 容器运行 Loki 和 Grafana,以便简化配置。

3.1.1.准备Loki和Grafanan的镜像

配置docker-compose.yml信息

version: '3.0'
services:
  loki:
    image: loki:latest
    container_name: base_software_loki
    ports:
      - "3100:3100"
    networks:
      - monitoring

  grafana:
    image: grafana:latest
    container_name: base_software_grafana
    ports:
      - "3200:3000"
    networks:
      - monitoring
    depends_on:
      - loki
volumes:
  es_data:
    driver: local

networks:
  monitoring:
    driver: bridge

3.1.2 创建两个容器

docker-compose up -d

启动后,可以通过浏览器访问 http://localhost:3000,默认的用户名和密码均为 admin

3.1.3 配置 Grafana 数据源

  1. 登录 Grafana。
  2. 添加 Loki 作为数据源:
    • 进入 Configuration > Data Sources
    • 点击 Add data source,选择 Loki
    • 在 URL 中填写 http://127.0.0.1:3100
    • 保存并测试连接。

在这里插入图片描述

3.2.步骤 2:配置 Spring Boot 项目

在 Spring Boot 项目中使用 Loki4j 来将日志发送到 Loki。

3.2.1 添加依赖

在 Spring Boot 的 pom.xml 文件中添加 Loki4j 依赖:

<dependency>  
    <groupId>com.github.loki4j</groupId>  
    <artifactId>loki-logback-appender-jdk8</artifactId>  
    <version>1.4.2</version>  
</dependency>

3.2.2 配置 logback-spring.xml

Loki4j 使用 Logback 作为日志框架。你需要配置 src/main/resources/logback-spring.xml 文件来使用 Loki4j 将日志发送到 Loki:

<?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="loki.log-file-dir" defaultValue="log"/>
    <!-- 控制台输出 -->
    <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>
  
    <springProperty name="lokiEnabled" source="loki.enabled" defaultValue="false"/>
    <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="xiaocai"/>
    <springProperty scope="context" name="hostIp" source="loki.label.host-ip" defaultValue="localhost"/>
    <springProperty scope="context" name="orgId" source="loki.org-id" defaultValue="xiaocai"/>

    <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,traceid=%X{traceid:-0},urlPath=%X{urlPath:-0}</pattern>
            </label>
            <message>
                <pattern>
                    {"timestamp": "%d{yyyy-MM-dd HH:mm:ss.SSS}", "level": "%level", "logger": "%logger{36}", "thread": "%thread","traceid": "%X{traceid:-0}", "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}/${jobName}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}/${jobName}error.%d.log</fileNamePattern>
            <!--保留30天日志-->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>

    <root level="info">
        <appender-ref ref="Console" />
        <appender-ref ref="fileInfoLog" />
        <appender-ref ref="fileErrorLog" />
        <if condition='${lokiEnabled} == true'>
            <then>
                <appender-ref ref="ASYNC_LOKI" />
            </then>
        </if>
    </root>
</configuration>

在这个配置中,日志会同时输出到控制台和 Loki。Loki4j 的 Loki4jHttpSender 会将日志推送到 Loki 的 API(在此例中是 http://localhost:3100)。

3.2.3. 配置application.yml设置开关以及配置项目相关信息

# Loki 日志配置  
loki:  
  # Loki 服务的 URL,用于推送日志数据  
  url: http://127.0.0.1:3100/loki/api/v1/push  
  log-file-dir: E:\xiaocaiDemo\log\1  
  enabled: true  
  # 标签配置,用于标识日志来源的额外信息  
  label:  
    # 环境标签,标识当前运行的环境,例如开发环境  
    env: dev  
    # 服务名称标签,标识日志来源的服务名称  
    job-name: xiaocaiDemo  
    # 主机 IP 标签,标识日志来源的主机 IP 地址  
    host-ip: localhost  
  # 组织 ID,用于多租户环境中标识日志所属的组织  
  org-id: Demo

3.2.4. 写一个Filter将所有的请求均生成一个唯一id

import org.apache.logging.log4j.ThreadContext;  
import org.springframework.stereotype.Component;  
  
import javax.servlet.Filter;  
import javax.servlet.FilterChain;  
import javax.servlet.FilterConfig;  
import javax.servlet.ServletException;  
import javax.servlet.ServletRequest;  
import javax.servlet.ServletResponse;  
import javax.servlet.http.HttpServletRequest;  
import java.io.IOException;  
import java.util.UUID;  
  
@Component  
public class LogTraceIdFilter implements Filter {  
  
    public static final String LogTrace_ID = "traceid";  
  
    public static final String UrlPath_Key = "urlPath";  
  
    @Override  
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {  
        try {  
            // 生成唯一的日志ID  
            String logId = UUID.randomUUID().toString().replace("-","");  
            // 将日志ID放入MDC  
            ThreadContext.put(LogTrace_ID, logId);  
  
            ThreadContext.put(UrlPath_Key, ((HttpServletRequest) request).getRequestURI());  
  
            // 打印日志信息  
            HttpServletRequest httpRequest = (HttpServletRequest) request;  
  
            // 继续处理请求  
            chain.doFilter(request, response);  
        } finally {  
            // 清除MDC中的日志ID  
            ThreadContext.clearMap();  
        }  
    }  
  
    @Override  
    public void init(FilterConfig filterConfig) {  
    }  
  
    @Override  
    public void destroy() {  
    }  
}
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.boot.web.servlet.FilterRegistrationBean;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
  
import javax.servlet.Filter;  
  
 */@Configuration  
public class Xiaocai_FilterConfig {  
  
    @Autowired  
    private LogTraceIdFilter logTraceIdFilter;  
  
    @Bean  
    public FilterRegistrationBean<Filter> logTraceIdFilterRegistrationBean() {  
        FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();  
        registrationBean.setFilter(logTraceIdFilter);  
        registrationBean.addUrlPatterns("*");  
        // 设置顺序,数值越小优先级越高  
        registrationBean.setOrder(1);  
        return registrationBean;  
    }  
}

3.2.5. 代码中使用

import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;

private final static Logger logger = LoggerFactory.getLogger(XiaocaiClass.class);

logger.info(String.format("接口请求信息为:%s", strBuilder.toString()));

3.3.步骤 3:启动应用程序并生成日志

现在,您可以运行 Spring Boot 应用程序。应用程序启动后,生成的日志将通过 Loki4j 发送到 Loki。

使用以下命令启动 Spring Boot 应用:

mvn spring-boot:run

Spring Boot 应用程序运行后,日志会自动推送到 Loki,并通过 Grafana 可视化查看。

3.4.步骤 4:在 Grafana 中可视化日志

  1. 登录 Grafana。

  2. 进入 Explore 页面。

  3. 选择数据源为 Loki,然后在查询框中输入查询语句:

    {urlPath="/login/loginToken2LoginUserInfo"}
    

    你可以根据 Loki 的查询语法筛选和查看日志。

在这里插入图片描述

4.总结

  • Loki 提供日志的存储和索引,轻量高效。
  • Loki4j 使得 Java 应用可以轻松将日志推送到 Loki。
  • Grafana 用于查询和展示日志。
  • Spring Boot 作为 Java 应用框架,生成并发送日志。

通过这些步骤,您就搭建了一个轻量级且易于使用的 Java 日志系统。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2161378.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中兴数通产品厉害了,获得CC EAL3+认证!

不知道朋友们最近听说没有&#xff0c;中兴的数通产品是真争气&#xff0c;有25款成功通过了国际信息技术安全评估通用准则CC的EAL 3级别认证。中兴一直是通讯行业的领先企业&#xff0c;这次CC EAL 3级别认证覆盖了多款主流设备型号&#xff0c;证明了它在网络安全领域的实力确…

TraceId在线程池及@Async异步线程中如何传递

何时使用线程池 提起线程池相信大家都不陌生&#xff0c;什么情况下会考虑使用线程池呢&#xff1f;我总结了一下大概是这么几种情况 第一种情况&#xff1a;程序中有任务需要异步执行。这样不可避免的要分配线程去执行&#xff0c;如果这个异步任务执行的频次很低&#xff0…

模拟实现 string 类的一些常用函数

目录 构造函数 析构函数 拷贝构造 赋值重载 迭代器( begin() 和 end() ) 运算符重载流插入( operator << ( ) ) size() capacity() 运算符重载operator[ ] clear() reserve ( ) push_back ( ) append ( ) 运算符重载 operator ( ) insert ( ) erase ( )…

IO相关流

IO流 一、C语言的输入与输出1、介绍2、输入输出缓冲区&#xff08;1&#xff09;介绍&#xff08;2&#xff09;示意图 二、流1、介绍2、主要特点 三、CIO流1、介绍2、示意图 四、iostream1、介绍2、基本概念3、注意 五、类型转换1、operator bool&#xff08;1&#xff09;介绍…

计算机毕业设计非遗项目网站 登录注册搜索 评论留言资讯 前后台管理/springboot/javaWEB/J2EE/MYSQL数据库/vue前后分离小程序

遗项目网站需求&#xff0c;以下是一个基于Spring Boot、Java Web、J2EE技术栈&#xff0c;使用MySQL数据库&#xff0c;并结合Vue实现前后端分离的简要设计方案&#xff1a; 系统功能概述 ‌用户登录与注册‌&#xff1a;实现用户的注册、登录功能&#xff0c;确保用户信息的…

【Python】PyCharm: 强大的 Python 开发环境

⭕️宇宙起点 &#x1f4e2; 引言&#x1f3ac; 什么是 PyCharm&#xff1f;&#x1f528; PyCharm 的核心特性1. 智能代码编辑2. 调试和测试3. 项目和代码结构导航4. 集成 AI 助手5. 远程开发6. 集成数据库7. 科学工具8. 版本控制集成9. Web 开发 &#x1f4e6; 安装 PyCharm&…

【NLP】daydayup 词向量训练模型word2vec

词嵌入算法 word2vec word2vec是一种高效训练词向量的模型&#xff0c;基本出发点是上下文相似的两个词。它们的词向量也应该相似。一般分为CBOW&#xff08;Continuous Bag-of-Words&#xff09;与 Skip-Gram CBOW 词袋模型&#xff0c;使用中心词周围的词来预测中心词&…

《微信小程序实战(4) · 地图导航功能》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

seL4 Mapping(三)

官网链接: Mapping Mapping 这节课程主要是介绍seL4的虚存管理。 虚存 Virtual memory 除了用于操作硬件分页结构的内核原语之外&#xff0c;seL4不提供虚拟内存管理。用户必须为创建中间级分页结构&#xff0c;映射页面以及取消映射页面提供服务。 用户可以随意的定义他们…

Python图表显示添加中文

import re import numpy as np import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties# 动态加载字体文件 font_path /usr/local/sunlogin/res/font/wqy-zenhei.ttc # 替换为实际字体路径 my_font FontProperties(fnamefont_path)# 定义日志…

Go语言基础学习01-Liunx下Go开发环境配置;源码组织方式;go build/install/get详解

目录 Linux环境下配置安装VScode并配置Go语言开发环境Go语言源码的组织方式Go语言源码安装后的结果Go程序构建和安装的过程go build扩展go get 命令详解 之前学习过Go语言&#xff0c;学习的时候没有记录笔记&#xff0c;最近找了个极客时间的Go语言36讲&#xff0c;打算时间学…

影响RPA流程稳定运行的若干因素|实在RPA研究

RPA发展现状 当前&#xff0c;中国正处于实现高质量发展、数字化转型升级的关键时期。RPA作为数字化转型的一项重要工具&#xff0c;已经开始在许多领域发挥积极作用。 RPA&#xff08;Robotic Process Automation 机器人流程自动化&#xff09;是一种通过软件机器人自动执行…

stm32 keil有一些别人的工程在你这打开为什么会乱码?如何解决的

因为别人编辑代码使用的编辑器和你的不一样&#xff0c;要更正可以调一下自己的翻译器编码格式 也可以直接换掉文件的格式&#xff0c; 用记事本打开文件&#xff0c;然后点会另存为&#xff0c;下面有个编码格式选择&#xff0c;换成你自己的就行

Neko一个在Docker环境下的虚拟浏览器

Neko是一个在 Docker 中运行并使用 WebRTC 技术的自托管虚拟浏览器。Neko 是一个强大的工具&#xff0c;可让您在虚拟环境中运行功能齐全的浏览器&#xff0c;使您能够从任何地方安全、私密地访问互联网。使用 Neko&#xff0c;您可以像在常规浏览器上一样浏览 Web、运行应用程…

Python接口测试实践—参数化测试、数据驱动测试和断言的使用

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 在Python接口测试实践中&#xff0c;参数化测试、数据驱动测试和断言是常用的技术手段。 参数化测试 参数化测试是指将测试用例中的某些部分&#xff08;如输入数…

蓝桥杯算法之暴力

暴力 1.十进制数转换成罗马数字 2.判断给出的罗马数字是否正确 小知识 %&#xff08;模除&#xff09;&#xff1a; % 符号用作模除&#xff08;或取模&#xff09;运算符。模除运算是一种数学运算&#xff0c;它返回两个数相除的余数。 具体来说&#xff0c;如果 a 和 b 是…

初识 C++ ( 1 )

引言&#xff1a;大家都说c是c的升级语言。我不懂这句话的含义后来看过解释才懂。 一、面向过程语言和面向对象语言 我们都知道C语言是面向过程语言&#xff0c;而C是面向对象语言&#xff0c;说C和C的区别&#xff0c;也就是在比较面向过程和面向对象的区别。 1.面向过程和面向…

自然语言处理实战项目:从理论到实现

一、引言 自然语言处理&#xff08;NLP&#xff09;是计算机科学、人工智能和语言学交叉的领域&#xff0c;旨在让计算机能够理解、处理和生成人类语言。随着互联网的飞速发展&#xff0c;大量的文本数据被产生&#xff0c;这为自然语言处理技术的发展提供了丰富的素材&#xf…

【动态规划】(五)动态规划——子序列问题

动态规划——子序列问题 子序列问题☆ 最长递增子序列&#xff08;离散&#xff09;最长连续递增序列&#xff08;连续&#xff09;最大子序和&#xff08;连续&#xff09;最长重复子数组&#xff08;连续&#xff09;☆ 最长公共子序列&#xff08;离散-编辑距离过渡&#xf…

【驱动】修改USB转串口设备的属性,如:Serial

1、查看串口信息 在Windows上,设备管理窗口中查看设备号 2、修改串口号工具 例如使用:CH34xSerCfg.exe 使用步骤:恢复默认值 - -> 修改 Serial String(或者Product String等属性)–> 写入配置 3、查看设备节点 在linux上使用lsub查看新增的设备信息,如下这个…