资讯详情

Logstash实战:nginx日志JSON化解析与Kibana可视化分析

📅 2026/10/14 10:57:20 | 华诺云谱 👁 阅读
Logstash实战:nginx日志JSON化解析与Kibana可视化分析
接上一回ELK系列这次讲Logstash这块。如果你已经搭好了Elasticsearch和Kibana服务器上面nginx的访问日志还在几十兆、几百兆地增长想查个慢接口、统计一下状态码分布只能靠grep和awk硬啃那这一篇正是你需要的怎么让Logstash把nginx日志吃进来解析成结构化字段写进ES最后在Kibana里直接画出访问趋势、响应耗时、状态码占比这些看板。这篇从nginx日志格式设计讲起到Logstash配置逐行拆解再到实际跑通验证最后把我踩过的坑一并列出来。适合刚把ES/Kibana跑起来、正准备啃Logstash配置的小伙伴也适合Logstash已经跑起来但日志解析不对、ES里全是字符串类型字段的人。我会按一套完整可落地的方案走一遍保证照着配能出数据。1. 日志摄入前先搞清nginx这边要做什么1.1 默认日志格式为什么不行很多人第一步就踩坑以为Logstash直接读nginx默认的access.log就能分析。默认的combined格式长这样127.0.0.1 - - [21/Jan/2025:13:45:26 0800] GET /api/v1/users HTTP/1.1 200 1536 https://example.com/page Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...这种格式拿来人肉排查问题没问题但要让Logstash解析出clientip、timestamp、request、status、body_bytes_sent这些独立字段kibana里才好做聚合统计比如按status分组看5xx比例按request_time排序找慢接口或者按客户端IP聚合做来源分析。默认格式信息量明显不够至少缺少两个关键字段请求处理耗时request_time、上游响应耗时upstream_response_time。没有这两个字段你想分析接口性能就是空话。还有一个隐藏问题默认格式里的时间戳是21/Jan/2025:13:45:26 0800这种字符串Logstash如果用grok解析后直接存进timestamp还得专门处理时区和格式转换做起来不麻烦但每多一个步骤就多一个出错点。所以我的建议是不要用默认格式直接在nginx侧定义一套专属的log_format信息一次给全后边解析省一大半事。1.2 自定义JSON格式日志的具体配置在nginx配置文件http块里加一段自定义格式。这里我推荐直接输出JSON理由很简单Logstash的json过滤器比grok正则解析快一个数量级而且字段结构天然清晰不会因为正则漏匹配导致整条日志解析失败。log_format json_log escapejson {timestamp:$time_iso8601, clientip:$remote_addr, remote_user:$remote_user, request:$request, status:$status, body_bytes_sent:$body_bytes_sent, request_time:$request_time, http_referer:$http_referer, http_user_agent:$http_user_agent, http_x_forwarded_for:$http_x_forwarded_for, host:$host, request_method:$request_method, request_uri:$request_uri, server_protocol:$server_protocol, upstream_response_time:$upstream_response_time};然后在对应的server块里指定access_log /var/log/nginx/json_access.log json_log;注意几个细节escapejson这个参数一定要加。它会让nginx自动把http_user_agent这类字段里的引号、反斜杠转义成合法的JSON字符串避免一条日志里因为UA里有个引号导致Logstash解析直接失败。$request_time和$status这些数值字段在JSON里不要加双引号否则后续还得用mutate转类型。upstream_response_time有多个上游时会返回多个值比如0.021, 0.019这里先用字符串存着后续如果需要按单个上游分析再在Logstash里拆。自定义log_format不会覆盖默认的combined格式你完全可以在不同的server或location里用不同的日志格式互不影响。如果nginx是1.11.4之前的旧版本不支持escapejson参数那就退而求其次用grok解析combined格式后面我会给出一套能用的grok配置。但新版本nginx比较少见还是优先JSON。2. 设计Logstash摄入链路2.1 一条完整日志的流动路径日志从nginx到ES中间要经过采集、解析、缓冲、写入四步。很多人一上来就纠结Filebeat和Logstash怎么选其实看规模一台或几台nginx服务器量级在每秒几千条以内Logstash直接读文件即可少一个组件少一份维护成本。服务器数量多或者日志量大到Logstash重启会造成数据积压就上Filebeat做轻量采集转发给Logstash统一解析。Filebeat的优势是资源占用极小支持backpressure还能在Logstash挂掉时本地缓存防止丢日志。这篇讲的是单机场景所以用Logstash直接读nginx日志文件的方案。如果是多机场景只需要在每台机器装个Filebeat把输出指向统一的Logstash实例就行Filter和Output配置完全复用。提示Logstash读文件是有状态的它用sincedb文件记录每个文件读到哪个位置。如果你改了进程内已经读取过的日志文件内容sincedb点位是旧的Logstash不会重复读。测试时要生成新日志或者手动清掉sincedb再重启。2.2 Logstash pipeline的组件分工Logstash的配置文件由input、filter、output三部分组成外加可选的pipeline配置。对于nginx访问日志各自的职责是input负责监听和读取文件把每行原始日志变成一条事件。filter负责把原始字符串加工成结构化数据核心是json解析或grok匹配再加上日期处理和类型转换。output负责把加工好的事件写入ES同时控制索引名称、模板、用户名密码这些。每个Logstash进程可以配置多个pipeline但入门阶段一个pipeline搞定就够。配置文件放在/etc/logstash/conf.d/下Logstash启动时会自动合并所有配置文件。如果你一个目录下放了多个配置文件它们会被拼成一个pipeline注意不要重复定义同名的input或filter插件。3. Logstash配置逐段拆解3.1 input看好文件别丢数据直接看配置input { file { path [/var/log/nginx/json_access.log] start_position end sincedb_path /var/lib/logstash/sincedb/nginx_access type nginx-access } }逐参数说path要读取的文件路径支持*通配符。注意如果写/var/log/nginx/*.log会把error.log也读进来那些日志格式完全不一样filter阶段会解析失败所以最好指定具体的access日志文件名。start_position两个值beginning和end。第一次启动时有效之后由sincedb决定。测试阶段想立即读到已有日志内容就用beginning上线阶段不希望Logstash一启动就灌入历史日志用end。sincedb_path指定sincedb文件位置。默认写在家目录或临时目录多实例跑的时候容易冲突单独指定一个路径最稳。type给事件打一个标签后续filter和output里可以用if [type] nginx-access区分日志类型。关于文件读取有个常被忽略的点Logstash默认每秒检查一次文件是否有新内容如果日志量小或者日志写入频率低从nginx写日志到Logstash读到可能需要1秒左右的延迟。对实时性要求高可以调整pipeline里的file_completed_path和关闭stat_interval但一般没啥必要。3.2 filterJSON解析、时间和类型三件套核心filter配置filter { if [type] nginx-access { json { source message target json_body remove_field [message] } date { match [[metadata][timestamp], ISO8601] target timestamp timezone Asia/Shanghai } mutate { convert { [json_body][status] integer [json_body][body_bytes_sent] integer [json_body][request_time] float } rename { [json_body][clientip] client_ip [json_body][http_user_agent] user_agent } remove_field [json_body] } } }这段配置里有几个逻辑要点json插件解析后用target json_body把解析结果临时存到一个嵌套字段里而不是直接覆盖到事件顶层。这样做的目的是避免字段冲突——比如nginx日志里的host字段和Logstash自带的host字段容易打架。解析完后通过mutate里的rename把需要顶层展示的字段提升出来。date插件是为timestamp服务的。$time_iso8601在nginx里输出的是2025-01-21T13:45:2608:00这种ISO8601格式date插件直接识别然后覆盖timestamp。timezone参数一定要指定否则Logstash会按服务器系统时区处理如果服务器是UTC而业务在东八区Kibana里看到的时间就会差8小时。mutate的convert把status、字节数、耗时转成数值类型的字段。这一步不做Kibana里所有字段都是字符串你没法做数值范围查询和平均值聚合后面做监控看板时会很痛苦。remove_field把原始message删掉节省ES存储空间。如果你还想保留原始日志做审计可以不删但一般没必要。如果因为各种原因必须用grok解析默认格式我给出经过实践的完整grok写法grok { match { message %{COMBINEDAPACHELOG} } remove_field [message] } grok { match { message %{IPORHOST:client_ip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] \%{WORD:request_method} %{URIPATHPARAM:request_uri} HTTP/%{NUMBER:http_version}\ %{NUMBER:status} %{NUMBER:body_bytes_sent} \%{NOTSPACE:http_referer}\ \%{DATA:user_agent}\ } remove_field [message] }但要注意grok的正则引擎是Onigmo解析复杂正则时CPU开销比json解析高很多。日志量大的场景能上JSON就尽量别用grok。注意如果使用了json插件但某条日志因为各种原因不是合法JSON这条事件会自动走添加tags [_jsonparsefailure]的路子不会再进入后续date和mutate。排查时看到ES里有_jsonparsefailure标签第一时间回看nginx日志是不是格式里引号没转义。3.3 output写入ES时的索引与模板控制output { if [type] nginx-access { elasticsearch { hosts [http://10.0.0.5:9200] index nginx-access-%{YYYY.MM.dd} user elastic password changeme manage_template true template /etc/logstash/templates/nginx-access-template.json template_name nginx-access template_overwrite true } } }几个参数的价值index用日期后缀做每日索引过一段时间的索引可以单独做生命周期管理或删除不用影响新数据。manage_template和template组合使用可以让Logstash在首次写入时自动创建索引模板。模板里主要定义两类信息一是timestamp和client_ip这些字段的映射类型二是分片数和副本数。没有模板的话ES会自动映射可能出现status字段被映射成long之后无法做keyword聚合或者user_agent字段被当作全文搜索导致存储膨胀。Logstash 7.x和ES 8.x之间需要配置用户名密码。这里写成明文配置文件里的user和password注意文件权限要收紧建议设成600。4. 实操全过程从改配置到Kibana看到数据4.1 改nginx配置并验证日志输出先把自定义log_format配置加到nginx主配置里然后nginx -t检查语法接着nginx -s reload重载。注意reload不会切掉现有连接不用担心影响线上流量。验证日志格式是否正常可以手动造几条请求for i in {1..10}; do curl -s http://127.0.0.1/api/health /dev/null done tail -3 /var/log/nginx/json_access.log | jq用jq格式化查看确认JSON结构完整、请求时间有值。这一步能发现大部分nginx配置问题比如escapejson没加导致UA里引号破坏了JSON结构。如果输出的JSON带转义符是因为你加了escapejson后在日志里看到的\是JSON标准的引号转义不要觉得它坏了。真正坏的JSON是字段之间没有逗号或者少了大括号。4.2 编写Logstash配置并本地调试把上面的input、filter、output配置存成/etc/logstash/conf.d/nginx-access.conf然后用Logstash自带的语法检查命令验证/usr/share/logstash/bin/logstash -t -f /etc/logstash/conf.d/nginx-access.conf输出Configuration OK才说明语法无问题。这里-t只会检查配置语法不会真正启动读取日志所以随便跑不会双开实例。调试阶段推荐一个技巧单独建一个调试配置把input改成stdin然后手动粘贴一条真实nginx日志观察解析后的字段。这样不用反复重启服务就能验证grok和date配置是否正确。input { stdin {} } filter { json { source message target json_body remove_field [message] } date { match [[metadata][timestamp], ISO8601] target timestamp timezone Asia/Shanghai } mutate { convert { [json_body][status] integer [json_body][request_time] float } rename { [json_body][clientip] client_ip } remove_field [json_body] } } output { stdout { codec rubydebug } }运行后粘贴一条真实日志输出里会看到解析后的完整字段树包括timestamp和类型转换后的status数值。确认无误后把这个调试配置删除换成正式配置。4.3 启动Logstash并验证数据落ES正式启动systemctl start logstash或者手动前台启动方便看日志/usr/share/logstash/bin/logstash -f /etc/logstash/conf.d/nginx-access.conf --config.reload.automatic--config.reload.automatic这个参数非常实用开发阶段配置改了自动生效不用反复重启。但上线后建议关掉避免配置热加载引发不可预期的问题。验证ES里有没有数据curl -s http://10.0.0.5:9200/_cat/indices/nginx-access-*?v看到docs.count数字在涨就说明写入成功了。然后在Kibana的Stack Management里创建一个索引模式nginx-access-*时间字段选timestamp就能在Discover里查到nginx日志了。如果一开始建的索引是nginx-access-2025.01.21这种当日索引等第二天日志量涨起来后又会出现新的索引Kibana的索引模式会自动覆盖所有匹配的索引不需要再手动建。5. 实际运维中高频踩坑点5.1 日志一直不写入ES日志明明有新增ES里就是没数据这种问题十次有八次出在文件位置记录或者解析失败上。排查路径看Logstash自己的日志/var/log/logstash/logstash-plain.log有没有报错。查ES里有没有_jsonparsefailure标签的文档有说明解析失败。如果日志完全没被读到很可能sincedb文件记录了旧的文件位置。新改的日志文件路径和旧路径不一致Logstash会认为不是同一个文件不会主动从旧位置继续。确认运行Logstash的用户对日志文件有读权限。很多日志目录是root用户创建的Logstash进程以logstash用户运行默认读不了。看看/var/log/nginx/目录权限是不是755日志文件是不是640。提示如果服务器上日志按天切割文件名会变成access.log-20250121这种。file插件的path通配符要写成/var/log/nginx/access.log*但要注意access.log-20250120这种老文件也会被继续读。建议文件名规范统一为access.log.YYYYMMDDpath写/var/log/nginx/access.log.*老的日志即使读了也会按日志里的timestamp归到对应日期的索引不会串。5.2 时间字段差8小时这个是Elastic Stack最常见的时区问题。timestamp在ES里存储为UTC时间Kibana界面展示时会自动转成浏览器本地时区。如果Kibana看了眼时间是乱的多半是date过滤器的timezone没指定导致Logstash把日志时间当成UTC解析或者当成服务器本地时区解析。解决办法就一条在date插件里明确指定业务时区date { match [[metadata][timestamp], ISO8601] timezone Asia/Shanghai }加了以后Logstash会先把字符串解析为东八区时间再转成UTC存入ES。Kibana里显示时按浏览器时区转回东八区就正常了。5.3 字段类型不匹配导致聚合报错用默认映射写入ES第一次写入时request_time被映射成float这没问题。但如果你后续改了日志格式新增了字段或者nginx配置里某字段返回值格式变了比如status偶尔出现-代替数字ES的映射就会冲突写入新索引时报mapper_parsing_exception。最稳妥的做法是预定义索引模板在模板里把你的核心字段的类型锁死。模板内容大致如下{ index_patterns: [nginx-access-*], settings: { number_of_shards: 1, number_of_replicas: 0, index.refresh_interval: 5s, index.lifecycle.name: nginx-ilm-policy }, mappings: { properties: { timestamp: { type: date }, client_ip: { type: ip }, status: { type: integer }, request_time: { type: float }, body_bytes_sent: { type: long }, request_method: { type: keyword }, request_uri: { type: keyword }, user_agent: { type: text, fields: { keyword: { type: keyword } } } } } }client_ip设置成ip类型还能支持IP范围查询。user_agent设置成text配合keyword子字段既能全文搜索又能精确统计。5.4 多行日志或者部分字段丢失怎么办nginx的access日志一般是一行一条用file插件逐行读没问题。但如果你的业务还在日志里打了Java堆栈或者JSON里带了换行符一条日志就会被拆成多条后面的解析直接失败。处理办法是用multiline插件在input阶段把连续多行合并成一条事件input { file { path [/var/log/nginx/json_access.log] start_position end codec multiline { pattern ^\{ negate true what previous } } }这个配置的意思是以{开头的行是新事件的开始其他行都合并到前一条。但要注意加了multiline会从整体上降低日志解析吞吐非必要不加。nginx日志比较规整一般不需要。6. 性能优化与后续扩展建议6.1 Logstash吞吐量相关的几个核心参数Logstash的默认配置偏向稳妥但对于日志量大的场景需要调优。调整点在/etc/logstash/config/logstash.ymlpipeline.workers: 4 pipeline.batch.size: 1250 pipeline.batch.delay: 50pipeline.workers控制过滤和输出阶段的并发线程数建议不要超过CPU核数。配置过高会导致ES写入压力过大。batch.size是每个worker一次批量处理的event数量。提高batch能显著提升吞吐但会带来更大的内存消耗。如果数据量不大用默认1250就行。batch.delay是批量等待时间。调低能降低写入延迟调高能提升吞吐。实时性要求不高的场景可以调到100。另外要留意JVM堆内存设置。Logstash默认堆内存1GB日志量大后频繁GC。修改/etc/logstash/jvm.options里的-Xms和-Xmx建议给个4GB以上。内存配置一定不能超过机器物理内存的50%否则ES和Kibana没内存可用整个栈都卡。6.2 什么场景下该上消息队列或FilebeatLogstash直接读文件有一个明显缺点如果ES临时不可用Logstash处理能力下降nginx日志文件持续增长Logstash重启后要追很久的进度期间内存和CPU都会飙升。量小没事量大了就要改架构。推荐的演进路线单机日志量不大Logstash直接读文件维护最简单。服务器超过5台或者单机日志量持续走高每台机器部署FilebeatFilebeat输出到统一的Logstash集群Filebeat内置背压机制不会打爆Logstash。日志量非常大或者有实时计算需求中间加Kafka用Filebeat把日志送进KafkaLogstash从Kafka消费。这样Logstash宕机也不影响数据落盘且可以多个Logstash实例做横向扩展。从投入产出比看大部分团队的nginx日志规模停留在前两个阶段没必要一上来就上Kafka。真到了日增几十GB以上再考虑也不晚。6.3 索引生命周期管理nginx访问日志通常只需保留30到90天。每天一个索引如果一直不清理ES磁盘很快告警。推荐在ES侧配置ILM策略让索引到达一定天数自动删除或者转移到冷节点。模板里已经写了一行index.lifecycle.name那个策略要在Kibana的Stack Management里提前定义好规则简单写就是索引达到30天切换到只读状态。达到60天删除。这样Logstash就只管写入不用关心清理省心很多。7. 一段时间的自用体会我在某公司搭这套东西的时候nginx和Logstash都跑在同一台虚拟机上日志量不算大日增两三个G。最开始图省事直接用默认的combined日志格式grok正则调了半天总有零星日志解析失败而且UA里带特殊字符就能让正则失配。后来咬咬牙改了JSON格式配置一下子清爽很多解析失败率也降下来了。事后复盘改nginx日志格式这点成本远比长期维护一套复杂grok正则划算。如果你已经走到了这一步接下来建议优先做两件事一是把上游响应时间upstream_response_time的字段在Kibana里用起来找出那些nginx响应快但后端拖慢接口的请求二是看下http_x_forwarded_for字段如果前面有LB或者CDN真实客户端IP多半在这别直接用$remote_addr那往往是负载均衡的IP统计来源时容易被误导。日志分析这件事前面百分之八十的工作都花在数据采集和数据治理上真正到了Kibana里画图其实很快。把格式定好、解析写对、时间处理对、字段类型锁死后面做任何看板都不会再来回返工。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑