-
Notifications
You must be signed in to change notification settings - Fork 734
Instrumenting Redis #21
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
Changes from 23 commits
7262fbb
b3cb5c5
a11a593
9d63b7e
a0e9498
2b38380
4c2cfea
38daba2
514ea82
ca0ba2e
f669783
a6c2626
bcd8d76
064c33a
6500779
aa19ed4
fdac399
a9c866e
ceea9d1
7d9883d
7d8fbcb
aff183f
40aa813
dea67fe
e3d35c7
20ed1ac
50f581f
52b6920
6166485
3d8305c
ba30d9f
f307d57
53ffc26
8be6885
6739b32
48307af
4102b37
f1c1702
98b4475
591f4d7
3b63eff
cb3030e
e294fbc
e96f43c
676c349
c53e2aa
b63e7c2
f71f885
18a847f
8e1b66e
ae4def8
18c247a
798d655
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 |
---|---|---|
|
@@ -7,4 +7,4 @@ sphinx~=2.1 | |
sphinx-rtd-theme~=0.4 | ||
sphinx-autodoc-typehints~=1.10.2 | ||
pytest!=5.2.3 | ||
pytest-cov>=2.8 | ||
pytest-cov>=2.8 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
version: "3" | ||
# remember to use this compose file __ONLY__ for development/testing purposes | ||
|
||
services: | ||
redis: | ||
image: redis:4.0-alpine | ||
ports: | ||
- "127.0.0.1:6379:6379" | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
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>`_ | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* `OpenTelemetry Project <https://opentelemetry.io/>`_ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# 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/instrumentation/opentelemetry-instrumentation-redis | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 = | ||
# pin versions | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
wrapt | ||
opentelemetry-api | ||
opentelemetry-sdk | ||
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. It also requires 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. We require this dependency also for the 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. @mauriciovasquezbernal what do you mean with "adding more things on the auto-instrumentation package?" @codeboten the SDK package is not necessary to run the 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. I meant that |
||
|
||
[options.extras_require] | ||
test = | ||
redis | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
[options.packages.find] | ||
where = src |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# 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__"], | ||
entry_points={ | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"opentelemetry_instrumentor": [ | ||
"redis = opentelemetry.instrumentation.redis:RedisInstrumentor" | ||
] | ||
}, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# 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. | ||
|
||
opentelemetry-auto-instrumentation will automatically patch your Redis client to make it work. | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
:: | ||
|
||
from opentelemetry.instrumentation.redis.patch import patch | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import redis | ||
|
||
# If not patched yet, you can patch redis specifically | ||
patch(redis=True) | ||
|
||
# This will report a span with the default settings | ||
client = redis.StrictRedis(host="localhost", port=6379) | ||
client.get("my-key") | ||
""" | ||
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() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# Copyright The OpenTelemetry Authors | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# | ||
# 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. | ||
# | ||
|
||
# defaults | ||
APP = "redis" | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
DEFAULT_SERVICE = "redis" | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# net extension | ||
DB = "out.redis_db" | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# standard tags | ||
RAWCMD = "redis.raw_command" | ||
CMD = "redis.command" | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ARGS_LEN = "redis.args_length" | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
PIPELINE_LEN = "redis.pipeline_length" | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
PIPELINE_AGE = "redis.pipeline_age" | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
# 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. | ||
# | ||
# pylint:disable=relative-beyond-top-level | ||
import redis | ||
import wrapt | ||
|
||
from opentelemetry import trace | ||
from opentelemetry.instrumentation.redis import constants | ||
|
||
from .util import _extract_conn_attributes, format_command_args | ||
from .version import __version__ | ||
|
||
|
||
def patch(): | ||
"""Patch the instrumented methods | ||
|
||
This duplicated doesn't look nice. The nicer alternative is to use an ObjectProxy on top | ||
of Redis and StrictRedis. However, it means that any "import redis.Redis" won't be instrumented. | ||
""" | ||
if getattr(redis, "_opentelemetry_patch", False): | ||
return | ||
setattr(redis, "_opentelemetry_patch", True) | ||
|
||
_w = wrapt.wrap_function_wrapper | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if redis.VERSION < (3, 0, 0): | ||
_w("redis", "StrictRedis.execute_command", traced_execute_command) | ||
_w("redis", "StrictRedis.pipeline", traced_pipeline) | ||
_w("redis", "Redis.pipeline", traced_pipeline) | ||
_w("redis.client", "BasePipeline.execute", traced_execute_pipeline) | ||
_w( | ||
"redis.client", | ||
"BasePipeline.immediate_execute_command", | ||
traced_execute_command, | ||
) | ||
else: | ||
_w("redis", "Redis.execute_command", traced_execute_command) | ||
_w("redis", "Redis.pipeline", traced_pipeline) | ||
_w("redis.client", "Pipeline.execute", traced_execute_pipeline) | ||
_w( | ||
"redis.client", | ||
"Pipeline.immediate_execute_command", | ||
traced_execute_command, | ||
) | ||
|
||
|
||
def unwrap(obj, attr): | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
func = getattr(obj, attr, None) | ||
if ( | ||
func | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
and isinstance(func, wrapt.ObjectProxy) | ||
and hasattr(func, "__wrapped__") | ||
): | ||
setattr(obj, attr, func.__wrapped__) | ||
|
||
|
||
def unpatch(): | ||
if getattr(redis, "_opentelemetry_patch", False): | ||
setattr(redis, "_opentelemetry_patch", False) | ||
print("unpatch") | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if redis.VERSION < (3, 0, 0): | ||
unwrap(redis.StrictRedis, "execute_command") | ||
unwrap(redis.StrictRedis, "pipeline") | ||
unwrap(redis.Redis, "pipeline") | ||
unwrap( | ||
redis.client.BasePipeline, # pylint:disable=no-member | ||
"execute", | ||
) | ||
unwrap( | ||
redis.client.BasePipeline, # pylint:disable=no-member | ||
"immediate_execute_command", | ||
) | ||
else: | ||
print("unwrapping") | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
unwrap(redis.Redis, "execute_command") | ||
unwrap(redis.Redis, "pipeline") | ||
unwrap(redis.client.Pipeline, "execute") | ||
unwrap(redis.client.Pipeline, "immediate_execute_command") | ||
|
||
|
||
# | ||
# tracing functions | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# | ||
def traced_execute_command(func, instance, args, kwargs): | ||
tracer = trace.get_tracer(constants.DEFAULT_SERVICE, __version__) | ||
|
||
with tracer.start_as_current_span(constants.CMD) as span: | ||
span.set_attribute("service", tracer.instrumentation_info.name) | ||
query = format_command_args(args) | ||
span.set_attribute(constants.RAWCMD, query) | ||
for key, value in _get_attributes(instance).items(): | ||
span.set_attribute(key, value) | ||
# TODO: set metric | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# s.set_metric(ARGS_LEN, len(args)) | ||
return func(*args, **kwargs) | ||
|
||
|
||
# pylint: disable=unused-argument | ||
def traced_pipeline(func, instance, args, kwargs): | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return func(*args, **kwargs) | ||
|
||
|
||
def traced_execute_pipeline(func, instance, args, kwargs): | ||
tracer = trace.get_tracer(constants.DEFAULT_SERVICE, __version__) | ||
|
||
# FIXME[matt] done in the agent. worth it? | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
cmds = [format_command_args(c) for c, _ in instance.command_stack] | ||
resource = "\n".join(cmds) | ||
|
||
with tracer.start_as_current_span(constants.CMD) as span: | ||
span.set_attribute("service", tracer.instrumentation_info.name) | ||
span.set_attribute(constants.RAWCMD, resource) | ||
for key, value in _get_attributes(instance).items(): | ||
span.set_attribute(key, value) | ||
# TODO: set metric | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# s.set_metric(PIPELINE_LEN, len(instance.command_stack)) | ||
return func(*args, **kwargs) | ||
|
||
|
||
def _get_attributes(conn): | ||
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return _extract_conn_attributes(conn.connection_pool.connection_kwargs) |
Uh oh!
There was an error while loading. Please reload this page.