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(' 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(' empty<br>')
99
100 else:
101 html.write(' {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 ' ', ' ').replace(
144 '<h2>', '').replace(
145 '</h2>', '\n')
146
147 print(html_to_console(document.html.getvalue()))
148
149if __name__ == '__main__':
150 main()