Skip to content

Commit 2b980c0

Browse files
committed
2 parents 3c58d85 + 50e7b1b commit 2b980c0

File tree

10 files changed

+386
-98
lines changed

10 files changed

+386
-98
lines changed

.github/workflows/close-stale-issues.yml

-18
This file was deleted.

CHANGELOG.md

+13
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
- `opentelemetry-sdk-extension-aws` Release AWS Python SDK Extension as 1.0.0
1010
([#667](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/667))
1111

12+
### Added
13+
- `opentelemetry-instrumentation-elasticsearch` Added `response_hook` and `request_hook` callbacks
14+
([#670](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/670))
15+
16+
### Added
17+
- `opentelemetry-instrumentation-redis` added request_hook and response_hook callbacks passed as arguments to the instrument method.
18+
([#669](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/669))
19+
1220
### Changed
1321
- `opentelemetry-instrumentation-botocore` Unpatch botocore Endpoint.prepare_request on uninstrument
1422
([#664](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/664))
1523
- `opentelemetry-instrumentation-botocore` Fix span injection for lambda invoke
1624
([#663](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/663))
1725

26+
### Changed
27+
28+
- `opentelemetry-instrumentation-urllib3` Updated `_RequestHookT` with two additional fields - the request body and the request headers
29+
([#660](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/660))
30+
1831
## [1.5.0-0.24b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.5.0-0.24b0) - 2021-08-26
1932

2033
### Added

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

+50-2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,41 @@
4444
.. code-block:: python
4545
4646
ElasticsearchInstrumentor("my-custom-prefix").instrument()
47+
48+
49+
The `instrument` method accepts the following keyword args:
50+
51+
tracer_provider (TracerProvider) - an optional tracer provider
52+
request_hook (Callable) - a function with extra user-defined logic to be performed before performing the request
53+
this function signature is:
54+
def request_hook(span: Span, method: str, url: str, kwargs)
55+
response_hook (Callable) - a function with extra user-defined logic to be performed after performing the request
56+
this function signature is:
57+
def response_hook(span: Span, response: dict)
58+
59+
for example:
60+
61+
.. code: python
62+
63+
from opentelemetry.instrumentation.elasticsearch import ElasticsearchInstrumentor
64+
import elasticsearch
65+
66+
def request_hook(span, method, url, kwargs):
67+
if span and span.is_recording():
68+
span.set_attribute("custom_user_attribute_from_request_hook", "some-value")
69+
70+
def response_hook(span, response):
71+
if span and span.is_recording():
72+
span.set_attribute("custom_user_attribute_from_response_hook", "some-value")
73+
74+
# instrument elasticsearch with request and response hooks
75+
ElasticsearchInstrumentor().instrument(request_hook=request_hook, response_hook=response_hook)
76+
77+
# Using elasticsearch as normal now will automatically generate spans,
78+
# including user custom attributes added from the hooks
79+
es = elasticsearch.Elasticsearch()
80+
es.index(index='my-index', doc_type='my-type', id=1, body={'my': 'data', 'timestamp': datetime.now()})
81+
es.get(index='my-index', doc_type='my-type', id=1)
4782
"""
4883

4984
from logging import getLogger
@@ -97,17 +132,23 @@ def _instrument(self, **kwargs):
97132
"""
98133
tracer_provider = kwargs.get("tracer_provider")
99134
tracer = get_tracer(__name__, __version__, tracer_provider)
135+
request_hook = kwargs.get("request_hook")
136+
response_hook = kwargs.get("response_hook")
100137
_wrap(
101138
elasticsearch,
102139
"Transport.perform_request",
103-
_wrap_perform_request(tracer, self._span_name_prefix),
140+
_wrap_perform_request(
141+
tracer, self._span_name_prefix, request_hook, response_hook
142+
),
104143
)
105144

106145
def _uninstrument(self, **kwargs):
107146
unwrap(elasticsearch.Transport, "perform_request")
108147

109148

110-
def _wrap_perform_request(tracer, span_name_prefix):
149+
def _wrap_perform_request(
150+
tracer, span_name_prefix, request_hook=None, response_hook=None
151+
):
111152
# pylint: disable=R0912
112153
def wrapper(wrapped, _, args, kwargs):
113154
method = url = None
@@ -127,6 +168,10 @@ def wrapper(wrapped, _, args, kwargs):
127168
with tracer.start_as_current_span(
128169
op_name, kind=SpanKind.CLIENT,
129170
) as span:
171+
172+
if callable(request_hook):
173+
request_hook(span, method, url, kwargs)
174+
130175
if span.is_recording():
131176
attributes = {
132177
SpanAttributes.DB_SYSTEM: "elasticsearch",
@@ -150,6 +195,9 @@ def wrapper(wrapped, _, args, kwargs):
150195
"elasticsearch.{0}".format(member),
151196
str(rv[member]),
152197
)
198+
199+
if callable(response_hook):
200+
response_hook(span, rv)
153201
return rv
154202

155203
return wrapper

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

+99-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
14+
import json
1515
import os
1616
import threading
1717
from ast import literal_eval
@@ -316,3 +316,101 @@ def test_dsl_index(self, request_mock):
316316
"title": "About searching",
317317
},
318318
)
319+
320+
def test_request_hook(self, request_mock):
321+
request_hook_method_attribute = "request_hook.method"
322+
request_hook_url_attribute = "request_hook.url"
323+
request_hook_kwargs_attribute = "request_hook.kwargs"
324+
325+
def request_hook(span, method, url, kwargs):
326+
327+
attributes = {
328+
request_hook_method_attribute: method,
329+
request_hook_url_attribute: url,
330+
request_hook_kwargs_attribute: json.dumps(kwargs),
331+
}
332+
333+
if span and span.is_recording():
334+
span.set_attributes(attributes)
335+
336+
ElasticsearchInstrumentor().uninstrument()
337+
ElasticsearchInstrumentor().instrument(request_hook=request_hook)
338+
339+
request_mock.return_value = (
340+
1,
341+
{},
342+
'{"found": false, "timed_out": true, "took": 7}',
343+
)
344+
es = Elasticsearch()
345+
index = "test-index"
346+
doc_type = "tweet"
347+
doc_id = 1
348+
kwargs = {"params": {"test": True}}
349+
es.get(index=index, doc_type=doc_type, id=doc_id, **kwargs)
350+
351+
spans = self.get_finished_spans()
352+
353+
self.assertEqual(1, len(spans))
354+
self.assertEqual(
355+
"GET", spans[0].attributes[request_hook_method_attribute]
356+
)
357+
self.assertEqual(
358+
f"/{index}/{doc_type}/{doc_id}",
359+
spans[0].attributes[request_hook_url_attribute],
360+
)
361+
self.assertEqual(
362+
json.dumps(kwargs),
363+
spans[0].attributes[request_hook_kwargs_attribute],
364+
)
365+
366+
def test_response_hook(self, request_mock):
367+
response_attribute_name = "db.query_result"
368+
369+
def response_hook(span, response):
370+
if span and span.is_recording():
371+
span.set_attribute(
372+
response_attribute_name, json.dumps(response)
373+
)
374+
375+
ElasticsearchInstrumentor().uninstrument()
376+
ElasticsearchInstrumentor().instrument(response_hook=response_hook)
377+
378+
response_payload = {
379+
"took": 9,
380+
"timed_out": False,
381+
"_shards": {
382+
"total": 1,
383+
"successful": 1,
384+
"skipped": 0,
385+
"failed": 0,
386+
},
387+
"hits": {
388+
"total": {"value": 1, "relation": "eq"},
389+
"max_score": 0.18232156,
390+
"hits": [
391+
{
392+
"_index": "test-index",
393+
"_type": "tweet",
394+
"_id": "1",
395+
"_score": 0.18232156,
396+
"_source": {"name": "tester"},
397+
}
398+
],
399+
},
400+
}
401+
402+
request_mock.return_value = (
403+
1,
404+
{},
405+
json.dumps(response_payload),
406+
)
407+
es = Elasticsearch()
408+
es.get(index="test-index", doc_type="tweet", id=1)
409+
410+
spans = self.get_finished_spans()
411+
412+
self.assertEqual(1, len(spans))
413+
self.assertEqual(
414+
json.dumps(response_payload),
415+
spans[0].attributes[response_attribute_name],
416+
)

0 commit comments

Comments
 (0)