Skip to content

Commit 860a553

Browse files
authored
Merge branch 'main' into metrics-instrumentation-celery
2 parents 2907481 + 46e4b1d commit 860a553

File tree

15 files changed

+98
-42
lines changed

15 files changed

+98
-42
lines changed

CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
- `opentelemetry-instrumentation-system-metrics` Add `process.` prefix to `runtime.memory`, `runtime.cpu.time`, and `runtime.gc_count`. Change `runtime.memory` from count to UpDownCounter. ([#1735](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1735))
1111
- Add request and response hooks for GRPC instrumentation (client only)
1212
([#1706](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1706))
13+
- `opentelemetry-instrumentation-pymemcache` Update instrumentation to support pymemcache >4
14+
([#1764](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1764))
1315

1416
### Added
1517

@@ -19,9 +21,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1921
([#1733](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1733))
2022
- Make Django request span attributes available for `start_span`.
2123
([#1730](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1730))
24+
- Make ASGI request span attributes available for `start_span`.
25+
([#1762](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1762))
26+
- `opentelemetry-instrumentation-celery` Add support for anonymous tasks.
27+
([#1407](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1407)
28+
2229

2330
### Fixed
2431

32+
- Fix elasticsearch db.statement attribute to be sanitized by default
33+
([#1758](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1758))
2534
- Fix `AttributeError` when AWS Lambda handler receives a list event
2635
([#1738](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1738))
2736
- Fix `None does not implement middleware` error when there are no middlewares registered

instrumentation/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
| [opentelemetry-instrumentation-mysql](./opentelemetry-instrumentation-mysql) | mysql-connector-python ~= 8.0 | No
2727
| [opentelemetry-instrumentation-pika](./opentelemetry-instrumentation-pika) | pika >= 0.12.0 | No
2828
| [opentelemetry-instrumentation-psycopg2](./opentelemetry-instrumentation-psycopg2) | psycopg2 >= 2.7.3.1 | No
29-
| [opentelemetry-instrumentation-pymemcache](./opentelemetry-instrumentation-pymemcache) | pymemcache >= 1.3.5, < 4 | No
29+
| [opentelemetry-instrumentation-pymemcache](./opentelemetry-instrumentation-pymemcache) | pymemcache >= 1.3.5, < 5 | No
3030
| [opentelemetry-instrumentation-pymongo](./opentelemetry-instrumentation-pymongo) | pymongo >= 3.1, < 5.0 | No
3131
| [opentelemetry-instrumentation-pymysql](./opentelemetry-instrumentation-pymysql) | PyMySQL < 2 | No
3232
| [opentelemetry-instrumentation-pyramid](./opentelemetry-instrumentation-pyramid) | pyramid >= 1.7 | Yes

instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def test_status_codes(self):
122122
(span_status, None),
123123
{
124124
SpanAttributes.HTTP_METHOD: "GET",
125-
SpanAttributes.HTTP_URL: f"http://{host}:{port}/test-path?query=param#foobar",
125+
SpanAttributes.HTTP_URL: f"http://{host}:{port}/test-path#foobar",
126126
SpanAttributes.HTTP_STATUS_CODE: int(
127127
status_code
128128
),

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -531,15 +531,16 @@ async def __call__(self, scope, receive, send):
531531

532532
span_name, additional_attributes = self.default_span_details(scope)
533533

534+
attributes = collect_request_attributes(scope)
535+
attributes.update(additional_attributes)
534536
span, token = _start_internal_or_server_span(
535537
tracer=self.tracer,
536538
span_name=span_name,
537539
start_time=None,
538540
context_carrier=scope,
539541
context_getter=asgi_getter,
542+
attributes=attributes,
540543
)
541-
attributes = collect_request_attributes(scope)
542-
attributes.update(additional_attributes)
543544
active_requests_count_attrs = _parse_active_request_count_attrs(
544545
attributes
545546
)

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

+10-3
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,17 @@ def _trace_before_publish(self, *args, **kwargs):
198198
task = utils.retrieve_task_from_sender(kwargs)
199199
task_id = utils.retrieve_task_id_from_message(kwargs)
200200

201-
if task is None or task_id is None:
201+
if task_id is None:
202202
return
203203

204-
operation_name = f"{_TASK_APPLY_ASYNC}/{task.name}"
204+
if task is None:
205+
# task is an anonymous task send using send_task or using canvas workflow
206+
# Signatures() to send to a task not in the current processes dependency
207+
# tree
208+
task_name = kwargs.get("sender", "unknown")
209+
else:
210+
task_name = task.name
211+
operation_name = f"{_TASK_APPLY_ASYNC}/{task_name}"
205212
span = self._tracer.start_span(
206213
operation_name, kind=trace.SpanKind.PRODUCER
207214
)
@@ -210,7 +217,7 @@ def _trace_before_publish(self, *args, **kwargs):
210217
if span.is_recording():
211218
span.set_attribute(_TASK_TAG_KEY, _TASK_APPLY_ASYNC)
212219
span.set_attribute(SpanAttributes.MESSAGING_MESSAGE_ID, task_id)
213-
span.set_attribute(_TASK_NAME_KEY, task.name)
220+
span.set_attribute(_TASK_NAME_KEY, task_name)
214221
utils.set_attributes_from_context(span, kwargs)
215222

216223
activation = trace.use_span(span, end_on_exit=True)

instrumentation/opentelemetry-instrumentation-celery/src/opentelemetry/instrumentation/celery/utils.py

+2
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ def attach_span(task, task_id, span, is_publish=False):
132132
NOTE: We cannot test for this well yet, because we do not run a celery worker,
133133
and cannot run `task.apply_async()`
134134
"""
135+
if task is None:
136+
return
135137
span_dict = getattr(task, CTX_KEY, None)
136138
if span_dict is None:
137139
span_dict = {}

instrumentation/opentelemetry-instrumentation-celery/tests/test_tasks.py

+50
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def tearDown(self):
3636
CeleryInstrumentor().uninstrument()
3737
self._worker.stop()
3838
self._thread.join()
39+
CeleryInstrumentor().uninstrument()
3940

4041
def test_task(self):
4142
CeleryInstrumentor().instrument()
@@ -97,3 +98,52 @@ def test_uninstrument(self):
9798

9899
spans = self.memory_exporter.get_finished_spans()
99100
self.assertEqual(len(spans), 0)
101+
102+
103+
class TestCelerySignatureTask(TestBase):
104+
def setUp(self):
105+
super().setUp()
106+
107+
def start_app(*args, **kwargs):
108+
# Add an additional task that will not be registered with parent thread
109+
@app.task
110+
def hidden_task(num_a):
111+
return num_a * 2
112+
113+
self._worker = app.Worker(app=app, pool="solo", concurrency=1)
114+
return self._worker.start(*args, **kwargs)
115+
116+
self._thread = threading.Thread(target=start_app)
117+
self._worker = app.Worker(app=app, pool="solo", concurrency=1)
118+
self._thread.daemon = True
119+
self._thread.start()
120+
121+
def tearDown(self):
122+
super().tearDown()
123+
self._worker.stop()
124+
self._thread.join()
125+
CeleryInstrumentor().uninstrument()
126+
127+
def test_hidden_task(self):
128+
# no-op since already instrumented
129+
CeleryInstrumentor().instrument()
130+
131+
res = app.signature("tests.test_tasks.hidden_task", (2,)).apply_async()
132+
while not res.ready():
133+
time.sleep(0.05)
134+
spans = self.sorted_spans(self.memory_exporter.get_finished_spans())
135+
self.assertEqual(len(spans), 2)
136+
137+
consumer, producer = spans
138+
139+
self.assertEqual(consumer.name, "run/tests.test_tasks.hidden_task")
140+
self.assertEqual(consumer.kind, SpanKind.CONSUMER)
141+
142+
self.assertEqual(
143+
producer.name, "apply_async/tests.test_tasks.hidden_task"
144+
)
145+
self.assertEqual(producer.kind, SpanKind.PRODUCER)
146+
147+
self.assertNotEqual(consumer.parent, producer.context)
148+
self.assertEqual(consumer.parent.span_id, producer.context.span_id)
149+
self.assertEqual(consumer.context.trace_id, producer.context.trace_id)

instrumentation/opentelemetry-instrumentation-celery/tests/test_utils.py

+7
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,13 @@ def fn_task():
185185
utils.detach_span(fn_task, task_id)
186186
self.assertEqual(utils.retrieve_span(fn_task, task_id), (None, None))
187187

188+
def test_optional_task_span_attach(self):
189+
task_id = "7c6731af-9533-40c3-83a9-25b58f0d837f"
190+
span = trace._Span("name", mock.Mock(spec=trace_api.SpanContext))
191+
192+
# assert this is is a no-aop
193+
self.assertIsNone(utils.attach_span(None, task_id, span))
194+
188195
def test_span_delete_empty(self):
189196
# ensure detach_span doesn't raise an exception if span is not present
190197
@self.app.task

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

+3-8
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
4545
The instrument() method accepts the following keyword args:
4646
tracer_provider (TracerProvider) - an optional tracer provider
47-
sanitize_query (bool) - an optional query sanitization flag
4847
request_hook (Callable) - a function with extra user-defined logic to be performed before performing the request
4948
this function signature is:
5049
def request_hook(span: Span, method: str, url: str, kwargs)
@@ -138,13 +137,11 @@ def _instrument(self, **kwargs):
138137
tracer = get_tracer(__name__, __version__, tracer_provider)
139138
request_hook = kwargs.get("request_hook")
140139
response_hook = kwargs.get("response_hook")
141-
sanitize_query = kwargs.get("sanitize_query", False)
142140
_wrap(
143141
elasticsearch,
144142
"Transport.perform_request",
145143
_wrap_perform_request(
146144
tracer,
147-
sanitize_query,
148145
self._span_name_prefix,
149146
request_hook,
150147
response_hook,
@@ -163,7 +160,6 @@ def _uninstrument(self, **kwargs):
163160

164161
def _wrap_perform_request(
165162
tracer,
166-
sanitize_query,
167163
span_name_prefix,
168164
request_hook=None,
169165
response_hook=None,
@@ -225,10 +221,9 @@ def wrapper(wrapped, _, args, kwargs):
225221
if method:
226222
attributes["elasticsearch.method"] = method
227223
if body:
228-
statement = str(body)
229-
if sanitize_query:
230-
statement = sanitize_body(body)
231-
attributes[SpanAttributes.DB_STATEMENT] = statement
224+
attributes[SpanAttributes.DB_STATEMENT] = sanitize_body(
225+
body
226+
)
232227
if params:
233228
attributes["elasticsearch.params"] = str(params)
234229
if doc_id:

instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/utils.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ def _flatten_dict(d, parent_key=""):
2929
items = []
3030
for k, v in d.items():
3131
new_key = parent_key + "." + k if parent_key else k
32-
if isinstance(v, dict):
32+
# recursive call _flatten_dict for a non-empty dict value
33+
if isinstance(v, dict) and v:
3334
items.extend(_flatten_dict(v, new_key).items())
3435
else:
3536
items.append((new_key, v))

instrumentation/opentelemetry-instrumentation-elasticsearch/tests/test_elasticsearch.py

+2-19
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,7 @@ class TestElasticsearchIntegration(TestBase):
5858
"elasticsearch.url": "/test-index/_search",
5959
"elasticsearch.method": helpers.dsl_search_method,
6060
"elasticsearch.target": "test-index",
61-
SpanAttributes.DB_STATEMENT: str(
62-
{"query": {"bool": {"filter": [{"term": {"author": "testing"}}]}}}
63-
),
61+
SpanAttributes.DB_STATEMENT: str({"query": {"bool": {"filter": "?"}}}),
6462
}
6563

6664
create_attributes = {
@@ -264,18 +262,6 @@ def test_dsl_search(self, request_mock):
264262
)
265263

266264
def test_dsl_search_sanitized(self, request_mock):
267-
# Reset instrumentation to use sanitized query (default)
268-
ElasticsearchInstrumentor().uninstrument()
269-
ElasticsearchInstrumentor().instrument(sanitize_query=True)
270-
271-
# update expected attributes to match sanitized query
272-
sanitized_search_attributes = self.search_attributes.copy()
273-
sanitized_search_attributes.update(
274-
{
275-
SpanAttributes.DB_STATEMENT: "{'query': {'bool': {'filter': '?'}}}"
276-
}
277-
)
278-
279265
request_mock.return_value = (1, {}, '{"hits": {"hits": []}}')
280266
client = Elasticsearch()
281267
search = Search(using=client, index="test-index").filter(
@@ -289,7 +275,7 @@ def test_dsl_search_sanitized(self, request_mock):
289275
self.assertIsNotNone(span.end_time)
290276
self.assertEqual(
291277
span.attributes,
292-
sanitized_search_attributes,
278+
self.search_attributes,
293279
)
294280

295281
def test_dsl_create(self, request_mock):
@@ -320,9 +306,6 @@ def test_dsl_create(self, request_mock):
320306
)
321307

322308
def test_dsl_create_sanitized(self, request_mock):
323-
# Reset instrumentation to explicitly use sanitized query
324-
ElasticsearchInstrumentor().uninstrument()
325-
ElasticsearchInstrumentor().instrument(sanitize_query=True)
326309
request_mock.return_value = (1, {}, {})
327310
client = Elasticsearch()
328311
Article.init(using=client)

instrumentation/opentelemetry-instrumentation-pymemcache/pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ dependencies = [
3333

3434
[project.optional-dependencies]
3535
instruments = [
36-
"pymemcache >= 1.3.5, < 4",
36+
"pymemcache >= 1.3.5, < 5",
3737
]
3838
test = [
3939
"opentelemetry-instrumentation-pymemcache[instruments]",

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@
1313
# limitations under the License.
1414

1515

16-
_instruments = ("pymemcache >= 1.3.5, < 4",)
16+
_instruments = ("pymemcache >= 1.3.5, < 5",)

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
"instrumentation": "opentelemetry-instrumentation-psycopg2==0.39b0.dev",
106106
},
107107
"pymemcache": {
108-
"library": "pymemcache >= 1.3.5, < 4",
108+
"library": "pymemcache >= 1.3.5, < 5",
109109
"instrumentation": "opentelemetry-instrumentation-pymemcache==0.39b0.dev",
110110
},
111111
"pymongo": {

tox.ini

+5-4
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,8 @@ envlist =
122122
; ext-psycopg2 intentionally excluded from pypy3
123123

124124
; opentelemetry-instrumentation-pymemcache
125-
py3{7,8,9,10,11}-test-instrumentation-pymemcache{135,200,300,342}
126-
pypy3-test-instrumentation-pymemcache{135,200,300,342}
125+
py3{7,8,9,10,11}-test-instrumentation-pymemcache{135,200,300,342,400}
126+
pypy3-test-instrumentation-pymemcache{135,200,300,342,400}
127127

128128
; opentelemetry-instrumentation-pymongo
129129
py3{7,8,9,10,11}-test-instrumentation-pymongo
@@ -267,6 +267,7 @@ deps =
267267
pymemcache200: pymemcache >2.0.0,<3.0.0
268268
pymemcache300: pymemcache >3.0.0,<3.4.2
269269
pymemcache342: pymemcache ==3.4.2
270+
pymemcache400: pymemcache ==4.0.0
270271
httpx18: httpx>=0.18.0,<0.19.0
271272
httpx18: respx~=0.17.0
272273
httpx21: httpx>=0.19.0
@@ -311,7 +312,7 @@ changedir =
311312
test-instrumentation-pika{0,1}: instrumentation/opentelemetry-instrumentation-pika/tests
312313
test-instrumentation-aio-pika{7,8,9}: instrumentation/opentelemetry-instrumentation-aio-pika/tests
313314
test-instrumentation-psycopg2: instrumentation/opentelemetry-instrumentation-psycopg2/tests
314-
test-instrumentation-pymemcache{135,200,300,342}: instrumentation/opentelemetry-instrumentation-pymemcache/tests
315+
test-instrumentation-pymemcache{135,200,300,342,400}: instrumentation/opentelemetry-instrumentation-pymemcache/tests
315316
test-instrumentation-pymongo: instrumentation/opentelemetry-instrumentation-pymongo/tests
316317
test-instrumentation-pymysql: instrumentation/opentelemetry-instrumentation-pymysql/tests
317318
test-instrumentation-pyramid: instrumentation/opentelemetry-instrumentation-pyramid/tests
@@ -390,7 +391,7 @@ commands_pre =
390391

391392
mysql: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-dbapi {toxinidir}/instrumentation/opentelemetry-instrumentation-mysql[test]
392393

393-
pymemcache{135,200,300,342}: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-pymemcache[test]
394+
pymemcache{135,200,300,342,400}: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-pymemcache[test]
394395

395396
pymongo: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-pymongo[test]
396397

0 commit comments

Comments
 (0)