# $Id: log4sh,v 1.4 2005/09/28 20:18:38 sfsetse Exp $ # # log4sh 1.2.8 # # written by Kate Ward # released under the LGPL # # this module implements something like the log4j module from the Apache group # # notes: # *) the default appender is a ConsoleAppender called stdout with a level # of ERROR and SimpleLayout # *) the appender levels are as follows (decreasing order of output): # DEBUG, INFO, WARN, ERROR, FATAL, OFF # # (possibly) declare variables that might come in from outside [ -z "$LOG4SH_CONFIGURATION" ] && LOG4SH_CONFIGURATION='' # treat unset variables as an error when substituting set -u # # constants # __LOG4SH_VERSION=1.2.8 __LOG4SH_TRUE=0 __LOG4SH_FALSE=1 __LOG4SH_CONFIGURATION='log4sh.properties' __LOG4SH_APPENDER_CONSOLE='ConsoleAppender' __LOG4SH_APPENDER_FILE='FileAppender' __LOG4SH_APPENDER_ROLLING_FILE='RollingFileAppender' __LOG4SH_APPENDER_DAILY_ROLLING_FILE='DailyRollingFileAppender' __LOG4SH_LAYOUT_HTML='HTMLLayout' __LOG4SH_LAYOUT_SIMPLE='SimpleLayout' __LOG4SH_LAYOUT_PATTERN='PatternLayout' __LOG4SH_LEVEL_ALL=0 __LOG4SH_LEVEL_DEBUG=1 __LOG4SH_LEVEL_INFO=2 __LOG4SH_LEVEL_WARN=3 __LOG4SH_LEVEL_ERROR=4 __LOG4SH_LEVEL_FATAL=5 __LOG4SH_LEVEL_OFF=6 __LOG4SH_LEVEL_CLOSED=255 __LOG4SH_PATTERN_DEFAULT='%d %p - %m%n' __LOG4SH_THREAD_DEFAULT='main' # set the constants to readonly for _const in `set |grep "^__LOG4SH_" |awk -F= '{print $1}'`; do readonly $_const done unset _const # # variables # __log4sh_filename="`basename $0`" __log4sh_tmpDir='' __log4shAppenders='' __log4shAppenderCount=0 __log4shAppenderFiles='' __log4shAppenderLayouts='' __log4shAppenderLevels='' __log4shAppenderPatterns='' __log4shAppenderTypes='' __log4shThreadArray=$__LOG4SH_THREAD_DEFAULT __log4shThreadName=$__LOG4SH_THREAD_DEFAULT # # private functions # # # Creates a secure temporary directory within which temporary files can be # created. Honors the TMPDIR variable if it is set. # # @return string path to temporary location # _log4sh_mktempDir() { _tmpDir="${TMPDIR-/tmp}/log4sh.$RANDOM$RANDOM$RANDOM$$" (umask 077 && mkdir "$_tmpDir") || { echo "log4sh:FATAL could not create temporary directory! exiting" >&2 exit 1 } echo $_tmpDir unset _tmpDir } # # Converts an internally used level constant into its external tag equivalent # # @param _level internal level (eg. 3) # @echo string external tag equivalent (eg. WARN) # @return boolean was the supplied _level valid? # _log4sh_level2tag() { _level=$1 _return=$__LOG4SH_TRUE _tag='' case $_level in $__LOG4SH_LEVEL_ALL) _tag='ALL' ;; $__LOG4SH_LEVEL_DEBUG) _tag='DEBUG' ;; $__LOG4SH_LEVEL_INFO) _tag='INFO' ;; $__LOG4SH_LEVEL_WARN) _tag='WARN' ;; $__LOG4SH_LEVEL_ERROR) _tag='ERROR' ;; $__LOG4SH_LEVEL_FATAL) _tag='FATAL' ;; $__LOG4SH_LEVEL_OFF) _tag='OFF' ;; $__LOG4SH_LEVEL_CLOSED) _tag='CLOSED' ;; *) _return=$__LOG4SH_FALSE ;; esac echo $_tag unset _level _tag return $_return } # # Converts an externally used tag into its internal constant equivalent # # @param _tag external tag (eg. WARN) # @echo string internal level equivalent (eg. 3) # @return boolean was the supplied _tag valid? # _log4sh_tag2level() { _tag=$1 _level='' _return=$__LOG4SH_TRUE case $_tag in ALL) _level=$__LOG4SH_LEVEL_ALL ;; DEBUG) _level=$__LOG4SH_LEVEL_DEBUG ;; INFO) _level=$__LOG4SH_LEVEL_INFO ;; WARN) _level=$__LOG4SH_LEVEL_WARN ;; ERROR) _level=$__LOG4SH_LEVEL_ERROR ;; FATAL) _level=$__LOG4SH_LEVEL_FATAL ;; OFF) _level=$__LOG4SH_LEVEL_OFF ;; CLOSED) _level=$__LOG4SH_LEVEL_CLOSED ;; *) _return=$__LOG4SH_FALSE ;; esac echo $_level unset _level _tag return $_return } # # Replaces a value at a given position in an array # # @param _pos position of value # @param _value value to place into array # @param _array space separated string of values (poor man's array) # @echo string new version of array # @return boolean boolean result of whether or not array position was found # _log4sh_arrayReplace() { _pos=$1 _value=$2 _array=$3 _i=1 _found=$__LOG4SH_FALSE _max=`echo $_array |wc -w` _new='' while [ $_i -le $_max ]; do if [ $_i -ne $_pos ]; then _new="$_new "`echo $_array |awk "{print \\$$_i}"` else _new="$_new $_value" _found=$__LOG4SH_TRUE fi _i=`expr $_i + 1` done echo "$_new" |sed 's/^ //' _return=$_found unset _i _found _max _new unset _pos _value _array return $_return } # # This function generates message given a pattern template and a message # to use # # Notes: # * The %r character modifier does not work in the Solaris /bin/sh shell # # @param _pattern the pattern template # @param _priority the priority of this logging message # @param _msg the message to be # @echo string parsed according to the pattern template # _log4sh_parsePattern() { _pattern=$1 _priority=$2 _msg=$3 _date=`date '+%Y-%m-%d %H:%M:%S'` echo "$_pattern" |sed \ -e 's/%c/shell/' \ -e "s/%d/$_date/" \ -e "s/%F/$__log4sh_filename/" \ -e 's/%L//' \ -e 's/%n//' \ -e "s/%p/$_priority/" \ -e "s/%r/$SECONDS/" \ -e "s/%t/$__log4shThreadName/" \ -e 's/%x//' \ -e "s%m$_msg" unset _date _pattern _tag _msg } # # Properties file functions # # # parses out the prefix of a property configuration command # # @param string configuration command # @echo string prefix of the configuration command # _log4sh_getPropPrefix() { echo $1 |sed 's/^\([^.]*\)\..*/\1/' } # # strips the prefix off a property configuration command # # @param string configuration command # @echo string configuration command with prefix stripped off # _log4sh_stripPropPrefix() { echo $1 |sed 's/^[^.]*\.//' } # # configures log4sh with an appender property configuration statement # # @param _key configuration command # @param _value configuration value # _log4sh_propAppender() { _key=$1 _value=$2 # strip the leading appender name _key=`_log4sh_stripPropPrefix $_key` # handle appender definitions if [ "$_key" '=' "`echo $_key |sed 's/\.//g'`" ]; then case $_value in $__LOG4SH_APPENDER_CONSOLE) appender_setAppenderType $_key $_value ;; $__LOG4SH_APPENDER_FILE) appender_setAppenderType $_key $_value ;; $__LOG4SH_APPENDER_DAILY_ROLLING_FILE) appender_setAppenderType $_key $_value ;; $__LOG4SH_APPENDER_ROLLING_FILE) appender_setAppenderType $_key $_value ;; *) echo "log4sh:ERROR appender type ($_value) unrecognized" ;; esac unset _key _value return fi # handle appender values and methods _appender=`_log4sh_getPropPrefix $_key` _key=`_log4sh_stripPropPrefix $_key` if [ "$_key" '=' "`echo $_key |sed 's/\.//g'`" ]; then case $_key in DatePattern) ;; # unsupported File) appender_setAppenderFile $_appender "$_value" ;; MaxBackupIndex) ;; # unsupported MaxFileSize) ;; # unsupported Threshold) appender_setLevel $_appender "$_value" ;; layout) appender_setLayout $_appender "$_value" ;; *) echo "log4sh:ERROR appender value/method ($_key) unrecognized" ;; esac unset _key _value _appender return fi # handle appender layout values and methods _key=`_log4sh_stripPropPrefix $_key` case $_key in ConversionPattern) appender_setPattern $_appender "$_value" ;; *) echo "log4sh:ERROR layout value/method ($_key) unrecognized" ;; esac unset _key _value _appender } # # TODO: configure log4sh with a logger configuration statement # # @param _key configuration command # @param _value configuration value # _log4sh_propLogger() { _key=$1 _value=$2 _key=`_log4sh_stripPropPrefix $_key` echo "logger: $_key $_value" unset _key _value } # # configure log4sh with a rootLogger configuration statement # # @param _key configuration command # @param _value configuration value # _log4sh_propRootLogger() { _value=$1 _count=`echo $_value |sed 's/,/ /' |wc -w` _i=1 while [ $_i -le $_count ]; do _a=`echo $_value |awk -F, "{print \\$$_i}"` if [ $_i -eq 1 ]; then logger_setLevel $_a else logger_addAppender $_a fi _i=`expr $_i + 1` done unset _a _count _i unset _value } # # reads a properties file, and configures appropriate configuration functions # # @param _file filename of the properties file # log4sh_readProperties() { _file=$1 _tmpDir=`_log4sh_mktempDir` _tmpFile="$_tmpDir/properties" grep "^log4sh\." $_file >"$_tmpFile" _rp_count=`wc -l $_tmpFile |awk '{print $1}'` _rp_i=1 while [ $_rp_i -le $_rp_count ]; do _line=`awk "$_rp_i==NR {print}" $_tmpFile` _key=`echo "$_line" |sed 's^\([^=]*\)=.*\1'` _value=`echo "$_line" |sed 's^[^=]*=\(.*\)\1'` # strip the leading 'log4sh.' _key=`_log4sh_stripPropPrefix $_key` _keyword=`_log4sh_getPropPrefix $_key` case $_keyword in appender) _log4sh_propAppender $_key "$_value" ;; logger) _log4sh_propLogger $_key "$_value" ;; rootLogger) _log4sh_propRootLogger "$_value" ;; *) echo "log4sh:ERROR unrecognized properties keyword ($_keyword)" ;; esac _rp_i=`expr $_rp_i + 1` done rm -fr $_tmpDir unset _tmpDir _tmpFile _rp_i _rp_count _line _key _value _keyword unset _file } # # Logger # # # sets the filename to be shown when the %F conversion character is used in a # pattern layout. # # @param string string to use as filename # logger_setFilename() { __log4sh_filename=$1 } # # gets the default logging level # # @echo string current logging level (eg. DEBUG) # logger_getLevel() { _log4sh_level2tag $__log4shLevel } # # sets the default logging level # # @param _tag new default logging level (eg. DEBUG) # logger_setLevel() { _tag=$1 _level=`_log4sh_tag2level $_tag` if [ $? -eq $__LOG4SH_TRUE ]; then __log4shLevel=$_level else echo "log4sh:ERROR attempt to set invalid log level '$_tag'" >&2 fi unset _level _tag } # # gets the thread name # # @echo string the current thread name # logger_getThreadName() { echo $__log4shThreadName } # # sets the thread name (eg. the name of the script). this thread name can be # used with the %t conversion character within a pattern layout. # # @param string string to use as thread name # logger_setThreadName() { _thread=$1 __log4shThreadName=$_thread _index=`echo $__log4shThreadArray |wc -w` __log4shThreadArray=`_log4sh_arrayReplace $_index $_thread "$__log4shThreadArray"` unset _index _thread } # # sets the thread name (eg. the name of the script) and pushes the old on to a # stack for later use. This thread name can be used with the %t conversion # character within a pattern layout. # # @param string string to use as thread name # # push thread name # logger_pushThreadName() { _thread=$1 __log4shThreadArray="$__log4shThreadArray $_thread" __log4shThreadName=$_thread unset _thread } # # Sets the thread name from the top item on the stack (and removes it from the # stack). If the stack is empty the default thread name is set. The default # thread name will never be popped from the stack. # logger_popThreadName() { _count=`echo $__log4shThreadArray |wc -w` if [ $_count -gt 1 ]; then __log4shThreadArray="`echo $__log4shThreadArray |sed 's/ [^ ]*$//'`" __log4shThreadName="`echo $__log4shThreadArray |awk '{print \$NF}'`" else __log4shThreadName=$__log4shThreadArray echo "log4sh:WARN no more thread names on stack." >&2 fi unset _count } # # Appenders # # # determines the index position of an appender in the appender array. # # @param _appender the name of the appender # @echo integer the position of the appender in the appender array; 0 if # not found # _appender_getIndex() { _appender=$1 _index=1 while [ $_index -le $__log4shAppenderCount ]; do _a=`echo $__log4shAppenders |awk "{print \\$$_index}"` [ "$_a" '=' "$_appender" ] && break _index=`expr $_index + 1` done [ $_index -gt $__log4shAppenderCount ] && _index=0 echo $_index unset _a _appender _index } # # adds a new appender # # @param _appender name of appender to add # logger_addAppender() { _appender=$1 _level=`logger_getLevel` __log4shAppenders="$__log4shAppenders $_appender" __log4shAppenderFiles="$__log4shAppenderFiles -" __log4shAppenderLevels="$__log4shAppenderLevels -" __log4shAppenderLayouts="$__log4shAppenderLayouts $__LOG4SH_LAYOUT_SIMPLE" __log4shAppenderPatterns="${__log4shAppenderPatterns:+$__log4shAppenderPatterns~}$__LOG4SH_PATTERN_DEFAULT" __log4shAppenderTypes="$__log4shAppenderTypes -" __log4shAppenderCount=`expr $__log4shAppenderCount + 1` unset _appender _layout _level } # # adds a new appender, and configures the appender to use a pattern layout # using the supplied pattern # # @param _myAppender name of new appender # @param _myPattern pattern to set for the new appender # logger_addAppenderWithPattern() { _myAppender=$1 _myPattern=$2 logger_addAppender $_myAppender appender_setLayout $_myAppender $__LOG4SH_LAYOUT_PATTERN appender_setPattern $_myAppender "$_myPattern" unset _myAppender _myPattern } # # Release any resources allocated within the appender # # @param _myAppender name of appender # @return boolean was the operation successful? # appender_close() { _myAppender=$1 # set the appender level to CLOSED appender_setLevel $_myAppender CLOSED _return=$? unset _myAppender return $_return } # # sets the appender type for an appender # # @param _appender name of appender # @param _type type to set appender to # @return boolean was the operation successful? # appender_setAppenderType() { _appender=$1 _type=$2 _index=`_appender_getIndex $_appender` __log4shAppenderTypes=`_log4sh_arrayReplace $_index $_type "$__log4shAppenderTypes"` _return=$? unset _index unset _appender _type return $_return } # # sets the filename for an appender # # @param _appender name of appender # @param _file filename for appender # @return boolean was the operation successful? # appender_setAppenderFile() { _appender=$1 _file=$2 if [ -n "$_appender" -a -n "$_file" ]; then # set the file _index=`_appender_getIndex $_appender` __log4shAppenderFiles=`_log4sh_arrayReplace $_index $_file "$__log4shAppenderFiles"` _return=$? # create the file (if it isn't already) [ $_return -eq $__LOG4SH_TRUE \ -a ! "$_file" '=' "-" \ -a ! "$_file" '=' "STDERR" \ -a ! -f "$_file" \ ] && touch $_file else echo "log4sh:ERROR appender_setAppenderFile(): missing appender and/or file" >&2 $_return=$__LOG4SH_FALSE fi unset _index unset _appender _file return $_return } # # gets the layout of the specified appender # # @param _appender name of appender # @echo string layout of specified appender # appender_getLayout() { _appender=$1 _index=`_appender_getIndex $_appender` if [ $_index -ne 0 ]; then _layout=`echo $__log4shAppenderLayouts |awk "{print \\$$_index}"` else _layout="unknown" fi echo $_layout unset _appender _index _layout } # # sets the layout of the specified appender # # @param _appender name of appender # @param _layout layout for the appender # @return boolean was the operation successful? # appender_setLayout() { _appender=$1 _layout=$2 case $_layout in '') _layout=$__LOG4SH_LAYOUT_SIMPLE ;; $__LOG4SH_LAYOUT_HTML|$__LOG4SH_LAYOUT_SIMPLE|$__LOG4SH_LAYOUT_PATTERN) ;; *) echo "log4sh:ERROR unknown layout $_layout" >&2 return $__LOG4SH_FALSE ;; esac _index=`_appender_getIndex $_appender` __log4shAppenderLayouts=`_log4sh_arrayReplace $_index $_layout "$__log4shAppenderLayouts"` _return=$? unset _appender _layout _index return $_return } # # gets the current logging level of the specified appender # # @param _appender name of appender # @echo string level of appender, or "unknown" if not found # @return boolean was the operation successful? # appender_getLevel() { _appender=$1 _return=$__LOG4SH_TRUE _index=`_appender_getIndex $_appender` if [ $_index -gt 0 ]; then _level=`echo $__log4shAppenderLevels |awk "{print \\$$_index}"` else _level="unknown" _return=$__LOG4SH_FALSE fi echo $_level unset _appender _index _level return $_return } # # set the logging level of the specified appender # # @param _appender name of appender # @param _level level to set appender to # @return boolean was the operation successful? # appender_setLevel() { _appender=$1 _level=$2 _index=`_appender_getIndex $_appender` __log4shAppenderLevels=`_log4sh_arrayReplace $_index $_level "$__log4shAppenderLevels"` _return=$? unset _appender _level _index return $_return } # # gets the pattern of appender at the specified array index # # @param _index array index of the pattern # @echo string pattern # _appender_getPatternAtIndex() { _index=$1 _pattern=`echo "$__log4shAppenderPatterns" |awk -F~ "{print \\$$_index}"` echo $_pattern unset _index } # # sets the pattern of the specified appender # # @param _appender name of appender # @param _pattern pattern to set for appender # @return boolean was the operation successful? # appender_setPattern() { _appender=$1 _pattern=$2 _index=1 _newPatterns='' _found=$__LOG4SH_FALSE while [ $_index -le $__log4shAppenderCount ]; do _a=`echo $__log4shAppenders |awk "{print \\$$_index}"` _p=`echo $__log4shAppenderPatterns |awk -F~ "{print \\$$_index}"` if [ ! "$_a" '=' "$_appender" ]; then _newPatterns="${_newPatterns:+$_newPatterns~}$_p" else _newPatterns="${_newPatterns:+$_newPatterns~}$_pattern" _found=$__LOG4SH_TRUE fi _index=`expr $_index + 1` done __log4shAppenderPatterns="$_newPatterns" _return=$_found unset _a _appender _found _index _newPatterns _p _pattern return $_return } # # Log # # # base logging command that logs a message to all defined appenders # # @param _tag logging level to log the message at # @param _msg message to be logged # log() { _tag=$1 _msg=$2 _level=`_log4sh_tag2level $_tag` _nextIndex=1 _l=0 while [ $_nextIndex -le $__log4shAppenderCount ]; do _myIndex=$_nextIndex _nextIndex=`expr $_nextIndex + 1` # determine appender level _tmpLevel=`echo $__log4shAppenderLevels |awk "{print \\$$_myIndex}"` if [ "$_tmpLevel" = "-" ]; then # continue if requested is level less than general level [ ! $__log4shLevel -le $_level ] && continue else _l=`_log4sh_tag2level $_tmpLevel` # continue if requested level is less than specific appender level [ ! $_l -le $_level ] && continue fi # determine appender layout _tmpLayout=`echo $__log4shAppenderLayouts |awk "{print \\$$_myIndex}"` case $_tmpLayout in $__LOG4SH_LAYOUT_SIMPLE|$__LOG4SH_LAYOUT_HTML) _thisLayout="$_tag - $_msg" ;; $__LOG4SH_LAYOUT_PATTERN) _tmpPattern=`_appender_getPatternAtIndex $_myIndex` _thisLayout=`_log4sh_parsePattern "$_tmpPattern" $_tag "$_msg"` ;; esac # log to appender _appenderType=`echo $__log4shAppenderTypes |awk "{print \\$$_myIndex}"` case $_appenderType in $__LOG4SH_APPENDER_CONSOLE) echo "$_thisLayout" ;; *) _appenderFile=`echo $__log4shAppenderFiles |awk "{print \\$$_myIndex}"` if [ "$_appenderFile" = "STDERR" ]; then echo "$_thisLayout" >&2 elif [ "$_appenderFile" != "-" ]; then echo "$_thisLayout" >>$_appenderFile fi ;; esac done unset _tag _msg unset _appenderFile _appenderType _myIndex _nextIndex _l _level unset _thisLayout _tmpLayout _tmpLevel _tmpPattern } # # This is a helper function for log function in the ALL priority mode. # # @param string message to be logged # logger_all() { log ALL "$1" } # # This is a helper function for log function in the DEBUG priority mode. # # @param string message to be logged # logger_debug() { log DEBUG "$1" } # # This is a helper function for log function in the INFO priority mode. # # @param string message to be logged # logger_info() { log INFO "$1" } # # This is a helper function for log function in the WARN priority mode. # # @param string message to be logged # logger_warn() { log WARN "$1" } # # This is a helper function for log function in the ERROR priority mode. # # @param string message to be logged # logger_error() { log ERROR "$1" } # # This is a helper function for log function in the FATAL priority mode. # # @param string message to be logged # logger_fatal() { log FATAL "$1" } # # main # # load the properties file if [ -n "$LOG4SH_CONFIGURATION" -a -r "$LOG4SH_CONFIGURATION" ]; then log4sh_readProperties $LOG4SH_CONFIGURATION elif [ -z "$LOG4SH_CONFIGURATION" -a -r "$__LOG4SH_CONFIGURATION" ]; then log4sh_readProperties $__LOG4SH_CONFIGURATION else if [ "$LOG4SH_CONFIGURATION" != "none" ]; then echo "log4sh:WARN No appenders could be found." >&2 echo "log4sh:WARN Please initalize the log4sh system properly." >&2 fi logger_setLevel ERROR logger_addAppender stdout appender_setAppenderType stdout ConsoleAppender fi # treat unset variables as an error when substituting (disable) set +u