????
Current Path : /usr/share/web-monitoring-tool/ |
Current File : //usr/share/web-monitoring-tool/reporter.py |
#!/opt/cloudlinux/venv/bin/python3 -bb # coding=utf-8 # # Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2020 All Rights Reserved # # Licensed under CLOUD LINUX LICENSE AGREEMENT # http://cloudlinux.com/docs/LICENCE.TXT # import json import os from datetime import date, timedelta, datetime from email.header import Header from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from typing import Dict, Any, List, Tuple from clcommon import clemail from clcommon.cpapi import get_admin_email as system_admin_email from clcommon.mail_helper import MailHelper from clcommon.utils import run_command from prettytable import PrettyTable, ALL from sentry import init_wmt_sentry_client, setup_logger logger = setup_logger('email_reporter') WMT_DIR = '/usr/share/web-monitoring-tool/' WMT_API = os.path.join(WMT_DIR, 'wmtbin', 'wmt-api') class WMTTemplateError(clemail.jinja2.exceptions.TemplateError): pass def to_seconds(microseconds: int) -> str: secs = str(microseconds / 1000000.0) return secs[:4] def _get_from_email(): admin_email = system_admin_email() # system_admin_email may return multiple emails divided by commas, # example: 'admin1@email.com,admin2@email.com' # get the first one if multiple emails returned return admin_email.split(',')[0].strip() def _get_to_email(): response = run_command([WMT_API, '--email-get']) response = json.loads(response) if 'email' not in response or response['email'] == '': return system_admin_email() return response['email'] def _generate_summary_table(summary_data: Dict[str, Any]): """Generate summary report table""" table_columns = [('All', 'count_all'), ('Successful', 'count_successful'), ('Failed', 'count_failed'), ('Undone', 'count_undone'), ('Average time (sec)', 'average_time')] summary_data['average_time'] = to_seconds(summary_data['average_time']) return _generate_table_data(table_columns, summary_data, True) def _generate_error_table(error_reports: List[Dict[str, Any]]): """Generate error report table""" table_columns = [('URL', 'url'), ('Errors', 'count_errors'), ('Error codes', 'error_codes')] for i, report in enumerate(error_reports): error_reports[i]['error_codes'] = report['code'].split(',') return _generate_table_data(table_columns, error_reports) def _generate_duration_table(duration_reports: List[Dict[str, Any]]): """Generate duration report table""" table_columns = [('URL', 'url'), ('Average time (sec)', 'average_time')] for i, report in enumerate(duration_reports): duration_reports[i]['average_time'] = to_seconds(report['average_time']) return _generate_table_data(table_columns, duration_reports) def _generate_table_data(table_columns, table_data, one_row=False): def gen_table_body(): table_b = list() if one_row: table_line = [] for _, user_data_key in table_columns: cell = str(table_data.get(user_data_key, '---')) table_line.append(cell) table_b.append(table_line) else: for data in table_data: table_line = [] for _, user_data_key in table_columns: cell = data.get(user_data_key, '---') if isinstance(cell, list): cell = ', '.join(map(str, cell)) else: cell = str(cell) table_line.append(cell) table_b.append(table_line) return table_b columns = [header for header, _ in table_columns] table = PrettyTable(columns) table.horizontal_char = '=' table.junction_char = "=" list(map(table.add_row, gen_table_body())) s_table = table.get_string() s_html_table = table.get_html_string(format=True, border=True, hrules=ALL, vrules=ALL) return s_table, s_html_table def _generate_template_data(report): summary_text_table, summary_html_table = _generate_summary_table(report['summary_report']) error_text_table, error_html_table = _generate_error_table(report['error_report']) duration_text_table, duration_html_table = _generate_duration_table(report['duration_report']) template_data = { 'SUMMARY_REPORT': summary_text_table, 'SUMMARY_HTML_REPORT': summary_html_table, 'ERROR_REPORT': error_text_table, 'ERROR_HTML_REPORT': error_html_table, 'DURATION_REPORT': duration_text_table, 'DURATION_HTML_REPORT': duration_html_table, 'FROMMAIL': _get_from_email(), 'LOCALE': 'en_US', 'TOMAIL': _get_to_email(), 'DATE': (date.today() - timedelta(days=1)).strftime("%b %d %Y"), 'TONAME': 'Administrator'} return template_data def generate_msg_body(templ_data): text_templ_path = os.path.join(WMT_DIR, 'templates', 'wmt_notify.txt') if not os.path.exists(WMT_DIR) or not os.path.exists(text_templ_path): logger.info( "Unable to find templates: file '%s' does not exist. ", text_templ_path) html_templ_path = os.path.join(WMT_DIR, 'templates', 'wmt_notify.html') if not os.path.exists(html_templ_path): logger.info( "Unable to find optional HTML message template '%s'. ", html_templ_path) html_templ_path = None html_body = None try: yesterday = datetime.now() - timedelta(1) yesterday = datetime.strftime(yesterday, '%Y-%m-%d') subject, text_body = clemail.ClEmail.generate_mail_jinja2( text_templ_path, templ_data=templ_data, subject=f'WMT report - {yesterday}') if html_templ_path: _, html_body = clemail.ClEmail.generate_mail_jinja2( html_templ_path, templ_data=templ_data, subject=f'WMT report - {yesterday}') except (clemail.jinja2.exceptions.TemplateError, IOError) as e: raise WMTTemplateError( 'Can not generate message; no templates in "{}". ' 'Jinja2: {}'.format( WMT_DIR, str(e))) return subject, text_body, html_body def generate_msg(templ_data): subject, text_body, html_body = generate_msg_body(templ_data) text_body = text_body \ .encode('utf-8', 'xmlcharrefreplace') \ .decode('utf-8') html_body = html_body \ .encode('utf-8', 'xmlcharrefreplace') \ .decode('utf-8') # Attach parts into message container. # According to RFC 2046, the last part of a multipart message, # in this case # the HTML message, is best and preferred. msg = MIMEMultipart('alternative') msg.attach(MIMEText(text_body, 'plain', 'utf-8')) msg.attach(MIMEText(html_body, 'html', 'utf-8')) # configure message headers msg['Subject'] = Header(subject, 'utf-8').encode() msg['From'] = templ_data.get('FROMMAIL') msg['To'] = templ_data['TOMAIL'] return msg def patched_run_command(cmd_list: List) -> Tuple[int, str, str]: """ use logger to log error and additional info if command executed unsuccessfully, otherwise works as run_command(cmd_list) """ ret_code, out, err = run_command(cmd_list, return_full_output=True) if ret_code != 0: cmd = ' '.join(cmd_list) msg = f'Error during command: {cmd}' logger.error(msg, extra={'output': out}) return ret_code, out, err def main(): init_wmt_sentry_client() try: patched_run_command([WMT_API, '--report-generate']) ret_code, response, _ = patched_run_command([WMT_API, '--report-get']) if ret_code != 0: logger.info('Can\'t create email: report is bad or corrupted\nExiting') return mailhelper = MailHelper() _response = response.encode('us-ascii', 'xmlcharrefreplace').decode('us-ascii') response = json.loads(_response) if 'report' not in response: # check that we have correct response from wmt-api --report-get # that contains 'report' field raise Exception('Key "report" is not found in JSON report') template_data = _generate_template_data(response['report']) msg = generate_msg(template_data) to_addrs = [i.strip() for i in msg['To'].split(',')] mailhelper.sendmail(msg['From'], to_addrs, msg) except WMTTemplateError as e: logger.warning(e) except Exception as e: logger.exception(e) if __name__ == "__main__": main()