-
Notifications
You must be signed in to change notification settings - Fork 652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Porting redis instrumentation from contrib repo #595
Changes from 10 commits
f91ce7c
88ea6f1
8799497
4157cbf
1e48c7d
d0af1ec
6809d9a
64e1d72
2e71d4f
a98fb47
43ae8b0
8933c0b
9b7e311
5e8172d
a33287b
5f40067
2bcadf5
65c6cb3
3a1d1dd
5fd98fe
ffab5f9
039ad13
75a9bf4
28e6d7c
9913c1c
1b978a5
5d74317
d843cad
e064dc1
2712732
8907032
5ee71de
2faca48
f372d1e
87eceb6
a10916b
0757069
e4d9084
b4d810e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
OpenTelemetry Redis Instrumentation | ||
=================================== | ||
|
||
.. automodule:: opentelemetry.instrumentation.redis | ||
:members: | ||
:undoc-members: | ||
:show-inheritance: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
# Copyright The OpenTelemetry Authors | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
import unittest | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
import redis | ||
|
||
from opentelemetry import trace | ||
from opentelemetry.instrumentation.redis.patch import patch, unpatch | ||
from opentelemetry.sdk.trace import TracerProvider | ||
from opentelemetry.sdk.trace.export import SimpleExportSpanProcessor | ||
from opentelemetry.sdk.trace.export.in_memory_span_exporter import ( | ||
InMemorySpanExporter, | ||
) | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
from opentelemetry.test.test_base import TestBase | ||
|
||
|
||
class TestRedisPatch(TestBase): | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
TEST_SERVICE = "redis" | ||
TEST_PORT = 6379 | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def setUp(self): | ||
patch() | ||
self.redis_client = redis.Redis(port=self.TEST_PORT) | ||
self.redis_client.flushall() | ||
super().setUp() | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def tearDown(self): | ||
unpatch() | ||
|
||
def test_long_command(self): | ||
self.redis_client.mget(*range(1000)) | ||
|
||
spans = self.memory_exporter.get_finished_spans() | ||
self.assertEqual(len(spans), 1) | ||
span = spans[0] | ||
self.assertEqual(span.attributes["service"], self.TEST_SERVICE) | ||
self.assertEqual(span.name, "redis.command") | ||
self.assertIs( | ||
span.status.canonical_code, trace.status.StatusCanonicalCode.OK | ||
) | ||
|
||
self.assertEqual(span.attributes.get("db.instance"), 0) | ||
self.assertEqual( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it worth refactoring some of these assertions into a utility method? there's a lot of identical code with regards to db.instance / url. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good call, refactored. |
||
span.attributes.get("db.url"), "redis://localhost:6379" | ||
) | ||
|
||
self.assertTrue( | ||
span.attributes.get("db.statement").startswith("MGET 0 1 2 3") | ||
) | ||
self.assertTrue(span.attributes.get("db.statement").endswith("...")) | ||
|
||
def test_basics(self): | ||
self.assertIsNone(self.redis_client.get("cheese")) | ||
spans = self.memory_exporter.get_finished_spans() | ||
self.assertEqual(len(spans), 1) | ||
span = spans[0] | ||
self.assertEqual(span.attributes["service"], self.TEST_SERVICE) | ||
self.assertEqual(span.name, "redis.command") | ||
self.assertIs( | ||
span.status.canonical_code, trace.status.StatusCanonicalCode.OK | ||
) | ||
self.assertEqual(span.attributes.get("db.instance"), 0) | ||
self.assertEqual( | ||
span.attributes.get("db.url"), "redis://localhost:6379" | ||
) | ||
self.assertEqual(span.attributes.get("db.statement"), "GET cheese") | ||
self.assertEqual(span.attributes.get("redis.args_length"), 2) | ||
|
||
def test_pipeline_traced(self): | ||
with self.redis_client.pipeline(transaction=False) as pipeline: | ||
pipeline.set("blah", 32) | ||
pipeline.rpush("foo", "éé") | ||
pipeline.hgetall("xxx") | ||
pipeline.execute() | ||
|
||
spans = self.memory_exporter.get_finished_spans() | ||
self.assertEqual(len(spans), 1) | ||
span = spans[0] | ||
self.assertEqual(span.attributes["service"], self.TEST_SERVICE) | ||
self.assertEqual(span.name, "redis.command") | ||
self.assertIs( | ||
span.status.canonical_code, trace.status.StatusCanonicalCode.OK | ||
) | ||
self.assertEqual(span.attributes.get("db.instance"), 0) | ||
self.assertEqual( | ||
span.attributes.get("db.url"), "redis://localhost:6379" | ||
) | ||
self.assertEqual( | ||
span.attributes.get("db.statement"), | ||
"SET blah 32\nRPUSH foo éé\nHGETALL xxx", | ||
) | ||
self.assertEqual(span.attributes.get("redis.pipeline_length"), 3) | ||
|
||
def test_pipeline_immediate(self): | ||
with self.redis_client.pipeline() as pipeline: | ||
pipeline.set("a", 1) | ||
pipeline.immediate_execute_command("SET", "b", 2) | ||
pipeline.execute() | ||
|
||
spans = self.memory_exporter.get_finished_spans() | ||
# expecting two separate spans here, rather than a | ||
# single span for the whole pipeline | ||
self.assertEqual(len(spans), 2) | ||
span = spans[0] | ||
self.assertEqual(span.attributes["service"], self.TEST_SERVICE) | ||
self.assertEqual(span.name, "redis.command") | ||
self.assertEqual(span.attributes.get("db.statement"), "SET b 2") | ||
self.assertIs( | ||
span.status.canonical_code, trace.status.StatusCanonicalCode.OK | ||
) | ||
self.assertEqual(span.attributes.get("db.instance"), 0) | ||
self.assertEqual( | ||
span.attributes.get("db.url"), "redis://localhost:6379" | ||
) | ||
|
||
def test_parent(self): | ||
"""Ensure OpenTelemetry works with redis.""" | ||
ot_tracer = trace.get_tracer("redis_svc") | ||
|
||
with ot_tracer.start_as_current_span("redis_get"): | ||
self.assertIsNone(self.redis_client.get("cheese")) | ||
|
||
spans = self.memory_exporter.get_finished_spans() | ||
self.assertEqual(len(spans), 2) | ||
child_span, parent_span = spans[0], spans[1] | ||
|
||
# confirm the parenting | ||
self.assertIsNone(parent_span.parent) | ||
self.assertIs(child_span.parent, parent_span) | ||
|
||
self.assertEqual(parent_span.name, "redis_get") | ||
self.assertEqual(parent_span.instrumentation_info.name, "redis_svc") | ||
|
||
self.assertEqual( | ||
child_span.attributes.get("service"), self.TEST_SERVICE | ||
) | ||
self.assertEqual(child_span.name, "redis.command") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
graft src | ||
graft tests | ||
global-exclude *.pyc | ||
global-exclude *.pyo | ||
global-exclude __pycache__/* | ||
include CHANGELOG.md | ||
include MANIFEST.in | ||
include README.rst | ||
include LICENSE |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
OpenTelemetry Redis Instrumentation | ||
=================================== | ||
|
||
|pypi| | ||
|
||
.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-redis.svg | ||
:target: https://pypi.org/project/opentelemetry-instrumentation-redis/ | ||
|
||
This library allows tracing requests made by the Redis library. | ||
|
||
Installation | ||
------------ | ||
|
||
:: | ||
|
||
pip install opentelemetry-instrumentation-redis | ||
|
||
|
||
References | ||
---------- | ||
|
||
* `OpenTelemetry Redis Instrumentation <https://opentelemetry-python.readthedocs.io/en/latest/instrumentation/opentelemetry-instrumentation-redis/opentelemetry-instrumentation-redis.html>`_ | ||
* `OpenTelemetry Project <https://opentelemetry.io/>`_ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# Copyright The OpenTelemetry Authors | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
[metadata] | ||
name = opentelemetry-instrumentation-redis | ||
description = Redis tracing for OpenTelemetry | ||
long_description = file: README.rst | ||
long_description_content_type = text/x-rst | ||
author = OpenTelemetry Authors | ||
author_email = [email protected] | ||
url = https://github.com/open-telemetry/opentelemetry-python-contrib/tree/master/instrumentation/opentelemetry-instrumentation-redis | ||
platforms = any | ||
license = Apache-2.0 | ||
classifiers = | ||
Development Status :: 4 - Beta | ||
Intended Audience :: Developers | ||
License :: OSI Approved :: Apache Software License | ||
Programming Language :: Python | ||
Programming Language :: Python :: 3 | ||
Programming Language :: Python :: 3.4 | ||
Programming Language :: Python :: 3.5 | ||
Programming Language :: Python :: 3.6 | ||
Programming Language :: Python :: 3.7 | ||
Programming Language :: Python :: 3.8 | ||
|
||
[options] | ||
python_requires = >=3.4 | ||
package_dir= | ||
=src | ||
packages=find_namespace: | ||
install_requires = | ||
opentelemetry-api == 0.7dev0 | ||
opentelemetry-auto-instrumentation == 0.7dev0 | ||
redis | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
wrapt >= 1.12.1 | ||
|
||
[options.extras_require] | ||
test = | ||
opentelemetry-test == 0.7.dev0 | ||
opentelemetry-sdk == 0.7dev0 | ||
|
||
[options.packages.find] | ||
where = src | ||
|
||
[options.entry_points] | ||
opentelemetry_instrumentor = | ||
redis = opentelemetry.instrumentation.redis:RedisInstrumentor |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Copyright The OpenTelemetry Authors | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
import os | ||
|
||
import setuptools | ||
|
||
BASE_DIR = os.path.dirname(__file__) | ||
VERSION_FILENAME = os.path.join( | ||
BASE_DIR, "src", "opentelemetry", "instrumentation", "redis", "version.py" | ||
) | ||
PACKAGE_INFO = {} | ||
with open(VERSION_FILENAME) as f: | ||
exec(f.read(), PACKAGE_INFO) | ||
|
||
setuptools.setup(version=PACKAGE_INFO["__version__"]) |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,60 @@ | ||||||||||
# Copyright The OpenTelemetry Authors | ||||||||||
# | ||||||||||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||||||||||
# you may not use this file except in compliance with the License. | ||||||||||
# You may obtain a copy of the License at | ||||||||||
# | ||||||||||
# http://www.apache.org/licenses/LICENSE-2.0 | ||||||||||
# | ||||||||||
# Unless required by applicable law or agreed to in writing, software | ||||||||||
# distributed under the License is distributed on an "AS IS" BASIS, | ||||||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||||||
# See the License for the specific language governing permissions and | ||||||||||
# limitations under the License. | ||||||||||
# | ||||||||||
""" | ||||||||||
Instrument `redis`_ to report Redis queries. | ||||||||||
|
||||||||||
There are two options for instrumenting code. The first option is to use | ||||||||||
the `opentelemetry-auto-instrumentation` executable which will automatically | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
To fix the sphinx target |
||||||||||
patch your Redis client. The second is to programmatically enable | ||||||||||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
instrumentation via the following code: | ||||||||||
|
||||||||||
.. _redis: https://pypi.org/project/redis/ | ||||||||||
|
||||||||||
Usage | ||||||||||
----- | ||||||||||
|
||||||||||
.. code:: python | ||||||||||
|
||||||||||
mauriciovasquezbernal marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
from opentelemetry import trace | ||||||||||
from opentelemetry.instrumentation.redis.patch import patch | ||||||||||
from opentelemetry.sdk.trace import TracerProvider | ||||||||||
import redis | ||||||||||
|
||||||||||
trace.set_tracer_provider(TracerProvider()) | ||||||||||
|
||||||||||
# You can patch redis specifically | ||||||||||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
patch(trace.get_tracer_provider()) | ||||||||||
|
||||||||||
# This will report a span with the default settings | ||||||||||
client = redis.StrictRedis(host="localhost", port=6379) | ||||||||||
client.get("my-key") | ||||||||||
|
||||||||||
API | ||||||||||
--- | ||||||||||
""" | ||||||||||
from opentelemetry.auto_instrumentation.instrumentor import BaseInstrumentor | ||||||||||
from opentelemetry.instrumentation.redis.patch import patch, unpatch | ||||||||||
|
||||||||||
|
||||||||||
class RedisInstrumentor(BaseInstrumentor): | ||||||||||
"""An instrumentor for Redis | ||||||||||
See `BaseInstrumentor` | ||||||||||
""" | ||||||||||
|
||||||||||
def _instrument(self): | ||||||||||
patch() | ||||||||||
|
||||||||||
def _uninstrument(self): | ||||||||||
unpatch() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if it is a really good approach to keep the tests of the different frameworks under the same test. What about if I only want to test redis?
Is there a technical limitation to do that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's no technical limitation. it's not the path currently but we can split them if it makes more sense (i suspect as we have more integration tests, we will need to do this to run tests in parallel)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Besides parallel tests, there is also the case when you only want to run a specific test without downloading and starting the other containers that you don't need.
This could be a little bit complicated to set up, so we can ignore and handle in a follow up PR.