Skip to content

Commit 0455ccb

Browse files
authored
Redis Visibility Improvements (#179)
* Improved json encoder; protect against error cases * Use the util to_json encoder * Better log extraction * Report path in http.path * Updated Redis test config * Updated Redis tests and docker-compose iamge * Redis: More safeties, better KV collection
1 parent cca299c commit 0455ccb

File tree

8 files changed

+111
-80
lines changed

8 files changed

+111
-80
lines changed

docker-compose.yml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
version: '2'
22
services:
33
redis:
4-
image: redis:4.0.6
4+
image: 'bitnami/redis:latest'
5+
environment:
6+
- ALLOW_EMPTY_PASSWORD=yes
57
ports:
68
- 6379:6379
79

10+
#
11+
# Dev: Optionally enable to validate Redis Sentinel
12+
#
13+
# redis-sentinel:
14+
# image: 'bitnami/redis-sentinel:latest'
15+
# environment:
16+
# - REDIS_MASTER_HOST=redis
17+
# ports:
18+
# - '26379:26379'
19+
820
# Kafka test will sometimes fail because Zookeeper won't start due to
921
# java.io.IOException: Unable to create data directory /opt/zookeeper-3.4.9/data/version-2, which seems to be a known issue:
1022
# -> https://issues.apache.org/jira/browse/ZOOKEEPER-1936
@@ -55,4 +67,4 @@ services:
5567
image: rabbitmq:3.7.8-alpine
5668
ports:
5769
- 5671:5671
58-
- 5672:5672
70+
- 5672:5672

instana/agent.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from .fsm import TheMachine
1515
from .log import logger
1616
from .sensor import Sensor
17+
from .util import to_json
1718

1819

1920
class From(object):
@@ -86,15 +87,6 @@ def reset(self):
8687
# Will schedule a restart of the announce cycle in the future
8788
self.machine.reset()
8889

89-
def to_json(self, o):
90-
def extractor(o):
91-
return {k.lower(): v for k, v in o.__dict__.items() if v is not None}
92-
93-
try:
94-
return json.dumps(o, default=extractor, sort_keys=False, separators=(',', ':')).encode()
95-
except Exception:
96-
logger.debug("to_json", exc_info=True)
97-
9890
def is_timed_out(self):
9991
if self.last_seen and self.can_send:
10092
diff = datetime.now() - self.last_seen
@@ -165,7 +157,7 @@ def announce(self, discovery):
165157
logger.debug("making announce request to %s", url)
166158
response = None
167159
response = self.client.put(url,
168-
data=self.to_json(discovery),
160+
data=to_json(discovery),
169161
headers={"Content-Type": "application/json"},
170162
timeout=0.8)
171163

@@ -196,7 +188,7 @@ def report_data(self, entity_data):
196188
try:
197189
response = None
198190
response = self.client.post(self.__data_url(),
199-
data=self.to_json(entity_data),
191+
data=to_json(entity_data),
200192
headers={"Content-Type": "application/json"},
201193
timeout=0.8)
202194

@@ -221,7 +213,7 @@ def report_traces(self, spans):
221213

222214
response = None
223215
response = self.client.post(self.__traces_url(),
224-
data=self.to_json(spans),
216+
data=to_json(spans),
225217
headers={"Content-Type": "application/json"},
226218
timeout=0.8)
227219

instana/instrumentation/redis.py

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,44 @@
1010

1111
if ((redis.VERSION >= (2, 10, 6)) and (redis.VERSION < (3, 0, 0))):
1212

13+
def collect_tags(span, instance, args, kwargs):
14+
try:
15+
ckw = instance.connection_pool.connection_kwargs
16+
17+
span.set_tag("driver", "redis-py")
18+
19+
host = ckw.get('host', None)
20+
port = ckw.get('port', '6379')
21+
db = ckw.get('db', None)
22+
23+
if host is not None:
24+
url = "redis://%s:%s" % (host, port)
25+
if db is not None:
26+
url = url + "/%s" % db
27+
span.set_tag('connection', url)
28+
29+
except:
30+
logger.debug("redis.collect_tags non-fatal error", exc_info=True)
31+
finally:
32+
return span
33+
1334
@wrapt.patch_function_wrapper('redis.client','StrictRedis.execute_command')
1435
def execute_command_with_instana(wrapped, instance, args, kwargs):
1536
parent_span = tracer.active_span
1637

1738
# If we're not tracing, just return
18-
if parent_span is None:
39+
if parent_span is None or parent_span.operation_name == "redis":
1940
return wrapped(*args, **kwargs)
2041

2142
with tracer.start_active_span("redis", child_of=parent_span) as scope:
22-
2343
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])
44+
collect_tags(scope.span, instance, args, kwargs)
45+
if (len(args) > 0):
46+
scope.span.set_tag("command", args[0])
2947

3048
rv = wrapped(*args, **kwargs)
3149
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)
50+
scope.span.log_exception(e)
3651
raise
3752
else:
3853
return rv
@@ -42,34 +57,26 @@ def execute_with_instana(wrapped, instance, args, kwargs):
4257
parent_span = tracer.active_span
4358

4459
# If we're not tracing, just return
45-
if parent_span is None:
60+
if parent_span is None or parent_span.operation_name == "redis":
4661
return wrapped(*args, **kwargs)
4762

4863
with tracer.start_active_span("redis", child_of=parent_span) as scope:
49-
5064
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")
65+
collect_tags(scope.span, instance, args, kwargs)
5566
scope.span.set_tag("command", 'PIPELINE')
5667

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")
68+
pipe_cmds = []
69+
for e in instance.command_stack:
70+
pipe_cmds.append(e[0][0])
71+
scope.span.set_tag("subCommands", pipe_cmds)
72+
except Exception as e:
73+
# If anything breaks during K/V collection, just log a debug message
74+
logger.debug("Error collecting pipeline commands", exc_info=True)
6675

76+
try:
6777
rv = wrapped(*args, **kwargs)
6878
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)
79+
scope.span.log_exception(e)
7380
raise
7481
else:
7582
return rv

instana/recorder.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -328,14 +328,24 @@ def get_span_kind_as_int(self, span):
328328
return kind
329329

330330
def collect_logs(self, span):
331+
"""
332+
Collect up log data and feed it to the Instana brain.
333+
334+
:param span: The span to search for logs in
335+
:return: Logs ready for consumption by the Instana brain.
336+
"""
331337
logs = {}
332-
for l in span.logs:
333-
ts = int(round(l.timestamp * 1000))
338+
for log in span.logs:
339+
ts = int(round(log.timestamp * 1000))
334340
if ts not in logs:
335341
logs[ts] = {}
336342

337-
for f in l.key_values:
338-
logs[ts][f] = l.key_values[f]
343+
if 'message' in log.key_values:
344+
logs[ts]['message'] = log.key_values['message']
345+
if 'event' in log.key_values:
346+
logs[ts]['event'] = log.key_values['event']
347+
if 'parameters' in log.key_values:
348+
logs[ts]['parameters'] = log.key_values['parameters']
339349

340350
return logs
341351

instana/util.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,16 @@ def to_json(obj):
8181
:return: json string
8282
"""
8383
try:
84-
return json.dumps(obj, default=lambda obj: {k.lower(): v for k, v in obj.__dict__.items()},
85-
sort_keys=False, separators=(',', ':')).encode()
84+
def extractor(o):
85+
if not hasattr(o, '__dict__'):
86+
logger.debug("Couldn't serialize non dict type: %s", type(o))
87+
return {}
88+
else:
89+
return {k.lower(): v for k, v in o.__dict__.items() if v is not None}
90+
91+
return json.dumps(obj, default=extractor, sort_keys=False, separators=(',', ':')).encode()
8692
except Exception:
87-
logger.debug("to_json: ", exc_info=True)
93+
logger.debug("to_json non-fatal encoding issue: ", exc_info=True)
8894

8995

9096
def package_version():

instana/wsgi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def new_start_response(status, headers, exc_info=None):
4545
self.scope.span.set_tag("http.%s" % custom_header, env[wsgi_header])
4646

4747
if 'PATH_INFO' in env:
48-
self.scope.span.set_tag(tags.HTTP_URL, env['PATH_INFO'])
48+
self.scope.span.set_tag('http.path', env['PATH_INFO'])
4949
if 'QUERY_STRING' in env and len(env['QUERY_STRING']):
5050
scrubbed_params = strip_secrets(env['QUERY_STRING'], agent.secrets_matcher, agent.secrets_list)
5151
self.scope.span.set_tag("http.params", scrubbed_params)

tests/helpers.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,7 @@
3131
"""
3232
Redis Environment
3333
"""
34-
if 'REDIS' in os.environ:
35-
testenv['redis_url']= os.environ['REDIS']
36-
else:
37-
testenv['redis_url'] = '127.0.0.1:6379'
34+
testenv['redis_host'] = os.environ.get('REDIS_HOST', '127.0.0.1')
3835

3936

4037
def get_first_span_by_name(spans, name):

0 commit comments

Comments
 (0)