前一篇把 Elasticsearch 装起来,并明确了数据流、模板、权限和生命周期。本文接着处理中间层:Logstash。它不是所有日志链路都必须出现的组件,但当日志来源复杂、字段需要治理、解析失败需要复盘、写入前需要脱敏或路由时,Logstash 仍然是 ELK Stack 里最适合承担“可审计数据加工”的位置。

这篇不把 Logstash 写成插件百科。我们用一条典型链路贯穿全文:Filebeat 把应用日志送到 Logstash,Logstash 解析、规范化、脱敏、打标签,再写入 Elasticsearch 的 data stream。中间会覆盖安装、管道语法、grok/date/mutate/json、持久化队列、死信队列、配置验证、监控 API 和交付清单。代码只保留能直接支撑这条链路的部分。

本文参考当前 Elastic Logstash 文档。官方 9.x 包仓库已经按大版本拆分,APT 源使用 packages/9.x/apt;Logstash 管道配置由 inputfilteroutput 三类插件段组成;持久化队列默认关闭,开启后会把在途事件放到本地磁盘;死信队列默认关闭,用来暂存无法正常处理的事件。版本细节会变,生产落地前仍要按目标版本文档复核。

先判断是否需要 Logstash

图:Logstash 的价值在输入、过滤、输出之间形成可验证的数据加工边界,而不是替代所有采集组件。

现代 Elastic 体系里,Filebeat、Elastic Agent 和 Elasticsearch ingest pipeline 已经能覆盖很多轻量场景。如果只是把标准 Nginx 日志、系统日志或容器日志送进 Elastic,优先考虑 Agent/Beats 加集成模板;如果只是简单字段重命名,也可以放在 ingest pipeline。Logstash 适合更重的中心化处理。

可以用这张表决定:

场景 是否适合 Logstash 原因
多来源日志格式差异大 适合 集中解析和标准化,便于统一模板
需要复杂条件路由 适合 可以按字段、标签、环境、错误类型分流
写入前要脱敏 适合 在入库前处理敏感字段,减少泄露面
需要缓冲和死信处理 适合 PQ/DLQ 能提供排障证据
单一标准日志采集 不一定 Filebeat 或 Agent 更轻
只做展示查询 不适合 Kibana 和 Elasticsearch 承担

Logstash 的成本也要明确。它运行在 JVM 上,资源占用高于 Beats;配置语言灵活,但错误配置也更容易造成字段冲突、重复写入或吞吐瓶颈。把 Logstash 放进链路,意味着团队要维护 pipeline、插件版本、队列磁盘、证书、账号和监控指标。

所以本文的目标不是“所有日志都经过 Logstash”,而是建立一条可接管的中心处理管道:输入可信,解析有证据,失败可隔离,输出有权限,变更可回滚。

管道的基本合同

Logstash 配置文件的核心结构很简单:

ruby
input {
  # 从哪里来
}

filter {
  # 如何解析、转换、脱敏、路由
}

output {
  # 写到哪里去
}

inputoutput 是必须的,filter 是可选的。但在真实日志治理里,filter 往往是最关键的部分。它决定一条原始日志最终变成怎样的结构化事件,字段类型是否稳定,时间戳是否正确,敏感内容是否被清理,失败能否被识别。

管道配置不是脚本文件。它更像一份数据合同:输入端承诺传入什么格式,过滤端承诺输出哪些字段,输出端承诺写到哪个数据流。合同不清楚,后面的 Kibana 看板、告警和自动化分析都会反复出问题。

一个生产管道至少要定义这些边界:

边界 要回答的问题
输入 数据从 Beats、TCP、HTTP、Kafka 还是文件进入
格式 原始日志是 JSON、Nginx 文本、syslog 还是自定义格式
时间 @timestamp 使用事件时间还是采集时间
字段 哪些字段进入 ECS,哪些字段保留为业务字段
脱敏 token、密码、手机号、身份证号如何处理
失败 解析失败、mapping 冲突、写入失败如何留下证据
输出 写入哪个 data stream,用什么账号和 CA

本文示例用 Beats 输入。原因很简单:在实际部署中,不建议让 Logstash 直接去每台机器 tail 文件。Filebeat 更适合贴近主机采集,Logstash 更适合集中处理。这样主机侧轻量,中心侧可治理。

安装与目录

Debian/Ubuntu 环境可以使用官方 APT 仓库。当前 9.x 仓库示例:

bash
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch \
  | sudo gpg --dearmor -o /usr/share/keyrings/elastic-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/elastic-keyring.gpg] https://artifacts.elastic.co/packages/9.x/apt stable main" \
  | sudo tee /etc/apt/sources.list.d/elastic-9.x.list

sudo apt-get update
sudo apt-get install logstash

安装后先认识目录:

路径 用途
/etc/logstash/logstash.yml Logstash 全局设置
/etc/logstash/pipelines.yml 多管道入口
/etc/logstash/conf.d/ 默认管道配置目录
/usr/share/logstash/bin/logstash 命令行入口
/var/lib/logstash/ 数据、队列、死信队列默认位置
/var/log/logstash/ 运行日志

不要把管道配置散落在个人目录。生产环境建议把 /etc/logstash 纳入配置管理,把 pipeline 文件放入 Git,变更通过评审、测试、发布和回滚。Logstash 的一次配置变更可能影响所有日志写入,不能靠 SSH 上去手改。

安装完成后验证版本和服务:

bash
/usr/share/logstash/bin/logstash --version
sudo systemctl enable logstash.service
sudo systemctl status logstash.service

服务不一定要马上启动。更好的顺序是先写好最小 pipeline,执行语法检查,再启动服务。否则 Logstash 会加载默认或空配置,问题不明显。

从最小管道开始

最小管道用于确认 Logstash 能解析配置、接收输入并输出事件:

ruby
input {
  stdin {}
}

output {
  stdout {
    codec => rubydebug
  }
}

命令行验证:

bash
/usr/share/logstash/bin/logstash \
  -e 'input { stdin {} } output { stdout { codec => rubydebug } }'

输入一行文本后,Logstash 会输出包含 message@timestamp@version 等字段的事件。这个测试不代表业务管道成功,只证明运行环境、JVM 和基本配置语言没有问题。

接下来进入真实链路:Beats 输入、过滤处理、Elasticsearch 输出。为了让示例可读,先定义目标事件:

json
{
  "@timestamp": "2026-06-11T10:15:30.000Z",
  "service.name": "payment-api",
  "log.level": "ERROR",
  "event.dataset": "app.log",
  "host.name": "app-01",
  "message": "payment failed",
  "trace.id": "8f3a1c9d",
  "labels.env": "prod"
}

这不是所有字段的最终形态,但它表达了核心要求:时间正确、服务可识别、级别可聚合、数据集可区分、主机可定位、原始消息仍可追溯。

Beats 输入

中心化 Logstash 常见输入是 Beats:

ruby
input {
  beats {
    port => 5044
    ssl_enabled => true
    ssl_certificate => "/etc/logstash/certs/logstash.crt"
    ssl_key => "/etc/logstash/certs/logstash.key"
  }
}

如果学习环境还没有证书,可以先在内网临时关闭 TLS,但生产不应这样做。Filebeat 到 Logstash 的链路可能包含业务日志、异常堆栈、请求参数和内部地址,裸传输会扩大风险。上一篇 Elasticsearch 已经强调证书和账号,Logstash 输入端也要保持同一标准。

Beats 输入适合做中心接入,因为它能和 Filebeat 形成背压关系。Logstash 处理变慢时,Beats 输入可以阻止无限制接入,避免中心节点内存被瞬间打爆。这一点和 UDP/TCP 裸输入不同。UDP 没有确认机制,丢了就丢了;TCP 虽然可靠一些,但应用侧协议和重放语义仍要自己处理。

输入端不要做太多业务判断。它只负责接入和基础标识,例如端口、TLS、来源标签。复杂解析应放在 filter,这样后续从 Beats 改成 Kafka 或 HTTP 时,解析逻辑不需要完全重写。

解析 JSON 日志

如果应用能输出 JSON 日志,优先使用 JSON。它比 grok 文本解析稳定得多,也更容易和 ECS 对齐。示例原始日志:

json
{"time":"2026-06-11T10:15:30+08:00","level":"ERROR","service":"payment-api","trace_id":"8f3a1c9d","message":"payment failed","password":"secret"}

对应 filter:

ruby
filter {
  json {
    source => "message"
    target => "app"
    tag_on_failure => ["_json_parse_failure"]
  }

  if "_json_parse_failure" not in [tags] {
    mutate {
      rename => {
        "[app][service]" => "[service][name]"
        "[app][level]" => "[log][level]"
        "[app][trace_id]" => "[trace][id]"
      }
      add_field => {
        "[event][dataset]" => "app.log"
        "[labels][env]" => "prod"
      }
      remove_field => ["[app][password]"]
    }

    date {
      match => ["[app][time]", "ISO8601"]
      target => "@timestamp"
    }
  }
}

这里有几个关键点。第一,JSON 解析结果放到 app 下,避免直接污染顶层字段。第二,字段重命名时尽量向 ECS 靠拢,比如 service.namelog.leveltrace.id。第三,敏感字段在写入前移除。第四,解析失败要打标签,而不是悄悄吞掉。

不要为了字段少就删除原始 message。排障时,原始日志经常是最后证据。更稳妥的做法是保留 message,同时把结构化字段展开。只有在明确存储成本和合规要求后,才考虑裁剪原文。

解析文本日志

不是所有系统都能输出 JSON。Nginx、老 Java 应用、设备日志和第三方系统经常还是文本。文本解析可以用 grok,但要控制复杂度。Grok 表达式越复杂,越容易慢、错、难维护。

典型 Nginx access log:

text
10.0.0.1 - - [11/Jun/2026:10:15:30 +0800] "GET /api/orders HTTP/1.1" 200 532 "-" "curl/8.0"

简化 filter:

ruby
filter {
  grok {
    match => {
      "message" => '%{IPORHOST:[client][ip]} %{USER:ident} %{USER:auth} \[%{HTTPDATE:nginx_time}\] "%{WORD:[http][request][method]} %{URIPATHPARAM:[url][path]} HTTP/%{NUMBER:[http][version]}" %{NUMBER:[http][response][status_code]:int} (?:%{NUMBER:[http][response][body][bytes]:int}|-) "%{DATA:[http][request][referrer]}" "%{DATA:[user_agent][original]}"'
    }
    tag_on_failure => ["_grok_nginx_failure"]
  }

  date {
    match => ["nginx_time", "dd/MMM/yyyy:HH:mm:ss Z"]
    target => "@timestamp"
  }

  mutate {
    add_field => {
      "[event][dataset]" => "nginx.access"
    }
    remove_field => ["ident", "auth", "nginx_time"]
  }
}

这段配置只解析常用字段,不追求一次覆盖所有边界。生产管道应该先用样本集验证:正常请求、404、500、body bytes 为 -、URL 带 query、User-Agent 为空、字段里有引号。每类样本都过了,再发布。

如果 grok 规则持续膨胀,要考虑两个替代方案:推动应用输出 JSON,或者把格式固定的日志交给 Elastic 集成。Logstash 能解析复杂文本,但不应该成为所有日志格式债务的垃圾桶。

文本日志还有一个经常被低估的问题:多行事件。Java 堆栈、Python traceback、数据库错误和容器启动日志都可能跨多行。如果采集端逐行发送,Logstash 看到的就不是一条完整异常,而是一堆互不关联的碎片。后续 Kibana 搜索、告警和自动化分析都会失去上下文。

多行合并通常更适合放在 Filebeat 侧完成,因为 Filebeat 离文件最近,能在发送前把同一个异常合并成一个事件。Logstash 也能处理多行,但中心化处理时更容易受不同来源格式影响。无论放在哪里,都要先定义清楚边界:哪些日志以时间戳开头,哪些行属于上一条事件,最大合并行数是多少,超过限制时是否截断。

例如 Java 异常可以按“以日期开头的新行代表新事件”来合并。进入 Logstash 后,filter 只负责解析合并后的完整 message,并提取 log.levelservice.nameerror.typeerror.message 等字段。不要让 grok 同时承担“合并多行”和“解析业务字段”两件事。责任拆开后,问题更容易定位:合并错了看采集端,字段错了看 Logstash。

还要保留解析失败样本。每次遇到 _grok_nginx_failure_json_parse_failure,都应该抽取一小批真实样本进入测试集。这样 pipeline 会随着真实日志演进,而不是靠想象维护。日志格式不是静态的,业务服务升级、框架升级、网关配置变化都会改变输出形态。测试样本就是管道配置的回归资产。

条件分支和路由

Logstash 支持条件判断。条件的用途不是把管道写成大程序,而是把不同日志类型分开处理。

ruby
filter {
  if [event][dataset] == "nginx.access" {
    mutate {
      add_tag => ["web"]
    }
  } else if [service][name] == "payment-api" {
    mutate {
      add_tag => ["business-critical"]
    }
  } else {
    mutate {
      add_tag => ["unknown-dataset"]
    }
  }
}

条件分支最怕类型不一致。例如一个字段有时是字符串,有时是整数,比较时可能失败。死信队列文档也明确提到,条件判断处理事件时出错,事件可能进入 DLQ。为了避免这种问题,先做类型转换,再做比较;不确定存在的字段,先判断字段是否存在。

更复杂的场景建议拆成多管道,而不是在一个文件里堆几百行 if/else。多管道可以隔离资源和故障,也方便按数据集独立发布。

yaml
- pipeline.id: app_logs
  path.config: "/etc/logstash/pipelines/app/*.conf"
  pipeline.workers: 2
  queue.type: persisted

- pipeline.id: nginx_logs
  path.config: "/etc/logstash/pipelines/nginx/*.conf"
  pipeline.workers: 2
  queue.type: persisted

拆管道不是为了显得复杂,而是为了让失败边界清晰。应用日志解析坏了,不应影响 Nginx access log;审计日志需要更高可靠性,也不应和 debug 日志共用同一套队列策略。

进一步拆分时,可以使用 pipeline-to-pipeline 模式。常见做法是入口管道只负责接收和粗分类,把事件按数据集发送给内部管道;后续管道分别处理应用、Nginx、审计或安全日志。这样可以把 TLS 接入、认证、背压和业务解析分开。

概念示例:

ruby
input {
  beats {
    port => 5044
  }
}

output {
  if [event][dataset] == "nginx.access" {
    pipeline {
      send_to => "nginx_pipeline"
    }
  } else {
    pipeline {
      send_to => "app_pipeline"
    }
  }
}

下游管道再用 pipeline input 接收。这个模式适合大型中心 Logstash,但小团队不必一开始使用。它的好处是隔离配置和资源,代价是排障链路变长。上线前要能回答一个事件从入口到下游经历了哪些 pipeline,否则出现丢失时很难追踪。

多管道还有一个治理价值:发布节奏可以按数据集拆开。Nginx pipeline 的变更不需要和应用 pipeline 一起发布;审计 pipeline 可以要求更严格评审;临时验证 pipeline 可以设置过期时间。随着日志平台扩大,这种边界比单个大配置文件更可维护。

输出到 Elasticsearch

输出端要和上一篇的 Elasticsearch 安装配置衔接。至少要配置 hosts、CA、认证方式、目标 data stream 或索引、失败处理。

ruby
output {
  if "_json_parse_failure" in [tags] or "_grok_nginx_failure" in [tags] {
    elasticsearch {
      hosts => ["https://es-prod-01:9200"]
      cacert => "/etc/logstash/certs/http_ca.crt"
      api_key => "${LOGSTASH_DLQ_API_KEY}"
      index => "logs-parse-failed-%{+YYYY.MM.dd}"
    }
  } else {
    elasticsearch {
      hosts => ["https://es-prod-01:9200"]
      cacert => "/etc/logstash/certs/http_ca.crt"
      api_key => "${LOGSTASH_WRITE_API_KEY}"
      data_stream => true
      data_stream_type => "logs"
      data_stream_dataset => "%{[event][dataset]}"
      data_stream_namespace => "%{[labels][env]}"
    }
  }
}

这里用环境变量传入 API Key,避免把凭证写进配置文件。生产中还可以使用 Logstash keystore 管理敏感值。重点是最小权限:正常写入账号只能写目标日志数据流,解析失败账号只能写失败索引或失败数据流,不要使用 elastic 超级用户。

解析失败是否写入 Elasticsearch,要看团队策略。完全丢弃最省成本,但失去证据;全部写入失败索引最便于复盘,但会占用存储并可能包含敏感信息。较稳妥的做法是写入受限失败数据流,设置短生命周期,并对敏感字段先做基础脱敏。

输出端还要理解 Elasticsearch output 的失败语义。整个 HTTP 请求失败时,插件可能持续重试;bulk 请求中单条文档失败时,如果配置了 DLQ,失败事件可以进入死信队列。不能把“开启 DLQ”理解为所有失败都自动解决。DLQ 只提供暂存和调查入口,仍需要人工或自动化流程处理。

输出配置还必须和 Elasticsearch 模板协同。Logstash 可以把字段写出去,但不能替 Elasticsearch 自动决定所有字段语义。上一篇已经建立了 data stream、索引模板和生命周期策略,本篇的 output 必须落到那些约定上。比如 data_stream_dataset 应该是可控集合,而不是任意从日志里透传;data_stream_namespace 应该来自环境或组织边界,而不是用户输入。

如果允许业务日志直接决定目标索引名,会带来两个风险。第一,恶意或异常字段可能创建大量索引,造成集群状态膨胀。第二,不同服务会绕过平台模板,导致 mapping 不一致。更稳妥的方式是 Logstash 根据受控字段路由,未知数据集写到隔离区,等确认模板后再纳入正式数据流。

对于关键业务,可以考虑确定性文档 ID,减少重复写入。比如 Filebeat 重试、Logstash 重启、PQ 重放都可能造成至少一次投递。日志类数据通常接受重复少量事件,但审计或计费相关数据不一定能接受。此时可以用 fingerprint filter 基于来源文件、offset、时间和消息摘要生成 ID,再在 output 里作为 document_id。这会降低写入吞吐,是否使用要按场景判断。

ruby
filter {
  fingerprint {
    source => ["[log][file][path]", "[log][offset]", "message"]
    target => "[@metadata][document_id]"
    method => "SHA256"
  }
}

不是所有日志都需要这一步。对高吞吐 access log,重复少量事件通常比生成 ID 的成本更容易接受;对审计日志、自动化处理结果和告警事件,确定性 ID 更有价值。Logstash 配置要体现数据价值,而不是一套规则套所有数据。

持久化队列

Logstash 配置语法验证

图:配置发布前要先通过语法、样本、队列和输出验证,再让生产采集端切流。

默认情况下,Logstash 使用内存队列。吞吐高,但进程异常退出时在途事件可能丢失。持久化队列会把在途事件写到本地磁盘,Logstash 重启后尝试继续投递。官方文档也提醒,PQ 可以吸收突发流量,但默认关闭,而且不能解决磁盘损坏、机器丢失、未 checkpoint 数据等所有问题。

启用示例:

yaml
queue.type: persisted
path.queue: /var/lib/logstash/queue
queue.max_bytes: 8gb

多管道时要注意,队列容量按管道计算,不是全局共享。如果三个 pipeline 都设置 queue.max_bytes: 8gb,磁盘至少要按 24GB 加上余量规划。队列目录必须使用本地文件系统,不要放到 NFS。日志平台遇到 Elasticsearch 短暂不可用时,PQ 可以争取恢复时间;但如果 Elasticsearch 长时间不可用,队列写满后仍会对输入端形成背压。

PQ 的设计要结合业务价值。审计日志、支付错误、核心应用日志可以启用更大的队列;低价值 debug 日志可以短队列甚至直接限流。可靠性不是无限堆磁盘,而是把不同数据价值分级。

队列容量要按“下游最长可接受不可用时间”估算,而不是拍脑袋。假设某条 pipeline 平均每小时接收 20GB 原始事件,团队希望 Elasticsearch 短暂维护 2 小时内不丢核心日志,那么队列容量至少要覆盖 40GB,再加上序列化开销和安全余量。如果磁盘只有 50GB,配置 queue.max_bytes: 64gb 没有意义,最终只会把节点拖进磁盘告警。

PQ 也会影响事件重复。Logstash 异常退出后,未确认的批次可能被重新处理。大多数日志分析场景能接受少量重复,但如果输出端触发工单、邮件或 Webhook,就必须谨慎。Logstash 更适合把事件写入存储系统,不适合直接把每条日志变成不可幂等的外部动作。需要触发动作时,最好在 Elasticsearch 或告警系统里基于聚合结果做。

维护 PQ 时要监控三个信号:队列大小、队列增长速度、最老事件滞留时间。队列短时间增长可能只是下游重启;持续增长说明系统吞吐已经小于输入速率。只看 Logstash 进程 alive 没意义,队列堆积才是中心处理层的真实压力。

死信队列

死信队列用于处理“事件已经进入 Logstash,但无法正常处理或写入”的情况。典型原因是 Elasticsearch mapping 冲突、单条 bulk item 失败、条件判断出错。DLQ 默认关闭,需要显式启用:

yaml
dead_letter_queue.enable: true
path.dead_letter_queue: /var/lib/logstash/dead_letter_queue
dead_letter_queue.max_bytes: 1gb
dead_letter_queue.retain.age: 2d

DLQ 不是垃圾桶。它是待处理清单。进入 DLQ 的事件包含原事件、失败原因、插件信息和进入时间。处理方式通常是另起一个 pipeline 读取 DLQ,输出到 stdout 调查,或修正字段后重新写入目标索引。

示例调查管道:

ruby
input {
  dead_letter_queue {
    path => "/var/lib/logstash/dead_letter_queue"
    pipeline_id => "app_logs"
    commit_offsets => true
  }
}

output {
  stdout {
    codec => rubydebug {
      metadata => true
    }
  }
}

如果确认是某个字段 mapping 错误,可以在重放管道里移除或重命名字段,再写入修复后的数据流。但要谨慎:DLQ 事件再次处理失败时,不一定会重新进入同一个 DLQ。重放前最好先在测试索引验证。

DLQ 还要配合告警。死信队列持续增长,说明解析、模板或写入权限存在问题;只开启不监控,会把线上故障延迟成存储问题。

DLQ 处理流程要像小型事故流程。第一步确认增长来源,是某个 pipeline、某个数据集还是某个字段;第二步抽样查看元数据,判断是 mapping、权限、条件判断还是字段格式;第三步修复 pipeline 或 Elasticsearch 模板;第四步决定是否重放;第五步清理已消费段并记录样本。没有这个流程,DLQ 会变成“没人看的失败仓库”。

重放时不要直接写回原目标。建议先写到临时数据流或测试索引,验证字段结构和文档数量,再决定是否 reindex 或重新投递正式数据流。如果失败原因是敏感字段或错误脱敏,重放前还要重新评估数据是否允许入库。DLQ 里的数据往往正是异常数据,不能默认安全。

DLQ 的保留时间也要和响应能力匹配。设置 2 天保留,但团队只有每周检查一次,等于大部分失败都来不及处理;设置 30 天保留但没有容量告警,又会把磁盘写满。合理做法是短保留加高优先级告警:失败出现就处理,而不是长期囤积。

文件采集验证

日志文件监控验证

图:文件采集验证要覆盖文件变化、采集端发送、Logstash 解析、Elasticsearch 写入和 Kibana 查询。

虽然生产更推荐 Filebeat 采集文件,但学习 Logstash 时可以用 file input 理解事件流。这个方式适合本机验证,不适合大规模主机采集。

ruby
input {
  file {
    path => "/tmp/demo-app.log"
    start_position => "beginning"
    sincedb_path => "/tmp/logstash-demo.sincedb"
    codec => json
  }
}

filter {
  mutate {
    rename => {
      "service" => "[service][name]"
      "level" => "[log][level]"
    }
    add_field => {
      "[event][dataset]" => "app.log"
      "[labels][env]" => "lab"
    }
  }

  date {
    match => ["time", "ISO8601"]
    target => "@timestamp"
  }
}

output {
  stdout {
    codec => rubydebug
  }
}

写入测试日志:

bash
echo '{"time":"2026-06-11T10:15:30+08:00","service":"demo-api","level":"INFO","message":"hello logstash"}' >> /tmp/demo-app.log

这里故意使用独立 sincedb_path,方便反复测试。生产 file input 的 sincedb 不要乱删,否则可能重复采集。Filebeat 也有自己的 registry 机制,本质上都是为了记录采集进度。采集状态文件属于运行状态,不应该放在临时目录里随系统清理。

配置验证和发布

每次变更 pipeline,先做语法检查:

bash
/usr/share/logstash/bin/logstash \
  -f /etc/logstash/conf.d \
  --config.test_and_exit

如果使用多管道,也要检查 pipelines.yml 是否指向正确目录。语法通过只说明能加载,不说明解析正确。因此还要准备样本集。样本集至少包含:

样本 目的
正常 JSON 日志 验证主路径
缺字段日志 验证默认值和标签
非法 JSON 验证解析失败标签
异常堆栈 验证多行或原文保留
敏感字段 验证脱敏
字段类型变化 验证 mapping 风险

样本验证可以用 stdin/stdout,不必每次都写入 Elasticsearch。先让 Logstash 输出 rubydebug,确认字段结构,再接 Elasticsearch 输出。这样能把“解析问题”和“写入问题”分开。

发布时建议按三步走:先在测试 Logstash 运行新配置,再让少量 Filebeat 指向新 pipeline,最后扩大到全量。不要在生产主 Logstash 上一次替换所有 pipeline。配置看起来只是文本,但它处理的是持续流量,错误会迅速放大。

如果启用了 config.reload.automatic,也要谨慎。自动 reload 能减少重启,但不能替代测试。错误配置可能导致 pipeline reload 失败或旧配置继续运行,必须通过日志和监控 API 确认当前加载的是哪份配置。

配置发布还要处理顺序问题。很多 pipeline 变更需要和 Elasticsearch 模板变更配套:先创建或更新模板,再发布 Logstash;如果先发布 Logstash,新的字段可能写进旧 mapping,引发冲突。反过来,如果先删除旧字段模板,但 Logstash 还在输出旧字段,也会造成失败。发布单要写清楚依赖顺序。

一次标准变更可以拆成:

  1. 准备样本和预期输出;
  2. 在测试环境更新模板;
  3. 用 stdout 验证 Logstash 字段;
  4. 写入测试数据流验证 mapping;
  5. 灰度一小部分采集端;
  6. 观察 events、queue、DLQ 和 Elasticsearch 写入错误;
  7. 全量切换并保留回滚配置。

回滚不一定只是恢复旧文件。如果新配置已经写入了错误字段,回滚 Logstash 只能阻止继续扩大,不能修复已写入数据。必要时还要删除测试数据流、修正模板、reindex 或标记异常时间窗口。文章前面强调样本验证,就是为了降低这种代价。

监控与容量

Logstash 默认提供 HTTP API,常用于查看节点和 pipeline 状态:

bash
curl http://localhost:9600/
curl http://localhost:9600/_node/pipelines
curl http://localhost:9600/_node/stats/pipelines

关注指标:

指标 含义 判断
events.in 输入事件数 应持续增长
events.out 输出事件数 长期应接近输入
events.filtered 过滤后事件数 异常下降要查 drop/失败
duration_in_millis 处理耗时 突增可能是 filter 或 output 慢
queue events 队列堆积 持续增长说明下游或处理瓶颈
DLQ 大小 死信事件量 增长说明失败未处理

容量调优先找瓶颈,不要上来就加 worker。Grok 复杂、DNS/GeoIP 查询慢、Elasticsearch 写入慢、磁盘队列满、JVM GC 都可能造成吞吐下降。盲目增加 pipeline.workers 可能让 Elasticsearch 承压更大,也可能让事件顺序变化。

一条可用的调优路径是:

  1. 先确认 Elasticsearch 写入健康,避免把下游问题当成 Logstash 问题;
  2. 再看 Logstash pipeline stats,定位是 input、filter 还是 output 慢;
  3. 对 filter 做样本压测,尤其是 grok、ruby、geoip;
  4. 调整 batch、workers 和 PQ 前先记录基线;
  5. 每次只改一个参数,并保留回滚值。

Logstash 是中心节点时,还要监控 CPU、heap、磁盘、队列目录、DLQ 目录和 5044/9600 端口。队列目录磁盘满会直接影响管道,DLQ 目录满会掩盖真实失败。

性能基线要在上线前建立。至少记录三组数据:空 filter 的输入输出吞吐、真实 filter 的输入输出吞吐、Elasticsearch 短暂不可用时 PQ 增长速度。没有基线,后续“Logstash 变慢”只能靠感觉判断。基线不需要复杂,固定样本、固定时间窗口、固定节点规格即可。

Grok 是常见性能热点。复杂正则、回溯严重的模式、多个候选 match 都会拉高 CPU。优化时优先减少正则复杂度,能用 dissect 处理固定分隔格式时就不要用 grok;能让应用输出 JSON 时就不要继续解析文本;能在 Filebeat 或 Agent 集成里解决的标准日志,不要强行回到 Logstash 中心解析。

Output 慢时,先看 Elasticsearch。Bulk 写入失败、磁盘水位、线程池拒绝、mapping 冲突都会让 Logstash 看起来像瓶颈。中心链路要同时监控两端:Logstash events.out 下降时,去 Elasticsearch 看 indexing pressure、thread pool、磁盘和集群健康。单看 Logstash 无法解释全链路。

资源隔离也很关键。不要把 Logstash 和 Elasticsearch 数据节点放在同一台低规格机器上跑生产流量。两者都会吃 CPU、内存和磁盘 IO,一起部署时故障会互相放大。学习环境可以同机,生产至少要评估中心处理节点和存储节点的资源边界。

安全与脱敏

Logstash 处在数据进入 Elasticsearch 之前,是脱敏的关键位置。不要把脱敏放到 Kibana 展示层,因为数据已经入库;也不要完全依赖应用开发自觉,因为不同团队标准不一。

常见脱敏可以使用 mutate/gsub:

ruby
filter {
  mutate {
    gsub => [
      "message", "password=[^&\\s]+", "password=***",
      "message", "token=[^&\\s]+", "token=***"
    ]
  }
}

这只是基础示例。真实脱敏要结合日志格式,避免误伤正常字段,也要覆盖结构化字段而不只是 message。对特别敏感的数据,最好在采集前或应用侧就不要输出。Logstash 可以补救,但不应该成为唯一防线。

凭证管理也属于安全。Elasticsearch API Key、证书私钥、Kafka 密码、HTTP 输出 token 都不能写进普通配置文件。可以使用环境变量、Logstash keystore 或平台密钥管理。配置仓库只保存变量名和引用方式。

网络上,Beats 输入端口只对采集端开放,监控 API 9600 只对运维网络开放。不要把 9600 暴露给普通业务网络,它可能泄露 pipeline、插件和运行信息。

还要明确哪些字段不允许进入日志平台。比如完整 Authorization header、Cookie、数据库连接串、云厂商 AK/SK、用户身份证号、银行卡号、未脱敏手机号,都应在应用侧或采集侧尽早处理。Logstash 可以做二次兜底,但如果所有敏感信息都先进入中心节点,再靠 filter 删除,中心节点本身已经承担了更高合规风险。

脱敏规则也要测试。正则过宽会把正常业务字段替换掉,正则过窄会漏掉真实凭证。可以准备一组“应脱敏”和“不应脱敏”的样本,用 stdout 输出验证。安全规则和解析规则一样,也需要回归测试。

常见故障

Logstash 故障要按阶段定位:

表现 可能阶段 排查方向
Beats 连接失败 input 端口、TLS、证书、防火墙
日志进入但字段不对 filter json/grok/date/mutate 顺序
@timestamp 不准 filter date match、时区、字段来源
写入 401/403 output API Key、索引权限、CA
写入 400 output/filter mapping 冲突、字段类型
队列持续增长 output 或 filter ES 慢、filter 慢、下游不可用
DLQ 增长 output/filter 单条失败、条件错误、mapping

最常见的错误是把所有问题都看成 grok 失败。实际上写入 400 往往是 Elasticsearch mapping 已经固定,Logstash 只是暴露了字段类型冲突。此时改 grok 不一定能解决,可能要修模板、修历史索引、写新字段或重建数据流。

第二个常见错误是丢弃失败事件。drop {} 适合明确不要的数据,比如健康检查日志、调试噪声;不适合处理解析失败。解析失败应该打标签、写失败数据流或 DLQ,至少留一段时间给团队分析。

第三个错误是把一个 pipeline 承载所有日志类型。最初方便,后面会变成变更风险。某个业务的字段调整,可能影响所有日志;某个 grok 慢,拖累整条管道。按数据集拆分 pipeline,是从试点走向生产时必须做的事。

第四个错误是只在 Logstash 上修数据质量。字段命名混乱、日志级别不规范、时间戳缺失、异常堆栈不完整,本质上很多是应用日志规范问题。Logstash 可以标准化和兜底,但不能无限替应用补债。平台团队应该把反复出现的解析问题反馈给业务团队,推动应用输出结构化日志。

第五个错误是忽略时区。应用日志可能是本地时间,容器日志可能是 UTC,Nginx 可以带时区,数据库日志可能不带时区。date filter 配错后,数据会写入错误时间窗口,Kibana 看起来像“没有数据”。处理方式是让源日志尽量输出 ISO8601 带时区时间;做不到时,在 Logstash 中为特定数据集显式声明 timezone,并把这个假设写进文档。

第六个错误是把 stdout 调试配置留在生产。stdout { codec => rubydebug } 很适合本地测试,但生产高流量下会产生大量日志,影响性能并泄露数据。上线前要检查 output,只保留必要目标和受控失败输出。

交付清单

Logstash 管道上线前,至少留下这些证据:

项目 证据
安装 版本、安装源、服务状态
输入 Beats/TCP/HTTP/Kafka 来源、端口、TLS
解析 样本集、字段映射、失败标签
输出 目标 data stream、账号权限、CA
队列 PQ 是否开启、容量、磁盘告警
死信 DLQ 是否开启、处理流程、保留时间
监控 9600 API、事件速率、队列、DLQ
发布 语法检查、灰度、回滚路径
安全 脱敏规则、凭证管理、网络范围

这份清单的目的不是让文章更完整,而是让后续 Kibana 和 Filebeat 两篇能接上。Kibana 需要稳定字段和 data view;Filebeat 需要知道输出到哪个 Logstash、用什么证书、出现背压时怎么处理。Logstash 如果没有明确合同,前后两端都会变成猜谜。

交付后还要做一次端到端回归:让 Filebeat 发送一条带唯一标识的测试日志,确认 Logstash 收到、字段被正确解析、Elasticsearch data stream 写入成功、Kibana 按时间范围能查到。这个回归样例要长期保留,用于证书轮换、API Key 轮换、模板变更和 Logstash 升级后的快速验证。

如果平台允许自动化,可以把这条回归做成脚本或运维任务,但文章这里不强行给脚本。关键不是脚本形式,而是每次变更都能证明链路仍然完整:采集、传输、解析、写入、查询五个步骤缺一不可。

从系列衔接看,Logstash 是 Elasticsearch 和 Filebeat 之间的契约层。上一篇已经定义了目标数据流、模板、权限和生命周期,本篇把原始日志转换成这些目标字段;后面的 Kibana 会基于这些字段做搜索、图表和告警,Filebeat 会负责把边缘主机日志稳定送进来。任何一端字段名、时间戳、证书或账号变更,都要回到这份管道合同里同步更新。只有这样,ELK 才不是四个工具临时拼在一起,而是一条可维护的数据链路。

如果团队后续引入 Dify 或其他自动化分析工具,也应读取 Logstash 已经稳定输出的字段,而不是再次解析原始 message。自动化越靠近结构化字段,结果越可解释;越依赖大模型猜日志格式,越难复盘。

这也是日志平台长期维护的关键:让机器消费字段,让人阅读原文,让失败路径留下证据,并把每次格式变化沉淀成样本、规则和验收记录。

管道交接时还要明确负责人,避免失败事件长期无人认领,也避免重复修同一个字段问题。

本篇结论

Logstash 的核心价值不是“插件多”,而是把复杂日志加工放到一个可测试、可审计、可回滚的位置。输入端要轻,过滤端要明确,输出端要安全,失败路径要能复盘。JSON 优先,grok 谨慎;PQ 解决短期缓冲,不替代备份;DLQ 留住失败证据,不替代修复流程。

下一篇进入 Kibana。只有 Logstash 把字段、时间、数据集和失败边界处理稳定,Kibana 的搜索、Lens、Dashboard 和告警才有可靠的数据基础。

参考资料