forked from idelsink/b-log
-
Notifications
You must be signed in to change notification settings - Fork 0
/
b-log.sh
executable file
·393 lines (373 loc) · 15.2 KB
/
b-log.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
#!/usr/bin/env bash
#########################################################################
# Script Name: b-log
# Script Version: See B_LOG_VERSION
# Script Date: 30 June 2016
#########################################################################
#
# a bash-logging interface, hence the name b-log.
# pronounced as 'bee log' or 'blog'... whatever you like.
#########################################################################
# include guard
[ -n "${B_LOG_SH+x}" ] && return || readonly B_LOG_SH=1
# global parameters
# default disable these settings
#set -e # kill script if a command fails
#set -o nounset # unset values give error
#set -o pipefail # prevents errors in a pipeline from being masked
B_LOG_APPNAME="b-log"
B_LOG_VERSION=1.2.2
# --- global variables ----------------------------------------------
# log levels
readonly LOG_LEVEL_OFF=0 # none
readonly LOG_LEVEL_FATAL=100 # unusable, crash
readonly LOG_LEVEL_ERROR=200 # error conditions
readonly LOG_LEVEL_WARN=300 # warning conditions
readonly LOG_LEVEL_NOTICE=400 # Nothing serious, but notably nevertheless.
readonly LOG_LEVEL_INFO=500 # informational
readonly LOG_LEVEL_DEBUG=600 # debug-level messages
readonly LOG_LEVEL_TRACE=700 # see stack traces
readonly LOG_LEVEL_ALL=-1 # all enabled
#############################
# Log template
#############################
# template based on a number between '@x@'
# so the following @a:b@ means:
# a is the string length and b is the selector
# or @a@ means:
# a is the selector
# so, @1@ will return the timestamp
# and @5:1@ will return the timestamp of string length 5
# selector: item
# 1: timestamp
# 2: log level name
# 3: function name
# 4: line number
# 5: log message
# 6: space
# 7: filename
: "${B_LOG_DEFAULT_TEMPLATE:="[@23:1@][@6:2@][@3@:@3:4@] @5@"}" # default template
# log levels information
# level code, level name, level template, prefix(colors etc.), suffix(colors etc.)
LOG_LEVELS=(
"${LOG_LEVEL_FATAL}" "FATAL" "${B_LOG_DEFAULT_TEMPLATE}" "\e[41;37m" "\e[0m"
"${LOG_LEVEL_ERROR}" "ERROR" "${B_LOG_DEFAULT_TEMPLATE}" "\e[1;31m" "\e[0m"
"${LOG_LEVEL_WARN}" "WARN" "${B_LOG_DEFAULT_TEMPLATE}" "\e[1;33m" "\e[0m"
"${LOG_LEVEL_NOTICE}" "NOTICE" "${B_LOG_DEFAULT_TEMPLATE}" "\e[1;32m" "\e[0m"
"${LOG_LEVEL_INFO}" "INFO" "${B_LOG_DEFAULT_TEMPLATE}" "\e[37m" "\e[0m"
"${LOG_LEVEL_DEBUG}" "DEBUG" "${B_LOG_DEFAULT_TEMPLATE}" "\e[1;34m" "\e[0m"
"${LOG_LEVEL_TRACE}" "TRACE" "${B_LOG_DEFAULT_TEMPLATE}" "\e[94m" "\e[0m"
)
# log levels columns
readonly LOG_LEVELS_LEVEL=0
readonly LOG_LEVELS_NAME=1
readonly LOG_LEVELS_TEMPLATE=2
readonly LOG_LEVELS_PREFIX=3
readonly LOG_LEVELS_SUFFIX=4
LOG_LEVEL=${LOG_LEVEL_WARN} # current log level
B_LOG_LOG_VIA_STDOUT=true # log via stdout
B_LOG_LOG_VIA_FILE="" # file if logging via file (file, add suffix, add prefix)
B_LOG_LOG_VIA_FILE_PREFIX=false # add prefix to log file
B_LOG_LOG_VIA_FILE_SUFFIX=false # add suffix to log file
B_LOG_LOG_VIA_SYSLOG="" # syslog flags so that "syslog 'flags' message"
B_LOG_TS="" # timestamp variable
B_LOG_TS_FORMAT="%Y-%m-%d %H:%M:%S.%N" # timestamp format
B_LOG_LOG_LEVEL_NAME="" # the name of the log level
B_LOG_LOG_MESSAGE="" # the log message
function B_LOG_ERR() {
# @description internal error message handler
# @param $1 return code of a command etc.
# @param $2 message when return code is 1
local return_code=${1:-0}
local return_message=${2:=""}
local prefix="\e[1;31m" # error color
local suffix="\e[0m" # error color
if [ $return_code -eq 1 ]; then
echo -e "${prefix}${return_message}${suffix}"
fi
}
function B_LOG(){
# @description setup interface
# see -h for help
local OPTIND=""
function PRINT_USAGE() {
# @description prints the short usage of the script
echo ""
echo "Usage: B_LOG [options]"
echo " -h, --help Show usage"
echo " -V, --version Version"
echo " -d, --date-format Date format used in the log eg. '%Y-%m-%d %H:%M:%S.%N'"
echo " -o, --stdout Log over stdout (true/false) default true."
echo " -f, --file File to log to, none set means disabled"
echo " --file-prefix-enable Enable the prefix for the log file"
echo " --file-prefix-disable Disable the prefix for the log file"
echo " --file-suffix-enable Enable the suffix for the log file"
echo " --file-suffix-disable Disable the suffix for the log file"
echo " -s, --syslog 'switches you want to use'. None set means disabled"
echo " results in: \"logger 'switches' log-message\""
echo " -l, --log-level The log level"
echo " Log levels : value"
echo " ---------------- : -----"
echo " LOG_LEVEL_OFF : ${LOG_LEVEL_OFF}"
echo " LOG_LEVEL_FATAL : ${LOG_LEVEL_FATAL}"
echo " LOG_LEVEL_ERROR : ${LOG_LEVEL_ERROR}"
echo " LOG_LEVEL_WARN : ${LOG_LEVEL_WARN}"
echo " LOG_LEVEL_NOTICE : ${LOG_LEVEL_NOTICE}"
echo " LOG_LEVEL_INFO : ${LOG_LEVEL_INFO}"
echo " LOG_LEVEL_DEBUG : ${LOG_LEVEL_DEBUG}"
echo " LOG_LEVEL_TRACE : ${LOG_LEVEL_TRACE}"
echo ""
}
for arg in "$@"; do # transform long options to short ones
shift
case "$arg" in
"--help") set -- "$@" "-h" ;;
"--version") set -- "$@" "-V" ;;
"--log-level") set -- "$@" "-l" ;;
"--date-format") set -- "$@" "-d" ;;
"--stdout") set -- "$@" "-o" ;;
"--file") set -- "$@" "-f" ;;
"--file-prefix-enable") set -- "$@" "-a" "file-prefix-enable" ;;
"--file-prefix-disable") set -- "$@" "-a" "file-prefix-disable" ;;
"--file-suffix-enable") set -- "$@" "-a" "file-suffix-enable" ;;
"--file-suffix-disable") set -- "$@" "-a" "file-suffix-disable" ;;
"--syslog") set -- "$@" "-s" ;;
*) set -- "$@" "$arg"
esac
done
# get options
while getopts "hVd:o:f:s:l:a:" optname
do
case "$optname" in
"h")
PRINT_USAGE
;;
"V")
echo "${B_LOG_APPNAME} v${B_LOG_VERSION}"
;;
"d")
B_LOG_TS_FORMAT=${OPTARG}
;;
"o")
if [ "${OPTARG}" = true ]; then
B_LOG_LOG_VIA_STDOUT=true
else
B_LOG_LOG_VIA_STDOUT=false
fi
;;
"f")
B_LOG_LOG_VIA_FILE=${OPTARG}
;;
"a")
case ${OPTARG} in
'file-prefix-enable' )
B_LOG_LOG_VIA_FILE_PREFIX=true
;;
'file-prefix-disable' )
B_LOG_LOG_VIA_FILE_PREFIX=false
;;
'file-suffix-enable' )
B_LOG_LOG_VIA_FILE_SUFFIX=true
;;
'file-suffix-disable' )
B_LOG_LOG_VIA_FILE_SUFFIX=false
;;
*)
;;
esac
;;
"s")
B_LOG_LOG_VIA_SYSLOG=${OPTARG}
;;
"l")
LOG_LEVEL=${OPTARG}
;;
*)
B_LOG_ERR '1' "unknown error while processing B_LOG option."
;;
esac
done
shift "$((OPTIND-1))" # shift out all the already processed options
}
function B_LOG_get_log_level_info() {
# @description get the log level information
# @param $1 log type
# @return returns information in the variables
# - log level name
# - log level template
# ...
local log_level=${1:-"$LOG_LEVEL_ERROR"}
LOG_FORMAT=""
LOG_PREFIX=""
LOG_SUFFIX=""
local i=0
for ((i=0; i<${#LOG_LEVELS[@]}; i+=$((LOG_LEVELS_SUFFIX+1)))); do
if [[ "$log_level" == "${LOG_LEVELS[i]}" ]]; then
B_LOG_LOG_LEVEL_NAME="${LOG_LEVELS[i+${LOG_LEVELS_NAME}]}"
LOG_FORMAT="${LOG_LEVELS[i+${LOG_LEVELS_TEMPLATE}]}"
LOG_PREFIX="${LOG_LEVELS[i+${LOG_LEVELS_PREFIX}]}"
LOG_SUFFIX="${LOG_LEVELS[i+${LOG_LEVELS_SUFFIX}]}"
return 0
fi
done
return 1
}
function B_LOG_convert_template() {
# @description converts the template to a usable string
# only call this after filling the global parameters
# @return fills a variable called 'B_LOG_CONVERTED_TEMPLATE_STRING'.
local template=${*:-}
local selector=0
local str_length=0
local to_replace=""
local log_layout_part=""
local found_pattern=true
B_LOG_CONVERTED_TEMPLATE_STRING=""
while $found_pattern ; do
if [[ "${template}" =~ @[0-9]+@ ]]; then
to_replace=${BASH_REMATCH[0]}
selector=${to_replace:1:(${#to_replace}-2)}
elif [[ "${template}" =~ @[0-9]+:[0-9]+@ ]]; then
to_replace=${BASH_REMATCH[0]}
if [[ "${to_replace}" =~ @[0-9]+: ]]; then
str_length=${BASH_REMATCH[0]:1:(${#BASH_REMATCH[0]}-2)}
else
str_length=0
fi
if [[ "${to_replace}" =~ :[0-9]+@ ]]; then
selector=${BASH_REMATCH[0]:1:(${#BASH_REMATCH[0]}-2)}
fi
else
found_pattern=false
fi
case "$selector" in
1) # timestamp
log_layout_part="${B_LOG_TS}"
;;
2) # log level name
log_layout_part="${B_LOG_LOG_LEVEL_NAME}"
;;
3) # function name
log_layout_part="${FUNCNAME[4]}"
;;
4) # line number
log_layout_part="${BASH_LINENO[3]}"
;;
5) # message
log_layout_part="${B_LOG_LOG_MESSAGE}"
;;
6) # space
log_layout_part=" "
;;
7) # file name
log_layout_part="$(basename ${BASH_SOURCE[4]})"
;;
*)
B_LOG_ERR '1' "unknown template parameter: '$selector'"
log_layout_part=""
;;
esac
if [ ${str_length} -gt 0 ]; then # custom string length
if [ ${str_length} -lt ${#log_layout_part} ]; then
# smaller as string, truncate
log_layout_part=${log_layout_part:0:str_length}
elif [ ${str_length} -gt ${#log_layout_part} ]; then
# bigger as string, append
printf -v log_layout_part "%-0${str_length}s" $log_layout_part
fi
fi
str_length=0 # set default
template="${template/$to_replace/$log_layout_part}"
done
B_LOG_CONVERTED_TEMPLATE_STRING=${template}
return 0
}
function B_LOG_print_message() {
log_level=${1:-"$LOG_LEVEL_ERROR"}
shift
if [ ${log_level} -gt ${LOG_LEVEL} ]; then # check log level
if [ ! ${LOG_LEVEL} -eq ${LOG_LEVEL_ALL} ]; then # check log level
if [ -z "${*:-}" ]; then # if message is empty, consume stdin
cat > /dev/null
fi
return 0;
fi
fi
# log level bigger as LOG_LEVEL? and level is not -1? return
local message=${*:-}
if [ -z "$message" ]; then # if message is empty, get from stdin
while read -r line; do
B_LOG_print_message_line "${line}"
done < /dev/stdin
else
B_LOG_print_message_line "${message}"
fi
}
B_LOG_print_message_line() {
local message=${1:-}
local file_directory=""
local err_ret_code=0
B_LOG_TS=$(date +"${B_LOG_TS_FORMAT}") # get the date
B_LOG_LOG_MESSAGE="${message}"
B_LOG_get_log_level_info "${log_level}" || true
B_LOG_convert_template ${LOG_FORMAT} || true
# output to stdout
if [ "${B_LOG_LOG_VIA_STDOUT}" = true ]; then
echo -ne "$LOG_PREFIX"
echo -ne "${B_LOG_CONVERTED_TEMPLATE_STRING}"
echo -e "$LOG_SUFFIX"
fi
# output to file
if [ ! -z "${B_LOG_LOG_VIA_FILE}" ]; then
file_directory=$(dirname $B_LOG_LOG_VIA_FILE)
if [ ! -z "${file_directory}" ]; then
if [ ! -d "${B_LOG_LOG_VIA_FILE%/*}" ]; then # check directory
# directory does not exist
mkdir -p "${file_directory}" || err_ret_code=$?
B_LOG_ERR "${err_ret_code}" "Error while making log directory: '${file_directory}'. Are the permissions ok?"
fi
fi
if [ ! -e "${B_LOG_LOG_VIA_FILE}" ]; then # check file
# file does not exist and making of folder went ok
if [ $err_ret_code -ne 1 ]; then
touch "${B_LOG_LOG_VIA_FILE}" || err_ret_code=$?
B_LOG_ERR "${err_ret_code}" "Error while making log file: '${B_LOG_LOG_VIA_FILE}'. Are the permissions ok?"
fi
else
message=""
if [ "${B_LOG_LOG_VIA_FILE_PREFIX}" = true ]; then
message="${message}${LOG_PREFIX}"
fi
message="${message}${B_LOG_CONVERTED_TEMPLATE_STRING}"
if [ "${B_LOG_LOG_VIA_FILE_SUFFIX}" = true ]; then
message="${message}${LOG_SUFFIX}"
fi
echo -e "${message}" >> ${B_LOG_LOG_VIA_FILE} || true
fi
fi
# output to syslog
if [ ! -z "${B_LOG_LOG_VIA_SYSLOG}" ]; then
logger ${B_LOG_LOG_VIA_SYSLOG} "${B_LOG_CONVERTED_TEMPLATE_STRING}" || err_ret_code=$?
B_LOG_ERR "${err_ret_code}" "Error while logging with syslog. Where these flags ok: '${B_LOG_LOG_VIA_SYSLOG}'"
fi
}
# Define commands
# Setting of log level
function LOG_LEVEL_OFF() { B_LOG --log-level ${LOG_LEVEL_OFF} "$@"; }
function LOG_LEVEL_FATAL() { B_LOG --log-level ${LOG_LEVEL_FATAL} "$@"; }
function LOG_LEVEL_ERROR() { B_LOG --log-level ${LOG_LEVEL_ERROR} "$@"; }
function LOG_LEVEL_WARN() { B_LOG --log-level ${LOG_LEVEL_WARN} "$@"; }
function LOG_LEVEL_NOTICE() { B_LOG --log-level ${LOG_LEVEL_NOTICE} "$@"; }
function LOG_LEVEL_INFO() { B_LOG --log-level ${LOG_LEVEL_INFO} "$@"; }
function LOG_LEVEL_DEBUG() { B_LOG --log-level ${LOG_LEVEL_DEBUG} "$@"; }
function LOG_LEVEL_TRACE() { B_LOG --log-level ${LOG_LEVEL_TRACE} "$@"; }
function LOG_LEVEL_ALL() { B_LOG --log-level ${LOG_LEVEL_ALL} "$@"; }
# Log commands
function B_LOG_MESSAGE() { B_LOG_print_message "$@"; }
function FATAL() { B_LOG_print_message ${LOG_LEVEL_FATAL} "$@"; }
function ERROR() { B_LOG_print_message ${LOG_LEVEL_ERROR} "$@"; }
function WARN() { B_LOG_print_message ${LOG_LEVEL_WARN} "$@"; }
function NOTICE() { B_LOG_print_message ${LOG_LEVEL_NOTICE} "$@"; }
function INFO() { B_LOG_print_message ${LOG_LEVEL_INFO} "$@"; }
function DEBUG() { B_LOG_print_message ${LOG_LEVEL_DEBUG} "$@"; }
function TRACE() { B_LOG_print_message ${LOG_LEVEL_TRACE} "$@"; }