diff --git a/.gitignore b/.gitignore index c3e3ec75..a8dcd608 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ htmlcov/ .coverage.* .cache nosetests.xml +nosetests.json coverage.xml *,cover .hypothesis/ diff --git a/.travis.yml b/.travis.yml index a15ec293..ae6c37a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/Configuration.md b/Configuration.md index a0990367..356a0652 100644 --- a/Configuration.md +++ b/Configuration.md @@ -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" +``` diff --git a/README.md b/README.md index 0c7225e1..06c960b4 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 diff --git a/instana/__init__.py b/instana/__init__.py index 266ecf86..490bc190 100644 --- a/instana/__init__.py +++ b/instana/__init__.py @@ -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.' @@ -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 diff --git a/instana/django.py b/instana/django.py index 9f983352..604b5a4d 100644 --- a/instana/django.py +++ b/instana/django.py @@ -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 @@ -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']) @@ -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 diff --git a/instana/django19.py b/instana/django19.py index be94c072..620fa0ce 100644 --- a/instana/django19.py +++ b/instana/django19.py @@ -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 @@ -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']) @@ -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 diff --git a/instana/fsm.py b/instana/fsm.py index 1afe968d..d9c7a9ca 100644 --- a/instana/fsm.py +++ b/instana/fsm.py @@ -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, diff --git a/instana/instrumentation/__init__.py b/instana/instrumentation/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/instana/instrumentation/urllib3.py b/instana/instrumentation/urllib3.py new file mode 100644 index 00000000..2be0e20b --- /dev/null +++ b/instana/instrumentation/urllib3.py @@ -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") diff --git a/instana/probe.py b/instana/probe.py index 6c401a82..ca2590f3 100644 --- a/instana/probe.py +++ b/instana/probe.py @@ -10,4 +10,4 @@ # c. Detect and instrument any libraries opts = options.Options() -ot.global_tracer = tracer.InstanaTracer(opts) +ot.tracer = tracer.InstanaTracer(opts) diff --git a/instana/recorder.py b/instana/recorder.py index a48b26a9..ca120ded 100644 --- a/instana/recorder.py +++ b/instana/recorder.py @@ -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() diff --git a/instana/runtime.py b/instana/runtime.py index ed06ba21..65e31c97 100644 --- a/instana/runtime.py +++ b/instana/runtime.py @@ -12,4 +12,4 @@ def hook(module): print("==========================================================") opts = options.Options() - ot.global_tracer = tracer.InstanaTracer(opts) + ot.tracer = tracer.InstanaTracer(opts) diff --git a/instana/span.py b/instana/span.py index af14b3ba..83222aee 100644 --- a/instana/span.py +++ b/instana/span.py @@ -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): diff --git a/instana/tracer.py b/instana/tracer.py index ab5650be..d871548b 100644 --- a/instana/tracer.py +++ b/instana/tracer.py @@ -2,8 +2,8 @@ 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 @@ -11,22 +11,13 @@ 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()) @@ -67,7 +58,7 @@ 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, @@ -75,6 +66,11 @@ def start_span( 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) diff --git a/instana/wsgi.py b/instana/wsgi.py index 983ee1e3..58d19e4f 100644 --- a/instana/wsgi.py +++ b/instana/wsgi.py @@ -1,5 +1,5 @@ import opentracing as ot -from instana import tracer, options +from instana import internal_tracer import opentracing.ext.tags as tags @@ -8,8 +8,6 @@ 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): @@ -17,7 +15,7 @@ def __call__(self, environ, start_response): 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] @@ -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']) diff --git a/requirements.txt b/requirements.txt index 184d47f4..56514551 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -nose>=1.0 fysom>=2.1.2 opentracing>=1.2.1 basictracer>=2.2.0 diff --git a/setup.py b/setup.py index 8e81250e..e256cad5 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ packages=find_packages(exclude=['tests', 'examples']), long_description="The instana package provides Python metrics and traces for Instana.", zip_safe=False, - setup_requires=['nose>=1.0'], + setup_requires=['nose>=1.0', 'flask>=0.12.2'], install_requires=['autowrapt>=1.0', 'fysom>=2.1.2', 'opentracing>=1.2.1,<1.3', diff --git a/test_requirements.txt b/test_requirements.txt new file mode 100644 index 00000000..ddef682d --- /dev/null +++ b/test_requirements.txt @@ -0,0 +1,7 @@ +nose>=1.0 +fysom>=2.1.2 +opentracing>=1.2.1 +basictracer>=2.2.0 +autowrapt>=1.0 +flask>=0.12.2 +urllib3>=1.9 diff --git a/tests/__init__.py b/tests/__init__.py index e95723bf..ebd8b01a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,6 +1,16 @@ +from __future__ import absolute_import import os -import opentracing -import instana.tracer +import time +import threading +from .apps.flaskalino import app as flaskalino os.environ["INSTANA_TEST"] = "true" -opentracing.global_tracer = instana.tracer.InstanaTracer() +# Spawn our background Flask app that the tests will throw +# requests at. Don't continue until the test app is fully +# up and running. +timer = threading.Thread(target=flaskalino.run) +timer.daemon = True +timer.name = "Test Flask app" +print("Starting background test app") +timer.start() +time.sleep(1) diff --git a/tests/apps/__init__.py b/tests/apps/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/apps/flaskalino.py b/tests/apps/flaskalino.py new file mode 100644 index 00000000..315098a9 --- /dev/null +++ b/tests/apps/flaskalino.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from flask import Flask +app = Flask(__name__) +app.debug = False +app.use_reloader = False + + +@app.route("/") +def hello(): + return "

🐍 Hello Stan! 🦄

" + + +@app.route("/400") +def fourhundred(): + return "Bad Request", 400 + + +@app.route("/405") +def fourhundredfive(): + return "Method not allowed", 405 + + +@app.route("/500") +def fivehundred(): + return "Internal Server Error", 500 + + +@app.route("/504") +def fivehundredfour(): + return "Gateway Timeout", 504 + + +@app.route("/exception") +def exception(): + raise Exception('fake error') + + +if __name__ == '__main__': + app.run() diff --git a/tests/test_ot_propagators.py b/tests/test_ot_propagators.py index e3996fa9..06856bb8 100644 --- a/tests/test_ot_propagators.py +++ b/tests/test_ot_propagators.py @@ -19,11 +19,11 @@ def test_basics(): def test_inject(): opts = options.Options() - ot.global_tracer = tracer.InstanaTracer(opts) + ot.tracer = tracer.InstanaTracer(opts) carrier = {} - span = ot.global_tracer.start_span("nosetests") - ot.global_tracer.inject(span.context, ot.Format.HTTP_HEADERS, carrier) + span = ot.tracer.start_span("nosetests") + ot.tracer.inject(span.context, ot.Format.HTTP_HEADERS, carrier) assert 'X-Instana-T' in carrier assert_equals(carrier['X-Instana-T'], util.id_to_header(span.context.trace_id)) diff --git a/tests/test_ot_span.py b/tests/test_ot_span.py index 170d3b88..b6f1fa85 100644 --- a/tests/test_ot_span.py +++ b/tests/test_ot_span.py @@ -6,7 +6,7 @@ class TestOTSpan: def setUp(self): """ Clear all spans before a test run """ - recorder = opentracing.global_tracer.recorder + recorder = opentracing.tracer.recorder recorder.clear_spans() def tearDown(self): @@ -14,7 +14,7 @@ def tearDown(self): return None def test_span_interface(self): - span = opentracing.global_tracer.start_span("blah") + span = opentracing.tracer.start_span("blah") assert hasattr(span, "finish") assert hasattr(span, "set_tag") assert hasattr(span, "tags") @@ -28,13 +28,13 @@ def test_span_ids(self): count = 0 while count <= 1000: count += 1 - span = opentracing.global_tracer.start_span("test_span_ids") + span = opentracing.tracer.start_span("test_span_ids") context = span.context assert -9223372036854775808 <= context.span_id <= 9223372036854775807 assert -9223372036854775808 <= context.trace_id <= 9223372036854775807 def test_span_fields(self): - span = opentracing.global_tracer.start_span("mycustom") + span = opentracing.tracer.start_span("mycustom") assert_equals("mycustom", span.operation_name) assert span.context @@ -45,12 +45,12 @@ def test_span_fields(self): assert_equals(150, span.tags['tagtwo']) def test_span_queueing(self): - recorder = opentracing.global_tracer.recorder + recorder = opentracing.tracer.recorder count = 1 while count <= 20: count += 1 - span = opentracing.global_tracer.start_span("queuethisplz") + span = opentracing.tracer.start_span("queuethisplz") span.set_tag("tagone", "string") span.set_tag("tagtwo", 150) span.finish() @@ -58,9 +58,9 @@ def test_span_queueing(self): assert_equals(20, recorder.queue_size()) def test_sdk_spans(self): - recorder = opentracing.global_tracer.recorder + recorder = opentracing.tracer.recorder - span = opentracing.global_tracer.start_span("custom_sdk_span") + span = opentracing.tracer.start_span("custom_sdk_span") span.set_tag("tagone", "string") span.set_tag("tagtwo", 150) span.set_tag('span.kind', "entry") @@ -88,25 +88,25 @@ def test_sdk_spans(self): assert sdk_span.data.sdk.custom.tags def test_span_kind(self): - recorder = opentracing.global_tracer.recorder + recorder = opentracing.tracer.recorder - span = opentracing.global_tracer.start_span("custom_sdk_span") + span = opentracing.tracer.start_span("custom_sdk_span") span.set_tag('span.kind', "consumer") span.finish() - span = opentracing.global_tracer.start_span("custom_sdk_span") + span = opentracing.tracer.start_span("custom_sdk_span") span.set_tag('span.kind', "server") span.finish() - span = opentracing.global_tracer.start_span("custom_sdk_span") + span = opentracing.tracer.start_span("custom_sdk_span") span.set_tag('span.kind', "producer") span.finish() - span = opentracing.global_tracer.start_span("custom_sdk_span") + span = opentracing.tracer.start_span("custom_sdk_span") span.set_tag('span.kind', "client") span.finish() - span = opentracing.global_tracer.start_span("custom_sdk_span") + span = opentracing.tracer.start_span("custom_sdk_span") span.set_tag('span.kind', "blah") span.finish() diff --git a/tests/test_ot_tracer.py b/tests/test_ot_tracer.py index 1e3706d4..5af3d40b 100644 --- a/tests/test_ot_tracer.py +++ b/tests/test_ot_tracer.py @@ -5,6 +5,6 @@ def test_tracer_basics(): assert hasattr(instana.tracer, 'InstanaTracer') - assert hasattr(opentracing.global_tracer, "start_span") - assert hasattr(opentracing.global_tracer, "inject") - assert hasattr(opentracing.global_tracer, "extract") + assert hasattr(opentracing.tracer, "start_span") + assert hasattr(opentracing.tracer, "inject") + assert hasattr(opentracing.tracer, "extract") diff --git a/tests/test_urllib3.py b/tests/test_urllib3.py new file mode 100644 index 00000000..9bb7596a --- /dev/null +++ b/tests/test_urllib3.py @@ -0,0 +1,147 @@ +from __future__ import absolute_import +from nose.tools import assert_equals +from instana import internal_tracer as tracer +import urllib3 + + +class TestUrllib3: + def setUp(self): + """ Clear all spans before a test run """ + self.http = urllib3.PoolManager() + self.recorder = tracer.recorder + self.recorder.clear_spans() + + def tearDown(self): + """ Do nothing for now """ + return None + + def test_vanilla_requests(self): + r = self.http.request('GET', 'http://127.0.0.1:5000/') + assert_equals(r.status, 200) + + def test_get_request(self): + span = tracer.start_span("test") + r = self.http.request('GET', 'http://127.0.0.1:5000/') + span.finish() + + spans = self.recorder.queued_spans() + assert_equals(2, len(spans)) + first_span = spans[1] + second_span = spans[0] + + assert(r) + assert_equals(200, r.status) + assert_equals("test", first_span.data.sdk.name) + assert_equals("urllib3", second_span.n) + assert_equals(200, second_span.data.http.status) + assert_equals("http://127.0.0.1:5000/", second_span.data.http.url) + assert_equals("GET", second_span.data.http.method) + + assert_equals(None, second_span.error) + assert_equals(None, second_span.ec) + + assert_equals(second_span.t, first_span.t) + assert_equals(second_span.p, first_span.s) + + def test_put_request(self): + span = tracer.start_span("test") + r = self.http.request('PUT', 'http://127.0.0.1:5000/notfound') + span.finish() + + spans = self.recorder.queued_spans() + assert_equals(2, len(spans)) + first_span = spans[1] + second_span = spans[0] + + assert(r) + assert_equals(404, r.status) + assert_equals("test", first_span.data.sdk.name) + assert_equals("urllib3", second_span.n) + assert_equals(404, second_span.data.http.status) + assert_equals("http://127.0.0.1:5000/notfound", second_span.data.http.url) + assert_equals("PUT", second_span.data.http.method) + assert_equals(None, second_span.error) + assert_equals(None, second_span.ec) + + assert_equals(second_span.t, first_span.t) + assert_equals(second_span.p, first_span.s) + + def test_5xx_request(self): + span = tracer.start_span("test") + r = self.http.request('GET', 'http://127.0.0.1:5000/504') + span.finish() + + spans = self.recorder.queued_spans() + assert_equals(2, len(spans)) + first_span = spans[1] + second_span = spans[0] + + assert(r) + assert_equals(504, r.status) + assert_equals("test", first_span.data.sdk.name) + assert_equals("urllib3", second_span.n) + assert_equals(504, second_span.data.http.status) + assert_equals("http://127.0.0.1:5000/504", second_span.data.http.url) + assert_equals("GET", second_span.data.http.method) + assert_equals(True, second_span.error) + assert_equals(1, second_span.ec) + + assert_equals(second_span.t, first_span.t) + assert_equals(second_span.p, first_span.s) + + def test_exception_logging(self): + span = tracer.start_span("test") + try: + r = self.http.request('GET', 'http://127.0.0.1:5000/exception') + except Exception: + pass + + span.finish() + + spans = self.recorder.queued_spans() + assert_equals(2, len(spans)) + first_span = spans[1] + second_span = spans[0] + + assert(r) + assert_equals(500, r.status) + assert_equals("test", first_span.data.sdk.name) + assert_equals("urllib3", second_span.n) + assert_equals(500, second_span.data.http.status) + assert_equals("http://127.0.0.1:5000/exception", second_span.data.http.url) + assert_equals("GET", second_span.data.http.method) + assert_equals(True, second_span.error) + assert_equals(1, second_span.ec) + + assert_equals(second_span.t, first_span.t) + assert_equals(second_span.p, first_span.s) + + def test_client_error(self): + span = tracer.start_span("test") + + r = None + try: + r = self.http.request('GET', 'http://doesnotexist.asdf:5000/504', + retries=False, + timeout=urllib3.Timeout(connect=0.5, read=0.5)) + except Exception: + pass + + span.finish() + + spans = self.recorder.queued_spans() + assert_equals(2, len(spans)) + first_span = spans[1] + second_span = spans[0] + + assert_equals(None, r) + assert_equals("test", first_span.data.sdk.name) + assert_equals("urllib3", second_span.n) + assert_equals(None, second_span.data.http.status) + assert_equals("http://doesnotexist.asdf:5000/504", second_span.data.http.url) + assert_equals("GET", second_span.data.http.method) + assert_equals(True, second_span.error) + assert_equals(1, second_span.ec) + + assert_equals(second_span.t, first_span.t) + assert_equals(second_span.p, first_span.s)