#!/bin/bash
#----------------------------------------------------------------------
# NGCP logs parser
#----------------------------------------------------------------------
set -e
set -u


# Constants
config_file='/etc/ngcp-logs/ngcp-logs.conf'
ngcp_logs_dir='/var/log/ngcp/'
ngcp_old_logs_dir="${ngcp_logs_dir}/old/"
ngcp_roles_file='/etc/default/ngcp-roles'
min_length=4
opts_format='h\?d:f:t:n:l:ice'
return_code=0
ngcp_hostname="$(ngcp-hostname)"

proxy='kamailio-proxy'
lb='kamailio-lb'
sems='sems-b2b'
rtpengine='rtp'
b2b='sems-b2b'
api='api'
panel='panel'

_opt_string=false
_opt_date=false
_opt_from=false
_opt_to=false
_opt_node=()
_opt_all=false
_opt_local=false
_opt_inactive=false
_opt_case=false
_opt_extended_search=false
_opt_log=()

#RED='\033[0;31m'
GREEN='\033[1;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color


# Reading configuration from config file
if [ -n "$config_file" ] && [ -r "$config_file" ] ; then
  source $config_file
fi


# Reading information from ngcp-roles
if ! [ -r "${ngcp_roles_file}" ] ; then
  echo -e "Error: can't read file ${ngcp_roles_file} !" >&2
  exit 1
fi

. "${ngcp_roles_file}"

if [ -z "${NGCP_TYPE}" ] ; then
  echo -e "Error: missing NGCP_TYPE in ${ngcp_roles_file}, cannot continue!" >&2
  exit 1
fi

if [ -z "${NGCP_IS_PROXY}" ] ; then
  echo -e "Error: missing NGCP_IS_PROXY in ${ngcp_roles_file}, cannot continue!" >&2
  exit 1
fi

if [ -z "${NGCP_IS_LB}" ] ; then
  echo -e "Error: missing NGCP_IS_LB in ${ngcp_roles_file}, cannot continue!" >&2
  exit 1
fi


# helper function
usage() {
  echo -e "$0 - Search a string in NGCP logs. Useful to retrieve kamailio and sems logs given a call-id.

  Usage: $0 [OPTION...] <string>

    -h,   Display this help text and exit program
    -l,   Log to grep: proxy|lb|sems|b2b|pbx|rtp|api|panel (default: proxy)
    -n,   Node name of the nodes where to grep: prx01a|lb01a|... (default: local node). Use 'all' to grep in all the nodes
    -i,   Search the string also on inactive nodes (default disabled)
    -d,   Exact date of log in the format YYYYMMDD (default today)
    -f,   From date of the log in the format YYYYMMDD (not in use at the moment)
    -t,   From date of the log in the format YYYYMMDD (not in use at the moment)
    -c,   Ignore case distinctions (attention: it could increase the required grep time)
    -e,   Extend the search to log file of the day if no -d option is used and to the following day if -d option is used

  Note: use the '+' separator if you want to specify multiple nodes or logs (for example prx01a+lb01a for nodes or proxy+lb+rtp for logs).
        Or repeat the same option more then one time (for example -n prx01a -n lb01a for nodes or -l proxy -l lb -l rtp for logs).
  "
}


# cmdline handling
if [ $# = 0 ]; then
  echo -e "Try '$0 -h' for more information" >&2
  exit 1
fi

_options=""


while getopts "$opts_format" OPT
do
  case $OPT in
    h|\?)
      usage
      exit 0
      ;;

    d)
      if [[ $OPTARG =~ ^[0-9]{8}$ ]] ; then
        _opt_date=$OPTARG
      else
        echo -e "Please insert a valid date in the form YYYYMMDD" >&2
        exit 1
      fi
      ;;

    f)
      if [[ $OPTARG =~ ^[0-9]{8}$ ]] ; then
        _opt_from=$OPTARG
        _options="${_options} -f $$OPTARG"
      else
        echo -e "Please insert a valid from date in the form YYYYMMDD" >&2
        exit 1
      fi
      ;;

    t)
      if [[ $OPTARG =~ ^[0-9]{8}$ ]] ; then
        _opt_to=$OPTARG
        _options="${_options} -t $OPTARG"
      else
        echo -e "Please insert a valid to date in the form YYYYMMDD" >&2
        exit 1
      fi
      ;;

    n)
      OLDIFS=$IFS
      IFS='+'
      for node in $OPTARG ; do
        if [[ $node =~ ^all$ ]] ; then
          _opt_all=true
          _opt_local=true
        elif [[ $node =~ ^sp[1-2]$|^prx[0-9]{2}[ab]?$|^prx$|^lb[0-9]{2}[ab]?$|^lb$|^slb[0-9]{2}[ab]?$|^slb$|^web[0-9]{2}[ab]?$|^web$|^db[0-9]{2}[ab]?$|^db$ ]] ; then
          eval "_opt_node[${#_opt_node[@]}]=$node"
        else
          echo -e "Node name '$node' not allowed" >&2
          exit 1
        fi
      done
      IFS=$OLDIFS
      if ! $_opt_all && [ ${#_opt_node[@]} -eq 0 ] ; then
        echo -e "Please insert the target node(s)" >&2
        exit 1;
      fi
      ;;

    i)
      _opt_inactive=true
      _options="${_options} -i"
      ;;

    c)
      _opt_case=true
      _options="${_options} -c"
      ;;

    l)
      OLDIFS=$IFS
      IFS='+'
      for log in $OPTARG ; do
        if [[ $log =~ ^proxy$|^p$|^lb$|^l$|^sems$|^s$|^b2b$|^b$|^pbx$|^x$|^rtp$|^r$|^api$|^a$|^panel$|^n$ ]] ; then
          eval "_opt_log[${#_opt_log[@]}]=$log"
        else
          echo -e "Log name '$log' not allowed" >&2
          exit 1
        fi
      done
      _options="${_options} -l $OPTARG"
      IFS=$OLDIFS
      if [ ${#_opt_log[@]} -eq 0 ] ; then
        echo -e "Please insert at least one log among proxy|lb|sems|b2b|rtp|api|panel" >&2
        exit 1;
      fi
      ;;

    e)
      _opt_extended_search=true
      _options="${_options} -e"
      ;;

  esac
done

shift $((OPTIND - 1))
if [ $# -gt 0 ] ; then
  _opt_string=$1
else
  echo -e "Please insert a string to search" >&2
  exit 1
fi


#======================================================================
main()
{
  # String validation
  if [ "${_opt_string}" = false ] ; then
    echo -e "Please insert a string to search" >&2
    exit 1
  fi

  if [ "${#_opt_string}" -lt "${min_length}" ] ; then
    echo -e "String '${_opt_string}' too short. Insert at least ${min_length} characters" >&2
    exit 1
  fi

  # If node list is empty, use the default (localhost)
  if [ ${#_opt_node[@]} -lt 1 ] ; then
    _opt_local=true
  fi

  # If log list is empty, use the default (proxy)
  if [ ${#_opt_log[@]} -lt 1 ] ; then
    _opt_log=(proxy)
  fi


  # Check if the date is today or false (look in the ngcp_logs_dir folder with grep)
  #  or if it is a previous day (look in the ngcp_old_logs_dir folder with zgrep)
  today=$(date +"%Y%m%d")
  if [ "${_opt_date}" = false ] || [ "$today" = "${_opt_date}" ] ; then
    script="grep"
    log_folder="${ngcp_logs_dir}"
    log_date=".log"
    log_date_long=".log-${today}-*.log"
    _opt_date="${today}"

    script2="zgrep"
    log_date_long2=".log-${today}-*.gz"
  else
    if [ "$today" -lt "${_opt_date}" ] ; then
      echo -e "Date in the future, please check the provided argument" >&2
      exit 1
    fi
    script="zgrep"
    log_folder="${ngcp_old_logs_dir}"
    log_date=".log-${_opt_date}.gz"
    log_date_long=".log-${_opt_date}-*.gz"

    script2="zgrep"
    tomorrow=$(date --date="${_opt_date} + 1 day" +"%Y%m%d")
    log_date_long2=".log-${tomorrow}-*.gz"
  fi

  #TODO: get the call date from the CDRs in mysql (mysql -e "select FROM_UNIXTIME(init_time) from accounting.cdr where call_id = '_opt_string';")  ---> check mysql injection

  if [ "${_opt_from}" != false ] && [ "$today" -lt "${_opt_from}" ] ; then
    echo -e "From date in the future, please check the provided argument" >&2
    exit 1
  fi

  if [ "${_opt_to}" != false ] && [ "$today" -lt "${_opt_to}" ] ; then
    echo -e "To date in the future, please check the provided argument" >&2
    exit 1
  fi
  #TODO: Use from and to date to search inside the logs


  # Check if ignore case distinctions is active or not
  if "$_opt_case" ; then
    script="${script} -i"
  fi


  # If --all option specified the script need to use parallel ssh on the other nodes
  if "$_opt_all" || [ ${#_opt_node[@]} -gt 0 ] ; then

    if [ -z "${NGCP_NEIGHBOURS_PSSH:-}" ] ; then
      echo -e "Warning: \$NGCP_NEIGHBOURS_PSSH is not defined, checking local node only!" >&2

    else
      if "$_opt_all" ; then
        if ! parallel-ssh -H"${NGCP_NEIGHBOURS_PSSH}" -i "nice -n 19 ionice -c 3 $0 -d ${_opt_date} ${_options} ${_opt_string}" ; then
          return_code=1
        fi

      else
        # need to filter the hosts
        node_list=""
        for node in "${_opt_node[@]}"; do

          # If node is the localhost
          if [[ "${node}" = "${ngcp_hostname}" ]] ; then
            _opt_local=true
            continue
          fi

          for ssh_node in $(echo "${NGCP_NEIGHBOURS_PSSH}" | tr " " "\n"); do
            if [[ $ssh_node =~ ^$node.* ]] ; then
              node_list="${node_list} ${ssh_node}"
            fi
          done
        done

        if [ ${#node_list} -gt 0 ] ; then
          if ! parallel-ssh -H"${node_list}" -i "nice -n 19 ionice -c 3 $0 -d ${_opt_date} ${_options} ${_opt_string}" ; then
            return_code=1
          fi
        fi
      fi
    fi
  fi


  # Check if the grep should be performed on this node
  if ! "$_opt_local" ; then
    exit 0
  fi


  echo -e "\n${GREEN}************************ ${ngcp_hostname} ************************${NC}"


  # If the local node inactive skip it unless "_opt_inactive" is true
  if ! /usr/sbin/ngcp-check-active && ! "$_opt_inactive" ; then
    echo -e "\nNode skipped because inactive\n"
    exit 0
  fi

  parse_log

  echo -e "\n"
}

#----------------------------------------------------------------------
# Function to check the existence of the file and to execute the grep
grep_exec () {

  long_format_files=0
  if [ ! -f "$log_file" ] ; then
    dirtofind=$(dirname "$log_file_long")
    filestofind=$(set -f && basename "$log_file_long" && set +f)
    num_longfiles=$(set -f; find "${dirtofind}" -name "$filestofind" -type f | wc -l ; set +f)
    if [ "$num_longfiles" -eq 0 ]; then
      echo -e "The file '$log_file' does not exist"
      echo "No long log files found at $log_file_long"
    else
      long_format_files=1
    fi
  fi

  if [ "$_opt_extended_search" = true ]; then
    ex_command2=""
    if [ ! -z "$script2" ]; then
      dirtofind_extra=$(dirname "$log_file_long2")
      filestofind_extra=$(set -f && basename "$log_file_long2" && set +f)
      num_extra_files=$(set -f; find "${dirtofind_extra}" -name "$filestofind_extra" -type f | wc -l ; set +f)
      if [ "$num_extra_files" -gt 0 ]; then
        ex_command2=(${script2} -s "${_opt_string}" ${log_file_long2})
      fi
    fi
  fi
  if [ $long_format_files -eq 0 ]; then
    ex_command=(${script} "${_opt_string}" ${log_file})
  else
    ex_command=(${script} "${_opt_string}" $(find "${dirtofind}" -name "$filestofind" -type f))
  fi
  echo -e "Executing the command: ${ex_command[*]}"
  if ! ${ex_command[*]} ; then
    echo -e "No records found"
  fi

  # execute extra search if user provided -e option
  if [ "$_opt_extended_search" = true ]; then
    if [ ! -z "${ex_command2[*]}" ]; then
      echo -e "Executing extra command to check old files: ${ex_command2[*]}"
      if ! ${ex_command2[*]} ; then
        echo -e "No records found in extra command"
      fi
    fi
  fi

  return 0
}

#----------------------------------------------------------------------
# Function to parse log files
parse_log()
{
  for log_file in "${_opt_log[@]}"; do
    case "${log_file}" in
      lb|l)
        # >>> Kamailio LB Log
        if [ "${NGCP_IS_LB}" == "yes" ] ; then
          echo -e "\n${YELLOW}${ngcp_hostname} >>> LB LOG${NC}"
          log_file="${log_folder}${lb}${log_date}"
          log_file_long="${log_folder}${lb}${log_date_long}"
          log_file_long2="${ngcp_old_logs_dir}${lb}${log_date_long2}"
          grep_exec
        fi
        ;;
      proxy|p)
        # >>> Kamailio PRX Log
        if [ "${NGCP_IS_PROXY}" == "yes" ] ; then
          echo -e "\n${YELLOW}${ngcp_hostname} >>> PROXY LOG${NC}"
          log_file="${log_folder}${proxy}${log_date}"
          log_file_long="${log_folder}${proxy}${log_date_long}"
          log_file_long2="${ngcp_old_logs_dir}${proxy}${log_date_long2}"
          grep_exec
        fi
        ;;
      sems|s)
        # >>> Sems Log
        if [ "${NGCP_IS_PROXY}" == "yes" ] ; then
          echo -e "\n${YELLOW}${ngcp_hostname} >>> SEMS LOG${NC}"
          log_file="${log_folder}${sems}${log_date}"
          log_file_long="${log_folder}${sems}${log_date_long}"
          log_file_long2="${ngcp_old_logs_dir}${sems}${log_date_long2}"
          grep_exec
        fi
        ;;
      b2b|b|pbx|x)
        # >>> Sems-B2B Log
        if [ "${NGCP_IS_PROXY}" == "yes" ] ; then
          echo -e "\n${YELLOW}${ngcp_hostname} >>> B2B LOG${NC}"
          log_file="${log_folder}${b2b}${log_date}"
          log_file_long="${log_folder}${b2b}${log_date_long}"
          log_file_long2="${ngcp_old_logs_dir}${b2b}${log_date_long2}"
          grep_exec
        fi
        ;;
      rtp|r)
        # >>> RtpEngine Log
        if [ "${NGCP_IS_RTP}" == "yes" ] ; then
          echo -e "\n${YELLOW}${ngcp_hostname} >>> RTP LOG${NC}"
          log_file="${log_folder}${rtpengine}${log_date}"
          log_file_long="${log_folder_long}${rtpengine}${log_date_long}"
          log_file_long2="${ngcp_old_logs_dir}${rtpengine}${log_date_long2}"
          grep_exec
        fi
        ;;
      api|a)
        # >>> Api Log
        if [ "${NGCP_IS_MGMT}" == "yes" ] ; then
          echo -e "\n${YELLOW}${ngcp_hostname} >>> API LOG${NC}"
          log_file="${log_folder}${api}${log_date}"
          log_file_long="${log_folder_long}${api}${log_date_long}"
          log_file_long2="${ngcp_old_logs_dir}${api}${log_date_long2}"
          grep_exec
        fi
        ;;
      panel|n)
        # >>> Panel Log
        if [ "${NGCP_IS_MGMT}" == "yes" ] ; then
          echo -e "\n${YELLOW}${ngcp_hostname} >>> PANEL LOG${NC}"
          log_file="${log_folder}${panel}${log_date}"
          log_file_long="${log_folder}${panel}${log_date_long}"
          log_file_long2="${ngcp_old_logs_dir}${panel}${log_date_long2}"
          grep_exec
        fi
        ;;
    esac
  done
}

#----------------------------------------------------------------------

main
exit "${return_code}"


: <<=cut

=pod

=head1 NAME

ngcp-logs - parses NGCP log files in a simplified and improved way

=head1 SYNOPSIS

B<ngcp-logs> [I<option>...] [I<search-string>]

=head1 DESCRIPTION

B<This program> parses NGCP log files and shows them in a simplified and improved way
Default configurations can be modified editing the dedicated config file.

=head1 OPTIONS

=over 8

=item B<-l>

Define which log files should be parsed. Available are proxy,lb,sems,b2b,pbx,rtp,api,panel.
It is possible to use also a short notation: p,l,s,b,x,r,a,n.
If no log is specified, the script will search in the proxy log only.
For multiple log files use the '+' symbol (i.e. proxy+lb+sems) or repeat this option more
than one time (i.e. -l proxy -l lb -l sems).

=item B<-n>

Nodes that will be used for logs search. Relevant on PRO or CARRIER setups.
By default the current host is used but you can specify an alternative one by using the
node name ('prx01a' or 'lb01a' or 'sp2' ... or use 'all' to search on both peers).
Use 'all' to search the string in all the nodes.
For multiple nodes use the '+' symbol (i.e. prx01a+lb01a+sp2) or repeat this option more
than one time (i.e. -n prx01a -n lb01a -n sp2).

=item B<-i>

By default the script skips the search on inactive nodes, to save time and resources.
With this option you can search the string also on inactive nodes.

=item B<-d>

Search logs only for the provided date. By default today.
Format is:
             20170531 (YYYYMMDD)                 - date

=item B<-f>

Search logs only from the provided date. (not active at the moment)
Format is:
             20170531 (YYYYMMDD)                 - date

=item B<-t>

Search logs only till the provided date. (not active at the moment)
Format is:
             20170531 (YYYYMMDD)                 - date

=item B<-c>

Search the string ignoring case distinctions.
Attention: it could increase the required time and resources.

=item B<-e>

 Extend the search to other files.
 If -d option is not provided extend the search to the log file of the day if present.
 If -d option is provided extend the search to the log file of the following day.

=back

=head1 EXIT STATUS

=over 8

=item B<exit code 0>

Everything is ok

=item B<exit code != 0>

Something is wrong, an error message raises

=back

=head1 INCOMPATIBILITIES

No known at this time.

=head1 BUGS AND LIMITATIONS

Please report problems you notice to the Sipwise Support Team <support@sipwise.com>.

=head1 AUTHOR

Marco Capetta <mcapetta@sipwise.com>,
Kirill Solomko <ksolomko@sipwise.com>,
Alessio Garzi <agarzi@sipwise.com>.

=head1 LICENSE AND COPYRIGHT

GPL-3+, Sipwise GmbH, Austria

=cut
