I want to write Bash logging helpers that add a timestamp and a colored severity label while sending diagnostic output to stderr. Standard output should remain available for functions that return values. Ideally, the helpers should support normal printf-style format strings and arguments, for example `log_info "What a %s %d" "day" 100` and `log_error "Error: %s %o" "something went wrong" "{1: "enter"}"`. What would a clean implementation of the underlying `log` function look like, and how should terminal color support be handled?
3 Answers
You can map severity levels to ANSI escape sequences and print the timestamp and level in color while leaving the actual message formatting to `printf`. For example: `declare -A colors=([INFO]=$'e[0;34m' [WARNING]=$'e[1;33m' [ERROR]=$'e[0;31m'); reset=$'e[0m'; log(){ local level=$1; shift; printf '%s%(%FT%T%z)T%s [%s] ' "${colors[$level]}" -1 "$reset" "$level" >&2; printf "$@" >&2; printf 'n' >&2; }; log_info(){ log INFO "$@"; }; log_warn(){ log WARNING "$@"; }; log_error(){ log ERROR "$@"; }`. ANSI colors are common in terminal emulators, but they should usually be disabled when stderr is redirected to a file or when the environment does not support color. You can add a `NO_COLOR` check and test `[[ -t 2 ]]` before emitting escape sequences.
With Bash 4.2 or newer, `printf` can format the current time directly. A simple version is `printf '%(%d-%m-%Y-%T:Z)T %sn' -1 "Some information"`. The `-1` argument means the current time. For printf-style log messages, keep the format string and its arguments together and pass them through with `printf` rather than joining them into one string: `log(){ local level=$1; shift; printf '%(%FT%T%z)T [%s] ' -1 "$level" >&2; printf "$@" >&2; printf 'n' >&2; }`. Then `log_info(){ log INFO "$@"; }`, and the call `log_info 'What a %s %d' day 100` works as expected.
That is the important distinction: the first remaining argument is the format string, and the rest are its substitutions. Passing `"$@"` preserves that structure, whereas using `"$*"` turns everything into one combined string and loses printf's argument handling.
For log files and tools that parse logs, an ISO 8601-style timestamp such as `%Y-%m-%dT%H:%M:%S%z` is generally easier to sort and interpret than day-month-year formatting. Also, keep the timestamp and level on stderr, but avoid adding extra formatting that could interfere with the caller's printf string.

Color escape sequences are not universal across every shell or output destination. They are terminal control codes, so checking whether file descriptor 2 is a terminal and providing a way to disable colors makes the logger safer.