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 grep 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.

  • 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.

Requirements

  • jq (optional): Used for parsing JSON responses; falls back to grep if unavailable.

  • screen or tmux (optional): Required for background execution.

  • Permissions: Ensure sufficient permissions to execute on the DataCore Swarm server and access required files.

...

Code Block
./castor-change-log-level.sh -d <node_ip> -p <admin:password> [-i <new new_log_level | -L new_node_log_level>level] [-t <duration_in_seconds>] [-D --detach] [-v]

Parameter

Description

-d, --swarm_ip

IP address of the Swarm API endpoint (or set SCSP_HOST environment variable).

-p, --credentials

Admin credentials in the format admin:password. Secure password entry is prompted if not provided.

-i, --log.level

New log level to set (values: 5, 10, 15, 20, 30, 40, 50, chatter, debug, announce, info, error, critical, default).

-t, L --time

Duration in seconds to keep the new log level (optional).

--detach

Runs the script in a detachable session using screen or tmux, allowing continued operation if terminal session endsnode.log.level

New node log level to set (values: 0, 5, 10, 15, 20, 30, 40, 50, chatter, debug, announce, info, error, critical, default).

-t, --time

Duration in seconds to keep the new log level (optional).

-D, --detach

Runs the script in a detachable session using screen or tmux, allowing continued operation if terminal session ends (optional).

Instruction for Use

Example 1: Set cluster log level to 20 and keep it for 10

...

minutes

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

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

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

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 --detach -vD

Running the Script at a Specific Time

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: Schedule the script to run at 3:00 AM and collect debug logs for 1 hour

Ensure the at service is installed, enabled and running

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.)

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

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

    Code Block
    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 10 -t 3600" | at 03:00 AM 203/1602/2025

This schedules the script to run at 3:00 AM on Feb 16 2025 and collects log at debug level for 1 hour.

  1. To verify scheduled job:

    Code Block
    atq
    7	Sun FebMar 1602 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

...

  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.Debug Mode: When -v is specified, debug messages display the script's internal operations.

Output Messages

Message

Description

Swarm IP:

Displays the specified Swarm IP address.

Credentials:

Credentials are masked for security.

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
[root@scs dist]# ./castor-change-log-level.sh -p admin:datacore -i 10 -t 300
Swarm IP: 192.168.1.84
Credentials: [hidden for security]
Cluster Name: gatewayadmindomainmsuen-scs1.suen.work

New log level: 10debug
Current log level is 30.
Updating log level to 10...default.
2025-03-02T05:15:49.901Z Log level changed successfully from 30 → 10.
Keeping log level at 10debug for 300 second (s00:05:00) ...
Countdown: 00:00:01 remaining...

Time's up! Reverting log level back to 30...
Approximate 6988.4MB9MB new logs were generated at log level 10. Current castor.log size is 371.3MB after 300 seconds (00:05:00).
2025-03-02T05:20:50.483Z Log level reverted successfully back to 30.

[root@scs dist]#

...

  • Missing Parameters: Missing parameters prompt a usage message.

  • Invalid Log Levels: If an unsupported log level is specified, the script will prompt the user to enter a valid value.

  • Invalid Duration: If a non-numeric duration is provided, you’re prompted to enter a valid duration in seconds.

  • Connection Issues: If unable to connect to the Swarm API, check the IP, credentials, and network access.

...

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----------------------------------------------------------------------------------------------------------------------
# Current Version: 1.2.3
# -------------------------------------------------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 Minor 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 Minor bug fix and enhancement.
# v1.3.3 - 2025-03-02 Fixed tmux command to long issue.
# ----------------------------------------------------------------------------
# KB: https://perifery.atlassian.net/wiki/spaces/public/pages/3872161835/Setting+and+Managing+Swarm+Log+Levels+with+script
# -------------------------------------------------
# Current Version: 1.3.3
# ---------------------------------------------------------------------------------------------

# 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() {
    echo "Usage: ./$SCRIPT_NAME -d swarm_ip -p admin:password [-i new_log_level | -L new_node_log_level] [-t duration_in_seconds] [-D]"
    echo "  -d, --swarm_ip           IP address of the Swarm API endpoint. Supports single or multiple IPs separated by \",\", \";\" or \" \"."
    echo "     ------------------------------------
# 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() {
    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               (Optional) Duration in seconds to keep the new log level (must be greater than 0)"
    echo -e "  -D, --detach             (Optional) Detach the script from 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"		        # Default log file location
SCRIPTDIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
JQLOCATION=$SCRIPTDIR/jq
MAX_RETRIES=3                                       # Maximum number of password retries attempts
username="admin"                                    # Default username
password=""                                         # 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"
}

# 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
        local caller_line=${BASH_LINENO[0]:-unknown}            # Fallback to "unknown" if empty
        echo -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."
        echo -e "  Please install either '${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 format duration
format_duration() {
    local duration=$1
    local hours=$((duration / 3600))
    local minutes=$(( (duration % 3600) / 60 ))
    local 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/bin/jq" "$(pwd)/jq"; do
        [[ -x "$jq_path" ]] && echo "$jq_path" && return
    done
    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
        if [[ -f "$log" ]]; then
            echo "$log"
            return
        fi
    done
    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 for security]"
    debug_msg "Swarm IP: $SWARM_IP"

    # API endpoint for validating user credentials
    local VALIDATE_URL="http://${SWARM_IP}:91/api/validateUser"
    debug_msg "Validate URL: $VALIDATE_URL"

    # Make the API request
    debug_msg "Validating user credentials..."
    debug_msg "curl -s -u \"********\" -X GET \"$VALIDATE_URL\" -H 'Content-Type: application/json'"
    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
        debug_msg "Authentication successful for user '${CREDENTIALS%%:*}'"
        return 0  # Success
    elif echo "$RESPONSE" | "$jq_or_grep" -e '.isValid == false' > /dev/null 2>&1; then
        debug_msg "Authentication failed for user '${CREDENTIALS%%:*}'"
        return 1  # Failure
    else
        debug_msg "Error: Unable to validate credentials. Please check your inputs."
        return 1  # Failure
    fi
}

# Function to print credentials - hide password
print_credentials() {
    local CREDENTIALS="$1"
    local USERNAME="${CREDENTIALS%%:*}"
    echo -e "${GREEN}$USERNAME${RESET}":"${GREEN}********${RESET}"
}

# Parse input arguments
while [[ "$#" -gt 0 ]]; do
    case $1 in
        -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 [[ -n "$new_log_level" ]]; then
                echo "Error: Options -i (cluster log leve) and -L (node log level) cannot be used together."
                usage
            fi
            log_level_type="cluster"
            if [[ ${log_level_names[$2]} ]]; then
                new_log_level=${log_level_names[$2]}
            elif [[ "$2" == "default" ]]; then
                new_log_level=${default_log_levels[$log_level_type]}
                new_log_level_name="default"
            elif [[ ${log_levels[$2]} ]]; then
                new_log_level=$2
            else
                echo "Invalid log level: $2"
                exit 1
            fi
            new_log_level_name=${log_levels[$new_log_level]}
            default_log_level=30
            debug_msg "Set new_log_level to $new_log_level ($new_log_level_name)"
            shift 2
            ;;
        -L|--node.log.level)
            if [[ -n "$new_log_level" ]]; then
                echo "Error: Options -i (cluster log leve) and -L (node log level) cannot be used together."
                usage
            fi
            log_level_type="node"
            if [[ ${log_level_names[$2]} ]]; then
                new_log_level=${log_level_names[$2]}
            elif [[ "$2" == "default" ]]; then
                new_log_level=${default_log_levels[$log_level_type]}
                new_log_level_name="default"
            elif [[ ${log_levels[$2]} || $2 -eq 0 ]]; then
                new_log_level=$2
                new_log_level_name="default"
            else
        If multiple IPs are provided, the script will updateecho the"Invalid log level for all nodes.": $2"
            echo "   exit 1
            fi
         (Alternatively, set the SCSP_HOST environment variable to the Swarm IP.)" new_log_level_name=${log_levels[$new_log_level]}
          echo "  -p, --credentialsdefault_log_level=0
         Credentials in the format admin:password"
    echo "  -i, --log.leveldebug_msg "Set new_log_level to $new_log_level ($new_log_level_name)"
            shift 2
  New cluster log level to set (5, 10, 15, 20, 30,;;
40, 50, chatter, debug, announce, info, error, critical, default)" -t|--time)
    echo "  -L, --node.log.level    if New[[ node log level to set (0, 5, 10, 15, 20, 30, 40, 50, chatter, debug, announce, info, error, critical, default)"-n "$2" && "$2" != -* && "$2" -gt 0 ]]; then
            echo "   duration="$2"
                debug_msg "Set duration to $duration"
  **Either -i or -L must be specified, but not both.**"     echoshift "2
 -t, --time          else
    (Optional) Duration in seconds to keep the new log level (must be greaterecho than-e 0)""

   echo "  -D, --detach         echo -e   (Optional) Detach the script from the current terminal and run in a detachable session using screen or tmux"
    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"		        # Default log file location
SCRIPTDIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
JQLOCATION=$SCRIPTDIR/jq

# 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"
    [30]="default"
)

# 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
    ["default"]=30
)

# Function to get the current timestamp
timestamp() {
    date +"%Y-%m-%d_%H:%M:%S.%4N"
}

# Function to display debug messages if debug mode is enabled
debug_msg() {
    if $debug; then"---------------------------------------------------------------------------------------"
                echo -e "    ${RED}Error${RESET}: Duration must be a number greater than ${BOLD_GREEN}0${RESET}."
                echo -e "    Please specify the duration using the ${BOLD_GREEN}-t${RESET} ${GREEN}<seconds>${RESET} or ${BOLD_GREEN}--time${RESET} ${GREEN}<seconds>${RESET} option."
                echo -e "---------------------------------------------------------------------------------------"
                usage
            fi
            ;;
        -D|--detach) detachable=true; debug_msg "Set detachable to true"; shift ;;
        --debug) debug=true; debug_msg "Set debug to true with ${YELLOW}--debug${RESET}"; shift ;;
        *) 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 [[ -z "$duration" ]]; then
        echo -e ""
        echo local caller_line=${BASH_LINENO[0]:-unknown}  # Fallback to "unknown" if empty-e "---------------------------------------------------------------------------------------"
        echo -e "$(timestamp) [DEBUG] (Line $caller_line) $*"
    fi
}

# Function to check if either 'screen' or 'tmux' is installed
check_screen_or_tmux() { ${RED}Error${RESET}: Duration must be specified when running in detachable mode."
      if ! commandecho -v screen &>/dev/null && ! command -v tmux &>/dev/null; then
        echo "Error: Neither 'screen' nor 'tmux' is installed. Cannot run in detachable modee "    Please specify the duration using the ${BOLD_GREEN}-t${RESET} or ${BOLD_GREEN}--time${RESET} option."
        detachable=false  # Disable detachable sessionecho -e "---------------------------------------------------------------------------------------"
        usage
    debug_msg "detachable=${YELLOW}false${RESET}"fi
    check_screen_or_tmux
fi
}

# Function to format file size
format_size() {
    local size=$1
    if (( size >= 1073741824 ))If swarm_ip is not provided, try using SCSP_HOST environment variable
if [[ -z "$swarm_ip" ]]; then
    if [[  -n echo "$(awk "BEGIN {printf \"%.1fGB\", $size/1073741824}")""$SCSP_HOST" ]]; then
     elif (( size >= 1048576 )); then
 swarm_ip="$SCSP_HOST"
      echo "$(awk "BEGIN {printf \"%.1fMB\", $size/1048576}")"
    elif (( size >= 1024 )); then debug_msg "Using Swarm IP from SCSP_HOST: $swarm_ip"
    else
        echo "$(awk "BEGIN {printf \"%.1fKB\", $size/1024}")"
    elseError: swarm_ip not provided and SCSP_HOST is not set."
         echo "${size}B"usage
    fi
}fi

# FunctionCheck toif formatrequired duration
format_duration() {
    local duration=$1
    local hours=$((duration / 3600))
    local minutes=$(( (duration % 3600) / 60 ))
    local 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() {
    arguments are provided
if [[ -z "$credentials" || -z "$new_log_level" ]]; then
    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 [[ -xn "/usr/local/bin/jq$credentials" ]]; then
    CREDENTIALS="$credentials"
   echo "/usr/local/bin/jq" ATTEMPT=$MAX_RETRIES
    elif [[ -x "$(pwd)/jq" ]]; thenUSERNAME=""
    PASSWORD=""
    echodebug_msg "CREDENTIALS: $(pwd)/jqprint_credentials "$CREDENTIALS")"
    elif command -v jq &>/dev/null; thendebug_msg "ATTEMPT: $ATTEMPT"

    # Check if credentials contain a echo "jq"
colon (username:password format)
   else debug_msg "Checking for colon     echo "grep"
in credentials..."
   fi }

if [[ -f "$JQLOCATION"[ "$CREDENTIALS" == *":"* ]]; then
    jq_or_grep=$JQLOCATION
else    debug_msg "Credentials contain colon"
     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() {USERNAME="${CREDENTIALS%%:*}"
        PASSWORD="${CREDENTIALS#*:}"

      if [[ -f "/var/log/datacore/castor.log" ]]; then# Validate credentials
         echo "/var/log/datacore/castor.logcheck_credentials "$CREDENTIALS" "${ip_array[0]}"
    elif    if [[ $? -f "/var/log/caringo/castor.log"ne 0 ]]; then
        echo "/var/log/caringo/castor.log"   debug_msg "Invalid elsecredentials. Please check your username and password."
  echo "Error: Log file not found in /var/log/datacore/castor.log or /var/log/caringo/castor.log"
   echo -e ""
     exit 1     fi }echo 
log_file=$(determine_log_file)

# Function to check if credentials are valid
check_credentials() {
    local CREDENTIALS="$1"-e "${RED}Error${RESET}: Invalid credentials. Please check your username and password."
       local SWARM_IP="$2"    echo debug_msg-e ""Credentials:
 $CREDENTIALS"     debug_msg "Swarm IP: $SWARM_IP"   exit 1
 # API endpoint for validating user credentials fi
   local VALIDATE_URL="http://${SWARM_IP}:91/api/validateUser"    debug_msg "Validate #credentials"
Make the API request     RESPONSEcredentials=$(curl$CREDENTIALS
-s -u "$CREDENTIALS" -X GETelse
"$VALIDATE_URL" -H 'Content-Type: application/json')     debug_msg "ValidateCredentials Userdo Response: $RESPONSE"

    # Check if the response contains "isValid": truenot contain password"
       if echodebug_msg "$RESPONSE" | "$jq_or_grep" -e '.isValid == true' > /dev/null 2>&1; then
Username: $(print_credentials "$CREDENTIALS")"
        USERNAME="$CREDENTIALS"
       return 0  # SuccessPrompt for password
  elif echo "$RESPONSE" | "$jq_or_grep" -e '.isValid == false' > /dev/null 2>&1; thenwhile [[ $ATTEMPTS -lt $MAX_RETRIES ]]; do
        debug_msg "Authentication failed for user '${CREDENTIALS%%:*}'"
 echo ""
      return 1  # Failure  read -sp "Enter elsepassword for user $USERNAME: " PASSWORD
   debug_msg "Error: Unable to validate credentials. Please check your inputs."         echo ""
         return  1 echo #""
Failure     fi }  # Parse input arguments whileif [[ -z "$#$PASSWORD" -gt 0 ]]; then
do     case $1 in         -d|--swarm_ip) swarm_ip="$2"; debug_msg "Set swarm_ip to $swarm_ip"; shift 2 ;;echo -e "${RED}Error${RESET}: Password cannot be empty."
           -p|--credentials)         ((ATTEMPTS++))
   credentials="$2"             ifcontinue
! [[ "$credentials" =~ ^[^:]+:[^:]+$ ]]; then      fi
          echo -e CREDENTIALS="$USERNAME:$PASSWORD"
            debug_msg "CREDENTIALS: $(print_credentials "$CREDENTIALS"))"
  echo    -e "Error: Credentials must be in the format check_credentials "$CREDENTIALS" "${BOLD_GREEN}username:password${RESETip_array[0]}"
            if [[ $? -eq exit0 1]]; then
           fi     debug_msg "CREDENTIALS: $(print_credentials "$CREDENTIALS"))"
    if [[ "$credentials" =~ [^a-zA-Z0-9:] ]]; then        debug_msg "Valid credentials"
      echo -e ""        credentials=$CREDENTIALS
        echo -e "Warning: Password contains special characters. Please enclosebreak
the credentials with single quotes (${BOLD_YELLOW}'${BOLD_GREEN}username:password${RESET}${BOLD_YELLOW}'${RESET})."       else
         exit 1      echo -e ""
    fi            echo debug_msg "Set credentials"; shift 2 ;;-e "${RED}Error${RESET}: Invalid credentials. Please check your username and password."
        -i|--log.level)    fi
        if [[ -n "$new_log_level" ]]; then ((ATTEMPTS++))
        done
        echo "Error: Options -i (cluster log leve) and -L (node log level) cannot be used together."
    if [[ $ATTEMPTS -ge $MAX_RETRIES ]]; then
            echo -e ""
           usage echo -e "${RED}Error${RESET}: Maximum number of password attempts reached. Exiting script."
 fi           exit 1
if [[ ${log_level_names[$2]} ]]; then    fi
    fi
else
    if ! new_log_level=${log_level_names[$2]}check_credentials "$credentials" "${ip_array[0]}"; then
        echo -e ""
 elif [[ ${log_levels[$2]} ]]; then   echo -e "${YELLOW}Warning${RESET}: ${RED}Invalid credentials${RESET}."
         new_log_level=$2
echo -e ""
        exit 1
 else   fi
fi

# Retrieve cluster name and handle JSON parsing using the first echoIP "Invalid log level: $2"
                exit 1
            fi
            new_log_level_name=${log_levels[$new_log_level]}
            log_level_type="cluster"
            default_log_level=30
            debug_msg "Set new_log_level to $new_log_level ($new_log_level_name)"
            shift 2
            ;;
        -L|--node.log.level)
            if [[ -n "$new_log_level" ]]; then
                echo "Error: Options -i (cluster log leve) and -L (node log level) cannot be used together."
                usage
            fi
       address
debug_msg "Retrieving the cluster name from Swarm API using IP: ${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"
    if [[ ${log_level_names[$2]}"$detachable" ]]; then
                new_log_level=${log_level_names[$2]}local debug="${10}"
        eval    elif [[ ${log_levels[$2]} || $2 -eq 0 ]]; then
"$(echo "${11}" | sed 's/declare -A/declare -A/')"
        eval "$(echo "${12}" | sed 's/declare  new_log_level=$2-A/declare -A/')"
    fi
    local default_log_levels="$13"
    local newdefault_log_levellevels_name="default$14"
    debug_msg "**********************************************************"
      else
           debug_msg "local variables"
    echodebug_msg "InvalidLog logLevel levelType: $2${GREEN}$log_level_type${RESET}"
    debug_msg "Default Log Level: ${GREEN}$default_log_level${RESET}"
    debug_msg "Swarm  exit 1IP: $swarm_ip"
    debug_msg "Credentials: $(print_credentials "$credentials")"
    debug_msg fi"New Log Level: $new_log_level"
    debug_msg "New log Level Name: new$new_log_level_name=${log_levels[$new_log_level]}"
    debug_msg "Duration: $duration"
    debug_msg "Log File: $log_file"
    log_level_type="node"
   debug_msg "Initial Log File Size: $initial_size"
    debug_msg "Current Log Level: default$current_log_level=0"
    debug_msg "Cluster Name: $clusterName"
    debug_msg "Set newjq_logor_level to $new_log_level ($new_log_level_name)grep: $jq_or_grep"
    debug_msg "Detach: $detachable"
     shift 2debug_msg "Debug: $debug"
        debug_msg "**********************************************************"

  ;;  # Split the swarm_ip into an  -t|--time)
 array of IP addresses
    IFS=';, ' read -r -a ip_array if [[ -n "$2" && "$2" != -* && "$2" -gt 0 ]]; then<<< "$swarm_ip"
    debug_msg "IP Array: ${ip_array[*]}"

    # Display initial information
    if [[ "$log_level_type" == duration="$2cluster" ]]; then
        echo -e "Swarm    debug_msg "Set duration to $duration"
IP: ${GREEN}${ip_array[0]}${RESET}"
    else
        echo -e shift"Swarm 2
IPs: ${GREEN}${ip_array[*]}${RESET}"
    fi
    # echo else-e "Swarm IP: ${GREEN}$swarm_ip${RESET}"
    debug_msg "Credentials: ${GREEN}[hidden for security]${RESET}"
    echo "Error: Duration must be a number greater than 0."
 -e "Cluster Name: ${GREEN}$clusterName${RESET}"

    debug_msg "Starting main_script function..."
    debug_msg "Log level type: $log_level_type"

    exit# 1Store the original log levels
    declare -A original_log_levels
 fi   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]}
     -D|--detach) detachable=true; debug_msg "SetDefault detachablelog to true"; shift ;;
        --debug) debug=true;level: $default_log_level"
    debug_msg "SetDefault debuglog tolevel true with ${YELLOW}--debug${RESET}"; shift ;;name: $default_log_level_name"

    if [[ $log_level_type ==  *) usage ;;"cluster" ]]; then
     esac done  debug_msg "SetSetting cluster log_ level_type to ${YELLOW}$log$new_log_level ($new_log_level_type${RESET}name)"
 # Set default values if not provided if# [[Retrieve -zcurrent "$new_log_ level"
  ]]; then     if [[ "$log$jq_levelor_typegrep" == "clustergrep" ]]; then
        new    current_log_level=$default_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]+')
    else    else
    new_log_level=0     fi     newcurrent_log_level_name="default"
    new_log_level_name=${log_levels[$new_log_level]}
    debug_msg "Set default new_log_level to $new_log_level ($new_log_level_name)"
fi

# Check if 'screen' or 'tmux' is installed
check_screen_or_tmux

# If swarm_ip is not provided, try using SCSP_HOST environment variable
if [[ -z "$swarm_ip" ]]; then=$(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 [[ -n "$SCSP_HOST" $current_log_level == 30 ]]; then
            swarm_ipcurrent_log_level_name="$SCSP_HOSTdefault"
        debugfi
"Using Swarm IP from SCSP_HOST: $swarm_ip"    if else
    [[ $new_log_level == 30 ]]; then
   echo "Error: swarm_ip not provided and SCSP_HOST is not set."
  new_log_level_name="default"
      usage     fi
fi

#
Check if required arguments are provided if [[ -z "$credentials" || -z "$new_log_level" ]]; then debug_msg "Current cluster log level: ${BOLD_GREEN}$current_log_level${RESET}"
       usage fidebug_msg "Current #log Splitlevel 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 ! check_credentials "$credentials" "${ip_array[0]}"; thenname: ${BOLD_GREEN}$current_log_level_name${RESET}"
        echo -e ""
        echo -e "New cluster log level: ${BOLD_GREEN}$new_log_level_name${RESET} (${BOLD_GREEN}$new_log_level${RESET})"
        echo -e ""Current cluster log level is echo -e "${YELLOW}Warning$BOLD_GREEN}$current_log_level_name${RESET}: (${RED}Invalid credentials$BOLD_GREEN}$current_log_level${RESET}). Please check your username and password in the format ${GREEN}username:password${RESET}."
    echo -e ""
    usage"

        # Skip update if new level matches the current level
        if [[ "$current_log_level" -eq "$new_log_level" ]]; then
        exit 1 fi  #echo Retrieve""
cluster name and handle JSON parsing using the first IP address debug_msg "Retrievingecho the-e cluster"Cluster namelog fromlevel Swarmis APIalready usingset IP:to ${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
fiBOLD_GREEN}$new_log_level_name${RESET} (${BOLD_GREEN}$new_log_level${RESET}). No changes made."
            return
        fi

        # Update the cluster log level using PUT
        debug_msg "Updating cluster log level to $new_log_level_name"
        debug_msg "Cluster Name: $clusterName"
 # Main logic function to run the script tasks
main_script() {debug_msg "Credentials: $(print_credentials "$credentials")"
     local swarm_ip="$1"  debug_msg "curl  local credentials="$2"
    local new_log_level="$3"
    local new_log_level_name="$4"
    local duration="$5"
    local log_level_type="$6--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}\""
    #local log_file="/var/log/datacore/castor.log"     if [[ -z "$log_file" ]]; then
        log_file=$(determine_log_file)response=$(curl --user "$credentials" -sS -X PUT -H "Content-Type: application/json" \
        fi     local initial_size=$(stat -c%s "$log_file" 2>/dev/null || echo 0)
    local current_log_level"http://${ip_array[0]}:91/api/storage/clusters/$clusterName/settings/log.level" \
            -d "{\"value\": $new_log_level}")
    local clusterName="$7"   debug_msg  local jq_or_grep="$8"Response: $response"

   local detachable="$9"
    if [[ "$jq_or_grep" == "$detachablegrep" ]]; then
        local debug="${10}"         eval "updated_log_level=$(echo "${11}$response" | sed 's/declaregrep -A/declare -A/')"
        eval "$(echo "${12}" | sed 's/declare -A/declare -A/')"oP '"value":\s*\K[0-9]+')
    fi    else
debug_msg "**********************************************************"
    debug_msg "local variables"     debug_msg "Swarm IP: $swarm_ip"
    debug_msg "Credentials: $credentials"updated_log_level=$(echo "$response" | "$jq_or_grep" -r '.value')
        fi
debug_msg "New Log Level: $new_log_level"     debug_msg "NewUpdated cluster log Level Namelevel: $new$updated_log_level_name"

   debug_msg "Duration: $duration"   if  debug_msg "Log Level Type: $log_level_type"
    debug_msg "Log File: $log_file"[[ "$updated_log_level" -eq "$new_log_level" ]]; then
          debug_msg "Initial Logecho File Size: $initial_size"
    debug_msg "Current Log Level: $current_log_level-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})."
    debug_msg "Cluster Name: $clusterName" else
   debug_msg "jq_or_grep: $jq_or_grep"     debug_msg "Detach: $detachable"echo     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-e "${GREEN}$(timestamp)${RESET} Failed to update log level. Response: ${RED}$response${RESET}"
            exit 1
        fi

        # Countdown and revert log level
        if [[ -n "$log_level_type$duration" ==&& "cluster$duration" -gt 0 ]]; then
            echo -e "Keeping log level at  echo -e "Swarm IP: ${GREEN}${ip_array[0]}${RESET}${YELLOW}$new_log_level_name${RESET} (${YELLOW}$new_log_level${RESET}) for ${YELLOW}$duration${RESET} seconds (${YELLOW}$(format_duration $duration)${RESET}) ..."
    else         echo -e "Swarm"
  IPs: ${GREEN}${ip_array[*]}${RESET}"     fi     # echo -e "Swarm IP: ${GREEN}$swarm_ip${RESET}"for ((i=duration; i>0; i--)); do
      echo -e "Credentials: ${GREEN}[hidden for security]${RESET}"     echoprintf -ev countdown "Cluster Name: ${GREEN}$clusterName${RESET}"

    debug_msg "Starting main_script function..."%02d:%02d:%02d" $((i/3600)) $(( (i%3600) / 60 )) $((i%60))
       debug_msg "Log level type: $log_level_type"     echo # Store the original log levels-ne "Countdown: ${YELLOW}$countdown${RESET} remaining...\r"
      declare -A original_log_levels     declare -A original_log_level_names sleep 1
   if [[ $log_level_type == "cluster" ]]; then   done
     debug_msg "Setting cluster log level to $new_log_level ($new_log_level_name)"
        # Retrieve current log level
  echo -e "\n\nTime's up! Reverting log level back to ${GREEN}$current_log_level_name${RESET} (${BOLD_GREEN}$current_log_level${RESET}) ..."

     if [[ "$jq_or_grep" == "grep" ]]; then # Revert the log level back to the original value
  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]+')  # echo -e "Level log revert on else$(timestamp)"
            current_log_levelresponse=$(curl --user "$credentials" -sS "http://${ip_array[0]}:91/api/storage/clusters/$clusterName/settings/log.level" | "$jq_or_grep" -r '.value')
-X PUT -H "Content-Type: application/json" \
       fi         current_log_level_name="http://${logip_levels[$current_log_level]}
        debug_msg "Current cluster log level: ${BOLD_GREEN}$current_log_level${RESET}"
array[0]}:91/api/storage/clusters/$clusterName/settings/log.level" \
       debug_msg "Current log level name: ${BOLD_GREEN}         -d "{\"value\": $current_log_level_name${RESET}")
           echo -edebug_msg "Response: $response"

       echo  -e "New cluster logif level: ${BOLD_GREEN}$new_log_level_name${RESET}"[[ "$jq_or_grep" == "grep" ]]; then
         echo -e "Current cluster log level is ${GREEN}$currentreverted_log_level_name.${RESET}"=$(echo "$response" | grep -oP '"value":\s*\K[0-9]+')
         # Skip update ifelse
new level matches the current level         if [[ "$currentreverted_log_level=$(echo "$response" -eq| "$new$jq_logor_levelgrep" ]]; then-r '.value')
            echofi
""            debug_msg echo -e "Cluster"Reverted cluster log level is already set to ${BOLD_GREEN}$new_log_level_name${RESET}. No changes made.: $reverted_log_level"

           return final_size=$(stat -c%s "$log_file" 2>/dev/null     fi

        # Update the cluster log level using PUT|| echo 0)
            debug_msg "UpdatingInitial cluster log levelfile tosize: $new_log_level_name"$initial_size"
            debug_msg "Cluster NameFinal log file size: $clusterName$final_size"
        response    size_diff=$(curl --user "$credentials" -sS -X PUT -H "Content-Type: application/json" \( final_size - initial_size ))
            size_diff_formatted=$(format_size "$size_diff")
             "http://${ip_array[0]}:91/api/storage/clusters/$clusterName/settings/log.level" \
duration_formatted=$(format_duration "$duration")
	    if (( size_diff < 0 )); then
		echo -de "{\"value\": $new_log_level}")
   castor.log file was rotated."
	    else
debug_msg "Response: $response"          ifecho [[-e "$jq_or_grep"
== "grep" ]]; then         echo -e   updated_log_level=$(echo "$response" | grep -oP '"value":\s*\K[0-9]+')
        else
            updated_log_level=$(echo "$response" | "$jq_or_grep" -r '.value')
        fi"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 debug_msg-e ""Updated
cluster	 log level: $updated_log_level" fi
        if [[ "$updated$reverted_log_level" -eq "$new$current_log_level" ]]; then
            echo -e "${GREEN}$(timestamp)${RESET} Log level changedreverted successfully fromback to ${BOLD_GREEN}$current_log_level_level$name${RESET} → (${BOLD_GREEN}$new$current_log_level${RESET})."
            echo -e ""
        else
            echo -e "${GREEN}$(timestamp)${RESET} Failed to updaterevert log level. Response: ${RED}$response${RESET}"
            echo -e ""
            exit 1
        fi
         # Countdown and revert log levelelse
            echo -e "${GREEN}$(timestamp)${RESET} Log level change is permanent until manually modified."
        fi
    ifelif [[ -n "$duration" && "$duration" -gt 0 "$log_level_type" == "node" ]]; then
   
        echo# -e "Keeping log level at ${YELLOW}$new_log_level_name${RESET} for ${YELLOW}$duration${RESET} second(s)..."First loop: Change the node log level
        local same_log_level=false
     echo -e "" for ip in "${ip_array[@]}"; do
       for ((i=duration; i>0; i--)); do # Retrieve current node log level
          printf -v countdowndebug_msg "%02d:%02d:%02d" $((i/3600)) $(( (i%3600) / 60 )) $((i%60))
 Retrieving current node log level for IP: $ip"
            debug_msg "curl echo-s -neu "Countdown: ${YELLOW}$countdown${RESET} remaining...\r\"$(print_credentials "$credentials")\" \"http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel\""
            current_log_level=$(curl -s -u  sleep 1
 "$credentials" "http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel" | $jq_or_grep -r '.value')
          done  debug_msg "Current log level for IP $ip: $current_log_level"
   echo -e "\n\nTime's up! Reverting log level back to ${GREEN} original_log_levels["$ip"]=$current_log_level${RESET}..."level
            debug_msg # Revert the "Original log level backfor toIP the$ip: ${original value_log_levels["$ip"]}"
            if [[  response=$(curl --user "$credentials" -sS -X PUT -H "Content-Type: application/json" \"$current_log_level" == "0" ]]; then
                current_log_level_name="default"
      "http://${ip_array[0]}:91/api/storage/clusters/$clusterName/settings/log.level" \
      else
               -d "{\"value\":  current_log_level_name=${log_levels[$current_log_level]}")
            debug_msg "Response: $response"fi
               if [[ "$jq_or_grep" == "grep" ]]; thenoriginal_log_level_names["$ip"]=$current_log_level_name
            debug_msg "Original log level name: reverted${original_log_level=$(echo "$response" | grep -oP '"value":\s*\K[0-9]+')_names["$ip"]}"
            if [[ "$new_log_level" == "0" ]]; then
else                 revertednew_log_level_name=$(echo "$responsedefault"
| "$jq_or_grep" -r '.value')         else
   fi             debug_msg "Reverted cluster log level: $revertednew_log_level_name=${log_levels[$new_log_level"]}
            fi
final_size=$(stat -c%s "$log_file" 2>/dev/null || echo 0)             debug_msg "InitialCurrent log level for fileIP size$ip: $initial_size"
            debug_msg "Final log file size: $final_size"$current_log_level ($current_log_level_name)"
            echo ""
 size_diff=$(( final_size - initial_size ))       echo -e "New node log  size_diff_formatted=$(format_size "$size_diff")level: ${BOLD_GREEN}$new_log_level_name${RESET} (${BOLD_GREEN}$new_log_level${RESET})"
            duration_formatted=$(format_duration "$duration")
	    if (( size_diff < 0 )); then
		echo -e "castor.log file was rotated."
	    else
      echo -e "Current node log level for IP ${BOLD_GREEN}$ip${RESET} is ${BOLD_GREEN}$current_log_level_name${RESET} (${BOLD_GREEN}$current_log_level${RESET})."

            # Skip update if new level matches the current level
     echo -e "Approximate ${BOLD_GREEN}$size_diff_formatted${RESET} new logs were generatedif at[[ "$current_log_level" level ${BOLD_GREEN}-eq "$new_log_level${RESET}. Current castor.log size is ${BOLD_GREEN}$(format_size "$final_size")${RESET} after $duration_formatted."
	    filevel" ]]; then
                same_log_level=true
             if [[ "$reverted_log_level" -eq "$current_log_level" ]]; then echo ""
                  echo -e "LogNode log level reverted successfully backfor IP ${BOLD_GREEN}$ip${RESET} is already set to ${BOLD_GREEN}$current$new_log_level_name${RESET} (${BOLD_GREEN}$new_log_level${RESET})." No changes made."
         else       continue
         echo -e "Failed toelse
revert log level. Response: ${RED}$response${RESET}"                 exit 1same_log_level=false
            fi

       else     if [[ "$same_log_level" == false    echo "Log level change is permanent until manually modified."]]; then
              fi  # Update the elifnode [[log "$log_level_type" == "node" ]]; thenlevel using PUT
             # First loop: Change thedebug_msg "Updating node log level to $new_log_level_name for IP $ip..."
   for ip in "${ip_array[@]}"; do         debug_msg "curl --user  # Retrieve current node log level\"$(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\""
                 debug_msg "Retrieving current node log level for IP: $ip"
response=$(curl --user "$credentials" -sS -X PUT -H "Content-Type: application/json" \
           debug_msg "curl -s -u \"$credentials\" \         "http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel\"?value=$new_log_level")
                debug_msg "Response: $response"

           current_log_level=$(curl -s -u "$credentials" "http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel" | $jq_or_grep -r '.value')     if [[ "$jq_or_grep" == "grep" ]]; then
              debug_msg "Current log level for IP $ip: $currentupdated_log_level=$(echo "$response" | grep -oP '"value":\s*\K[0-9]+')
             original_log_levels["$ip"]=$current_log_level   else
             debug_msg "Original log level for IP $ip: ${originalupdated_log_levels["$ip"]}"
   level=$(echo "$response" | "$jq_or_grep" -r '.value')
        if [[ "$current_log_level" == "0" ]]; then   fi
             current_log_level_name="default"   debug_msg "Updated log level for IP $ip: $updated_log_level"

 else               if [[ current"$updated_log_level_name=${log_levels[$current" -eq "$new_log_level" ]}]; then
           fi         echo -e "${GREEN}$(timestamp)${RESET} Node original_log_level_names["$ip"]=$current_log_level_name
            debug_msg "Original log level name: ${original_log_level_names["$ip"]}"
            if [[ "$new_log_level" == "0" ]]; thenlog 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})."
                else
   new_log_level_name="default"             else    echo -e "${GREEN}$(timestamp)${RESET} Failed to update node log level for IP ${BOLD_GREEN}$ip${RESET}.  new_log_level_name=${log_levels[$new_log_level]}Response: ${RED}$response${RESET}"
            fi        exit 1
   debug_msg "Current log level for IP $ip: $current_log_level ($current_log_level_name)"     fi
       echo ""    fi
        echodone
-e
"New node log level: ${BOLD_GREEN}$new_log_level_name${RESET}"    # Second loop: Countdown if duration is provided
 echo -e "Current node log level for IPif $ip is ${GREEN}$current[[ "$same_log_level_name.${RESET}" == false ]]; then\
         # Skip update if new[[ level matches the current level
            if [[ "$current_log_level" -eq "$new_log_level"-n "$duration" && "$duration" -gt 0 ]]; then
                echo -e ""
                echo -e "NodeKeeping node(s) log level for IP $ip is already set to ${BOLD_GREENat ${YELLOW}$new_log_level_name${RESET}. No changes made."
                continue
            fi

            # Update the node log level using PUT (${YELLOW}$new_log_level${RESET}) for ${YELLOW}$duration${RESET} seconds (${YELLOW}$(format_duration $duration)${RESET}) ..."
                echo debug_msg-e "Updating"
node log level to $new_log_level_name for IP $ip..."         for ((i=duration; i>0; i--)); do
 debug_msg  "curl --user \"$credentials\" -sS -X PUT -H \"Content-Type: application/json\" \"http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel?value=$new_log_level\""        printf -v countdown "%02d:%02d:%02d"  response=$(curl --user "$credentials" -sS -X PUT -H "Content-Type: application/json" \$((i/3600)) $(( (i%3600) / 60 )) $((i%60))
                    echo -ne "http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel?value=$new_log_level")Countdown: ${YELLOW}$countdown${RESET} remaining...\r"
             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]+')
      \n\nTime's up! Reverting node log level back to original levels..."

    else            # Third loop: Revert the updated_node log_level=$(echo "$response" | "$jq_or_grep" -r '.value') level
                for ip in "${ip_array[@]}"; do
fi             debug_msg "Updated log level for IP $ip: $updatedcurrent_log_level=${original_log_levels["$ip"]}
                if   [[ "$updatedcurrent_log_level" -eq "$new_name=${original_log_level_names["$ip" ]]; then
   ]}
            echo -e "Node log level for IP $ip changed successfully from ${GREEN}$current_log_level_name${RESET} → ${BOLD_GREEN}$new_debug_msg "Reverting node log level back to $current_log_level_name${RESET}."
   name for IP $ip..."
        else            debug_msg     echo -e "Failed to update node log level for IP $ip. Response: ${RED}$response${RESET}"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\""
                exit 1   # echo -e "Node level log revert on $(timestamp) for fiIP $ip"
       done          # Second loop: Countdown if duration is provided
        if [[ -n "$duration" && "$duration" -gt 0 ]]; then
  response=$(curl --user "$credentials" -sS -X PUT -H "Content-Type: application/json" \
          echo -e ""             echo -e "Keeping node(s) log level at ${YELLOW}$new_log_level_name${RESET} for ${YELLOW}$duration${RESET} second(s)..." "http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel?value=$current_log_level")
               echo -e ""   debug_msg "Response: $response"

      for ((i=duration; i>0; i--)); do          if [[ "$jq_or_grep" == "grep" ]]; then
printf -v countdown "%02d:%02d:%02d" $((i/3600)) $(( (i%3600) / 60 )) $((i%60))              reverted_log_level=$(echo "$response" | echogrep -neoP '"value"Countdown: ${YELLOW}$countdown${RESET} remaining...\r":\s*\K[0-9]+')
                  sleep 1 else
           done             reverted_log_level=$(echo "$response" -e "\n\nTime's up! Reverting node log level back to original levels..."| "$jq_or_grep" -r '.value')
                    fi

# Third loop: Revert the node log level             for ip in "${ip_array[@]}"; dofinal_size=$(stat -c%s "$log_file" 2>/dev/null || echo 0)
                    currentsize_log_leveldiff=${original_log_levels["$ip"]}(( final_size - initial_size ))
                    currentdebug_msg "Initial log_level_name=${original_log_level_names["$ip"]} file size: $initial_size"
                    debug_msg "Reverting nodeFinal log levelfile back to $current_log_level_name for IP $ip...size: $final_size"
                debug_msg "curl --user \"$credentials\" -sS -X PUT -H \"Content-Type: application/json\" \"http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel?value=$current_log_level\"" size_diff_formatted=$(format_size "$size_diff")
                 response   duration_formatted=$(curl --user "$credentials" -sS -X PUT -H "Content-Type: application/json" \format_duration "$duration")

                    if [[     "http://$ip:91/api/storage/nodes/_self/settings/log.nodeLogLevel?value="$reverted_log_level" -eq "$current_log_level") ]]; then
              debug_msg "Response: $response"        echo -e "${GREEN}$(timestamp)${RESET} Node log level for    if [[ "$jq_or_grep" == "grep" ]]; then
    IP ${BOLD_GREEN}$ip${RESET} reverted successfully back to ${BOLD_GREEN}${current_log_level_name}${RESET} (${BOLD_GREEN}$current_log_level${RESET})."
               reverted_log_level=$(echo "$response" | grep -oP '"value":\s*\K[0-9]+') else
                else        echo -e "${GREEN}$(timestamp)${RESET} Failed to revert node log level for IP ${BOLD_GREEN}$ip${RESET}.  reverted_log_level=$(echo "$response" | "$jq_or_grep" -r '.value')Response: ${RED}$response${RESET}"
                  fi      exit 1
          final_size=$(stat -c%s "$log_file" 2>/dev/null || echo 0)          fi
             size_diff=$(( final_size - initial_size ))   done

                # debug_msg "InitialCombine the log fileoutput size: $initial_size"
     for multiple IP addresses into a single summary
          debug_msg "Final logif file(( size: $final_size"
  _diff < 0 )); then
             size_diff_formatted=$(format_size "$size_diff")
  echo -e "castor.log file was rotated."
             duration_formatted=$(format_duration "$duration")else
                  if [[ "$reverted_log_level" echo -eqe "$current_log_level"
]]; then                     echo -e "NodeApproximate ${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 reverted successfully back to${ip_array[*]}. Current castor.log size is ${BOLD_GREEN}${current_log_level_name}${RESET}."
   (format_size "$final_size")${RESET} after ${YELLOW}$duration${RESET} seconds (${YELLOW}$duration_formatted${RESET})."
            else                     echo -e "Failed"
to revert node log level for IP $ip. Response: ${RED}$response${RESET}"   fi
            else
    exit 1           echo -e "${GREEN}$(timestamp)${RESET} Log level change fiis permanent until manually modified."
        done    fi
        fi
# Combine the log outputfi
for}
multiple
IP# addressesRun intoin adetachable single summary
	    if (( size_diff < 0 ))or directly
if $detachable; then
		echo -e "castor.log file was# rotated."Pass 	the main_script function to elsethe screen session and store the output in a file
    debug_msg "**********************************************************" | echotee -ea "$output_log"
    debug_msg "Detach mode - Parameters passed to main_script:" | tee -a "$output_log"
 echo -e "Approximate ${BOLD_GREEN}$size_diff_formatted${RESET} new logs were generated at log level ${BOLD_GREEN}$new_log_level_name${RESET} for IP ${ip_array[*]}. Current castor.log size is ${BOLD_GREEN}$(format_size "$final_size")${RESET} after $duration_formatted."
	    fi
        else
            echo "Log level change is permanent until manually modified."
        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 filedebug_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: $credentials$jq_or_grep"  | tee -a "$output_log"
    debug_msg "     New Log LevelDetach: $new_log_level$detachable" | tee -a "$output_log"
    debug_msg "     New log Level NameDebug: $new_log_level_name$debug" | tee -a "$output_log"
    debug_msg "     Duration: $duration**********************************************************" | tee -a "$output_log"

     debug_msg "     Log Level Type: $log_level_type" | tee -a "$output_log"# Convert associative arrays to strings and pass them to the screen session
    log_levels_string=$(declare -p log_levels)
    debug_msg "log_level_names_string=$(declare -p log_level_names)
    Log Filedebug_msg "log_levels_string: $log_levels_filestring" | tee -a "$output_log"
    debug_msg "     Initial Log File Size: $initial_sizelog_level_names_string: $log_level_names_string" | tee -a "$output_log"

   debug_msg "if command    Current Log Level: $current_log_level" | tee -a "$output_log"-v screen &>/dev/null; then
      debug_msg " echo -e "Running  Cluster Name: $clusterNamein ${YELLOW}screen${RESET} detachable mode..." | tee -a "$output_log"
    debug_msg "   screen -dmS jqcastor_orlog_grep: $jq_or_grep" | tee -a "$output_log"
    debug_msg "     Detach: $detachable" | tee -a "$output_log"
    debug_msg "     Debug: $debug" | tee -a "$output_log"
    debug_msg "**********************************************************" | tee -a "$output_log"script 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 \"$output_log\""
         # Convert associative arrays to strings and pass them toscreen -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)display the output log
        sleep 1
        while screen -list | grep -q "castor_log_script"; do
    debug_msg "log_levels_string: $log_levels_string" | tee -a "$output_log"  sleep 1
 debug_msg "log_level_names_string: $log_level_names_string" | tee -a "$output_log" done
    ifelif command -v screentmux &>/dev/null; then
        echo -e "Running in ${YELLOW}screen$tmux${RESET} detachable mode..." |> tee -a "$output_log"  # Truncate log file to remove old #entries
if
[[ "$debug" ]]; then     # Create a temp #script file
   echo -e "Debug mode enabled. Check the output log for debug messages. temp_script="$SCRIPTDIR/castor_log_script.sh"

        # Write the script to sleepa 5file to avoid printing function definitions
   # fi    cat <<EOF    screen -dmS indexer> "$temp_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}\" | tee \"$output_log\""
        screen -r indexer_script
        # Wait for the screen session to complete and then display the output log
        sleep 1
        while screen -list | grep -q "indexer_script"; do_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"                sleep 1   # Remove the script file
tmux donekill-session -t castor_log_script
EOF

elif command -v tmux &>/dev/null; then   # Ensure the script is executable
echo -e "Running in ${YELLOW}tmux${RESET} detachable mode..." | teechmod -a+x "$output$temp_logscript"

        # ifStart [[ "$debug" ]]; then
  tmux session and execute the script inside
     #   tmux new-session echo-d -es castor_log_script "Debug mode enabled. Check the output log for debug messages."
        #bash $temp_script"

        # Attach session only if it's still running
    sleep 5   while tmux has-session -t castor_log_script 2>/dev/null; do
  # fi         tmux newattach-session -d -s indexert castor_log_script
"$(declare -f main_script timestamp debug_msg format_size format_duration check_jq determine_log_file); 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}\" | tee \"$output_log\""
           done
        # if tmux has-session -t castor_log_script 2>/dev/null; then
        #     tmux attach-session -t indexercastor_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

...