source: TI07-MOLES/trunk/PythonCode/wsgi/ndgLog.py @ 2528

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI07-MOLES/trunk/PythonCode/wsgi/ndgLog.py@2528
Revision 2528, 6.8 KB checked in by lawrence, 12 years ago (diff)

Improvements associated with ticket:733

Line 
1
2# NDG Logging middleware, heavily based on code by L. C. Rees published with a BSD license.
3#
4# File rotation constants
5# Following should be read from a config file at some point
6#
7BACKUPS = 1
8INTERVAL = 'h'
9# Default logger name (should be changed)
10LOGNAME = 'wsgilog.log'
11# Default 'environ' entries
12CATCHID = 'wsgilog.catch'
13LOGGERID = 'wsgilog.logger'
14# Current proposed 'environ' key signalling no middleware exception handling
15THROWERR = 'x-wsgiorg.throw_errors'
16# HTTP error messages
17HTTPMSG = '500 Internal error'
18ERRORMSG = 'Server got itself in trouble'
19# Default log formats
20DATEFORMAT = '%a, %d %b %Y %H:%M:%S'
21LOGFORMAT = '%(name)s: %(asctime)s %(levelname)-4s %(message)s'
22#
23import sys
24import logging
25from cgitb import html
26from logging.handlers import HTTPHandler, SysLogHandler
27from logging.handlers import TimedRotatingFileHandler, SMTPHandler
28#
29
30def logFactory(global_config,**local_conf):
31   
32    ''' This factory is intended to be used by paste-deploy to return
33    the ndgLog wsgi application, configured to use the configDir
34    to find local configuration information '''
35   
36    if 'logFile' in local_conf.keys():
37        logFile=local_conf['logFile']
38    else:
39        try:
40            logFile=global_config['logFile']
41        except:
42            logFile='ndgWSGI.log'
43
44    def filter(app):
45        return ndgLog(app,tofile=logFile,tostream=True)
46   
47    return filter
48         
49def _errapp(environ, start_response):
50    '''Default error handling WSGI application.'''
51    start_response(HTTPMSG, [('Content-type', 'text/plain')],
52         exc_info=sys.exc_info())
53#        sys.exc_info())
54    return [ERRORMSG]   
55
56class LogIO(object):
57
58    '''File-like object for sending stdout output to a logger.'''   
59
60    def __init__(self, logger, level=logging.DEBUG):
61        # Set logger level
62        if level == logging.DEBUG:
63            self.logger = logger.debug
64        elif level == logging.CRITICAL:
65            self.logger = logger.critical
66        elif level == logging.ERROR:
67            self.logger = logger.warning
68        elif level == logging.WARNING:
69            self.logger = logger.warning
70        elif level == logging.INFO:
71            self.logger = logger.info       
72
73    def write(self, info):
74        '''Writes non-whitespace strings to logger.'''
75        if info.lstrip().rstrip() != '': self.logger(info)
76
77
78class ndgLog(object):
79
80    '''Class for WSGI logging and event recording middleware.'''   
81
82    def __init__(self, application, **kw):
83        self.application = application
84        # Error handling WSGI app
85        self._errapp = kw.get('errapp', _errapp)
86        # Flag controlling logging
87        self.log = kw.get('log', True)
88        # Log if set
89        if self.log:
90            # Log error message
91            self.message = kw.get('logmessage', ERRORMSG)
92            # Individual logger for WSGI app with custom name 'logname'
93            self.logger = logging.getLogger(kw.get('logname', LOGNAME))
94            # Set logger level
95            self.logger.setLevel(kw.get('loglevel', logging.DEBUG))
96            # Log formatter
97            format = logging.Formatter(
98                # Log entry format
99                kw.get('logformat', LOGFORMAT),
100                # Date format
101                kw.get('datefmt', DATEFORMAT))
102            # Coroutine for setting individual log handlers
103            def setlog(logger):
104                logger.setFormatter(format)
105                self.logger.addHandler(logger)
106            # Log to STDOUT
107            if 'tostream' in kw:
108                setlog(logging.StreamHandler())
109            # Log to a rotating file that with periodic backup deletions
110            if 'tofile' in kw:
111                setlog(TimedRotatingFileHandler(
112                    # Log file path
113                    kw.get('file', LOGNAME),
114                    # Interval to backup log file
115                    kw.get('interval', INTERVAL),
116                    # Number of backups to keep
117                    kw.get('backups', BACKUPS)))
118            # Send log entries to an email address
119            if 'toemail' in kw:
120                setlog(SMTPHandler(
121                    # Mail server
122                    kw.get('mailserver'),
123                    # From email address
124                    kw.get('frommail'),
125                    # To email address
126                    kw.get('toemail'),
127                    # Email subject
128                    kw.get('mailsubject')))
129            # Send log entries to a web server
130            if 'tohttp' in kw:
131                setlog(HTTPHandler(
132                    # Web server host
133                    kw.get('httphost'),
134                    # Web URL
135                    kw.get('httpurl'),
136                    # HTTP method
137                    kw.get('httpmethod', 'GET')))
138            # Log to syslog
139            if 'tosyslog' in kw:
140                setlog(SysLogHandler(
141                    # syslog host
142                    kw.get('syshost', ('localhost', 514)),
143                    # syslog user
144                    kw.get('facility', 'LOG_USER')))
145            assert self.logger.handlers, 'At least one logging handler must be configured'   
146            # Redirect STDOUT to the logger
147            if 'toprint' in kw:
148                sys.stdout = LogIO(self.logger,
149                    # Sets log level STDOUT is displayed under
150                    kw.get('prnlevel', logging.DEBUG))
151        # Flag for sending HTML-formatted exception tracebacks to the browser
152        self.tohtml = kw.get('tohtml', False)
153        # Write HTML-formatted exception tracebacks to a file if provided
154        self.htmlfile = kw.get('htmlfile')
155               
156    def __call__(self, environ, start_response):
157        # Make logger available to other WSGI apps/middlware
158        if self.log: environ[LOGGERID] = self.logger
159        # Make catch method available to other WSGI apps/middleware
160        environ[CATCHID] = self.catch
161        # Let exceptions "bubble up" to WSGI server/gateway
162        if THROWERR in environ:
163            return self.application(environ, start_response)
164        # Try application
165        try:
166            return self.application(environ, start_response)
167        # Log and/or report any errors
168        except:
169            return self.catch(environ, start_response)
170
171    def catch(self, environ, start_response):
172        '''Exception catcher.'''
173        # Log exception
174        if self.log: self.logger.exception(self.message)
175        # Write HTML-formatted exception tracebacks to a file
176        if self.htmlfile is not None:
177            open(self.htmlfile, 'wb').write(html(sys.exc_info()))
178        # Send HTML-formatted exception tracebacks to the browser
179        if self.tohtml:
180            start_response(HTTPMSG, [('Content-type', 'text/html')])
181            return [html(sys.exc_info())]
182        # Return error handler
183        return self._errapp(environ, start_response)
Note: See TracBrowser for help on using the repository browser.