Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0d36c0c
Manually update init keys to avoid overwriting via update
pglombardo Dec 22, 2017
8df3cec
Add urllib3 as a registered span
pglombardo Dec 22, 2017
0884e7c
Boot background flask app to test against.
pglombardo Dec 22, 2017
e8aa850
Add nosetests json file
pglombardo Dec 22, 2017
c0df08b
Add instrumentation dir with urllib3 instrumentation
pglombardo Dec 22, 2017
7bf23a7
Urllib3 instrumentation tests
pglombardo Dec 22, 2017
fbe7f0c
Add Flask as a dependency
pglombardo Dec 22, 2017
7643103
Separate out test requirements from production
pglombardo Dec 22, 2017
ee6012f
Updated package initialization; automatic tracer instantiation
pglombardo Dec 22, 2017
581099d
Update to follow changes; fill out configuration doc
pglombardo Dec 22, 2017
1820acb
Load instrumentation directly
pglombardo Dec 22, 2017
1f19399
Better named import
pglombardo Dec 22, 2017
697d56d
Expand 5xx coverage; log exceptions
pglombardo Dec 23, 2017
e86f88b
More routes to test against
pglombardo Dec 23, 2017
3dda05c
Add tests for errors & exceptions
pglombardo Dec 23, 2017
b39471d
Use opentracing.tracer
pglombardo Dec 23, 2017
226a4c1
Make sure to finish span when there is an exception
pglombardo Dec 23, 2017
4bfca0b
Add test to validate tracing through client errors.
pglombardo Dec 23, 2017
34ea942
Add a way to retrieve current context of active span
pglombardo Dec 24, 2017
7c1d628
Make urllib3 childof current span; Add tests to validate
pglombardo Dec 24, 2017
f7a16d4
Runtime metrics are now automatic. Remove runtime section.
pglombardo Dec 27, 2017
ad914ab
Have instrumentation always use internal_tracer
pglombardo Dec 27, 2017
83002fa
Better non-*nix process command line handling.
pglombardo Dec 27, 2017
80a130c
Fix environment variable name
pglombardo Dec 27, 2017
dad4f3d
Fix OpenTracing example
pglombardo Dec 27, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ htmlcov/
.coverage.*
.cache
nosetests.xml
nosetests.json
coverage.xml
*,cover
.hypothesis/
Expand Down
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ python:
- "3.4"
- "3.5"
- "3.6"
install: "pip install -r requirements.txt"
install: "pip install -r test_requirements.txt"
script: nosetests -v
22 changes: 21 additions & 1 deletion Configuration.md
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
TBD
# Configuration

## Agent Communication

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.

To use these, these environment variables should be set in the environment of the running Python process.

```shell
export INSTANA_AGENT_IP = '127.0.0.1'
export INSTANA_AGENT_PORT = '42699'
```

## Debugging & More Verbosity

Setting `INSTANA_DEV` to a non nil value will enable extra logging output generally useful
for development.

```Python
export INSTANA_DEV="true"
```
30 changes: 20 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,6 @@ To enable the Flask instrumentation, set the following environment variable in y

`export AUTOWRAPT_BOOTSTRAP=flask`

## Runtime Monitoring Only

_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._

To enable runtime monitoring (without request tracing), set the following environment variable in your _application boot environment_ and then restart your application:

`export AUTOWRAPT_BOOTSTRAP=runtime`

## uWSGI

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

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

## Tracing
## OpenTracing

This Python package supports [OpenTracing](http://opentracing.io/). When using this package, the OpenTracing tracer (`opentracing.tracer`) is automatically set to the `InstanaTracer`.

```Python
import opentracing

parent_span = opentracing.tracer.start_span(operation_name="asteroid")
# ... work
child_span = opentracing.tracer.start_span(operation_name="spacedust", child_of=parent_span)
child_span.set_tag(ext.SPAN_KIND, ext.SPAN_KIND_RPC_CLIENT)
# ... work
child_span.finish()
# ... work
parent_span.finish()
```

## Configuration

See [Configuration.md](https://github.com/instana/python-sensor/blob/master/Configuration.md)

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

## Documentation

Expand Down
41 changes: 37 additions & 4 deletions instana/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
from __future__ import absolute_import
import opentracing
from .sensor import Sensor
from .tracer import InstanaTracer
from .options import Options

# Import & initialize instrumentation
from .instrumentation import urllib3

"""
Instana sensor and tracer. It consists of two modules that can be used as entry points:
The Instana package has two core components: the sensor and the tracer.

The sensor is individual to each python process and handles process metric
collection and reporting.

- sensor: activates the meter to collect and transmit all kind of built-in metrics
- tracer: OpenTracing tracer implementation. It implicitly activates the meter
The tracer upholds the OpenTracing API and is responsible for reporting
span data to Instana.
"""

__author__ = 'Instana Inc.'
Expand All @@ -13,4 +25,25 @@
__maintainer__ = 'Peter Giacomo Lombardo'
__email__ = 'peter.lombardo@instana.com'

__all__ = ['sensor', 'tracer']
# For any given Python process, we only want one sensor as multiple would
# collect/report metrics in duplicate, triplicate etc..
#
# Usage example:
#
# import instana
# instana.global_sensor
#
global_sensor = Sensor(Options())

# The global OpenTracing compatible tracer used internally by
# this package.
#
# Usage example:
#
# import instana
# instana.internal_tracer.start_span(...)
#
internal_tracer = InstanaTracer()

# Set ourselves as the tracer.
opentracing.tracer = internal_tracer
12 changes: 5 additions & 7 deletions instana/django.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import print_function
import opentracing as ot
from instana import tracer, options
from instana import internal_tracer
import opentracing.ext.tags as ext
import os

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

def __call__(self, request):
env = request.environ
if 'HTTP_X_INSTANA_T' in env and 'HTTP_X_INSTANA_S' in env:
ctx = ot.global_tracer.extract(ot.Format.HTTP_HEADERS, env)
span = ot.global_tracer.start_span("django", child_of=ctx)
ctx = internal_tracer.extract(ot.Format.HTTP_HEADERS, env)
span = internal_tracer.start_span("django", child_of=ctx)
else:
span = ot.global_tracer.start_span("django")
span = internal_tracer.start_span("django")

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

span.set_tag(ext.HTTP_STATUS_CODE, response.status_code)
ot.global_tracer.inject(span.context, ot.Format.HTTP_HEADERS, response)
internal_tracer.inject(span.context, ot.Format.HTTP_HEADERS, response)
span.finish()
return response

Expand Down
12 changes: 5 additions & 7 deletions instana/django19.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import print_function
import opentracing as ot
from instana import tracer, options
from instana import internal_tracer
import opentracing.ext.tags as ext
import os

Expand All @@ -11,18 +11,16 @@
class InstanaMiddleware19(object):
""" Django 1.9 Middleware to provide request tracing for Instana """
def __init__(self):
opts = options.Options(service="Django")
ot.global_tracer = tracer.InstanaTracer(opts)
self.span = None
self

def process_request(self, request):
env = request.environ
if 'HTTP_X_INSTANA_T' in env and 'HTTP_X_INSTANA_S' in env:
ctx = ot.global_tracer.extract(ot.Format.HTTP_HEADERS, env)
span = ot.global_tracer.start_span("django", child_of=ctx)
ctx = internal_tracer.extract(ot.Format.HTTP_HEADERS, env)
span = internal_tracer.start_span("django", child_of=ctx)
else:
span = ot.global_tracer.start_span("django")
span = internal_tracer.start_span("django")

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

self.span.set_tag(ext.HTTP_STATUS_CODE, response.status_code)
ot.global_tracer.inject(self.span.context, ot.Format.HTTP_HEADERS, response)
internal_tracer.inject(self.span.context, ot.Format.HTTP_HEADERS, response)
self.span.finish()
self.span = None
return response
Expand Down
2 changes: 1 addition & 1 deletion instana/fsm.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def announce_sensor(self, e):
cmdinfo = cmd.read()
cmdline = cmdinfo.split('\x00')
else:
cmdline = [os.path.basename(sys.executable)]
cmdline = [sys.executable]
cmdline += sys.argv

d = Discovery(pid=pid,
Expand Down
Empty file.
37 changes: 37 additions & 0 deletions instana/instrumentation/urllib3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from __future__ import absolute_import
import opentracing.ext.tags as ext
import instana
import opentracing
import wrapt


@wrapt.patch_function_wrapper('urllib3', 'PoolManager.urlopen')
def urlopen_with_instana(wrapped, instance, args, kwargs):
try:
context = instana.internal_tracer.current_context()
span = instana.internal_tracer.start_span("urllib3", child_of=context)
span.set_tag(ext.HTTP_URL, args[1])
span.set_tag(ext.HTTP_METHOD, args[0])

instana.internal_tracer.inject(span.context, opentracing.Format.HTTP_HEADERS, kwargs["headers"])
rv = wrapped(*args, **kwargs)

span.set_tag(ext.HTTP_STATUS_CODE, rv.status)
if 500 <= rv.status <= 599:
span.set_tag("error", True)
ec = span.tags.get('ec', 0)
span.set_tag("ec", ec+1)

except Exception as e:
span.log_kv({'message': e})
span.set_tag("error", True)
ec = span.tags.get('ec', 0)
span.set_tag("ec", ec+1)
span.finish()
raise
else:
span.finish()
return rv


instana.log.debug("Instrumenting urllib3")
2 changes: 1 addition & 1 deletion instana/probe.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
# c. Detect and instrument any libraries

opts = options.Options()
ot.global_tracer = tracer.InstanaTracer(opts)
ot.tracer = tracer.InstanaTracer(opts)
2 changes: 1 addition & 1 deletion instana/recorder.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

class InstanaRecorder(SpanRecorder):
sensor = None
registered_spans = ("django", "memcache", "rpc-client", "rpc-server", "wsgi")
registered_spans = ("django", "memcache", "rpc-client", "rpc-server", "urllib3", "wsgi")
entry_kind = ["entry", "server", "consumer"]
exit_kind = ["exit", "client", "producer"]
queue = queue.Queue()
Expand Down
2 changes: 1 addition & 1 deletion instana/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ def hook(module):
print("==========================================================")

opts = options.Options()
ot.global_tracer = tracer.InstanaTracer(opts)
ot.tracer = tracer.InstanaTracer(opts)
5 changes: 3 additions & 2 deletions instana/span.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ class InstanaSpan(object):
n = None
f = None
ec = 0
error = False
error = None
data = None

def __init__(self, **kwds):
self.__dict__.update(kwds)
for key in kwds:
self.__dict__[key] = kwds[key]


class Data(object):
Expand Down
22 changes: 9 additions & 13 deletions instana/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,22 @@
from basictracer import BasicTracer
import instana.recorder as r
import opentracing as ot
import instana
import instana.options as o
import instana.sensor as s

from basictracer.context import SpanContext
from basictracer.span import BasicSpan
from instana.http_propagator import HTTPPropagator
from instana.text_propagator import TextPropagator
from instana.util import generate_id

# In case a user or app creates multiple tracers, we limit to just
# one sensor per process otherwise metrics collection is duplicated,
# triplicated etc.
gSensor = None


class InstanaTracer(BasicTracer):
sensor = None
current_span = None

def __init__(self, options=o.Options()):
global gSensor
if gSensor is None:
self.sensor = s.Sensor(options)
gSensor = self.sensor
else:
self.sensor = gSensor
self.sensor = instana.global_sensor
super(InstanaTracer, self).__init__(
r.InstanaRecorder(self.sensor), r.InstanaSampler())

Expand Down Expand Up @@ -67,14 +58,19 @@ def start_span(
ctx.sampled = self.sampler.sampled(ctx.trace_id)

# Tie it all together
return BasicSpan(
self.current_span = BasicSpan(
self,
operation_name=operation_name,
context=ctx,
parent_id=(None if parent_ctx is None else parent_ctx.span_id),
tags=tags,
start_time=start_time)

return self.current_span

def current_context(self):
return self.current_span.context

def inject(self, span_context, format, carrier):
if format in self._propagators:
self._propagators[format].inject(span_context, carrier)
Expand Down
12 changes: 5 additions & 7 deletions instana/wsgi.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import opentracing as ot
from instana import tracer, options
from instana import internal_tracer
import opentracing.ext.tags as tags


Expand All @@ -8,16 +8,14 @@ class iWSGIMiddleware(object):

def __init__(self, app):
self.app = app
opts = options.Options()
ot.global_tracer = tracer.InstanaTracer(opts)
self

def __call__(self, environ, start_response):
env = environ

def new_start_response(status, headers, exc_info=None):
"""Modified start response with additional headers."""
ot.global_tracer.inject(span.context, ot.Format.HTTP_HEADERS, headers)
internal_tracer.inject(span.context, ot.Format.HTTP_HEADERS, headers)
res = start_response(status, headers, exc_info)

sc = status.split(' ')[0]
Expand All @@ -31,10 +29,10 @@ def new_start_response(status, headers, exc_info=None):
return res

if 'HTTP_X_INSTANA_T' in env and 'HTTP_X_INSTANA_S' in env:
ctx = ot.global_tracer.extract(ot.Format.HTTP_HEADERS, env)
span = ot.global_tracer.start_span("wsgi", child_of=ctx)
ctx = internal_tracer.extract(ot.Format.HTTP_HEADERS, env)
span = internal_tracer.start_span("wsgi", child_of=ctx)
else:
span = ot.global_tracer.start_span("wsgi")
span = internal_tracer.start_span("wsgi")

span.set_tag(tags.HTTP_URL, env['PATH_INFO'])
span.set_tag("http.params", env['QUERY_STRING'])
Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
nose>=1.0
fysom>=2.1.2
opentracing>=1.2.1
basictracer>=2.2.0
Expand Down
Loading