#!/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 # Hartes Zeitbudget: Telegraf bricht den exec sonst ab (timeout 25s bei # interval 30s). Wir lesen nie laenger als BUDGET Sekunden pro Aufruf — der # Cursor macht den Rest beim naechsten Lauf. BUDGET="${JOURNAL_BUDGET:-15}" 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="" # --no-pager + hartes Zeitlimit: journalctl kann bei viel Aufkommen laenger # brauchen als Telegrafs timeout erlaubt. timeout -k 2 "$BUDGET" 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 # Tag "jhost" statt "host": Telegrafs global_tag host= hat Vorrang behalten, # sonst filtert das Dashboard-Log-Panel nach dem falschen Knoten. printf 'journal,unit=%s,ident=%s,priority=%s,jhost=%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