~ubuntu-branches/ubuntu/quantal/nova/quantal-proposed

« back to all changes in this revision

Viewing changes to bin/nova-logspool

  • Committer: Bazaar Package Importer
  • Author(s): Chuck Short
  • Date: 2011-01-21 11:48:06 UTC
  • mto: This revision was merged to the branch mainline in revision 9.
  • Revision ID: james.westby@ubuntu.com-20110121114806-v8fvnnl6az4m4ohv
Tags: upstream-2011.1~bzr597
ImportĀ upstreamĀ versionĀ 2011.1~bzr597

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
 
 
3
# Copyright 2010 United States Government as represented by the
 
4
# Administrator of the National Aeronautics and Space Administration.
 
5
# All Rights Reserved.
 
6
#
 
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
 
10
#
 
11
#        http://www.apache.org/licenses/LICENSE-2.0
 
12
#
 
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.
 
18
 
 
19
"""
 
20
Tools for working with logs generated by nova components
 
21
"""
 
22
 
 
23
 
 
24
import json
 
25
import os
 
26
import re
 
27
import sys
 
28
 
 
29
 
 
30
class Request(object):
 
31
    def __init__(self):
 
32
        self.time = ""
 
33
        self.host = ""
 
34
        self.logger = ""
 
35
        self.message = ""
 
36
        self.trace = ""
 
37
        self.env = ""
 
38
        self.request_id = ""
 
39
 
 
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])
 
48
 
 
49
    def add_environment_line(self, env_line):
 
50
        self.env = self.clean_env_line(env_line)
 
51
 
 
52
    def clean_log_line(self, line):
 
53
        """Remove log format for time, level, etc: split after context"""
 
54
        return line.split('] ')[-1]
 
55
 
 
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))
 
59
 
 
60
    def clean_trace(self, line):
 
61
        """trace has a different format, so split on TRACE:"""
 
62
        return line.split('TRACE: ')[-1]
 
63
 
 
64
    def to_dict(self):
 
65
        return {'traceback': self.trace, 'message': self.message,
 
66
                'host': self.host, 'env': self.env, 'logger': self.logger,
 
67
                'request_id': self.request_id}
 
68
 
 
69
 
 
70
class LogReader(object):
 
71
    def __init__(self, filename):
 
72
        self.filename = filename
 
73
        self._errors = {}
 
74
 
 
75
    def process(self, spooldir):
 
76
        with open(self.filename) as f:
 
77
            line = f.readline()
 
78
            while len(line) > 0:
 
79
                parts = line.split(" ")
 
80
                level = (len(parts) < 6) or parts[5]
 
81
                if level == 'ERROR':
 
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
 
87
                else:
 
88
                    self.last_error = None
 
89
                line = f.readline()
 
90
        self.update_spool(spooldir)
 
91
 
 
92
    def handle_logged_error(self, line):
 
93
        request_id = re.search(r' \[([A-Z0-9\-/]+)', line)
 
94
        if not request_id:
 
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)
 
102
        else:
 
103
            # possibly error from twsited
 
104
            data.add_error_line(line)
 
105
        self.last_error = data
 
106
        self._errors[request_id] = data
 
107
 
 
108
    def is_env_line(self, line):
 
109
        return re.search('Environment: ', line)
 
110
 
 
111
    def is_error_line(self, line):
 
112
        return re.search('raised', line)
 
113
 
 
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)
 
122
 
 
123
    def _ensure_dir_exists(self, d):
 
124
        mkdir = False
 
125
        try:
 
126
            os.stat(d)
 
127
        except:
 
128
            mkdir = True
 
129
        if mkdir:
 
130
            os.mkdir(d)
 
131
 
 
132
    def has_been_processed(self, processed_dir, rid):
 
133
        rv = False
 
134
        try:
 
135
            os.stat("%s/%s" % (processed_dir, rid))
 
136
            rv = True
 
137
        except:
 
138
            pass
 
139
        return rv
 
140
 
 
141
    def flush_old_processed_spool(self, processed_dir):
 
142
        keys = self._errors.keys()
 
143
        procs = os.listdir(processed_dir)
 
144
        for p in procs:
 
145
            if p not in keys:
 
146
                # log has rotated and the old error won't be seen again
 
147
                os.unlink("%s/%s" % (processed_dir, p))
 
148
 
 
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)