/
framework_bottle.py
184 lines (140 loc) · 6.65 KB
/
framework_bottle.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
# Copyright 2010 New Relic, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Instrumentation module for Bottle framework.
"""
import functools
from newrelic.api.function_trace import (
FunctionTrace,
FunctionTraceWrapper,
wrap_function_trace,
)
from newrelic.api.transaction import current_transaction
from newrelic.api.wsgi_application import wrap_wsgi_application
from newrelic.common.object_names import callable_name
from newrelic.common.object_wrapper import (
ObjectProxy,
function_wrapper,
wrap_function_wrapper,
wrap_object_attribute,
wrap_out_function,
)
module_bottle = None
def status_code(exc, value, tb):
# The HTTPError class derives from HTTPResponse and so we do not
# need to check for it seperately as isinstance() will pick it up.
if isinstance(value, module_bottle.HTTPResponse): # pragma: no cover
if hasattr(value, "status_code"):
return value.status_code
elif hasattr(value, "status"):
return value.status
elif hasattr(value, "http_status_code"):
return value.http_status_code
def should_ignore(exc, value, tb):
if hasattr(module_bottle, "RouteReset"):
if isinstance(value, module_bottle.RouteReset):
return True
@function_wrapper
def callback_wrapper(wrapped, instance, args, kwargs):
transaction = current_transaction()
if transaction is None:
return wrapped(*args, **kwargs)
name = callable_name(wrapped)
# Needs to be at a higher priority so that error handler processing
# below will not override the web transaction being named after the
# actual request handler.
transaction.set_transaction_name(name, priority=2)
with FunctionTrace(name=name, source=wrapped) as trace:
try:
return wrapped(*args, **kwargs)
except: # Catch all
# In most cases this seems like it will never be invoked as
# bottle will internally capture the exception before we
# get a chance and rather than propagate the exception will
# return it instead. This doesn't always seem to be the case
# though when plugins are used, although that may depend on
# the specific bottle version being used.
trace.notice_error(status_code=status_code, ignore=should_ignore)
raise
def output_wrapper_Bottle_match(result):
callback, args = result
return callback_wrapper(callback), args
def output_wrapper_Route_make_callback(callback):
return callback_wrapper(callback)
class proxy_Bottle_error_handler(ObjectProxy):
# This proxy wraps the error_handler attribute of the Bottle class.
# The attribute is a dictionary of handlers for HTTP status codes.
# We specifically override the get() method of the dictionary so
# that we can determine if the dictionary actually held a handler
# for a specific HTTP status code. If it didn't, we name the web
# transaction based on the status code as a fallback if not already
# set based on a specific request handler. Otherwise, if there was
# an error handler we will name the web transaction after the error
# handler instead if not already set based on a specific request
# handler.
def get(self, status, default=None):
transaction = current_transaction()
if transaction is None:
return self.__wrapped__.get(status, default)
handler = self.__wrapped__.get(status)
if handler:
name = callable_name(handler)
transaction.set_transaction_name(name, priority=1)
handler = FunctionTraceWrapper(handler, name=name)
else:
transaction.set_transaction_name(str(status), group="StatusCode", priority=1)
return handler or default
def wrapper_auth_basic(wrapped, instance, args, kwargs):
# Bottle has a bug whereby functools.wraps() is not used on the
# nested wrapper function in the implementation of auth_basic()
# decorator. We apply it ourself to try and workaround the issue.
# Note that this is dependent though on the agent having been
# initialised before the auth_basic() decorator is used. The issue
# exists in bottle up to version 0.12.3, but shouldn't matter that
# we reapply wraps() even in newer versions which address issue.
decorator = wrapped(*args, **kwargs)
def _decorator(func):
return functools.wraps(func)(decorator(func))
return _decorator
def instrument_bottle(module):
global module_bottle
module_bottle = module
framework_details = ("Bottle", getattr(module, "__version__"))
# version >= 0.9
if hasattr(module.Bottle, "wsgi"): # pragma: no cover
wrap_wsgi_application(module, "Bottle.wsgi", framework=framework_details)
# version < 0.9
elif hasattr(module.Bottle, "__call__"): # pragma: no cover
wrap_wsgi_application(module, "Bottle.__call__", framework=framework_details)
# version >= 0.10
if hasattr(module, "Route") and hasattr(module.Route, "_make_callback"): # pragma: no cover
wrap_out_function(module, "Route._make_callback", output_wrapper_Route_make_callback)
# version >= 0.9
elif hasattr(module.Bottle, "_match"): # pragma: no cover
wrap_out_function(module, "Bottle._match", output_wrapper_Bottle_match)
# version < 0.9
elif hasattr(module.Bottle, "match_url"): # pragma: no cover
wrap_out_function(module, "Bottle.match_url", output_wrapper_Bottle_match)
wrap_object_attribute(module, "Bottle.error_handler", proxy_Bottle_error_handler)
if hasattr(module, "auth_basic"):
wrap_function_wrapper(module, "auth_basic", wrapper_auth_basic)
if hasattr(module, "SimpleTemplate"):
wrap_function_trace(module, "SimpleTemplate.render")
if hasattr(module, "MakoTemplate"):
wrap_function_trace(module, "MakoTemplate.render")
if hasattr(module, "CheetahTemplate"):
wrap_function_trace(module, "CheetahTemplate.render")
if hasattr(module, "Jinja2Template"):
wrap_function_trace(module, "Jinja2Template.render")
if hasattr(module, "SimpleTALTemplate"): # pragma: no cover
wrap_function_trace(module, "SimpleTALTemplate.render")