使用docker部署elk,实现日志追踪
·
一、前提条件 环境要求: 操作系统:Linux(CentOS/Ubuntu/Debian 均可,本文以 Ubuntu 为例); Docker 版本:20.10+(确保 Docker 服务正常运行); 内存:至少 2GB(建议分配 1GB 给 Elasticsearch JVM)。 检查 Docker 状态: # 确保 Docker 已启动 sudo systemctl status docker # 若未启动,执行启动命令 sudo systemctl start docker && sudo systemctl enable docker拉取elk镜像 docker pull elasticsearch:8.19.2 docker pull kibana:8.19.2 docker pull logstash:8.19.2
安装elasticsearch:8.19.2
Step 1:创建专用网络(ELK 栈互通)
为 Elasticsearch、Kibana、Logstash 创建独立 Docker 网络,避免端口冲突,确保服务间通信: # 创建名为「elk」的桥接网络(后续 Kibana/Logstash 也加入此网络) docker network create elk # 验证网络创建成功 docker network ls | grep elk
Step 2:规划目录结构(避免文件混乱)
提前创建宿主机目录,用于挂载 Elasticsearch 配置、数据、证书(持久化存储,容器删除后数据不丢失):
# 创建根目录(根据实际需求调整路径,本文以 /opt/elk 为例)
sudo mkdir -p /opt/elk/elasticsearch/{config,certs,data}
# 递归修改目录所有者为 1000:1000(匹配容器内 elasticsearch 用户 UID/GID)
sudo chown -R 1000:1000 /opt/elk/elasticsearch/
# 设置目录权限(所有者可读写,其他只读)
sudo chmod -R 755 /opt/elk/elasticsearch/
# 验证目录结构
tree /opt/elk/elasticsearch/
预期目录结构:
/opt/elk/elasticsearch/
├── certs # 存储 SSL 证书
├── config # 存储配置文件(elasticsearch.yml、密钥库等)
└── data # 存储数据(持久化)
Step 3:生成 SSL 证书(8.x 必需)
Elasticsearch 8.x 强制要求节点间传输加密,需生成「CA 根证书 + 实例证书」,避免之前的「证书缺失 / 密码错误」问题: 3.1 生成自签名 CA(根证书) bash # 启动临时容器,生成 CA 证书(密码:建议自定义,本文用 ElkCert@2025) docker run -it --rm \ -v /opt/elk/elasticsearch/certs:/usr/share/elasticsearch/certs \ elasticsearch:8.19.2 \ /usr/share/elasticsearch/bin/elasticsearch-certutil ca \ --out /usr/share/elasticsearch/certs/elastic-ca.p12 \ # CA 输出路径 --pass "ElkCert@2025" # CA 密码(记牢,后续生成实例证书需用) 3.2 生成实例证书(用于 Elasticsearch 节点) 用上述 CA 根证书签署实例证书,确保证书包含「节点身份信息 + CA 根证书」: bash docker run -it --rm \ -v /opt/elk/elasticsearch/certs:/usr/share/elasticsearch/certs \ elasticsearch:8.19.2 \ /usr/share/elasticsearch/bin/elasticsearch-certutil cert \ --ca /usr/share/elasticsearch/certs/elastic-ca.p12 \ # 引用 CA 根证书 --ca-pass "ElkCert@2025" \ # 与 CA 密码一致 --out /usr/share/elasticsearch/certs/elasticsearch.p12 \ # 实例证书输出路径 --pass "ElkCert@2025" # 实例证书密码(建议与 CA 一致,方便管理) 3.3 修正证书权限 确保容器内 elasticsearch 用户能读取证书(避免「权限拒绝」错误): bash sudo chown -R 1000:1000 /opt/elk/elasticsearch/certs/ sudo chmod 644 /opt/elk/elasticsearch/certs/* # 证书文件仅可读 # 验证证书生成成功 ls -l /opt/elk/elasticsearch/certs/ 预期输出: -rw-r--r-- 1 1000 1000 2688 Oct 6 17:43 elastic-ca.p12 -rw-r--r-- 1 1000 1000 3612 Oct 6 17:44 elasticsearch.p12
Step 4:配置 elasticsearch.yml(核心配置)
创建并编辑 Elasticsearch 主配置文件,避免「配置缺失 / 格式错误」,使用绝对路径减少路径解析问题: 4.1 创建配置文件 bash sudo nano /opt/elk/elasticsearch/config/elasticsearch.yml 4.2 写入完整配置(复制以下内容) yaml # ======================== 基础集群配置 ======================== cluster.name: es-app-cluster # 集群名称(自定义,需与后续 Kibana 一致) node.name: node-01 # 节点名称(单节点场景自定义) network.host: 0.0.0.0 # 监听所有网卡(允许外部访问) discovery.type: single-node # 单节点模式(无需集群发现) bootstrap.memory_lock: true # 锁定 JVM 内存(避免内存交换,提升性能) # ======================== 安全功能配置 ======================== xpack.security.enabled: true # 启用用户名密码认证(必需) # ======================== SSL 传输加密配置(8.x 必需) ======================== xpack.security.transport.ssl.enabled: true # 启用节点间传输加密 xpack.security.transport.ssl.verification_mode: certificate # 仅验证证书有效性(单节点足够) xpack.security.transport.ssl.client_authentication: required # 强制客户端认证 # 证书绝对路径(容器内路径,避免相对路径解析错误) xpack.security.transport.ssl.keystore.path: /usr/share/elasticsearch/config/certs/elasticsearch.p12 xpack.security.transport.ssl.truststore.path: /usr/share/elasticsearch/config/certs/elasticsearch.p12 4.3 修正配置文件权限 sudo chown 1000:1000 /opt/elk/elasticsearch/config/elasticsearch.yml sudo chmod 644 /opt/elk/elasticsearch/config/elasticsearch.yml
Step 5:配置密钥库(存储敏感密码)
Elasticsearch 8.x 废弃明文密码配置,需将证书密码存入「密钥库」(避免配置文件泄露密码):
5.1 自动创建密钥库并添加密码
docker run -it --rm \
-v /opt/elk/elasticsearch/config:/usr/share/elasticsearch/config \
elasticsearch:8.19.2 \
/bin/bash -c "
# 自动确认创建密钥库(echo 'y' 处理交互提示)
echo 'y' | /usr/share/elasticsearch/bin/elasticsearch-keystore create;
# 添加证书密码(与实例证书密码一致:ElkCert@2025)
echo 'ElkCert@2025' | /usr/share/elasticsearch/bin/elasticsearch-keystore add -x xpack.security.transport.ssl.keystore.secure_password;
echo 'ElkCert@2025' | /usr/share/elasticsearch/bin/elasticsearch-keystore add -x xpack.security.transport.ssl.truststore.secure_password;
# 修正密钥库权限(仅 elasticsearch 用户可读写)
chown 1000:1000 /usr/share/elasticsearch/config/elasticsearch.keystore;
chmod 600 /usr/share/elasticsearch/config/elasticsearch.keystore;
"
5.2 验证密钥库
bash
ls -l /opt/elk/elasticsearch/config/elasticsearch.keystore
预期输出:
-rw------- 1 1000 1000 2560 Oct 6 18:00 elasticsearch.keystore
Step 6:复制默认核心配置文件(避免缺失)
Elasticsearch 启动需 jvm.options(JVM 配置)和 log4j2.properties(日志配置),从官方镜像复制默认文件:
6.1 复制 jvm.options
bash
docker run -it --rm --name es-tmp elasticsearch:8.19.2
# 新开一个终端,执行复制命令(不要关闭临时容器)
docker cp es-tmp:/usr/share/elasticsearch/config/jvm.options /opt/elk/elasticsearch/config/
# 关闭临时容器
docker stop es-tmp
6.2 复制 log4j2.properties
bash
docker run -it --rm --name es-tmp elasticsearch:8.19.2
# 新开一个终端,执行复制命令
docker cp es-tmp:/usr/share/elasticsearch/config/log4j2.properties /opt/elk/elasticsearch/config/
# 关闭临时容器
docker stop es-tmp
6.3 修正默认文件权限
bash
sudo chown -R 1000:1000 /opt/elk/elasticsearch/config/
sudo chmod 644 /opt/elk/elasticsearch/config/{jvm.options,log4j2.properties}
# 验证所有配置文件是否齐全
ls -l /opt/elk/elasticsearch/config/
预期配置文件:
plaintext
elasticsearch.keystore elasticsearch.yml jvm.options log4j2.properties
Step 7:启动 Elasticsearch 容器
执行完整启动命令,挂载所有必要目录,避免「挂载路径错误」: bash # 停止并删除旧容器(若存在) docker stop elasticsearch 2>/dev/null && docker rm elasticsearch 2>/dev/null # 启动 Elasticsearch 8.19.2 # 停止并删除旧容器(若存在) docker stop elasticsearch 2>/dev/null && docker rm elasticsearch 2>/dev/null # 启动 Elasticsearch 8.19.2 docker run -d \ --name elasticsearch \ --network elk \ # 加入之前创建的 elk 网络 --publish 9200:9200 \ # HTTP 访问端口(外部访问) --publish 9300:9300 \ # 节点间通信端口 # 环境变量配置 --env cluster.name=es-app-cluster \ --env node.name=node-01 \ --env discovery.type=single-node \ --env network.host=0.0.0.0 \ --env xpack.security.enabled=true \ --env ELASTIC_USERNAME=elastic \ # 超级用户名(固定为 elastic) --env ELASTIC_PASSWORD=165656656 \ # 自定义超级用户密码(记牢) --env ES_JAVA_OPTS="-Xms512m -Xmx1g" \ # JVM 内存配置(建议为物理内存的 50%) --env bootstrap.memory_lock=true \ # 挂载宿主机目录(持久化存储) -v /opt/elk/elasticsearch/config:/usr/share/elasticsearch/config \ -v /opt/elk/elasticsearch/data:/usr/share/elasticsearch/data \ -v /opt/elk/elasticsearch/certs:/usr/share/elasticsearch/config/certs \ # 镜像版本 elasticsearch:8.19.2
Step 8:验证 Elasticsearch 服务
8.1 检查容器状态 bash docker ps | grep elasticsearch 若 STATUS 为 Up,说明容器启动成功;若为 Exited,需查看日志排查错误 测试 HTTP 接口访问 bash # 用超级用户密码访问 Elasticsearch(未启用 HTTP SSL,用 http) curl -u elastic:165656656 http://localhost:9200
安装Kibana:8.19.2
前提条件 已安装 Docker 20.10+ 已部署 Elasticsearch 8.19.2 并正常运行 已创建 elk 网络 内存:至少 1.5GB(建议 2GB)
Step 1:创建 Kibana 目录结构
# 创建 Kibana 配置和数据目录
sudo mkdir -p /opt/elk/kibana/{config,data}
# 递归修改目录所有者为 1000:1000
sudo chown -R 1000:1000 /opt/elk/kibana/
# 设置目录权限
sudo chmod -R 755 /opt/elk/kibana/
# 验证目录结构
tree /opt/elk/kibana/
Step 2:在 Elasticsearch 中创建服务账户令牌
# 进入 Elasticsearch 容器 docker exec -it elasticsearch bash # 创建 Kibana 服务账户令牌 bin/elasticsearch-service-tokens create elastic/kibana kibana-token # 输出示例(记下这个令牌): # AAEAAWVsYXN0aWMva2liYW5hL2tpYmFuYS10b2tlbjpqMU1ZUUM1blFMU3NGOERhQ2xNaXpR # 退出容器 exit
Step 3:创建 Kibana 配置文件
# 创建 Kibana 配置文件 sudo tee /opt/elk/kibana/config/kibana.yml > /dev/null << 'EOF' server.name: kibana server.host: "0.0.0.0" server.port: 5601 elasticsearch.hosts: ["http://elasticsearch:9200"] elasticsearch.serviceAccountToken: "AAEAAWVsYXN0aWMva2liYW5hL2tpYmFuYS10b2tlbjpqMU1ZUUM1blFMU3NGOERhQ2xNaXpR" xpack.security.enabled: true xpack.encryptedSavedObjects.encryptionKey: "afafsdfdsgegergergweqwrerbhgjntyhtyhtyewergergergegerg" monitoring.ui.container.elasticsearch.enabled: true # 内存优化 node.options: "--max-old-space-size=1024" EOF # 修正配置文件权限 sudo chown 1000:1000 /opt/elk/kibana/config/kibana.yml sudo chmod 644 /opt/elk/kibana/config/kibana.yml
Step 4:启动 Kibana 容器
# 停止并删除旧容器(若存在) docker stop kibana 2>/dev/null && docker rm kibana 2>/dev/null # 启动 Kibana 8.19.2 docker run -d \ --name kibana \ --network elk \ --publish 5601:5601 \ --memory 1.5g \ --memory-swap 2g \ --cpus 1.5 \ -v /opt/elk/kibana/config:/usr/share/kibana/config \ -v /opt/elk/kibana/data:/usr/share/kibana/data \ kibana:8.19.2
Step 5:验证 Kibana 服务
5.1 检查容器状态 bash docker ps | grep kibana 5.2 查看启动日志 bash docker logs -f kibana 等待看到成功启动消息(通常需要1-3分钟): text [info][listening] Server running at http://0.0.0.0:5601 [info][server][Kibana][http] http server running at http://0.0.0.0:5601 5.3 检查服务健康状态 bash # 等待 Kibana 完全启动后测试 curl http://localhost:5601/api/status
安装Logstash:8.19.2
1. 准备工作
创建目录结构
bash
# 创建所有必要的目录
sudo mkdir -p /opt/elk/logstash/{config,pipeline,data,logs,certs}
# 设置正确的权限(Logstash 使用 1000:1000 用户)
sudo chown -R 1000:1000 /opt/elk/logstash/
sudo chmod -R 755 /opt/elk/logstash/
# 验证目录结构
tree /opt/elk/logstash/
2. 配置文件创建
创建 Logstash 主配置文件 bash sudo tee /opt/elk/logstash/config/logstash.yml > /dev/null <<EOF api.http.host: "0.0.0.0" api.http.port: 9600 xpack.monitoring.elasticsearch.hosts: [ "http://elasticsearch:9200" ] xpack.monitoring.elasticsearch.username: "elastic" xpack.monitoring.elasticsearch.password: "165656656" xpack.monitoring.enabled: true path.data: /usr/share/logstash/data path.logs: /usr/share/logstash/logs log.level: info config.reload.automatic: true config.reload.interval: 3s EOF
创建管道配置文件
sudo tee /opt/elk/logstash/pipeline/logstash.conf > /dev/null <<EOF
input {
tcp {
port => 5044
codec => json_lines
}
}
filter {
# Token 认证检查
if [token] != "AAEAAWVsYXN0aWMva2liYW5hL2tpYmFuYS10b2tlbjpqMU1ZUUM1blFMU3NGOERhQ2xNaXpR" {
drop { }
}
# 根据服务名称设置索引
if [service] {
mutate {
add_field => {
"index_name" => "logs-%{service}-%{+YYYY}"
}
}
} else {
mutate {
add_field => {
"index_name" => "logs-unknown-%{+YYYY}"
}
}
}
# 解析时间戳
date {
match => [ "timestamp", "ISO8601" ]
target => "@timestamp"
}
# 添加通用字段
mutate {
#add_field => {
# "environment" => "production"
# "processed_by" => "logstash"
# "log_type" => "application"
#}
# 移除敏感字段
remove_field => [ "token", "host" ]
}
# 数据验证
if ![message] or [message] == "" {
drop { }
}
}
output {
# 输出到 Elasticsearch,使用动态索引名称
elasticsearch {
hosts => ["http://elasticsearch:9200"]
index => "%{index_name}"
user => "elastic"
password => "165656656"
ssl_certificate_verification => false
action => "create" # 关键:使用 create 操作而不是 index
}
# 输出到控制台(用于调试)
stdout {
codec => rubydebug
}
}
EOF
3. 运行 Logstash 容器
docker run -d \ --name logstash \ --network elk \ --user 1000:1000 \ --publish 5044:5044 \ --publish 9600:9600 \ --env LS_JAVA_OPTS="-Xms512m -Xmx512m" \ --env XPACK_MONITORING_ENABLED="true" \ --memory 1g \ --memory-swap 1.5g \ --cpus 1 \ -v /opt/elk/logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml \ -v /opt/elk/logstash/pipeline/:/usr/share/logstash/pipeline/ \ -v /opt/elk/logstash/data/:/usr/share/logstash/data/ \ -v /opt/elk/logstash/logs/:/usr/share/logstash/logs/ \ logstash:8.19.2
4. 故障排除
常见问题解决 bash # 如果容器启动失败,检查日志 docker logs -f logstash # 检查端口是否监听 netstat -tlnp | grep 5044 netstat -tlnp | grep 9600 # 进入容器检查 docker exec -it logstash bash ls -la /usr/share/logstash/data/ ls -la /usr/share/logstash/logs/ # 重启服务 docker restart logstash # 完全重新部署 docker stop logstash docker rm logstash # 然后重新运行 docker run 命令
集成到springboot中使用,实现服务日志追踪查看:
注意这里使用的是jdk17版本
1.导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version> <!-- 版本可根据需求调整,建议使用较新稳定版 -->
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Logstash Logback编码器 -->
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>
</dependencies>
2.添加服务配置文件
application.yml
spring:
application:
name: springboot-elk-demo
profiles:
active: dev # 开发环境使用 dev,生产环境改为 prod
app:
logging:
token: "AAEAAWVsYXN0aWMva2liYW5hL2tpYmFuYS10b2tlbjpqMU1ZUUM1blFMU3NGOERhQ2xNaXpR"
logging:
config: classpath:logback-spring.xml
level:
org.elk.demo01: DEBUG
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<springProperty scope="context" name="appName" source="spring.application.name" defaultValue="user-service"/>
<springProperty scope="context" name="appToken" source="app.logging.token" defaultValue="AAEAAWVsYXN0aWMva2liYW5hL2tpYmFuYS10b2tlbjpqMU1ZUUM1blFMU3NGOERhQ2xNaXpR"/>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- Logstash TCP 输出 -->
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>IP:5044</destination>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>UTC</timeZone>
</timestamp>
<logLevel/>
<loggerName/>
<pattern>
<pattern>
{
"service": "${appName}",
"token": "${appToken}",
"traceId": "%mdc{traceId}",
"spanId": "%mdc{spanId}",
"environment": "production"
}
</pattern>
</pattern>
<threadName/>
<message/>
<mdc/>
<stackTrace/>
<context/>
</providers>
</encoder>
<keepAliveDuration>5 minutes</keepAliveDuration>
<connectionStrategy>
<roundRobin>
<connectionTTL>5 minutes</connectionTTL>
</roundRobin>
</connectionStrategy>
</appender>
<!-- 异步 appender 提高性能 -->
<appender name="ASYNC_LOGSTASH" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="LOGSTASH" />
<queueSize>1024</queueSize>
<discardingThreshold>0</discardingThreshold>
<includeCallerData>true</includeCallerData>
</appender>
<!-- Root 日志配置:同时输出到控制台和 Logstash -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_LOGSTASH"/>
</root>
<!-- 服务特定包日志级别 -->
<logger name="org.elk.demo01" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_LOGSTASH"/>
</logger>
</configuration>
3.添加测试类
LogController
package org.elk.demo01.controller;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@RestController
@Slf4j
public class LogController {
@GetMapping("/test")
public String testLog() {
log.info("测试INFO级别日志");
log.debug("测试DEBUG级别日志");
log.error("测试ERROR级别日志");
logWithCustomFields();
return "日志测试完成";
}
// 使用MDC添加自定义字段
public void logWithCustomFields() {
MDC.put("userId", "12345");
MDC.put("requestId", UUID.randomUUID().toString());
log.info("业务操作日志");
MDC.clear();
}
@GetMapping("/error-test")
public String errorTest() {
try {
int result = 10 / 0;
} catch (Exception e) {
log.error("发生数学运算错误", e);
}
return "错误日志测试";
}
}
4.查看测试生成的日志

Spring Boot 3.x 全局链路追踪实现:
方案一:Micrometer Tracing
环境:使用jdk17,springboot版本3.3.11,springcloud:2023.0.5
1. 依赖配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.11-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example</groupId>
<artifactId>elk-demo-02</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>elk-demo-02</name>
<description>elk-demo-02</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2023.0.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2023.0.1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--单元测试的坐标-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>
<!-- Micrometer Tracing Core (无Zipkin) -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
</dependency>
<!-- Brave Tracer -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<!-- Actuator for Observation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 确保包含Feign的Micrometer集成 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
</dependency>
<!-- OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
<!-- 在pom.xml的build/plugins中添加 -->
<build>
<plugins>
<!-- Lombok 编译插件:处理Lombok注解 -->
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.20.0</version> <!-- 版本建议与lombok依赖版本一致 -->
<executions>
<execution>
<phase>compile</phase> <!-- 在编译阶段执行 -->
<goals>
<goal>delombok</goal> <!-- 核心目标:解析Lombok注解并生成代码 -->
</goals>
<configuration>
<!-- 生成的代码输出目录(默认与源文件同目录,可自定义) -->
<sourceDirectory>src/main/java</sourceDirectory>
<outputDirectory>target/generated-sources/delombok</outputDirectory>
<addOutputDirectory>true</addOutputDirectory> <!-- 将生成目录加入编译路径 -->
</configuration>
</execution>
</executions>
</plugin>
<!-- 其他插件(如maven-compiler-plugin) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source> <!-- 对应项目的JDK版本 -->
<target>1.8</target>
<!-- 确保编译时包含Lombok生成的代码目录 -->
<generatedSourcesDirectory>target/generated-sources/delombok</generatedSourcesDirectory>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. 应用配置
application.yml
spring:
application:
name: springboot-elk-demo02
profiles:
active: dev # 开发环境使用 dev,生产环境改为 prod
#使用elk日志,如果不使用elk日志则把 logging:config修改为:logback-spring.xml
app:
logging:
token: "AAEAAWVsYXN0aWMva2liYW5hL2tpYmFuYS10b2tlbjpqMU1ZUUM1blFMU3NGOERhQ2xNaXpR"
#指定日志配置文件
logging:
pattern:
# 在日志中显示Trace ID和Span ID
level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"
level:
io.micrometer.tracing: DEBUG
org.springframework.cloud: INFO
config: classpath:logback-elk-spring.xml
management:
tracing:
sampling:
probability: 1.0 # 采样率,生产环境可以设置为0.1
endpoints:
web:
exposure:
include: health,metrics,info
# 不配置zipkin相关属性
# 自定义Tracing配置
tracing:
enabled: true
log-level: INFO
server:
port: 8082
logback-elk-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 引入Spring Boot的默认日志基础配置 -->
<include resource="org/springframework/boot/logging/logback/base.xml" />
<springProperty scope="context" name="appName" source="spring.application.name" defaultValue="user-service"/>
<springProperty scope="context" name="appToken" source="app.logging.token" defaultValue="AAEAAWVsYXN0aWMva2liYW5hL2tpYmFuYS10b2tlbjpqMU1ZUUM1blFMU3NGOERhQ2xNaXpR"/>
<!-- Logstash TCP 输出 -->
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>ip:5044</destination>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>UTC</timeZone>
</timestamp>
<logLevel/>
<loggerName/>
<pattern>
<pattern>
{
"service": "${appName}",
"token": "${appToken}",
"traceId": "%mdc{traceId}",
"spanId": "%mdc{spanId}",
"environment": "production"
}
</pattern>
</pattern>
<threadName/>
<message/>
<mdc/>
<stackTrace/>
<context/>
</providers>
</encoder>
<keepAliveDuration>5 minutes</keepAliveDuration>
<connectionStrategy>
<roundRobin>
<connectionTTL>5 minutes</connectionTTL>
</roundRobin>
</connectionStrategy>
</appender>
<!-- 异步 appender 提高性能 -->
<appender name="ASYNC_LOGSTASH" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="LOGSTASH" />
<queueSize>1024</queueSize>
<discardingThreshold>0</discardingThreshold>
<includeCallerData>true</includeCallerData>
</appender>
<!-- 从Spring配置中获取值(在application.yml/properties中定义) -->
<springProperty scope="context" name="LOG_PATH" source="logging.path" defaultValue="./logs"/>
<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="myapp"/>
<!-- ************************** -->
<!-- # 1. 定义日志输出格式 -->
<!-- ************************** -->
<!-- 控制台彩色日志格式(通常base.xml已定义,此处可覆盖) -->
<property name="CONSOLE_PATTERN" value="%clr(%d{HH:mm:ss.SSS}){faint} %clr(%-5level) %clr(${APP_NAME}){magenta} %clr([%thread]){blue} %clr(%logger{36}){cyan} %msg%n"/>
<!-- 文件日志格式 -->
<property name="FILE_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<!-- ************************** -->
<!-- # 2. 输出到控制台 -->
<!-- ************************** -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- ************************** -->
<!-- # 3. 输出到文件(按级别和时间滚动) -->
<!-- ************************** -->
<!-- 3.1 INFO级别日志 -->
<appender name="FILE-INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APP_NAME}-info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 归档的日志文件路径和格式:按天和索引分割 -->
<fileNamePattern>${LOG_PATH}/${APP_NAME}-info.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<!-- 单个日志文件最大大小 -->
<maxFileSize>100MB</maxFileSize>
<!-- 日志文件保留天数 -->
<maxHistory>30</maxHistory>
<!-- 所有日志文件总大小上限 -->
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${FILE_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 过滤器:只记录INFO级别日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 3.2 ERROR级别日志 -->
<appender name="FILE-ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APP_NAME}-error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APP_NAME}-error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${FILE_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 过滤器:只记录ERROR级别日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- ************************** -->
<!-- # 4. 指定包或类的日志级别 -->
<!-- ************************** -->
<!-- 设置特定包的日志级别,例如将SQL日志输出为DEBUG -->
<logger name="org.apache.ibatis" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="java.sql.Connection" level="DEBUG" additivity="false"/>
<logger name="java.sql.Statement" level="DEBUG" additivity="false"/>
<logger name="java.sql.PreparedStatement" level="DEBUG" additivity="false"/>
<!-- ************************** -->
<!-- # 5. 根日志记录器配置 -->
<!-- ************************** -->
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE-INFO" />
<appender-ref ref="FILE-ERROR" />
</root>
<!-- 服务特定包日志级别 -->
<logger name="org.example.demo02" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_LOGSTASH"/>
</logger>
</configuration>
3. Tracing配置类
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration @EnableConfigurationProperties(TracingProperties.class) @EnableFeignClients public class TracingConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } // 自定义Brave配置 @Bean public brave.Tracing braveTracing(TracingProperties properties) { return brave.Tracing.newBuilder() .localServiceName(properties.getServiceName()) .sampler(brave.sampler.Sampler.create(properties.getSamplingProbability())) .build(); } }import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; // Tracing配置属性 @ConfigurationProperties(prefix = "tracing") @Data public class TracingProperties { private boolean enabled = true; private String serviceName = "default-service"; private float samplingProbability = 1.0f; private String logLevel = "INFO"; }
4. 业务代码示例
前3个步骤的配置代码需要在两个服务(demo02,demo3)中配置;
demo02业务代码:
AsynService
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class AsynService {
public void test() {
log.info("异步开始执行");
}
}
TestServiceClient
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient(name = "springboot-elk-demo03", path = "/demo03")
public interface TestServiceClient {
@GetMapping("/test1")
List<Long> demoTest1(@RequestParam Long userId);
@PostMapping("/test2")
List<Long> demoTest2();
}
Controller
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RefreshScope
@RestController
@Slf4j
public class Controller {
@Autowired
private TestServiceClient testServiceClient;
@Autowired
private AsynService asynService;
@GetMapping("/test2")
public List<Long> index2() {
log.info("调用demo03服务的demoTest1");
List<Long> list1 = testServiceClient.demoTest1(1L);
asynService.test();
log.info("调用demo03服务的demoTest2");
List<Long> list2 = testServiceClient.demoTest2();
list1.addAll(list2);
return list1;
}
}
demo03业务代码:
Controller
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
@RefreshScope
@RestController
@Slf4j
public class Controller {
@GetMapping("demo03/test1")
List<Long> demoTest1(@RequestParam Long userId){
log.info("userId:{}",userId);
return Arrays.asList(userId);
}
@PostMapping("demo03/test2")
List<Long> demoTest2(){
try {
log.info("test2");
int a = 1/0;
}catch (Exception e){
log.error("test2 error:{}",e);
}
return Arrays.asList(11L,12L,13L);
}
}
5.调用测试接口
输出:[1,11,12,13]
查看es上的日志:

从结果可以看到,demo02和demo03的日志,而且链路id是从demo02生成然后流转到demo03,这样子就可以解决接口调用各个微服务后,可以通过统一的链路id查看同一请求不同服务之间的日志了
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)