Skip to content

Instantly share code, notes, and snippets.

@josephbolus
Created October 8, 2025 17:36
Show Gist options
  • Select an option

  • Save josephbolus/735a7caa2ffecc75859ee3f6646cc44d to your computer and use it in GitHub Desktop.

Select an option

Save josephbolus/735a7caa2ffecc75859ee3f6646cc44d to your computer and use it in GitHub Desktop.
log-helper.sh — minimal logging library for Bash

Logging Library – Install & Integration Guide

This document explains:

  1. Ensuring file logs are always colorless.
  2. Converting the logger into a reusable library for other Bash scripts.
  3. Where to install it on a Linux system (conventional paths), plus minimal packaging tips.

1) File logs: always no color

The provided logger writes two variants of each line:

  • STDOUT: colorized (unless LOG_NO_COLOR=1 or 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"
fi

2) Use as a library

Place the logger in a single file (e.g., log-helper.sh) and source it from other scripts.

2.1 Library file: log-helper.sh

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

2.2 Consuming script example: backup.sh

#!/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"

2.3 Project vendor option

For self‑contained projects, vendor the file at scripts/lib/log-helper.sh and source it relatively:

source "$(dirname "$0")/lib/log-helper.sh"

3) Installation paths (conventional)

Recommended system‑wide locations:

  • Library: /usr/local/lib/log-helper.sh

    • Owner: root:root, Mode: 0644
  • Optional convenience symlink for discovery (not required): /usr/local/share/log-helper.sh

  • Example logs directory (if you standardize): /var/log/yourapp/ (mode 0750, 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.


4) Minimal packaging / deployment

4.1 Manual install

install -D -m 0644 log-helper.sh /usr/local/lib/log-helper.sh

4.2 With a Makefile (optional)

PREFIX ?= /usr/local
install:
	install -D -m 0644 log-helper.sh $(PREFIX)/lib/log-helper.sh
uninstall:
	rm -f $(PREFIX)/lib/log-helper.sh

4.3 Ansible snippet (optional)

- name: Install logging helper
  copy:
    src: log-helper.sh
    dest: /usr/local/lib/log-helper.sh
    owner: root
    group: root
    mode: '0644'

5) Operational notes

  • File output is always plain text (no ANSI). This makes logs ingestion‑friendly.
  • Set LOG_FILE to enable append‑only logging; ensure the parent directory exists and has proper ownership.
  • Use LOG_TIMESTAMP=1 and optionally override LOG_TS_FORMAT for 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.

6) Quick smoke test

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment