#!/bin/bash
#< Log rotation script, able to rotate and compress logs - run from cron
BASENAME="/usr/bin/basename"
AWK="/usr/bin/awk"
BZIP="/usr/bin/bzip2"
CP="/bin/cp"
CUT="/usr/bin/cut"
ECHO="/bin/echo"
EGREP="/usr/bin/egrep"
GREP="/usr/bin/grep"
GZIP="/usr/bin/gzip"
SED="/usr/bin/sed"
#############################################################################
# Config file should contain fields of the following format:
#
# logfile_to_rotate:num_rotations:action
#
# where action is one of
# g GZIP after rotation
# b BZIP after rotation
# n do nothing after rotation
#
# Whole line comments, starting with '#', are permitted
#
# No white space should exist in the file
#
CONFIG_FILE="/Users/kevin/home_projects/logrevolver/0.1/etc/logrevolver.conf"
#############################################################################
THIS_PROG=$( ${BASENAME} $0 )
VERBOSE=0
function print_usage {
{
${ECHO} "Usage: ${THIS_PROG} [-h] [-c <config_file>]"
${ECHO} " -c <config_file> Specify path to configuration file"
${ECHO} " -h Display this help message"
${ECHO} " -v Verbose mode"
} >&2
}
function print_error {
${ECHO} "Error: $@" >&2
}
function check_file_exists {
if [ -e "$1" ]; then
return 0
else
return 1
fi
}
function check_config_file_syntax {
# Here, we'll go over each line in the config file and ensure
# that target logs exist, number of rotations is sane, and archival
# options are correct. We end up parsing the config file twice (once here,
# and once in the process_logs function. Oh well.
LINE_COUNT=0
ERROR_COUNT=0
while read LINE; do
(( LINE_COUNT = LINE_COUNT + 1 ))
${ECHO} "${LINE}" | ${GREP} '^#' >/dev/null 2>&1
if [ "$?" -eq "0" ]; then
continue
fi
NUM_FIELDS=$( ${ECHO} "${LINE}" | ${AWK} -v FS=":" '{print NF}' )
if [ "${NUM_FIELDS}" -ne "3" ]; then
print_error "Line ${LINE_COUNT} does not have three fields"
(( ERROR_COUNT = ERROR_COUNT + 1 ))
else
LOGFILE=$( ${ECHO} "${LINE}" | ${CUT} -d: -f1 )
ROTATIONS=$( ${ECHO} "${LINE}" | ${CUT} -d: -f2 )
METHOD=$( ${ECHO} "${LINE}" | ${CUT} -d: -f3 )
if [ ! -e "${LOGFILE}" ]; then
print_error "Line ${LINE_COUNT} logfile does not exist"
(( ERROR_COUNT = ERROR_COUNT + 1 ))
fi
${ECHO} "${ROTATIONS}" | ${EGREP} "^[0-9]+$" >/dev/null 2>&1
if [ "$?" -ne "0" ]; then
print_error "Line ${LINE_COUNT} rotation count should be an integer"
(( ERROR_COUNT = ERROR_COUNT + 1 ))
else
if [ "${ROTATIONS}" -eq "0" ]; then
print_error "Line ${LINE_COUNT} rotation count should be greater than zero"
(( ERROR_COUNT = ERROR_COUNT + 1 ))
fi
fi
${ECHO} "${METHOD}" | ${GREP} "^[bgn]$" >/dev/null 2>&1
if [ "$?" -ne "0" ]; then
print_error "Line ${LINE_COUNT} invalid archival method specified"
(( ERROR_COUNT = ERROR_COUNT + 1 ))
fi
fi
done < ${CONFIG_FILE}
if [ "${ERROR_COUNT}" -gt "0" ]; then
print_error "${ERROR_COUNT} errors found in configuration file. Exiting."
exit 1
fi
}
function check_config_file {
check_file_exists "${CONFIG_FILE}"
if [ "$?" -ne "0" ]; then
print_error "Config file ${CONFIG_FILE} does not exist"
exit 1
fi
check_config_file_syntax
}
function process_logs {
${GREP} '^[^#]' ${CONFIG_FILE} | while read LINE; do
ROTATIONS=$( ${ECHO} "${LINE}" | ${CUT} -d: -f2 )
METHOD=$( ${ECHO} "${LINE}" | ${CUT} -d: -f3 )
for (( i = $(( ROTATIONS - 1 )); i >= 0; i-- )); do
LOGFILE=$( ${ECHO} "${LINE}" | ${CUT} -d: -f1 )
if [ "${i}" -gt "0" ]; then
case ${METHOD} in
"b") LOGFILE="${LOGFILE}.${i}.bz2" ;;
"g") LOGFILE="${LOGFILE}.${i}.gz" ;;
"n") LOGFILE="${LOGFILE}.${i}" ;;
esac
fi
if [ -e "${LOGFILE}" ]; then
(( NEXTi = i + 1 ))
if [ "${i}" -eq "0" ]; then
TARGET="${LOGFILE}.${NEXTi}"
${CP} -p ${LOGFILE} ${TARGET}
> ${LOGFILE}
case ${METHOD} in
"b") (( VERBOSE )) && {
${ECHO} "--> Rotating ${LOGFILE} to ${TARGET}.bz2"
}
${BZIP} -9 -f ${TARGET}
;;
"g")
(( VERBOSE )) && {
${ECHO} "--> Rotating ${LOGFILE} to ${TARGET}.gz"
}
${GZIP} -9 -f ${TARGET}
;;
"n") (( VERBOSE )) && {
${ECHO} "--> Rotating ${LOGFILE} to ${TARGET}"
}
;;
esac
else
case ${METHOD} in
"b") TARGET=$( ${ECHO} "${LOGFILE}" | ${SED} 's/\.bz2$//' )
TARGET="${TARGET%.*}.${NEXTi}"
(( VERBOSE )) && {
${ECHO} "--> Rotating ${LOGFILE} to ${TARGET}.bz2"
}
${CP} -p ${LOGFILE} ${TARGET}.bz2
;;
"g") TARGET=$( ${ECHO} "${LOGFILE}" | ${SED} 's/\.gz$//' )
TARGET="${TARGET%.*}.${NEXTi}"
(( VERBOSE )) && {
${ECHO} "--> Rotating ${LOGFILE} to ${TARGET}.gz"
}
${CP} -p ${LOGFILE} ${TARGET}.gz
;;
"n") TARGET="${LOGFILE%.*}.${NEXTi}"
(( VERBOSE )) && {
${ECHO} "--> Rotating ${LOGFILE} to ${TARGET}"
}
${CP} -p ${LOGFILE} ${TARGET}
;;
esac
fi
fi
done
done
}
while getopts ":hc:v" OPTION; do
case ${OPTION} in
"h") print_usage && exit 0 ;;
"c") CONFIG_FILE="${OPTARG}" ;;
"v") VERBOSE=1 ;;
* ) print_usage && exit 1 ;;
esac
done
check_config_file
process_logs