当前位置: 首页 > news >正文

实战:将 Nginx 日志实时解析并写入 MySQL,不再依赖 ELK

最近在做系统监控和日志分析时,遇到一个很现实的问题:我们不想引入 ELK(Elasticsearch + Logstash + Kibana)这么重的架构,但又需要把 Nginx 的访问日志结构化存入数据库,用于后续的业务分析、异常追踪或安全审计。

于是,我决定用一个轻量级方案:用 Bash 脚本实时解析 Nginx 日志,并直接写入 MySQL。整个过程踩了一些坑,也积累了一些经验,今天就来分享一下这个“小而美”的实现思路。


一、Nginx 日志格式定制

首先,得确保 Nginx 输出的日志格式是我们可控的。默认的 combined 格式虽然通用,但缺少一些关键字段,比如后端响应时间、服务端口等。

我在 nginx.conf 中自定义了一个 main 格式:

log_format main '$remote_addr - $remote_user $server_port [$time_local] "$request" ''$status $request_time $body_bytes_sent "$http_referer" ''"$http_user_agent" "$http_x_forwarded_for"';access_log /data/log/access.log main;
error_log /data/log/error.log error;

注意这里我用了 $request_time 而不是 $upstream_response_time,因为业务场景中更关心整个请求的耗时(包括网络、排队等),而不仅仅是后端处理时间。


二、日志样例与字段拆解

来看一条真实的日志:

172.33.45.11 - - 443 [21/Oct/2025:10:13:59 +0800] "POST /gatewayproxy/api HTTP/1.1" 200 0.253 489 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36 Hutool" "-"

对应字段如下:

字段
$remote_addr172.33.45.11
$remote_user-(未认证)
$server_port443
$time_local21/Oct/2025:10:13:59 +0800
$requestPOST /gatewayproxy/api HTTP/1.1
$status200
$request_time0.253(秒)
$body_bytes_sent489
$http_referer-
$http_user_agentMozilla/5.0 ... Hutool
$http_x_forwarded_for-

这里有个细节:$request 是一个复合字段,包含方法、URL 和协议,后续需要拆解。


三、Bash 脚本:实时解析 + 写入 MySQL

1. 数据库表结构

先建好表(MySQL 8.0+):

CREATE TABLE nginx_log (id BIGINT AUTO_INCREMENT PRIMARY KEY,remote_addr VARCHAR(45),remote_user VARCHAR(100),server_port INT,time_local DATETIME,request TEXT,request_method VARCHAR(10),request_url VARCHAR(2000),request_protocol VARCHAR(20),status INT,request_time DECIMAL(6,3),body_bytes_sent INT,http_referer TEXT,http_user_agent TEXT,http_x_forwarded_for TEXT,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

注意:time_local 字段我用的是 DATETIME,但原始日志是 [21/Oct/2025:10:13:59 +0800] 格式,需要在插入前转换。不过为了简化脚本,我先原样存为字符串,后续用 SQL 或应用层处理时间转换(也可以在 Bash 中用 date -d 转换,但会增加复杂度)。

2. 解析脚本核心逻辑

脚本使用 tail -F 实时监听日志文件,通过正则匹配提取字段:

#!/bin/bashLOG_FILE="/data/log/access.log"
DB_HOST="127.0.0.1"
DB_USER="log_push"
DB_PASS="log_push@123"
DB_NAME="log"
TABLE_NAME="nginx_log"escape() {echo "${1//\'/\'\'}"
}tail -F "$LOG_FILE" | while read -r line; doif [[ $line =~ ^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\ \-\ ([^\ ]*)\ ([0-9]+)\ \[([^\]]+)\]\ \"([^\"]+)\"\ ([0-9]+)\ ([0-9\.]+)\ ([0-9\-]+)\ \"([^\"]*)\"\ \"([^\"]+)\"\ \"([^\"]*)\"$ ]]; then# 提取字段remote_addr="${BASH_REMATCH[1]}"remote_user="${BASH_REMATCH[2]}"server_port="${BASH_REMATCH[3]}"time_local="${BASH_REMATCH[4]}"request="${BASH_REMATCH[5]}"status="${BASH_REMATCH[6]}"request_time="${BASH_REMATCH[7]}"body_bytes_sent="${BASH_REMATCH[8]}"http_referer="${BASH_REMATCH[9]}"http_user_agent="${BASH_REMATCH[10]}"http_x_forwarded_for="${BASH_REMATCH[11]}"# 拆解 requestrequest_arr=($request)if [ ${#request_arr[@]} -eq 3 ]; thenrequest_method="${request_arr[0]}"request_url="${request_arr[1]}"request_protocol="${request_arr[2]}"elserequest_method=""request_url=""request_protocol=""fi# 构造 SQL(注意转义单引号)SQL="INSERT INTO $TABLE_NAME (...) VALUES (...);"mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" -D "$DB_NAME" -e "$SQL"elseecho "Failed to parse: $line" >> /var/log/nginx_log_parser.errfi
done

3. 关键点说明

  • 正则表达式必须严格匹配:Nginx 日志中如果有换行或特殊字符(比如 User-Agent 含引号),会导致解析失败。生产环境建议先做日志清洗。
  • 单引号转义escape() 函数简单处理了 SQL 注入风险(虽然 User-Agent 一般不会恶意,但安全起见)。
  • 性能考量:每行日志都调用一次 mysql 命令,高频场景下会有性能瓶颈。如果 QPS > 100,建议改用批量插入(比如缓存 100 行再批量写)或换 Python/Go 实现。
  • 时间格式问题:如前所述,[21/Oct/2025:10:13:59 +0800] 不能直接插入 DATETIME。如果必须转,可以用:
    mysql_time=$(date -d "${time_local//\// }" +"%Y-%m-%d %H:%M:%S")
    
    但要注意时区一致性。

四、部署与守护

脚本写好后,用 systemdsupervisor 守护起来:

# /etc/systemd/system/nginx-log-parser.service
[Unit]
Description=Nginx Log Parser to MySQL
After=network.target[Service]
Type=simple
User=root
ExecStart=/bin/bash /opt/scripts/nginx_log_to_mysql.sh
Restart=always
RestartSec=5[Install]
WantedBy=multi-user.target

然后:

systemctl daemon-reload
systemctl start nginx-log-parser
systemctl enable nginx-log-parser

五、为什么不直接用 Filebeat + Logstash?

  • 轻量:我们的日志量不大(日均百万级),没必要上 ELK。
  • 可控:自己写脚本,字段解析逻辑完全掌握,调试方便。
  • 成本低:省去了维护 Elasticsearch 集群的资源和人力。

当然,如果未来日志量暴涨或需要全文检索,再迁移到 ELK 也不迟。


六、总结

这个方案虽然“土”,但在中小项目中非常实用。它不依赖复杂中间件,开发成本低,且能快速满足业务对结构化日志的需求。

技术选型没有银弹,适合的才是最好的。

如果你也在寻找一个轻量级的日志入库方案,不妨试试这个 Bash 脚本。代码虽糙,但能跑就行 😄


http://www.dtcms.com/a/528849.html

相关文章:

  • Redis 黑马点评day02 商户查询缓存
  • 品牌网站建设切入点wordpress很好的博客
  • ASP.NET Core读取Excel文件
  • 器材管理网站开发沈阳网站建设费用
  • 巧用 CSS linear-gradient 实现多种下划线文字特效(纯 CSS 无需额外标签)
  • 地州电视网站建设流程网址域名大全
  • 计算机网络自顶向下方法 1——因特网的介绍及构成 介绍协议
  • 学习笔记|受限波尔兹曼机(RBM)
  • DiVE长尾识别的虚拟实例蒸馏方法
  • 视频网站很难建设吗珠海网站运营
  • h5游戏免费下载:废柴勇士
  • 简单的企业网站源码网站建设业务
  • 基于鸿蒙 UniProton 的汽车电子系统开发指南
  • 建设部质监局网站电子商务网站策划书2000字
  • 使用表达式树实现字符串形式的表达式访问对象属性
  • SFT(有监督微调)、RLHF(强化学习)、RAG(检索增强⽣成)
  • 网页设计模板图片代码seo岗位职责
  • wordpress开发网站html如何建网站
  • 深度学习核心模型详解:CNN与RNN
  • 哈尔滨整站如何做网站流量买卖
  • 智能制造知识图谱的建设路线
  • IPIDEA实现数据采集自动化:高效自动化采集方案
  • 网站开发认证考试wordpress目录 读写权限设置
  • 【51单片机】【protues仿真】基于51单片机热敏电阻数字温度计数码管系统
  • Java基础与集合小压八股
  • 网站建设做网站需要多少钱?杭州网站建设公司有哪些
  • [ Redis ] SpringBoot集成使用Redis(补充)
  • GitHub等平台形成的开源文化正在重塑伊朗人
  • 贵州省建设厅网站造价工程信息网东港建站公司
  • UE5 蓝图-17:主 mainUI 界面蓝图,构成与尺寸分析;界面菜单栏里按钮 Ul_menuButtonsUl 蓝图的构成记录,