simplify log once
[log-quiet] / log-once
1 #!/bin/bash
2 # Copyright (C) 2019 Ian Kelling
3 # SPDX-License-Identifier: AGPL-3.0-or-later
4
5 append() {
6 cat >> "$1"
7 }
8 log-once() {
9 local cbase c log line i out file o tmp
10 cbase=/var/local/cron-errors
11 [[ $EUID == 0 ]] || cbase=$HOME/cron-errors
12 local help="Usage: some-command-that-outputs-on-error |& log-once [OPTION]... LOG_NAME
13
14 Main use case: in cronjobs where STDIN represents an error,
15 but we only want to output that to STDOUT if we've seen this type of
16 error ERRORS(default 3) number of times in a row, then we don't
17 want to output anything again until we've seen a success: no standard input.
18
19 Logs STDIN to /var/local/cron-errors/LOG_NAME\$error_count or
20 $HOME/cron-errors if not root, and keeps state in the same directory.
21
22 -ERRORS: ERRORS is the number of errors to accumulate before outputing the error
23
24
25 You can emulate how cronjobs work by doing this for example:
26
27 cmdname |& log-once | ifne mail -s 'cmdname failed' root@localhost
28
29 I could imagine a similar command that considers its non-option args to
30 be an error, but I haven't written it yet.
31 "
32 errors=3
33 while true; do
34 if [[ $1 == --help ]]; then
35 echo "$help"
36 return
37 elif [[ $1 == -[0-9]* ]]; then
38 errors=${1#-}
39 shift
40 elif [[ $1 == -- ]]; then
41 shift
42 break
43 else
44 break
45 fi
46 done
47 log_name=$1
48 # todo, make option & make them overridable based on command line or env variable
49 [[ -d $cbase ]] || mkdir -p $cbase
50 c=$cbase/$log_name
51 log=false
52 while read -r line; do
53 output+=( "$line" )
54 # If we find something that is not just a newline:
55 if [[ $line ]]; then
56 log=true
57 break
58 fi
59 done
60 glob="$c[0-9]*"
61 # file is error file indicating previous error
62 tmp=($glob); file="${tmp[0]}"
63 if [[ $file == "$glob" ]]; then
64 file=
65 fi
66 if $log; then
67 out=append
68 if [[ $file ]]; then
69 i="${file#$c}"
70 if (( i < errors )); then
71 new_file=$c$((i+1))
72 mv $file $new_file
73 file=$new_file
74 if [[ $file == $c$errors ]]; then
75 out="tee -a"
76 fi
77 fi
78 else
79 file=${c}1
80 if (( errors == 1 )); then
81 out="tee -a"
82 fi
83 fi
84 $out $file <<<"log-once: $(date -R)"
85 if [[ $2 ]]; then
86 $out $file <<<"$*"
87 else
88 for o in "${output[@]}"; do
89 $out $file <<<"$o"
90 done
91 $out $file
92 fi
93 return 0
94 fi
95 if [[ $file ]]; then
96 rm -f $file
97 if [[ $file == $c$errors ]]; then
98 echo "log-once: $(date -R): success after failure for $c"
99 fi
100 fi
101 return 0
102 }
103 log-once "$@"