Skip to content

Commit 0100654

Browse files
authored
Redis Instrumentation & Tests (#104)
* Initial Redis instrumentation, tests and infra * Pipeline support; subCommands; More Tests * Fix error logging
1 parent fba3799 commit 0100654

File tree

7 files changed

+426
-5
lines changed

7 files changed

+426
-5
lines changed

instana/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def load_instrumentation():
6161
# Import & initialize instrumentation
6262
from .instrumentation import asynqp # noqa
6363
from .instrumentation import mysqlpython # noqa
64+
from .instrumentation import redis # noqa
6465
from .instrumentation import sqlalchemy # noqa
6566
from .instrumentation import sudsjurko # noqa
6667
from .instrumentation import urllib3 # noqa

instana/instrumentation/redis.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from __future__ import absolute_import
2+
3+
import opentracing
4+
import opentracing.ext.tags as ext
5+
import wrapt
6+
7+
from ..log import logger
8+
from ..singletons import tracer
9+
10+
try:
11+
import redis
12+
13+
@wrapt.patch_function_wrapper('redis.client','StrictRedis.execute_command')
14+
def execute_command_with_instana(wrapped, instance, args, kwargs):
15+
parent_span = tracer.active_span
16+
17+
# If we're not tracing, just return
18+
if parent_span is None:
19+
return wrapped(*args, **kwargs)
20+
21+
with tracer.start_active_span("redis", child_of=parent_span) as scope:
22+
23+
try:
24+
ckw = instance.connection_pool.connection_kwargs
25+
url = "redis://%s:%d/%d" % (ckw['host'], ckw['port'], ckw['db'])
26+
scope.span.set_tag("connection", url)
27+
scope.span.set_tag("driver", "redis-py")
28+
scope.span.set_tag("command", args[0])
29+
30+
rv = wrapped(*args, **kwargs)
31+
except Exception as e:
32+
scope.span.set_tag("redis.error", str(e))
33+
scope.span.set_tag("error", True)
34+
ec = scope.span.tags.get('ec', 0)
35+
scope.span.set_tag("ec", ec+1)
36+
raise
37+
else:
38+
return rv
39+
40+
@wrapt.patch_function_wrapper('redis.client','BasePipeline.execute')
41+
def execute_with_instana(wrapped, instance, args, kwargs):
42+
parent_span = tracer.active_span
43+
44+
# If we're not tracing, just return
45+
if parent_span is None:
46+
return wrapped(*args, **kwargs)
47+
48+
with tracer.start_active_span("redis", child_of=parent_span) as scope:
49+
50+
try:
51+
ckw = instance.connection_pool.connection_kwargs
52+
url = "redis://%s:%d/%d" % (ckw['host'], ckw['port'], ckw['db'])
53+
scope.span.set_tag("connection", url)
54+
scope.span.set_tag("driver", "redis-py")
55+
scope.span.set_tag("command", 'PIPELINE')
56+
57+
try:
58+
pipe_cmds = []
59+
for e in instance.command_stack:
60+
pipe_cmds.append(e[0][0])
61+
scope.span.set_tag("subCommands", pipe_cmds)
62+
except Exception as e:
63+
# If anything breaks during cmd collection, just log a
64+
# debug message
65+
logger.debug("Error collecting pipeline commands")
66+
67+
rv = wrapped(*args, **kwargs)
68+
except Exception as e:
69+
scope.span.set_tag("redis.error", str(e))
70+
scope.span.set_tag("error", True)
71+
ec = scope.span.tags.get('ec', 0)
72+
scope.span.set_tag("ec", ec+1)
73+
raise
74+
else:
75+
return rv
76+
77+
logger.debug("Instrumenting redis")
78+
except ImportError:
79+
pass

instana/json_span.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class Data(object):
3030
custom = None
3131
http = None
3232
rabbitmq = None
33+
redis = None
3334
sdk = None
3435
service = None
3536
sqlalchemy = None
@@ -72,6 +73,18 @@ class RabbitmqData(object):
7273
def __init__(self, **kwds):
7374
self.__dict__.update(kwds)
7475

76+
77+
class RedisData(object):
78+
connection = None
79+
driver = None
80+
command = None
81+
error = None
82+
subCommands = None
83+
84+
def __init__(self, **kwds):
85+
self.__dict__.update(kwds)
86+
87+
7588
class SQLAlchemyData(object):
7689
sql = None
7790
url = None

instana/recorder.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
import instana.singletons
1313

1414
from .json_span import (CustomData, Data, HttpData, JsonSpan, MySQLData,
15-
RabbitmqData, SDKData, SoapData, SQLAlchemyData)
15+
RabbitmqData, RedisData, SDKData, SoapData,
16+
SQLAlchemyData)
1617
from .log import logger
1718

1819
if sys.version_info.major is 2:
@@ -22,12 +23,12 @@
2223

2324

2425
class InstanaRecorder(SpanRecorder):
25-
registered_spans = ("django", "memcache", "mysql", "rabbitmq", "rpc-client",
26-
"rpc-server", "sqlalchemy", "soap", "urllib3", "wsgi")
26+
registered_spans = ("django", "memcache", "mysql", "rabbitmq", "redis",
27+
"rpc-client", "rpc-server", "sqlalchemy", "soap", "urllib3", "wsgi")
2728
http_spans = ("django", "wsgi", "urllib3", "soap")
2829

29-
exit_spans = ("memcache", "mysql", "rabbitmq", "rpc-client", "sqlalchemy",
30-
"soap", "urllib3")
30+
exit_spans = ("memcache", "mysql", "rabbitmq", "redis", "rpc-client",
31+
"sqlalchemy", "soap", "urllib3")
3132
entry_spans = ("django", "wsgi", "rabbitmq", "rpc-server")
3233

3334
entry_kind = ["entry", "server", "consumer"]
@@ -115,6 +116,13 @@ def build_registered_span(self, span):
115116
address=span.tags.pop('address', None),
116117
key=span.tags.pop('key', None))
117118

119+
if span.operation_name == "redis":
120+
data.redis = RedisData(connection=span.tags.pop('connection', None),
121+
driver=span.tags.pop('driver', None),
122+
command=span.tags.pop('command', None),
123+
error=span.tags.pop('redis.error', None),
124+
subCommands=span.tags.pop('subCommands', None))
125+
118126
if span.operation_name == "sqlalchemy":
119127
data.sqlalchemy = SQLAlchemyData(sql=span.tags.pop('sqlalchemy.sql', None),
120128
eng=span.tags.pop('sqlalchemy.eng', None),

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def check_setuptools():
5757
'psycopg2>=2.7.1',
5858
'pyOpenSSL>=16.1.0;python_version<="2.7"',
5959
'pytest>=3.0.1',
60+
'redis>=2.10.6',
6061
'requests>=2.17.1',
6162
'sqlalchemy>=1.1.15',
6263
'spyne>=2.9',

tests/helpers.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,11 @@
4343
testenv['postgresql_pw'] = os.environ['TRAVIS_POSTGRESQL_PASS']
4444
else:
4545
testenv['postgresql_pw'] = ''
46+
47+
"""
48+
Redis Environment
49+
"""
50+
if 'REDIS' in os.environ:
51+
testenv['redis_url']= os.environ['REDIS']
52+
else:
53+
testenv['redis_url'] = '127.0.0.1:6379'

0 commit comments

Comments
 (0)