Skip to content

Commit a5621d3

Browse files
authored
Merge pull request #2 from instana/django
Django middlware instrumentation
2 parents 2a10ef5 + af59201 commit a5621d3

File tree

8 files changed

+132
-30
lines changed

8 files changed

+132
-30
lines changed

instana/agent.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import instana.log as l
33
import instana.fsm as f
44
import instana.agent_const as a
5+
import threading
56

67
try:
78
import urllib.request as urllib2
@@ -76,6 +77,7 @@ def full_request_response(self, url, method, o, body, header):
7677
request = urllib2.Request(url, self.to_json(o))
7778
request.add_header("Content-Type", "application/json")
7879

80+
# print self.to_json(o)
7981
response = urllib2.urlopen(request, timeout=2)
8082

8183
if not response:
@@ -96,7 +98,10 @@ def full_request_response(self, url, method, o, body, header):
9698
if method == "HEAD":
9799
b = True
98100
except Exception as e:
99-
l.error("full_request_response: " + str(e))
101+
# No need to show the initial 404s or timeouts. The agent
102+
# should handle those correctly.
103+
if type(e) is urllib2.HTTPError and e.code != 404:
104+
l.error("%s: full_request_response: %s" % (threading.current_thread().name, str(e)))
100105

101106
return (b, h)
102107

instana/fsm.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class Fsm(object):
2424
timer = None
2525

2626
def __init__(self, agent):
27+
l.info("Stan is on the scene. Starting Instana instrumentation.")
2728
l.debug("initializing fsm")
2829

2930
self.agent = agent
@@ -41,7 +42,7 @@ def __init__(self, agent):
4142

4243
def printstatechange(self, e):
4344
l.debug('========= (%i#%s) FSM event: %s, src: %s, dst: %s ==========' % \
44-
(os.getpid(), t.current_thread().name, e.event, e.src, e.dst))
45+
(os.getpid(), t.current_thread().name, e.event, e.src, e.dst))
4546

4647
def reset(self):
4748
self.fsm.lookup()
@@ -106,6 +107,7 @@ def announce_sensor(self, e):
106107
else:
107108
self.agent.set_from(b)
108109
self.fsm.ready()
110+
l.warn("Host agent available. We're in business. (Announced pid: %i)" % p.pid)
109111
return True
110112

111113
def schedule_retry(self, fun, e, name):

instana/middleware.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import opentracing as ot
2+
from instana import tracer
3+
import opentracing.ext.tags as ext
4+
5+
6+
class InstanaMiddleware(object):
7+
def __init__(self, get_response):
8+
self.get_response = get_response
9+
ot.global_tracer = tracer.InstanaTracer()
10+
self
11+
12+
def __call__(self, request):
13+
env = request.environ
14+
if 'HTTP_X_INSTANA_T' in env and 'HTTP_X_INSTANA_S' in env:
15+
ctx = ot.global_tracer.extract(ot.Format.HTTP_HEADERS, env)
16+
span = ot.global_tracer.start_span("django", child_of=ctx)
17+
else:
18+
span = ot.global_tracer.start_span("django")
19+
20+
span.set_tag(ext.HTTP_URL, env['PATH_INFO'])
21+
span.set_tag("http.params", env['QUERY_STRING'])
22+
span.set_tag(ext.HTTP_METHOD, request.method)
23+
span.set_tag("http.host", env['HTTP_HOST'])
24+
25+
response = self.get_response(request)
26+
27+
span.set_tag(ext.HTTP_STATUS_CODE, response.status_code)
28+
ot.global_tracer.inject(span.context, ot.Format.HTTP_HEADERS, response)
29+
span.finish()
30+
return response

instana/options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class Options(object):
55
service = ''
66
agent_host = ''
77
agent_port = 0
8-
log_level = logging.ERROR
8+
log_level = logging.WARN
99

1010
def __init__(self, **kwds):
1111
self.__dict__.update(kwds)

instana/propagator.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from __future__ import absolute_import
2+
import opentracing as ot
3+
from instana import util
4+
5+
prefix_tracer_state = 'HTTP_X_INSTANA_'
6+
field_name_trace_id = prefix_tracer_state + 'T'
7+
field_name_span_id = prefix_tracer_state + 'S'
8+
field_count = 2
9+
10+
11+
class HTTPPropagator():
12+
"""A Propagator for Format.HTTP_HEADERS. """
13+
14+
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)
17+
18+
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)

instana/recorder.py

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
class InstanaRecorder(SpanRecorder):
1212
sensor = None
13-
registered_spans = ("memcache", "rpc-client", "rpc-server")
13+
registered_spans = ("django", "memcache", "rpc-client", "rpc-server")
1414
entry_kind = ["entry", "server", "consumer"]
1515
exit_kind = ["exit", "client", "producer"]
1616
queue = Queue.Queue()
@@ -33,7 +33,7 @@ def report_spans(self):
3333
if self.sensor.agent.can_send() and self.queue.qsize:
3434
url = self.sensor.agent.make_url(a.AGENT_TRACES_URL)
3535
self.sensor.agent.request(url, "POST", self.queued_spans())
36-
time.sleep(2)
36+
time.sleep(1)
3737

3838
def queue_size(self):
3939
""" Return the size of the queue; how may spans are queued, """
@@ -72,21 +72,20 @@ def record_span(self, span):
7272

7373
def build_registered_span(self, span):
7474
""" Takes a BasicSpan and converts it into a registered InstanaSpan """
75-
data = sd.Data(service=self.get_service_name(span),
76-
http=sd.HttpData(host=self.get_host_name(span),
75+
data = sd.Data(http=sd.HttpData(host=self.get_host_name(span),
7776
url=self.get_string_tag(span, ext.HTTP_URL),
7877
method=self.get_string_tag(span, ext.HTTP_METHOD),
7978
status=self.get_tag(span, ext.HTTP_STATUS_CODE)),
8079
baggage=span.context.baggage,
8180
custom=sd.CustomData(tags=span.tags,
8281
logs=self.collect_logs(span)))
8382
return sd.InstanaSpan(
83+
n=span.operation_name,
8484
t=span.context.trace_id,
8585
p=span.parent_id,
8686
s=span.context.span_id,
8787
ts=int(round(span.start_time * 1000)),
8888
d=int(round(span.duration * 1000)),
89-
n=self.get_http_type(span),
9089
f=self.sensor.agent.from_,
9190
data=data)
9291

@@ -102,13 +101,7 @@ def build_sdk_span(self, span):
102101
custom=custom_data
103102
)
104103

105-
if "span.kind" in span.tags:
106-
if span.tags["span.kind"] in self.entry_kind:
107-
sdk_data.Type = "entry"
108-
elif span.tags["span.kind"] in self.exit_kind:
109-
sdk_data.Type = "exit"
110-
else:
111-
sdk_data.Type = "local"
104+
sdk_data.Type = self.get_span_kind(span)
112105

113106
data = sd.Data(service=self.get_service_name(span),
114107
sdk=sdk_data)
@@ -137,7 +130,7 @@ def get_string_tag(self, span, tag):
137130
return ret
138131

139132
def get_host_name(self, span):
140-
h = self.get_string_tag(span, ext.PEER_HOSTNAME)
133+
h = self.get_string_tag(span, "http.host")
141134
if len(h) > 0:
142135
return h
143136

@@ -158,12 +151,16 @@ def get_service_name(self, span):
158151

159152
return self.sensor.service_name
160153

161-
def get_http_type(self, span):
162-
k = self.get_string_tag(span, ext.SPAN_KIND)
163-
if k == ext.SPAN_KIND_RPC_CLIENT:
164-
return http.HTTP_CLIENT
165-
166-
return http.HTTP_SERVER
154+
def get_span_kind(self, span):
155+
kind = ""
156+
if "span.kind" in span.tags:
157+
if span.tags["span.kind"] in self.entry_kind:
158+
kind = "entry"
159+
elif span.tags["span.kind"] in self.exit_kind:
160+
kind = "exit"
161+
else:
162+
kind = "local"
163+
return kind
167164

168165
def collect_logs(self, span):
169166
logs = {}

instana/tracer.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import time
22
from basictracer import BasicTracer
33
import instana.recorder as r
4-
import opentracing
4+
import opentracing as ot
55
import instana.options as o
66
import instana.sensor as s
7-
import instana.log as ilog
7+
import instana.propagator as tp
88

99
from basictracer.context import SpanContext
1010
from basictracer.span import BasicSpan
@@ -29,6 +29,8 @@ def __init__(self, options=o.Options()):
2929
super(InstanaTracer, self).__init__(
3030
r.InstanaRecorder(self.sensor), r.InstanaSampler())
3131

32+
self._propagators[ot.Format.HTTP_HEADERS] = tp.HTTPPropagator()
33+
3234
def start_span(
3335
self,
3436
operation_name=None,
@@ -44,7 +46,7 @@ def start_span(
4446
parent_ctx = None
4547
if child_of is not None:
4648
parent_ctx = (
47-
child_of if isinstance(child_of, opentracing.SpanContext)
49+
child_of if isinstance(child_of, ot.SpanContext)
4850
else child_of.context)
4951
elif references is not None and len(references) > 0:
5052
# TODO only the first reference is currently used
@@ -71,6 +73,18 @@ def start_span(
7173
tags=tags,
7274
start_time=start_time)
7375

76+
def inject(self, span_context, format, carrier):
77+
if format in self._propagators:
78+
self._propagators[format].inject(span_context, carrier)
79+
else:
80+
raise ot.UnsupportedFormatException()
81+
82+
def extract(self, format, carrier):
83+
if format in self._propagators:
84+
return self._propagators[format].extract(carrier)
85+
else:
86+
raise ot.UnsupportedFormatException()
87+
7488

7589
def init(options):
76-
opentracing.tracer = InstanaTracer(options)
90+
ot.tracer = InstanaTracer(options)

setup.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,35 @@
11
from setuptools import setup, find_packages
22

33
setup(name='instana',
4-
version='0.0.1',
5-
url='https://github.com/instana/python-sensor',
4+
version='0.0.1.2',
5+
download_url='https://github.com/instana/python-sensor',
6+
url='https://www.instana.com/',
67
license='MIT',
78
author='Instana Inc.',
89
author_email='[email protected]',
910
description='Metrics sensor and trace collector for Instana',
1011
packages=find_packages(exclude=['tests', 'examples']),
11-
long_description=open('README.md').read(),
12+
long_description="The instana package provides Python metrics and traces for Instana.",
1213
zip_safe=False,
1314
setup_requires=['nose>=1.0',
1415
'fysom>=2.1.2',
1516
'opentracing>=1.2.1,<1.3',
1617
'basictracer>=2.2.0',
1718
'psutil>=5.1.3'],
1819
test_suite='nose.collector',
19-
keywords=['performance', 'opentracing', 'metrics', 'monitoring'])
20+
keywords=['performance', 'opentracing', 'metrics', 'monitoring'],
21+
classifiers=[
22+
'Development Status :: 4 - Beta',
23+
'Intended Audience :: Developers',
24+
'Intended Audience :: Information Technology',
25+
'License :: OSI Approved :: MIT License',
26+
'Operating System :: OS Independent',
27+
'Programming Language :: Python',
28+
'Programming Language :: Python :: 2.7',
29+
'Programming Language :: Python :: 3',
30+
'Programming Language :: Python :: 3.4',
31+
'Programming Language :: Python :: 3.5',
32+
'Programming Language :: Python :: 3.6',
33+
'Topic :: System :: Monitoring',
34+
'Topic :: System :: Networking :: Monitoring',
35+
'Topic :: Software Development :: Libraries :: Python Modules'])

0 commit comments

Comments
 (0)