Skip to content

Instantly share code, notes, and snippets.

@yumike
Last active August 23, 2018 14:29
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yumike/7ed521eccfde363f300d6c6b52159d5b to your computer and use it in GitHub Desktop.
Save yumike/7ed521eccfde363f300d6c6b52159d5b to your computer and use it in GitHub Desktop.
cian-slowlog
# coding: utf-8
import threading
import time
from collections import deque
from datetime import datetime
from functools import wraps
from django.utils.encoding import smart_text
class Slowlog(object):
DEFAULT_SETTINGS = {
'session_threshold': 500, # in ms,
}
def __init__(self, logger, settings=None):
self._local = threading.local()
self._logger = logger
self._settings = self.DEFAULT_SETTINGS.copy()
self._settings.update(settings or {})
def start_session(self, request, **kwargs):
session = {
'date': datetime.utcnow(),
'operations': [],
'method': request.method,
'full_url': request.build_absolute_uri(),
'host': request.get_host(),
'path': request.get_full_path(),
'referrer': request.META.get('HTTP_REFERER'),
'user_agent': request.META.get('HTTP_USER_AGENT'),
'headers': self._get_headers_as_key_values(request),
}
session.update(kwargs)
self._local.session = session
self._local.session_start_time = time.time()
self._local.operations = deque()
def finish_session(self, response=None):
session = getattr(self._local, 'session', None)
if session is None:
self._local.operations = deque()
return
session['duration'] = int(1000 * (time.time() - self._local.session_start_time))
if response is not None:
session['response'] = response.status_code
if self._should_profile(session):
self._logger.record(session)
self._local.session = None
self._local.operations = deque()
return session
def operation(self, args=None, kwargs=None, arguments_processor=None, **operation_kwargs):
return _OperationContextDecorator(
local=self._local,
args=args,
kwargs=kwargs,
arguments_processor=arguments_processor,
**operation_kwargs
)
def _should_profile(self, session):
return session['duration'] >= self._settings['session_threshold']
def _get_headers_as_key_values(self, request):
results = []
for header_name, value in request.META.items():
if header_name.startswith('HTTP_'):
name = header_name.split('HTTP_', 1)[1]
results.append({'key': name, 'value': value})
elif header_name in ('CONTENT_LENGTH', 'CONTENT_TYPE'):
results.append({'key': header_name, 'value': value})
return results
def _get_operation_data(*args, **kwargs):
data = [
{'key': '_arg{}'.format(i), 'value': smart_text(arg, errors='ignore')}
for i, arg in enumerate(args)
]
data.extend(
{'key': k, 'value': smart_text(v, errors='ignore')}
for k, v in kwargs.items()
)
return data
class _OperationContextDecorator(object):
def __init__(self, local, args=None, kwargs=None, arguments_processor=None, **operation_kwargs):
self._local = local
self._args = args or ()
self._kwargs = kwargs or {}
self._arguments_processor = arguments_processor
self._operation_kwargs = operation_kwargs
def __enter__(self):
return self._start_operation()
def __exit__(self, exc_type, exc_value, traceback):
self._finish_operation()
def __call__(self, f):
@wraps(f)
def wrapper(*args, **kwargs):
self._args = args
self._kwargs = kwargs
with self:
return f(*args, **kwargs)
return wrapper
def _start_operation(self):
if getattr(self._local, 'session', None) is None:
return
operation = {
'date': datetime.utcnow(),
'data': _get_operation_data(*self._args, **self._kwargs),
}
if self._arguments_processor is not None:
arguments_processor_result = self._arguments_processor(*self._args, **self._kwargs)
if arguments_processor_result is None:
return
operation.update(arguments_processor_result)
operation.update(self._operation_kwargs)
self._local.operations.append(operation)
def _finish_operation(self):
if getattr(self._local, 'session', None) is None:
return
operation = self._local.operations.pop()
duration = datetime.utcnow() - operation['date']
operation['duration'] = int(1000 * duration.total_seconds())
self._local.session['operations'].append(operation)
# coding: utf-8
from cian_slowlog.base import Slowlog
slowlog = Slowlog()
class SlowlogMiddleware(object):
def process_request(self, request):
slowlog.start_session(request)
def process_response(self, request, response):
slowlog.finish_session(response)
return response
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment