Files
netbird-telegraf-installer/scripts/syslog-tail.sh
T

148 lines
5.8 KiB
Bash

#!/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-<name>. 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
# WICHTIG: Telegraf startet inputs.exec-Unterprozesse mit den Rechten des
# Dienst-Benutzers (im Container uid=999 "telegraf"), NICHT mit den Rechten des
# Telegraf-Prozesses. /hostfs/var/log/syslog gehoert Gruppe "adm" (Modus 640) —
# als uid 999 ist die Datei nicht lesbar und das Script gibt still nichts aus.
# Deshalb: Lesen ueber cat als root, wenn wir nicht schon root sind und sudo/cat
# verfuegbar ist.
read_head() {
if [ "$(id -u)" -eq 0 ]; then
head -c "$2" "$1" 2>/dev/null
elif command -v sudo >/dev/null 2>&1; then
sudo -n head -c "$2" "$1" 2>/dev/null
else
head -c "$2" "$1" 2>/dev/null
fi
}
FILES="${SYSLOG_FILES:-/hostfs/var/log/syslog /hostfs/var/log/kern.log}"
MAX_LINES="${SYSLOG_MAX_LINES:-500}"
# Default /tmp: als Dienst-Benutzer (uid 999 im Container) immer schreibbar.
# /var/lib scheitert dort mit "Permission denied" — und ohne schreibbaren State
# kann der Offset nie gespeichert werden, wodurch das Script bei JEDEM Lauf im
# Erstanlauf-Zweig landet und dann still NICHTS ausgibt.
STATE_DIR="${SYSLOG_STATE_DIR:-/tmp/telegraf-syslog}"
if ! mkdir -p "$STATE_DIR" 2>/dev/null; then
echo "syslog-tail: State-Verzeichnis $STATE_DIR nicht anlegbar" >&2
fi
if ! touch "$STATE_DIR/.wtest" 2>/dev/null; then
echo "syslog-tail: WARNUNG $STATE_DIR nicht beschreibbar - es werden KEINE Logs geliefert" >&2
echo "syslog-tail: SYSLOG_STATE_DIR auf ein schreibbares Verzeichnis setzen" >&2
else
rm -f "$STATE_DIR/.wtest" 2>/dev/null
fi
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
}
# WICHTIG: KEINEN Zeitstempel selbst berechnen.
# Die Syslog-Zeit ist LOKAL (+02:00), mawk/mktime kennt aber keine
# Zeitzonen-Offsets — im UTC-Container entstand daraus ein Wert 2 h in der
# Zukunft und Loki wies jeden Push ab:
# "entry for stream '{…}' has timestamp too new: 2026-09-13T23:08:19Z"
# Ohne Zeitstempel stempelt Telegraf die Zeile beim Einsammeln — bei
# interval = 30s genau genug und zeitzonensicher.
function ts2epoch(t) { return 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) {
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)
}
}
}
}
# Kein Zeitstempel -> Telegraf setzt ihn (zeitzonensicher, siehe oben).
printf "journal,source=%s,ident=%s,priority=%s,jhost=%s message=\"%s\"\n", \
etag(SRC), etag(ident), etag(prio), etag(host), estr(msg)
}
'
# 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