Enhance dovecot-logreport script: add progress display, improve sudo handling for unreadable log files, and update usage instructions
This commit is contained in:
+86
-42
@@ -26,7 +26,7 @@ set -euo pipefail
|
||||
# * --oneline: 1 Zeile TSV (kein Leerzeilentrenner)
|
||||
# * --stats: Statistik statt Events (inkl. Matrix/Toplisten)
|
||||
# - Live-Modus:
|
||||
# * --follow: tail -F (gz-Dateien werden dafür übersprungen)
|
||||
# * --follow: tail -F, sudo nur falls nötig (gz-Dateien werden dafür übersprungen)
|
||||
#
|
||||
# Anforderungen/Annahmen:
|
||||
# - Timestamp steht als erstes Feld in ISO-Format (z.B. 2026-01-17T12:34:56+01:00)
|
||||
@@ -53,6 +53,10 @@ FOLLOW=0
|
||||
ALL=0
|
||||
ONELINE=0
|
||||
STATS=0
|
||||
SHOW_PROGRESS=0
|
||||
if [[ -t 2 ]]; then
|
||||
SHOW_PROGRESS=1
|
||||
fi
|
||||
|
||||
# Service-/Event-Filter (OR-verknüpft, wenn mehrere gesetzt sind)
|
||||
ONLY_LOGIN=0
|
||||
@@ -111,7 +115,7 @@ INPUT OPTIONS
|
||||
You can combine --all and --file to add extra files.
|
||||
|
||||
--follow
|
||||
Follow the active logfile(s) (tail -F).
|
||||
Follow the active logfile(s) (tail -F; sudo only if needed).
|
||||
.gz files cannot be followed and will be skipped in follow mode.
|
||||
|
||||
TIME FILTERS (ISO8601 recommended)
|
||||
@@ -336,6 +340,24 @@ if [[ $FOLLOW -eq 1 ]]; then
|
||||
[[ ${#FOLLOW_FILES[@]} -gt 0 ]] || die "--follow requested, but only .gz files selected. Add an active log with --file."
|
||||
fi
|
||||
|
||||
NEED_SUDO=0
|
||||
declare -a UNREADABLE_FILES=()
|
||||
for f in "${LOGFILES[@]}"; do
|
||||
if ! { : < "$f"; } 2>/dev/null; then
|
||||
NEED_SUDO=1
|
||||
UNREADABLE_FILES+=("$f")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $NEED_SUDO -eq 1 ]]; then
|
||||
{
|
||||
echo "Using sudo because these selected log file(s) cannot be opened for reading by $(id -un):"
|
||||
echo "Current groups: $(id -nG)"
|
||||
printf " %s\n" "${UNREADABLE_FILES[@]}"
|
||||
} >&2
|
||||
sudo -v || die "sudo authentication failed; cannot read Dovecot log files."
|
||||
fi
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# POSIX-AWK Parser
|
||||
# - kompatibel zu mawk/nawk/busybox awk
|
||||
@@ -388,7 +410,7 @@ function extract_type(line, arr, t) {
|
||||
# Tries to extract a user/mailbox from common patterns.
|
||||
function extract_user(line, arr, u) {
|
||||
if (match(line, /user=<[^>]+>/, arr)) {
|
||||
return substr(arr[0], 7, length(arr[0])-7-1)
|
||||
return substr(arr[0], 7, length(arr[0])-7)
|
||||
}
|
||||
|
||||
if (match(line, /: (imap|pop3|lmtp)\([^)]*\)/, arr)) {
|
||||
@@ -468,42 +490,49 @@ function type_regex_match(type, typere) {
|
||||
return 0
|
||||
}
|
||||
|
||||
# ---- STATS helper: sort and print top N ----
|
||||
function print_top_sorted(title, arr, n, k,i,j,keys,tmp) {
|
||||
# ---- progress spinner on stderr ----
|
||||
function progress_tick( now, ch) {
|
||||
if (show_progress != 1) return
|
||||
|
||||
now = systime()
|
||||
if (PROGRESS_LAST != "" && now - PROGRESS_LAST < 2) return
|
||||
|
||||
PROGRESS_LAST = now
|
||||
PROGRESS_IDX = (PROGRESS_IDX % 4) + 1
|
||||
ch = substr("|/-\\", PROGRESS_IDX, 1)
|
||||
printf "\r%s", ch > "/dev/stderr"
|
||||
fflush("/dev/stderr")
|
||||
PROGRESS_DRAWN = 1
|
||||
}
|
||||
|
||||
function progress_clear() {
|
||||
if (show_progress != 1 || PROGRESS_DRAWN != 1) return
|
||||
printf "\r \r" > "/dev/stderr"
|
||||
fflush("/dev/stderr")
|
||||
PROGRESS_DRAWN = 0
|
||||
}
|
||||
|
||||
# ---- STATS helper: keep only Top N while scanning the array ----
|
||||
function print_top_sorted(title, arr, n, k,i,pos,count,topk,topv) {
|
||||
print title
|
||||
i=0
|
||||
for (k in arr) { i++; keys[i]=k }
|
||||
|
||||
for (i=1; i<=length(keys); i++) {
|
||||
for (j=i+1; j<=length(keys); j++) {
|
||||
if (arr[keys[j]] > arr[keys[i]]) {
|
||||
tmp=keys[i]; keys[i]=keys[j]; keys[j]=tmp
|
||||
}
|
||||
count=0
|
||||
for (k in arr) {
|
||||
pos=1
|
||||
while (pos<=count && arr[k] <= topv[pos]) pos++
|
||||
if (pos > n) continue
|
||||
|
||||
if (count < n) count++
|
||||
for (i=count; i>pos; i--) {
|
||||
topk[i]=topk[i-1]
|
||||
topv[i]=topv[i-1]
|
||||
}
|
||||
topk[pos]=k
|
||||
topv[pos]=arr[k]
|
||||
}
|
||||
|
||||
for (i=1; i<=length(keys) && i<=n; i++) {
|
||||
printf " %7d %s\n", arr[keys[i]], keys[i]
|
||||
}
|
||||
print ""
|
||||
}
|
||||
|
||||
# ---- STATS helper: sort and print top N for matrix-like keys ----
|
||||
function print_matrix(title, arr, n, k,i,j,keys,tmp) {
|
||||
print title
|
||||
i=0
|
||||
for (k in arr) { i++; keys[i]=k }
|
||||
|
||||
for (i=1; i<=length(keys); i++) {
|
||||
for (j=i+1; j<=length(keys); j++) {
|
||||
if (arr[keys[j]] > arr[keys[i]]) {
|
||||
tmp=keys[i]; keys[i]=keys[j]; keys[j]=tmp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i=1; i<=length(keys) && i<=n; i++) {
|
||||
printf " %7d %s\n", arr[keys[i]], keys[i]
|
||||
for (i=1; i<=count; i++) {
|
||||
printf " %7d %s\n", topv[i], topk[i]
|
||||
}
|
||||
print ""
|
||||
}
|
||||
@@ -524,10 +553,14 @@ BEGIN {
|
||||
onlypop3 = ENVIRON["ONLY_POP3"] + 0
|
||||
|
||||
typere = ENVIRON["TYPE_RE"]
|
||||
show_progress = ENVIRON["SHOW_PROGRESS"] + 0
|
||||
total=0
|
||||
progress_tick()
|
||||
}
|
||||
|
||||
{
|
||||
progress_tick()
|
||||
|
||||
line=$0
|
||||
if (line !~ /dovecot:/) next
|
||||
|
||||
@@ -592,10 +625,12 @@ BEGIN {
|
||||
|
||||
# Eventausgabe
|
||||
if (oneline == 1) {
|
||||
progress_clear()
|
||||
printf "%s\t%s\t%s\t%s\t%s\t%s\n", ts, type, res, ip, user, msg
|
||||
next
|
||||
}
|
||||
|
||||
progress_clear()
|
||||
print "time: " ts
|
||||
print "type: " type
|
||||
print "client-ip: " ip
|
||||
@@ -606,6 +641,7 @@ BEGIN {
|
||||
}
|
||||
|
||||
END {
|
||||
progress_clear()
|
||||
if (stats != 1) exit
|
||||
|
||||
print "Events: " total
|
||||
@@ -626,7 +662,7 @@ END {
|
||||
print_top_sorted("Top users (overall, Top 10)", c_user, 10)
|
||||
print_top_sorted("Top users (fail only, Top 10)", c_user_fail, 10)
|
||||
|
||||
print_matrix("Matrix result@type (Top 30)", c_res_type, 30)
|
||||
print_top_sorted("Matrix result@type (Top 30)", c_res_type, 30)
|
||||
|
||||
print_top_sorted("Fail combos IP -> user (Top 20)", c_fail_ip_user, 20)
|
||||
}
|
||||
@@ -635,16 +671,25 @@ END {
|
||||
# -------------------------------------------------------------------
|
||||
# run_pipeline():
|
||||
# Liest die ausgewählten Files (inkl. .gz über zcat -f) und pipe't
|
||||
# in awk. In --follow Mode: tail -F auf nicht-gz Dateien.
|
||||
# in awk. sudo wird nur verwendet, wenn mindestens eine Logdatei nicht
|
||||
# direkt lesbar ist. In --follow Mode: tail -F auf nicht-gz Dateien.
|
||||
# -------------------------------------------------------------------
|
||||
run_read() {
|
||||
if [[ $NEED_SUDO -eq 1 ]]; then
|
||||
sudo "$@"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
run_pipeline() {
|
||||
local f
|
||||
|
||||
if [[ $FOLLOW -eq 1 ]]; then
|
||||
tail -F "${FOLLOW_FILES[@]}" | \
|
||||
run_read tail -F "${FOLLOW_FILES[@]}" | \
|
||||
env SINCE="$SINCE" UNTIL="$UNTIL" ADDR_RE="$ADDR_RE" EMAIL_NEEDLE="$EMAIL_NEEDLE" TYPE_RE="$TYPE_RE" \
|
||||
ONLY_LOGIN="$ONLY_LOGIN" ONLY_LMTP="$ONLY_LMTP" ONLY_IMAP="$ONLY_IMAP" ONLY_POP3="$ONLY_POP3" \
|
||||
STATUS_FILTER="$STATUS_FILTER" ONELINE="$ONELINE" STATS="$STATS" \
|
||||
STATUS_FILTER="$STATUS_FILTER" ONELINE="$ONELINE" STATS="$STATS" SHOW_PROGRESS="$SHOW_PROGRESS" \
|
||||
awk "$AWK_SCRIPT"
|
||||
return
|
||||
fi
|
||||
@@ -652,16 +697,15 @@ run_pipeline() {
|
||||
{
|
||||
for f in "${LOGFILES[@]}"; do
|
||||
if [[ "$f" =~ \.gz$ ]]; then
|
||||
zcat -f -- "$f" 2>/dev/null || true
|
||||
run_read zcat -f -- "$f" || true
|
||||
else
|
||||
cat -- "$f" 2>/dev/null || true
|
||||
run_read cat -- "$f" || true
|
||||
fi
|
||||
done
|
||||
} | env SINCE="$SINCE" UNTIL="$UNTIL" ADDR_RE="$ADDR_RE" EMAIL_NEEDLE="$EMAIL_NEEDLE" TYPE_RE="$TYPE_RE" \
|
||||
ONLY_LOGIN="$ONLY_LOGIN" ONLY_LMTP="$ONLY_LMTP" ONLY_IMAP="$ONLY_IMAP" ONLY_POP3="$ONLY_POP3" \
|
||||
STATUS_FILTER="$STATUS_FILTER" ONELINE="$ONELINE" STATS="$STATS" \
|
||||
STATUS_FILTER="$STATUS_FILTER" ONELINE="$ONELINE" STATS="$STATS" SHOW_PROGRESS="$SHOW_PROGRESS" \
|
||||
awk "$AWK_SCRIPT"
|
||||
}
|
||||
|
||||
run_pipeline
|
||||
|
||||
|
||||
Reference in New Issue
Block a user