Skip to content

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

Closed
wants to merge 53 commits into from
Closed
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
7262fbb
move redis directory
Apr 3, 2020
b3cb5c5
move tests
Apr 3, 2020
a11a593
create opentelemetry-ext-redis package
Apr 3, 2020
9d63b7e
fix import paths
Apr 3, 2020
a0e9498
update code to use opentelemetry API
Apr 4, 2020
2b38380
removing legacy code
Apr 4, 2020
4c2cfea
Update setup.cfg
Apr 4, 2020
38daba2
fix tests
Apr 6, 2020
514ea82
fix naming to use instrumentation
Apr 9, 2020
ca0ba2e
Merge remote-tracking branch 'origin/master' into instrumentor-redis
Apr 9, 2020
f669783
cleaning up code
Apr 9, 2020
a6c2626
Merge remote-tracking branch 'origin/master' into instrumentor-redis
Apr 9, 2020
bcd8d76
more cleanup
Apr 9, 2020
064c33a
fix readme
Apr 9, 2020
6500779
fixing tox, ignore reference for pytest
Apr 9, 2020
aa19ed4
adding docker compose file
Apr 9, 2020
fdac399
cleanup tox, add dep for py34
Apr 9, 2020
a9c866e
adding constraint for ddtrace version
Apr 9, 2020
ceea9d1
fix dep
Apr 9, 2020
7d9883d
fix isort
Apr 9, 2020
7d8fbcb
removing some dependency on ddtrace
Apr 9, 2020
aff183f
removing dep on ddtrace
Apr 10, 2020
40aa813
remove from requirements
Apr 10, 2020
dea67fe
review cleanup
Apr 10, 2020
e3d35c7
set attributes for what used to be metric
Apr 10, 2020
20ed1ac
cleaning up tests
Apr 10, 2020
50f581f
more test cleanup
Apr 10, 2020
52b6920
more cleanups
Apr 10, 2020
6166485
cleaning up a bunch of variables
Apr 10, 2020
3d8305c
updating name
Apr 10, 2020
ba30d9f
more fixes
Apr 10, 2020
f307d57
Apply suggestions from code review
Apr 14, 2020
53ffc26
cleanup
Apr 14, 2020
8be6885
updating assert statements
Apr 14, 2020
6739b32
removing hyphen
Apr 14, 2020
48307af
more feedback updates
Apr 14, 2020
4102b37
removing unnecessary dict
Apr 14, 2020
f1c1702
set versions
Apr 14, 2020
98b4475
updating docs
Apr 14, 2020
591f4d7
removing comment
Apr 14, 2020
3b63eff
revert change to use full query in span name, more feedback fixes
Apr 14, 2020
cb3030e
updating docs
Apr 14, 2020
e294fbc
sdk is not required
Apr 14, 2020
e96f43c
test still needs the sdk
Apr 14, 2020
676c349
fix missing dep
Apr 15, 2020
c53e2aa
pin otel dependencies
Apr 15, 2020
b63e7c2
move entrypoint to setup.cfg
Apr 15, 2020
f71f885
use semantic conventions, remove useless test
Apr 15, 2020
18a847f
Update instrumentation/opentelemetry-instrumentation-redis/setup.cfg
Apr 15, 2020
8e1b66e
cleaning up formatting method, updating test
Apr 16, 2020
ae4def8
don't get a tracer on each operation
Apr 16, 2020
18c247a
updating test
Apr 16, 2020
798d655
method was used in the context of Pin
Apr 16, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 8 additions & 0 deletions docker-compose.yml
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"
23 changes: 23 additions & 0 deletions instrumentation/opentelemetry-instrumentation-redis/README.rst
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/>`_
53 changes: 53 additions & 0 deletions instrumentation/opentelemetry-instrumentation-redis/setup.cfg
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
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
wrapt
opentelemetry-api
opentelemetry-sdk

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also requires opentelemetry-auto-instrumentation because the ABC defined there.
Do we plan to make this dependency stronger by adding more things on the auto-instrumentation package? Otherwise I'm not sure if it is a good idea to require this package only for a very simple ABC defined there.
Probably @ocelotl has more opinions on this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We require this dependency also for the opentelemetry-auto-instrumentation executable

Copy link
Contributor

Choose a reason for hiding this comment

The 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 opentelemetry-auto-instrumentation executable.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant that opentelemetry-auto-instrumentation is only needed for the BaseInstrumentor ABC defined there. I was wondering if there are any plans to add more features to this package in the future.


[options.extras_require]
test =
redis

[options.packages.find]
where = src
33 changes: 33 additions & 0 deletions instrumentation/opentelemetry-instrumentation-redis/setup.py
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={
"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.
::

from opentelemetry.instrumentation.redis.patch import patch
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
#
# 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"
DEFAULT_SERVICE = "redis"

# net extension
DB = "out.redis_db"

# standard tags
RAWCMD = "redis.raw_command"
CMD = "redis.command"
ARGS_LEN = "redis.args_length"
PIPELINE_LEN = "redis.pipeline_length"
PIPELINE_AGE = "redis.pipeline_age"
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
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):
func = getattr(obj, attr, None)
if (
func
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")
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")
unwrap(redis.Redis, "execute_command")
unwrap(redis.Redis, "pipeline")
unwrap(redis.client.Pipeline, "execute")
unwrap(redis.client.Pipeline, "immediate_execute_command")


#
# tracing functions
#
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
# s.set_metric(ARGS_LEN, len(args))
return func(*args, **kwargs)


# pylint: disable=unused-argument
def traced_pipeline(func, instance, args, kwargs):
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?
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
# s.set_metric(PIPELINE_LEN, len(instance.command_stack))
return func(*args, **kwargs)


def _get_attributes(conn):
return _extract_conn_attributes(conn.connection_pool.connection_kwargs)
Loading