2019-10-21 22:52:14 +00:00
|
|
|
import logging
|
2024-04-19 08:36:40 +00:00
|
|
|
import re
|
2019-10-21 22:52:14 +00:00
|
|
|
|
|
|
|
import requests
|
|
|
|
|
2024-03-10 23:18:49 +00:00
|
|
|
import borgmatic.hooks.logs
|
2019-11-12 23:31:07 +00:00
|
|
|
from borgmatic.hooks import monitor
|
|
|
|
|
2019-10-21 22:52:14 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2019-11-12 23:31:07 +00:00
|
|
|
MONITOR_STATE_TO_HEALTHCHECKS = {
|
|
|
|
monitor.State.START: 'start',
|
|
|
|
monitor.State.FINISH: None, # Healthchecks doesn't append to the URL for the finished state.
|
|
|
|
monitor.State.FAIL: 'fail',
|
2023-03-05 13:57:32 +00:00
|
|
|
monitor.State.LOG: 'log',
|
2019-11-12 23:31:07 +00:00
|
|
|
}
|
|
|
|
|
2024-03-10 23:18:49 +00:00
|
|
|
DEFAULT_PING_BODY_LIMIT_BYTES = 1500
|
|
|
|
HANDLER_IDENTIFIER = 'healthchecks'
|
2019-11-18 00:54:27 +00:00
|
|
|
|
2019-10-21 22:52:14 +00:00
|
|
|
|
2023-07-10 00:40:02 +00:00
|
|
|
def initialize_monitor(hook_config, config, config_filename, monitoring_log_level, dry_run):
|
2020-06-02 21:33:41 +00:00
|
|
|
'''
|
2022-05-24 21:44:33 +00:00
|
|
|
Add a handler to the root logger that stores in memory the most recent logs emitted. That way,
|
|
|
|
we can send them all to Healthchecks upon a finish or failure state. But skip this if the
|
|
|
|
"send_logs" option is false.
|
2020-06-02 21:33:41 +00:00
|
|
|
'''
|
2022-05-24 21:44:33 +00:00
|
|
|
if hook_config.get('send_logs') is False:
|
|
|
|
return
|
|
|
|
|
2022-05-24 19:23:38 +00:00
|
|
|
ping_body_limit = max(
|
|
|
|
hook_config.get('ping_body_limit', DEFAULT_PING_BODY_LIMIT_BYTES)
|
2024-03-10 23:18:49 +00:00
|
|
|
- len(borgmatic.hooks.logs.PAYLOAD_TRUNCATION_INDICATOR),
|
2022-05-24 19:23:38 +00:00
|
|
|
0,
|
|
|
|
)
|
|
|
|
|
2024-03-10 23:18:49 +00:00
|
|
|
borgmatic.hooks.logs.add_handler(
|
|
|
|
borgmatic.hooks.logs.Forgetful_buffering_handler(
|
|
|
|
HANDLER_IDENTIFIER, ping_body_limit, monitoring_log_level
|
|
|
|
)
|
2020-06-02 21:33:41 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-07-09 06:14:30 +00:00
|
|
|
def ping_monitor(hook_config, config, config_filename, state, monitoring_log_level, dry_run):
|
2019-10-21 22:52:14 +00:00
|
|
|
'''
|
2022-05-24 03:02:10 +00:00
|
|
|
Ping the configured Healthchecks URL or UUID, modified with the monitor.State. Use the given
|
2020-01-22 23:10:47 +00:00
|
|
|
configuration filename in any log entries, and log to Healthchecks with the giving log level.
|
|
|
|
If this is a dry run, then don't actually ping anything.
|
2019-10-21 22:52:14 +00:00
|
|
|
'''
|
|
|
|
ping_url = (
|
2022-05-24 03:02:10 +00:00
|
|
|
hook_config['ping_url']
|
|
|
|
if hook_config['ping_url'].startswith('http')
|
2023-03-24 06:11:14 +00:00
|
|
|
else f"https://hc-ping.com/{hook_config['ping_url']}"
|
2019-10-21 22:52:14 +00:00
|
|
|
)
|
|
|
|
dry_run_label = ' (dry run; not actually pinging)' if dry_run else ''
|
|
|
|
|
2022-05-24 21:09:42 +00:00
|
|
|
if 'states' in hook_config and state.name.lower() not in hook_config['states']:
|
2022-05-24 20:59:28 +00:00
|
|
|
logger.info(
|
2022-05-24 21:09:42 +00:00
|
|
|
f'{config_filename}: Skipping Healthchecks {state.name.lower()} ping due to configured states'
|
2022-05-24 20:59:28 +00:00
|
|
|
)
|
|
|
|
return
|
|
|
|
|
2024-04-22 18:44:20 +00:00
|
|
|
ping_url_is_uuid = re.search(r'\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$', ping_url)
|
2024-04-19 08:36:40 +00:00
|
|
|
|
2019-11-12 23:31:07 +00:00
|
|
|
healthchecks_state = MONITOR_STATE_TO_HEALTHCHECKS.get(state)
|
|
|
|
if healthchecks_state:
|
2023-03-24 06:11:14 +00:00
|
|
|
ping_url = f'{ping_url}/{healthchecks_state}'
|
2019-10-21 22:52:14 +00:00
|
|
|
|
2024-04-22 18:45:36 +00:00
|
|
|
if hook_config.get('create_slug'):
|
|
|
|
if ping_url_is_uuid:
|
|
|
|
logger.warning(
|
|
|
|
f'{config_filename}: Healthchecks UUIDs do not support auto provisionning; ignoring'
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
ping_url = f'{ping_url}?create=1'
|
2024-04-19 08:36:40 +00:00
|
|
|
|
2023-03-24 06:11:14 +00:00
|
|
|
logger.info(f'{config_filename}: Pinging Healthchecks {state.name.lower()}{dry_run_label}')
|
|
|
|
logger.debug(f'{config_filename}: Using Healthchecks ping URL {ping_url}')
|
2019-10-21 22:52:14 +00:00
|
|
|
|
2023-03-05 13:57:32 +00:00
|
|
|
if state in (monitor.State.FINISH, monitor.State.FAIL, monitor.State.LOG):
|
2024-03-10 23:18:49 +00:00
|
|
|
payload = borgmatic.hooks.logs.format_buffered_logs_for_payload(HANDLER_IDENTIFIER)
|
2020-06-02 21:33:41 +00:00
|
|
|
else:
|
|
|
|
payload = ''
|
2019-11-18 00:54:27 +00:00
|
|
|
|
2019-11-07 18:08:44 +00:00
|
|
|
if not dry_run:
|
|
|
|
logging.getLogger('urllib3').setLevel(logging.ERROR)
|
2022-05-24 22:50:04 +00:00
|
|
|
try:
|
2022-07-23 20:07:06 +00:00
|
|
|
response = requests.post(
|
|
|
|
ping_url, data=payload.encode('utf-8'), verify=hook_config.get('verify_tls', True)
|
|
|
|
)
|
2022-06-30 04:19:40 +00:00
|
|
|
if not response.ok:
|
|
|
|
response.raise_for_status()
|
2022-05-24 22:50:04 +00:00
|
|
|
except requests.exceptions.RequestException as error:
|
|
|
|
logger.warning(f'{config_filename}: Healthchecks error: {error}')
|
2020-06-23 18:01:03 +00:00
|
|
|
|
|
|
|
|
2023-07-10 00:40:02 +00:00
|
|
|
def destroy_monitor(hook_config, config, config_filename, monitoring_log_level, dry_run):
|
2020-06-23 18:01:03 +00:00
|
|
|
'''
|
|
|
|
Remove the monitor handler that was added to the root logger. This prevents the handler from
|
|
|
|
getting reused by other instances of this monitor.
|
|
|
|
'''
|
2024-03-10 23:18:49 +00:00
|
|
|
borgmatic.hooks.logs.remove_handler(HANDLER_IDENTIFIER)
|