Skip to content

Commit 3950b85

Browse files
authored
Merge pull request #43 from instana/urllib3
Urllib3 instrumentation & tests
2 parents 17d8075 + dad4f3d commit 3950b85

26 files changed

+376
-81
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ htmlcov/
4242
.coverage.*
4343
.cache
4444
nosetests.xml
45+
nosetests.json
4546
coverage.xml
4647
*,cover
4748
.hypothesis/

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ python:
55
- "3.4"
66
- "3.5"
77
- "3.6"
8-
install: "pip install -r requirements.txt"
8+
install: "pip install -r test_requirements.txt"
99
script: nosetests -v

Configuration.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,21 @@
1-
TBD
1+
# Configuration
2+
3+
## Agent Communication
4+
5+
The sensor tries to communicate with the Instana agent via IP 127.0.0.1 and as a fallback via the host's default gateway for containerized environments. Should the agent not be available under either of these IPs, e.g. due to iptables or other networking tricks, you can use environment variables to configure where the Instana host agent lives.
6+
7+
To use these, these environment variables should be set in the environment of the running Python process.
8+
9+
```shell
10+
export INSTANA_AGENT_IP = '127.0.0.1'
11+
export INSTANA_AGENT_PORT = '42699'
12+
```
13+
14+
## Debugging & More Verbosity
15+
16+
Setting `INSTANA_DEV` to a non nil value will enable extra logging output generally useful
17+
for development.
18+
19+
```Python
20+
export INSTANA_DEV="true"
21+
```

README.md

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,6 @@ To enable the Flask instrumentation, set the following environment variable in y
3535

3636
`export AUTOWRAPT_BOOTSTRAP=flask`
3737

38-
## Runtime Monitoring Only
39-
40-
_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._
41-
42-
To enable runtime monitoring (without request tracing), set the following environment variable in your _application boot environment_ and then restart your application:
43-
44-
`export AUTOWRAPT_BOOTSTRAP=runtime`
45-
4638
## uWSGI
4739

4840
### Threads
@@ -57,9 +49,27 @@ If you use uWSGI in forking workers mode, you must specify `--lazy-apps` (or `la
5749

5850
The instana package will automatically collect key metrics from your Python processes. Just install and go.
5951

60-
## Tracing
52+
## OpenTracing
53+
54+
This Python package supports [OpenTracing](http://opentracing.io/). When using this package, the OpenTracing tracer (`opentracing.tracer`) is automatically set to the `InstanaTracer`.
55+
56+
```Python
57+
import opentracing
58+
59+
parent_span = opentracing.tracer.start_span(operation_name="asteroid")
60+
# ... work
61+
child_span = opentracing.tracer.start_span(operation_name="spacedust", child_of=parent_span)
62+
child_span.set_tag(ext.SPAN_KIND, ext.SPAN_KIND_RPC_CLIENT)
63+
# ... work
64+
child_span.finish()
65+
# ... work
66+
parent_span.finish()
67+
```
68+
69+
## Configuration
70+
71+
See [Configuration.md](https://github.com/instana/python-sensor/blob/master/Configuration.md)
6172

62-
This Python package supports [OpenTracing](http://opentracing.io/).
6373

6474
## Documentation
6575

instana/__init__.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
1+
from __future__ import absolute_import
2+
import opentracing
3+
from .sensor import Sensor
4+
from .tracer import InstanaTracer
5+
from .options import Options
6+
7+
# Import & initialize instrumentation
8+
from .instrumentation import urllib3
9+
110
"""
2-
Instana sensor and tracer. It consists of two modules that can be used as entry points:
11+
The Instana package has two core components: the sensor and the tracer.
12+
13+
The sensor is individual to each python process and handles process metric
14+
collection and reporting.
315
4-
- sensor: activates the meter to collect and transmit all kind of built-in metrics
5-
- tracer: OpenTracing tracer implementation. It implicitly activates the meter
16+
The tracer upholds the OpenTracing API and is responsible for reporting
17+
span data to Instana.
618
"""
719

820
__author__ = 'Instana Inc.'
@@ -13,4 +25,25 @@
1325
__maintainer__ = 'Peter Giacomo Lombardo'
1426
__email__ = '[email protected]'
1527

16-
__all__ = ['sensor', 'tracer']
28+
# For any given Python process, we only want one sensor as multiple would
29+
# collect/report metrics in duplicate, triplicate etc..
30+
#
31+
# Usage example:
32+
#
33+
# import instana
34+
# instana.global_sensor
35+
#
36+
global_sensor = Sensor(Options())
37+
38+
# The global OpenTracing compatible tracer used internally by
39+
# this package.
40+
#
41+
# Usage example:
42+
#
43+
# import instana
44+
# instana.internal_tracer.start_span(...)
45+
#
46+
internal_tracer = InstanaTracer()
47+
48+
# Set ourselves as the tracer.
49+
opentracing.tracer = internal_tracer

instana/django.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import print_function
22
import opentracing as ot
3-
from instana import tracer, options
3+
from instana import internal_tracer
44
import opentracing.ext.tags as ext
55
import os
66

@@ -12,17 +12,15 @@ class InstanaMiddleware(object):
1212
""" Django Middleware to provide request tracing for Instana """
1313
def __init__(self, get_response):
1414
self.get_response = get_response
15-
opts = options.Options(service="Django")
16-
ot.global_tracer = tracer.InstanaTracer(opts)
1715
self
1816

1917
def __call__(self, request):
2018
env = request.environ
2119
if 'HTTP_X_INSTANA_T' in env and 'HTTP_X_INSTANA_S' in env:
22-
ctx = ot.global_tracer.extract(ot.Format.HTTP_HEADERS, env)
23-
span = ot.global_tracer.start_span("django", child_of=ctx)
20+
ctx = internal_tracer.extract(ot.Format.HTTP_HEADERS, env)
21+
span = internal_tracer.start_span("django", child_of=ctx)
2422
else:
25-
span = ot.global_tracer.start_span("django")
23+
span = internal_tracer.start_span("django")
2624

2725
span.set_tag(ext.HTTP_URL, env['PATH_INFO'])
2826
span.set_tag("http.params", env['QUERY_STRING'])
@@ -37,7 +35,7 @@ def __call__(self, request):
3735
span.set_tag("ec", ec+1)
3836

3937
span.set_tag(ext.HTTP_STATUS_CODE, response.status_code)
40-
ot.global_tracer.inject(span.context, ot.Format.HTTP_HEADERS, response)
38+
internal_tracer.inject(span.context, ot.Format.HTTP_HEADERS, response)
4139
span.finish()
4240
return response
4341

instana/django19.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import print_function
22
import opentracing as ot
3-
from instana import tracer, options
3+
from instana import internal_tracer
44
import opentracing.ext.tags as ext
55
import os
66

@@ -11,18 +11,16 @@
1111
class InstanaMiddleware19(object):
1212
""" Django 1.9 Middleware to provide request tracing for Instana """
1313
def __init__(self):
14-
opts = options.Options(service="Django")
15-
ot.global_tracer = tracer.InstanaTracer(opts)
1614
self.span = None
1715
self
1816

1917
def process_request(self, request):
2018
env = request.environ
2119
if 'HTTP_X_INSTANA_T' in env and 'HTTP_X_INSTANA_S' in env:
22-
ctx = ot.global_tracer.extract(ot.Format.HTTP_HEADERS, env)
23-
span = ot.global_tracer.start_span("django", child_of=ctx)
20+
ctx = internal_tracer.extract(ot.Format.HTTP_HEADERS, env)
21+
span = internal_tracer.start_span("django", child_of=ctx)
2422
else:
25-
span = ot.global_tracer.start_span("django")
23+
span = internal_tracer.start_span("django")
2624

2725
span.set_tag(ext.HTTP_URL, env['PATH_INFO'])
2826
span.set_tag("http.params", env['QUERY_STRING'])
@@ -38,7 +36,7 @@ def process_response(self, request, response):
3836
self.span.set_tag("ec", ec+1)
3937

4038
self.span.set_tag(ext.HTTP_STATUS_CODE, response.status_code)
41-
ot.global_tracer.inject(self.span.context, ot.Format.HTTP_HEADERS, response)
39+
internal_tracer.inject(self.span.context, ot.Format.HTTP_HEADERS, response)
4240
self.span.finish()
4341
self.span = None
4442
return response

instana/fsm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def announce_sensor(self, e):
124124
cmdinfo = cmd.read()
125125
cmdline = cmdinfo.split('\x00')
126126
else:
127-
cmdline = [os.path.basename(sys.executable)]
127+
cmdline = [sys.executable]
128128
cmdline += sys.argv
129129

130130
d = Discovery(pid=pid,

instana/instrumentation/__init__.py

Whitespace-only changes.

instana/instrumentation/urllib3.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from __future__ import absolute_import
2+
import opentracing.ext.tags as ext
3+
import instana
4+
import opentracing
5+
import wrapt
6+
7+
8+
@wrapt.patch_function_wrapper('urllib3', 'PoolManager.urlopen')
9+
def urlopen_with_instana(wrapped, instance, args, kwargs):
10+
try:
11+
context = instana.internal_tracer.current_context()
12+
span = instana.internal_tracer.start_span("urllib3", child_of=context)
13+
span.set_tag(ext.HTTP_URL, args[1])
14+
span.set_tag(ext.HTTP_METHOD, args[0])
15+
16+
instana.internal_tracer.inject(span.context, opentracing.Format.HTTP_HEADERS, kwargs["headers"])
17+
rv = wrapped(*args, **kwargs)
18+
19+
span.set_tag(ext.HTTP_STATUS_CODE, rv.status)
20+
if 500 <= rv.status <= 599:
21+
span.set_tag("error", True)
22+
ec = span.tags.get('ec', 0)
23+
span.set_tag("ec", ec+1)
24+
25+
except Exception as e:
26+
span.log_kv({'message': e})
27+
span.set_tag("error", True)
28+
ec = span.tags.get('ec', 0)
29+
span.set_tag("ec", ec+1)
30+
span.finish()
31+
raise
32+
else:
33+
span.finish()
34+
return rv
35+
36+
37+
instana.log.debug("Instrumenting urllib3")

0 commit comments

Comments
 (0)