60 lines
1.5 KiB
Bash
Executable File
60 lines
1.5 KiB
Bash
Executable File
#!/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
|
||
|