Skip to content

Commit 5615201

Browse files
committed
Add falcon version 1.4.1 support to opentelemetry-instrumentation-falcon
1 parent 8727bc3 commit 5615201

File tree

8 files changed

+71
-27
lines changed

8 files changed

+71
-27
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- `opentelemetry-instrumentation-psycopg2` extended the sql commenter support of dbapi into psycopg2
1313
([#940](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/940))
14+
- `opentelemetry-instrumentation-falcon` Add support for falcon==1.4.1
15+
([#1000])(https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1000)
1416
- `opentelemetry-instrumentation-flask` Fix non-recording span bug
1517
([#999])(https://github.com/open-telemetry/opentelemetry-python-contrib/pull/999)
1618
- `opentelemetry-instrumentation-tornado` Fix non-recording span bug

instrumentation/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
| [opentelemetry-instrumentation-dbapi](./opentelemetry-instrumentation-dbapi) | dbapi |
1313
| [opentelemetry-instrumentation-django](./opentelemetry-instrumentation-django) | django >= 1.10 |
1414
| [opentelemetry-instrumentation-elasticsearch](./opentelemetry-instrumentation-elasticsearch) | elasticsearch >= 2.0 |
15-
| [opentelemetry-instrumentation-falcon](./opentelemetry-instrumentation-falcon) | falcon >= 2.0.0, < 4.0.0 |
15+
| [opentelemetry-instrumentation-falcon](./opentelemetry-instrumentation-falcon) | falcon >= 1.4.1, < 4.0.0 |
1616
| [opentelemetry-instrumentation-fastapi](./opentelemetry-instrumentation-fastapi) | fastapi ~= 0.58 |
1717
| [opentelemetry-instrumentation-flask](./opentelemetry-instrumentation-flask) | flask >= 1.0, < 3.0 |
1818
| [opentelemetry-instrumentation-grpc](./opentelemetry-instrumentation-grpc) | grpcio ~= 1.27 |

instrumentation/opentelemetry-instrumentation-falcon/src/opentelemetry/instrumentation/falcon/__init__.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ def response_hook(span, req, resp):
9595
from typing import Collection
9696

9797
import falcon
98+
from packaging import version as package_version
9899

99100
import opentelemetry.instrumentation.wsgi as otel_wsgi
100101
from opentelemetry import context, trace
@@ -126,12 +127,19 @@ def response_hook(span, req, resp):
126127

127128
_response_propagation_setter = FuncSetter(falcon.Response.append_header)
128129

129-
if hasattr(falcon, "App"):
130+
_parsed_falcon_version = package_version.parse(falcon.__version__)
131+
if _parsed_falcon_version >= package_version.parse("3.0.0"):
130132
# Falcon 3
131133
_instrument_app = "App"
132-
else:
134+
_falcon_version = 3
135+
elif _parsed_falcon_version >= package_version.parse("2.0.0"):
133136
# Falcon 2
134137
_instrument_app = "API"
138+
_falcon_version = 2
139+
else:
140+
# Falcon 1
141+
_instrument_app = "API"
142+
_falcon_version = 1
135143

136144

137145
class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
@@ -163,13 +171,31 @@ def __init__(self, *args, **kwargs):
163171
super().__init__(*args, **kwargs)
164172

165173
def _handle_exception(
166-
self, req, resp, ex, params
174+
self, arg1, arg2, arg3, arg4
167175
): # pylint: disable=C0103
168176
# Falcon 3 does not execute middleware within the context of the exception
169177
# so we capture the exception here and save it into the env dict
178+
179+
# Translation layer for handling the changed arg position of "ex" in Falcon > 2 vs
180+
# Falcon < 2
181+
if _falcon_version == 1:
182+
ex = arg1
183+
req = arg2
184+
resp = arg3
185+
params = arg4
186+
else:
187+
req = arg1
188+
resp = arg2
189+
ex = arg3
190+
params = arg4
191+
170192
_, exc, _ = exc_info()
171193
req.env[_ENVIRON_EXC] = exc
172-
return super()._handle_exception(req, resp, ex, params)
194+
195+
if _falcon_version == 1:
196+
return super()._handle_exception(ex, req, resp, params)
197+
else:
198+
return super()._handle_exception(req, resp, ex, params)
173199

174200
def __call__(self, env, start_response):
175201
# pylint: disable=E1101

instrumentation/opentelemetry-instrumentation-falcon/src/opentelemetry/instrumentation/falcon/package.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@
1313
# limitations under the License.
1414

1515

16-
_instruments = ("falcon >= 2.0.0, < 4.0.0",)
16+
_instruments = ("falcon >= 1.4.1, < 4.0.0",)

instrumentation/opentelemetry-instrumentation-falcon/tests/app.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import falcon
2+
from packaging import version as package_version
23

34
# pylint:disable=R0201,W0613,E0602
45

@@ -46,12 +47,17 @@ def on_get(self, _, resp):
4647

4748

4849
def make_app():
49-
if hasattr(falcon, "App"):
50+
_parsed_falcon_version = package_version.parse(falcon.__version__)
51+
if _parsed_falcon_version >= package_version.parse("3.0.0"):
5052
# Falcon 3
5153
app = falcon.App()
52-
else:
54+
elif _parsed_falcon_version >= package_version.parse("2.0.0"):
5355
# Falcon 2
5456
app = falcon.API()
57+
else:
58+
# Falcon 1
59+
app = falcon.API()
60+
5561
app.add_route("/hello", HelloWorldResource())
5662
app.add_route("/ping", HelloWorldResource())
5763
app.add_route("/error", ErrorResource())

instrumentation/opentelemetry-instrumentation-falcon/tests/test_falcon.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,7 @@ def test_head(self):
8484
self._test_method("HEAD")
8585

8686
def _test_method(self, method):
87-
self.client().simulate_request(
88-
method=method, path="/hello", remote_addr="127.0.0.1"
89-
)
87+
self.client().simulate_request(method=method, path="/hello")
9088
spans = self.memory_exporter.get_finished_spans()
9189
self.assertEqual(len(spans), 1)
9290
span = spans[0]
@@ -105,17 +103,20 @@ def _test_method(self, method):
105103
SpanAttributes.NET_HOST_PORT: 80,
106104
SpanAttributes.HTTP_HOST: "falconframework.org",
107105
SpanAttributes.HTTP_TARGET: "/",
108-
SpanAttributes.NET_PEER_IP: "127.0.0.1",
109106
SpanAttributes.NET_PEER_PORT: "65133",
110107
SpanAttributes.HTTP_FLAVOR: "1.1",
111108
"falcon.resource": "HelloWorldResource",
112109
SpanAttributes.HTTP_STATUS_CODE: 201,
113110
},
114111
)
112+
if SpanAttributes.NET_PEER_IP in span.attributes:
113+
self.assertEqual(
114+
span.attributes[SpanAttributes.NET_PEER_IP], "127.0.0.1"
115+
)
115116
self.memory_exporter.clear()
116117

117118
def test_404(self):
118-
self.client().simulate_get("/does-not-exist", remote_addr="127.0.0.1")
119+
self.client().simulate_get("/does-not-exist")
119120
spans = self.memory_exporter.get_finished_spans()
120121
self.assertEqual(len(spans), 1)
121122
span = spans[0]
@@ -130,16 +131,19 @@ def test_404(self):
130131
SpanAttributes.NET_HOST_PORT: 80,
131132
SpanAttributes.HTTP_HOST: "falconframework.org",
132133
SpanAttributes.HTTP_TARGET: "/",
133-
SpanAttributes.NET_PEER_IP: "127.0.0.1",
134134
SpanAttributes.NET_PEER_PORT: "65133",
135135
SpanAttributes.HTTP_FLAVOR: "1.1",
136136
SpanAttributes.HTTP_STATUS_CODE: 404,
137137
},
138138
)
139+
if SpanAttributes.NET_PEER_IP in span.attributes:
140+
self.assertEqual(
141+
span.attributes[SpanAttributes.NET_PEER_IP], "127.0.0.1"
142+
)
139143

140144
def test_500(self):
141145
try:
142-
self.client().simulate_get("/error", remote_addr="127.0.0.1")
146+
self.client().simulate_get("/error")
143147
except NameError:
144148
pass
145149
spans = self.memory_exporter.get_finished_spans()
@@ -161,12 +165,15 @@ def test_500(self):
161165
SpanAttributes.NET_HOST_PORT: 80,
162166
SpanAttributes.HTTP_HOST: "falconframework.org",
163167
SpanAttributes.HTTP_TARGET: "/",
164-
SpanAttributes.NET_PEER_IP: "127.0.0.1",
165168
SpanAttributes.NET_PEER_PORT: "65133",
166169
SpanAttributes.HTTP_FLAVOR: "1.1",
167170
SpanAttributes.HTTP_STATUS_CODE: 500,
168171
},
169172
)
173+
if SpanAttributes.NET_PEER_IP in span.attributes:
174+
self.assertEqual(
175+
span.attributes[SpanAttributes.NET_PEER_IP], "127.0.0.1"
176+
)
170177

171178
def test_uninstrument(self):
172179
self.client().simulate_get(path="/hello")
@@ -191,7 +198,7 @@ def test_exclude_lists(self):
191198
self.assertEqual(len(span_list), 1)
192199

193200
def test_traced_request_attributes(self):
194-
self.client().simulate_get(path="/hello?q=abc")
201+
self.client().simulate_get(path="/hello", query_string="q=abc")
195202
span = self.memory_exporter.get_finished_spans()[0]
196203
self.assertIn("query_string", span.attributes)
197204
self.assertEqual(span.attributes["query_string"], "q=abc")
@@ -201,7 +208,9 @@ def test_trace_response(self):
201208
orig = get_global_response_propagator()
202209
set_global_response_propagator(TraceResponsePropagator())
203210

204-
response = self.client().simulate_get(path="/hello?q=abc")
211+
response = self.client().simulate_get(
212+
path="/hello", query_string="q=abc"
213+
)
205214
self.assertTraceResponseHeaderMatchesSpan(
206215
response.headers, self.memory_exporter.get_finished_spans()[0]
207216
)
@@ -215,7 +224,7 @@ def test_traced_not_recording(self):
215224
mock_tracer.start_span.return_value = mock_span
216225
with patch("opentelemetry.trace.get_tracer") as tracer:
217226
tracer.return_value = mock_tracer
218-
self.client().simulate_get(path="/hello?q=abc")
227+
self.client().simulate_get(path="/hello", query_string="q=abc")
219228
self.assertFalse(mock_span.is_recording())
220229
self.assertTrue(mock_span.is_recording.called)
221230
self.assertFalse(mock_span.set_attribute.called)
@@ -261,7 +270,7 @@ def response_hook(self, span, req, resp):
261270
span.update_name("set from hook")
262271

263272
def test_hooks(self):
264-
self.client().simulate_get(path="/hello?q=abc")
273+
self.client().simulate_get(path="/hello", query_string="q=abc")
265274
span = self.memory_exporter.get_finished_spans()[0]
266275

267276
self.assertEqual(span.name, "set from hook")

opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"instrumentation": "opentelemetry-instrumentation-elasticsearch==0.29b0",
5454
},
5555
"falcon": {
56-
"library": "falcon >= 2.0.0, < 4.0.0",
56+
"library": "falcon >= 1.4.1, < 4.0.0",
5757
"instrumentation": "opentelemetry-instrumentation-falcon==0.29b0",
5858
},
5959
"fastapi": {

tox.ini

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ envlist =
5959
pypy3-test-instrumentation-elasticsearch5
6060

6161
; opentelemetry-instrumentation-falcon
62-
py3{6,7,8,9,10}-test-instrumentation-falcon{2,3}
63-
pypy3-test-instrumentation-falcon{2,3}
62+
py3{6,7,8,9,10}-test-instrumentation-falcon{1,2,3}
63+
pypy3-test-instrumentation-falcon{1,2,3}
6464

6565
; opentelemetry-instrumentation-fastapi
6666
; fastapi only supports 3.6 and above.
@@ -215,6 +215,7 @@ deps =
215215
; FIXME: Elasticsearch >=7 causes CI workflow tests to hang, see open-telemetry/opentelemetry-python-contrib#620
216216
; elasticsearch7: elasticsearch-dsl>=7.0,<8.0
217217
; elasticsearch7: elasticsearch>=7.0,<8.0
218+
falcon1: falcon ==1.4.1
218219
falcon2: falcon >=2.0.0,<3.0.0
219220
falcon3: falcon >=3.0.0,<4.0.0
220221
sqlalchemy11: sqlalchemy>=1.1,<1.2
@@ -250,7 +251,7 @@ changedir =
250251
test-instrumentation-dbapi: instrumentation/opentelemetry-instrumentation-dbapi/tests
251252
test-instrumentation-django{1,2,3,4}: instrumentation/opentelemetry-instrumentation-django/tests
252253
test-instrumentation-elasticsearch{2,5,6}: instrumentation/opentelemetry-instrumentation-elasticsearch/tests
253-
test-instrumentation-falcon{2,3}: instrumentation/opentelemetry-instrumentation-falcon/tests
254+
test-instrumentation-falcon{1,2,3}: instrumentation/opentelemetry-instrumentation-falcon/tests
254255
test-instrumentation-fastapi: instrumentation/opentelemetry-instrumentation-fastapi/tests
255256
test-instrumentation-flask: instrumentation/opentelemetry-instrumentation-flask/tests
256257
test-instrumentation-urllib: instrumentation/opentelemetry-instrumentation-urllib/tests
@@ -303,8 +304,8 @@ commands_pre =
303304

304305
grpc: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-grpc[test]
305306

306-
falcon{2,3},flask,django{1,2,3,4},pyramid,tornado,starlette,fastapi,aiohttp,asgi,requests,urllib,urllib3,wsgi: pip install {toxinidir}/util/opentelemetry-util-http[test]
307-
wsgi,falcon{2,3},flask,django{1,2,3,4},pyramid: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-wsgi[test]
307+
falcon{1,2,3},flask,django{1,2,3,4},pyramid,tornado,starlette,fastapi,aiohttp,asgi,requests,urllib,urllib3,wsgi: pip install {toxinidir}/util/opentelemetry-util-http[test]
308+
wsgi,falcon{1,2,3},flask,django{1,2,3,4},pyramid: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-wsgi[test]
308309
asgi,django{3,4},starlette,fastapi: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-asgi[test]
309310

310311
asyncpg: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-asyncpg[test]
@@ -314,7 +315,7 @@ commands_pre =
314315
boto: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-botocore[test]
315316
boto: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-boto[test]
316317

317-
falcon{2,3}: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-falcon[test]
318+
falcon{1,2,3}: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-falcon[test]
318319

319320
flask: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-flask[test]
320321

0 commit comments

Comments
 (0)