1#!/usr/bin/env python3
  2
  3"""Implementation of the daily digest Logs widget."""
  4
  5
  6
  7
  8import os
  9import re
 10import logging
 11from collections import defaultdict
 12from collections import OrderedDict
 13
 14from chart.common.path import Path
 15from chart.project import settings
 16from chart.reports.widget import Widget
 17
 18logger = logging.getLogger()
 19
 20# scan CHART python log files
 21# (allowing optional ms lets us handle supervisor.log too)
 22# 2013-09-26 00:11:12 jobs DEBUG
 23logger_finder = re.compile(r'[0-9-]+ [0-9:]+ [^ ]+ ([A-Z]+)')
 24
 25# old scheme, no module name
 26# logger_finder = re.compile(r'^.{20}([A-Z]+)')
 27
 28sleep_finder = re.compile(r'.*Sleeping for (?P<sleep>\d+)s')
 29
 30supervisord_finder = re.compile(r'^[^ ]+ [^ ]+ ([A-Z]+)')
 31
 32# scan nginx log files
 33#nginx_finder = re.compile(r'^.{20}\[([a-z]+)')
 34
 35
 36class Logs(Widget):
 37    """Write a section giving log file stats to the daily digest report."""
 38
 39    name = 'digest-logs'
 40
 41    document_options = OrderedDict([
 42            ('sensing-start', {'type': 'datetime'}),
 43            ('sensing-stop', {'type': 'datetime'})])
 44
 45    def html(self, document):
 46        dc = document.config
 47        html = document.html
 48
 49        logs = (('scheduler.log', logger_finder),
 50                ('worker.log', logger_finder),
 51                ('reporter.log', logger_finder),
 52                ('publisher.log', logger_finder),
 53                ('startup_monitor.log', logger_finder),
 54                ('oracle_monitor.log', logger_finder),
 55                ('event_notifications.log', logger_finder),
 56                ('supervisord.log', supervisord_finder),)
 57
 58        if settings.ROTATING_LOG_FILE is None:
 59            basedir = Path('~').expand()
 60
 61        else:
 62            basedir = settings.ROTATING_LOG_FILE.parent
 63
 64        logger.info('log base dir {p} settings {s} env {e}'.format(
 65            p=basedir,
 66            s=settings.ROTATING_LOG_FILE,
 67            e=os.getenv('CHART_ROTATING_LOG_FILE')))
 68
 69        for log in logs:
 70            sleeps = 0
 71            msg_map = defaultdict(int)
 72            # look for a rotating log file first, then a single log file
 73            filename = basedir.child(log[0] + '.' + dc['sensing-start'].strftime('%Y-%m-%d'))
 74
 75            html.write('{path}:<br>'.format(path=filename))
 76            if not filename.exists():
 77                html.write('&nbsp; no log file<br><br>')
 78                continue
 79
 80            for line in filename.open('r'):
 81                match = log[1].match(line)
 82                if match is not None:
 83                    msg_map[match.group(1)] += 1
 84
 85                if sleep_finder.match(line) is not None:
 86                    sleeps += 1
 87
 88            if 'debug' in msg_map:
 89                del msg_map['debug']
 90
 91            if 'DEBUG' in msg_map:
 92                del msg_map['DEBUG']
 93
 94            if sleeps > 0:
 95                msg_map['sleeps'] = sleeps
 96
 97            if len(msg_map) == 0:
 98                html.write('&nbsp; empty<br>')
 99
100            else:
101                html.write('&nbsp; {entries}<br>'.format(
102                        entries=' '.join(str(v) + ' ' +
103                                         k.lower() for k, v in msg_map.items())))
104
105            html.write('<br>')
106
107
108def main():
109    """Command line entry point."""
110    import io
111    from chart.common.args import ArgumentParser
112    parser = ArgumentParser()
113
114    parser.add_argument('--start',
115                        type=ArgumentParser.start_time,
116                        metavar='TIME',
117                        help='Begin search at TIME.')
118    parser.add_argument('--stop',
119                        type=ArgumentParser.stop_time,
120                        metavar='TIME',
121                        help='End search at TIME.')
122    args = parser.parse_args()
123
124    widget = Logs()
125
126    class Document:
127        """Fake document object"""
128
129        def __init__(self):
130            self.html = io.StringIO()
131            self.config = None
132
133    document = Document()
134    document.config = {'sensing-start': args.start,
135                       'sensing-stop': args.stop}
136
137    widget.html(document)
138
139    def html_to_console(html):
140        """Convert HTML to readable plain text."""
141        return html.replace(
142            '<br>', '\n').replace(
143            '&nbsp;', ' ').replace(
144                '<h2>', '').replace(
145                '</h2>', '\n')
146
147    print(html_to_console(document.html.getvalue()))
148
149if __name__ == '__main__':
150    main()