Last active
February 14, 2024 21:53
-
-
Save CosmicToast/64b22bebcd984078e6b7147827a331c1 to your computer and use it in GitHub Desktop.
POSIX sh stack-based logging system.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# this file is licensed under any of the following SPDX licenses | |
# to be chosen by the user: | |
# * 0BSD (https://spdx.org/licenses/0BSD.html) | |
# * BlueOak-1.0.0 (https://blueoakcouncil.org/license/1.0.0) | |
# * CC0-1.0 (https://creativecommons.org/publicdomain/zero/1.0/) | |
# * Unlicense (https://unlicense.org/) | |
## log.sh - Bunker Log | |
# This is the bunker logging system for POSIX sh. | |
# It provides you with stack-based logging semantics, | |
# and includes advances features like stdin-forwarding. | |
# It should not leak any environment variables, | |
# besides those documented, and potentially `add` on buggy shells. | |
# Everything here should be POSIXLY correct. | |
# If it isn't, let me know! <toast (at) bunkerlabs.net> | |
## Usage | |
# Call `log some text here` or `something_with_output | log_stdin`. | |
# It will then be formatted under the current log tree. | |
# | |
# You can manipulate the log tree using log_push and log_pop. | |
# log_push can have multiple levels pushed on it at once. | |
# For example, `log_push one two` from the default tree state will result | |
# in the new tree being blog/one/two. | |
# log_pop can pop multiple levels at once. To undo the above example, you could | |
# call `log_pop 2` instead of calling log_pop twice. | |
# | |
# Whenever the log tree changes, the next call to `log` or `log_stdin` will | |
# cause the state of the log tree to be printed in full. | |
## Customizing | |
# You can customize the "root" node of the log tree by setting log_tree to a | |
# single word (no whitespace as per IFS) before you source this. | |
# You can set a hard limit to the maximum length of any log level by setting | |
# log_maxlen. This will truncate log levels when they are pushed. | |
# You can also force padding on the output by setting log_minlen. | |
# If log_minlen is negative, it will right-pad, like with printf. | |
# You can change the way truncation is done by changing log_tstyles, | |
# which is a space-delineated list of strategies. | |
## API Summary | |
# The following is a summary of all user-facing functions and environment | |
# variables. Functions are denoted with `(args...)`s. | |
# * log(msg...): log a message | |
# * log_stdin(): log lines from stdin; do not use on interactive programs | |
# * log[_stdin](v|q): log only if VERBOSE is set / QUIET is not set | |
# * log_push(level...): push levels on the log tree | |
# * log_pop(amount?): pop levels off the log tree; amount defaults to 1 | |
# * log_shift(level...): push levels on the log tree after popping that amount | |
# | equivalent to `log_pop $#; log_push "$@"` | |
# | with word splitting | |
# * log_reset(): pop all levels except the root | |
# * log_tree: initial state of the log tree, set this before sourcing this file | |
# | to set the default (not removable) log level | |
# * log_minlen: the minimum length of a log level. Levels that are too short | |
# | will be space-padded. Padding inserted to the right if negative | |
# * log_maxlen: the maximum length of a log level. Levels that are too long | |
# | will be truncated | |
# you can initialize your "root" to anything by setting log_tree before | |
# you source this | |
# the default is "blog" - bunker log | |
: ${log_tree:=blog} | |
log_indent=0 | |
# shadow tree, used to calculate indent | |
# if you push, don't log, and then pop, you shadow tree will = your tree | |
# => no need to recalculate indents | |
# in short, if shadow tree doesn't match the tree, | |
# the indent is recalculated at log-time | |
log_stree=$log_tree | |
# this is a function that allows for selecting different styles of truncation | |
# $log_tstyles will be tried in a row until the result fits | |
# as such, it's highly recommended the final option be a terminal one | |
# available styles, with *s being terminal: | |
# * cut*: truncates rightwards | |
# * rcut*: truncates leftwards | |
# * vowels: removes all vowels | |
# * numbers: remove all numbers | |
# * noalnum: remove everything other than alphanumerics | |
: ${log_tstyles:=cut} | |
log_truncate() ( | |
set -- "$*" "$(printf "$*" | wc -c)" \ | |
"$(echo "$log_tstyles" | cut -d' ' -f1)" | |
if [ "${log_maxlen:-$2}" -ge "$2" ]; then | |
echo "$1" | |
return 0 | |
fi | |
case "$3" in | |
cut) | |
echo "$1" | cut -c 1-"${log_maxlen:-$2}" | |
return 0 | |
;; | |
rcut) | |
start=$(( $2 - ${log_maxlen:-$2} + 1 )) | |
[ "$start" -lt 1 ] && start=1 | |
echo "$1" | cut -c "$start"- | |
return 0 | |
;; | |
vowels) | |
set -- "$(echo "$1" | sed -e 's/[aeiou]//g')" | |
;; | |
numbers) | |
set -- "$(echo "$1" | sed -e 's/[0-9]//g')" | |
;; | |
noalnum) | |
set -- "$(echo "$1" | sed -e 's/[^0-9a-zA-Z]//g')" | |
;; | |
*) return 1 ;; | |
esac | |
# goto next method | |
log_tstyles=$(echo "$log_tstyles" | cut -d' ' -f2-) | |
log_truncate "$1" | |
) | |
# you can push multiple levels at once | |
# if a level has embedded spaces, it counts for multiple levels | |
# all words will be truncated to $log_maxlen, but only if it's set | |
log_push() { | |
# do word splitting in case of extra embedded " "s | |
set -- "$*" | |
for add in $1; do | |
# truncate to log_maxlen, if it's set | |
set -- "$@" "$(log_truncate "$add")" | |
done | |
shift | |
log_tree="$log_tree $@" | |
} | |
# you can pop multiple levels at once, $1 is number to pop | |
log_pop() { | |
# default to popping 1 | |
# save the number of words in the log tree | |
set -- ${1:-1} $(echo "$log_tree" | wc -w) | |
# never pop the last word | |
if [ $1 -ge $2 ]; then | |
set -- $(( $2 - 1 )) $2 | |
fi | |
log_tree=$(echo "$log_tree" | cut -d' ' -f 1-$(( $2 - $1 )) ) | |
} | |
log_shift() { | |
# perform word splitting | |
set -- "$*" | |
set -- $1 | |
log_pop $# | |
log_push "$@" | |
} | |
log_reset() { | |
set -- $(echo "$log_tree" | wc -w) | |
log_pop $(( $1 - 1 )) | |
} | |
# this will read stdin line by line | |
# to integrate external commands into the log | |
log_stdin() { | |
while read -r line; do | |
log "$line" | |
done | |
} | |
# echo "$*", but with | |
# every level will be padded to $log_minlen, if it's set | |
# you can set it to a negative number to right-pad it | |
log() { | |
# we need to recalculate the indent | |
if [ "$log_indent" -eq 0 ] || [ "$log_tree" != "$log_stree" ]; then | |
# update the shadow tree and calculate the indentation level | |
log_stree=$log_tree | |
log_indent=$(printf "%${log_minlen}s/" $log_tree | wc -c) | |
# prefix printing | |
printf "%${log_minlen}s/" $log_tree | |
printf "\b: " | |
else | |
# indent up to the indent, but print a | in place of the : | |
# the 2 is because the above has an off-by-one, and this is easier | |
printf ' %.0s' $(seq 2 "$log_indent") | |
printf '| ' | |
fi | |
echo "$*" | |
} >&2 | |
# *v and *q variants | |
logv() { | |
[ -n "$VERBOSE" ] && log "$@" | |
} | |
logq() { | |
[ -z "$QUIET" ] && log "$@" | |
} | |
log_stdinv() { | |
[ -n "$VERBOSE" ] && log_stdin "$@" | |
} | |
log_stdinq() { | |
[ -z "$QUIET" ] && log_stdin "$@" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment