Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Empty file.
79 changes: 79 additions & 0 deletions instana/instrumentation/pyramid/tweens.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from __future__ import absolute_import

from pyramid.httpexceptions import HTTPException

import opentracing as ot
import opentracing.ext.tags as ext

from ...log import logger
from ...singletons import tracer, agent
from ...util import strip_secrets

class InstanaTweenFactory(object):
"""A factory that provides Instana instrumentation tween for Pyramid apps"""

def __init__(self, handler, registry):
self.handler = handler

def __call__(self, request):
ctx = tracer.extract(ot.Format.HTTP_HEADERS, request.headers)
scope = tracer.start_active_span('http', child_of=ctx)

scope.span.set_tag(ext.SPAN_KIND, ext.SPAN_KIND_RPC_SERVER)
scope.span.set_tag("http.host", request.host)
scope.span.set_tag(ext.HTTP_METHOD, request.method)
scope.span.set_tag(ext.HTTP_URL, request.path)

if request.matched_route is not None:
scope.span.set_tag("http.path_tpl", request.matched_route.pattern)

if hasattr(agent, 'extra_headers') and agent.extra_headers is not None:
for custom_header in agent.extra_headers:
# Headers are available in this format: HTTP_X_CAPTURE_THIS
h = ('HTTP_' + custom_header.upper()).replace('-', '_')
if h in request.headers:
scope.span.set_tag("http.%s" % custom_header, request.headers[h])

if len(request.query_string):
scrubbed_params = strip_secrets(request.query_string, agent.secrets_matcher, agent.secrets_list)
scope.span.set_tag("http.params", scrubbed_params)

response = None
try:
response = self.handler(request)

tracer.inject(scope.span.context, ot.Format.HTTP_HEADERS, response.headers)
response.headers['Server-Timing'] = "intid;desc=%s" % scope.span.context.trace_id
except HTTPException as e:
response = e
raise
except BaseException as e:
scope.span.set_tag("http.status", 500)

# we need to explicitly populate the `message` tag with an error here
# so that it's picked up from an SDK span
scope.span.set_tag("message", str(e))
scope.span.log_exception(e)

logger.debug("Pyramid Instana tween", exc_info=True)
finally:
if response:
scope.span.set_tag("http.status", response.status_int)

if 500 <= response.status_int <= 511:
if response.exception is not None:
message = str(response.exception)
scope.span.log_exception(response.exception)
else:
message = response.status

scope.span.set_tag("message", message)
scope.span.assure_errored()

scope.close()

return response

def includeme(config):
logger.debug("Instrumenting pyramid")
config.add_tween(__name__ + '.InstanaTweenFactory')
3 changes: 2 additions & 1 deletion instana/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
import os

from .util import determine_service_name

class StandardOptions(object):
""" Configurable option bits for this package """
Expand All @@ -20,7 +21,7 @@ def __init__(self, **kwds):
self.log_level = logging.DEBUG
self.debug = True

self.service_name = os.environ.get("INSTANA_SERVICE_NAME", None)
self.service_name = determine_service_name()
self.agent_host = os.environ.get("INSTANA_AGENT_HOST", self.AGENT_DEFAULT_HOST)
self.agent_port = os.environ.get("INSTANA_AGENT_PORT", self.AGENT_DEFAULT_PORT)

Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def check_setuptools():
'gevent>=1.4.0'
'mock>=2.0.0',
'nose>=1.0',
'pyramid>=1.2',
'urllib3[secure]>=1.15'
],
'test-cassandra': [
Expand All @@ -96,6 +97,7 @@ def check_setuptools():
'pytest>=3.0.1',
'psycopg2>=2.7.1',
'pymongo>=3.7.0',
'pyramid>=1.2',
'redis>3.0.0',
'requests>=2.17.1',
'sqlalchemy>=1.1.15',
Expand All @@ -112,6 +114,7 @@ def check_setuptools():
'Development Status :: 5 - Production/Stable',
'Framework :: Django',
'Framework :: Flask',
'Framework :: Pyramid',
'Intended Audience :: Developers',
'Intended Audience :: Information Technology',
'Intended Audience :: Science/Research',
Expand Down
22 changes: 14 additions & 8 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,22 @@

if 'CASSANDRA_TEST' not in os.environ:
from .apps.flaskalino import flask_server
from .apps.app_pyramid import pyramid_server

# Background Flask application
#
# Spawn our background Flask app that the tests will throw
# Background applications
servers = {
'Flask': flask_server,
'Pyramid': pyramid_server,
}

# Spawn background apps that the tests will throw
# requests at.
flask = threading.Thread(target=flask_server.serve_forever)
flask.daemon = True
flask.name = "Background Flask app"
print("Starting background Flask app...")
flask.start()
for (name, server) in servers.items():
p = threading.Thread(target=server.serve_forever)
p.daemon = True
p.name = "Background %s app" % name
print("Starting background %s app..." % name)
p.start()

if 'GEVENT_TEST' not in os.environ and 'CASSANDRA_TEST' not in os.environ:

Expand Down
37 changes: 37 additions & 0 deletions tests/apps/app_pyramid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
import logging

from pyramid.response import Response
import pyramid.httpexceptions as exc

from ..helpers import testenv

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

testenv["pyramid_port"] = 10815
testenv["pyramid_server"] = ("http://127.0.0.1:" + str(testenv["pyramid_port"]))

def hello_world(request):
return Response('Ok')

def please_fail(request):
raise exc.HTTPInternalServerError("internal error")

def tableflip(request):
raise BaseException("fake exception")

app = None
with Configurator() as config:
config.add_tween('instana.instrumentation.pyramid.tweens.InstanaTweenFactory')
config.add_route('hello', '/')
config.add_view(hello_world, route_name='hello')
config.add_route('fail', '/500')
config.add_view(please_fail, route_name='fail')
config.add_route('crash', '/exception')
config.add_view(tableflip, route_name='crash')
app = config.make_wsgi_app()

pyramid_server = make_server('127.0.0.1', testenv["pyramid_port"], app)

Loading