Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit 2e396b0

Browse files
tpyoreyang
authored andcommitted
Use Django DB instrumentation (#775)
1 parent 8e582cb commit 2e396b0

File tree

2 files changed

+127
-0
lines changed

2 files changed

+127
-0
lines changed

contrib/opencensus-ext-django/opencensus/ext/django/middleware.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,22 @@
1313
# limitations under the License.
1414

1515
"""Django middleware helper to capture and trace a request."""
16+
import django
1617
import logging
1718
import six
1819

1920
import django.conf
21+
from django.db import connection
2022
from django.utils.deprecation import MiddlewareMixin
23+
from google.rpc import code_pb2
2124

2225
from opencensus.common import configuration
2326
from opencensus.trace import attributes_helper
2427
from opencensus.trace import execution_context
2528
from opencensus.trace import print_exporter
2629
from opencensus.trace import samplers
2730
from opencensus.trace import span as span_module
31+
from opencensus.trace import status as status_module
2832
from opencensus.trace import tracer as tracer_module
2933
from opencensus.trace import utils
3034
from opencensus.trace.propagation import trace_context_http_header_format
@@ -99,6 +103,37 @@ def _set_django_attributes(span, request):
99103
span.add_attribute('django.user.name', str(user_name))
100104

101105

106+
def _trace_db_call(execute, sql, params, many, context):
107+
tracer = _get_current_tracer()
108+
if not tracer:
109+
return execute(sql, params, many, context)
110+
111+
vendor = context['connection'].vendor
112+
alias = context['connection'].alias
113+
114+
span = tracer.start_span()
115+
span.name = '{}.query'.format(vendor)
116+
span.span_kind = span_module.SpanKind.CLIENT
117+
118+
tracer.add_attribute_to_current_span('component', vendor)
119+
tracer.add_attribute_to_current_span('db.instance', alias)
120+
tracer.add_attribute_to_current_span('db.statement', sql)
121+
tracer.add_attribute_to_current_span('db.type', 'sql')
122+
123+
try:
124+
result = execute(sql, params, many, context)
125+
except Exception: # pragma: NO COVER
126+
status = status_module.Status(
127+
code=code_pb2.UNKNOWN, message='DB error'
128+
)
129+
span.set_status(status)
130+
raise
131+
else:
132+
return result
133+
finally:
134+
tracer.end_span()
135+
136+
102137
class OpencensusMiddleware(MiddlewareMixin):
103138
"""Saves the request in thread local"""
104139

@@ -126,6 +161,9 @@ def __init__(self, get_response=None):
126161

127162
self.blacklist_hostnames = settings.get(BLACKLIST_HOSTNAMES, None)
128163

164+
if django.VERSION >= (2,): # pragma: NO COVER
165+
connection.execute_wrappers.append(_trace_db_call)
166+
129167
def process_request(self, request):
130168
"""Called on each request, before Django decides which view to execute.
131169
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Copyright 2017, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import unittest
16+
from collections import namedtuple
17+
18+
import django
19+
import mock
20+
import pytest
21+
from django.test.utils import teardown_test_environment
22+
23+
from opencensus.trace import execution_context
24+
25+
26+
class TestOpencensusDatabaseMiddleware(unittest.TestCase):
27+
def setUp(self):
28+
from django.conf import settings as django_settings
29+
from django.test.utils import setup_test_environment
30+
31+
if not django_settings.configured:
32+
django_settings.configure()
33+
setup_test_environment()
34+
35+
def tearDown(self):
36+
execution_context.clear()
37+
teardown_test_environment()
38+
39+
def test_process_request(self):
40+
if django.VERSION < (2, 0):
41+
pytest.skip("Wrong version of Django")
42+
43+
from opencensus.ext.django import middleware
44+
45+
sql = "SELECT * FROM users"
46+
47+
MockConnection = namedtuple('Connection', ('vendor', 'alias'))
48+
connection = MockConnection('mysql', 'default')
49+
50+
mock_execute = mock.Mock()
51+
mock_execute.return_value = "Mock result"
52+
53+
middleware.OpencensusMiddleware()
54+
55+
patch_no_tracer = mock.patch(
56+
'opencensus.ext.django.middleware._get_current_tracer',
57+
return_value=None)
58+
with patch_no_tracer:
59+
result = middleware._trace_db_call(
60+
mock_execute, sql, params=[], many=False,
61+
context={'connection': connection})
62+
self.assertEqual(result, "Mock result")
63+
64+
mock_tracer = mock.Mock()
65+
mock_tracer.return_value = mock_tracer
66+
patch = mock.patch(
67+
'opencensus.ext.django.middleware._get_current_tracer',
68+
return_value=mock_tracer)
69+
with patch:
70+
result = middleware._trace_db_call(
71+
mock_execute, sql, params=[], many=False,
72+
context={'connection': connection})
73+
74+
(mock_sql, mock_params, mock_many,
75+
mock_context) = mock_execute.call_args[0]
76+
77+
self.assertEqual(mock_sql, sql)
78+
self.assertEqual(mock_params, [])
79+
self.assertEqual(mock_many, False)
80+
self.assertEqual(mock_context, {'connection': connection})
81+
self.assertEqual(result, "Mock result")
82+
83+
result = middleware._trace_db_call(
84+
mock_execute, sql, params=[], many=True,
85+
context={'connection': connection})
86+
87+
(mock_sql, mock_params, mock_many,
88+
mock_context) = mock_execute.call_args[0]
89+
self.assertEqual(mock_many, True)

0 commit comments

Comments
 (0)