-
Notifications
You must be signed in to change notification settings - Fork 468
Matt/django #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Matt/django #7
Changes from all commits
463a7ca
c0638fd
41b5073
c9fea94
215e14b
0eeb5f1
644bc96
500bd8d
b1ba6af
2e7c663
659adff
5b07633
49c565f
34f3f8b
b6e45c4
aab5f31
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
|
|
||
|
|
||
| def func_name(f): | ||
| """ Return a human readable version of the function's name. """ | ||
| return "%s.%s" % (f.__module__, f.__name__) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
|
|
||
|
|
||
| import logging | ||
| from types import MethodType | ||
|
|
||
|
|
||
| # project | ||
| from ... import tracer | ||
| from ...ext import http, errors | ||
| from ...contrib import func_name | ||
| from .templates import patch_template | ||
| from .db import patch_db | ||
|
|
||
| # 3p | ||
| from django.apps import apps | ||
|
|
||
|
|
||
| log = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class TraceMiddleware(object): | ||
|
|
||
| def __init__(self): | ||
| # override if necessary (can't initialize though) | ||
| self.tracer = tracer | ||
| self.service = "django" | ||
|
|
||
| try: | ||
| patch_template(self.tracer) | ||
| except Exception: | ||
| log.exception("error patching template class") | ||
|
|
||
| def process_request(self, request): | ||
| try: | ||
| patch_db(self.tracer) # ensure that connections are always patched. | ||
|
|
||
| span = self.tracer.trace( | ||
| "django.request", | ||
| service=self.service, | ||
| resource="unknown", # will be filled by process view | ||
| span_type=http.TYPE) | ||
|
|
||
| span.set_tag(http.METHOD, request.method) | ||
| span.set_tag(http.URL, request.path) | ||
| _set_req_span(request, span) | ||
| except Exception: | ||
| log.exception("error tracing request") | ||
|
|
||
| def process_view(self, request, view_func, *args, **kwargs): | ||
| span = _get_req_span(request) | ||
| if span: | ||
| span.resource = func_name(view_func) | ||
|
|
||
| def process_response(self, request, response): | ||
| try: | ||
| span = _get_req_span(request) | ||
| if span: | ||
| span.set_tag(http.STATUS_CODE, response.status_code) | ||
|
|
||
| if apps.is_installed("django.contrib.auth"): | ||
| span = _set_auth_tags(span, request) | ||
|
|
||
| span.finish() | ||
|
|
||
| except Exception: | ||
| log.exception("error tracing request") | ||
| finally: | ||
| return response | ||
|
|
||
| def process_exception(self, request, exception): | ||
| try: | ||
| span = _get_req_span(request) | ||
| if span: | ||
| span.set_tag(http.STATUS_CODE, '500') | ||
| span.set_traceback() # will set the exception info | ||
| except Exception: | ||
| log.exception("error processing exception") | ||
|
|
||
|
|
||
|
|
||
| def _get_req_span(request): | ||
| """ Return the datadog span from the given request. """ | ||
| return getattr(request, '_datadog_request_span', None) | ||
|
|
||
| def _set_req_span(request, span): | ||
| """ Set the datadog span on the given request. """ | ||
| return setattr(request, '_datadog_request_span', span) | ||
|
|
||
|
|
||
| def _set_auth_tags(span, request): | ||
| """ Patch any available auth tags from the request onto the span. """ | ||
| user = getattr(request, 'user', None) | ||
| if not user: | ||
| return | ||
|
|
||
| if hasattr(user, 'is_authenticated'): | ||
| span.set_tag('django.user.is_authenticated', user.is_authenticated()) | ||
|
|
||
| uid = getattr(user, 'pk', None) | ||
| if uid: | ||
| span.set_tag('django.user.id', uid) | ||
|
|
||
| uname = getattr(user, 'username', None) | ||
| if uname: | ||
| span.set_tag('django.user.name', uname) | ||
|
|
||
| return span | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
|
|
||
| import logging | ||
|
|
||
| from django.db import connections | ||
|
|
||
| # project | ||
| from ...ext import sql as sqlx | ||
|
|
||
|
|
||
| log = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def patch_db(tracer): | ||
| for c in connections.all(): | ||
| patch_conn(tracer, c) | ||
|
|
||
| def patch_conn(tracer, conn): | ||
| attr = '_datadog_original_cursor' | ||
| if hasattr(conn, attr): | ||
| log.debug("already patched") | ||
| return | ||
|
|
||
| conn._datadog_original_cursor = conn.cursor | ||
| def cursor(): | ||
| return TracedCursor(tracer, conn, conn._datadog_original_cursor()) | ||
| conn.cursor = cursor | ||
|
|
||
|
|
||
| class TracedCursor(object): | ||
|
|
||
| def __init__(self, tracer, conn, cursor): | ||
| self.tracer = tracer | ||
| self.conn = conn | ||
| self.cursor = cursor | ||
|
|
||
| self._vendor = getattr(conn, 'vendor', 'db') # e.g sqlite, postgres | ||
| self._alias = getattr(conn, 'alias', 'default') # e.g. default, users | ||
|
|
||
| prefix = _vendor_to_prefix(self._vendor) | ||
| self._name = "%s.%s" % (prefix, "query") # e.g sqlite3.query | ||
| self._service = "%s%s" % (self._alias or prefix, "db") # e.g. defaultdb or postgresdb | ||
|
|
||
| def _trace(self, func, sql, params): | ||
| with self.tracer.trace(self._name, resource=sql, service=self._service, span_type=sqlx.TYPE) as span: | ||
| span.set_tag(sqlx.QUERY, sql) | ||
| span.set_tag("django.db.vendor", self._vendor) | ||
| span.set_tag("django.db.alias", self._alias) | ||
| try: | ||
| return func(sql, params) | ||
| finally: | ||
| rows = self.cursor.cursor.rowcount | ||
| if rows and 0 <= rows: | ||
| span.set_tag(sqlx.ROWS, self.cursor.cursor.rowcount) | ||
|
|
||
| def callproc(self, procname, params=None): | ||
| return self._trace(self.cursor.callproc, procname, params) | ||
|
|
||
| def execute(self, sql, params=None): | ||
| return self._trace(self.cursor.execute, sql, params) | ||
|
|
||
| def executemany(self, sql, param_list): | ||
| return self._trace(self.cursor.executemany, sql, param_list) | ||
|
|
||
| def close(self): | ||
| return self.cursor.close() | ||
|
|
||
| def __getattr__(self, attr): | ||
| return getattr(self.cursor, attr) | ||
|
|
||
| def __iter__(self): | ||
| return iter(self.cursor) | ||
|
|
||
| def __enter__(self): | ||
| return self | ||
|
|
||
| def __exit__(self, type, value, traceback): | ||
| self.close() | ||
|
|
||
|
|
||
| def _vendor_to_prefix(vendor): | ||
| if not vendor: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems this method is only invoked with
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i'm just worried it might be missing at some point. want to be defensive. |
||
| return "db" # should this ever happen? | ||
| elif vendor == "sqlite": | ||
| return "sqlite3" # for consitency with the sqlite3 integration | ||
| else: | ||
| return vendor | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| """ | ||
| code to measure django template rendering. | ||
| """ | ||
|
|
||
|
|
||
| # stdlib | ||
| import logging | ||
|
|
||
| # project | ||
| from ...ext import http, errors | ||
|
|
||
| # 3p | ||
| from django.template import Template | ||
|
|
||
|
|
||
| log = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def patch_template(tracer): | ||
| """ will patch django's template rendering function to include timing | ||
| and trace information. | ||
| """ | ||
|
|
||
| # FIXME[matt] we're patching the template class here. ideally we'd only | ||
| # patch so we can use multiple tracers at once, but i suspect this is fine | ||
| # in practice. | ||
| attr = '_datadog_original_render' | ||
| if getattr(Template, attr, None): | ||
| log.debug("already patched") | ||
| return | ||
|
|
||
| setattr(Template, attr, Template.render) | ||
|
|
||
| class TracedTemplate(object): | ||
|
|
||
| def render(self, context): | ||
| with tracer.trace('django.template', span_type=http.TEMPLATE) as span: | ||
| try: | ||
| return Template._datadog_original_render(self, context) | ||
| finally: | ||
| span.set_tag('django.template_name', context.template_name or 'unknown') | ||
|
|
||
| Template.render = TracedTemplate.render.__func__ | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |
| 'blinker', | ||
| 'elasticsearch', | ||
| 'psycopg2', | ||
| 'django' | ||
| ] | ||
|
|
||
| setup( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Been a long time since I used django (around 1.4), but I think you need to check for class views.
https://github.com/getsentry/sentry/blob/master/src/sentry/middleware/stats.py#L33
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why Sentry does that, within
process_viewtheas_viewmethod of CBVs is already called [0][1], soview_funcis a function object in any case andfunc_nameshould work. See [2] for a similar use case.[0] https://docs.djangoproject.com/en/1.9/topics/http/middleware/#process-view
[1] https://github.com/django/django/blob/1.9/django/views/generic/base.py#L78
[2] https://github.com/django-debug-toolbar/django-debug-toolbar/blob/master/debug_toolbar/middleware.py#L70
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DataDog/trace-examples@60e6d66
i tried it, i think it works.
hello.views.IndexView