Crontab + Shell 实现日志监听

概述

需要将服务器日志利用 ELK 可视化

前提

  • 因为客观条件没有办法直接修改服务端代码
  • 应用服务器也无法随意安装软件
  • 服务器的日志是文件形式输出的
  • 日志服务器部署在内网的机器上

解决思路

使用的工具

ELK 是 (Elasticsearch + Logstash + Kibana) 的简称,指的是利用 Logstash收集日志,输出到Elasticsearch进行数据处理,再利用Kibana界面对日志进行可视化

Logstash 利用插件,可以支持非常多的输入方式,原生支持且较常用的有:

  • File : 最常用的输入方式,监听本地日志文件作为数据源

  • TCP : 监听发送到端口的数据

  • HTTP : 监听通过HTTP协议POST到端口的数据

问题分析

  1. 应用服务器不好安装Logstash实例(占用资源),所以无法使用本地Logstash实例使用 File 方式监听本地日志文件

  2. 由于日志服务与应用服务器不在同一台服务器,且部署在内网服务器上,所以File输入无法实现

  3. 由于无法直接修改服务端代码,所以只能利用现有的日志文件,所以TCP输入方式无法使用

鉴于以上原因,目前想到的方案就是利用HTTP请求,将服务器日志按批发送到日志服务器,由于日志服务器在内网,所以要实现这个方案,还需要在内网Logstash监听端口打个洞(内网穿透)

解决方案

有了思路,说干就干!

  1. 首先,先在内网服务器打个洞🕳️,可以利用npc+nps方案或者frpc+frps方案,具体教程很多,在这里旧不再赘述了

  2. 假设第一步已经完成:内网Logstash服务监听到端口号为 9566 ,利用nps映射到公网域名http://your-domain

  3. 完成前面两步之后,就可以编写脚本实现定时监听(滚动)日志文件并发送到日志服务器了

编写脚本

脚本 log_monitor 文件内容如下:

#!/bin/bash

# 日志文件路径
ROOT=/pato/to
LOG_FILE="$ROOT/test.log"
# 发送日志的远程Logstash服务器地址
LOGSTASH_URL="http://your-domain"
# 存储上次读取位置的文件
POSITION_FILE="$ROOT/posfile"
# 目前日志文件的大小
LOG_SIZE=$(wc -c < $LOG_FILE)
# 临时文件保存新增的日志行
TEMP_FILE=$(mktemp)
# 一次发送的行数
BATCH_SIZE=100

# 确保位置文件存在
if [ ! -f $POSITION_FILE ]; then
echo "0" > $POSITION_FILE # 如果位置文件不存在,创建并初始化为 0
fi

# 获取上次读取的位置
POSITION=$(cat $POSITION_FILE)

# 判断读取的位置是否合法
if [ $POSITION -gt $LOG_SIZE ]; then
echo "上次读取的位置 $POSITION 大于日志文件大小 $LOG_SIZE,将重新读取日志文件"
POSITION=0
echo "0" > $POSITION_FILE
fi

# 读取从上次位置到文件末尾的所有新日志行
tail -c +$((POSITION + 1)) $LOG_FILE > $TEMP_FILE

# 更新位置文件
echo $(wc -c < $LOG_FILE) > $POSITION_FILE

# 初始化缓冲区和计数器
BUFFER=()
LINE_COUNT=0

# 遍历新日志行并累积到缓冲区
while IFS= read -r line; do
BUFFER+=("$line")
((LINE_COUNT++))

# 当缓冲区中的行数达到 BATCH_SIZE 时发送一次
if [ $LINE_COUNT -ge $BATCH_SIZE ]; then
# 将数组元素连接成一个JSON数组字符串
BUFFER_STR=$(printf ",%s" "${BUFFER[@]}")
BUFFER_STR="[${BUFFER_STR:1}]"
curl -X POST -H "Content-Type: application/json" --data-raw "$BUFFER_STR" $LOGSTASH_URL
BUFFER=()
LINE_COUNT=0
fi
done < $TEMP_FILE

# 发送剩余的日志行(如果有)
if [ $LINE_COUNT -gt 0 ]; then
# 将数组元素连接成一个JSON数组字符串
BUFFER_STR=$(printf ",%s" "${BUFFER[@]}")
BUFFER_STR="[${BUFFER_STR:1}]"
curl -X POST -H "Content-Type: application/json" --data-raw "$BUFFER_STR" $LOGSTASH_URL
fi

# 删除临时文件
rm -f $TEMP_FILE

注意:记得执行chmod +x log_monitor给脚本添加可执行权限,并且脚本用到了数组,需要使用bash shell才能正常运行

crontab

编写完脚本后,还需要编写执行计划,执行 crontab -e,输入以下指令:

*/5 * * * * /path/to/log_monitor > /var/log/cron-log-monitor.log 2>&1

每间隔五分钟运行一次脚本,将执行记录输出到指定日志文件

总结

在这个方案中,实现了一个 shell 脚本,用于定期将日志文件中的新日志行发送到远程 Logstash 服务器:

  • 不遗漏和重复处理:通过记录和读取日志文件的位置,确保不会遗漏或重复处理日志行。

  • 批量发送:将日志行按批次发送,减少网络请求次数,提高效率。

  • 简单可靠:使用标准的 bash 和常见的命令行工具,无需额外依赖复杂的软件。

致此,完结撒花🎉