Skip to content

Commit 62f4ebd

Browse files
committed
Merge branch 'main' of https://github.com/open-telemetry/opentelemetry-python-contrib into django-issue-fix
2 parents 5bdec78 + 6c2aa7f commit 62f4ebd

File tree

11 files changed

+670
-151
lines changed

11 files changed

+670
-151
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3131
([#3731](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3731))
3232
- Fix documentation order of sections and headers for Django, Flask, MySQL, mysqlclient, psycopg, psycopg2, pymysql, sqlalchemy instrumentations.
3333
([#3719](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3719))
34+
- `opentelemetry-instrumentation-asgi` Fixed an issue where FastAPI reports IP instead of URL.
35+
([#3670](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3670))
3436
- `opentelemetry-instrumentation-httpx`: fix missing metric response attributes when tracing is disabled
3537
([#3615](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3615))
38+
- `opentelemetry-instrumentation-fastapi`: Don't pass bounded server_request_hook when using `FastAPIInstrumentor.instrument()`
39+
([#3701](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3701))
3640

3741
### Added
3842

@@ -46,6 +50,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4650
([#3366](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3366))
4751
- `opentelemetry-instrumentation`: add support for `OTEL_PYTHON_AUTO_INSTRUMENTATION_EXPERIMENTAL_GEVENT_PATCH` to inform opentelemetry-instrument about gevent monkeypatching
4852
([#3699](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3699))
53+
- `opentelemetry-instrumentation`: botocore: Add support for AWS Step Functions semantic convention attributes
54+
([#3737](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3737))
4955
- `opentelemetry-instrumentation-botocore`: Add support for SNS semantic convention attribute aws.sns.topic.arn
5056
([#3734](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3734))
5157
- `opentelemetry-instrumentation`: botocore: upgrade moto package from 5.0.9 to 5.1.11

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,15 +445,28 @@ def get_host_port_url_tuple(scope):
445445
"""Returns (host, port, full_url) tuple."""
446446
server = scope.get("server") or ["0.0.0.0", 80]
447447
port = server[1]
448+
449+
host_header = asgi_getter.get(scope, "host")
450+
if host_header:
451+
host_value = host_header[0]
452+
# Ensure host_value is a string, not bytes
453+
if isinstance(host_value, bytes):
454+
host_value = _decode_header_item(host_value)
455+
456+
url_host = host_value
457+
458+
else:
459+
url_host = server[0] + (":" + str(port) if str(port) != "80" else "")
448460
server_host = server[0] + (":" + str(port) if str(port) != "80" else "")
461+
449462
# using the scope path is enough, see:
450463
# - https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope (see: root_path and path)
451464
# - https://asgi.readthedocs.io/en/latest/specs/www.html#wsgi-compatibility (see: PATH_INFO)
452465
# PATH_INFO can be derived by stripping root_path from path
453466
# -> that means that the path should contain the root_path already, so prefixing it again is not necessary
454467
# - https://wsgi.readthedocs.io/en/latest/definitions.html#envvar-PATH_INFO
455468
full_path = scope.get("path", "")
456-
http_url = scope.get("scheme", "http") + "://" + server_host + full_path
469+
http_url = scope.get("scheme", "http") + "://" + url_host + full_path
457470
return server_host, port, http_url
458471

459472

instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,10 @@ async def test_host_header(self):
780780

781781
def update_expected_server(expected):
782782
expected[3]["attributes"].update(
783-
{SpanAttributes.HTTP_SERVER_NAME: hostname.decode("utf8")}
783+
{
784+
SpanAttributes.HTTP_SERVER_NAME: hostname.decode("utf8"),
785+
SpanAttributes.HTTP_URL: f"http://{hostname.decode('utf8')}/",
786+
}
784787
)
785788
return expected
786789

@@ -797,7 +800,10 @@ async def test_host_header_both_semconv(self):
797800

798801
def update_expected_server(expected):
799802
expected[3]["attributes"].update(
800-
{SpanAttributes.HTTP_SERVER_NAME: hostname.decode("utf8")}
803+
{
804+
SpanAttributes.HTTP_SERVER_NAME: hostname.decode("utf8"),
805+
SpanAttributes.HTTP_URL: f"http://{hostname.decode('utf8')}/",
806+
}
801807
)
802808
return expected
803809

@@ -1728,7 +1734,7 @@ def test_request_attributes(self):
17281734
SpanAttributes.HTTP_METHOD: "GET",
17291735
SpanAttributes.HTTP_HOST: "127.0.0.1",
17301736
SpanAttributes.HTTP_TARGET: "/",
1731-
SpanAttributes.HTTP_URL: "http://127.0.0.1/?foo=bar",
1737+
SpanAttributes.HTTP_URL: "http://test/?foo=bar",
17321738
SpanAttributes.NET_HOST_PORT: 80,
17331739
SpanAttributes.HTTP_SCHEME: "http",
17341740
SpanAttributes.HTTP_SERVER_NAME: "test",
@@ -1781,7 +1787,7 @@ def test_request_attributes_both_semconv(self):
17811787
SpanAttributes.HTTP_METHOD: "GET",
17821788
SpanAttributes.HTTP_HOST: "127.0.0.1",
17831789
SpanAttributes.HTTP_TARGET: "/",
1784-
SpanAttributes.HTTP_URL: "http://127.0.0.1/?foo=bar",
1790+
SpanAttributes.HTTP_URL: "http://test/?foo=bar",
17851791
SpanAttributes.NET_HOST_PORT: 80,
17861792
SpanAttributes.HTTP_SCHEME: "http",
17871793
SpanAttributes.HTTP_SERVER_NAME: "test",

instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/extensions/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def loader():
3535
"bedrock-runtime": _lazy_load(".bedrock", "_BedrockRuntimeExtension"),
3636
"dynamodb": _lazy_load(".dynamodb", "_DynamoDbExtension"),
3737
"lambda": _lazy_load(".lmbd", "_LambdaExtension"),
38+
"stepfunctions": _lazy_load(".sfns", "_StepFunctionsExtension"),
3839
"sns": _lazy_load(".sns", "_SnsExtension"),
3940
"sqs": _lazy_load(".sqs", "_SqsExtension"),
4041
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Copyright The OpenTelemetry 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+
from opentelemetry.instrumentation.botocore.extensions.types import (
15+
_AttributeMapT,
16+
_AwsSdkExtension,
17+
_BotocoreInstrumentorContext,
18+
_BotoResultT,
19+
)
20+
from opentelemetry.semconv._incubating.attributes.aws_attributes import (
21+
AWS_STEP_FUNCTIONS_ACTIVITY_ARN,
22+
AWS_STEP_FUNCTIONS_STATE_MACHINE_ARN,
23+
)
24+
from opentelemetry.trace.span import Span
25+
26+
27+
class _StepFunctionsExtension(_AwsSdkExtension):
28+
@staticmethod
29+
def _set_arn_attributes(source, target, setter_func):
30+
"""Helper to set ARN attributes if they exist in source."""
31+
activity_arn = source.get("activityArn")
32+
if activity_arn:
33+
setter_func(target, AWS_STEP_FUNCTIONS_ACTIVITY_ARN, activity_arn)
34+
35+
state_machine_arn = source.get("stateMachineArn")
36+
if state_machine_arn:
37+
setter_func(
38+
target, AWS_STEP_FUNCTIONS_STATE_MACHINE_ARN, state_machine_arn
39+
)
40+
41+
def extract_attributes(self, attributes: _AttributeMapT):
42+
self._set_arn_attributes(
43+
self._call_context.params,
44+
attributes,
45+
lambda target, key, value: target.__setitem__(key, value),
46+
)
47+
48+
def on_success(
49+
self,
50+
span: Span,
51+
result: _BotoResultT,
52+
instrumentor_context: _BotocoreInstrumentorContext,
53+
):
54+
self._set_arn_attributes(
55+
result,
56+
span,
57+
lambda target, key, value: target.set_attribute(key, value),
58+
)
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import json
2+
3+
import botocore.session
4+
from moto import mock_aws
5+
6+
from opentelemetry.instrumentation.botocore import BotocoreInstrumentor
7+
from opentelemetry.semconv._incubating.attributes.aws_attributes import (
8+
AWS_STEP_FUNCTIONS_ACTIVITY_ARN,
9+
AWS_STEP_FUNCTIONS_STATE_MACHINE_ARN,
10+
)
11+
from opentelemetry.test.test_base import TestBase
12+
13+
14+
class TestSfnsExtension(TestBase):
15+
def setUp(self):
16+
super().setUp()
17+
BotocoreInstrumentor().instrument()
18+
session = botocore.session.get_session()
19+
session.set_credentials(
20+
access_key="access-key", secret_key="secret-key"
21+
)
22+
self.region = "us-west-2"
23+
self.client = session.create_client(
24+
"stepfunctions", region_name=self.region
25+
)
26+
27+
def tearDown(self):
28+
super().tearDown()
29+
BotocoreInstrumentor().uninstrument()
30+
31+
SIMPLE_STATE_MACHINE_DEF = {
32+
"Comment": "A simple Hello World example",
33+
"StartAt": "HelloState",
34+
"States": {
35+
"HelloState": {
36+
"Type": "Pass",
37+
"Result": "Hello, Moto!",
38+
"End": True,
39+
}
40+
},
41+
}
42+
43+
def create_state_machine_and_get_arn(
44+
self, name: str = "TestStateMachine"
45+
) -> str:
46+
"""
47+
Create a state machine in mocked Step Functions and return its ARN.
48+
"""
49+
definition_json = json.dumps(self.SIMPLE_STATE_MACHINE_DEF)
50+
role_arn = "arn:aws:iam::123456789012:role/DummyRole"
51+
52+
response = self.client.create_state_machine(
53+
name=name,
54+
definition=definition_json,
55+
roleArn=role_arn,
56+
)
57+
return response["stateMachineArn"]
58+
59+
def create_activity_and_get_arn(self, name: str = "TestActivity") -> str:
60+
"""
61+
Create an activity in mocked Step Functions and return its ARN.
62+
"""
63+
response = self.client.create_activity(
64+
name=name,
65+
)
66+
return response["activityArn"]
67+
68+
@mock_aws
69+
def test_sfns_create_state_machine(self):
70+
state_machine_arn = self.create_state_machine_and_get_arn()
71+
spans = self.memory_exporter.get_finished_spans()
72+
assert spans
73+
self.assertEqual(len(spans), 1)
74+
span = spans[0]
75+
self.assertEqual(
76+
span.attributes[AWS_STEP_FUNCTIONS_STATE_MACHINE_ARN],
77+
state_machine_arn,
78+
)
79+
80+
@mock_aws
81+
def test_sfns_describe_state_machine(self):
82+
state_machine_arn = self.create_state_machine_and_get_arn()
83+
self.client.describe_state_machine(stateMachineArn=state_machine_arn)
84+
85+
spans = self.memory_exporter.get_finished_spans()
86+
assert spans
87+
self.assertEqual(len(spans), 2)
88+
span = spans[1]
89+
self.assertEqual(
90+
span.attributes[AWS_STEP_FUNCTIONS_STATE_MACHINE_ARN],
91+
state_machine_arn,
92+
)
93+
94+
@mock_aws
95+
def test_sfns_create_activity(self):
96+
activity_arn = self.create_activity_and_get_arn()
97+
spans = self.memory_exporter.get_finished_spans()
98+
assert spans
99+
self.assertEqual(len(spans), 1)
100+
span = spans[0]
101+
self.assertEqual(
102+
span.attributes[AWS_STEP_FUNCTIONS_ACTIVITY_ARN],
103+
activity_arn,
104+
)
105+
106+
@mock_aws
107+
def test_sfns_describe_activity(self):
108+
activity_arn = self.create_activity_and_get_arn()
109+
self.client.describe_activity(activityArn=activity_arn)
110+
spans = self.memory_exporter.get_finished_spans()
111+
assert spans
112+
self.assertEqual(len(spans), 2)
113+
span = spans[1]
114+
self.assertEqual(
115+
span.attributes[AWS_STEP_FUNCTIONS_ACTIVITY_ARN],
116+
activity_arn,
117+
)

instrumentation/opentelemetry-instrumentation-django/tests/test_middleware_asgi.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ async def test_templated_route_get(self):
174174
self.assertEqual(span.attributes[SpanAttributes.HTTP_METHOD], "GET")
175175
self.assertEqual(
176176
span.attributes[SpanAttributes.HTTP_URL],
177-
"http://127.0.0.1/route/2020/template/",
177+
"http://testserver/route/2020/template/",
178178
)
179179
self.assertEqual(
180180
span.attributes[SpanAttributes.HTTP_ROUTE],
@@ -219,7 +219,7 @@ async def test_templated_route_get_both_semconv(self):
219219
self.assertEqual(span.attributes[SpanAttributes.HTTP_METHOD], "GET")
220220
self.assertEqual(
221221
span.attributes[SpanAttributes.HTTP_URL],
222-
"http://127.0.0.1/route/2020/template/",
222+
"http://testserver/route/2020/template/",
223223
)
224224
self.assertEqual(span.attributes[SpanAttributes.HTTP_SCHEME], "http")
225225
self.assertEqual(span.attributes[SpanAttributes.HTTP_STATUS_CODE], 200)
@@ -248,7 +248,7 @@ async def test_traced_get(self):
248248
self.assertEqual(span.attributes[SpanAttributes.HTTP_METHOD], "GET")
249249
self.assertEqual(
250250
span.attributes[SpanAttributes.HTTP_URL],
251-
"http://127.0.0.1/traced/",
251+
"http://testserver/traced/",
252252
)
253253
self.assertEqual(
254254
span.attributes[SpanAttributes.HTTP_ROUTE], "^traced/"
@@ -289,7 +289,7 @@ async def test_traced_get_both_semconv(self):
289289
self.assertEqual(span.attributes[SpanAttributes.HTTP_METHOD], "GET")
290290
self.assertEqual(
291291
span.attributes[SpanAttributes.HTTP_URL],
292-
"http://127.0.0.1/traced/",
292+
"http://testserver/traced/",
293293
)
294294
self.assertEqual(span.attributes[SpanAttributes.HTTP_SCHEME], "http")
295295
self.assertEqual(span.attributes[SpanAttributes.HTTP_STATUS_CODE], 200)
@@ -328,7 +328,7 @@ async def test_traced_post(self):
328328
self.assertEqual(span.attributes[SpanAttributes.HTTP_METHOD], "POST")
329329
self.assertEqual(
330330
span.attributes[SpanAttributes.HTTP_URL],
331-
"http://127.0.0.1/traced/",
331+
"http://testserver/traced/",
332332
)
333333
self.assertEqual(
334334
span.attributes[SpanAttributes.HTTP_ROUTE], "^traced/"
@@ -369,7 +369,7 @@ async def test_traced_post_both_semconv(self):
369369
self.assertEqual(span.attributes[SpanAttributes.HTTP_METHOD], "POST")
370370
self.assertEqual(
371371
span.attributes[SpanAttributes.HTTP_URL],
372-
"http://127.0.0.1/traced/",
372+
"http://testserver/traced/",
373373
)
374374
self.assertEqual(span.attributes[SpanAttributes.HTTP_SCHEME], "http")
375375
self.assertEqual(span.attributes[SpanAttributes.HTTP_STATUS_CODE], 200)
@@ -396,7 +396,7 @@ async def test_error(self):
396396
self.assertEqual(span.attributes[SpanAttributes.HTTP_METHOD], "GET")
397397
self.assertEqual(
398398
span.attributes[SpanAttributes.HTTP_URL],
399-
"http://127.0.0.1/error/",
399+
"http://testserver/error/",
400400
)
401401
self.assertEqual(span.attributes[SpanAttributes.HTTP_ROUTE], "^error/")
402402
self.assertEqual(span.attributes[SpanAttributes.HTTP_SCHEME], "http")
@@ -450,7 +450,7 @@ async def test_error_both_semconv(self):
450450
self.assertEqual(span.attributes[SpanAttributes.HTTP_METHOD], "GET")
451451
self.assertEqual(
452452
span.attributes[SpanAttributes.HTTP_URL],
453-
"http://127.0.0.1/error/",
453+
"http://testserver/error/",
454454
)
455455
self.assertEqual(span.attributes[SpanAttributes.HTTP_ROUTE], "^error/")
456456
self.assertEqual(span.attributes[SpanAttributes.HTTP_SCHEME], "http")

0 commit comments

Comments
 (0)