Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  • Set and Revert Log Levels: Temporarily change the log level and revert after a specified duration.

  • Flexible JSON Parsing: Uses jq for JSON parsing if available; defaults to jq otherwise.

  • Detachable Session Execution: Runs in a detachable session using screen or tmux, allowing the script to continue running independently of the terminal session. If both screen and tmux are installed, the script will prioritize screen first.

  • Supports Cluster and Node Log Levels: Allows setting log level at both the cluster and individual node levels.

  • Log File Auto-Detection: Automatically detects and adjust the castor.log file path for CSN and SCS environments.

  • Log Size Monitoring: Reports the log size generated during the temporary log level change.

  • Countdown Display: Shows a countdown for the specified duration.

...

Code Block
./castor-change-log-level.sh -d 192.168.8.84 -p admin:datacore -i 20 -t 600

Example 2: Set node(s) log level to debug (10) and keep it for 10 minutes

Code Block
./castor-change-log-level.sh -d 192.168.8.84,192.168.8.86 -p admin:datacore -i debug -t 600

Example 3: Run in

...

detachable mode

Code Block
./castor-change-log-level.sh -d 192.168.8.84 -p admin:datacore -i 20 -t 30 -D

...

The at command can used to schedule the script to run at a later time. This is useful when you need to start collecting debug logs at a specific hour.

Example 4: Secure password prompt if password are not provided

Code Block
./castor-change-log-level.sh -p admin -d 192.168.8.84 -i 10 -t 300

(You will be prompted to enter the password securely.)

Code Block
./castor-change-log-level.sh -p admin -d 192.168.8.84 -i 10 -t 300

Enter password for user admin:

Example 5: Schedule the script to run at 3:00 AM on 03/02/2025 and collect debug logs of node 192.168.8.84 and 192.168.8.89 for 1 hour

  1. Ensure the at service is installed, enabled and running

    Code Block
    dnf -y install epel-release
    dnf -y install at atd
    systemctl enable --now at
  2. Schedule the script execution using at:

    Code Block
    echo "/root/dist/castor-change-log-level.sh -i -p admin:datacore -d 192.168.8.84 -L '192.168.8.84,192.168.8.89' 10 -t 3600" | at 03:00 AM 03/02/2025

...

  1. To verify scheduled job:

    Code Block
    atq
    7	Sun Mar 02 03:00:00 2025 a root
  2. To remove a scheduled job (replace JOB_ID with the actual job number from atq output):

    Code Block
    atrm 7

Behavior

  1. Log Level Change: Sets the log level to the specified value. If the current log level matches the requested level, the script skips the update.

  2. Countdown: During the specified duration, the script displays a countdown every second.

  3. Revert Log Level: After the countdown, the log level reverts to the initial value.

  4. Log Size Report: Provides approximately log size generated during the temporary log level change.

Output Messages

...

Message

...

Description

...

Swarm IP:

...

Displays the specified Swarm IP address.

...

Cluster Name:

...

Displays the cluster name retrieved from the Swarm API.

...

New log level:

...

Shows the new log level requested.

...

Current log level:

...

Displays the current log level.

...

Updating log level to X...

...

Indicates the beginning of the log level update process.

...

Reattaching to a Detached Session

For more details on using screen and tmux, refer to their official documentation:

If you accidentally close your terminal (e.g. putty, iterm2, etc) while the script is running in a detached session, you can reattach using the following commands:

For screen users:

  1. List active screen session:

    Code Block
    screen -ls
  2. Reattach to the session:

    Code Block
    screen -r <session_name>

For tmux users:

  1. List active tmux sessions:

    Code Block
    tmux ls
  2. Reattach to the session:

    Code Block
    tmux a -t <session_name>

If the session has ended or was not found, you may need to restart the script manually.

Behavior

  1. Log Level Change: Sets the log level to the specified value. If the current log level matches the requested level, the script skips the update.

  2. Countdown: During the specified duration, the script displays a countdown every second.

  3. Revert Log Level: After the countdown, the log level reverts to the initial value.

  4. Log Size Report: Provides approximately log size generated during the temporary log level change.

Output Messages

Message

Description

Swarm IP:

Displays the specified Swarm IP address.

Cluster Name:

Displays the cluster name retrieved from the Swarm API.

New log level:

Shows the new log level requested.

Current log level:

Displays the current log level.

Updating log level to X...

Indicates the beginning of the log level update process.

Log level changed successfully...

Confirms that the log level was successfully updated.

Keeping log level at X for Y...

Shows the temporary period for which the new log level is retained, with a countdown.

Time's up! Reverting log level...

Indicates that the temporary period has ended and the script is reverting the log level.

Approximate X new logs generated...

Provides information on the amount of logging activity generated during the temporary log level.

...

Code Block
languagebash
#!/bin/bash
# -----------------------------------------------------------------------------------------------------------------------------
# Script: castor-change-log-level.sh
# -----------------------------------------------------------------------------------------------------------------------------
# Description:
# This script changes the log level for the Castor cluster or node(s) using the Swarm API.
# The script supports changing the log level for the entire cluster or individual node(s).
# The script can run in a detachable session using 'screen' or 'tmux'.
# -----------------------------------------------------------------------------------------------------------------------------
# Written by Milton Suen (milton.suen@datacore.com) Oct 31, 2024
# Revision History:
# v1.0.0 - Update to support running the script in a detachable session using screen or tmux.
# v1.1.0 - 2025-02-20 Add support node(s) level log level change.
# v1.2.0 - 2025-02-26 SUPSCR-208:
#        - Enforced proper credential formatting: credentials must be in the username:password format.
#        - Fixed help message shows script name without hard code it.
# v1.2.1 - 2025-02-26 SUPSCR-209: Auto detect CSN or SCS to adjust castor.log file path.
# v1.2.2 - 2025-02-27 Address the issue of the script not display correct when the castor.log file is rotated.
# v1.2.3 - 2025-02-27 Address the issue of log level not display correct within detach session.
# v1.2.4 - 2025-02-27 Bug fix: The default node-level log level is 0 (unset), which differs from the cluster-level default of 30.
# v1.2.5 - 2025-02-28 SUPSCR-208:
#        - Credentials validation and password prompt enhancements.
#        - screen or tmux required with detachable mode, script will stopped with '-D' option if neither is installed.
#        - Fixed an issue where users were prompted to enter password multiple times issue.
#        - Passwords are now hidden in debug output messages.
# v1.2.6 - 2025-02-28 SUPSCR-208:
#        - Disabled credential display on the screen.
#        - Bug fix: Resolved the "debug: command not found" error when removing the -d [IP address] option.
# v1.3.0 - 2025-02-28 minorMinor bug fix and enhancement.
# v1.3.1 - 2025-03-01 SUPSCR-208:
#        - Fixed an issue where credentials enclosed in single quotes (') were not processed correctly.
# v1.3.2 - 2025-03-02 minorMinor bug fix and enhancement.
# v1.3.3 - 2025-03-02 Fixed tmux command to long issue.
# -----------------------------------------------------------------------------------------------------------------------------
# Current Version: 1.3.23
# -----------------------------------------------------------------------------------------------------------------------------
# KB: https://perifery.atlassian.net/wiki/spaces/public/pages/3872161835/Setting+and+Managing+Swarm+Log+Levels+with+script
# -----------------------------------------------------------------------------------------------------------------------------

# Define colors
RED='\033[0;31m'
BOLD_RED='\033[1;31m'
GREEN='\033[0;32m'
BOLD_GREEN='\033[1;32m'
UNDERLINE_BOLD_GREEN='\033[4;32m'
YELLOW='\033[0;33m'
BOLD_YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD_BLUE='\033[1;34m'
MAGENTA='\033[0;35m'
BOLD_MAGENTA='\033[1;35m'
CYAN='\033[0;36m'
BOLD_CYAN='\033[1;36m'
RESET='\033[0m' # Reset color to default

SCRIPT_NAME=$(basename "$0")

# Function to display usage information
usage() {0;32m'
BOLD_GREEN='\033[1;32m'
UNDERLINE_BOLD_GREEN='\033[4;32m'
YELLOW='\033[0;33m'
BOLD_YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD_BLUE='\033[1;34m'
MAGENTA='\033[0;35m'
BOLD_MAGENTA='\033[1;35m'
CYAN='\033[0;36m'
BOLD_CYAN='\033[1;36m'
RESET='\033[0m' # Reset color to default

SCRIPT_NAME=$(basename "$0")

# Function to display usage information
usage() {
    echo -e ""
    echo -e "${BOLD_GREEN}Usage:${RESET} ./$SCRIPT_NAME -d swarm_ip -p admin:password [-i new_log_level | -L new_node_log_level] [-t duration_in_seconds] [-D | --detach]"
    echo -e "  -d, --swarm_ip           IP address of the Swarm API endpoint. Supports single or multiple IPs separated by \",\", \";\" or \" \"."
    echo -e "                           If multiple IPs are provided, the script will update the log level for all nodes."
    echo -e "                           (Alternatively, set the SCSP_HOST environment variable to the Swarm IP.)"
    echo -e "  -p, --credentials        Credentials in the format admin:password"
    echo -e "  -i, --log.level          Set cluster log level to set (5, 10, 15, 20, 30, 40, 50)"
    echo -e "                           Aliases: chatter, debug, announce, info, error, critical, default."
    echo -e "  -L, --node.log.level     Set node log level to set (0, 5, 10, 15, 20, 30, 40, 50)"
    echo -e "                           Aliases: chatter, debug, announce, info, error, critical, default."
    echo -e "                           **Either -i or -L must be specified, but not both.**"
    echo -e ""  -t, --time          echo -e "${BOLD_GREEN}Usage:${RESET} ./$SCRIPT_NAME -d swarm_ip -p admin:password [-i new_log_level | -L new_node_log_level] [-t duration_in_seconds] [-D | --detach] (Optional) Duration in seconds to keep the new log level (must be greater than 0)"
    echo -e "  -dD, --swarm_ipdetach            IP address(Optional) ofDetach the script Swarmfrom APIthe endpoint.current Supportsterminal singleand orrun multiplein IPsa separateddetachable by \",\", \";\"session using screen or \tmux"
\"."     echo -e ""
    exit 1
}

# Default options
detachable=false
debug=false
output_log="castor-change-log-level_output.log"		# Log file for capturing detachable session output
# log_level_type="cluster"							# Default log level type
# default_log_level=30								# Default log  If multiple IPs are provided, the script will update the log level for all nodes."
    echo -e " level
log_file="/var/log/datacore/castor.log"		        # Default log file location
SCRIPTDIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
JQLOCATION=$SCRIPTDIR/jq
MAX_RETRIES=3                          (Alternatively, set the SCSP_HOST environment variable to the Swarm IP.)"    # echoMaximum -enumber "of password -p, --credentials retries attempts
username="admin"       Credentials in the format admin:password"     echo -e "  -i, --log.level          Set cluster log level to set# (5, 10, 15, 20, 30, 40, 50)"Default username
password=""         echo -e "                           Aliases: chatter, debug, announce, info,# error,Default critical,password
default."
# Global associative array echofor -elog "levels
declare -L, --node.log.levelA log_levels=(
    [5]="chatter"
Set node log level to set (0, 5, 10, 15, 20, 30, 40, 50) [10]="debug"
    [15]="audit"
    [20]="info"
    echo -e "[30]="warning"
    [40]="error"
    [45]="defect"
    [50]="critical"
    [60]="announce"
)

# Global associative array for log Aliases:level chatter,names
debug, announce, info, error, critical, default."
    echo -e "declare -A log_level_names=(
    ["chatter"]=5
    ["debug"]=10
    ["audit"]=15
    ["info"]=20
    ["warning"]=30
    ["error"]=40
    ["defect"]=45
 **Either -i or -L must be specified, but not both.**"
    echo -e "  -t, --time     ["critical"]=50
    ["announce"]=60
)

# Separate default values for cluster and node levels
declare -A default_log_levels=(
    ["cluster"]=30
     (Optional["node"]=0
)
Duration
# inSeparate secondsdefault tovalues keepfor thecluster newand lognode level (mustnames
be greater than 0)"
declare -A default_log_level_names=(
   echo -e  ["cluster"]="default"
 -D, --detach  ["node"]="default"
)

# Function to get the current timestamp
 timestamp(Optional) Detach{
  the script fromdate the current terminal and run in a detachable session using screen or tmux"
    echo -e ""
    exit 1
}

# Default options
detachable=false
debug=false
output_log="castor-change-log-level_output.log"		# Log file for capturing detachable session output
# log_level_type="cluster"							# Default log level type
# default_log_level=30								# Default log level
log_file="/var/log/datacore/castor.log"		  -u +"%Y-%m-%dT%H:%M:%S.%3NZ"
}

# Function to display debug messages if debug mode is enabled
debug_msg() {
    if $debug; then
        local caller_func=${FUNCNAME[1]:-main}                  # Fallback to "unknown" if empty
     # Default log file location
SCRIPTDIR=$( cd "$( dirname "local caller_line=${BASH_SOURCELINENO[0]}" )" && pwd )
JQLOCATION=$SCRIPTDIR/jq
MAX_RETRIES=3       :-unknown}            # Fallback to "unknown" if empty
        echo -e "$(timestamp) [DEBUG] ($caller_func:$caller_line) $*"
    fi
}

# Function to check if either 'screen' or 'tmux' is # Maximum number of password retries attempts
username="admin"            installed
check_screen_or_tmux() {
    if ! command -v screen &>/dev/null && ! command -v tmux &>/dev/null; then
        echo -e ""
        echo    # Default username
password=""-e "---------------------------------------------------------------------------------------"
        echo -e "  ${YELLOW}Warning${RESET}: Neither '${BOLD_GREEN}screen${RESET}' nor '${BOLD_GREEN}tmux${RESET}' is installed. Cannot run in detachable mode."
        echo -e "  Please install either  # Default password

# Global associative array for log levels
declare -A log_levels=(
    [5]="chatter"
    [10]="debug"
    [15]="audit"
    [20]="info"
    [30]="warning"
    [40]="error"
    [45]="defect"
    [50]="critical"
    [60]="announce"
)

# Global associative array for log level names
declare -A log_level_names=(
    ["chatter"]=5
    ["debug"]=10
    ["audit"]=15
    ["info"]=20
    ["warning"]=30
    ["error"]=40
    ["defect"]=45
    ["critical"]=50
    ["announce"]=60
)

# Separate default values for cluster and node levels
declare -A default_log_levels=(
    ["cluster"]=30
    ["node"]=0
)

# Separate default values for cluster and node level names
declare -A default_log_level_names=(
    ["cluster"]="default"
    ["node"]="default"
)

# Function to get the current timestamp
timestamp() {
    date -u +"%Y-%m-%dT%H:%M:%S.%3NZ"'${BOLD_GREEN}screen${RESET}' or '${BOLD_GREEN}tmux${RESET}' to run the script in a detachable session."
        echo -e "---------------------------------------------------------------------------------------"
        detachable=false  # Disable detachable session
        usage
    fi
}

# Function to format file size
format_size() {
    local size=$1
    if (( size >= 1073741824 )); then
        echo "$(awk "BEGIN {printf \"%.1fGB\", $size/1073741824}")"
    elif (( size >= 1048576 )); then
        echo "$(awk "BEGIN {printf \"%.1fMB\", $size/1048576}")"
    elif (( size >= 1024 )); then
        echo "$(awk "BEGIN {printf \"%.1fKB\", $size/1024}")"
    else
        echo "${size}B"
    fi
}

# Function to display debug messages if debug mode is enabled
debug_msg() { format duration
format_duration() {
    local duration=$1
    local hours=$((duration / 3600))
    local if $debug; then
   minutes=$(( (duration % 3600) / 60 ))
    local caller_func=${FUNCNAME[1]:-main}                  # Fallback to "unknown" if empty
seconds=$((duration % 60))
    printf "%02d:%02d:%02d" $hours $minutes $seconds
}

# Function to check if jq is available and set up JSON parsing method
check_jq() {
    for jq_path in "/usr/local caller_line=${BASH_LINENO[0]:-unknown}/bin/jq" "$(pwd)/jq"; do
        [[ -x  # Fallback to "unknown" if empty"$jq_path" ]] && echo "$jq_path" && return
    done
    echocommand -e "$(timestamp) [DEBUG] ($caller_func:$caller_line) $*"
    fi
}

# Function to check if either 'screen' or 'tmux' is installed
check_screen_or_tmux() {
    if ! command -v screen &>/dev/null && ! command -v tmux &>/dev/null; then
        echo -e ""
        echo -e "---------------------------------------------------------------------------------------"
        echo -e "  ${YELLOW}Warning${RESET}: Neither '${BOLD_GREEN}screen${RESET}' nor '${BOLD_GREEN}tmux${RESET}' is installed. Cannot run in detachable mode."v jq &>/dev/null && echo "jq" || echo "grep"
}

if [[ -f "$JQLOCATION" ]]; then
    jq_or_grep=$JQLOCATION
else
    jq_or_grep=$(check_jq)
fi
debug_msg "jq_or_grep: $jq_or_grep"
#jq_or_grep=$(check_jq)

# Function to determine the log file path
determine_log_file() {
    for log in "/var/log/datacore/castor.log" "/var/log/caringo/castor.log"; do
        if [[ -f "$log" ]]; then
            echo "$log"
            return
        fi
 echo -e "   done
   Please installecho either-e '"${BOLD_GREEN}screen${RESET}' or '${BOLD_GREEN}tmux${RESET}' to run the script in a detachable session."
        echo -e "---------------------------------------------------------------------------------------"
        echo -e "RED}Error: Log file not found.${RESET}"
    exit 1
}

log_file=$(determine_log_file)

# Function to check if credentials are valid
check_credentials() {
    local CREDENTIALS="$1"
    local SWARM_IP="$2"
    debug_msg "Credentials: [hidden  detachable=falsefor security]"
 # Disable detachable sessiondebug_msg "Swarm IP: $SWARM_IP"

    if# [[ "$detachable" == false ]]; thenAPI endpoint for validating user credentials
    local VALIDATE_URL="http://${SWARM_IP}:91/api/validateUser"
      debug_msg "detachable=${YELLOW}false${RESET}Validate URL: $VALIDATE_URL"

    # Make the API request
    debug_msg "StoppingValidating theuser scriptcredentials..."
    debug_msg "curl -s -u \"********\" -X GET  exit 1
  \"$VALIDATE_URL\" -H 'Content-Type: application/json'"
     fi
    fi
}

# Function to format file size
format_size() {
    local size=$1
    if (( size >= 1073741824 )); then
        echo "$(awk "BEGIN {printf \"%.1fGB\", $size/1073741824}")"
    elif (( size >= 1048576 ))RESPONSE=$(curl -s -u "$CREDENTIALS" -X GET "$VALIDATE_URL" -H 'Content-Type: application/json')
    debug_msg "Validate User Response: $RESPONSE"

    # Check if the response contains "isValid": true
    if echo "$RESPONSE" | "$jq_or_grep" -e '.isValid == true' > /dev/null 2>&1; then
        echodebug_msg "$(awk "BEGIN {printf \"%.1fMB\", $size/1048576}")"Authentication successful for user '${CREDENTIALS%%:*}'"
    elif (( size >= 1024return ));0 then # Success
    elif  echo "$(awk "BEGIN {printf \"%.1fKB\", $size/1024}")"
$RESPONSE" | "$jq_or_grep" -e '.isValid == false' > /dev/null 2>&1; then
   else     debug_msg "Authentication failed for echouser "'${sizeCREDENTIALS%%:*}B'"
    fi }  # Functionreturn to1 format duration
format_duration() {# Failure
    localelse
duration=$1     local hours=$((duration / 3600))  debug_msg "Error: Unable local minutes=$(( (duration % 3600) / 60 ))to validate credentials. Please check your inputs."
     local seconds=$((duration % 60))return 1  # Failure
printf "%02d:%02d:%02d" $hours $minutes $secondsfi
}

# Function to check if jq is available and set up JSON parsing method
check_jq() { print credentials - hide password
print_credentials() {
    local CREDENTIALS="$1"
    local USERNAME="${CREDENTIALS%%:*}"
    for jq_path in "/usr/local/bin/jq" "$(pwd)/jq"; do
        [[ -x "$jq_path" ]] && echo "$jq_path" && return
    doneecho -e "${GREEN}$USERNAME${RESET}":"${GREEN}********${RESET}"
}

# Parse input arguments
while [[ "$#" -gt 0 ]]; do
    case $1 in
       command -v jq &>/dev/null && echo "jq" || echo "grep"
}

if [[ -f "$JQLOCATION" ]]; then
    jq_or_grep=$JQLOCATION
else
    jq_or_grep=$(check_jq)
fi
debug_msg "jq_or_grep: $jq_or_grep"
#jq_or_grep=$(check_jq)

# Function to determine the log file path
determine_log_file() {
    for log in "/var/log/datacore/castor.log" "/var/log/caringo/castor.log"; do-d|--swarm_ip) swarm_ip="$2"; debug_msg "Set swarm_ip to $swarm_ip"; shift 2 ;;
        -p|--credentials)
            credentials="$2"
            shift 2 ;;
        -i|--log.level)
            if [[ -fn "$log$new_log_level" ]]; then
                echo "$logError: Options -i (cluster log leve) and -L (node log level) cannot be used together."
            return    usage
            fi
     done     echo -e "${RED}Error: Log file not found.${RESET}" log_level_type="cluster"
          exit 1 }if [[ ${log_file=$(determine_log_file)

# Function to check if credentials are valid
check_credentials() {
    local CREDENTIALS="$1"level_names[$2]} ]]; then
               local SWARMnew_log_IP="$2"level=${log_level_names[$2]}
    debug_msg "Credentials: [hidden for security]"    elif debug_msg "Swarm IP: $SWARM_IP"
[[ "$2" == "default" ]]; then
    # API endpoint for validating user credentials     local VALIDATEnew_log_URLlevel="http://${SWARM_IP}:91/api/validateUser"default_log_levels[$log_level_type]}
             debug_msg "Validate URL: $VALIDATE_URL new_log_level_name="default"
     # Make the API request   elif [[ debug_msg "Validating user credentials..."${log_levels[$2]} ]]; then
     debug_msg "curl -s -u \"********\" -X GET \"$VALIDATE_URL\" -H 'Content-Type: application/json'"
 new_log_level=$2
   RESPONSE=$(curl -s -u "$CREDENTIALS" -X GET "$VALIDATE_URL" -H 'Content-Type: application/json') else
    debug_msg "Validate User Response: $RESPONSE"      # Check ifecho the"Invalid responselog containslevel: $2"isValid":
 true     if echo "$RESPONSE" | "$jq_or_grep" -e '.isValid == true' > /dev/null 2>&1; thenexit 1
           debug_msg "Authenticationfi
successful for user '${CREDENTIALS%%:*}'"         return 0  # Successnew_log_level_name=${log_levels[$new_log_level]}
        elif echo "$RESPONSE" | "$jqdefault_or_grep" -e '.isValid == false' > /dev/null 2>&1; then
log_level=30
            debug_msg "Authentication failed for user '${CREDENTIALS%%:*}'Set new_log_level to $new_log_level ($new_log_level_name)"
        return 1  # Failureshift 2
   else         debug_msg "Error: Unable to validate credentials. Please check your inputs.";;
        -L|--node.log.level)
        return 1  # Failureif [[ -n "$new_log_level" ]]; fithen
}  # Function to print credentials - hide password print_credentials() {     localecho CREDENTIALS="$1"
    local USERNAME="${CREDENTIALS%%:*}"
    echo -e "${GREEN}$USERNAME${RESET}":"${GREEN}********${RESET}"
}

# Parse input arguments
while [[ "$#" -gt 0 ]]; do
    case $1 in"Error: Options -i (cluster log leve) and -L (node log level) cannot be used together."
                usage
            fi
            -d|--swarm_ip) swarm_ip="$2"; debug_msg "Set swarm_ip to $swarm_ip"; shift 2 ;;log_level_type="node"
            if [[ ${log_level_names[$2]}  -p|--credentials)]]; then
            credentials="$2"    new_log_level=${log_level_names[$2]}
        shift 2 ;;  elif [[ "$2" == "default" ]];  -i|--log.level)then
             if [[ -n "$new_log_level" ]]; then new_log_level=${default_log_levels[$log_level_type]}
                 echo "Error: Options -i (cluster log leve) and -L (node log level) cannot be used together."
      new_log_level_name="default"
            elif [[ ${log_levels[$2]} || $2 -eq 0 ]]; then
         usage       new_log_level=$2
     fi             new_log_level_typename="clusterdefault"
            if [[ ${log_level_names[$2]} ]]; thenelse
                echo "Invalid new_log_level=${log_level_names[$2]} level: $2"
                exit 1
  elif [[ "$2" == "default" ]]; then    fi
            new_log_level_name=${default_log_levels[$log$new_log_level_type]}
                newdefault_log_level_name="default"0
            elif [[ ${log_levels[$2]} ]]; then
debug_msg "Set new_log_level to $new_log_level ($new_log_level_name)"
            shift 2
 new_log_level=$2           ;;
  else      -t|--time)
           echo "Invalidif log level: [[ -n "$2" && "$2" != -* && "$2" -gt 0 ]]; then
      exit 1         duration="$2"
   fi             new_log_level_name=${log_levels[$new_log_level]} debug_msg "Set duration to $duration"
              default_log_level=30  shift 2
         debug_msg "Set new_log_level toelse
$new_log_level ($new_log_level_name)"             shift 2 echo -e ""
         ;;       echo -e -L|--node.log.level)
            if [[ -n "$new_log_level" ]]; then"---------------------------------------------------------------------------------------"
                echo -e echo "Error: Options -i (cluster log leve) and -L (node log level) cannot be used together ${RED}Error${RESET}: Duration must be a number greater than ${BOLD_GREEN}0${RESET}."
                usageecho -e "    Please specify the duration using the  fi
            log_level_type="node"${BOLD_GREEN}-t${RESET} ${GREEN}<seconds>${RESET} or ${BOLD_GREEN}--time${RESET} ${GREEN}<seconds>${RESET} option."
               if [[ ${log_level_names[$2]} ]]; thenecho -e "---------------------------------------------------------------------------------------"
                 new_log_level=${log_level_names[$2]}usage
            eliffi
[[ "$2" == "default" ]]; then       ;;
         new_log_level=${default_log_levels[$log_level_type]}
     -D|--detach) detachable=true; debug_msg "Set detachable to true"; shift ;;
        --debug) debug=true; new_log_level_name="default"
      debug_msg "Set debug to true with ${YELLOW}--debug${RESET}"; shift ;;
     elif [[ ${log_levels[$2]} || $2 -eq 0 ]]; then
                new_log_level=$2
         *) usage ;;
    esac
done

debug_msg "Set log_level_type to ${YELLOW}$log_level_type${RESET}"

# Check if 'screen' or 'tmux' is installed
if [[ "$detachable" == true ]]; then
    if [[  new_log_level_name="default"
 -z "$duration" ]]; then
        echo -e else""
        echo -e "---------------------------------------------------------------------------------------"
        echo -e "Invalid log level: $2"
           ${RED}Error${RESET}: Duration must be specified when running in detachable mode."
        exitecho 1-e "    Please specify the duration using the ${BOLD_GREEN}-t${RESET}  fi
or ${BOLD_GREEN}--time${RESET} option."
        echo   new_log_level_name=${log_levels[$new_log_level]}-e "---------------------------------------------------------------------------------------"
        usage
   default_log_level=0 fi
           debug_msg "Set new_log_level to $new_log_level ($new_log_level_name)"
            shift 2
            ;;
        -t|--time)
            check_screen_or_tmux
fi

# If swarm_ip is not provided, try using SCSP_HOST environment variable
if [[ -z "$swarm_ip" ]]; then
    if [[ -n "$2" && "$2" != -* && "$2" -gt 0$SCSP_HOST" ]]; then
        swarm_ip="$SCSP_HOST"
        debug_msg duration="$2"Using Swarm IP from SCSP_HOST: $swarm_ip"
    else
        debug_msgecho "Set duration to $duration"
    Error: swarm_ip not provided and SCSP_HOST is not set."
        usage
  shift 2 fi
fi

# Check if required arguments are provided
if  else
  [[ -z "$credentials" || -z "$new_log_level" ]]; then
    usage
fi

# Split the swarm_ip into an echoarray "Error:of DurationIP mustaddresses beif ait numbercontains greater than 0."
                exit 1
            fi
            ;;
        -D|--detach) detachable=true; debug_msg "Set detachable to true"; shift ;;
        --debug) debug=true;delimiters
IFS=';, ' read -r -a ip_array <<< "$swarm_ip"

# Validate credentials before proceeding
debug_msg "Validating credentials..."
#if [[ "$credentials" =~ ^[^:]+$ ]]; then
if [[ -n "$credentials" ]]; then
    CREDENTIALS="$credentials"
    ATTEMPT=$MAX_RETRIES
    USERNAME=""
    PASSWORD=""
    debug_msg "CREDENTIALS: $(print_credentials "$CREDENTIALS")"
    debug_msg "Set debug to true with ${YELLOW}--debug${RESET}"; shift ;;ATTEMPT: $ATTEMPT"

    # Check if   *) usage ;;
    esac
donecredentials contain a colon (username:password format)
    debug_msg "Set log_level_type to ${YELLOW}$log_level_type${RESET}"

# Check if 'screen' or 'tmux' is installedChecking for colon in credentials..."
    if [[ "$detachable$CREDENTIALS" == true*":"* ]]; then
    check_screen_or_tmux fi  # If swarmdebug_ipmsg is"Credentials notcontain provided,colon"
try using SCSP_HOST environment variable if [[ -z "$swarm_ip" ]]; thenUSERNAME="${CREDENTIALS%%:*}"
      if [[ -n "$SCSP_HOST" ]]; then PASSWORD="${CREDENTIALS#*:}"

        swarm_ip="$SCSP_HOST"     # Validate credentials
   debug_msg "Using Swarm IP from SCSP_HOST: $swarm_ipcheck_credentials "$CREDENTIALS" "${ip_array[0]}"
    else    if [[ $? -ne 0 ]]; echothen
"Error: swarm_ip not provided and SCSP_HOST is not set."    debug_msg "Invalid credentials. Please check usageyour username and password."
 fi fi  # Check if required arguments are provided if [[echo -ze "$credentials"
   ||  -z "$new_log_level" ]]; then    echo usage
fi

# Split the swarm_ip into an array of IP addresses if it contains delimiters
IFS=';, ' read -r -a ip_array <<< "$swarm_ip"

# Validate credentials before proceeding
debug_msg "Validating credentials..."
#if [[ "$credentials" =~ ^[^:]+$ ]]; then
if [[ -n "$credentials" ]]; then
    CREDENTIALS="$credentials"
    ATTEMPT=$MAX_RETRIES
    USERNAME=""
    PASSWORD=""
    debug_msg "CREDENTIALS: $(print_credentials "$CREDENTIALS")"-e "${RED}Error${RESET}: Invalid credentials. Please check your username and password."
            echo -e ""
            exit 1
        fi
        debug_msg "Validate credentials"
        credentials=$CREDENTIALS
    else
        debug_msg "ATTEMPT: $ATTEMPT"

Credentials do not contain password"
   # Check if credentials contain a colon (username:password format)debug_msg "Username: $(print_credentials "$CREDENTIALS")"
    debug_msg "Checking for colon in credentials... USERNAME="$CREDENTIALS"
    if [[ "$CREDENTIALS" == *":"* ]]; then
 # Prompt for password
       debug_msg "Credentialswhile contain[[ colon"$ATTEMPTS -lt $MAX_RETRIES ]]; do
    USERNAME="${CREDENTIALS%%:*}"        echo PASSWORD="${CREDENTIALS#*:}"
         # Validate credentials read -sp "Enter password for    check_credentials "$CREDENTIALS" "${ip_array[0]}"
user $USERNAME: " PASSWORD
       if [[ $? -ne 0 ]]; thenecho ""
            debug_msgecho ""Invalid
 credentials. Please check your username and password."     if [[ -z "$PASSWORD" ]]; then
  echo -e ""             echo -e "${RED}Error${RESET}: InvalidPassword credentials.cannot Pleasebe checkempty."
your username and password."             echo -e ""((ATTEMPTS++))
              exit 1 continue
       fi     fi
   debug_msg "Validate credentials"         credentials=$CREDENTIALS
CREDENTIALS="$USERNAME:$PASSWORD"
   else         debug_msg "Credentials do not contain password"CREDENTIALS: $(print_credentials "$CREDENTIALS"))"
          debug_msg "Username: $(print check_credentials "$CREDENTIALS")" "${ip_array[0]}"
            if [[  USERNAME="$CREDENTIALS"
  $? -eq 0 ]]; then
     # Prompt for password         while [[ $ATTEMPTS -lt $MAX_RETRIES ]]; dodebug_msg "CREDENTIALS: $(print_credentials "$CREDENTIALS"))"
                echodebug_msg "Valid credentials"
            read  -sp "Enter passwordcredentials=$CREDENTIALS
for user $USERNAME: " PASSWORD            break
echo ""           else
 echo ""             if [[echo -ze "$PASSWORD"
]]; then                 echo -e "${RED}Error${RESET}: Password cannot be empty Invalid credentials. Please check your username and password."
            fi
            ((ATTEMPTS++))
        done
        if [[ $ATTEMPTS -ge $MAX_RETRIES ]]; then
            echo -e ""
  continue          echo -e "${RED}Error${RESET}: Maximum number fiof password attempts reached. Exiting script."
       CREDENTIALS="$USERNAME:$PASSWORD"     exit 1
      debug_msg "CREDENTIALS: $(print_credentials "$CREDENTIALS"))" fi
    fi
else
    if !  check_credentials "$CREDENTIALS$credentials" "${ip_array[0]}"; then
        echo -e  if [[ $? -eq 0 ]]; then""
        echo -e "${YELLOW}Warning${RESET}: ${RED}Invalid credentials${RESET}."
        echo -e ""
        exit debug_msg "CREDENTIALS: $(print_credentials "$CREDENTIALS"))"
               1
    fi
fi

# Retrieve cluster name and handle JSON parsing using the first IP address
debug_msg "Valid credentials"
      Retrieving the cluster name from Swarm API using IP: ${ip_array[0]}"
if [[ "$jq_or_grep" == "grep" ]]; then
    clusterName=$(curl --user "$credentials" -sS  credentials=$CREDENTIALS
   "http://${ip_array[0]}:91/api/storage/clusters" | grep -oP '"name":\s*"\K[^"]+')
else
    clusterName=$(curl --user "$credentials" -sS "http://${ip_array[0]}:91/api/storage/clusters" | "$jq_or_grep"  break
       -r '._embedded.clusters[0].name')
fi

if [[ -z "$clusterName" ]]; then
    elseecho "Failed to retrieve the cluster name. Please check your inputs."
    exit 1
echo -efi
debug_msg ""Cluster Name: $clusterName"

# Main logic function to run the script tasks
    echo -e "${RED}Error${RESET}: Invalid credentials. Please check your username and password.main_script() {
    local swarm_ip="$1"
    local credentials="$2"
    local  finew_log_level="$3"
    local new_log_level_name="$4"
    local duration="$5"
 ((ATTEMPTS++))   local log_level_type="$6"
    done
   #local log_file="/var/log/datacore/castor.log"
    if [[ $ATTEMPTS -gez $MAX"$log_RETRIESfile" ]]; then
        log_file=$(determine_log_file)
   echo fi
    local initial_size=$(stat -ec%s "$log_file" 2>/dev/null || echo 0)
    local current_log_level
   echo -elocal "${RED}Error${RESET}: Maximum number of password attempts reached. Exiting script.clusterName="$7"
    local jq_or_grep="$8"
    local detachable="$9"
    if [[ exit 1
"$detachable" ]]; then
       fi     fi
elselocal debug="${10}"
      if ! check_credentialseval "$credentials"$(echo "${ip_array[0]11}"; then| sed        echo -e ""'s/declare -A/declare -A/')"
        eval "$(echo -e "${YELLOW}Warning${RESET}: ${RED}Invalid credentials${RESET}.12}" | sed 's/declare -A/declare -A/')"
    fi
   echo -e "local default_log_levels="$13"
    local default_log_levels_name="$14"
  exit 1     fi
fi

# Retrieve cluster name and handle JSON parsing using the first IP address
debug_msg "**********************************************************"
    debug_msg "local variables"
    debug_msg "RetrievingLog the cluster name from Swarm API using IPLevel Type: ${ip_array[0]}"
if [[ "$jq_or_grep" == "grep" ]]; then
    clusterName=$(curl --user "$credentials" -sS "http://${ip_array[0]}:91/api/storage/clusters" | grep -oP '"name":\s*"\K[^"]+')
else
    clusterName=$(curl --user "$credentials" -sS "http://${ip_array[0]}:91/api/storage/clusters" | "$jq_or_grep" -r '._embedded.clusters[0].name')
fi

if [[ -z "$clusterName" ]]; then
    echo "Failed to retrieve the cluster name. Please check your inputs."
    exit 1
fi
debug_msg "Cluster Name: $clusterName"

# Main logic function to run the script tasks
main_script() {
    local swarm_ip="$1"
    local credentials="$2"
    local new_log_level="$3"
    local new_log_level_name="$4"
    local duration="$5"
    local log_level_type="$6"
    #local log_file="/var/log/datacore/castor.log"
    if [[ -z "$log_file" ]]; then
        log_file=$(determine_log_file)
    fi
    local initial_size=$(stat -c%s "$log_file" 2>/dev/null || echo 0)
    local current_log_level
    local clusterName="$7"
    local jq_or_grep="$8"
    local detachable="$9"
GREEN}$log_level_type${RESET}"
    debug_msg "Default Log Level: ${GREEN}$default_log_level${RESET}"
    debug_msg "Swarm IP: $swarm_ip"
    debug_msg "Credentials: $(print_credentials "$credentials")"
    debug_msg "New Log Level: $new_log_level"
    debug_msg "New log Level Name: $new_log_level_name"
    debug_msg "Duration: $duration"
    debug_msg "Log File: $log_file"
    debug_msg "Initial Log File Size: $initial_size"
    debug_msg "Current Log Level: $current_log_level"
    debug_msg "Cluster Name: $clusterName"
    debug_msg "jq_or_grep: $jq_or_grep"
    debug_msg "Detach: $detachable"
    debug_msg "Debug: $debug"
    debug_msg "**********************************************************"

    # Split the swarm_ip into an array of IP addresses
    IFS=';, ' read -r -a ip_array <<< "$swarm_ip"
    debug_msg "IP Array: ${ip_array[*]}"

    # Display initial information
    if [[ "$detachable$log_level_type" == "cluster" ]]; then
        echo local-e debug="${10Swarm IP: ${GREEN}${ip_array[0]}${RESET}"
    else
   eval "$(echo "${11     echo -e "Swarm IPs: ${GREEN}${ip_array[*]}${RESET}"
| sed 's/declare -A/declare -A/')" fi
    # echo -e "Swarm evalIP: "$(echo "${12}" | sed 's/declare -A/declare -A/')"
    fi
    local default_log_levels="$13${GREEN}$swarm_ip${RESET}"
    debug_msg "Credentials: ${GREEN}[hidden for security]${RESET}"
    echo -e "Cluster Name: ${GREEN}$clusterName${RESET}"

   local default_log_levels_name="$14"debug_msg "Starting main_script function..."
    debug_msg "**********************************************************"
    debug_msg "local variables"
    debug_msg "Log Level Type: ${GREEN}$log_level_type${RESET}"Log level type: $log_level_type"

    # Store the original log levels
    declare -A original_log_levels
    declare -A original_log_level_names

    default_log_level=${default_log_levels[$log_level_type]}
    default_log_level_name=${default_log_level_names[$log_level_type]}
    debug_msg "Default Loglog Levellevel: ${GREEN}$default_log_level${RESET}level"
    debug_msg "Swarm IP: $swarm_ip"
    debug_msg "Credentials: $(print_credentials "$credentials")"Default log level name: $default_log_level_name"

    if [[ $log_level_type == "cluster" ]]; then
         debug_msg "New Log Level: Setting cluster log level to $new_log_level ($new_log_level_name)"
      debug_msg "New log# LevelRetrieve Name:current $new_log_ level_name"
    debug_msg "Duration: $duration"  if   debug_msg "Log File: $log_file"
    debug_msg "Initial Log File Size: $initial_size"[[ "$jq_or_grep" == "grep" ]]; then
            debug_msg "Current Log Level: $current_log_level"
    debug_msg "Cluster Name: $clusterName"current_log_level=$(curl --user "$credentials" -sS "http://${ip_array[0]}:91/api/storage/clusters/$clusterName/settings/log.level" | grep -oP '"value":\s*\K[0-9]+')
     debug_msg "jq_or_grep: $jq_or_grep" else
   debug_msg "Detach: $detachable"     debug_msg "Debug: $debug"
    debug_msg "**********************************************************"

    # Split the swarm_ip into an array of IP addresses
    IFS=';, ' read -r -a ip_array <<< "$swarm_ip"
    debug_msg "IP Array: ${ip_array[*]}"

    # Display initial information
    if [[ "$log_level_type" == "cluster" ]]; then
        echo -e "Swarm IP: ${GREEN}${ip_array[0]}${RESET}"
    else current_log_level=$(curl --user "$credentials" -sS "http://${ip_array[0]}:91/api/storage/clusters/$clusterName/settings/log.level" | "$jq_or_grep" -r '.value')
        fi
        current_log_level_name=${log_levels[$current_log_level]}
        if [[ $current_log_level == 30 ]]; then
            current_log_level_name="default"
        fi
        if [[ $new_log_level == 30 ]]; then
        echo -e "Swarm IPs: ${GREEN}${ip_array[*]}${RESET} new_log_level_name="default"
    fi    fi
# echo -e "Swarm IP: ${GREEN}$swarm_ip${RESET}"     debug_msg "Credentials: ${GREEN}[hidden for security]${RESET}"
    echo -e "Cluster NameCurrent cluster log level: ${BOLD_GREEN}$clusterName$$current_log_level${RESET}"

    debug_msg "Starting main_script function...""
        debug_msg "LogCurrent log level typename: $log${BOLD_GREEN}$current_log_level_typename${RESET}"
      # Store theecho original-e log""
levels     declare -A original_log_levels echo -e "New cluster declarelog -A originallevel: ${BOLD_GREEN}$new_log_level_namesname${RESET} (${BOLD_GREEN}$new_log_level${RESET})"
        default_log_level=${default_log_levels[$log_level_type]}
    default_log_level_name=${default_log_level_names[$log_level_type]}echo -e "Current cluster log level is ${BOLD_GREEN}$current_log_level_name${RESET} (${BOLD_GREEN}$current_log_level${RESET})."

   debug_msg "Default log level: $default_log_level"
    debug_msg "Default log level name: $default_log_level_name"     # Skip update if new level matches the current level
        if [[ $log"$current_log_level_type == "cluster" -eq "$new_log_level" ]]; then
        debug_msg "Setting cluster    echo ""
            echo -e "Cluster log level is already set to ${BOLD_GREEN}$new_log_level_name${RESET} (${BOLD_GREEN}$new_log_level_name)"level${RESET}). No changes made."
            return
 #  Retrieve current log level  fi

     if [[ "$jq_or_grep" == "grep" ]]; then
    # Update the cluster log level using PUT
        current_log_level=$(curl --user "$credentials" -sS "http://${ip_array[0]}:91/api/storage/clusters/$clusterName/settings/log.level" | grep -oP '"value":\s*\K[0-9]+')
debug_msg "Updating cluster log level to $new_log_level_name"
        debug_msg "Cluster Name: $clusterName"
       else debug_msg "Credentials: $(print_credentials "$credentials")"
        current_log_level=$(debug_msg "curl --user \"$(print_credentials "$credentials")\" -sS -X PUT -H \"Content-Type: application/json\" \"http://${ip_array[0]}:91/api/storage/clusters/$clusterName/settings/log.level\" |-d "$jq_or_grep" -r '.value')
        fi
        current_log_level_name=${log_levels[$current\"{\\\"value\\\": $new_log_level]}\""
        if [[ $current_log_level == 30 ]]; then
            current_log_level_name="default"
        firesponse=$(curl --user "$credentials" -sS -X PUT -H "Content-Type: application/json" \
          if [[ $new_log_level == 30 ]]; then "http://${ip_array[0]}:91/api/storage/clusters/$clusterName/settings/log.level" \
            -d   new"{\"value\": $new_log_level_name="default"}")
        fi
 debug_msg "Response: $response"

     debug_msg "Current cluster logif level: ${BOLD_GREEN}$current_log_level${RESET}"
[[ "$jq_or_grep" == "grep" ]]; then
       debug_msg "Current log level name: ${BOLD_GREEN}$current updated_log_level_name${RESET}=$(echo "$response" | grep -oP '"value":\s*\K[0-9]+')
    echo -e ""  else
      echo -e "New cluster log level: ${BOLD_GREEN}$new updated_log_level_name${RESET} (${BOLD_GREEN}$new_log_level${RESET})"
        echo -e "Current cluster log level is ${BOLD_GREEN}$current_log_level_name${RESET} (${BOLD_GREEN}$current_log_level${RESET})."=$(echo "$response" | "$jq_or_grep" -r '.value')
        fi
    # Skip update if new level matches the current leveldebug_msg "Updated cluster log level: $updated_log_level"

        if [[ "$current$updated_log_level" -eq "$new_log_level" ]]; then
            echo ""
            echo -e "Cluster log level is already set to -e "${GREEN}$(timestamp)${RESET} Log level changed successfully from ${BOLD_GREEN}$current_log_level_name${RESET} (${BOLD_GREEN}$current_log_level${RESET}) -> ${BOLD_GREEN}$new_log_level_name${RESET} (${BOLD_GREEN}$new_log_level${RESET})."
        else
            echo -e "${GREEN}$(timestamp)${RESET} Failed to update log level. Response: ${RED}$response${RESET})."
No changes made."          exit  1
return         fi

        # UpdateCountdown theand clusterrevert log level
using  PUT      if [[  debug_msg "Updating cluster-n "$duration" && "$duration" -gt 0 ]]; then
            echo -e "Keeping log level toat ${YELLOW}$new_log_level_name"
        debug_msg "Cluster Name: $clusterName"name${RESET} (${YELLOW}$new_log_level${RESET}) for ${YELLOW}$duration${RESET} seconds (${YELLOW}$(format_duration $duration)${RESET}) ..."
           debug_msg "Credentials: $(print_credentials "$credentials") echo -e ""
        debug_msg "curl --user \"$(print_credentials "$credentials")\" -sS -X PUT -H \"Content-Type: application/json\" \"http://${ip_array[0]}:91/api/storage/clusters/$clusterName/settings/log.level\" -d \"{\\\"value\\\": $new_log_level}\""    for ((i=duration; i>0; i--)); do
             response=$(curl --user "$credentials" -sSprintf -X PUT -H "Content-Type: application/json" \
      v countdown "%02d:%02d:%02d" $((i/3600)) $(( (i%3600) / 60 )) $((i%60))
     "http://${ip_array[0]}:91/api/storage/clusters/$clusterName/settings/log.level" \          echo   -dne "Countdown: ${YELLOW}$countdown${RESET} remaining...\r"value\":
$new_log_level}")         debug_msg "Response: $response"     sleep 1
   if [[ "$jq_or_grep" == "grep" ]]; then   done
         updated_log_level=$(echo "$response" | grepecho -oPe '"value":\s*\K[0-9]+')
        else\n\nTime's up! Reverting log level back to ${GREEN}$current_log_level_name${RESET} (${BOLD_GREEN}$current_log_level${RESET}) ..."

            # Revert the updated_log_ level=$(echo "$response" | "$jq_or_grep" -r '.value') back to the original value
        fi    #     debug_msgecho -e "UpdatedLevel clusterlog logrevert level: $updated_log_levelon $(timestamp)"
         if  [[ "$updated_log_level" -eq "$new_log_level" ]]; then
     response=$(curl --user "$credentials" -sS -X PUT -H "Content-Type: application/json" \
       echo -e "${GREEN}$(timestamp)${RESET} Log level changed successfully from ${BOLD_GREEN}$current_log_level_name${RESET} (${BOLD_GREEN}$current_log_level${RESET}) -> ${BOLD_GREEN}$new_log_level_name${RESET} (${BOLD_GREEN}$new_log_level${RESET})."
 "http://${ip_array[0]}:91/api/storage/clusters/$clusterName/settings/log.level" \
       else             echo -e-d "${GREEN}$(timestamp)${RESET} Failed to update log level. Response: ${RED}$response${RESET}"{\"value\": $current_log_level}")
            exit 1
debug_msg "Response: $response"

      fi      if [[ "$jq_or_grep" ==  # Countdown and revert log level"grep" ]]; then
              if [[ -n "$duration" && "$duration" -gt 0 ]]; then
  reverted_log_level=$(echo "$response" | grep -oP '"value":\s*\K[0-9]+')
            else
          echo -e "Keeping log level at ${YELLOW}$newreverted_log_level_name${RESET} (${YELLOW}$new_log_level${RESET}) for ${YELLOW}$duration${RESET} seconds (${YELLOW}$(format_duration $duration)${RESET}) ..."
=$(echo "$response" | "$jq_or_grep" -r '.value')
            fi
           echo -e "debug_msg "Reverted cluster log level: $reverted_log_level"

           for ((i=duration; i>0; i--)); do
  final_size=$(stat -c%s "$log_file" 2>/dev/null || echo 0)
            debug_msg printf"Initial -vlog countdownfile "%02d:%02d:%02d" $((i/3600)) $(( (i%3600) / 60 )) $((i%60))
      size: $initial_size"
            debug_msg "Final log file size: $final_size"
         echo -ne "Countdown: ${YELLOW}$countdown${RESET} remaining...\r"
   size_diff=$(( final_size - initial_size ))
             sleep 1size_diff_formatted=$(format_size "$size_diff")
             done
duration_formatted=$(format_duration "$duration")
	    if (( size_diff < 0 )); then
 		echo -e "\n\nTime's up! Reverting castor.log levelfile back to ${GREEN}$current_log_level_name${RESET} (${BOLD_GREEN}$current_log_level${RESET}) ..."
was rotated."
	    else
        # Revert the log level back to the originalecho value-e ""
           # echo -e "Level log revert on $(timestamp)"
            response=$(curl --user "$credentials" -sS -X PUT -H "Content-Type: application/json" \
Approximate ${BOLD_GREEN}$size_diff_formatted${RESET} new logs were generated at log level ${BOLD_GREEN}$new_log_level_name${RESET} (${BOLD_GREEN}$new_log_level${RESET}). Current castor.log size is ${BOLD_GREEN}$(format_size "$final_size")${RESET} after ${YELLOW}$duration${RESET} seconds (${YELLOW}$duration_formatted${RESET})."
            echo   "http://${ip_array[0]}:91/api/storage/clusters/$clusterName/settings/log.level" \-e ""
	    fi
        if [[ "$reverted_log_level"  -deq "{\"value\": $current_log_level}") ]]; then
          debug_msg "Response: $response"echo -e "${GREEN}$(timestamp)${RESET} Log level reverted successfully back to      if [[ "$jq_or_grep" == "grep" ]]; then
 ${BOLD_GREEN}$current_log_level_name${RESET} (${BOLD_GREEN}$current_log_level${RESET})."
              reverted_log_level=$(echo "$response" | grep -oPe '"value":\s*\K[0-9]+')
            else
            echo    reverted_log_level=$(echo "$response" | "$jq_or_grep" -r '.value')
 -e "${GREEN}$(timestamp)${RESET} Failed to revert log level. Response: ${RED}$response${RESET}"
          fi  echo           debug_msg "Reverted cluster log level: $reverted_log_level"-e ""
            exit 1
   final_size=$(stat -c%s "$log_file" 2>/dev/null || echo 0)fi
        else
   debug_msg "Initial log file size: $initial_size"    echo -e "${GREEN}$(timestamp)${RESET} Log level change is permanent until debug_msgmanually modified."Final
log file size: $final_size"     fi
    elif [[  size_diff=$(( final_size - initial_size ))
"$log_level_type" == "node" ]]; then
        # First  size_diff_formatted=$(format_size "$size_diff")
  loop: Change the node log level
        local durationsame_log_formatted=$(format_duration "$duration")
	level=false
      if (( size_difffor <ip 0 )); then
		echo -e "castor.log file was rotated."
	in "${ip_array[@]}"; do
          else  # Retrieve current node log level
     echo -e ""     debug_msg "Retrieving current node log level for IP: $ip"
echo -e "Approximate ${BOLD_GREEN}$size_diff_formatted${RESET} new logs were generated at log level ${BOLD_GREEN}$new_log_level_name${RESET} (${BOLD_GREEN}$new_log_level${RESET}). Current castor.log size is ${BOLD_GREEN}$(format_size "$final_size")${RESET} after ${YELLOW}$duration${RESET} seconds (${YELLOW}$duration_formatted${RESET})." debug_msg "curl -s -u \"$(print_credentials "$credentials")\" \"http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel\""
            echocurrent_log_level=$(curl -s -eu ""
	 $credentials" "http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel" | $jq_or_grep -r '.value')
  fi         if [[debug_msg "$reverted_Current log_level" -eq " level for IP $ip: $current_log_level"
]]; then           original_log_levels["$ip"]=$current_log_level
 echo -e "${GREEN}$(timestamp)${RESET} Log level reverted successfully back to ${BOLD_GREEN}$current_log_level_name${RESET} (${BOLD_GREEN}$current_log_level${RESET}).          debug_msg "Original log level for IP $ip: ${original_log_levels["$ip"]}"
            echoif -e[[ "$current_log_level" == "0" ]]; then
    else             echo -e "${GREEN}$(timestamp)${RESET} Failed to revert log level. Response: ${RED}$response${RESET}"current_log_level_name="default"
            else
    echo -e ""          current_log_level_name=${log_levels[$current_log_level]}
  exit 1         fi

       else     original_log_level_names["$ip"]=$current_log_level_name
       echo -e "${GREEN}$(timestamp)${RESET} Log level change is permanent until manually modified.debug_msg "Original log level name: ${original_log_level_names["$ip"]}"
        fi    if elif [[ "$log$new_log_level_type" == "node0" ]]; then
         #  First loop: Change the node new_log level_level_name="default"
            else
 local same_log_level=false            for ip in " new_log_level_name=${iplog_array[@levels[$new_log_level]}";
do             # Retrieve current node log levelfi
            debug_msg "Retrieving currentCurrent node log level for IP $ip: $ip $current_log_level ($current_log_level_name)"
            debug_msgecho "curl"
 -s -u \"$(print_credentials "$credentials")\" \"http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel\""       echo -e "New node log  currentlevel: ${BOLD_GREEN}$new_log_level=$(curl -s -u "$credentials" "http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel" | $jq_or_grep -r '.value')_name${RESET} (${BOLD_GREEN}$new_log_level${RESET})"
            echo debug_msg-e "Current node log level for IP $ip: ${BOLD_GREEN}$ip${RESET} is ${BOLD_GREEN}$current_log_level"
            original_log_levels["$ip"]=_name${RESET} (${BOLD_GREEN}$current_log_levellevel${RESET})."

            debug_msg "Original log# Skip update if new level formatches IPthe $ip: ${original_log_levels["$ip"]}"current level
            if [[ "$current_log_level" ==-eq "0$new_log_level" ]]; then
                currentsame_log_level_name="default=true
                echo ""
            else    echo -e "Node log level for IP ${BOLD_GREEN}$ip${RESET} is already set   currentto ${BOLD_GREEN}$new_log_level_name=name${RESET} (${log_levels[$currentBOLD_GREEN}$new_log_level]}level${RESET}). No changes made."
            fi    continue
          original_log_level_names["$ip"]=$current_log_level_name
  else
           debug_msg "Original log level name: ${originalsame_log_level_names["$ip"]}"=false
            fi

            if [[ "$new$same_log_level" == "0"false ]]; then
  
             new_log_level_name="default"         # Update the node log level using elsePUT
                new_log_level_name=${log_levels[debug_msg "Updating node log level to $new_log_level]}_name for IP $ip..."
         fi       debug_msg "curl --user \"$(print_credentials "$credentials")\"  debug_msg "Current log level for IP $ip: $current_log_level ($current_log_level_name)-sS -X PUT -H \"Content-Type: application/json\" \"http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel?value=$new_log_level\""
            echo ""             echo -e "New node log level: ${BOLD_GREEN}$new_log_level_name${RESET} (${BOLD_GREEN}$new_log_level${RESET})"
 response=$(curl --user "$credentials" -sS -X PUT -H "Content-Type: application/json" \
          echo -e "Current node log level for IP ${BOLD_GREEN}$ip${RESET} is ${BOLD_GREEN}$current "http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel?value=$new_log_level_name${RESET} (${BOLD_GREEN}$current_log_level${RESET})."")
              # Skip update if new level matches the current leveldebug_msg "Response: $response"

                if [[ "$current$jq_logor_levelgrep" -eq== "$new_log_levelgrep" ]]; then
                same    updated_log_level=true
    $(echo "$response" | grep -oP '"value":\s*\K[0-9]+')
           echo ""    else
            echo -e "Node log level for IP ${BOLD_GREEN}$ip${RESET} is already set to ${BOLD_GREEN}$new_log_level_name${RESET} (${BOLD_GREEN}$new_log_level${RESET}). No changes made."
  updated_log_level=$(echo "$response" | "$jq_or_grep" -r '.value')
              continue             elsefi
                same_log_level=false
      debug_msg "Updated log level for IP $ip: $updated_log_level"

    fi              if [[ "$same$updated_log_level" == false-eq "$new_log_level" ]]; then
                # Update the node    echo -e "${GREEN}$(timestamp)${RESET} Node log level using PUT
     for IP ${BOLD_GREEN}$ip${RESET} changed successfully from ${BOLD_GREEN}$current_log_level_name${RESET} (${GREEN}$current_log_level${RESET}) -> ${BOLD_GREEN}$new_log_level_name${RESET} (${BOLD_GREEN}$new_log_level${RESET})."
          debug_msg "Updating node log level to $new_log_level_name for IP $ip..." else
                   debug_msg "curlecho --usere \"$(print_credentials "$credentials")\" -sS -X PUT -H \"Content-Type: application/json\" \"http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel?value=$new_log_level\""{GREEN}$(timestamp)${RESET} Failed to update node log level for IP ${BOLD_GREEN}$ip${RESET}. Response: ${RED}$response${RESET}"
                   response=$(curl --user "$credentials" -sS -X PUT -H "Content-Type: application/json" \ exit 1
                fi
          "http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel?value=$new_log_level")  fi
        done

    debug_msg "Response: $response"  # Second loop: Countdown if duration is provided
        if [[ "$jq$same_orlog_greplevel" == "grep"false ]]; then\
            if [[ -n "$duration" && "$duration" -gt  updated_log_level=$(echo "$response" | grep -oP '"value":\s*\K[0-9]+')0 ]]; then
                echo -e ""
  else              echo -e "Keeping node(s) log level at updated${YELLOW}$new_log_level=$(echo "$response" | "$jq_or_grep" -r '.value')_name${RESET} (${YELLOW}$new_log_level${RESET}) for ${YELLOW}$duration${RESET} seconds (${YELLOW}$(format_duration $duration)${RESET}) ..."
                fiecho -e ""
              debug_msg "Updated log level for IP $ip: $updated_log_level"for ((i=duration; i>0; i--)); do
                  if [[ "$updated_log_level" -eq "$new_log_level" ]]; then printf -v countdown "%02d:%02d:%02d" $((i/3600)) $(( (i%3600) / 60 )) $((i%60))
                    echo -ene "Countdown: ${GREEN}$(timestamp)$YELLOW}$countdown${RESET} Node log level for IP ${BOLD_GREEN}$ip${RESET} changed successfully from ${BOLD_GREEN}$current_log_level_name${RESET} (${GREEN}$current_log_level${RESET}) -> ${BOLD_GREEN}$new_log_level_name${RESET} (${BOLD_GREEN}$new_log_level${RESET})."
   remaining...\r"
                    sleep 1
            else    done
                echo -e "${GREEN}$(timestamp)${RESET} Failed to update \n\nTime's up! Reverting node log level back forto IP ${BOLD_GREEN}$ip${RESET}. Response: ${RED}$response${RESET}original levels..."

                # Third loop: exitRevert 1the node log level
             fi   for ip in "${ip_array[@]}"; do
     fi         done      current_log_level=${original_log_levels["$ip"]}
   # Second loop: Countdown if duration is provided         if [[ "$same current_log_level_name=${original_log_level_names["$ip"]}
== false ]]; then\             if [[ -n "$duration" && debug_msg "$duration"Reverting -gtnode 0log ]];level thenback      to $current_log_level_name for IP $ip..."
          echo -e ""        debug_msg "curl --user \"$(print_credentials "$credentials")\" -sS -X PUT  echo -e "Keeping node(s) log level at ${YELLOW}$new_log_level_name${RESET} (${YELLOW}$new_log_level${RESET}) for ${YELLOW}$duration${RESET} seconds (${YELLOW}$(format_duration $duration)${RESET}) ...-H \"Content-Type: application/json\" \"http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel?value=$current_log_level\""
                echo -e ""  # echo -e "Node level log revert on $(timestamp) for IP $ip"
   for ((i=duration; i>0; i--)); do             response=$(curl --user "$credentials" -sS     printf-X PUT -v countdownH "%02d:%02d:%02d" $((i/3600)) $(( (i%3600) / 60 )) $((i%60))Content-Type: application/json" \
                      echo -ne "Countdown: ${YELLOW}$countdown${RESET} remaining...\r"
     http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel?value=$current_log_level")
              sleep 1     debug_msg "Response: $response"

        done            if [[ "$jq_or_grep"   echo -e "\n\nTime's up! Reverting node log level back to original levels..."== "grep" ]]; then
                   # Third loop: Revert the node reverted_log _level=$(echo "$response" | grep           -oP '"value":\s*\K[0-9]+')
  for ip in "${ip_array[@]}"; do                 else
   current_log_level=${original_log_levels["$ip"]}                     currentreverted_log_level_name=${original_log_level_names["$ip"]}
   (echo "$response" | "$jq_or_grep" -r '.value')
                debug_msg "Reverting node log level back to $current_log_level_name for IP $ip..." fi

                    debug_msg "curl --user \"$(print_credentials "$credentials")\" -sS -X PUT -H \"Content-Type: application/json\" \"http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel?value=$current_log_level\""
       final_size=$(stat -c%s "$log_file" 2>/dev/null || echo 0)
            # echo -e "Node level log revert on size_diff=$((timestamp) forfinal_size IP $ip"
- initial_size ))
                   response=$(curl --user "$credentials" -sS -X PUT -H "Content-Type: application/json" \ debug_msg "Initial log file size: $initial_size"
                    debug_msg "Final log file  "http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel?value=$current_log_level")size: $final_size"
                    debug_msg "Response: $response"
size_diff_formatted=$(format_size "$size_diff")
                    if [[ "$jq_or_grep" == "grep" ]]; then   duration_formatted=$(format_duration "$duration")

                    if     reverted[[ "$reverted_log_level=$(echo "$response" | grep -oPeq '"value":\s*\K[0-9]+')
$current_log_level" ]]; then
                   else     echo -e "${GREEN}$(timestamp)${RESET} Node log level for IP ${BOLD_GREEN}$ip${RESET} reverted successfully back         revertedto ${BOLD_GREEN}${current_log_level=$(echo "$response" | "$jq_or_grep" -r '.value')_name}${RESET} (${BOLD_GREEN}$current_log_level${RESET})."
                    fielse
                     final_size=$(stat -c%s "$log_file" 2>/dev/null || echo 0)
                    size_diff=$(( final_size - initial_size ))
                    debug_msg "Initial log file size: $initial_size"-e "${GREEN}$(timestamp)${RESET} Failed to revert node log level for IP ${BOLD_GREEN}$ip${RESET}. Response: ${RED}$response${RESET}"
                       debug_msg "Finalexit log1
file size: $final_size"                  fi
  size_diff_formatted=$(format_size "$size_diff")             done

      duration_formatted=$(format_duration "$duration")         # Combine the log output for multiple IP addresses into a single summary
if [[ "$reverted_log_level" -eq "$current_log_level" ]]; then      if (( size_diff < 0 )); then
            echo -e "${GREEN}$(timestamp)${RESET} Node castor.log levelfile for IP ${BOLD_GREEN}$ip${RESET} reverted successfully back to ${BOLD_GREEN}${current_log_level_name}${RESET} (${BOLD_GREEN}$current_log_level${RESET})."
 was rotated."
            else
                  else  echo -e ""
                    echo -e "Approximate ${BOLD_GREEN}$(timestamp)$$size_diff_formatted${RESET} Failednew logs towere revertgenerated nodeat log level for IP ${BOLD_GREEN}$ip$$new_log_level_name${RESET}. Response: (${RED}$response$BOLD_GREEN}$new_log_level${RESET}") for IP ${ip_array[*]}. Current castor.log size is                  exit 1
                    fi
         ${BOLD_GREEN}$(format_size "$final_size")${RESET} after ${YELLOW}$duration${RESET} seconds (${YELLOW}$duration_formatted${RESET})."
      done              echo -e ""
 # Combine the log output for multiple IP addresses into a singlefi
summary             if (( size_diff < 0 )); thenelse
                echo -e "castor.log file was rotated."
    ${GREEN}$(timestamp)${RESET} Log level change is permanent until manually modified."
       else     fi
        fi
    fi
}
echo
-e# ""Run in detachable or directly
if $detachable; then
    # Pass the main_script function to the screen session echoand -e "Approximate ${BOLD_GREEN}$size_diff_formatted${RESET} new logs were generated at log level ${BOLD_GREEN}$new_log_level_name${RESET} (${BOLD_GREEN}$new_log_level${RESET}) for IP ${ip_array[*]}. Current castor.log size is ${BOLD_GREEN}$(format_size "$final_size")${RESET} after ${YELLOW}$duration${RESET} seconds (${YELLOW}$duration_formatted${RESET})."
                    echo -e ""
            fi
            else
                echo -e "${GREEN}$(timestamp)${RESET} Log level change is permanent until manually modified."
            fi
        fi
    fi
}

# Run in detachable or directly
if $detachable; then
    # Pass the main_script function to the screen session and store the output in a file
    debug_msg "**********************************************************store the output in a file
    debug_msg "**********************************************************" | tee -a "$output_log"
    debug_msg "Detach mode - Parameters passed to main_script:" | tee -a "$output_log"
    debug_msg "     Swarm IP: $swarm_ip" | tee -a "$output_log"
    debug_msg "     Credentials: $(print_credentials "$credentials")"  | tee -a "$output_log"
    debug_msg "     New Log Level: $new_log_level" | tee -a "$output_log"
    debug_msg "     New log Level Name: $new_log_level_name" | tee -a "$output_log"
    debug_msg "     Duration: $duration" | tee -a "$output_log"
    debug_msg "     Log Level Type: $log_level_type" | tee -a "$output_log"
    debug_msg "     Log File: $log_file" | tee -a "$output_log"
    debug_msg "     Initial Log File Size: $initial_size" | tee -a "$output_log"
    debug_msg "Detach   mode - ParametersCurrent passedLog to main_script:Level: $current_log_level" | tee -a "$output_log"
    debug_msg "     SwarmCluster IPName: $swarm_ip$clusterName" | tee -a "$output_log"
    debug_msg "     Credentialsjq_or_grep: $(print_credentials "$credentials")"  $jq_or_grep" | tee -a "$output_log"
    debug_msg "     New Log LevelDetach: $new_log_level$detachable" | tee -a "$output_log"
    debug_msg "     Debug: $debug" | New log Level Name: $new_log_level_nametee -a "$output_log"
    debug_msg "**********************************************************" | tee -a "$output_log"

   debug_msg "# Convert associative arrays to Duration:strings $duration"and |pass teethem -a "$output_log"
 to the screen session
  debug_msg "     Log Level Type: $log_level_type" | tee -a "$output_log"log_levels_string=$(declare -p log_levels)
    debug_msg "     Log File: $log_file" | tee -a "$output_log"log_level_names_string=$(declare -p log_level_names)
    debug_msg "     Initial Log File Size: $initial_sizelog_levels_string: $log_levels_string" | tee -a "$output_log"
    debug_msg "     Current Log Level: $current_log_levellog_level_names_string: $log_level_names_string" | tee -a "$output_log"

   debug_msg "     Cluster Name: $clusterName if command -v screen &>/dev/null; then
        echo -e "Running in ${YELLOW}screen${RESET} detachable mode..." | tee -a "$output_log"
    debug_msg "   screen -dmS jqcastor_orlog_grep: $jq_or_grep" | tee -a "$output_log"
   script bash -c "$(declare -f main_script timestamp debug_msg "     Detach: $detachable" | tee -a "$output_log"
    debug_msg "     Debug: $debug" | tee -a "$output_log"
    debug_msg "**********************************************************" | tee -a "$output_log"

    # Convert associative arrays to strings and pass them toformat_size format_duration check_jq determine_log_file print_credentials); main_script \"$swarm_ip\" \"$credentials\" \"$new_log_level\" \"$new_log_level_name\" \"$duration\" \"$log_level_type\" \"$clusterName\" \"$jq_or_grep\" \"$detachable\" \"$debug\" \"${log_levels_string}\" \"${log_level_names_string}\" \"${default_log_levels}\" \"${default_log_levels_name}\" | tee \"$output_log\""
        screen -r castor_log_script
        # Wait for the screen session to complete and then log_levels_string=$(declare -p log_levels)
    log_level_names_string=$(declare -p log_level_names)
    debug_msg "log_levels_string: $log_levels_string" | tee -a "$output_log"
    debug_msg "log_level_names_string: $log_level_names_string" | tee -a "$output_log"

    ifdisplay the output log
        sleep 1
        while screen -list | grep -q "castor_log_script"; do
            sleep 1
        done
    elif command -v screentmux &>/dev/null; then
        echo -e "Running in ${YELLOW}screen$tmux${RESET} detachable mode..." | tee -a "$output_log"" > "$output_log"  # Truncate log file to remove old entries

        # Create a temp script file
        temp_script="$SCRIPTDIR/castor_log_script.sh"

        # Write the script to a file to avoid printing function definitions
        cat screen<<EOF -dmS> castor"$temp_log_script"
#!/bin/bash
-c "$(declare -f main_script timestamp debug_msg format_size format_duration check_jq determine_log_file print_credentials);
main_script \"$swarm_ip\" \"$credentials\" \"$new_log_level\" \"$new_log_level_name\" \"$duration\" \"$log_level_type\" \"$clusterName\" \"$jq_or_grep\" \"$detachable\" \"$debug\" \"${log_levels_string}\" \"${log_level_names_string}\" \"${default_log_levels}\" \"${default_log_levels_name}\" | tee \-a "$output_log\"
rm -f "$temp_script"         screen -r castor_log_script         # WaitRemove for the screenscript sessionfile
totmux complete and then display the output logkill-session -t castor_log_script
EOF

        # Ensure sleepthe 1script is executable
      while screen -list | grep -q "castor_logchmod +x "$temp_script";
do
        # Start tmux session sleepand 1execute the   script inside
   done     eliftmux commandnew-session -vd tmux &>/dev/null; then
-s castor_log_script "bash $temp_script"

      echo -e "Running# in ${YELLOW}tmux${RESET} detachable mode..." | tee -a "$output_log"Attach session only if it's still running
        while tmux newhas-session -dt -s castor_log_script "$(declare -f main_script timestamp debug_msg format_size format_duration check_jq determine_log_file print_credentials); main_script \"$swarm_ip\" \"$credentials\" \"$new_log_level\" \"$new_log_level_name\" \"$duration\" \"$log_level_type\" \"$clusterName\" \"$jq_or_grep\" \"$detachable\" \"$debug\" \"${log_levels_string}\" \"${log_level_names_string}\" \"${default_log_levels}\" \"${default_log_levels_name}\" | tee \"$output_log\""
    2>/dev/null; do
            tmux attach-session -t castor_log_script
        done
        # if tmux has-session -t castor_log_script 2>/dev/null; then
        #     tmux attach-session -t castor_log_script
        # fi
    else
        echo "Error: Neither screen nor tmux available. Run without --detachable."
        exit 1
    fi

    echo ""
    cat "$output_log"
else
    # main_script "$swarm_ip" "$credentials" "$new_log_level" "$duration" "$clusterName" "$jq_or_grep" | tee "$output_log"
    debug_msg "**********************************************************"
    debug_msg "Parameters passed to main_script:"
    debug_msg "Swarm IP: $swarm_ip"
    debug_msg "Credentials: $(print_credentials "$credentials")"
    debug_msg "New Log Level: $new_log_level"
    debug_msg "Duration: $duration"
    debug_msg "Cluster Name: $clusterName"
    debug_msg "jq_or_grep: $jq_or_grep"
    debug_msg "**********************************************************"
    debug_msg "Running main_script function..."
    main_script "$swarm_ip" "$credentials" "$new_log_level" "$new_log_level_name" "$duration" "$log_level_type" "$clusterName" "$jq_or_grep" | tee "$output_log"
fi

...