3
# Copyright 2010 United States Government as represented by the
4
# Administrator of the National Aeronautics and Space Administration.
7
# Licensed under the Apache License, Version 2.0 (the "License");
8
# you may not use this file except in compliance with the License.
9
# You may obtain a copy of the License at
11
# http://www.apache.org/licenses/LICENSE-2.0
13
# Unless required by applicable law or agreed to in writing, software
14
# distributed under the License is distributed on an "AS IS" BASIS,
15
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
# See the License for the specific language governing permissions and
17
# limitations under the License.
20
Tools for working with logs generated by nova components
30
class Request(object):
40
def add_error_line(self, error_line):
41
self.time = " ".join(error_line.split(" ")[:3])
42
self.host = error_line.split(" ")[3]
43
self.logger = error_line.split("(")[1].split(" ")[0]
44
self.request_id = error_line.split("[")[1].split(" ")[0]
45
error_lines = error_line.split("#012")
46
self.message = self.clean_log_line(error_lines.pop(0))
47
self.trace = "\n".join([self.clean_trace(l) for l in error_lines])
49
def add_environment_line(self, env_line):
50
self.env = self.clean_env_line(env_line)
52
def clean_log_line(self, line):
53
"""Remove log format for time, level, etc: split after context"""
54
return line.split('] ')[-1]
56
def clean_env_line(self, line):
57
"""Also has an 'Environment: ' string in the message"""
58
return re.sub(r'^Environment: ', '', self.clean_log_line(line))
60
def clean_trace(self, line):
61
"""trace has a different format, so split on TRACE:"""
62
return line.split('TRACE: ')[-1]
65
return {'traceback': self.trace, 'message': self.message,
66
'host': self.host, 'env': self.env, 'logger': self.logger,
67
'request_id': self.request_id}
70
class LogReader(object):
71
def __init__(self, filename):
72
self.filename = filename
75
def process(self, spooldir):
76
with open(self.filename) as f:
79
parts = line.split(" ")
80
level = (len(parts) < 6) or parts[5]
82
self.handle_logged_error(line)
83
elif level == '[-]' and self.last_error:
84
# twisted stack trace line
85
clean_line = " ".join(line.split(" ")[6:])
86
self.last_error.trace = self.last_error.trace + clean_line
88
self.last_error = None
90
self.update_spool(spooldir)
92
def handle_logged_error(self, line):
93
request_id = re.search(r' \[([A-Z0-9\-/]+)', line)
95
raise Exception("Unable to parse request id from %s" % line)
96
request_id = request_id.group(1)
97
data = self._errors.get(request_id, Request())
98
if self.is_env_line(line):
99
data.add_environment_line(line)
100
elif self.is_error_line(line):
101
data.add_error_line(line)
103
# possibly error from twsited
104
data.add_error_line(line)
105
self.last_error = data
106
self._errors[request_id] = data
108
def is_env_line(self, line):
109
return re.search('Environment: ', line)
111
def is_error_line(self, line):
112
return re.search('raised', line)
114
def update_spool(self, directory):
115
processed_dir = "%s/processed" % directory
116
self._ensure_dir_exists(processed_dir)
117
for rid, value in self._errors.iteritems():
118
if not self.has_been_processed(processed_dir, rid):
119
with open("%s/%s" % (directory, rid), "w") as spool:
120
spool.write(json.dumps(value.to_dict()))
121
self.flush_old_processed_spool(processed_dir)
123
def _ensure_dir_exists(self, d):
132
def has_been_processed(self, processed_dir, rid):
135
os.stat("%s/%s" % (processed_dir, rid))
141
def flush_old_processed_spool(self, processed_dir):
142
keys = self._errors.keys()
143
procs = os.listdir(processed_dir)
146
# log has rotated and the old error won't be seen again
147
os.unlink("%s/%s" % (processed_dir, p))
149
if __name__ == '__main__':
150
filename = '/var/log/nova.log'
151
spooldir = '/var/spool/nova'
152
if len(sys.argv) > 1:
153
filename = sys.argv[1]
154
if len(sys.argv) > 2:
155
spooldir = sys.argv[2]
156
LogReader(filename).process(spooldir)