- Posted on
- • Artificial Intelligence
Artificial Intelligence Bash Logging Best Practices
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Artificial Intelligence Bash Logging Best Practices
If your AI jobs are orchestrated by Bash—fetching models, kicking off training, calling inference endpoints—your logs are the single source of truth when something goes wrong at 2 a.m. But default Bash output is noisy, inconsistent, and all too often leaks secrets. This guide shows how to turn plain shell output into reliable, structured, and safe logs tailored for AI/ML workflows.
You’ll get practical patterns and drop-in snippets for:
Structured JSON logs with levels and timestamps
Durable storage to file and system journal/syslog
Correlation IDs for multi-step AI pipelines
Secret redaction and safer debugging
Rotation and retention so logs don’t fill your disk
Why this matters for AI workloads
Long and expensive runs: Training or batch inference often runs for hours. When it fails at 95%, you need precise breadcrumbs.
Sensitive data and credentials: API keys, tokens, and PII must never end up in plaintext logs.
Reproducibility and auditability: Structured logs let you compute metrics, tie outputs to inputs, and satisfy compliance.
Distributed complexity: You’ll want to correlate steps across nodes, containers, or services without guesswork.
Prerequisites: tools used below (with install commands)
These examples use jq for JSON handling and optionally rsyslog/logrotate for centralization and retention. Install with your package manager:
jq
- apt:
sudo apt-get update && sudo apt-get install -y jq - dnf:
sudo dnf install -y jq - zypper:
sudo zypper install -y jq
- apt:
rsyslog (optional, for syslog-based centralization)
- apt:
sudo apt-get update && sudo apt-get install -y rsyslog && sudo systemctl enable --now rsyslog - dnf:
sudo dnf install -y rsyslog && sudo systemctl enable --now rsyslog - zypper:
sudo zypper install -y rsyslog && sudo systemctl enable --now rsyslog
- apt:
logrotate (optional, for file-based rotation)
- apt:
sudo apt-get update && sudo apt-get install -y logrotate - dnf:
sudo dnf install -y logrotate - zypper:
sudo zypper install -y logrotate
- apt:
Note: systemd-journald is present on most modern distributions by default and works with journalctl.
1) Log in structured JSON with levels and timestamps
Plain text is hard to search and parse. JSON logs let you filter by run_id, step, or level and feed logs to downstream tools.
Drop-in snippet:
#!/usr/bin/env bash
set -Eeuo pipefail
# Directory for logs (user-writable, private)
LOG_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/ai-logs"
install -d -m 700 "$LOG_DIR"
# One correlation ID per run
RUN_ID="$(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid)"
SYSLOG_TAG="myai" # shown in journalctl/syslog
LOG_FILE="$LOG_DIR/$(date -u +'%Y%m%dT%H%M%SZ')_${RUN_ID}.log"
ts() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
# Map our levels to syslog priorities
_syslog_prio() {
case "$1" in
debug) echo "user.debug" ;;
info) echo "user.info" ;;
warn|warning) echo "user.warning" ;;
error|err) echo "user.err" ;;
*) echo "user.notice" ;;
esac
}
log() {
local level="$1"; shift || true
local msg="${*:-}"
# Build JSON; jq -Rs safely quotes the message
local json
json=$(printf '{"ts":"%s","level":"%s","run_id":"%s","pid":%d,"msg":%s}\n' \
"$(ts)" "$level" "$RUN_ID" "$$" \
"$(printf '%s' "$msg" | jq -Rs '.')")
# Write to file
printf '%s\n' "$json" >> "$LOG_FILE"
# Also to syslog/journal if available (non-fatal if logger is absent)
if command -v logger >/dev/null 2>&1; then
logger -t "$SYSLOG_TAG" -p "$(_syslog_prio "$level")" -- "$json" || true
fi
}
log info "run_start model=bert-base-uncased dataset=imdb"
Read your logs:
Tail file:
tail -f "$LOG_FILE" | jqJournal by tag:
journalctl -t myai -o json-pretty | jq -r '.msg' | jq
Why this helps:
Machine-readable output for dashboards and alerts.
Levels allow you to filter noise during normal operation but keep detail for investigations.
2) Make logs durable and discoverable: file + journal/syslog
Combining a private file with the system journal is robust:
Files are easy for quick grep, and you control retention.
Journal/syslog makes aggregation and cross-host search possible.
Basic setup is already in the log() function above:
It appends to
$LOG_FILEIt uses
loggerto push the same JSON into journald/rsyslog
Query examples:
Today’s errors with your tag:
journalctl -t myai --since today -p err -o json | jq -r '.msg' | jqA specific run:
journalctl -t myai -o json | jq -r 'select(.msg|test("RUN_ID_GOES_HERE")) | .msg' | jq
Tip:
- For multi-line output from tools (e.g., curl errors), wrap them and log once:
log error "$(some_cmd 2>&1 || true)"
3) Keep secrets out of logs
AI pipelines often depend on API keys and tokens. Don’t leak them.
Never echo secrets
Avoid set -x around sensitive areas
Redact known patterns before logging
Example redaction helpers:
# Define patterns to redact (extend as needed)
REDACT_PATTERNS=(
'OPENAI_API_KEY=[^[:space:]]+'
'HUGGINGFACE_TOKEN=[^[:space:]]+'
'Authorization: Bearer [^[:space:]]+'
)
redact() {
local input="$*"
local out="$input"
for pat in "${REDACT_PATTERNS[@]}"; do
out="$(printf '%s' "$out" | sed -E "s/${pat}/REDACTED/g")"
done
printf '%s' "$out"
}
safe_log_cmd() {
# Logs a command line without leaking values
# Usage: safe_log_cmd curl -H "Authorization: Bearer $OPENAI_API_KEY" ...
local cmd_str
cmd_str="$(printf '%q ' "$@")"
log info "cmd=$(redact "$cmd_str")"
"$@"
}
Safer debug tracing:
Keep xtrace off by default
If you must use it, route it away from stdout and redact:
# Send xtrace to FD 9 and into a protected file
exec 9>>"$LOG_DIR/xtrace_${RUN_ID}.log"
export BASH_XTRACEFD=9
export PS4='+ ${BASH_SOURCE##*/}:${LINENO}:${FUNCNAME[0]:-main} '
# Turn on tracing in a tiny, non-secret section only:
# set -x; some_non_secret_call; set +x
4) Correlate everything: run_id, step, and metadata
Add a run_id everywhere and enrich logs with context (step, model, dataset, host). This makes distributed or multi-phase pipelines traceable.
HOST="$(hostname -f 2>/dev/null || hostname)"
STEP="download_model"
log info "$(printf 'ctx host=%s step=%s model=%s dataset=%s' \
"$HOST" "$STEP" "bert-base-uncased" "imdb")"
# Later steps:
STEP="train"
log info "ctx host=$HOST step=$STEP epochs=3 lr=3e-5"
You can also export RUN_ID so sub-processes include it:
export RUN_ID
child_script.sh # which uses the same logging functions
5) Rotate and retain your logs
Prevent disks from filling up and keep a sane retention policy.
- File-based rotation with logrotate:
# /etc/logrotate.d/myai (requires sudo)
# Adjust paths to your LOG_DIR
/home/USERNAME/.local/state/ai-logs/*.log {
daily
rotate 14
compress
missingok
notifempty
copytruncate
}
Journald retention:
- Configure in
/etc/systemd/journald.conf(e.g.,SystemMaxUse=1G) - Apply changes:
sudo systemctl restart systemd-journald
- Configure in
rsyslog centralization (optional):
- Ensure rsyslog is running (see install above)
- Configure forwarding or index logs with your favorite stack
Real-world mini example: logging an inference call
This small script calls a local inference API, captures latency, and logs success/failure without leaking secrets.
#!/usr/bin/env bash
set -Eeuo pipefail
# --- include or source the logging helpers from earlier ---
LOG_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/ai-logs"; install -d -m 700 "$LOG_DIR"
RUN_ID="$(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid)"
SYSLOG_TAG="myai"; LOG_FILE="$LOG_DIR/$(date -u +'%Y%m%dT%H%M%SZ')_${RUN_ID}.log"
ts() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
_syslog_prio(){ case "$1" in debug)echo user.debug;;info)echo user.info;;warn|warning)echo user.warning;;error|err)echo user.err;;*)echo user.notice;;esac; }
log(){ local level="$1"; shift||true; local msg="${*:-}"; local json; json=$(printf '{"ts":"%s","level":"%s","run_id":"%s","pid":%d,"msg":%s}\n' "$(ts)" "$level" "$RUN_ID" "$$" "$(printf '%s' "$msg" | jq -Rs '.')"); printf '%s\n' "$json" >> "$LOG_FILE"; command -v logger >/dev/null 2>&1 && logger -t "$SYSLOG_TAG" -p "$(_syslog_prio "$level")" -- "$json" || true; }
REDACT_PATTERNS=('Authorization: Bearer [^[:space:]]+'); redact(){ local input="$*"; local out="$input"; for pat in "${REDACT_PATTERNS[@]}"; do out="$(printf '%s' "$out" | sed -E "s/${pat}/REDACTED/g")"; done; printf '%s' "$out"; }
API_URL="${API_URL:-http://127.0.0.1:8000/infer}"
PROMPT="${PROMPT:-Write a haiku about Bash logging.}"
AUTH_HEADER="${AUTH_HEADER:-}" # e.g., "Authorization: Bearer $OPENAI_API_KEY"
log info "run_start api_url=$API_URL"
start_ns=$(date +%s%N || true)
# Build curl command
curl_cmd=(curl -sS -X POST "$API_URL" -H "Content-Type: application/json")
if [[ -n "$AUTH_HEADER" ]]; then curl_cmd+=(-H "$AUTH_HEADER"); fi
curl_cmd+=(-d "$(jq -n --arg p "$PROMPT" '{prompt:$p}')")
# Log the command safely, then run it
log info "request $(redact "${curl_cmd[*]}")"
set +e
response="$("${curl_cmd[@]}" 2>&1)"; rc=$?
set -e
end_ns=$(date +%s%N || true)
latency_ms=""
if [[ -n "${start_ns:-}" && -n "${end_ns:-}" ]]; then
latency_ms=$(( (end_ns - start_ns)/1000000 ))
fi
if [[ $rc -ne 0 ]]; then
log error "request_failed rc=$rc latency_ms=${latency_ms:-na} err=$(redact "$response")"
exit $rc
fi
# Log structured success; show only a short preview
preview="$(printf '%s' "$response" | head -c 200 | tr '\n' ' ')"
log info "request_ok latency_ms=${latency_ms:-na} preview=$(redact "$preview")"
log info "run_end status=ok"
Inspect results:
File:
jq . "$LOG_FILE"Journal:
journalctl -t myai --since -1h -o json | jq -r '.msg' | jq
Conclusion and next steps
Good logging makes AI pipelines observable, debuggable, and safe. By adopting a structured JSON format, dual sinks (file + journal/syslog), correlation IDs, secret redaction, and rotation, you’ll save time and headaches on your next incident.
Your next step:
1) Install jq (and optionally rsyslog/logrotate) using your package manager.
2) Copy the logging functions into a reusable logging.sh.
3) Instrument one script this week—start with run_id, JSON logs, and a few info/error calls.
4) Add rotation and a journal query you can paste during an outage.
If you want a follow-up, ask for a minimal logging.sh module you can source across all your AI Bash jobs.