diff --git a/install.sh b/install.sh index 0b21367..51272f3 100755 --- a/install.sh +++ b/install.sh @@ -270,14 +270,18 @@ esac # --- System-Logs (journald) → Loki — nur auf systemd-Systemen --- if command -v journalctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then + fetch scripts/journal-tail.sh /etc/telegraf/journal-tail.sh + chmod 0755 /etc/telegraf/journal-tail.sh fetch_conf telegraf/journal.conf /etc/telegraf/telegraf.d/journal.conf - log "journald-Logs aktiv (Warnungen+ ab jetzt; PVE-Units inklusive)." - # Journal-Zugriff fuer den Dienst-Benutzer + # Zustandsverzeichnis fuer den Journal-Cursor + mkdir -p /var/lib/telegraf-journal if [ "$ROLE" = "proxmox" ]; then - : # laeuft als root (Drop-in) — voller Zugriff + : # laeuft als root (Drop-in) — voller Journal-Zugriff else + chown telegraf:telegraf /var/lib/telegraf-journal 2>/dev/null || true usermod -aG systemd-journal telegraf 2>/dev/null || true fi + log "Journald-Logs aktiv (Warnungen+, inkrementell ueber Cursor)." fi # --- Node-Info (LAN-IP, NetBird-IP/FQDN, PVE-URL) — auf jeder Node --- diff --git a/scripts/journal-tail.sh b/scripts/journal-tail.sh new file mode 100755 index 0000000..1b0dabf --- /dev/null +++ b/scripts/journal-tail.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +# journal-tail.sh — journald-Logs inkrementell als Influx Line Protocol liefern. +# +# Warum dieses Script: +# * inputs.journal existiert in den offiziellen Telegraf-Builds NICHT +# (Docker-Image + InfluxData-Debian-Paket 1.40 geprüft). +# * Auf PVE gibt es KEIN rsyslog — /var/log/syslog existiert dort nicht. +# * Der JSON-Parser von inputs.exec liest nur EIN Objekt, keine NDJSON-Zeilen. +# * Das fertige Line Protocol ist dasselbe Muster wie gpu-rocm.sh (bewährt). +# +# Zustand: der Cursor steckt in jeder journalctl-JSON-Zeile ("__CURSOR"). +# Er wird in $STATE_DIR/cursor gemerkt -> keine Duplikate, kein Verlust. +# +# Telegraf: +# [[inputs.exec]] +# interval = "30s" +# timeout = "25s" +# data_format = "influx" +# commands = ["/etc/telegraf/journal-tail.sh"] + +set -u + +PRIORITY="${JOURNAL_PRIORITY:-warning}" # warning|err|info|debug +SINCE="${JOURNAL_SINCE:-}" # optional, nur beim Erstanlauf +MAX_LINES="${JOURNAL_MAX_LINES:-500}" # Schutz vor Flut pro Lauf +STATE_DIR="${JOURNAL_STATE_DIR:-/var/lib/telegraf-journal}" +CURSOR_FILE="$STATE_DIR/cursor" + +mkdir -p "$STATE_DIR" 2>/dev/null || true + +args=(-o json --no-pager --no-hostname -p "$PRIORITY" -n "$MAX_LINES") +if [ -s "$CURSOR_FILE" ]; then + args+=(--after-cursor "$(cat "$CURSOR_FILE" 2>/dev/null)") +elif [ -n "$SINCE" ]; then + args+=(--since "$SINCE") +else + args+=(--since "-5min") +fi + +last_cursor="" + +journalctl "${args[@]}" 2>/dev/null | while IFS= read -r line; do + case "$line" in \{*) ;; *) continue ;; esac + + # Felder aus dem JSON ziehen (jq-frei, damit kein Extra-Paket nötig ist) + get() { printf '%s' "$line" | sed -n "s/.*\"$1\":\"\([^\"]*\)\".*/\1/p" | head -1; } + getnum() { printf '%s' "$line" | sed -n "s/.*\"$1\":\([0-9]*\).*/\1/p" | head -1; } + + cur="$(get '__CURSOR')" + [ -n "$cur" ] && last_cursor="$cur" + + msg="$(get 'MESSAGE')" + [ -n "$msg" ] || continue + + unit="$(get '_SYSTEMD_UNIT')"; [ -n "$unit" ] || unit="$(get 'SYSLOG_IDENTIFIER')" + ident="$(get 'SYSLOG_IDENTIFIER')" + prio="$(get 'PRIORITY')" + ts_us="$(getnum '__REALTIME_TIMESTAMP')" + host="$(get '_HOSTNAME')" + + # Line Protocol: Escaping für Tag-Werte und String-Felder + esc_tag() { printf '%s' "$1" | sed 's/[ ,=]/\\&/g'; } + esc_str() { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'; } + + # Nanosekunden: __REALTIME_TIMESTAMP ist in Mikrosekunden + if [ -n "$ts_us" ]; then + ns="${ts_us}000" + else + ns="$(date +%s)000000000" + fi + + printf 'journal,unit=%s,ident=%s,priority=%s,host=%s message="%s" %s\n' \ + "$(esc_tag "${unit:-unknown}")" \ + "$(esc_tag "${ident:-unknown}")" \ + "$(esc_tag "${prio:-0}")" \ + "$(esc_tag "${host:-unknown}")" \ + "$(esc_str "$msg")" \ + "$ns" + + # Zustand nach jedem Datensatz sichern + [ -n "$cur" ] && { printf '%s' "$cur" > "$CURSOR_FILE.tmp" && mv -f "$CURSOR_FILE.tmp" "$CURSOR_FILE"; } +done diff --git a/telegraf/journal.conf b/telegraf/journal.conf index 63ead1c..2a16ff6 100644 --- a/telegraf/journal.conf +++ b/telegraf/journal.conf @@ -1,33 +1,22 @@ -# System-Logs (Syslog) → Loki +# System-Logs (journald) -> Loki # -# Warum: inputs.docker_log deckt nur Container ab. PVE-Knoten und alle -# Nicht-Docker-Hosts lieferten damit gar keine Logs — genau dort stehen aber -# die relevanten Meldungen (pvedaemon, pveproxy, qemu, Kernel, Storage). +# Warum dieses Script statt eines Plugins: +# * inputs.journal existiert in den offiziellen Telegraf-Builds NICHT +# (Docker-Image UND InfluxData-Debian-Paket 1.40 geprueft, Web-Recherche +# bestaetigt: Plugin braucht einen Build mit systemd-Headern). +# * Auf PVE gibt es KEIN rsyslog -> /var/log/syslog existiert dort nicht. +# * Der JSON-Parser von inputs.exec liest nur ein Objekt, keine NDJSON-Zeilen. +# * Line Protocol direkt ist dasselbe bewaehrte Muster wie gpu-rocm.sh. # -# Umsetzung: inputs.tail auf die Syslog-Dateien. -# HINWEIS: inputs.journal existiert in den offiziellen Telegraf-Builds NICHT -# (Docker-Image und InfluxData-Debian-Paket 1.40 wurden beide geprüft) — das -# Plugin braucht einen Build mit systemd-Headern. Deshalb der Datei-Weg. +# Cursor-Zustand liegt in /var/lib/telegraf-journal/cursor -> keine Duplikate, +# kein Verlust bei Neustart oder Journal-Rotation. # -# Voraussetzung: rsyslog schreibt eine Datei. Auf PVE ist das /var/log/syslog -# (pve-manager hängt an rsyslog). Prüfen mit: ls -la /var/log/syslog +# Feintuning per Umgebungsvariable in /etc/default/telegraf: +# JOURNAL_PRIORITY=warning (warning|err|info|debug) +# JOURNAL_MAX_LINES=500 -[[inputs.tail]] - # Fehlende Dateien sind unkritisch (Debian hat kein /var/log/messages). - files = ["/var/log/syslog", "/var/log/messages", "/var/log/kern.log", "/var/log/daemon.log"] - name_override = "journal" - # Default "saved-or-end": beim ersten Start nichts nachholen, danach merken. - initial_read_offset = "saved-or-end" - # Zeilen als Text durchreichen (kein Parsing nötig) - data_format = "value" - data_type = "string" - # Pfad als Tag, damit man in Loki nach Datei filtern kann - path_tag = "logfile" - -# Feldname vereinheitlichen: tail/value liefert "value", das Logs-Panel -# erwartet (wie docker_log) "message". Umbenennen statt Panel-Sonderfall. -[[processors.rename]] - namepass = ["journal"] - [[processors.rename.replace]] - field = "value" - dest = "message" +[[inputs.exec]] + interval = "30s" + timeout = "25s" + data_format = "influx" + commands = ["/etc/telegraf/journal-tail.sh"]