MonkeyCode搞定ELK日志系统:从零搭建生产级日志平台
程序员的日常:看日志→猜问题→改代码→看日志。有了ELK,整个过程从"猜"变成"查"。
ELK是什么?
ELK Stack
├── Elasticsearch → 存储 + 搜索(核心)
├── Logstash → 收集 + 过滤 + 转发(处理器)
├── Kibana → 查询 + 可视化(界面)
└── Beats → 日志采集Agent(搬运工)
新版本叫 ELK Stack = Elasticsearch + Logstash + Kibana + Beats
整体架构
你的应用(Python/Go/Java)
↓ 输出JSON日志(stdout或文件)
Filebeat(轻量Agent,部署在每台服务器)
↓ 采集日志,转发
Logstash(过滤、解析、丰富)
↓ 写入
Elasticsearch(存储、索引、搜索)
↓ 查询展示
Kibana(可视化、告警、Dashboard)
第一步:Docker Compose一键启动
让MonkeyCode生成:
# docker-compose.elk.yml
version: '3.8'
services:
elasticsearch:
image: elasticsearch:8.12.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms2g -Xmx2g"
ports: ["9200:9200", "9300:9300"]
volumes: ["es_data:/usr/share/elasticsearch/data"]
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:9200 || exit 1"]
interval: 30s
timeout: 10s
retries: 5
logstash:
image: logstash:8.12.0
ports: ["5044:5044", "5000:5000/udp"]
volumes:
- ./logstash/pipeline:/usr/share/logstash/pipeline:ro
- ./logstash/config:/usr/share/logstash/config:ro
depends_on: [elasticsearch]
kibana:
image: kibana:8.12.0
ports: ["5601:5601"]
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
depends_on: [elasticsearch]
filebeat:
image: elastic/filebeat:8.12.0
user: root
volumes:
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
- ./logs:/var/log/app:ro
depends_on: [logstash]
network_mode: "host"
volumes:
es_data:
启动:
docker-compose -f docker-compose.elk.yml up -d
访问Kibana:http://localhost:5601
第二步:应用输出结构化日志
关键原则:永远输出JSON格式,不要输出文本日志。
# app/logging_config.py
import structlog
import json
import sys
def setup_logging():
structlog.configure(
processors=[
# 添加时间戳
structlog.processors.TimeStamper(fmt="iso"),
# 添加日志级别
structlog.processors.add_log_level,
# 添加进程/线程信息
structlog.processors.add_logger_name,
# 异常信息格式化
structlog.processors.format_exc_info,
# 输出为JSON
structlog.dev.JSONRenderer()
],
logger_factory=structlog.PrintLoggerFactory(file=sys.stdout),
wrapper_class=structlog.stdlib.BoundLogger,
)
logger = structlog.get_logger()
# 使用
async def create_order(req: CreateOrderRequest, user_id: int):
log = logger.bind(user_id=user_id, endpoint="/orders")
log.info("order.create.attempt", amount=req.amount, items=len(req.items))
try:
order = await db.create_order(req, user_id)
log.info("order.create.success", order_id=order.id, amount=order.total_amount)
return order
except Exception as e:
log.error("order.create.failed", error=str(e), exc_info=True)
raise
输出到stdout的JSON日志:
{"event": "order.create.success", "level": "info", "timestamp": "2024-06-01T10:30:00Z", "user_id": 42, "order_id": 10086, "amount": 299.0}
第三步:Filebeat采集日志
# filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/app/*.log
- /var/log/app/*.json
json.keys_under_root: true # 解析JSON,字段提到顶层
json.overwrite_keys: true
tags: ["app", "production"]
fields:
env: production
app: my-service
fields_under_root: true
- type: container # Docker容器日志
enabled: true
paths:
- /var/lib/docker/containers/*/*.log
json.keys_under_root: true
processors:
- add_docker_metadata: ~
- add_host_metadata: ~
- dissect:
tokenizer: "%{timestamp} %{level} %{message}"
field: "message"
target_prefix: ""
# 输出到Logstash(推荐)或直连Elasticsearch
output.logstash:
hosts: ["logstash:5044"]
# 或者直接输出到Elasticsearch(跳过Logstash,简单场景)
# output.elasticsearch:
# hosts: ["elasticsearch:9200"]
# index: "my-app-%{+yyyy.MM.dd}"
第四步:Logstash过滤与解析
# logstash/pipeline/logstash.conf
input {
beats {
port => 5044
}
}
filter {
# 解析时间戳
if [timestamp] {
date {
match => [ "timestamp", "ISO8601" ]
}
}
# 解析JSON字段(如果Filebeat没解析)
if [message] =~ /^\{.*\}$/ {
json {
source => "message"
skip_on_invalid_json => true
}
}
# 添加地理位置(IP地址)
if [remote_ip] {
geoip {
source => "remote_ip"
target => "geoip"
}
}
# 添加User-Agent解析
if [user_agent] {
useragent {
source => "user_agent"
target => "ua"
}
}
# 过滤掉健康检查日志
if [endpoint] == "/health" {
drop {}
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
index => "logs-%{+YYYY.MM.dd}"
manage_template => false
}
# 调试用:同时输出到控制台
# stdout { codec => rubydebug }
}
第五步:在Kibana中查询
启动后,在Kibana中:
1. 创建Index Pattern
进入 Management → Stack Management → Index Patterns,创建 logs-*
2. 常用查询语法(KQL)
# 精确匹配
level: error
# 模糊搜索
message: "order"
# 多条件
level: error AND service: payment
# 范围查询
@timestamp >= "2024-06-01T00:00:00Z" AND @timestamp <= "2024-06-02T00:00:00Z"
# 存在性检查
exists: user_id
# 通配符
endpoint: /api/*
3. 创建Dashboard
在 Analytics → Dashboard 中创建可视化:
常用Panel:
- 错误日志时序图(line chart, filter: level:error)
- 按服务分组的日志量(bar chart, group by: service)
- Top 10错误类型(pie chart, group by: error_type)
- 延迟P99趋势(line chart, metric: p99(duration))
- 实时日志流(discovery table)
日志保留策略(Index Lifecycle Management)
Elasticsearch 8.x 内置ILM,配置日志自动删除:
# 通过Kibana Dev Tools执行
PUT _ilm/policy/logs_policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_size": "1GB",
"max_age": "1d"
}
}
},
"warm": {
"min_age": "7d",
"actions": {
"allocate": {
"number_of_replicas": 0
}
}
},
"cold": {
"min_age": "30d",
"actions": {
"allocate": {
"number_of_replicas": 0
}
}
},
"delete": {
"min_age": "90d"
}
}
}
}
应用直接输出到ELK(不依赖Filebeat)
# 通过TCP直接发送日志到Logstash
import asyncio
import json
from structlog.processors import JSONRenderer
class ELKLogEmitter:
def __init__(self, host="logstash", port=5000):
self.host = host
self.port = port
async def emit(self, event_dict: dict):
message = json.dumps(event_dict) + "\n"
try:
reader, writer = await asyncio.open_connection(self.host, self.port)
writer.write(message.encode())
await writer.drain()
writer.close()
await writer.wait_closed()
except Exception:
pass # 日志发送失败不能影响主业务
# 集成到structlog
structlog.configure(
processors=[...],
logger_factory=ELKLogEmitter("logstash", 5000)
)
告警(Elasticsearch Watcher)
# 错误率超过5%触发告警
PUT _watcher/watch/high_error_rate
{
"trigger": {
"schedule": { "interval": "5m" },
"condition": {
"compare": {
"ctx.payload.aggregations.error_rate.value": { "gt": 0.05 }
}
}
},
"actions": {
"send_dingtalk": {
"webhook": {
"method": "POST",
"url": "https://oapi.dingtalk.com/robot/send?access_token=xxx",
"body": "{\"msgtype\":\"text\",\"text\":{\"content\":\"🚨 错误率超过5%!\"}}"
}
}
}
}
性能优化
| 优化项 | 做法 |
|---|---|
| 索引模板 | 提前创建,指定字段类型和分词器 |
| 分片数 | 每个索引1~3个primary shard(避免过多) |
| 刷新间隔 | 调大到30s(index.refresh_interval: 30s) |
| 批量写入 | Logstash pipeline.batch.size: 1000 |
| JVM堆 | 不超过物理内存50%,最大32GB |
MonkeyCode Prompt模板
帮我搭建ELK日志系统,需求:
1. docker-compose.yml(Elasticsearch + Logstash + Kibana + Filebeat)
2. 应用输出JSON格式结构化日志(Python structlog)
3. Filebeat配置:采集Docker容器日志 + 应用文件日志
4. Logstash配置:解析时间戳、IP地理位置、UA解析
5. Kibana Dashboard:错误时序图 + 服务日志量 + Top错误
6. 索引生命周期:热数据7天,温数据30天,90天删除
7. 告警:错误率超5%发钉钉通知
总结
ELK的核心价值是让日志可搜索、可聚合、可告警。
MonkeyCode能帮你:
- 一键生成ELK全家桶Docker配置
- 重构应用日志为JSON结构化格式
- 配置Logstash过滤解析规则
- 生成Kibana Dashboard和告警规则
记住:日志要输出JSON格式,时间戳用ISO8601,关键字段(user_id、order_id、request_id)必须记录,才能实现全链路追踪。

浙公网安备 33010602011771号