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
1 change: 1 addition & 0 deletions instana/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def boot_agent():
from .instrumentation.aiohttp import client
from .instrumentation.aiohttp import server
from .instrumentation import asynqp
from .instrumentation import flask
from .instrumentation.tornado import client
from .instrumentation.tornado import server
from .instrumentation import logging
Expand Down
22 changes: 0 additions & 22 deletions instana/flaskana.py

This file was deleted.

18 changes: 18 additions & 0 deletions instana/instrumentation/flask/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from __future__ import absolute_import

try:
import flask
from flask.signals import signals_available

# `signals_available` indicates whether the Flask process is running with or without blinker support:
# https://pypi.org/project/blinker/
#
# Blinker support is preferred but we do the best we can when it's not available.
#

if signals_available is True:
import instana.instrumentation.flask.with_blinker
else:
import instana.instrumentation.flask.vanilla
except ImportError:
pass
123 changes: 123 additions & 0 deletions instana/instrumentation/flask/vanilla.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
from __future__ import absolute_import

import opentracing
import opentracing.ext.tags as ext
import wrapt

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

import flask


def before_request_with_instana(*argv, **kwargs):
try:
env = flask.request.environ
ctx = None

if 'HTTP_X_INSTANA_T' in env and 'HTTP_X_INSTANA_S' in env:
ctx = tracer.extract(opentracing.Format.HTTP_HEADERS, env)

flask.g.scope = tracer.start_active_span('wsgi', child_of=ctx)
span = flask.g.scope.span

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

span.set_tag(ext.HTTP_METHOD, flask.request.method)
if 'PATH_INFO' in env:
span.set_tag(ext.HTTP_URL, env['PATH_INFO'])
if 'QUERY_STRING' in env and len(env['QUERY_STRING']):
scrubbed_params = strip_secrets(env['QUERY_STRING'], agent.secrets_matcher, agent.secrets_list)
span.set_tag("http.params", scrubbed_params)
if 'HTTP_HOST' in env:
span.set_tag("http.host", env['HTTP_HOST'])
except:
logger.debug("Flask before_request", exc_info=True)
finally:
return None


def after_request_with_instana(response):
try:
scope = None

# If we're not tracing, just return
if not hasattr(flask.g, 'scope'):
return response

scope = flask.g.scope
span = scope.span

if 500 <= response.status_code <= 511:
span.set_tag("error", True)
ec = span.tags.get('ec', 0)
if ec is 0:
span.set_tag("ec", ec+1)

span.set_tag(ext.HTTP_STATUS_CODE, int(response.status_code))
tracer.inject(scope.span.context, opentracing.Format.HTTP_HEADERS, response.headers)
response.headers.add('Server-Timing', "intid;desc=%s" % scope.span.context.trace_id)
except:
logger.debug("Flask after_request", exc_info=True)
finally:
if scope is not None:
scope.close()
return response


@wrapt.patch_function_wrapper('flask', 'Flask.handle_user_exception')
def handle_user_exception_with_instana(wrapped, instance, argv, kwargs):
exc = argv[0]

if hasattr(flask.g, 'scope'):
scope = flask.g.scope
span = scope.span

if not hasattr(exc, 'code'):
span.log_exception(argv[0])
span.set_tag(ext.HTTP_STATUS_CODE, 500)
scope.close()

return wrapped(*argv, **kwargs)


@wrapt.patch_function_wrapper('flask', 'templating._render')
def render_with_instana(wrapped, instance, argv, kwargs):
ctx = argv[1]

# If we're not tracing, just return
if not hasattr(ctx['g'], 'scope'):
return wrapped(*argv, **kwargs)

with tracer.start_active_span("render", child_of=ctx['g'].scope.span) as rscope:
try:
template = argv[0]

rscope.span.set_tag("type", "template")
if template.name is None:
rscope.span.set_tag("name", '(from string)')
else:
rscope.span.set_tag("name", template.name)
return wrapped(*argv, **kwargs)
except Exception as e:
rscope.span.log_exception(e)
raise


@wrapt.patch_function_wrapper('flask', 'Flask.full_dispatch_request')
def full_dispatch_request_with_instana(wrapped, instance, argv, kwargs):
if not hasattr(instance, '_stan_wuz_here'):
logger.debug("Applying flask before/after instrumentation funcs")
setattr(instance, "_stan_wuz_here", True)
instance.after_request(after_request_with_instana)
instance.before_request(before_request_with_instana)
return wrapped(*argv, **kwargs)


logger.debug("Instrumenting flask (without blinker support)")
137 changes: 137 additions & 0 deletions instana/instrumentation/flask/with_blinker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
from __future__ import absolute_import

import opentracing
import opentracing.ext.tags as ext
import wrapt

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

import flask
from flask import request_started, request_finished, got_request_exception


def request_started_with_instana(sender, **extra):
try:
env = flask.request.environ
ctx = None

if 'HTTP_X_INSTANA_T' in env and 'HTTP_X_INSTANA_S' in env:
ctx = tracer.extract(opentracing.Format.HTTP_HEADERS, env)

flask.g.scope = tracer.start_active_span('wsgi', child_of=ctx)
span = flask.g.scope.span

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

span.set_tag(ext.HTTP_METHOD, flask.request.method)
if 'PATH_INFO' in env:
span.set_tag(ext.HTTP_URL, env['PATH_INFO'])
if 'QUERY_STRING' in env and len(env['QUERY_STRING']):
scrubbed_params = strip_secrets(env['QUERY_STRING'], agent.secrets_matcher, agent.secrets_list)
span.set_tag("http.params", scrubbed_params)
if 'HTTP_HOST' in env:
span.set_tag("http.host", env['HTTP_HOST'])
except:
logger.debug("Flask before_request", exc_info=True)


def request_finished_with_instana(sender, response, **extra):
try:
scope = None

# If we're not tracing, just return
if not hasattr(flask.g, 'scope'):
return

scope = flask.g.scope
span = scope.span

if 500 <= response.status_code <= 511:
span.set_tag("error", True)
ec = span.tags.get('ec', 0)
if ec is 0:
span.set_tag("ec", ec+1)

span.set_tag(ext.HTTP_STATUS_CODE, int(response.status_code))
tracer.inject(scope.span.context, opentracing.Format.HTTP_HEADERS, response.headers)
response.headers.add('Server-Timing', "intid;desc=%s" % scope.span.context.trace_id)
except:
logger.debug("Flask after_request", exc_info=True)
finally:
if scope is not None:
scope.close()
return response


def log_exception_with_instana(sender, exception, **extra):
# If we're not tracing, just return
if not hasattr(flask.g, 'scope'):
return

scope = flask.g.scope

if scope is not None:
span = scope.span
if span is not None:
span.log_exception(exception)


@wrapt.patch_function_wrapper('flask', 'Flask.handle_user_exception')
def handle_user_exception_with_instana(wrapped, instance, argv, kwargs):
exc = argv[0]

if hasattr(flask.g, 'scope'):
scope = flask.g.scope
span = scope.span

if not hasattr(exc, 'code'):
span.log_exception(exc)
span.set_tag(ext.HTTP_STATUS_CODE, 500)
scope.close()
flask.g.scope = None

return wrapped(*argv, **kwargs)


@wrapt.patch_function_wrapper('flask', 'templating._render')
def render_with_instana(wrapped, instance, argv, kwargs):
ctx = argv[1]

# If we're not tracing, just return
if not hasattr(ctx['g'], 'scope'):
return wrapped(*argv, **kwargs)

with tracer.start_active_span("render", child_of=ctx['g'].scope.span) as rscope:
try:
template = argv[0]

rscope.span.set_tag("type", "template")
if template.name is None:
rscope.span.set_tag("name", '(from string)')
else:
rscope.span.set_tag("name", template.name)
return wrapped(*argv, **kwargs)
except Exception as e:
rscope.span.log_exception(e)
raise


@wrapt.patch_function_wrapper('flask', 'Flask.full_dispatch_request')
def full_dispatch_request_with_instana(wrapped, instance, argv, kwargs):
if not hasattr(instance, '_stan_wuz_here'):
logger.debug("Applying flask before/after instrumentation funcs")
setattr(instance, "_stan_wuz_here", True)
got_request_exception.connect(log_exception_with_instana, instance)
request_started.connect(request_started_with_instana, instance)
request_finished.connect(request_finished_with_instana, instance)
return wrapped(*argv, **kwargs)


logger.debug("Instrumenting flask (with blinker support)")
6 changes: 3 additions & 3 deletions instana/instrumentation/urllib3.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
from ..util import strip_secrets

try:
import urllib3 # noqa
import urllib3

def collect(instance, args, kwargs):
""" Build and return a fully qualified URL for this request """
try:
kvs = {}
kvs = dict()
kvs['host'] = instance.host
kvs['port'] = instance.port

Expand All @@ -28,7 +28,7 @@ def collect(instance, args, kwargs):
kvs['path'] = kwargs.get('url')

# Strip any secrets from potential query params
if '?' in kvs['path']:
if kvs.get('path') is not None and ('?' in kvs['path']):
parts = kvs['path'].split('?')
kvs['path'] = parts[0]
if len(parts) is 2:
Expand Down
20 changes: 20 additions & 0 deletions instana/json_span.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ class Data(BaseSpan):
baggage = None
custom = None
http = None
log = None
rabbitmq = None
redis = None
rpc = None
render = None
sdk = None
service = None
sqlalchemy = None
Expand All @@ -56,6 +58,14 @@ class HttpData(BaseSpan):
error = None


class LogData(object):
message = None
parameters = None

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


class MySQLData(BaseSpan):
db = None
host = None
Expand Down Expand Up @@ -91,6 +101,16 @@ class RPCData(BaseSpan):
error = None


class RenderData(object):
type = None
name = None
message = None
parameters = None

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


class SQLAlchemyData(BaseSpan):
sql = None
url = None
Expand Down
Loading