60 lines
1.5 KiB
Bash
60 lines
1.5 KiB
Bash
|
|
#!/usr/bin/env bash
|
|||
|
|
# Keep a log file capped by trimming from the front.
|
|||
|
|
# Usage:
|
|||
|
|
# ./log-trim-daemon.sh --file /path/to/app.log --max-mb 5 --interval-sec 3
|
|||
|
|
set -euo pipefail
|
|||
|
|
|
|||
|
|
FILE=""
|
|||
|
|
MAX_MB=5
|
|||
|
|
INTERVAL_SEC=3
|
|||
|
|
|
|||
|
|
while [ $# -gt 0 ]; do
|
|||
|
|
case "$1" in
|
|||
|
|
--file) FILE="${2:-}"; shift 2 ;;
|
|||
|
|
--max-mb) MAX_MB="${2:-}"; shift 2 ;;
|
|||
|
|
--interval-sec) INTERVAL_SEC="${2:-}"; shift 2 ;;
|
|||
|
|
-h|--help)
|
|||
|
|
echo "Usage: $0 --file PATH [--max-mb 5] [--interval-sec 3]"
|
|||
|
|
exit 0
|
|||
|
|
;;
|
|||
|
|
*)
|
|||
|
|
echo "Unknown arg: $1" >&2
|
|||
|
|
exit 2
|
|||
|
|
;;
|
|||
|
|
esac
|
|||
|
|
done
|
|||
|
|
|
|||
|
|
if [ -z "${FILE}" ]; then
|
|||
|
|
echo "Missing --file" >&2
|
|||
|
|
exit 2
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
MAX_BYTES=$((MAX_MB * 1024 * 1024))
|
|||
|
|
TMP="${FILE}.trimtmp.$$"
|
|||
|
|
LOCK="${FILE}.trimlock"
|
|||
|
|
|
|||
|
|
mkdir -p "$(dirname "$FILE")"
|
|||
|
|
touch "$FILE" 2>/dev/null || true
|
|||
|
|
|
|||
|
|
while true; do
|
|||
|
|
if [ -f "$FILE" ]; then
|
|||
|
|
size=$(stat -c%s "$FILE" 2>/dev/null || echo 0)
|
|||
|
|
if [ "${size:-0}" -gt "$MAX_BYTES" ]; then
|
|||
|
|
# Keep only the last MAX_BYTES bytes.
|
|||
|
|
# IMPORTANT: do NOT replace the file inode (mv),否则 tail -f/编辑器可能还在看旧 inode,
|
|||
|
|
# 会出现“日志不更新/看不到最新内容”的错觉。这里用 copy+truncate 保持 inode 不变。
|
|||
|
|
(
|
|||
|
|
flock -w 2 9 || exit 0
|
|||
|
|
tail -c "$MAX_BYTES" "$FILE" > "$TMP" 2>/dev/null || true
|
|||
|
|
# 覆盖写回同一文件(inode 不变)
|
|||
|
|
if [ -s "$TMP" ]; then
|
|||
|
|
cat "$TMP" > "$FILE" 2>/dev/null || true
|
|||
|
|
fi
|
|||
|
|
rm -f "$TMP" 2>/dev/null || true
|
|||
|
|
) 9>"$LOCK" || true
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
sleep "$INTERVAL_SEC"
|
|||
|
|
done
|
|||
|
|
|