diff --git a/scripts/syslog-tail.sh b/scripts/syslog-tail.sh new file mode 100644 index 0000000..6dbfef6 --- /dev/null +++ b/scripts/syslog-tail.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +# syslog-tail.sh — Syslog-Dateien inkrementell als Influx Line Protocol. +# +# WOFUER: Telegraf laeuft auf imac/kalina/x05 als DOCKER-CONTAINER. Dort gibt es +# kein journalctl (Image = Debian ohne systemd) und der Host ist nur read-only +# unter /hostfs gemountet. Journal-Logs sind auf diesem Weg nicht erreichbar. +# Ersatz: die Syslog-Dateien des Hosts, die im Container lesbar sind +# (/hostfs/var/log/syslog, /hostfs/var/log/kern.log) — vorausgesetzt, rsyslog +# laeuft auf dem Host (auf imac und kalina der Fall). +# +# ACHTUNG: Auf PVE gibt es KEIN rsyslog -> dort ist journal-tail.sh der Weg, +# nicht dieses Script. Fehlt die Datei, endet der Lauf ohne Ausgabe. +# +# ZUSTAND: Byte-Offset je Datei in $STATE_DIR/offset-. Beim ersten Lauf +# beginnt es am DATEIENDE (keine Altlasten), danach wird nur Neues gelesen. +# Rotation: schrumpft die Datei unter den gemerkten Offset, wird neu bei 0 +# begonnen. +# +# Telegraf: +# [[inputs.exec]] +# interval = "30s" +# timeout = "25s" +# data_format = "influx" +# commands = ["/etc/telegraf/syslog-tail.sh"] + +set -u + +FILES="${SYSLOG_FILES:-/hostfs/var/log/syslog /hostfs/var/log/kern.log}" +MAX_LINES="${SYSLOG_MAX_LINES:-500}" +STATE_DIR="${SYSLOG_STATE_DIR:-/var/lib/telegraf-syslog}" + +mkdir -p "$STATE_DIR" 2>/dev/null || true + +for f in $FILES; do + [ -r "$f" ] || continue + + base="$(basename "$f")" + off_file="$STATE_DIR/offset-$base" + + # Aktuelle Dateigroesse; Rotation erkennen + size="$(stat -c %s "$f" 2>/dev/null || wc -c < "$f" 2>/dev/null || echo 0)" + first=0 + from=0 + if [ -s "$off_file" ]; then + from="$(cat "$off_file" 2>/dev/null || echo 0)" + [ "$from" -gt "$size" ] 2>/dev/null && from=0 # Rotation: kleiner geworden + else + first=1 + from="$size" # Erstanlauf: nur Neues + fi + + # Erstanlauf: Startpunkt SOFORT merken. Sonst springt der naechste Lauf auf 0 + # und schuettet die komplette Datei aus (Bug in der ersten Fassung). + if [ "$first" -eq 1 ]; then + printf '%s' "$size" > "$off_file.tmp" 2>/dev/null && mv -f "$off_file.tmp" "$off_file" 2>/dev/null + continue + fi + + [ "$from" -ge "$size" ] && continue + + # Ab Offset lesen, maximal MAX_LINES Zeilen + tail -c +$((from + 1)) "$f" 2>/dev/null \ + | head -n "$MAX_LINES" \ + | awk -v SRC="$base" ' + # WICHTIG: im Telegraf-Image ist mawk der Standard — der versteht KEINE + # Intervall-Regexe ({4}). Positionsbasiert parsen, nie mit {n}. + function etag(s) { gsub(/[ ,=]/, "\\\\&", s); return s } + function estr(s) { gsub(/\\/, "\\\\", s); gsub(/"/, "\\\"", s); return s } + function isdig(s, i, c) { + if (length(s) == 0) return 0 + for (i = 1; i <= length(s); i++) { c = substr(s, i, 1); if (c < "0" || c > "9") return 0 } + return 1 + } + # "2026-09-13T22:52:10..." -> Sekunden seit Epoche (mktime braucht "Y M D H M S") + function ts2epoch(t, y, mo, d, h, mi, se) { + if (length(t) < 19) return 0 + if (substr(t, 5, 1) != "-" || substr(t, 8, 1) != "-" || substr(t, 11, 1) != "T") return 0 + y = substr(t, 1, 4); mo = substr(t, 6, 2); d = substr(t, 9, 2) + h = substr(t, 12, 2); mi = substr(t, 15, 2); se = substr(t, 18, 2) + if (!isdig(y) || !isdig(mo) || !isdig(d) || !isdig(h) || !isdig(mi) || !isdig(se)) return 0 + # mktime ist bei mawk vorhanden; sonst Fallback auf jetzt + e = mktime(y " " mo " " d " " h " " mi " " se) + return (e > 0) ? e : 0 + } + + /./ { + line = $0 + host = "unknown"; ident = "syslog"; msg = line; ns = ""; prio = "6" + + # Fuehrendes "zeitstempel host ident: rest" abtrennen — positionsbasiert. + if (substr(line, 5, 1) == "-" && substr(line, 8, 1) == "-" && substr(line, 11, 1) == "T") { + sp1 = index(line, " ") + if (sp1 > 0) { + ts = substr(line, 1, sp1 - 1) + rest = substr(line, sp1 + 1) + sp2 = index(rest, " ") + if (sp2 > 0) { host = substr(rest, 1, sp2 - 1); rest = substr(rest, sp2 + 1) } + msg = rest + # Praefix "ident[pid]: " bzw. "ident: " abtrennen + colon = index(msg, ": ") + if (colon > 1 && colon <= 64) { + head = substr(msg, 1, colon - 1) + if (index(head, " ") == 0 && index(head, "=") == 0) { + br = index(head, "[") + ident = (br > 1) ? substr(head, 1, br - 1) : head + msg = substr(msg, colon + 2) + } + } + e = ts2epoch(ts) + if (e > 0) ns = e "000000000" + } + } + + if (ns == "") ns = sprintf("%.0f000000000", systime()) + + # Tag heisst jhost, NICHT host: Telegraf setzt host= als global_tag. + printf "journal,source=%s,ident=%s,priority=%s,jhost=%s message=\"%s\" %s\n", \ + etag(SRC), etag(ident), etag(prio), etag(host), estr(msg), ns + } + ' + + # Neuen Offset merken (Dateigroesse am Ende des Lesens) + newsize="$(stat -c %s "$f" 2>/dev/null || echo "$size")" + printf '%s' "$newsize" > "$off_file.tmp" 2>/dev/null && mv -f "$off_file.tmp" "$off_file" 2>/dev/null +done + +exit 0