-
Notifications
You must be signed in to change notification settings - Fork 39
/
Copy path__init__.py
188 lines (150 loc) · 6.63 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
import contextlib
import datetime
import beeline
from beeline.propagation import Request
from django.db import connections
class DjangoRequest(Request):
def __init__(self, request):
self._request = request
self._META = request.META
# only write log if beeline has been initalised
if beeline.get_beeline():
beeline.get_beeline().log(request.META)
def header(self, key):
lookup_key = "HTTP_" + key.upper().replace('-', '_')
return self._request.META.get(lookup_key)
def method(self):
return self._request.method
def scheme(self):
return self._request.scheme
def host(self):
return self._request.get_host()
def path(self):
return self._request.path
def query(self):
return self._request.META.get('QUERY_STRING')
def middleware_request(self):
return self._request
class HoneyDBWrapper(object):
def __call__(self, execute, sql, params, many, context):
# if beeline has not been initialised, just execute query
if not beeline.get_beeline():
return execute(sql, params, many, context)
vendor = context['connection'].vendor
trace_name = "django_%s_query" % vendor
with beeline.tracer(trace_name):
beeline.add_context({
"type": "db",
"db.query": sql,
"db.query_args": params,
})
try:
db_call_start = datetime.datetime.now()
result = execute(sql, params, many, context)
db_call_diff = datetime.datetime.now() - db_call_start
beeline.add_context_field(
"db.duration", db_call_diff.total_seconds() * 1000)
except Exception as e:
beeline.add_context_field("db.error", str(type(e)))
beeline.add_context_field(
"db.error_detail", beeline.internal.stringify_exception(e))
raise
else:
return result
finally:
if vendor in ('postgresql', 'mysql'):
beeline.add_context({
"db.last_insert_id": context['cursor'].cursor.lastrowid,
"db.rows_affected": context['cursor'].cursor.rowcount,
})
class HoneyMiddlewareBase(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.create_http_event(request)
return response
def get_context_from_request(self, request):
trace_name = "django_http_%s" % request.method.lower()
return {
"name": trace_name,
"type": "http_server",
"request.host": request.get_host(),
"request.method": request.method,
"request.path": request.path,
"request.remote_addr": request.META.get('REMOTE_ADDR'),
"request.content_length": request.META.get('CONTENT_LENGTH', 0),
"request.user_agent": request.META.get('HTTP_USER_AGENT'),
"request.scheme": request.scheme,
"request.secure": request.is_secure(),
"request.query": request.GET.dict(),
"request.xhr": request.headers.get('x-requested-with') == 'XMLHttpRequest',
}
def get_context_from_response(self, request, response):
return {
"response.status_code": response.status_code,
}
def create_http_event(self, request):
# if beeline has not been initialised, just execute request
if not beeline.get_beeline():
return self.get_response(request)
# Code to be executed for each request before
# the view (and later middleware) are called.
dr = DjangoRequest(request)
request_context = self.get_context_from_request(request)
root_span = beeline.propagate_and_start_trace(request_context, dr)
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
response_context = self.get_context_from_response(request, response)
beeline.add_context(response_context)
beeline.finish_trace(root_span)
return response
def process_exception(self, request, exception):
if beeline.get_beeline():
beeline.add_context_field(
"request.error_detail", beeline.internal.stringify_exception(exception))
def process_view(self, request, view_func, view_args, view_kwargs):
if beeline.get_beeline():
try:
beeline.add_context_field("django.view_func", view_func.__name__)
except AttributeError:
pass
try:
beeline.add_context_field("request.route", request.resolver_match.route)
except AttributeError:
pass
class HoneyMiddlewareHttp(HoneyMiddlewareBase):
pass
class HoneyMiddleware(HoneyMiddlewareBase):
def __call__(self, request):
try:
db_wrapper = HoneyDBWrapper()
# db instrumentation is only present in Django > 2.0
with contextlib.ExitStack() as stack:
for connection in connections.all():
stack.enter_context(connection.execute_wrapper(db_wrapper))
response = self.create_http_event(request)
except AttributeError:
response = self.create_http_event(request)
return response
class HoneyMiddlewareWithPOST(HoneyMiddleware):
''' HoneyMiddlewareWithPOST is a subclass of HoneyMiddleware. The only difference is that
the `request.post` field is instrumented. This was removed from the base implementation in 2.8.0
due to conflicts with other middleware. See https://github.com/honeycombio/beeline-python/issues/74.'''
def get_context_from_request(self, request):
trace_name = "django_http_%s" % request.method.lower()
return {
"name": trace_name,
"type": "http_server",
"request.host": request.get_host(),
"request.method": request.method,
"request.path": request.path,
"request.remote_addr": request.META.get('REMOTE_ADDR'),
"request.content_length": request.META.get('CONTENT_LENGTH', 0),
"request.user_agent": request.META.get('HTTP_USER_AGENT'),
"request.scheme": request.scheme,
"request.secure": request.is_secure(),
"request.query": request.GET.dict(),
"request.xhr": request.headers.get('x-requested-with') == 'XMLHttpRequest',
"request.post": request.POST.dict(),
}