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
  • 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
  • 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

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" | jq

  • Journal 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_FILE

  • It uses logger to 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' | jq

  • A 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
  • 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.