Skip to content

Commit 3abb228

Browse files
author
Emanuele Palazzetti
authored
Merge pull request #51 from palazzem/flask-cache-integration
flask-cache integration
2 parents 3f9a942 + 9f8696a commit 3abb228

File tree

8 files changed

+841
-1
lines changed

8 files changed

+841
-1
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""
2+
The flask cache tracer will track any access to a cache backend.
3+
You can this tracer together with the Flask tracer middleware.
4+
5+
To install the tracer, do the following::
6+
7+
from flask import Flask
8+
9+
from ddtrace import tracer
10+
from ddtrace.contrib.flask_cache import get_traced_cache
11+
12+
app = Flask(__name__)
13+
14+
# get the traced Cache class
15+
Cache = get_traced_cache(tracer, service='flask-cache-experiments')
16+
17+
# use the Cache as usual
18+
cache = Cache(app, config={'CACHE_TYPE': 'simple'})
19+
20+
def counter():
21+
# this access is traced
22+
conn_counter = cache.get("conn_counter")
23+
"""
24+
25+
from ..util import require_modules
26+
27+
required_modules = ['flask_cache']
28+
29+
with require_modules(required_modules) as missing_modules:
30+
if not missing_modules:
31+
from .tracers import get_traced_cache
32+
33+
__all__ = ['get_traced_cache']
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
"""
2+
Datadog trace code for flask_cache
3+
"""
4+
5+
# stdlib
6+
import logging
7+
8+
# project
9+
from .utils import _extract_conn_tags, _resource_from_cache_prefix
10+
from ...ext import AppTypes
11+
12+
# 3rd party
13+
from flask.ext.cache import Cache
14+
15+
16+
log = logging.Logger(__name__)
17+
18+
TYPE = "cache"
19+
DEFAULT_SERVICE = "flask-cache"
20+
21+
# standard tags
22+
COMMAND_KEY = "flask_cache.key"
23+
CACHE_BACKEND = "flask_cache.backend"
24+
CONTACT_POINTS = "flask_cache.contact_points"
25+
26+
27+
def get_traced_cache(ddtracer, service=DEFAULT_SERVICE, meta=None):
28+
"""
29+
Return a traced Cache object that behaves exactly as the ``flask.ext.cache.Cache class``
30+
"""
31+
32+
# set the Tracer info
33+
ddtracer.set_service_info(
34+
app="flask",
35+
app_type=AppTypes.cache,
36+
service=service,
37+
)
38+
39+
class TracedCache(Cache):
40+
"""
41+
Traced cache backend that monitors any operations done by flask_cache. Observed actions are:
42+
* get, set, add, delete, clear
43+
* all many_ operations
44+
"""
45+
_datadog_tracer = ddtracer
46+
_datadog_service = service
47+
_datadog_meta = meta
48+
49+
def __trace(self, cmd):
50+
"""
51+
Start a tracing with default attributes and tags
52+
"""
53+
# create a new span
54+
s = self._datadog_tracer.trace(
55+
cmd,
56+
span_type=TYPE,
57+
service=self._datadog_service
58+
)
59+
# set span tags
60+
s.set_tag(CACHE_BACKEND, self.config.get("CACHE_TYPE"))
61+
s.set_tags(self._datadog_meta)
62+
# add connection meta if there is one
63+
if getattr(self.cache, "_client", None):
64+
try:
65+
s.set_tags(_extract_conn_tags(self.cache._client))
66+
except Exception:
67+
log.exception("error parsing connection tags")
68+
69+
return s
70+
71+
def get(self, *args, **kwargs):
72+
"""
73+
Track ``get`` operation
74+
"""
75+
with self.__trace("flask_cache.cmd") as span:
76+
span.resource = _resource_from_cache_prefix("GET", self.config)
77+
if len(args) > 0:
78+
span.set_tag(COMMAND_KEY, args[0])
79+
return super(TracedCache, self).get(*args, **kwargs)
80+
81+
def set(self, *args, **kwargs):
82+
"""
83+
Track ``set`` operation
84+
"""
85+
with self.__trace("flask_cache.cmd") as span:
86+
span.resource = _resource_from_cache_prefix("SET", self.config)
87+
if len(args) > 0:
88+
span.set_tag(COMMAND_KEY, args[0])
89+
return super(TracedCache, self).set(*args, **kwargs)
90+
91+
def add(self, *args, **kwargs):
92+
"""
93+
Track ``add`` operation
94+
"""
95+
with self.__trace("flask_cache.cmd") as span:
96+
span.resource = _resource_from_cache_prefix("ADD", self.config)
97+
if len(args) > 0:
98+
span.set_tag(COMMAND_KEY, args[0])
99+
return super(TracedCache, self).add(*args, **kwargs)
100+
101+
def delete(self, *args, **kwargs):
102+
"""
103+
Track ``delete`` operation
104+
"""
105+
with self.__trace("flask_cache.cmd") as span:
106+
span.resource = _resource_from_cache_prefix("DELETE", self.config)
107+
if len(args) > 0:
108+
span.set_tag(COMMAND_KEY, args[0])
109+
return super(TracedCache, self).delete(*args, **kwargs)
110+
111+
def delete_many(self, *args, **kwargs):
112+
"""
113+
Track ``delete_many`` operation
114+
"""
115+
with self.__trace("flask_cache.cmd") as span:
116+
span.resource = _resource_from_cache_prefix("DELETE_MANY", self.config)
117+
span.set_tag(COMMAND_KEY, list(args))
118+
return super(TracedCache, self).delete_many(*args, **kwargs)
119+
120+
def clear(self, *args, **kwargs):
121+
"""
122+
Track ``clear`` operation
123+
"""
124+
with self.__trace("flask_cache.cmd") as span:
125+
span.resource = _resource_from_cache_prefix("CLEAR", self.config)
126+
return super(TracedCache, self).clear(*args, **kwargs)
127+
128+
def get_many(self, *args, **kwargs):
129+
"""
130+
Track ``get_many`` operation
131+
"""
132+
with self.__trace("flask_cache.cmd") as span:
133+
span.resource = _resource_from_cache_prefix("GET_MANY", self.config)
134+
span.set_tag(COMMAND_KEY, list(args))
135+
return super(TracedCache, self).get_many(*args, **kwargs)
136+
137+
def set_many(self, *args, **kwargs):
138+
"""
139+
Track ``set_many`` operation
140+
"""
141+
with self.__trace("flask_cache.cmd") as span:
142+
span.resource = _resource_from_cache_prefix("SET_MANY", self.config)
143+
if len(args) > 0:
144+
span.set_tag(COMMAND_KEY, list(args[0].keys()))
145+
return super(TracedCache, self).set_many(*args, **kwargs)
146+
147+
return TracedCache
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# project
2+
from ...ext import net
3+
from ..redis.util import _extract_conn_tags as extract_redis_tags
4+
5+
6+
def _resource_from_cache_prefix(resource, cache):
7+
"""
8+
Combine the resource name with the cache prefix (if any)
9+
"""
10+
if getattr(cache, "key_prefix", None):
11+
name = "{} {}".format(resource, cache.key_prefix)
12+
else:
13+
name = resource
14+
15+
# enforce lowercase to make the output nicer to read
16+
return name.lower()
17+
18+
19+
def _extract_conn_tags(client):
20+
"""
21+
For the given client extracts connection tags
22+
"""
23+
tags = {}
24+
25+
if getattr(client, "servers", None):
26+
# Memcached backend supports an address pool
27+
if isinstance(client.servers, list) and len(client.servers) > 0:
28+
# use the first address of the pool as a host because
29+
# the code doesn't expose more information
30+
contact_point = client.servers[0].address
31+
tags[net.TARGET_HOST] = contact_point[0]
32+
tags[net.TARGET_PORT] = contact_point[1]
33+
34+
if getattr(client, "connection_pool", None):
35+
# Redis main connection
36+
redis_tags = extract_redis_tags(client.connection_pool.connection_kwargs)
37+
tags.update(**redis_tags)
38+
39+
return tags

tests/contrib/flask_cache/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)