监控平台搭建-日志-springboot直接推送loki篇(九)

loki可以通过上文讲解的alloy监控文件推送,也可以通过Promtail推送日志,此外对于微服务spring boot项目,可以直接推送到loki中

先看下项目结构

maven配置

XML 复制代码
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>3.2.9</version>
        </dependency>
        <!--springboot web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>3.2.9</version>
        </dependency>

        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>3.5.14</version>
        </dependency>

        <!-- MySQL 驱动(必须) -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.4.0</version>
            <scope>runtime</scope>
        </dependency>

        <!-- Lombok(可选) -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.36</version>
            <optional>true</optional>
        </dependency>

        <!-- Druid 数据源 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-3-starter</artifactId>
            <version>1.2.27</version>
        </dependency>

        <!-- 添加 P6Spy 依赖到 pom.xml -->
        <dependency>
            <groupId>p6spy</groupId>
            <artifactId>p6spy</artifactId>
            <version>3.9.1</version>
        </dependency>

        <!--            loki日志推送-->
        <dependency>
            <groupId>com.github.loki4j</groupId>
            <artifactId>loki-logback-appender</artifactId>
            <version>1.6.0</version>
        </dependency>

        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-core</artifactId>
            <version>1.13.8</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>
    </dependencies>

application.yml

XML 复制代码
server:
    port: 20001
spring:
    application:
        name: test
    profiles:
        active: dev
    main:
        allow-bean-definition-overriding: true
    # 数据库
    datasource:
        url: jdbc:p6spy:mysql://mysqlurl
        username: xxx
        password: xxx
#        driver-class-name: com.mysql.cj.jdbc.Driver
        driver-class-name: com.p6spy.engine.spy.P6SpyDriver
#        type: com.alibaba.druid.pool.DruidDataSource
        type: com.zaxxer.hikari.HikariDataSource
        # 指定使用Druid数据源
        hikari:
            pool-name: PaymentHikariPool
            minimum-idle: 10      # 【死亡陷阱】≠maximum-pool-size
            maximum-pool-size: 100 # CPU核数*2 + 磁盘数(SSD可更高)
            idle-timeout: 60000   # 空闲连接超时(ms)
            max-lifetime: 1800000 # 连接最大生命周期(30分钟)
            connection-timeout: 3000 # 获取连接超时(ms)
            leak-detection-threshold: 5000 # 连接泄露检测阈值(ms)
            initialization-fail-timeout: 1 # 启动失败超时(秒)
            keepalive-time: 30000  # 【关键】TCP保活间隔
#        druid:
#            db-type: mysql
#            # 下面为连接池的补充设置,应用到上面所有数据源中
#            # 初始化大小,最小,最大
#            initial-size: 5
#            # 最小连接池数量
#            min-idle: 5
#            # 最大连接池数量
#            max-active: 50
#            # 配置获取连接等待超时的时间
#            max-wait: 60000
#            #申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
#            test-while-idle: true
#            #既作为检测的间隔时间又作为testWhileIdel执行的依据
#            time-between-eviction-runs-millis: 60000
#            #销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接
#            min-evictable-idle-time-millis: 30000
#            #用来检测连接是否有效的sql 必须是一个查询语句
#            #mssql中为 select 1
#            #mysql中为 select 'x'
#            #oracle中为 select 1 from dual
#            validation-query: select 'x'
#            #申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
#            test-on-borrow: false
#            #归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
#            test-on-return: false
#            #当数据库抛出不可恢复的异常时,抛弃该连接
#            #exception-sorter: true
#            #是否缓存preparedStatement,mysql5.5+建议开启
#            pool-prepared-statements: true
#            #当值大于0时poolPreparedStatements会自动修改为true
#            max-pool-prepared-statement-per-connection-size: 20
#            #配置扩展插件
#            #配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
#            filters: stat,wall,slf4j
#            #通过connectProperties属性来打开mergeSql功能;慢SQL记录
#            connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
#            #合并多个DruidDataSource的监控数据
#            use-global-data-source-stat: true
#            #设置访问druid监控页的账号和密码,默认没有。(通过http://host:port/druid/index.html进入druid监控界面)
#            stat-view-servlet:
#                login-username: admin
#                login-password: admin
#                enabled: true
#            #开启web filter
#            web-stat-filter:
#                enabled: true
# 日志配置
logging:
    config: classpath:logback-spring.xml
    level:
        jdbc.sqlonly: INFO
        # MyBatis 相关包
        org.apache.ibatis: INFO
        org.apache.ibatis.session: INFO
        org.apache.ibatis.executor: INFO
        org.apache.ibatis.executor.statement: INFO
        org.apache.ibatis.executor.resultset: DEBUG
        org.apache.ibatis.transaction: INFO
        org.apache.ibatis.jdbc: INFO

        # 其他可能的 logger(根据实际输出调整)
        java.sql: INFO
        java.sql.Connection: INFO
        java.sql.Statement: INFO
        java.sql.PreparedStatement: INFO
        java.sql.ResultSet: DEBUG

        # Spring JDBC
        org.springframework.jdbc: INFO
        org.springframework.jdbc.core: INFO
        org.springframework.jdbc.core.JdbcTemplate: INFO

        # 根 logger 设置为 DEBUG 捕获所有
        root: DEBUG

        # 业务包
        com.xxx: DEBUG
# Loki 日志推送配置
loki:
    # 是否启用 Loki 推送
    enabled: true
    # HTTP 配置
    http:
        url: http://lokiUrl或ip:端口/loki/api/v1/push  # 环境变量优先
        connect-timeout: 5000    # 连接超时(ms)
        request-timeout: 10000   # 请求超时(ms)
        max-connections: 10      # 最大连接数
        keep-alive: true         # 保持连接

    # 异步批处理配置
    batch:
        enabled: true            # 启用批处理
        timeout: 1000            # 批处理超时(ms),每1秒发送一批
        size: 100000             # 批处理大小(bytes),100KB发送一批
        max-requests: 5          # 并行发送请求数

    # 内存队列配置
    queue:
        capacity: 10000          # 内存队列容量(条数)
        drain-on-stop: true      # 停止时清空队列

    # 重试机制
    retry:
        max-attempts: 3          # 最大重试次数
        min-backoff: 1000        # 最小重试间隔(ms)
        max-backoff: 10000       # 最大重试间隔(ms)

    # 标签配置
    labels:
        app: ${spring.application.name}  # 应用名称
        job: ${spring.application.name}  # 任务名称
        env: ${spring.profiles.active}   # 环境
        host: ${HOSTNAME:localhost}      # 主机名
        region: ${REGION:cn-east-1}      # 区域
        cluster: ${CLUSTER:default}      # 集群
        version: ${APP_VERSION:2.0.0}    # 版本

    # 指标监控(可选)
    metrics:
        enabled: true
        prefix: loki.appender
        export:
            prometheus:
                enabled: true
# MyBatis Plus 配置
mybatis-plus:
    configuration:
        # 使用 Slf4j 日志实现
#        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        # 开启下划线转驼峰
        map-underscore-to-camel-case: true
        # 配置日志前缀
        log-prefix: ""

重写p6logger

java 复制代码
import com.p6spy.engine.logging.Category;
import com.p6spy.engine.spy.appender.P6Logger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CustomLokiLogger implements P6Logger {
    private static final Logger LOKI_LOGGER = LoggerFactory.getLogger("SQL_LOKI");

    @Override
    public void logSQL(int i, String s, long l, Category category, String s1, String s2, String s3) {
        // 格式化 SQL 日志
        String logMessage = String.format(
                "SQL执行日志 | 连接ID: %d | 时间: %s | 耗时: %dms | 类型: %s%nSQL: %s%n",
                i, s, l, category, s2
        );

        // 发送到 Loki
        LOKI_LOGGER.info(logMessage);
    }

    @Override
    public void logException(Exception e) {
        LOKI_LOGGER.error("SQL执行异常", e);
        e.printStackTrace();
    }

    @Override
    public void logText(String text) {
        LOKI_LOGGER.info(text);
        System.out.println(text);
    }

    @Override
    public boolean isCategoryEnabled(Category category) {
        if (Category.ERROR.equals(category)) {
            return this.LOKI_LOGGER.isErrorEnabled();
        } else if (Category.WARN.equals(category)) {
            return this.LOKI_LOGGER.isWarnEnabled();
        } else {
            return Category.DEBUG.equals(category) ? this.LOKI_LOGGER.isDebugEnabled() : this.LOKI_LOGGER.isInfoEnabled();
        }
    }
}
复制代码
mybatisplus的p6s配置文件   spy.properties
XML 复制代码
modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 输出格式
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#自定义输出日志  控制台还是其他,下方为自定义重写的输出
#appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
appender=com.xxx.xxx.config.CustomLokiLogger
#输出配置
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 是否在关闭连接池时注销JDBC驱动  true:应用关闭时注销所有已注册的JDBC驱动 false:不注销  作用:防止应用重启时出现"Driver already registered"警告
deregisterdrivers=true
#是否在SQL日志前添加前缀 true:添加jdbc.sqlonly、jdbc.sqltiming等前缀 false:不添加前缀 作用:便于在日志中区分不同类型的SQL日志
useprefix=true
# 排除不需要的日志类别 可用的类别有:error:SQL错误info:连接信息、事务信息debug:调试信息batch:批处理操作 statement:SQL语句  commit:提交事务 rollback:回滚事务 result:SQL执行结果 
resultset:结果集详细信息
excludecategories=info,debug,result,commit,resultset
#日志时间格式 指定日志中时间戳的显示格式 使用Java的SimpleDateFormat格式 示例输出:2026-01-08 14:04:08
dateformat=yyyy-MM-dd HH:mm:ss
# 指定要监控的JDBC驱动列表  可以列出多个驱动,用逗号分隔 示例:driverlist=com.mysql.cj.jdbc.Driver,org.postgresql.Driver 作用:只监控指定的驱动,减少性能影响
#driverlist=org.h2.Driver
#是否启用连接中断检测 true:启用 false:禁用 作用:检测数据库连接是否中断,并记录相关日志 outagedetection=true
#连接中断检测间隔(单位:秒) 每隔多少秒检测一次连接是否中断 配合outagedetection=true使用 默认值通常为60秒,你设置为2秒更敏感
outagedetectioninterval=2
复制代码
对输出日志的配置logback-spring.xml
XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
    <!-- ==================== Spring 属性配置 ==================== -->
    <springProperty scope="context" name="APP_NAME" source="spring.application.name"/>
    <springProperty scope="context" name="APP_ENV" source="spring.profiles.active"/>
    <springProperty scope="context" name="APP_VERSION" source="app.version"/>

    <!-- Loki 配置 -->
    <springProperty scope="context" name="LOKI_ENABLED" source="loki.enabled" defaultValue="false"/>
    <springProperty scope="context" name="LOKI_URL" source="loki.http.url"/>
    <springProperty scope="context" name="LOKI_CONNECT_TIMEOUT" source="loki.http.connect-timeout" defaultValue="5000"/>
    <springProperty scope="context" name="LOKI_REQUEST_TIMEOUT" source="loki.http.request-timeout" defaultValue="10000"/>
    <springProperty scope="context" name="LOKI_MAX_CONNECTIONS" source="loki.http.max-connections" defaultValue="10"/>

    <!-- 批处理配置 -->
    <springProperty scope="context" name="LOKI_BATCH_ENABLED" source="loki.batch.enabled" defaultValue="true"/>
    <springProperty scope="context" name="LOKI_BATCH_TIMEOUT" source="loki.batch.timeout" defaultValue="1000"/>
    <springProperty scope="context" name="LOKI_BATCH_SIZE" source="loki.batch.size" defaultValue="100000"/>
    <springProperty scope="context" name="LOKI_BATCH_MAX_REQUESTS" source="loki.batch.max-requests" defaultValue="5"/>

    <!-- 队列配置 -->
    <springProperty scope="context" name="LOKI_QUEUE_CAPACITY" source="loki.queue.capacity" defaultValue="10000"/>
    <springProperty scope="context" name="LOKI_DRAIN_ON_STOP" source="loki.queue.drain-on-stop" defaultValue="true"/>

    <!-- 重试配置 -->
    <springProperty scope="context" name="LOKI_RETRY_MAX_ATTEMPTS" source="loki.retry.max-attempts" defaultValue="3"/>
    <springProperty scope="context" name="LOKI_RETRY_MIN_BACKOFF" source="loki.retry.min-backoff" defaultValue="1000"/>
    <springProperty scope="context" name="LOKI_RETRY_MAX_BACKOFF" source="loki.retry.max-backoff" defaultValue="10000"/>

    <!-- 标签配置 -->
    <springProperty scope="context" name="LOKI_LABEL_APP" source="loki.labels.app" defaultValue="cloudAssess"/>
    <springProperty scope="context" name="LOKI_LABEL_JOB" source="loki.labels.job" defaultValue="cloudAssess"/>
    <springProperty scope="context" name="LOKI_LABEL_ENV" source="loki.labels.env" defaultValue="${APP_ENV}"/>
    <springProperty scope="context" name="LOKI_LABEL_HOST" source="loki.labels.host" defaultValue="${HOSTNAME:-localhost}"/>
    <springProperty scope="context" name="LOKI_LABEL_REGION" source="loki.labels.region" defaultValue="cn-east-1"/>
    <springProperty scope="context" name="LOKI_LABEL_CLUSTER" source="loki.labels.cluster" defaultValue="default"/>
    <springProperty scope="context" name="LOKI_LABEL_VERSION" source="loki.labels.version" defaultValue="${APP_VERSION}"/>

    <!-- ==================== 控制台输出配置 ==================== -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- 彩色日志输出 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{36}) - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
    </appender>

    <!-- ==================== 文件输出配置 ==================== -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>../logs/${APP_NAME}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 按天滚动,保留30天 -->
            <fileNamePattern>../logs/${APP_NAME}_%d{yyyyMMdd}.%i.log</fileNamePattern>
            <maxHistory>90</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- ==================== Loki 异步输出配置 ==================== -->
    <appender name="LOKI_ASYNC" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 异步队列配置 -->
        <queueSize>${LOKI_QUEUE_CAPACITY}</queueSize>
        <discardingThreshold>0</discardingThreshold> <!-- 队列满时丢弃低级别日志 -->
        <includeCallerData>false</includeCallerData> <!-- 不包含调用者数据,性能更好 -->
        <neverBlock>true</neverBlock> <!-- 队列满时不阻塞 -->
        <maxFlushTime>10000</maxFlushTime> <!-- 最大刷新时间 -->

        <!-- 关联的同步 Loki appender -->
        <appender-ref ref="LOKI_SYNC"/>
    </appender>

    <appender name="LOKI_SYNC" class="com.github.loki4j.logback.Loki4jAppender">
        <!-- HTTP 连接配置 -->
        <http>
            <url>${LOKI_URL}</url>
            <connectionTimeout>${LOKI_CONNECT_TIMEOUT}</connectionTimeout>
            <requestTimeout>${LOKI_REQUEST_TIMEOUT}</requestTimeout>
            <tenantId></tenantId> <!-- 多租户支持 -->
        </http>

        <!-- 编码格式配置 -->
        <format>
            <label>
                <!-- 标签模板,支持动态变量 -->
                <pattern>app=${LOKI_LABEL_APP},job=${LOKI_LABEL_JOB},host=${LOKI_LABEL_HOST},env=${LOKI_LABEL_ENV},region=${LOKI_LABEL_REGION},cluster=${LOKI_LABEL_CLUSTER},version=${LOKI_LABEL_VERSION}</pattern>
                <readMarkers>true</readMarkers> <!-- 读取日志标记 -->
            </label>
            <message>
                <!-- 日志消息格式 -->
                <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </message>
            <sortByTime>true</sortByTime> <!-- 按时间排序 -->
        </format>

        <!-- 异步批处理配置 -->
        <batchTimeoutMs>${LOKI_BATCH_TIMEOUT}</batchTimeoutMs>
        <batchSize>${LOKI_BATCH_SIZE}</batchSize>
        <maxRetries>${LOKI_RETRY_MAX_ATTEMPTS}</maxRetries>
        <retryTimeoutMs>${LOKI_RETRY_MIN_BACKOFF}</retryTimeoutMs>

        <!-- 高级性能配置 -->
        <drainOnStop>${LOKI_DRAIN_ON_STOP}</drainOnStop>
        <verbose>false</verbose> <!-- 关闭详细日志 -->

        <!-- 内存队列配置 -->
        <sendQueueCapacity>${LOKI_QUEUE_CAPACITY}</sendQueueCapacity>

        <!-- 压缩配置 -->
        <compress>true</compress>
        <compressLevel>1</compressLevel> <!-- 1-9,1最快,9压缩率最高 -->

        <!-- 指标统计(可选) -->
        <metricsEnabled>true</metricsEnabled>
    </appender>
    <!-- ==================== 日志级别和环境输出配置 ==================== -->

    <!-- 开发环境配置 -->
    <springProfile name="dev">
        <root level="DEBUG">
            <appender-ref ref="CONSOLE"/>
            <!-- 开发环境可关闭 Loki -->
            <!--            <if condition='property("LOKI_ENABLED").equals("true")'>-->
            <!--                <then>-->
            <appender-ref ref="LOKI_ASYNC"/>
            <!--                </then>-->
            <!--            </if>-->
        </root>

        <logger name="org.springframework" level="WARN"/>
        <logger name="org.hibernate" level="ERROR"/>
        <logger name="com.zaxxer.hikari" level="WARN"/>
        <logger name="io.netty" level="WARN"/>
    </springProfile>

    <!-- 测试环境配置 -->
    <springProfile name="test">
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="FILE"/>
            <if condition='property("LOKI_ENABLED").equals("true")'>
                <then>
                    <appender-ref ref="LOKI_ASYNC"/>
                </then>
            </if>
        </root>
        <logger name="org.springframework" level="WARN"/>
        <logger name="org.hibernate" level="ERROR"/>
        <logger name="com.zaxxer.hikari" level="WARN"/>
        <logger name="io.netty" level="WARN"/>
    </springProfile>

    <!-- 生产环境配置 -->
    <springProfile name="prod">
        <root level="INFO">
            <!-- 生产环境关闭控制台输出,减少性能影响 -->
             <appender-ref ref="CONSOLE"/>
            <appender-ref ref="FILE"/>
            <if condition='property("LOKI_ENABLED").equals("true")'>
                <then>
                    <appender-ref ref="LOKI_ASYNC"/>
                </then>
            </if>
        </root>

        <!-- 生产环境特定包级别 -->
        <logger name="org.springframework" level="WARN"/>
        <logger name="org.hibernate" level="ERROR"/>
        <logger name="com.zaxxer.hikari" level="WARN"/>
        <logger name="io.netty" level="WARN"/>

        <!-- 业务关键日志 -->
        <!--        <logger name="com.ybowu.service.PaymentService" level="INFO" >-->
        <!--            <appender-ref ref="FILE"/>-->
        <!--            <if condition='property("LOKI_ENABLED").equals("true")'>-->
        <!--                <then>-->
        <!--                    <appender-ref ref="LOKI_ASYNC"/>-->
        <!--                </then>-->
        <!--            </if>-->
        <!--        </logger>-->
    </springProfile>

    <!-- ==================== 性能监控配置 ==================== -->
    <!-- 可选:监控日志系统性能 -->
    <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>

    <!-- JMS 日志配置(可选) -->
    <!-- <appender name="JMS" class="org.springframework.integration.jms.JmsDestinationBackingMessageHandler"> -->
    <!-- </appender> -->

</configuration>

loki效果图

相关推荐
忧郁的Mr.Li17 小时前
SpringBoot中实现多数据源配置
java·spring boot·后端
yq19820430115617 小时前
静思书屋:基于Java Web技术栈构建高性能图书信息平台实践
java·开发语言·前端
一个public的class17 小时前
你在浏览器输入一个网址,到底发生了什么?
java·开发语言·javascript
有位神秘人17 小时前
kotlin与Java中的单例模式总结
java·单例模式·kotlin
golang学习记17 小时前
IntelliJ IDEA 2025.3 重磅发布:K2 模式全面接管 Kotlin —— 告别 K1,性能飙升 40%!
java·kotlin·intellij-idea
爬山算法17 小时前
Hibernate(89)如何在压力测试中使用Hibernate?
java·压力测试·hibernate
暮色妖娆丶18 小时前
SpringBoot 启动流程源码分析 ~ 它其实不复杂
spring boot·后端·spring
消失的旧时光-194318 小时前
第十四课:Redis 在后端到底扮演什么角色?——缓存模型全景图
java·redis·缓存
BD_Marathon18 小时前
设计模式——依赖倒转原则
java·开发语言·设计模式
BD_Marathon18 小时前
设计模式——里氏替换原则
java·设计模式·里氏替换原则