Use standard logging for scripts

logger and systemd-cat serve pretty much the same purpose. You’re just obviously more likely to find logger on older distros. The following function abstracts both, as well as covering the scenario of the non-existence of either:

logmsg() {
  local logIdent
  while getopts ":t:" optFlags; do
    case "${optFlags}" in
      (t)   logIdent="-t ${OPTARG}";;
      (\?)  echo "ERROR: Invalid option: '-$OPTARG'." >&2
            return 1;;
      (:)   echo \
              "Option '-$OPTARG' requires an argument. e.g. '-$OPTARG 10'" >&2
            return 1;;
  shift "$((OPTIND-1))"
  if command -v systemd-cat &>/dev/null; then
    systemd-cat "${logIdent}" <<< "${*}"
  elif command -v logger &>/dev/null; then
    logger "${logIdent}" "${*}"
    if [[ -w /var/log/messages ]]; then
    elif [[ -w /var/log/syslog ]]; then
    printf '%s\n' \
      "$(date '+%b %d %T') ${HOSTNAME} ${logIdent/-t /} ${*}" >> "${logFile}" 2>&1

You can also run your scripts with systemd-run. Sometimes it makes sense to redirect other streams into the output. In those cases run:

# view output with: journalctl -t 2>&1 | systemd-cat -t

Priorities are specified just by part of the string:

echo 'hello' | systemd-cat -t -p info
echo 'hello' | systemd-cat -t -p warning # bold
echo 'hello' | systemd-cat -t -p emerg   # bold and red

The reason for this is that I like to design my automation scripts to have no output by default, so everything it prints to console is a warning or error which must be logged. To make it more usable, I usually add a verbose switch for debugging or simply running it by hand and in that case I wouldn’t need to log the output.

Note that this squishes stderr into stdout, and if you are putting a script into cron, you want that stderr so that cron will email you if something goes wrong.

Finally, you can the following snippet to streamline your logging:

# not tested, but should work
exec {log_fd}> >(systemd-cat -t
log_emerg()  { printf '<0>%s\n' "$1" >&"$log_fd"; }
log_alert()  { printf '<1>%s\n' "$1" >&"$log_fd"; }
log_crit()   { printf '<2>%s\n' "$1" >&"$log_fd"; }
log_err()    { printf '<3>%s\n' "$1" >&"$log_fd"; }
log_warn()   { printf '<4>%s\n' "$1" >&"$log_fd"; }
log_notice() { printf '<5>%s\n' "$1" >&"$log_fd"; }
log_info()   { printf '<6>%s\n' "$1" >&"$log_fd"; }
log_debug()  { printf '<7>%s\n' "$1" >&"$log_fd"; }


  • avoids forking off new processes all the timedynamically allocates an unused file descriptor (above 10 so scripts that hard-code single-digit FDs don’t break)
  • lets you specify the level (INFO is what you were using by default)
  • also set up some functions to add the <5> etc prefixes to control the logging level per-line.