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

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

Oops, left this out as well ...

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    return [ERRORMSG]   
54
55class LogIO(object):
56
57    '''File-like object for sending stdout output to a logger.'''   
58
59    def __init__(self, logger, level=logging.DEBUG):
60        # Set logger level
61        if level == logging.DEBUG:
62            self.logger = logger.debug
63        elif level == logging.CRITICAL:
64            self.logger = logger.critical
65        elif level == logging.ERROR:
66            self.logger = logger.warning
67        elif level == logging.WARNING:
68            self.logger = logger.warning
69        elif level == logging.INFO:
70            self.logger = logger.info       
71
72    def write(self, info):
73        '''Writes non-whitespace strings to logger.'''
74        if info.lstrip().rstrip() != '': self.logger(info)
75
76
77class ndgLog(object):
78
79    '''Class for WSGI logging and event recording middleware.'''   
80
81    def __init__(self, application, **kw):
82        self.application = application
83        # Error handling WSGI app
84        self._errapp = kw.get('errapp', _errapp)
85        # Flag controlling logging
86        self.log = kw.get('log', True)
87        # Log if set
88        if self.log:
89            # Log error message
90            self.message = kw.get('logmessage', ERRORMSG)
91            # Individual logger for WSGI app with custom name 'logname'
92            self.logger = logging.getLogger(kw.get('logname', LOGNAME))
93            # Set logger level
94            self.logger.setLevel(kw.get('loglevel', logging.DEBUG))
95            # Log formatter
96            format = logging.Formatter(
97                # Log entry format
98                kw.get('logformat', LOGFORMAT),
99                # Date format
100                kw.get('datefmt', DATEFORMAT))
101            # Coroutine for setting individual log handlers
102            def setlog(logger):
103                logger.setFormatter(format)
104                self.logger.addHandler(logger)
105            # Log to STDOUT
106            if 'tostream' in kw:
107                setlog(logging.StreamHandler())
108            # Log to a rotating file that with periodic backup deletions
109            if 'tofile' in kw:
110                setlog(TimedRotatingFileHandler(
111                    # Log file path
112                    kw.get('file', LOGNAME),
113                    # Interval to backup log file
114                    kw.get('interval', INTERVAL),
115                    # Number of backups to keep
116                    kw.get('backups', BACKUPS)))
117            # Send log entries to an email address
118            if 'toemail' in kw:
119                setlog(SMTPHandler(
120                    # Mail server
121                    kw.get('mailserver'),
122                    # From email address
123                    kw.get('frommail'),
124                    # To email address
125                    kw.get('toemail'),
126                    # Email subject
127                    kw.get('mailsubject')))
128            # Send log entries to a web server
129            if 'tohttp' in kw:
130                setlog(HTTPHandler(
131                    # Web server host
132                    kw.get('httphost'),
133                    # Web URL
134                    kw.get('httpurl'),
135                    # HTTP method
136                    kw.get('httpmethod', 'GET')))
137            # Log to syslog
138            if 'tosyslog' in kw:
139                setlog(SysLogHandler(
140                    # syslog host
141                    kw.get('syshost', ('localhost', 514)),
142                    # syslog user
143                    kw.get('facility', 'LOG_USER')))
144            assert self.logger.handlers, 'At least one logging handler must be configured'   
145            # Redirect STDOUT to the logger
146            if 'toprint' in kw:
147                sys.stdout = LogIO(self.logger,
148                    # Sets log level STDOUT is displayed under
149                    kw.get('prnlevel', logging.DEBUG))
150        # Flag for sending HTML-formatted exception tracebacks to the browser
151        self.tohtml = kw.get('tohtml', False)
152        # Write HTML-formatted exception tracebacks to a file if provided
153        self.htmlfile = kw.get('htmlfile')
154               
155    def __call__(self, environ, start_response):
156        # Make logger available to other WSGI apps/middlware
157        if self.log: environ[LOGGERID] = self.logger
158        # Make catch method available to other WSGI apps/middleware
159        environ[CATCHID] = self.catch
160        # Let exceptions "bubble up" to WSGI server/gateway
161        if THROWERR in environ:
162            return self.application(environ, start_response)
163        # Try application
164        try:
165            return self.application(environ, start_response)
166        # Log and/or report any errors
167        except:
168            return self.catch(environ, start_response)
169
170    def catch(self, environ, start_response):
171        '''Exception catcher.'''
172        # Log exception
173        if self.log: self.logger.exception(self.message)
174        # Write HTML-formatted exception tracebacks to a file
175        if self.htmlfile is not None:
176            open(self.htmlfile, 'wb').write(html(sys.exc_info()))
177        # Send HTML-formatted exception tracebacks to the browser
178        if self.tohtml:
179            start_response(HTTPMSG, [('Content-type', 'text/html')])
180            return [html(sys.exc_info())]
181        # Return error handler
182        return self._errapp(environ, start_response)
Note: See TracBrowser for help on using the repository browser.