第十三章 —— WordPress安全实战指南 · 日志监控

📌 第五部分:日志监控

🧭 日志不是“翻”,是“查”——Grafana 查询与告警的优势

在传统的服务器运维中,排查问题往往依赖于 tail -fgrepless 等命令手动翻日志,面对成千上万行内容,不仅耗时费力,还容易遗漏关键线索。而在现代网站安全和系统监控实践中,我们需要的,不只是“看日志”,而是“结构化地查询日志、自动识别风险、及时通知响应”。

这正是 Grafana + Loki 架构带来的巨大价值。


🔍 为什么选择 Grafana 查询日志?
  • 更快定位问题:可视化时间筛选、关键词过滤、状态码聚合,让你几秒钟查到错误发生的时间、请求路径、IP 源头;
  • 结构化查询语法(LogQL):像写数据库一样写日志查询,支持统计、分组、条件过滤;
  • 支持图表与表格展示:让趋势一目了然,比纯文本 grep 更直观;
  • 标签索引机制:比传统全文检索更轻量,速度更快,资源占用低,非常适合中小型网站。

📣 为什么要配置告警系统?

日志查询虽然强大,但它仍然是事后分析;而告警系统的意义在于:

  • 第一时间发现异常:如暴力破解、SQL 注入、站点宕机等,可在发生几秒内发出告警;
  • 避免人为盲点:即便管理员没在值班,邮件、Webhook、钉钉等通知也能自动到达;
  • 配合安全响应机制:比如搭配 fail2ban 自动封禁攻击 IP,形成闭环防护;
  • 满足合规要求:很多系统安全规范要求具备主动告警能力,特别是在金融、电商等行业。

🛡️ 日志 + 告警,是安全监控的“左眼与右眼”

部署好日志收集系统只是第一步。只有配合 Grafana 强大的查询功能与灵活的告警系统,才能构建一个真正具备主动防御能力的日志平台。它不仅能帮你在问题发生后迅速追溯,还能在问题刚露苗头时提前预警。

在接下来的章节中,我们将通过实际配置和示例,带你一步步搭建一个既能查询、又能告警的日志监控系统。

安装Grafana

参考Alloy安装流程在本机上配置Grafana官方yum仓库,安装Grafana并启用

$ sudo dnf install grafana
$ sudo systemctl enable --now grafana-server

添加数据源

Grafana默认会在本机的3000的端口监听,用浏览器访问localhost:3000,进入首页后,点击菜单 → Connections → Datasource

在添加数据源页面中,配置如下:

  • 添加新数据源,选择Loki
  • URL:https://example.com:8443
  • Authentication选择Basic Authentication
  • 输入htpasswd的用户名和密码
  • 点击Save & Test尝试连接。

你会发现尝试连接失败了,我们来看看服务器上的/var/log/nginx/access.log和error.log。

$ sudo tail -f /var/log/nginx/access.log
$ sudo tail -f /var/log/nginx/error.log

一条相关的记录都没有,也就是说请求没有出本机,再看看本机的audit日志

$ sudo sealert -a /var/log/audit/audit.log

发现被SELinux拦截了,其根本原因是

在 SELinux 处于启用并严格模式(Enforcing)时,默认策略中:

Grafana(其进程类型为 grafana_t)默认是不被允许连接非特权端口(unreserved_port_t)的。

那我们就遵循日志的建议,为Grafana生成策略模块并加载

$ sudo ausearch -c 'grafana' --raw | audit2allow -M my-grafana
$ sudo semodule -X 300 -i my-grafana.pp
$ sudo systemctl restart grafana-server.service

如果你好奇的看了看生成的规则文件,你会发现

require {
        type unreserved_port_t;
        type pki_ca_port_t;
        type websm_port_t;
        type grafana_t;
        class tcp_socket name_connect;
}

这些规则的含义简单的说就是

允许 Grafana 发起 TCP 连接到多个指定类型的端口,以满足其访问外部服务或 API 的需求。

最后再尝试添加数据源并测试连接。

日志查询

Loki 是专为日志设计的“时间序列索引系统”,搭配 Grafana 后,你可以像查询数据库一样快速定位日志中的错误、访问请求、攻击行为等关键事件。下面是一个典型的日志查询流程,以 /var/log/nginx/access.log 为例进行演示。


✅ 一、确认日志是否已被收集

确保你的 Alloy 配置中已包含如下路径:

path_targets = [{ "__path__" = "/var/log/nginx/access.log" }]

Loki 接收到了该日志后,才可以在 Grafana 中查询。


✅ 二、打开日志查询界面
  1. 登录 Grafana;
  2. 进入左侧菜单 → Explore(探索);
  3. 在左上角选择数据源为 Loki

✅ 三、选择日志标签(label)

Grafana默认使用Builder方式来构造查询,点击labe filter会看到如下的标签(和配置有关),

标签名说明
job日志采集任务名(如 nginx)
filename日志文件路径
host来源主机名(如果配置了)

在下拉菜单中选择filename,右侧的下拉菜单中选择/var/log/nginx/access.log,然后将下面的Operations输入框删除,你会注意到Grafana生成了这样一条查询语句

{filename="/var/log/nginx/access.log"}

这将返回该日志文件中最近的日志条目。


✅ 四、添加关键词过滤

例如,你想查找所有访问了 /login 的请求:
点击Operations,找到并添加Line Contains操作符,输入/login,生成如下查询语句

{filename="/var/log/nginx/access.log"} |= "/login"

如果你想排除静态文件请求:
那么添加Line does not contain操作符,输入.css,生成如下查询语句

{filename="/var/log/nginx/access.log"} != ".css"

✅ 五、指定时间范围
  • 在上方时间选择器中选择「最近 1 小时」、「自定义时间」等;
  • Grafana 会自动将查询范围限制在你选定的时间窗口内。

✅ 六、统计某类日志数量(分析型查询)

比如在过去3小时中,按照一定的采样间隔,统计采样点前 5 分钟出现多少次 /admin 访问:
这里我们需要添加两个操作符,分别是Line contains和Count over time,在Line contains中输入/admin,在Count over time中选择5m
通过Range查询,我们能看到一段时间内匹配到的日志及图表

count_over_time({filename="/var/log/nginx/access.log"} |= "/admin" [5m])

还可以用 rate()sum()avg_over_time() 等函数做更复杂的图表分析。


✅ 七、组合筛选与高级语法

多个条件组合:
这里的表达式留给大家思考,不再细讲

{filename="/var/log/nginx/access.log"} |= "/login" != "404"

📌 示例:排查异常登录请求
{filename="/var/log/nginx/access.log"} |= "POST" |= "/login"

这条查询能帮助你快速找到所有登录行为,配合 IP、状态码等字段,你可以进一步分析是否存在暴力破解。

配置邮件通知通道

为了第一时间获知系统异常(如登录暴力、接口异常、服务器宕机等),我们可以在 Grafana 中设置邮件告警。Grafana 支持通过 SMTP 发送告警邮件,并结合 Loki、Prometheus 等数据源创建灵活的告警规则。


✅ 一、配置邮件通知通道(SMTP)
  1. 打开 Grafana 配置文件(路径通常为 /etc/grafana/grafana.ini);
  2. 找到 [smtp] 部分,修改如下内容:
$ sudo vim /etc/grafana/grafana.ini

#调整smtp的配置
[smtp]
enabled = true #允许Grafana发送邮件
host = mail.example.com:587 #邮件服务器的主机名和端口
user = saslusername #如果用starttls做身份验证,那这里输入sasl用户名
password = saslpassword #sasl用户的密码
skip_verify = false #验证邮件服务器的证书
from_address = grafana@example.com #发件人的地址
from_name = Grafana #发件人的名字
startTLS_policy = MandatoryStartTLS #强制starttls
  1. 重启 Grafana 以生效:
sudo systemctl restart grafana-server

✅ 二、创建邮件通知通道
  1. 登录 Grafana Web 页面;
  2. 进入「Alerting → Contact points」;
  3. 点击「New contact point」,选择类型为 Email;
  4. 填入邮件地址、名字,点击Test,看是否能收到测试邮件
  5. 保存配置。

💡你也可以配置多个收件人,或选择多个通知方式(如钉钉、Webhook、Slack等)。

创建Notification Policies

在 Grafana 的告警系统中,Notification Policy 是控制“如何、何时、向谁发送告警通知”的核心组件。它不仅定义了告警通知的通道,还负责对不同类型的告警进行分类、分组与发送策略控制。

🚧 为什么要配置通知策略?

默认情况下,所有告警都会落入一条叫做 Default policy 的默认策略中。虽然默认策略能保证告警不丢,但缺乏灵活性。通过配置 Notification Policy,我们可以实现:

  • 不同服务的告警发往不同团队;
  • 严重级别不同的告警采用不同的通知方式;
  • 控制告警发送频率,防止邮件轰炸;
  • 支持“恢复通知”,让用户知道问题已解决。

🧭 树状结构:必须嵌套

Grafana 的通知策略采用树状嵌套结构,所有自定义策略必须作为 Default Policy 的“子策略”存在。告警在触发后会从 Default 开始,自上而下、深度优先匹配,第一个匹配成功的策略将被使用。


🔧 配置关键字段解释
参数说明示例建议值
Matching labels设置哪些标签的告警匹配该策略service = nginx, severity = critical
Grouped by决定将哪些标签相同的告警归为一组统一发送alertname, grafana_folder
Group wait等待多久发送首次通知(用于聚合)30s:等待更多告警一起发
Group interval同组中后续新告警的最小通知间隔5m:避免每条都立即发
Repeat interval对同一告警未恢复时多久重复一次通知4h:降低骚扰频率
Continue matching subsequent sibling nodes是否继续匹配“下一个兄弟策略”默认不开启

✉️ 实战建议

📌 一条好的策略应该“聚而不滥”,即合理分组、避免重复,又不遗漏任何严重告警。


🔧 配置步骤
  1. 我们将 label 设置成 nginx = modsecurity
  2. Contact point 选择之前配置的邮件通知通道
  3. 其余保持默认,继承default policy的设置
  4. 点击 Update policy

配置告警规则前需要了解的

🧠 1. 什么样的数据才能用于告警规则?

Grafana 告警规则必须基于可量化的数值来判断是否“超过阈值”或“异常变化”。

日志数据本质是文本,但 Loki 提供了函数将其“转换成数值”:

  • count_over_time(...) → 统计条数
  • rate(...) → 日志增长速率
  • avg_over_time(...) → 日志值的平均(用于结构化日志)

文本本身不可直接用于告警,必须经过这些表达式“数值化”。


🔄 2. Range 与 Instant 查询的区别
项目Range 查询Instant 查询
查询类型时间序列(多个点)当前时刻(单个点)
Grafana用法搭配 Expressions 后使用可直接用于判断
典型用途告警规则、趋势图、统计分析实时监测、低延迟告警
数据返回每个时间点的数据(图表)当前一刻的值

🧊 3. 什么是“活动窗口”(evaluation window)?

活动窗口(evaluation window)是 LogQL 表达式中像 [5m] 这样的部分,表示:

每次评估时,从当前时刻向前推 N 分钟的日志范围内做统计。

例如:

count_over_time({...} |= "error" [10m])

如果你用Instant方式查询则表示:

取出“当前时刻往回 10 分钟”的日志片段,统计出现 “error” 的次数。

如果你用Range方式查询则表示:

取出一段时间的日志,按照一个固定的采样间隔(step)生成采样点,在每个点上取出“往回10分钟“的日志片段,统计出现 “error” 的次数。


🕒 4. 什么是“评估间隔”(Evaluation interval)?

这指的是告警规则每隔多长时间执行一次,在配置中叫:

Evaluate every: 1m (或 30s、5m 等)

它决定了告警检测的频率与实时性。


📌 5. 什么是“采样间隔下限”(Min interval)?

Min interval 是查询的最小采样间隔,用于控制 Grafana 在时间范围内查询数据时的粒度下限。

在Range查询中,Grafana会自动计算一个step,并按照step生成采样点。假设你要查询1小时的日志抓其中的关键内容,grafana为你计算的step是20s,那么将生成180个采样点,在每个采样点上进行日志查询。

min interval用来控制step的最低值,默认是10s,即step最低不能低于10s。

设置 min interval 的主要作用是:

  • 限制高频查询,避免资源开销过大;
  • 保证图表粒度不会过细,适用于性能优先场景;
  • 对高并发 Loki、Prometheus 等数据源尤其重要。

Grafana官方建议min interval的值应该与日志写入的频率一致。

配置告警规则

✅ 目标:

如果在过去 10 分钟内,/var/log/nginx/error.log 中出现了 3 次含有 ModSecurity 的日志,就立即发邮件通知 admin@example.com


🛠 环境前提
  • Grafana 已连接 Loki 数据源;
  • Loki 已采集 error.log
  • 邮件通知通道(SMTP)已配置完成并测试通过。
  • 已配置Notification Policy。

📐 创建告警规则

在 Grafana → Alert → New Alert Rule


① Rule Name

Nginx-ModSecurity-Alert #这个名称会作为alertname标签的值,该标签用于告警分组

② Define query and alert condition

选择查询类型:✅ Instant

表达式:

count_over_time({filename="/var/log/nginx/error.log"} |= "ModSecurity" [10m])

含义:每次评估时,实时计算当前时间点向前 10 分钟内出现 “ModSecurity” 的日志条数。

Add Expressions 添加 Threshold(阈值):

  • 条件:IS ABOVE
  • 值:2

意味着:如果最近 10 分钟内有 3 条或以上拦截日志,就触发告警。


③ Set evaluation behavior

  • 添加一个用于存放规则的目录,这个目录的名称会作为grafana_folder标签的值,该标签用于告警分组
  • 创建一个评估组,设置间隔为1m,即每分钟执行一次告警规则
  • Pending period设置为0s
  • 设置“Alert state if no data or all values are null“为OK

Pending Period: 当告警条件首次满足时,不立即触发告警,而是等待一段“观察期”,如果这段时间内条件持续成立,才真正触发告警。

Alert state if no data or all values are null:如果查询结果为空、没有任何数据点,或者所有数据点都是 null,Grafana 应该怎么做?(对于一个非趋势型的日志来说,采集不到任何数据是正常现象。但对于一个趋势型的日志来说,采集不到数据说明服务或硬件故障)


④ Add annotations(可选)

summary: "Nginx 检测到多次 WAF 拦截"
description: "在 10 分钟内发生了至少 3 次 ModSecurity 拦截行为。请检查是否有攻击行为或误判。"

⑤ Labels and notifications

  • Labels输入nginx = modsecurity,与Notification Policy的标签一致
  • 点击Preview routing,检查是否匹配到了标签为 nginx = modsecurity 的Notification policy

🧪 测试告警效果

在文章评论区中输入</script>,触发ModSecurity拦截。
连续三次后,Grafana 在下一次评估(1 分钟内)应触发邮件告警。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注