Files

116 lines
4.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# journal-tail.sh — journald-Logs inkrementell als Influx Line Protocol.
#
# Warum dieses Script:
# * inputs.journal existiert in den offiziellen Telegraf-Builds NICHT
# (Docker-Image UND InfluxData-Debian-Paket 1.40 geprueft; das 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.
#
# PERFORMANCE: das Parsen laeuft in EINEM awk-Prozess. Eine Vorgaengerversion
# rief pro Feld ein sed auf (~2500 Prozessstarts bei 500 Zeilen) und riss
# dadurch Telegrafs exec-Timeout. Keine externen Abhaengigkeiten (kein jq).
#
# ZUSTAND: der Cursor steckt in jeder journalctl-JSON-Zeile ("__CURSOR").
# Er wird am Ende des Laufs in $STATE_DIR/cursor geschrieben -> keine
# Duplikate, kein Verlust bei Neustart oder Journal-Rotation.
#
# Telegraf:
# [[inputs.exec]]
# interval = "30s"
# timeout = "28s"
# data_format = "influx"
# commands = ["/etc/telegraf/journal-tail.sh"]
#
# Feintuning via /etc/default/telegraf:
# JOURNAL_PRIORITY=warning warning|err|info|debug
# JOURNAL_MAX_LINES=500 Obergrenze pro Lauf
# JOURNAL_BUDGET=15 Sekunden, die journalctl hoechstens lesen darf
set -u
PRIORITY="${JOURNAL_PRIORITY:-warning}"
MAX_LINES="${JOURNAL_MAX_LINES:-500}"
BUDGET="${JOURNAL_BUDGET:-15}"
SINCE="${JOURNAL_SINCE:-}"
STATE_DIR="${JOURNAL_STATE_DIR:-/var/lib/telegraf-journal}"
CURSOR_FILE="$STATE_DIR/cursor"
mkdir -p "$STATE_DIR" 2>/dev/null || true
# Wurde noch nie gelesen? Dann nicht die komplette Historie ausschuetten.
if [ -s "$CURSOR_FILE" ]; then
START_ARGS=(--after-cursor "$(cat "$CURSOR_FILE" 2>/dev/null)")
elif [ -n "$SINCE" ]; then
START_ARGS=(--since "$SINCE")
else
START_ARGS=(--since "-5min")
fi
# --no-hostname: den Hostnamen liefert Telegraf als global_tag (node). Der
# journaleigene Name waere sonst ein zweites, abweichendes Feld.
timeout -k 2 "$BUDGET" journalctl -o json --no-pager --no-hostname \
-p "$PRIORITY" -n "$MAX_LINES" "${START_ARGS[@]}" 2>/dev/null \
| awk -v TMP="$CURSOR_FILE.tmp" '
# JSON-String-Feld lesen — beruecksichtigt Backslash-Escapes.
function jget(line, key, pat, p, rest, out, i, n, c) {
pat = "\"" key "\":\""
p = index(line, pat)
if (p == 0) return ""
rest = substr(line, p + length(pat))
out = ""; n = length(rest)
for (i = 1; i <= n; i++) {
c = substr(rest, i, 1)
if (c == "\\") { out = out c; i++; if (i <= n) out = out substr(rest, i, 1); continue }
if (c == "\"") break
out = out c
}
return out
}
# Tag-Werte: Komma, Leerzeichen, Gleichheitszeichen escapen
function etag(s) { gsub(/[ ,=]/, "\\\\&", s); return s }
# String-Feld: Backslash und Anfuehrungszeichen escapen
function estr(s) { gsub(/\\/, "\\\\", s); gsub(/"/, "\\\"", s); return s }
function emit( cur, msg, unit, ident, prio, ts, lhost, ns) {
cur = jget($0, "__CURSOR")
if (cur != "") last = cur
msg = jget($0, "MESSAGE")
if (msg == "") return
unit = jget($0, "_SYSTEMD_UNIT"); if (unit == "") unit = jget($0, "SYSLOG_IDENTIFIER")
ident = jget($0, "SYSLOG_IDENTIFIER")
prio = jget($0, "PRIORITY")
ts = jget($0, "__REALTIME_TIMESTAMP")
lhost = jget($0, "_HOSTNAME")
if (unit == "") unit = "unknown"
if (ident == "") ident = "unknown"
if (prio == "") prio = "0"
if (lhost == "") lhost = "unknown"
# __REALTIME_TIMESTAMP ist in Mikrosekunden -> Nanosekunden
ns = (ts ~ /^[0-9]+$/) ? ts "000" : sprintf("%.0f000000000", systime())
# Tag heisst jhost, NICHT host: Telegraf setzt host=<node> als global_tag,
# sonst filtert das Dashboard-Log-Panel nach dem falschen Knoten.
printf "journal,unit=%s,ident=%s,priority=%s,jhost=%s message=\"%s\" %s\n", \
etag(unit), etag(ident), etag(prio), etag(lhost), estr(msg), ns
count++
}
{ if (substr($0, 1, 1) == "{") emit() }
END {
# Cursor einmal am Ende sichern — atomar ersetzt das aufrufende Shell-Skript.
if (last != "") { printf "%s", last > TMP; close(TMP) }
}
'
if [ -s "$CURSOR_FILE.tmp" ]; then
mv -f "$CURSOR_FILE.tmp" "$CURSOR_FILE" 2>/dev/null || true
fi