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效果图
