2 # I, Ian Kelling, follow the GNU license recommendations at
3 # https://www.gnu.org/licenses/license-recommendations.en.html. They
4 # recommend that small programs, < 300 lines, be licensed under the
5 # Apache License 2.0. This file contains or is part of one or more small
6 # programs. If a small program grows beyond 300 lines, I plan to switch
9 # Copyright 2024 Ian Kelling
11 # Licensed under the Apache License, Version 2.0 (the "License");
12 # you may not use this file except in compliance with the License.
13 # You may obtain a copy of the License at
15 # http://www.apache.org/licenses/LICENSE-2.0
17 # Unless required by applicable law or agreed to in writing, software
18 # distributed under the License is distributed on an "AS IS" BASIS,
19 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 # See the License for the specific language governing permissions and
21 # limitations under the License.
23 # Copyright (C) 2019 Ian Kelling
24 # Log errors once or so instead of many times.
26 # This program is free software: you can redistribute it and/or modify
27 # it under the terms of the GNU General Public License as published by
28 # the Free Software Foundation, either version 3 of the License, or
29 # (at your option) any later version.
31 # This program is distributed in the hope that it will be useful,
32 # but WITHOUT ANY WARRANTY; without even the implied warranty of
33 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 # GNU General Public License for more details.
36 # You should have received a copy of the GNU General Public License
37 # along with this program. If not, see <http://www.gnu.org/licenses/>.
39 # SPDX-License-Identifier: GPL-3.0-or-later
45 local cbase c log line i out
file o tmp mvout mverr verbose
46 cbase
=/var
/local
/cron-errors
47 [[ $EUID == 0 ]] || cbase
=$HOME/cron-errors
48 local help="Usage: some-command-that-outputs-on-error |& log-once [OPTION]... LOG_NAME
50 Main use case: in cronjobs where STDIN represents an error,
51 but we only want to output that to STDOUT if we've seen this type of
52 error ERRORS(default 3) number of times in a row, then we don't
53 want to output anything again until we've seen a success: no standard input.
55 Logs STDIN to /var/local/cron-errors/LOG_NAME\$error_count or
56 $HOME/cron-errors if not root, and keeps state in the same directory.
58 -ERRORS: ERRORS is the number of errors to accumulate before outputing the error
59 -v: Output the errors along the way to ERRORS
62 You can emulate how cronjobs work by doing this for example:
64 cmdname |& log-once | ifne mail -s 'cmdname failed' root@localhost
66 I could imagine a similar command that considers its non-option args to
67 be an error, but I haven't written it yet.
74 if [[ $1 == --help ]]; then
77 elif [[ $1 == -v ]]; then
80 elif [[ $1 == -[0-9]* ]]; then
83 elif [[ $1 == -- ]]; then
91 # todo, make option & make them overridable based on command line or env variable
92 [[ -d $cbase ]] || mkdir
-p $cbase
95 while read -r line
; do
97 # If we find something that is not just a newline:
104 # file is error file indicating previous error
105 tmp
=($glob); file="${tmp[0]}"
106 if [[ $file == "$glob" ]]; then
113 if (( i
< errors
)); then
115 mvout
=$
(mv $file $new_file 2>&1) || mverr
=$?
117 if [[ $mvout == *": No such file or directory" ]]; then
118 # We've very likely hit a race condition, where another
119 # log-once did the mv before us.
122 echo "log-once: error on mv $file $new_file"
127 if [[ $file == $c$errors ]] ||
$verbose; then
133 if (( errors
== 1 )); then
137 $out $file <<<"log-once: $(date -R)"
141 for o in "${output[@]}"; do
150 if [[ $file == $c$errors ]]; then
151 echo "log-once: $(date -R): success after failure for $c"