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能帮你:

  1. 一键生成ELK全家桶Docker配置
  2. 重构应用日志为JSON结构化格式
  3. 配置Logstash过滤解析规则
  4. 生成Kibana Dashboard和告警规则

记住:日志要输出JSON格式,时间戳用ISO8601,关键字段(user_id、order_id、request_id)必须记录,才能实现全链路追踪。

posted @ 2026-05-29 21:49  机房管理员  阅读(31)  评论(0)    收藏  举报