Skip to content

Commit c654a73

Browse files
authored
Merge pull request #8 from instana/flask
Flask Instrumentation through WSGI middleware.
2 parents 7756d9c + 0e50715 commit c654a73

File tree

10 files changed

+147
-31
lines changed

10 files changed

+147
-31
lines changed

README.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,29 @@ Any and all feedback is welcome. Happy Python visibility.
1818

1919
## Installation
2020

21-
There are two steps required to install the the Instana package for your applications:
21+
For this BETA, we currently support tracing of Django and Flask applications or optionally just runtime monitoring of your Python applications.
2222

23-
1. `pip install instana` into the virtual-env or container ([hosted on pypi](https://pypi.python.org/pypi/instana))
23+
`pip install instana` into the virtual-env or container ([hosted on pypi](https://pypi.python.org/pypi/instana))
2424

25-
2. Enable instrumentation for the frameworks in use by setting an environment variable:
26-
`AUTOWRAPT_BOOTSTRAP=instana.django`
25+
## Django
26+
27+
To enable the Django instrumentation, set the following environment variable in your _application boot environment_ and then restart your application:
28+
29+
`export AUTOWRAPT_BOOTSTRAP=django`
30+
31+
## Flask
32+
33+
To enable the Flask instrumentation, set the following environment variable in your _application boot environment_ and then restart your application:
34+
35+
`export AUTOWRAPT_BOOTSTRAP=flask`
36+
37+
## Runtime Monitoring Only
38+
39+
_Note: When the Django or Flask instrumentation is used, runtime monitoring is automatically included. Use this section if you only want to see runtime metrics._
40+
41+
To enable runtime monitoring (without request tracing), set the following environment variable in your _application boot environment_ and then restart your application:
42+
43+
`export AUTOWRAPT_BOOTSTRAP=runtime`
2744

2845
## Usage
2946

instana/agent.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ def full_request_response(self, url, method, o, body, header):
8080
request = urllib2.Request(url, self.to_json(o))
8181
request.add_header("Content-Type", "application/json")
8282

83-
l.debug("request: ", method, self.to_json(o))
8483
response = urllib2.urlopen(request, timeout=2)
8584

8685
if not response:

instana/flaskana.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from instana import wsgi
2+
from flask.cli import ScriptInfo
3+
4+
5+
def wrap_load_app(func):
6+
def wrapper(self, *args):
7+
app = func(self, *args)
8+
app.wsgi_app = wsgi.iWSGIMiddleware(app.wsgi_app)
9+
return app
10+
return wrapper
11+
12+
13+
def hook(module):
14+
""" Hook method to install the Instana middleware into Flask """
15+
ScriptInfo.load_app = wrap_load_app(ScriptInfo.load_app)

instana/meter.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,12 @@ def process(self):
148148

149149
def collect_snapshot(self):
150150
try:
151-
s = Snapshot(name=self.sensor.service_name,
151+
if "FLASK_APP" in os.environ:
152+
appname = os.environ["FLASK_APP"]
153+
else:
154+
appname = os.path.basename(sys.executable)
155+
156+
s = Snapshot(name=appname,
152157
version=sys.version,
153158
rlimit_core=resource.getrlimit(resource.RLIMIT_CORE),
154159
rlimit_cpu=resource.getrlimit(resource.RLIMIT_CPU),

instana/propagator.py

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import absolute_import
22
import opentracing as ot
3-
from instana import util
3+
from basictracer.context import SpanContext
4+
from instana import util, log
45

56
prefix_tracer_state = 'HTTP_X_INSTANA_'
67
field_name_trace_id = prefix_tracer_state + 'T'
@@ -12,27 +13,40 @@ class HTTPPropagator():
1213
"""A Propagator for Format.HTTP_HEADERS. """
1314

1415
def inject(self, span_context, carrier):
15-
carrier[field_name_trace_id] = util.id_to_header(span_context.trace_id)
16-
carrier[field_name_span_id] = util.id_to_header(span_context.span_id)
16+
try:
17+
trace_id = util.id_to_header(span_context.trace_id)
18+
span_id = util.id_to_header(span_context.span_id)
19+
if type(carrier) is dict:
20+
carrier[field_name_trace_id] = trace_id
21+
carrier[field_name_span_id] = span_id
22+
elif type(carrier) is list:
23+
trace_header = (field_name_trace_id, trace_id)
24+
carrier.append(trace_header)
25+
span_header = (field_name_span_id, span_id)
26+
carrier.append(span_header)
27+
else:
28+
raise Exception("Unsupported carrier type", type(carrier))
29+
30+
except Exception as e:
31+
log.debug("inject error: ", str(e))
1732

1833
def extract(self, carrier): # noqa
19-
count = 0
20-
span_id, trace_id = (0, 0)
21-
for k in carrier:
22-
v = carrier[k]
23-
k = k.lower()
24-
if k == field_name_span_id:
25-
span_id = util.header_to_id(v)
26-
count += 1
27-
elif k == field_name_trace_id:
28-
trace_id = util.header_to_id(v)
29-
count += 1
30-
31-
if count != field_count:
32-
raise ot.SpanContextCorruptedException()
33-
34-
return ot.SpanContext(
35-
span_id=span_id,
36-
trace_id=trace_id,
37-
baggage={},
38-
sampled=True)
34+
try:
35+
if type(carrier) is dict:
36+
dc = carrier
37+
elif type(carrier) is list:
38+
dc = dict(carrier)
39+
else:
40+
raise ot.SpanContextCorruptedException()
41+
42+
if field_name_trace_id in dc and field_name_span_id in dc:
43+
trace_id = util.header_to_id(dc[field_name_trace_id])
44+
span_id = util.header_to_id(dc[field_name_span_id])
45+
46+
return SpanContext(span_id=span_id,
47+
trace_id=trace_id,
48+
baggage={},
49+
sampled=True)
50+
51+
except Exception as e:
52+
log.debug("extract error: ", str(e))

instana/recorder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
class InstanaRecorder(SpanRecorder):
1818
sensor = None
19-
registered_spans = ("django", "memcache", "rpc-client", "rpc-server")
19+
registered_spans = ("django", "memcache", "rpc-client", "rpc-server", "wsgi")
2020
entry_kind = ["entry", "server", "consumer"]
2121
exit_kind = ["exit", "client", "producer"]
2222
queue = queue.Queue()

instana/runtime.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import opentracing as ot
2+
from instana import tracer, options
3+
import logging
4+
import os
5+
6+
7+
def hook(module):
8+
""" Hook method to install the Instana middleware into Flask """
9+
if os.environ["AUTOWRAPT_BOOTSTRAP"] == "runtime":
10+
if "INSTANA_DEV" in os.environ:
11+
level = logging.DEBUG
12+
else:
13+
level = logging.WARN
14+
opts = options.Options(log_level=level)
15+
ot.global_tracer = tracer.InstanaTracer(opts)

instana/span.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class InstanaSpan(object):
1616
def __init__(self, **kwds):
1717
self.__dict__.update(kwds)
1818

19+
1920
class Data(object):
2021
service = None
2122
http = None
@@ -26,6 +27,7 @@ class Data(object):
2627
def __init__(self, **kwds):
2728
self.__dict__.update(kwds)
2829

30+
2931
class HttpData(object):
3032
host = None
3133
url = None
@@ -35,13 +37,15 @@ class HttpData(object):
3537
def __init__(self, **kwds):
3638
self.__dict__.update(kwds)
3739

40+
3841
class CustomData(object):
3942
tags = None
4043
logs = None
4144

4245
def __init__(self, **kwds):
4346
self.__dict__.update(kwds)
4447

48+
4549
class SDKData(object):
4650
name = None
4751
Type = None

instana/wsgi.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from __future__ import print_function
2+
import opentracing as ot
3+
from instana import tracer, options
4+
import opentracing.ext.tags as ext
5+
import logging
6+
7+
8+
class iWSGIMiddleware(object):
9+
""" Instana WSGI middleware """
10+
11+
def __init__(self, app):
12+
self.app = app
13+
opts = options.Options(log_level=logging.DEBUG)
14+
ot.global_tracer = tracer.InstanaTracer(opts)
15+
self
16+
17+
def __call__(self, environ, start_response):
18+
env = environ
19+
20+
def new_start_response(status, headers, exc_info=None):
21+
"""Modified start response with additional headers."""
22+
ot.global_tracer.inject(span.context, ot.Format.HTTP_HEADERS, headers)
23+
res = start_response(status, headers, exc_info)
24+
span.set_tag(ext.HTTP_STATUS_CODE, status.split(' ')[0])
25+
span.finish()
26+
return res
27+
28+
if 'HTTP_X_INSTANA_T' in env and 'HTTP_X_INSTANA_S' in env:
29+
ctx = ot.global_tracer.extract(ot.Format.HTTP_HEADERS, env)
30+
span = ot.global_tracer.start_span("wsgi", child_of=ctx)
31+
else:
32+
span = ot.global_tracer.start_span("wsgi")
33+
34+
span.set_tag(ext.HTTP_URL, env['PATH_INFO'])
35+
span.set_tag("http.params", env['QUERY_STRING'])
36+
span.set_tag(ext.HTTP_METHOD, env['REQUEST_METHOD'])
37+
span.set_tag("http.host", env['HTTP_HOST'])
38+
39+
return self.app(environ, new_start_response)
40+
41+
42+
def make_middleware(app=None, *args, **kw):
43+
""" Given an app, return that app wrapped in iWSGIMiddleware """
44+
app = iWSGIMiddleware(app, *args, **kw)
45+
return app

setup.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
'opentracing>=1.2.1,<1.3',
1818
'basictracer>=2.2.0',
1919
'psutil>=5.1.3'],
20-
entry_points={'instana.django': ['django.core.handlers.base = instana.django:hook']},
20+
entry_points={'django': ['django.core.handlers.base = instana.django:hook'],
21+
'flask': ['flask.cli = instana.flaskana:hook'],
22+
'runtime': ['string = instana.runtime:hook']},
2123
test_suite='nose.collector',
2224
keywords=['performance', 'opentracing', 'metrics', 'monitoring'],
2325
classifiers=[

0 commit comments

Comments
 (0)