#!/usr/bin/env bash # echo "entering: ${BASH_SOURCE[0]}" # echo "remove all echos to preseve output" # echo "" # export PS4='\e[90m+${LINENO} in ${#BASH_SOURCE[@]}>${FUNCNAME[0]}:${BASH_SOURCE[@]##*/} \e[0m' # set -x # Ensure LOG_DEVICE has a default — prevents "ambiguous redirect" errors if [ -z "$LOG_DEVICE" ] || [ ! -w "$LOG_DEVICE" ]; then if [ -t 1 ] && [ -w "$(tty 2>/dev/null)" ]; then export LOG_DEVICE="$(tty)" elif [ -w /proc/self/fd/1 ]; then export LOG_DEVICE=/proc/self/fd/1 else export LOG_DEVICE=/dev/null fi fi # Always resolve LOG_LIVE relative to current user (shared config may have another user's path) export LOG_LIVE=~/config/log.live.out touch "$LOG_LIVE" 2>/dev/null type private.ensure.groupWrite >/dev/null 2>&1 && private.ensure.groupWrite "$LOG_LIVE" log.device() { local device=$1 if [ -z "$device" ]; then device=/dev/tty fi export LOG_DEVICE=$device echo "initializing logging to $LOG_DEVICE" >>$LOG_DEVICE RETURN_VALUE="$?" if [ $RETURN_VALUE -eq 1 ]; then old_log_device=$LOG_DEVICE export LOG_DEVICE=/dev/stderr echo " Tried to initialize logging to $old_log_device and failed" >>$LOG_DEVICE echo " logging over ssh (or other non-tty) ... using $LOG_DEVICE" >>$LOG_DEVICE fi } log.init() { local device=$1 if [ -z "$device" ]; then device=/dev/tty fi export LOG_DEVICE=$device echo "initializing logging to $LOG_DEVICE" >>$LOG_DEVICE RETURN_VALUE="$?" if [ $RETURN_VALUE -eq 1 ]; then old_log_device=$LOG_DEVICE export LOG_DEVICE=/dev/stderr echo " Tried to initialize logging to $old_log_device and failed" >>$LOG_DEVICE echo " logging over ssh (or other non-tty) ... using $LOG_DEVICE" >>$LOG_DEVICE fi } log.init() { local level=$1 if [ "$level" = "localInstall" ] || ! this.isNumber $level; then level=3 fi if [ -z "$CONFIG" ]; then CONFIG=~/config/user.env source $CONFIG source $OOSH_DIR/config log.init.colors fi echo "initializing logging to $LOG_DEVICE" >>$LOG_DEVICE RETURN_VALUE="$?" if [ $RETURN_VALUE -eq 1 ]; then export LOG_DEVICE=/dev/stderr echo " logging over ssh ... using $LOG_DEVICE" #config save log LOG fi if [ -n "$level" ]; then export LOG_LEVEL="$level" important.log "log level changed to $LOG_LEVEL" else export LOG_LEVEL=3 fi # Initialize LOG_LIVE with default path if not already set if [ -z "$LOG_LIVE" ]; then export LOG_LIVE=~/config/log.live.out touch "$LOG_LIVE" 2>/dev/null private.ensure.groupWrite "$LOG_LIVE" fi important.log "log initialized on level $LOG_LEVEL" } if ! [ "$(type -t info.log)" = "function" ]; then source $(dirname ${BASH_SOURCE[0]})/this fi test.console.log() { seq.puml.log private.log.install.append "TEST.CONSOLE" "$*" if [ -n "$LOG_LIVE" ] && [ -w "$LOG_LIVE" ] \ && [ "$LOG_LEVEL" -gt "0" ]; then echo -e "${NO_COLOR}log: ${BASH_SOURCE[0]##*/} -> ${BASH_SOURCE[1]##*/}: ${FUNCNAME[0]}:${LINENO} - ${BASH_SOURCE[@]##*/} >$*${NORMAL}" | tee -a ~/config/result.txt >>$LOG_LIVE fi if [ "$LOG_LEVEL" -gt "0" ]; then echo -e "${NO_COLOR}$*" >>$LOG_DEVICE fi } console.log() { seq.puml.log private.log.install.append "CONSOLE" "$*" if [ -n "$LOG_LIVE" ] && [ -w "$LOG_LIVE" ] \ && [ "$LOG_LEVEL" -gt "0" ]; then echo -e "${NO_COLOR}log: ${BASH_SOURCE[0]##*/} -> ${BASH_SOURCE[1]##*/}: ${FUNCNAME[0]}:${LINENO} - ${BASH_SOURCE[@]##*/} >$*${NORMAL}" | tee -a ~/config/result.txt >>$LOG_LIVE fi if [ "$LOG_LEVEL" -gt "2" ]; then echo -e "${NO_COLOR}$*" >>$LOG_DEVICE fi } seq.puml.log() { { if [ "$LOG_LEVEL" -gt "5" ]; then set -x export PS4='\e[90m ${BASH_SOURCE[0]##*/} -> ${BASH_SOURCE[1]##*/}: ${FUNCNAME[0]}:${LINENO} - ${BASH_SOURCE[@]##*/} \e[0m' pumlPrefix='echo \"${BASH_SOURCE[0]##*/} -> ${BASH_SOURCE[1]##*/}: ${FUNCNAME[0]}:${LINENO} - \"' if [ "$LOG_LEVEL" -gt "6" ]; then export STEP_DEBUG=ON else export STEP_DEBUG=OFF fi else # TODO cleanup PS4 # pumlPrefix="" # export PS4='\e[90m+${LINENO} in ${#BASH_SOURCE[@]}>${FUNCNAME[0]}:${BASH_SOURCE[@]##*/} \e[0m' pumlPrefix='echo \"${BASH_SOURCE[0]##*/} -> ${BASH_SOURCE[1]##*/}: ${FUNCNAME[0]}:${LINENO} - \"' export PS4='\e[90m ${BASH_SOURCE[0]##*/} -> ${BASH_SOURCE[1]##*/}: ${FUNCNAME[0]}:${LINENO} - ${BASH_SOURCE[@]##*/} \e[0m' #put back #set +x fi } 2>/dev/null } silent.log() { seq.puml.log 2>/dev/null private.log.install.append "SILENT" "$*" if [ -n "$LOG_LIVE" ] && [ -w "$LOG_LIVE" ] \ && [ "$LOG_LEVEL" -gt "1" ]; then echo -e "${GRAY}-: ${BASH_SOURCE[0]##*/} -> ${BASH_SOURCE[1]##*/}: ${FUNCNAME[0]}:${LINENO} - ${BASH_SOURCE[@]##*/} ${GRAY}> $*${NORMAL}" | tee -a ~/config/error.txt >>$LOG_LIVE ## cyan fi if [ "$LOG_LEVEL" -gt "2" ]; then echo -e "${GRAY} $*${NORMAL}" >>$LOG_DEVICE fi } success.log() { seq.puml.log 2>/dev/null private.log.install.append "SUCCESS" "$*" if [ -n "$LOG_LIVE" ] && [ -w "$LOG_LIVE" ] \ && [ "$LOG_LEVEL" -gt "1" ]; then echo -e "${BOLD_GREEN}SUCCESS: ${GRAY}${BASH_SOURCE[0]##*/} -> ${BASH_SOURCE[1]##*/}: ${FUNCNAME[0]}:${LINENO} - ${BASH_SOURCE[@]##*/} ${BOLD_GREEN}> $*${NORMAL}" | tee -a ~/config/result.txt >>$LOG_LIVE ## cyan fi if [ "$LOG_LEVEL" -gt "2" ]; then echo -e "${BOLD_GREEN}SUCCESS> $*${NORMAL}" >>$LOG_DEVICE fi } test.success.log() { seq.puml.log 2>/dev/null if [ "$LOG_LEVEL" -gt "0" ]; then echo -e "${BOLD_GREEN}SUCCESS> $*${NORMAL}" fi } error.details.log() { seq.puml.log 2>/dev/null if [ "$LOG_LEVEL" -gt "0" ]; then echo -e "${NORMAL}$*" fi } warn.log() { seq.puml.log 2>/dev/null private.log.install.append "WARNING" "$*" if [ -n "$LOG_LIVE" ] && [ -w "$LOG_LIVE" ] \ && [ "$LOG_LEVEL" -gt "1" ]; then echo -e "${BOLD_YELLOW}WARNING: $(date) ${COLOR_GRAY}${BASH_SOURCE[0]##*/} -> ${BASH_SOURCE[1]##*/}: ${FUNCNAME[0]}:${LINENO} - ${BASH_SOURCE[@]##*/} ${BOLD_YELLOW}> $*${NORMAL}" | tee -a ~/config/error.txt >>$LOG_LIVE ## cyan fi if [ "$LOG_LEVEL" -gt "1" ]; then echo -e "${BOLD_YELLOW}WARNING> $*${NORMAL}" fi { shift RETURN=$1 } 2>/dev/null } important.log() { seq.puml.log 2>/dev/null private.log.install.append "IMPORTANT" "$*" if [ -n "$LOG_LIVE" ] && [ -w "$LOG_LIVE" ] \ && [ "$LOG_LEVEL" -gt "1" ]; then echo -e "${BOLD_CYAN}IMPORTANT: ${COLOR_GRAY}${BASH_SOURCE[0]##*/} -> ${BASH_SOURCE[1]##*/}: ${FUNCNAME[0]}:${LINENO} - ${BASH_SOURCE[@]##*/} ${BOLD_CYAN}> $*${NORMAL}" >>$LOG_LIVE ## cyan fi if [ "$LOG_LEVEL" -gt "1" ]; then echo -e "${BOLD_CYAN}IMPORTANT> $*${NORMAL}" >>$LOG_DEVICE fi { shift RETURN=$1 } 2>/dev/null } problem.log() { seq.puml.log 2>/dev/null private.log.install.append "PROBLEM" "$*" if [ "$LOG_LEVEL" -gt "1" ]; then echo -e "${BOLD_RED}PROBLEM BREAKPOINT> $*${NORMAL}" #>>$LOG_DEVICE export STEP_DEBUG=ON fi } debug.log() { seq.puml.log 2>/dev/null private.log.install.append "DEBUG" "$*" if [ -n "$LOG_LIVE" ] && [ -w "$LOG_LIVE" ] \ && [ "$LOG_LEVEL" -gt "1" ]; then echo -e "${BOLD_CYAN}-: ${COLOR_GRAY}${BASH_SOURCE[0]##*/} -> ${BASH_SOURCE[1]##*/}: ${FUNCNAME[0]}:${LINENO} - ${BASH_SOURCE[@]##*/} ${BOLD_CYAN}> $*${NORMAL}" >>$LOG_LIVE fi if [ "$LOG_LEVEL" -gt "4" ]; then echo -e "${XXXX}${BOLD_CYAN}- $*${NORMAL}" >>$LOG_DEVICE fi } stop.log() { seq.puml.log 2>/dev/null private.log.install.append "BREAKPOINT" "$*" if [ -n "$LOG_LIVE" ] && [ -w "$LOG_LIVE" ] \ && [ "$LOG_LEVEL" -gt "1" ]; then echo -e "${BOLD_GREEN}BREAKPOINT: $(date) ${COLOR_GRAY}${BASH_SOURCE[0]##*/} -> ${BASH_SOURCE[1]##*/}: ${FUNCNAME[0]}:${LINENO} - ${BASH_SOURCE[@]##*/} ${BOLD_GREEN}> $*${NORMAL}" | tee -a ~/config/error.txt >>$LOG_LIVE ## cyan fi if [ "$LOG_LEVEL" -gt "3" ]; then echo -e "${BOLD_GREEN}BREAKPOINT> $*${NORMAL}" >>$LOG_DEVICE export STEP_DEBUG=ON fi } error.log() { err.log "$@" } err.log() { if [ -z "$CONFIG" ]; then CONFIG=~/config/user.env source $CONFIG fi seq.puml.log private.log.install.append "ERROR" "$*" if [ -n "$LOG_LIVE" ] && [ -w "$LOG_LIVE" ]; then echo -e "${BOLD_RED}ERROR: $(date) ${BOLD_WHITE}${BASH_SOURCE[0]##*/} -> ${BASH_SOURCE[1]##*/}: ${FUNCNAME[0]}:${LINENO} - ${BASH_SOURCE[@]##*/} ${BOLD_RED}> $*${NORMAL}" | tee -a ~/config/error.txt ~/config/result.txt 2>/dev/null >>$LOG_LIVE fi if [ "$LOG_LEVEL" -gt 0 ]; then echo -e "${BOLD_RED}ERROR> $*${NORMAL}" >>$LOG_DEVICE fi # seq.puml.log if [ "$LOG_LEVEL" -gt "4" ]; then export STEP_DEBUG=ON fi } capture.log() { clear.resultFiles important.log "capturing logs of: $*" #### write to console AND result.text AND write errors to errot.txt AND result.txt "$@" > >(tee -a $CONFIG_PATH/result.txt) 2> >(tee -a ~/config/result.txt ~/config/error.txt >&2) } capture.log.silent() { clear.resultFiles important.log "capturing logs of: $* use ${WHITE}log inspect${CYAN} in another shell to see whats going on. waiting for results... " #### write to console AND result.text AND write errors to errot.txt AND result.txt "$@" >$CONFIG_PATH/result.txt 2> >(tee -a $CONFIG_PATH/result.txt $CONFIG_PATH/error.txt >&2) important.log " results ready in: $CONFIG_PATH/result.txt" } log.capture.result() { capture.log "$@" } log.capture.result.silent() { capture.log.silent "$@" } log.inspect() { tail -f $CONFIG_PATH/result.txt } log.inspect.errors() { tail -f $CONFIG_PATH/error.txt } clear.resultFiles() { rm -f "$CONFIG_PATH/result.txt" "$CONFIG_PATH/error.txt" 2>/dev/null touch "$CONFIG_PATH/result.txt" "$CONFIG_PATH/error.txt" 2>/dev/null private.ensure.groupWrite "$CONFIG_PATH/result.txt" "$CONFIG_PATH/error.txt" success.log "result log files cleared" } # log.noop() # { # return 0 # } log.level.completion.levelno() { echo "1 2 3 4 5 6" } log.level() { # # switch to a specific log level if [ -n "$1" ]; then local level=$1 if [ "$level" = "reset" ]; then local current=$LOG_LEVEL export LOG_LEVEL=$LOG_LEVEL_RESET export LOG_LEVEL_RESET=$current else export LOG_LEVEL_RESET=$LOG_LEVEL export LOG_LEVEL="$1" important.log "log level changed to $LOG_LEVEL" check.debug.level fi if [ "$(type -t this.isSourced)" = "function" ]; then if (this.isSourced); then info.log "log level sourced" else info.log "log level started" if [ -z "$LOG_DEVICE" ]; then export LOG_DEVICE=/dev/tty fi this.call config save log LOG exit 0 #config add log fi else important.log "should not happen: this.isSourced is not defined....continuing" fi shift export RETURN=$1 else this=${0##*/} RESULT="export LOG_LEVEL=$LOG_LEVEL" RETURN="$1" echo "$RESULT" if [ "$this" = "log" ]; then debug.log "$this was started" exit 0 else debug.log "$this was sourced" fi fi # LOG_LEVEL=6 # check.debug.level } log.live() { # # tail the live log file if [ -z "$LOG_LIVE" ]; then export LOG_LIVE=~/config/log.live.out touch "$LOG_LIVE" 2>/dev/null private.ensure.groupWrite "$LOG_LIVE" fi tail -f "$LOG_LIVE" } log.clear.liveLog() { # # set the log device local file=$1 if [ -z $file ]; then file=$LOG_LIVE fi log.clear.scrollbackBuffer >$file echo "# clear log $file... -------------- $(date)--------------------" >$file log.clear.scrollbackBuffer echo "# clear log $file -------------- $(date)--------------------" ls -l $file } log.clear.screen() { echo -ne '\033c' # clear screen: https://unix.stackexchange.com/questions/375743/why-clear-do-not-clear-whole-screen/375784#375784 } log.clear.scrollbackBuffer() { log.clear.screen echo -ne '\033[3J' # clear scrollback buffer: https://invisible-island.net/ncurses/man/clear.1.html } log.cls() { # # alias for clear scrollback buffer: log.clear.scrollbackBuffer } log.clear.all() { log.clear.liveLog ~/config/result.txt log.clear.liveLog ~/config/error.txt log.clear.liveLog #log.clear.scrollbackBuffer } log.live.file() { # # set or show the log live file if [ -n "$1" ]; then export LOG_LIVE=$1 important.log "new LOG_LIVE=$LOG_LIVE" this.call config save log LOG else echo "$LOG_LIVE" fi } log.live.result.txt() { # # shows live intermediate results tail -f ~/config/result.txt } log.live.result.env() { # # shows live intermediate results tail -f ~/config/result.env } log.live.error() { # # shows live errors tail -f ~/config/error.txt } log.live.panes() { # # open tmux 4-pane layout for live log monitoring local SESSION="ooshlog" local OOSH="${OOSH_DIR:-$(dirname "${BASH_SOURCE[0]}")}" source "$OOSH/otmux" if otmux.session.has "$SESSION"; then important.log "Session '$SESSION' already exists, attaching..." if [ -n "$TMUX" ]; then otmux.session.switch "$SESSION" else otmux.session.attach "$SESSION" fi return 0 fi log.clear.all # Layout: left=interactive (full height), right-top=log live, right-bottom=result|error local left=$($TMUX_CMD new-session -d -s "$SESSION" -n logs -c "$OOSH" -P -F "#{pane_id}") local rightTop=$($TMUX_CMD split-window -h -t "$left" -c "$OOSH" -P -F "#{pane_id}") local rightBottom=$($TMUX_CMD split-window -v -t "$rightTop" -c "$OOSH" -P -F "#{pane_id}") local rightBottomRight=$($TMUX_CMD split-window -h -t "$rightBottom" -c "$OOSH" -P -F "#{pane_id}") # Show pane titles in borders otmux.set -t "$SESSION" pane-border-status top otmux.set -t "$SESSION" pane-border-format " #{pane_title} " # Set pane titles otmux.pane.title "$left" "interactive" otmux.pane.title "$rightTop" "log-live" otmux.pane.title "$rightBottom" "log-live-result" otmux.pane.title "$rightBottomRight" "log-live-error" # Start log monitoring in 3 panes otmux.pane.send "$rightTop" "$OOSH/log live" otmux.pane.send "$rightBottom" "$OOSH/log live.result" otmux.pane.send "$rightBottomRight" "$OOSH/log live.error" # Focus interactive pane and attach $TMUX_CMD select-pane -t "$left" if [ -n "$TMUX" ]; then otmux.session.switch "$SESSION" else otmux.session.attach "$SESSION" fi } log.live.panes.stop() { # # stop the live log monitoring tmux session local SESSION="ooshlog" source "${OOSH_DIR:-$(dirname "${BASH_SOURCE[0]}")}/otmux" if otmux.session.has "$SESSION"; then otmux.session.kill "$SESSION" important.log "Session '$SESSION' stopped" else important.log "Session '$SESSION' is not running" fi } log.device() { # # set the log device if [ -n "$1" ]; then export LOG_DEVICE=$1 important.log "new LOG_DEVICE=$LOG_DEVICE" this.call config save log LOG else important.log "LOG_DEVICE=$LOG_DEVICE" fi } log.init.colors() { if [ -z "$CONFIG_PATH" ]; then warn.log "no color yet" >>/dev/tty return 1 fi if [ -f $CONFIG_PATH/setup.color.env ]; then source $CONFIG_PATH/setup.color.env fi } log.install.init() { # # start install logging to capture all messages to file local logFile="${1:-$HOME/config/install.log}" mkdir -p "$(dirname "$logFile")" 2>/dev/null echo "# OOSH Install Log - $(date)" > "$logFile" export LOG_INSTALL="$logFile" important.log "Install logging started: $logFile" } log.install.finish() { # # finalize install log and report summary if [ -n "$LOG_INSTALL" ] && [ -f "$LOG_INSTALL" ]; then echo "# Install completed: $(date)" >> "$LOG_INSTALL" local lineCount=$(wc -l < "$LOG_INSTALL") local errorCount=$(grep -c "^ERROR:" "$LOG_INSTALL" 2>/dev/null || echo 0) important.log "Install log: $LOG_INSTALL ($lineCount lines, $errorCount errors)" fi unset LOG_INSTALL } log.install() { # # view the install log local logFile="${LOG_INSTALL:-$HOME/config/install.log}" if [ -f "$logFile" ]; then cat "$logFile" else error.log "No install log found at $logFile" fi } log.install.errors() { # # show only errors from the install log local logFile="${LOG_INSTALL:-$HOME/config/install.log}" if [ -f "$logFile" ]; then grep "^ERROR:" "$logFile" else error.log "No install log found at $logFile" fi } log.install.live() { # # tail the install log in real-time (during install) local logFile="${LOG_INSTALL:-$HOME/config/install.log}" if [ -f "$logFile" ]; then tail -f "$logFile" else error.log "No install log found at $logFile" fi } log.usage() { local this=${0##*/} echo "You started: $this Usage: $this: command important.log success.log console.log warn.log error.log info.log debug.log stop.log silent.log install install.errors install.live install.init install.finish Examples $this level <0...7> for live logging open another shell and start there in each $this live $this live.error $this live.result or open all three in a tmux dashboard: $this live panes $this live panes.stop inspect the install log (after install completes) $this install $this install.errors follow install log in real-time (during install) $this install.live clear the live log with $this clear.liveLog live log file currently is: $LOG_LIVE " } log.start() { #echo "log startet" if [ -z "$LOG_LEVEL" ]; then log.init $1 else local command=$1 seq.puml.log #log.init.colors # if [ -z "$command" ]; then # command="level" # fi this=${0##*/} if [ "$this" = "log" ]; then debug.log "$this was started" else return 0 fi if [ -n "$command" ]; then if (this.functionExists log.$command); then local nextArg="$2" if [ -n "$nextArg" ] && (this.functionExists "log.${command}.${nextArg}"); then shift 2 log.${command}.${nextArg} "$@" else shift log.$command "$@" fi elif (this.functionExists $command.log); then shift $command.log "$@" fi else log.usage exit 0 fi #if [ "$(type -t this.init)" = "function" ]; then #result 0 "export LOG_LEVEL=$LOG_LEVEL" "$1" >/dev/null #else RETURN_VALUE=0 RESULT="export LOG_LEVEL=$LOG_LEVEL" RETURN="$1" #fi return $RETURN_VALUE fi # check.debug.level # create.result 0 "export LOG_LEVEL=$LOG_LEVEL" "$1" >/dev/null # return $(result) } log.start "$@" # console.log " # console.log: still in ${BASH_SOURCE[0]##*/} # "