Last active
August 23, 2018 14:29
-
-
Save yumike/7ed521eccfde363f300d6c6b52159d5b to your computer and use it in GitHub Desktop.
cian-slowlog
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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