Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17241e0d80
|
||
|
|
239721a8f6
|
||
|
|
7ac825b59d
|
||
|
|
1aff092d62
|
||
|
|
85c97bcf12
|
||
|
|
fb33fa7596
|
||
|
|
f759b8bb1e
|
||
|
|
93f5a9aaad
|
@@ -29,3 +29,10 @@ alert_method=sysalert.email
|
|||||||
#mail_to=root <root@localhost>
|
#mail_to=root <root@localhost>
|
||||||
smtp_host=localhost
|
smtp_host=localhost
|
||||||
|
|
||||||
|
# Specific services can have their own configuration
|
||||||
|
# If you have an unstable service that sometimes fails three times
|
||||||
|
# before it recovers you can override max_failures for this service
|
||||||
|
# by adding a section named after that service:
|
||||||
|
#
|
||||||
|
#[unstable.service]
|
||||||
|
#max_failures=3
|
||||||
|
|||||||
+1
-2
@@ -11,12 +11,11 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
description = "Generic OnFailure= and OnSuccess= handler for systemd"
|
description = "Generic OnFailure= and OnSuccess= handler for systemd"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { file = "LICENSE" }
|
license = "MIT"
|
||||||
keywords = [ "systemd" ]
|
keywords = [ "systemd" ]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 4 - Beta",
|
||||||
"Intended Audience :: System Administrators",
|
"Intended Audience :: System Administrators",
|
||||||
"License :: OSI Approved :: MIT License",
|
|
||||||
"Operating System :: POSIX :: Linux",
|
"Operating System :: POSIX :: Linux",
|
||||||
"Topic :: System :: Monitoring",
|
"Topic :: System :: Monitoring",
|
||||||
]
|
]
|
||||||
|
|||||||
+15
-1
@@ -2,7 +2,7 @@ import datetime
|
|||||||
import os
|
import os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
def register_success(name, db):
|
def clear_failure(name, db):
|
||||||
cur = db.cursor()
|
cur = db.cursor()
|
||||||
cur.execute('''
|
cur.execute('''
|
||||||
DELETE FROM alert
|
DELETE FROM alert
|
||||||
@@ -86,6 +86,20 @@ def get_failures(name, db):
|
|||||||
failures.sort(key=lambda x: x['timestamp'])
|
failures.sort(key=lambda x: x['timestamp'])
|
||||||
return failures
|
return failures
|
||||||
|
|
||||||
|
def get_services(db):
|
||||||
|
cur = db.cursor()
|
||||||
|
cur.execute('''SELECT
|
||||||
|
service.name, COUNT(*)
|
||||||
|
FROM service
|
||||||
|
JOIN failure ON
|
||||||
|
failure.service=service.id
|
||||||
|
GROUP BY
|
||||||
|
service.name;
|
||||||
|
''')
|
||||||
|
ret = {name: failures for (name, failures) in cur.fetchall()}
|
||||||
|
db.commit()
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def init(path):
|
def init(path):
|
||||||
con = sqlite3.connect(path, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES)
|
con = sqlite3.connect(path, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES)
|
||||||
|
|||||||
@@ -50,7 +50,11 @@ def failure(name, failures, config):
|
|||||||
reader.add_match(MONITOR_INVOCATION_ID=latest_inv_id)
|
reader.add_match(MONITOR_INVOCATION_ID=latest_inv_id)
|
||||||
reader.add_disjunction()
|
reader.add_disjunction()
|
||||||
reader.add_match(_SYSTEMD_INVOCATION_ID=latest_inv_id)
|
reader.add_match(_SYSTEMD_INVOCATION_ID=latest_inv_id)
|
||||||
journal_txt = "\n".join([entry['MESSAGE'] for entry in reader])
|
journal_txt = "\n".join(
|
||||||
|
[
|
||||||
|
entry["MESSAGE"]
|
||||||
|
for entry in list(reader)[-200:]
|
||||||
|
])
|
||||||
|
|
||||||
if nr_failures <= 1:
|
if nr_failures <= 1:
|
||||||
subject=f"{hostname} - {name}: failure"
|
subject=f"{hostname} - {name}: failure"
|
||||||
|
|||||||
+58
-3
@@ -1,4 +1,4 @@
|
|||||||
|
import argparse
|
||||||
import configparser
|
import configparser
|
||||||
import datetime
|
import datetime
|
||||||
import importlib
|
import importlib
|
||||||
@@ -37,11 +37,12 @@ def register_exit(config, db):
|
|||||||
if os.environ['MONITOR_SERVICE_RESULT'] == 'success':
|
if os.environ['MONITOR_SERVICE_RESULT'] == 'success':
|
||||||
# exit with success status
|
# exit with success status
|
||||||
failures = sysalert.db.get_failures(service_name, db)
|
failures = sysalert.db.get_failures(service_name, db)
|
||||||
sysalert.db.register_success(service_name, db)
|
sysalert.db.clear_failure(service_name, db)
|
||||||
try:
|
try:
|
||||||
do_alert = config.getboolean(section_name, 'recovery_alert')
|
do_alert = config.getboolean(section_name, 'recovery_alert')
|
||||||
except ValueError:
|
except ValueError:
|
||||||
if config.get(section_name, 'recovery_alert') == 'if-alerted' and failures:
|
if config.get(section_name, 'recovery_alert') == 'if-alerted' and \
|
||||||
|
any([f['alert_method'] for f in failures]):
|
||||||
do_alert = True
|
do_alert = True
|
||||||
else:
|
else:
|
||||||
do_alert = False
|
do_alert = False
|
||||||
@@ -75,6 +76,31 @@ def register_exit(config, db):
|
|||||||
alert.failure(service_name, failures, alert_config)
|
alert.failure(service_name, failures, alert_config)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def cli_args():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Manage active sysalerts',
|
||||||
|
epilog='''Note that when called from systemd this argument parser
|
||||||
|
is not used. Use the provided sysalert-* services and see
|
||||||
|
documentation for details.''')
|
||||||
|
parser.add_argument('--service', '-s',
|
||||||
|
help='Only operate on specific service')
|
||||||
|
subparsers = parser.add_subparsers()
|
||||||
|
|
||||||
|
parser_list = subparsers.add_parser('list')
|
||||||
|
parser_list.set_defaults(cmd='list')
|
||||||
|
|
||||||
|
parser_clear = subparsers.add_parser('clear')
|
||||||
|
parser_clear.set_defaults(cmd='clear')
|
||||||
|
parser_clear.add_argument('--force', '-f',
|
||||||
|
action='store_true',
|
||||||
|
help='''Force clear for all services if service is
|
||||||
|
not specified''')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
if 'cmd' not in args:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
return args
|
||||||
|
|
||||||
def cli():
|
def cli():
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
@@ -85,9 +111,38 @@ def cli():
|
|||||||
if _test_env():
|
if _test_env():
|
||||||
# invoked by systemd
|
# invoked by systemd
|
||||||
ret = register_exit(config, db)
|
ret = register_exit(config, db)
|
||||||
|
else:
|
||||||
|
args = cli_args()
|
||||||
|
if args.service and not args.service.endswith('.service'):
|
||||||
|
args.service = f'{args.service}.service'
|
||||||
|
|
||||||
|
if args.cmd == 'list':
|
||||||
|
cli_list(args, db)
|
||||||
|
elif args.cmd == 'clear':
|
||||||
|
cli_clear(args, db)
|
||||||
|
|
||||||
sysalert.db.close(db)
|
sysalert.db.close(db)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def cli_list(args, db):
|
||||||
|
for service, failures in sysalert.db.get_services(db).items():
|
||||||
|
if not args.service or (args.service and args.service == service):
|
||||||
|
print(f'{service}: {failures} failures')
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def cli_clear(args, db):
|
||||||
|
if not args.service and not args.force:
|
||||||
|
print(f'No service specified, use "--force" to clear all services',
|
||||||
|
file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
if args.service:
|
||||||
|
sysalert.db.clear_failure(args.service, db)
|
||||||
|
print(f'Cleared failures for {args.service}')
|
||||||
|
else:
|
||||||
|
for service, failures in sysalert.db.get_services(db).items():
|
||||||
|
sysalert.db.clear_failure(service, db)
|
||||||
|
print(f'Cleared all failures')
|
||||||
|
return 0
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sys.exit(cli())
|
sys.exit(cli())
|
||||||
|
|||||||
Reference in New Issue
Block a user