You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写Shell脚本读取日志新行并推送到Elasticsearch去重

嘿,针对你的两个问题,我来分享几个在日常运维中实测好用的方案,帮你解决实时日志处理和ES重复推送的问题👇

问题1:实时监控日志新行并执行读写操作

最常用的工具就是tail -f(或者tail -F,支持日志轮转场景),它会持续输出文件的新增内容,结合管道或循环就能实现自定义的读写逻辑。

基础用法:实时输出+备份新行

如果只是简单地把新行同时输出到终端和写入备份文件,用tee就能快速搞定:

# 监控/var/log/app.log的新行,同时输出到终端和追加到app_new_lines.log
tail -F /var/log/app.log | tee -a /var/log/app_new_lines.log

自定义处理逻辑:解析并加工新行

如果需要更复杂的操作(比如过滤关键词、格式化内容、写入数据库等),可以用while read循环来逐行处理:

tail -F /var/log/app.log | while read -r line; do
  # 跳过空行
  [[ -z "$line" ]] && continue

  # 示例1:只处理包含"ERROR"的行,写入错误日志并加上时间戳
  if echo "$line" | grep -q "ERROR"; then
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] $line" >> /var/log/app_errors.log
  fi

  # 示例2:如果是JSON格式日志,格式化后写入另一个文件
  if echo "$line" | jq . >/dev/null 2>&1; then
    echo "$line" | jq '.processed_at = now' >> /var/log/formatted_app.log
  fi
done

问题2:实时推送新日志行到Elasticsearch并避免重复

核心思路是只处理从未推送过的新增行,我们可以通过记录已处理行的唯一标识(比如哈希值),或者追踪文件的读取位置来实现去重。

方案1:tail跟踪+哈希去重(简单易实现)

这个方案用tail -F跟踪日志,通过SHA256哈希值标记已处理的行,避免重复推送:

#!/bin/bash

# 配置参数
LOG_FILE="/path/to/your/target.log"
ES_URL="http://your-es-server:9200/your-log-index/_doc"
# 存储已处理行哈希的临时文件
PROCESSED_HASHES="/tmp/processed_log_hashes.txt"

# 初始化文件
touch "$PROCESSED_HASHES"

# 实时跟踪日志新行(-F支持日志轮转自动切换)
tail -F "$LOG_FILE" --retry | while read -r line; do
  # 跳过空行
  [[ -z "$line" ]] && continue

  # 生成当前行的唯一哈希值
  line_hash=$(echo "$line" | sha256sum | awk '{print $1}')

  # 检查哈希是否已存在,不存在则推送ES
  if ! grep -q "$line_hash" "$PROCESSED_HASHES"; then
    # 推送至Elasticsearch,可根据你的日志格式调整JSON内容
    curl -X POST "$ES_URL" \
      -H "Content-Type: application/json" \
      -d "{
        \"@timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\",
        \"message\": \"$line\",
        \"source\": \"$(basename "$LOG_FILE")\"
      }"

    # 将哈希写入文件,标记为已处理
    echo "$line_hash" >> "$PROCESSED_HASHES"

    # 可选:定期清理哈希文件,避免占用过多磁盘(比如保留最近7天的记录)
    find "$PROCESSED_HASHES" -mtime +7 -exec truncate -s 0 {} \;
  fi
done

方案2:inotifywait监控文件修改(更精准)

如果对实时性要求更高,或者需要处理特殊的日志更新场景,可以用inotifywait监控文件的修改事件,只读取新增的内容:

#!/bin/bash

LOG_FILE="/path/to/your/target.log"
ES_URL="http://your-es-server:9200/your-log-index/_doc"
PROCESSED_HASHES="/tmp/processed_log_hashes.txt"
# 记录上次读取的文件字节位置
LAST_POSITION="/tmp/log_last_position.txt"

# 初始化文件
touch "$PROCESSED_HASHES" "$LAST_POSITION"

# 首次运行时,记录当前文件大小作为起始位置
if [[ ! -s "$LAST_POSITION" ]]; then
  stat -c %s "$LOG_FILE" > "$LAST_POSITION"
fi

# 持续监控文件修改事件
while inotifywait -e modify "$LOG_FILE"; do
  current_size=$(stat -c %s "$LOG_FILE")
  last_size=$(cat "$LAST_POSITION")

  # 读取新增的内容(从上次位置到当前位置的字节)
  if [[ "$current_size" -gt "$last_size" ]]; then
    tail -c +$((last_size + 1)) "$LOG_FILE" | while read -r line; do
      [[ -z "$line" ]] && continue

      line_hash=$(echo "$line" | sha256sum | awk '{print $1}')
      if ! grep -q "$line_hash" "$PROCESSED_HASHES"; then
        # 推送ES
        curl -X POST "$ES_URL" \
          -H "Content-Type: application/json" \
          -d "{
            \"@timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\",
            \"message\": \"$line\"
          }"

        echo "$line_hash" >> "$PROCESSED_HASHES"
      fi
    done

    # 更新最后读取的位置
    echo "$current_size" > "$LAST_POSITION"
  fi
done

注意事项

  • 日志轮转处理:tail -F会自动识别日志轮转后的新文件(比如logrotate生成的新文件),而inotifywait需要额外处理文件替换的情况(可以监控move_self事件)。
  • 去重逻辑调整:如果你的日志行本身包含唯一ID(比如request_id),可以直接用这个ID代替哈希值,效率更高。
  • 推送重试:如果ES偶尔不可用,可以给curl加--retry 3参数实现自动重试。
  • 性能优化:如果日志量极大,while read循环可能会有瓶颈,可以考虑用awk来批量处理行,提升效率。

内容的提问来源于stack exchange,提问作者sai kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 19:47:29