mirror of
https://github.com/YunoHost-Apps/seafile_ynh.git
synced 2024-09-03 20:26:01 +02:00
243 lines
7.9 KiB
Python
243 lines
7.9 KiB
Python
#
|
|
# middleware.py -- Middleware implementation for logging
|
|
#
|
|
# Copyright (c) 2008-2009 Christian Hammond
|
|
# Copyright (c) 2008-2009 David Trowbridge
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining
|
|
# a copy of this software and associated documentation files (the
|
|
# "Software"), to deal in the Software without restriction, including
|
|
# without limitation the rights to use, copy, modify, merge, publish,
|
|
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
# permit persons to whom the Software is furnished to do so, subject to
|
|
# the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included
|
|
# in all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
#
|
|
|
|
|
|
import logging
|
|
import sys
|
|
import time
|
|
import traceback
|
|
|
|
from django.conf import settings
|
|
from django.db import connection
|
|
from django.db.backends import util
|
|
|
|
from djblets.log import init_logging, init_profile_logger, log_timed
|
|
|
|
|
|
class CursorDebugWrapper(util.CursorDebugWrapper):
|
|
"""
|
|
Replacement for CursorDebugWrapper which stores a traceback in
|
|
`connection.queries`. This will dramatically increase the overhead of having
|
|
DEBUG=True, so use with caution.
|
|
"""
|
|
def execute(self, sql, params=()):
|
|
start = time.time()
|
|
try:
|
|
return self.cursor.execute(sql, params)
|
|
finally:
|
|
stop = time.time()
|
|
sql = self.db.ops.last_executed_query(self.cursor, sql, params)
|
|
self.db.queries.append({
|
|
'sql': sql,
|
|
'time': stop - start,
|
|
'stack': traceback.format_stack(),
|
|
})
|
|
util.CursorDebugWrapper = CursorDebugWrapper
|
|
|
|
|
|
def reformat_sql(sql):
|
|
sql = sql.replace('`,`', '`, `')
|
|
sql = sql.replace('SELECT ', 'SELECT\t')
|
|
sql = sql.replace('` FROM ', '`\nFROM\t')
|
|
sql = sql.replace(' WHERE ', '\nWHERE\t')
|
|
sql = sql.replace(' INNER JOIN ', '\nINNER JOIN\t')
|
|
sql = sql.replace(' LEFT OUTER JOIN ', '\nLEFT OUTER JOIN\t')
|
|
sql = sql.replace(' OUTER JOIN ', '\nOUTER JOIN\t')
|
|
sql = sql.replace(' ON ', '\n ON ')
|
|
sql = sql.replace(' ORDER BY ', '\nORDER BY\t')
|
|
return sql
|
|
|
|
|
|
class LoggingMiddleware(object):
|
|
"""
|
|
A piece of middleware that sets up logging.
|
|
|
|
This a few settings to configure.
|
|
|
|
LOGGING_ENABLED
|
|
---------------
|
|
|
|
Default: False
|
|
|
|
Sets whether or not logging is enabled.
|
|
|
|
|
|
LOGGING_DIRECTORY
|
|
-----------------
|
|
|
|
Default: None
|
|
|
|
Specifies the directory that log files should be stored in.
|
|
This directory must be writable by the process running Django.
|
|
|
|
|
|
LOGGING_NAME
|
|
------------
|
|
|
|
Default: None
|
|
|
|
The name of the log files, excluding the extension and path. This will
|
|
usually be the name of the website or web application. The file extension
|
|
will be automatically appended when the file is written.
|
|
|
|
|
|
LOGGING_ALLOW_PROFILING
|
|
-----------------------
|
|
|
|
Default: False
|
|
|
|
Specifies whether or not code profiling is allowed. If True, visiting
|
|
any page with a ``?profiling=1`` parameter in the URL will cause the
|
|
request to be profiled and stored in a ``.prof`` file using the defined
|
|
``LOGGING_DIRECTORY`` and ``LOGGING_NAME``.
|
|
|
|
|
|
LOGGING_LINE_FORMAT
|
|
-------------------
|
|
|
|
Default: "%(asctime)s - %(levelname)s - %(message)s"
|
|
|
|
The format for lines in the log file. See Python's logging documentation
|
|
for possible values in the format string.
|
|
|
|
|
|
LOGGING_PAGE_TIMES
|
|
------------------
|
|
|
|
Default: False
|
|
|
|
If enabled, page access times will be logged. Specifically, it will log
|
|
the initial request, the finished render and response, and the total
|
|
time it look.
|
|
|
|
The username and page URL will be included in the logs.
|
|
|
|
|
|
LOGGING_LEVEL
|
|
-------------
|
|
|
|
Default: "DEBUG"
|
|
|
|
The minimum level to log. Possible values are ``DEBUG``, ``INFO``,
|
|
``WARNING``, ``ERROR`` and ``CRITICAL``.
|
|
"""
|
|
|
|
def process_request(self, request):
|
|
"""
|
|
Processes an incoming request. This will set up logging.
|
|
"""
|
|
if getattr(settings, 'LOGGING_PAGE_TIMES', False):
|
|
request._page_timedloginfo = \
|
|
log_timed('Page request: HTTP %s %s (by %s)' %
|
|
(request.method, request.path, request.user))
|
|
|
|
if ('profiling' in request.GET and
|
|
getattr(settings, "LOGGING_ALLOW_PROFILING", False)):
|
|
settings.DEBUG = True
|
|
|
|
def process_view(self, request, callback, callback_args, callback_kwargs):
|
|
"""
|
|
Handler for processing a view. This will run the profiler on the view
|
|
if profiling is allowed in the settings and the user specified the
|
|
profiling parameter on the URL.
|
|
"""
|
|
init_logging()
|
|
|
|
if ('profiling' in request.GET and
|
|
getattr(settings, "LOGGING_ALLOW_PROFILING", False)):
|
|
import cProfile
|
|
self.profiler = cProfile.Profile()
|
|
args = (request,) + callback_args
|
|
settings.DEBUG = True
|
|
return self.profiler.runcall(callback, *args, **callback_kwargs)
|
|
|
|
def process_response(self, request, response):
|
|
"""
|
|
Handler for processing a response. Dumps the profiling information
|
|
to the profile log file.
|
|
"""
|
|
timedloginfo = getattr(request, '_page_timedloginfo', None)
|
|
|
|
if timedloginfo:
|
|
timedloginfo.done()
|
|
|
|
if ('profiling' in request.GET and
|
|
getattr(settings, "LOGGING_ALLOW_PROFILING", False)):
|
|
|
|
init_profile_logger()
|
|
|
|
from cStringIO import StringIO
|
|
self.profiler.create_stats()
|
|
|
|
# Capture the stats
|
|
out = StringIO()
|
|
old_stdout, sys.stdout = sys.stdout, out
|
|
self.profiler.print_stats(1)
|
|
sys.stdout = old_stdout
|
|
|
|
profile_log = logging.getLogger("profile")
|
|
profile_log.log(logging.INFO,
|
|
"Profiling results for %s (HTTP %s):",
|
|
request.path, request.method)
|
|
profile_log.log(logging.INFO, out.getvalue().strip())
|
|
|
|
profile_log.log(logging.INFO,
|
|
'%d database queries made\n',
|
|
len(connection.queries))
|
|
|
|
queries = {}
|
|
for query in connection.queries:
|
|
sql = reformat_sql(query['sql'])
|
|
stack = ''.join(query['stack'][:-1])
|
|
time = query['time']
|
|
if sql in queries:
|
|
queries[sql].append((time, stack))
|
|
else:
|
|
queries[sql] = [(time, stack)]
|
|
|
|
times = {}
|
|
for sql, entries in queries.iteritems():
|
|
time = sum((float(entry[0]) for entry in entries))
|
|
tracebacks = '\n\n'.join((entry[1] for entry in entries))
|
|
times[time] = \
|
|
'SQL Query profile (%d times, %.3fs average)\n%s\n\n%s\n\n' % \
|
|
(len(entries), time / len(entries), sql, tracebacks)
|
|
|
|
sorted_times = sorted(times.keys(), reverse=1)
|
|
for time in sorted_times:
|
|
profile_log.log(logging.INFO, times[time])
|
|
|
|
return response
|
|
|
|
def process_exception(self, request, exception):
|
|
"""Handle for exceptions on a page.
|
|
|
|
Logs the exception, along with the username and path where the
|
|
exception occurred.
|
|
"""
|
|
logging.error("Exception thrown for user %s at %s\n\n%s",
|
|
request.user, request.build_absolute_uri(),
|
|
exception, exc_info=1)
|