Skip to content

Commit 79ab08c

Browse files
authored
Merge branch 'main' into metrics-instrumentation-celery
2 parents 4f1a7b5 + 0417141 commit 79ab08c

File tree

25 files changed

+664
-416
lines changed

25 files changed

+664
-416
lines changed

Diff for: .github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
- 'release/*'
77
pull_request:
88
env:
9-
CORE_REPO_SHA: d0bb12b34b0c487198c935001636b6163485a50f
9+
CORE_REPO_SHA: 2d1f0b9f5fce62549d1338882f37b91b95881c75
1010

1111
jobs:
1212
build:

Diff for: CHANGELOG.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
- Add metric instrumentation for celery
1111
([#1679](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1679))
1212

13+
- Fix exception in Urllib3 when dealing with filelike body.
14+
([#1399](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1399))
15+
16+
### Added
17+
18+
- Add connection attributes to sqlalchemy connect span
19+
([#1608](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1608))
20+
- Add support for enabling Redis sanitization from environment variable
21+
([#1690](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1690))
22+
23+
### Fixed
24+
25+
- Fix Flask instrumentation to only close the span if it was created by the same thread.
26+
([#1654](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1654))
27+
1328
## Version 1.16.0/0.37b0 (2023-02-17)
1429

1530
### Added
1631

1732
- Support `aio_pika` 9.x (([#1670](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1670])
1833
- `opentelemetry-instrumentation-redis` Add `sanitize_query` config option to allow query sanitization. ([#1572](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1572))
19-
- `opentelemetry-instrumentation-elasticsearch` Add optional db.statement query sanitization.
34+
- `opentelemetry-instrumentation-elasticsearch` Add optional db.statement query sanitization.
2035
([#1598](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1598))
2136
- `opentelemetry-instrumentation-celery` Record exceptions as events on the span.
2237
([#1573](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1573))

Diff for: docs-requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ aiopg>=0.13.0,<1.3.0
2424
asyncpg>=0.12.0
2525
boto~=2.0
2626
botocore~=1.0
27+
boto3~=1.0
2728
celery>=4.0
2829
confluent-kafka>= 1.8.2,< 2.0.0
2930
elasticsearch>=2.0,<9.0

Diff for: docs/conf.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def getlistcfg(strval):
126126
]
127127

128128

129-
ignore_categories = ["py-class", "py-func", "py-exc", "any"]
129+
ignore_categories = ["py-class", "py-func", "py-exc", "py-obj", "any"]
130130

131131
for category in ignore_categories:
132132
if category in mcfg:

Diff for: docs/instrumentation/boto3sqs/boto3sqs.rst

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.. include:: ../../../instrumentation/opentelemetry-instrumentation-boto3sqs/README.rst
2+
3+
.. automodule:: opentelemetry.instrumentation.boto3sqs
4+
:members:
5+
:undoc-members:
6+
:show-inheritance:

Diff for: docs/nitpick-exceptions.ini

+11-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ py-class=
1313
opentelemetry.sdk.trace.id_generator.IdGenerator
1414
opentelemetry.instrumentation.confluent_kafka.ProxiedProducer
1515
opentelemetry.instrumentation.confluent_kafka.ProxiedConsumer
16+
opentelemetry.instrumentation.instrumentor.BaseInstrumentor
1617
; - AwsXRayIdGenerator
1718
TextMapPropagator
1819
CarrierT
@@ -54,7 +55,16 @@ any=
5455
; - instrumentation.*
5556
Setter
5657
httpx
57-
;
58+
instrument
59+
__iter__
60+
list.__iter__
61+
__getitem__
62+
list.__getitem__
63+
SQS.ReceiveMessage
64+
65+
py-obj=
66+
opentelemetry.propagators.textmap.CarrierT
67+
5868
py-func=
5969
poll
6070
flush

Diff for: exporter/opentelemetry-exporter-prometheus-remote-write/example/Dockerfile

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ WORKDIR /code
66
COPY . .
77

88
RUN pip install -e .
9-
RUN pip install -r ./examples/requirements.txt
9+
RUN pip install -r ./example/requirements.txt
1010

11-
CMD ["python", "./examples/sampleapp.py"]
11+
CMD ["python", "./example/sampleapp.py"]

Diff for: exporter/opentelemetry-exporter-prometheus-remote-write/example/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ data
1414
*Users do not need to install Python as the app will be run in the Docker Container*
1515

1616
## Instructions
17-
1. Run `docker-compose up -d` in the the `examples/` directory
17+
1. Run `docker-compose up -d` in the the `example/` directory
1818

1919
The `-d` flag causes all services to run in detached mode and frees up your
2020
terminal session. This also causes no logs to show up. Users can attach themselves to the service's logs manually using `docker logs ${CONTAINER_ID} --follow`
@@ -39,4 +39,4 @@ terminal session. This also causes no logs to show up. Users can attach themselv
3939
* Click the refresh button and data should show up on the graph
4040

4141
6. Shutdown the services when finished
42-
* Run `docker-compose down` in the examples directory
42+
* Run `docker-compose down` in the example directory

Diff for: exporter/opentelemetry-exporter-prometheus-remote-write/example/docker-compose.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@ services:
3030
sample_app:
3131
build:
3232
context: ../
33-
dockerfile: ./examples/Dockerfile
33+
dockerfile: ./example/Dockerfile

Diff for: instrumentation/opentelemetry-instrumentation-boto3sqs/src/opentelemetry/instrumentation/boto3sqs/__init__.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@
1616
1717
.. _boto3sqs: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sqs.html
1818
19-
2019
Usage
2120
-----
2221
23-
.. code:: python
22+
.. code-block:: python
2423
2524
import boto3
2625
from opentelemetry.instrumentation.boto3sqs import Boto3SQSInstrumentor
2726
28-
2927
Boto3SQSInstrumentor().instrument()
28+
29+
---
3030
"""
3131
import logging
3232
from typing import Any, Collection, Dict, Generator, List, Mapping, Optional

Diff for: instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/__init__.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,8 @@ def response_hook(span: Span, status: str, response_headers: List):
238238
API
239239
---
240240
"""
241-
242241
from logging import getLogger
242+
from threading import get_ident
243243
from time import time_ns
244244
from timeit import default_timer
245245
from typing import Collection
@@ -265,6 +265,7 @@ def response_hook(span: Span, status: str, response_headers: List):
265265
_ENVIRON_STARTTIME_KEY = "opentelemetry-flask.starttime_key"
266266
_ENVIRON_SPAN_KEY = "opentelemetry-flask.span_key"
267267
_ENVIRON_ACTIVATION_KEY = "opentelemetry-flask.activation_key"
268+
_ENVIRON_THREAD_ID_KEY = "opentelemetry-flask.thread_id_key"
268269
_ENVIRON_TOKEN = "opentelemetry-flask.token"
269270

270271
_excluded_urls_from_env = get_excluded_urls("FLASK")
@@ -398,6 +399,7 @@ def _before_request():
398399
activation = trace.use_span(span, end_on_exit=True)
399400
activation.__enter__() # pylint: disable=E1101
400401
flask_request_environ[_ENVIRON_ACTIVATION_KEY] = activation
402+
flask_request_environ[_ENVIRON_THREAD_ID_KEY] = get_ident()
401403
flask_request_environ[_ENVIRON_SPAN_KEY] = span
402404
flask_request_environ[_ENVIRON_TOKEN] = token
403405

@@ -437,10 +439,17 @@ def _teardown_request(exc):
437439
return
438440

439441
activation = flask.request.environ.get(_ENVIRON_ACTIVATION_KEY)
440-
if not activation:
442+
thread_id = flask.request.environ.get(_ENVIRON_THREAD_ID_KEY)
443+
if not activation or thread_id != get_ident():
441444
# This request didn't start a span, maybe because it was created in
442445
# a way that doesn't run `before_request`, like when it is created
443446
# with `app.test_request_context`.
447+
#
448+
# Similarly, check the thread_id against the current thread to ensure
449+
# tear down only happens on the original thread. This situation can
450+
# arise if the original thread handling the request spawn children
451+
# threads and then uses something like copy_current_request_context
452+
# to copy the request context.
444453
return
445454
if exc is None:
446455
activation.__exit__(None, None, None)

Diff for: instrumentation/opentelemetry-instrumentation-flask/tests/base_test.py

+23
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from concurrent.futures import ThreadPoolExecutor, as_completed
16+
from random import randint
17+
1518
import flask
1619
from werkzeug.test import Client
1720
from werkzeug.wrappers import Response
@@ -34,6 +37,25 @@ def _sqlcommenter_endpoint():
3437
)
3538
return sqlcommenter_flask_values
3639

40+
@staticmethod
41+
def _multithreaded_endpoint(count):
42+
def do_random_stuff():
43+
@flask.copy_current_request_context
44+
def inner():
45+
return randint(0, 100)
46+
47+
return inner
48+
49+
executor = ThreadPoolExecutor(count)
50+
futures = []
51+
for _ in range(count):
52+
futures.append(executor.submit(do_random_stuff()))
53+
numbers = []
54+
for future in as_completed(futures):
55+
numbers.append(future.result())
56+
57+
return " ".join([str(i) for i in numbers])
58+
3759
@staticmethod
3860
def _custom_response_headers():
3961
resp = flask.Response("test response")
@@ -61,6 +83,7 @@ def excluded2_endpoint():
6183
# pylint: disable=no-member
6284
self.app.route("/hello/<int:helloid>")(self._hello_endpoint)
6385
self.app.route("/sqlcommenter")(self._sqlcommenter_endpoint)
86+
self.app.route("/multithreaded")(self._multithreaded_endpoint)
6487
self.app.route("/excluded/<int:helloid>")(self._hello_endpoint)
6588
self.app.route("/excluded")(excluded_endpoint)
6689
self.app.route("/excluded2")(excluded2_endpoint)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
15+
import flask
16+
from werkzeug.test import Client
17+
from werkzeug.wrappers import Response
18+
19+
from opentelemetry.instrumentation.flask import FlaskInstrumentor
20+
from opentelemetry.test.wsgitestutil import WsgiTestBase
21+
22+
# pylint: disable=import-error
23+
from .base_test import InstrumentationTest
24+
25+
26+
class TestMultiThreading(InstrumentationTest, WsgiTestBase):
27+
def setUp(self):
28+
super().setUp()
29+
FlaskInstrumentor().instrument()
30+
self.app = flask.Flask(__name__)
31+
self._common_initialization()
32+
33+
def tearDown(self):
34+
super().tearDown()
35+
with self.disable_logging():
36+
FlaskInstrumentor().uninstrument()
37+
38+
def test_multithreaded(self):
39+
"""Test that instrumentation tear down does not blow up
40+
when the request thread spawn children threads and the request
41+
context is copied to the children threads
42+
"""
43+
self.app = flask.Flask(__name__)
44+
self.app.route("/multithreaded/<int:count>")(
45+
self._multithreaded_endpoint
46+
)
47+
client = Client(self.app, Response)
48+
count = 5
49+
resp = client.get(f"/multithreaded/{count}")
50+
self.assertEqual(200, resp.status_code)
51+
# Should return the specified number of random integers
52+
self.assertEqual(count, len(resp.text.split(" ")))

Diff for: instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/__init__.py

+29-1
Original file line numberDiff line numberDiff line change
@@ -88,17 +88,37 @@ def response_hook(span, instance, response):
8888
client = redis.StrictRedis(host="localhost", port=6379)
8989
client.get("my-key")
9090
91+
Configuration
92+
-------------
93+
94+
Query sanitization
95+
******************
96+
To enable query sanitization with an environment variable, set
97+
``OTEL_PYTHON_INSTRUMENTATION_SANITIZE_REDIS`` to "true".
98+
99+
For example,
100+
101+
::
102+
103+
export OTEL_PYTHON_INSTRUMENTATION_SANITIZE_REDIS="true"
104+
105+
will result in traced queries like "SET ? ?".
106+
91107
API
92108
---
93109
"""
94110
import typing
111+
from os import environ
95112
from typing import Any, Collection
96113

97114
import redis
98115
from wrapt import wrap_function_wrapper
99116

100117
from opentelemetry import trace
101118
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
119+
from opentelemetry.instrumentation.redis.environment_variables import (
120+
OTEL_PYTHON_INSTRUMENTATION_SANITIZE_REDIS,
121+
)
102122
from opentelemetry.instrumentation.redis.package import _instruments
103123
from opentelemetry.instrumentation.redis.util import (
104124
_extract_conn_attributes,
@@ -287,7 +307,15 @@ def _instrument(self, **kwargs):
287307
tracer,
288308
request_hook=kwargs.get("request_hook"),
289309
response_hook=kwargs.get("response_hook"),
290-
sanitize_query=kwargs.get("sanitize_query", False),
310+
sanitize_query=kwargs.get(
311+
"sanitize_query",
312+
environ.get(
313+
OTEL_PYTHON_INSTRUMENTATION_SANITIZE_REDIS, "false"
314+
)
315+
.lower()
316+
.strip()
317+
== "true",
318+
),
291319
)
292320

293321
def _uninstrument(self, **kwargs):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
15+
OTEL_PYTHON_INSTRUMENTATION_SANITIZE_REDIS = (
16+
"OTEL_PYTHON_INSTRUMENTATION_SANITIZE_REDIS"
17+
)

Diff for: instrumentation/opentelemetry-instrumentation-redis/tests/test_redis.py

+26
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,32 @@ def test_query_sanitizer_enabled(self):
168168
span = spans[0]
169169
self.assertEqual(span.attributes.get("db.statement"), "SET ? ?")
170170

171+
def test_query_sanitizer_enabled_env(self):
172+
redis_client = redis.Redis()
173+
connection = redis.connection.Connection()
174+
redis_client.connection = connection
175+
176+
RedisInstrumentor().uninstrument()
177+
178+
env_patch = mock.patch.dict(
179+
"os.environ",
180+
{"OTEL_PYTHON_INSTRUMENTATION_SANITIZE_REDIS": "true"},
181+
)
182+
env_patch.start()
183+
RedisInstrumentor().instrument(
184+
tracer_provider=self.tracer_provider,
185+
)
186+
187+
with mock.patch.object(redis_client, "connection"):
188+
redis_client.set("key", "value")
189+
190+
spans = self.memory_exporter.get_finished_spans()
191+
self.assertEqual(len(spans), 1)
192+
193+
span = spans[0]
194+
self.assertEqual(span.attributes.get("db.statement"), "SET ? ?")
195+
env_patch.stop()
196+
171197
def test_query_sanitizer_disabled(self):
172198
redis_client = redis.Redis()
173199
connection = redis.connection.Connection()

0 commit comments

Comments
 (0)