This document explains:
- Ensuring file logs are always colorless.
- Converting the logger into a reusable library for other Bash scripts.
- Where to install it on a Linux system (conventional paths), plus minimal packaging tips.
The provided logger writes two variants of each line:
- STDOUT: colorized (unless
LOG_NO_COLOR=1or not a TTY) - LOG_FILE: plain text (no ANSI codes)
This is already enforced by building a separate plain_line and appending that to LOG_FILE.
Key snippet (kept in the library):
# Compose the line (colored for stdout)
colored_line="${ts}${color}${prefix}${NO_COLOR} ${title}${msg}"
plain_line="${ts}${plain_prefix} ${title}${msg}"
# STDOUT (color may be present)
printf "%b\n" "$colored_line"
# File append (always plain, never colored)
if [[ -n "${LOG_FILE:-}" ]]; then
mkdir -p -- "$(dirname -- "$LOG_FILE")" 2>/dev/null || true
printf "%s\n" "$plain_line" >> "$LOG_FILE"
fiPlace the logger in a single file (e.g., log-helper.sh) and source it from other scripts.
Copy your final logger into this file, then add a guard so direct execution does nothing, and provide a tiny self‑test if desired.
#!/usr/bin/env bash
# log-helper.sh — minimal logging library for Bash
# Source this file in scripts: source /usr/local/lib/log-helper.sh
: "${LOG_TITLE:=}" # optional title
: "${LOG_LEVEL:=INFO}" # DEBUG|INFO|WARN|ERROR
: "${LOG_TIMESTAMP:=0}" # 1 to enable timestamps
: "${LOG_TS_FORMAT:=%Y-%m-%d %H:%M:%S}" # timestamp format
# Colors (TTY only, unless LOG_NO_COLOR=1)
if [[ -t 1 && "${LOG_NO_COLOR:-0}" != "1" ]]; then
RED='\x1b[0;31m'; GREEN='\x1b[38;5;22m'; CYAN='\x1b[36m'; YELLOW='\x1b[33m'; NO_COLOR='\x1b[0m'
else
RED=''; GREEN=''; CYAN=''; YELLOW=''; NO_COLOR=''
fi
_level_num(){ case "$1" in DEBUG)echo 10;; INFO)echo 20;; WARN)echo 30;; ERROR)echo 40;; *)echo 20;; esac; }
_now(){ date +"${LOG_TS_FORMAT}"; }
log(){
local level="$1"; shift; local msg="$*"
local want="$(_level_num "$LOG_LEVEL")" have="$(_level_num "$level")"
(( have < want )) && return 0
local color prefix plain_prefix
case "$level" in
DEBUG) color="$GREEN"; prefix="[DEBUG]"; plain_prefix="[DEBUG]" ;;
INFO) color="$CYAN"; prefix="[INFO] "; plain_prefix="[INFO] " ;;
WARN) color="$YELLOW";prefix="[WARN] "; plain_prefix="[WARN] " ;;
ERROR) color="$RED"; prefix="[ERROR]"; plain_prefix="[ERROR]" ;;
*) color=""; prefix="[$level]"; plain_prefix="[$level]" ;;
esac
local title=""; [[ -n "$LOG_TITLE" ]] && title="(${LOG_TITLE}) "
local ts=""; [[ "$LOG_TIMESTAMP" == "1" ]] && ts="$(_now) "
local colored_line plain_line
colored_line="${ts}${color}${prefix}${NO_COLOR} ${title}${msg}"
plain_line="${ts}${plain_prefix} ${title}${msg}"
printf "%b\n" "$colored_line"
if [[ -n "${LOG_FILE:-}" ]]; then
mkdir -p -- "$(dirname -- "$LOG_FILE")" 2>/dev/null || true
printf "%s\n" "$plain_line" >> "$LOG_FILE"
fi
}
debug(){ log DEBUG "$@"; }
info(){ log INFO "$@"; }
warn(){ log WARN "$@"; }
error(){ log ERROR "$@"; }
# If sourced, functions are available; if executed directly, run a quick demo
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
export LOG_LEVEL="INFO" LOG_TITLE="Demo" LOG_TIMESTAMP=1
info "Library loaded in standalone mode"
warn "This is a warning"
error "This is an error"
fi#!/usr/bin/env bash
set -euo pipefail
# Prefer an env override, then fallback to a standard path
: "${LOG_LIB:=/usr/local/lib/log-helper.sh}"
# shellcheck source=/usr/local/lib/log-helper.sh
source "$LOG_LIB"
export LOG_TITLE="Backup" # Tag lines with (Backup)
export LOG_LEVEL="INFO"
export LOG_TIMESTAMP=1
export LOG_FILE="/var/log/backup/run.log" # Always plain text in file
info "Starting backup run"
# ... your work here ...
warn "Disk usage 92%"
error "Could not snapshot volume"
info "Done"For self‑contained projects, vendor the file at scripts/lib/log-helper.sh and source it relatively:
source "$(dirname "$0")/lib/log-helper.sh"Recommended system‑wide locations:
-
Library:
/usr/local/lib/log-helper.sh- Owner:
root:root, Mode:0644
- Owner:
-
Optional convenience symlink for discovery (not required):
/usr/local/share/log-helper.sh -
Example logs directory (if you standardize):
/var/log/yourapp/(mode0750, group‑owned if multiple services write)
Why /usr/local/lib? It’s the customary prefix for locally installed, admin‑managed tooling (non‑distro). Libraries or helper scripts that are sourced (not executed) fit well here.
Per‑app installs: If this logger is only for a single application, place it under that app’s tree, e.g. /opt/yourapp/lib/log-helper.sh and reference it via LOG_LIB.
install -D -m 0644 log-helper.sh /usr/local/lib/log-helper.shPREFIX ?= /usr/local
install:
install -D -m 0644 log-helper.sh $(PREFIX)/lib/log-helper.sh
uninstall:
rm -f $(PREFIX)/lib/log-helper.sh- name: Install logging helper
copy:
src: log-helper.sh
dest: /usr/local/lib/log-helper.sh
owner: root
group: root
mode: '0644'- File output is always plain text (no ANSI). This makes logs ingestion‑friendly.
- Set
LOG_FILEto enable append‑only logging; ensure the parent directory exists and has proper ownership. - Use
LOG_TIMESTAMP=1and optionally overrideLOG_TS_FORMATfor machine‑parsable timestamps (e.g.,%Y-%m-%dT%H:%M:%SZ). - To disable terminal colors for humans too, set
LOG_NO_COLOR=1. - Keep library Bash‑specific (
#!/usr/bin/env bash), since it uses arrays and[[ ]]semantics.
source /usr/local/lib/log-helper.sh
export LOG_TITLE="Smoke" LOG_LEVEL=INFO LOG_TIMESTAMP=1 LOG_FILE=/tmp/log-smoke.log
info "hello"
warn "world"
error "!"
# Inspect
cat /tmp/log-smoke.log