diff --git a/instana/instrumentation/aiohttp/client.py b/instana/instrumentation/aiohttp/client.py index 418233ce..3f2da1f8 100644 --- a/instana/instrumentation/aiohttp/client.py +++ b/instana/instrumentation/aiohttp/client.py @@ -10,11 +10,11 @@ from ...singletons import agent, async_tracer from ...util.secrets import strip_secrets_from_query - try: import aiohttp import asyncio + async def stan_request_start(session, trace_config_ctx, params): try: parent_span = async_tracer.active_span @@ -31,13 +31,15 @@ async def stan_request_start(session, trace_config_ctx, params): parts = str(params.url).split('?') if len(parts) > 1: - cleaned_qp = strip_secrets_from_query(parts[1], agent.options.secrets_matcher, agent.options.secrets_list) + cleaned_qp = strip_secrets_from_query(parts[1], agent.options.secrets_matcher, + agent.options.secrets_list) scope.span.set_tag("http.params", cleaned_qp) scope.span.set_tag("http.url", parts[0]) scope.span.set_tag('http.method', params.method) except Exception: logger.debug("stan_request_start", exc_info=True) + async def stan_request_end(session, trace_config_ctx, params): try: scope = trace_config_ctx.scope @@ -56,6 +58,7 @@ async def stan_request_end(session, trace_config_ctx, params): except Exception: logger.debug("stan_request_end", exc_info=True) + async def stan_request_exception(session, trace_config_ctx, params): try: scope = trace_config_ctx.scope @@ -66,7 +69,8 @@ async def stan_request_exception(session, trace_config_ctx, params): except Exception: logger.debug("stan_request_exception", exc_info=True) - @wrapt.patch_function_wrapper('aiohttp.client','ClientSession.__init__') + + @wrapt.patch_function_wrapper('aiohttp.client', 'ClientSession.__init__') def init_with_instana(wrapped, instance, argv, kwargs): instana_trace_config = aiohttp.TraceConfig() instana_trace_config.on_request_start.append(stan_request_start) @@ -79,7 +83,7 @@ def init_with_instana(wrapped, instance, argv, kwargs): return wrapped(*argv, **kwargs) + logger.debug("Instrumenting aiohttp client") except ImportError: pass - diff --git a/instana/instrumentation/aiohttp/server.py b/instana/instrumentation/aiohttp/server.py index 30a482d7..451e63a8 100644 --- a/instana/instrumentation/aiohttp/server.py +++ b/instana/instrumentation/aiohttp/server.py @@ -10,13 +10,13 @@ from ...singletons import agent, async_tracer from ...util.secrets import strip_secrets_from_query - try: import aiohttp import asyncio from aiohttp.web import middleware + @middleware async def stan_middleware(request, handler): try: @@ -80,6 +80,7 @@ def init_with_instana(wrapped, instance, argv, kwargs): return wrapped(*argv, **kwargs) + logger.debug("Instrumenting aiohttp server") except ImportError: pass diff --git a/instana/instrumentation/asgi.py b/instana/instrumentation/asgi.py index f9eb9c9e..b8c95f5c 100644 --- a/instana/instrumentation/asgi.py +++ b/instana/instrumentation/asgi.py @@ -10,10 +10,12 @@ from ..singletons import async_tracer, agent from ..util.secrets import strip_secrets_from_query + class InstanaASGIMiddleware: """ Instana ASGI Middleware """ + def __init__(self, app): self.app = app @@ -41,7 +43,8 @@ def _collect_kvs(self, scope, span): if isinstance(query, (str, bytes)) and len(query): if isinstance(query, bytes): query = query.decode('utf-8') - scrubbed_params = strip_secrets_from_query(query, agent.options.secrets_matcher, agent.options.secrets_list) + scrubbed_params = strip_secrets_from_query(query, agent.options.secrets_matcher, + agent.options.secrets_list) span.set_tag("http.params", scrubbed_params) app = scope.get('app') @@ -55,7 +58,6 @@ def _collect_kvs(self, scope, span): except Exception: logger.debug("ASGI collect_kvs: ", exc_info=True) - async def __call__(self, scope, receive, send): request_context = None diff --git a/instana/instrumentation/asynqp.py b/instana/instrumentation/asynqp.py index c3b40f1f..9e5c67e1 100644 --- a/instana/instrumentation/asynqp.py +++ b/instana/instrumentation/asynqp.py @@ -13,7 +13,8 @@ import asynqp import asyncio - @wrapt.patch_function_wrapper('asynqp.exchange','Exchange.publish') + + @wrapt.patch_function_wrapper('asynqp.exchange', 'Exchange.publish') def publish_with_instana(wrapped, instance, argv, kwargs): parent_span = async_tracer.active_span @@ -27,12 +28,13 @@ def publish_with_instana(wrapped, instance, argv, kwargs): msg = argv[0] if msg.headers is None: msg.headers = {} - async_tracer.inject(scope.span.context, opentracing.Format.HTTP_HEADERS, msg.headers) + async_tracer.inject(scope.span.context, opentracing.Format.HTTP_HEADERS, msg.headers, + disable_w3c_trace_context=True) try: scope.span.set_tag("exchange", instance.name) scope.span.set_tag("sort", "publish") - scope.span.set_tag("address", host + ":" + str(port) ) + scope.span.set_tag("address", host + ":" + str(port)) if 'routing_key' in kwargs: scope.span.set_tag("key", kwargs['routing_key']) @@ -46,8 +48,9 @@ def publish_with_instana(wrapped, instance, argv, kwargs): else: return rv + @asyncio.coroutine - @wrapt.patch_function_wrapper('asynqp.queue','Queue.get') + @wrapt.patch_function_wrapper('asynqp.queue', 'Queue.get') def get_with_instana(wrapped, instance, argv, kwargs): parent_span = async_tracer.active_span @@ -59,7 +62,7 @@ def get_with_instana(wrapped, instance, argv, kwargs): host, port = instance.sender.protocol.transport._sock.getsockname() scope.span.set_tag("sort", "consume") - scope.span.set_tag("address", host + ":" + str(port) ) + scope.span.set_tag("address", host + ":" + str(port)) msg = yield from wrapped(*argv, **kwargs) @@ -69,15 +72,17 @@ def get_with_instana(wrapped, instance, argv, kwargs): return msg + @asyncio.coroutine - @wrapt.patch_function_wrapper('asynqp.queue','Queue.consume') + @wrapt.patch_function_wrapper('asynqp.queue', 'Queue.consume') def consume_with_instana(wrapped, instance, argv, kwargs): def callback_generator(original_callback): def callback_with_instana(*argv, **kwargs): ctx = None msg = argv[0] if msg.headers is not None: - ctx = async_tracer.extract(opentracing.Format.HTTP_HEADERS, dict(msg.headers)) + ctx = async_tracer.extract(opentracing.Format.HTTP_HEADERS, dict(msg.headers), + disable_w3c_trace_context=True) with async_tracer.start_active_span("rabbitmq", child_of=ctx) as scope: host, port = msg.sender.protocol.transport._sock.getsockname() @@ -85,7 +90,7 @@ def callback_with_instana(*argv, **kwargs): try: scope.span.set_tag("exchange", msg.exchange_name) scope.span.set_tag("sort", "consume") - scope.span.set_tag("address", host + ":" + str(port) ) + scope.span.set_tag("address", host + ":" + str(port)) scope.span.set_tag("key", msg.routing_key) original_callback(*argv, **kwargs) @@ -99,6 +104,7 @@ def callback_with_instana(*argv, **kwargs): argv = (callback_generator(cb),) return wrapped(*argv, **kwargs) + logger.debug("Instrumenting asynqp") except ImportError: pass diff --git a/instana/instrumentation/aws/triggers.py b/instana/instrumentation/aws/triggers.py index 70b043d4..c91aac33 100644 --- a/instana/instrumentation/aws/triggers.py +++ b/instana/instrumentation/aws/triggers.py @@ -8,6 +8,7 @@ import json import base64 from io import BytesIO +import opentracing as ot from ...log import logger @@ -21,9 +22,9 @@ def get_context(tracer, event): is_application_load_balancer_trigger(event) if is_proxy_event: - return tracer.extract('http_headers', event.get('headers', {})) + return tracer.extract(ot.Format.HTTP_HEADERS, event.get('headers', {}), disable_w3c_trace_context=True) - return tracer.extract('http_headers', event) + return tracer.extract(ot.Format.HTTP_HEADERS, event, disable_w3c_trace_context=True) def is_api_gateway_proxy_trigger(event): diff --git a/instana/instrumentation/boto3_inst.py b/instana/instrumentation/boto3_inst.py index c7a6c8b3..cf6511b5 100644 --- a/instana/instrumentation/boto3_inst.py +++ b/instana/instrumentation/boto3_inst.py @@ -12,6 +12,7 @@ from ..util.traceutils import get_active_tracer try: + import opentracing as ot import boto3 from boto3.s3 import inject @@ -28,7 +29,7 @@ def lambda_inject_context(payload, scope): if not isinstance(invoke_payload, dict): invoke_payload = json.loads(invoke_payload) - tracer.inject(scope.span.context, 'http_headers', invoke_payload) + tracer.inject(scope.span.context, ot.Format.HTTP_HEADERS, invoke_payload) payload['Payload'] = json.dumps(invoke_payload) except Exception: logger.debug("non-fatal lambda_inject_context: ", exc_info=True) diff --git a/instana/instrumentation/celery/hooks.py b/instana/instrumentation/celery/hooks.py index 5d022813..2f6f7323 100644 --- a/instana/instrumentation/celery/hooks.py +++ b/instana/instrumentation/celery/hooks.py @@ -19,6 +19,7 @@ import urlparse as parse import urllib + def add_broker_tags(span, broker_url): try: url = parse.urlparse(broker_url) @@ -45,6 +46,7 @@ def add_broker_tags(span, broker_url): except Exception: logger.debug("Error parsing broker URL: %s" % broker_url, exc_info=True) + @signals.task_prerun.connect def task_prerun(*args, **kwargs): try: @@ -55,7 +57,7 @@ def task_prerun(*args, **kwargs): headers = task.request.get('headers', {}) if headers is not None: - ctx = tracer.extract(opentracing.Format.HTTP_HEADERS, headers) + ctx = tracer.extract(opentracing.Format.HTTP_HEADERS, headers, disable_w3c_trace_context=True) scope = tracer.start_active_span("celery-worker", child_of=ctx) scope.span.set_tag("task", task.name) @@ -67,6 +69,7 @@ def task_prerun(*args, **kwargs): except: logger.debug("task_prerun: ", exc_info=True) + @signals.task_postrun.connect def task_postrun(*args, **kwargs): try: @@ -78,6 +81,7 @@ def task_postrun(*args, **kwargs): except: logger.debug("after_task_publish: ", exc_info=True) + @signals.task_failure.connect def task_failure(*args, **kwargs): try: @@ -95,6 +99,7 @@ def task_failure(*args, **kwargs): except: logger.debug("task_failure: ", exc_info=True) + @signals.task_retry.connect def task_retry(*args, **kwargs): try: @@ -109,6 +114,7 @@ def task_retry(*args, **kwargs): except: logger.debug("task_failure: ", exc_info=True) + @signals.before_task_publish.connect def before_task_publish(*args, **kwargs): try: @@ -127,7 +133,8 @@ def before_task_publish(*args, **kwargs): # Context propagation context_headers = {} - active_tracer.inject(scope.span.context, opentracing.Format.HTTP_HEADERS, context_headers) + active_tracer.inject(scope.span.context, opentracing.Format.HTTP_HEADERS, context_headers, + disable_w3c_trace_context=True) # Fix for broken header propagation # https://github.com/celery/celery/issues/4875 @@ -141,6 +148,7 @@ def before_task_publish(*args, **kwargs): except: logger.debug("before_task_publish: ", exc_info=True) + @signals.after_task_publish.connect def after_task_publish(*args, **kwargs): try: @@ -152,6 +160,7 @@ def after_task_publish(*args, **kwargs): except: logger.debug("after_task_publish: ", exc_info=True) + logger.debug("Instrumenting celery") except ImportError: pass diff --git a/instana/instrumentation/flask/vanilla.py b/instana/instrumentation/flask/vanilla.py index a9906247..9cd4f2c8 100644 --- a/instana/instrumentation/flask/vanilla.py +++ b/instana/instrumentation/flask/vanilla.py @@ -22,8 +22,7 @@ def before_request_with_instana(*argv, **kwargs): 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) + ctx = tracer.extract(opentracing.Format.HTTP_HEADERS, env) flask.g.scope = tracer.start_active_span('wsgi', child_of=ctx) span = flask.g.scope.span @@ -39,7 +38,8 @@ def before_request_with_instana(*argv, **kwargs): 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_from_query(env['QUERY_STRING'], agent.options.secrets_matcher, agent.options.secrets_list) + scrubbed_params = strip_secrets_from_query(env['QUERY_STRING'], agent.options.secrets_matcher, + agent.options.secrets_list) span.set_tag("http.params", scrubbed_params) if 'HTTP_HOST' in env: span.set_tag("http.host", env['HTTP_HOST']) diff --git a/instana/instrumentation/flask/with_blinker.py b/instana/instrumentation/flask/with_blinker.py index 1cca6648..8b2c6801 100644 --- a/instana/instrumentation/flask/with_blinker.py +++ b/instana/instrumentation/flask/with_blinker.py @@ -23,8 +23,7 @@ def request_started_with_instana(sender, **extra): 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) + ctx = tracer.extract(opentracing.Format.HTTP_HEADERS, env) flask.g.scope = tracer.start_active_span('wsgi', child_of=ctx) span = flask.g.scope.span @@ -40,7 +39,8 @@ def request_started_with_instana(sender, **extra): 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_from_query(env['QUERY_STRING'], agent.options.secrets_matcher, agent.options.secrets_list) + scrubbed_params = strip_secrets_from_query(env['QUERY_STRING'], agent.options.secrets_matcher, + agent.options.secrets_list) span.set_tag("http.params", scrubbed_params) if 'HTTP_HOST' in env: span.set_tag("http.host", env['HTTP_HOST']) diff --git a/instana/instrumentation/google/cloud/pubsub.py b/instana/instrumentation/google/cloud/pubsub.py index baa864dd..b62b816f 100644 --- a/instana/instrumentation/google/cloud/pubsub.py +++ b/instana/instrumentation/google/cloud/pubsub.py @@ -47,7 +47,7 @@ def publish_with_instana(wrapped, instance, args, kwargs): with tracer.start_active_span('gcps-producer', child_of=parent_span) as scope: # trace continuity, inject to the span context headers = dict() - tracer.inject(scope.span.context, Format.TEXT_MAP, headers) + tracer.inject(scope.span.context, Format.TEXT_MAP, headers, disable_w3c_trace_context=True) # update the metadata dict with instana trace attributes kwargs.update(headers) @@ -73,7 +73,7 @@ def subscribe_with_instana(wrapped, instance, args, kwargs): def callback_with_instana(message): if message.attributes: - parent_span = tracer.extract(Format.TEXT_MAP, message.attributes) + parent_span = tracer.extract(Format.TEXT_MAP, message.attributes, disable_w3c_trace_context=True) else: parent_span = None diff --git a/instana/instrumentation/grpcio.py b/instana/instrumentation/grpcio.py index 919a2945..1c65abcd 100644 --- a/instana/instrumentation/grpcio.py +++ b/instana/instrumentation/grpcio.py @@ -12,12 +12,13 @@ try: import grpc from grpc._channel import _UnaryUnaryMultiCallable, _StreamUnaryMultiCallable, \ - _UnaryStreamMultiCallable, _StreamStreamMultiCallable + _UnaryStreamMultiCallable, _StreamStreamMultiCallable + + SUPPORTED_TYPES = [_UnaryUnaryMultiCallable, + _StreamUnaryMultiCallable, + _UnaryStreamMultiCallable, + _StreamStreamMultiCallable] - SUPPORTED_TYPES = [ _UnaryUnaryMultiCallable, - _StreamUnaryMultiCallable, - _UnaryStreamMultiCallable, - _StreamStreamMultiCallable ] def collect_tags(span, instance, argv, kwargs): try: @@ -58,7 +59,8 @@ def unary_unary_with_call_with_instana(wrapped, instance, argv, kwargs): if "metadata" not in kwargs: kwargs["metadata"] = [] - kwargs["metadata"] = tracer.inject(scope.span.context, opentracing.Format.BINARY, kwargs['metadata']) + kwargs["metadata"] = tracer.inject(scope.span.context, opentracing.Format.BINARY, kwargs['metadata'], + disable_w3c_trace_context=True) collect_tags(scope.span, instance, argv, kwargs) scope.span.set_tag('rpc.call_type', 'unary') @@ -69,6 +71,7 @@ def unary_unary_with_call_with_instana(wrapped, instance, argv, kwargs): else: return rv + @wrapt.patch_function_wrapper('grpc._channel', '_UnaryUnaryMultiCallable.future') def unary_unary_future_with_instana(wrapped, instance, argv, kwargs): parent_span = tracer.active_span @@ -82,7 +85,8 @@ def unary_unary_future_with_instana(wrapped, instance, argv, kwargs): if "metadata" not in kwargs: kwargs["metadata"] = [] - kwargs["metadata"] = tracer.inject(scope.span.context, opentracing.Format.BINARY, kwargs['metadata']) + kwargs["metadata"] = tracer.inject(scope.span.context, opentracing.Format.BINARY, kwargs['metadata'], + disable_w3c_trace_context=True) collect_tags(scope.span, instance, argv, kwargs) scope.span.set_tag('rpc.call_type', 'unary') @@ -93,6 +97,7 @@ def unary_unary_future_with_instana(wrapped, instance, argv, kwargs): else: return rv + @wrapt.patch_function_wrapper('grpc._channel', '_UnaryUnaryMultiCallable.__call__') def unary_unary_call_with_instana(wrapped, instance, argv, kwargs): parent_span = tracer.active_span @@ -106,7 +111,8 @@ def unary_unary_call_with_instana(wrapped, instance, argv, kwargs): if not "metadata" in kwargs: kwargs["metadata"] = [] - kwargs["metadata"] = tracer.inject(scope.span.context, opentracing.Format.BINARY, kwargs['metadata']) + kwargs["metadata"] = tracer.inject(scope.span.context, opentracing.Format.BINARY, kwargs['metadata'], + disable_w3c_trace_context=True) collect_tags(scope.span, instance, argv, kwargs) scope.span.set_tag('rpc.call_type', 'unary') @@ -117,6 +123,7 @@ def unary_unary_call_with_instana(wrapped, instance, argv, kwargs): else: return rv + @wrapt.patch_function_wrapper('grpc._channel', '_StreamUnaryMultiCallable.__call__') def stream_unary_call_with_instana(wrapped, instance, argv, kwargs): parent_span = tracer.active_span @@ -130,7 +137,8 @@ def stream_unary_call_with_instana(wrapped, instance, argv, kwargs): if not "metadata" in kwargs: kwargs["metadata"] = [] - kwargs["metadata"] = tracer.inject(scope.span.context, opentracing.Format.BINARY, kwargs['metadata']) + kwargs["metadata"] = tracer.inject(scope.span.context, opentracing.Format.BINARY, kwargs['metadata'], + disable_w3c_trace_context=True) collect_tags(scope.span, instance, argv, kwargs) scope.span.set_tag('rpc.call_type', 'stream') @@ -141,6 +149,7 @@ def stream_unary_call_with_instana(wrapped, instance, argv, kwargs): else: return rv + @wrapt.patch_function_wrapper('grpc._channel', '_StreamUnaryMultiCallable.with_call') def stream_unary_with_call_with_instana(wrapped, instance, argv, kwargs): parent_span = tracer.active_span @@ -154,7 +163,8 @@ def stream_unary_with_call_with_instana(wrapped, instance, argv, kwargs): if not "metadata" in kwargs: kwargs["metadata"] = [] - kwargs["metadata"] = tracer.inject(scope.span.context, opentracing.Format.BINARY, kwargs['metadata']) + kwargs["metadata"] = tracer.inject(scope.span.context, opentracing.Format.BINARY, kwargs['metadata'], + disable_w3c_trace_context=True) collect_tags(scope.span, instance, argv, kwargs) scope.span.set_tag('rpc.call_type', 'stream') @@ -165,6 +175,7 @@ def stream_unary_with_call_with_instana(wrapped, instance, argv, kwargs): else: return rv + @wrapt.patch_function_wrapper('grpc._channel', '_StreamUnaryMultiCallable.future') def stream_unary_future_with_instana(wrapped, instance, argv, kwargs): parent_span = tracer.active_span @@ -178,7 +189,8 @@ def stream_unary_future_with_instana(wrapped, instance, argv, kwargs): if not "metadata" in kwargs: kwargs["metadata"] = [] - kwargs["metadata"] = tracer.inject(scope.span.context, opentracing.Format.BINARY, kwargs['metadata']) + kwargs["metadata"] = tracer.inject(scope.span.context, opentracing.Format.BINARY, kwargs['metadata'], + disable_w3c_trace_context=True) collect_tags(scope.span, instance, argv, kwargs) scope.span.set_tag('rpc.call_type', 'stream') @@ -189,6 +201,7 @@ def stream_unary_future_with_instana(wrapped, instance, argv, kwargs): else: return rv + @wrapt.patch_function_wrapper('grpc._channel', '_UnaryStreamMultiCallable.__call__') def unary_stream_call_with_instana(wrapped, instance, argv, kwargs): parent_span = tracer.active_span @@ -202,7 +215,8 @@ def unary_stream_call_with_instana(wrapped, instance, argv, kwargs): if not "metadata" in kwargs: kwargs["metadata"] = [] - kwargs["metadata"] = tracer.inject(scope.span.context, opentracing.Format.BINARY, kwargs['metadata']) + kwargs["metadata"] = tracer.inject(scope.span.context, opentracing.Format.BINARY, kwargs['metadata'], + disable_w3c_trace_context=True) collect_tags(scope.span, instance, argv, kwargs) scope.span.set_tag('rpc.call_type', 'stream') @@ -213,6 +227,7 @@ def unary_stream_call_with_instana(wrapped, instance, argv, kwargs): else: return rv + @wrapt.patch_function_wrapper('grpc._channel', '_StreamStreamMultiCallable.__call__') def stream_stream_call_with_instana(wrapped, instance, argv, kwargs): parent_span = tracer.active_span @@ -226,7 +241,8 @@ def stream_stream_call_with_instana(wrapped, instance, argv, kwargs): if not "metadata" in kwargs: kwargs["metadata"] = [] - kwargs["metadata"] = tracer.inject(scope.span.context, opentracing.Format.BINARY, kwargs['metadata']) + kwargs["metadata"] = tracer.inject(scope.span.context, opentracing.Format.BINARY, kwargs['metadata'], + disable_w3c_trace_context=True) collect_tags(scope.span, instance, argv, kwargs) scope.span.set_tag('rpc.call_type', 'stream') @@ -237,6 +253,7 @@ def stream_stream_call_with_instana(wrapped, instance, argv, kwargs): else: return rv + @wrapt.patch_function_wrapper('grpc._server', '_call_behavior') def call_behavior_with_instana(wrapped, instance, argv, kwargs): # Prep any incoming context headers @@ -245,7 +262,7 @@ def call_behavior_with_instana(wrapped, instance, argv, kwargs): for c in metadata: metadata_dict[c.key] = c.value - ctx = tracer.extract(opentracing.Format.BINARY, metadata_dict) + ctx = tracer.extract(opentracing.Format.BINARY, metadata_dict, disable_w3c_trace_context=True) with tracer.start_active_span("rpc-server", child_of=ctx) as scope: try: @@ -257,6 +274,7 @@ def call_behavior_with_instana(wrapped, instance, argv, kwargs): else: return rv + logger.debug("Instrumenting grpcio") except ImportError: pass diff --git a/instana/instrumentation/pika.py b/instana/instrumentation/pika.py index 3b844082..e26bf200 100644 --- a/instana/instrumentation/pika.py +++ b/instana/instrumentation/pika.py @@ -62,7 +62,8 @@ def _bind_args(exchange, routing_key, body, properties=None, *args, **kwargs): properties = properties or pika.BasicProperties() properties.headers = properties.headers or {} - tracer.inject(scope.span.context, opentracing.Format.HTTP_HEADERS, properties.headers) + tracer.inject(scope.span.context, opentracing.Format.HTTP_HEADERS, properties.headers, + disable_w3c_trace_context=True) args = (exchange, routing_key, body, properties) + args try: @@ -81,7 +82,8 @@ def _bind_args(queue, callback, *args, **kwargs): queue, callback, args, kwargs = _bind_args(*args, **kwargs) def _cb_wrapper(channel, method, properties, body): - parent_span = tracer.extract(opentracing.Format.HTTP_HEADERS, properties.headers) + parent_span = tracer.extract(opentracing.Format.HTTP_HEADERS, properties.headers, + disable_w3c_trace_context=True) with tracer.start_active_span("rabbitmq", child_of=parent_span) as scope: try: @@ -109,7 +111,8 @@ def _bind_args(queue, on_consume_callback, *args, **kwargs): queue, on_consume_callback, args, kwargs = _bind_args(*args, **kwargs) def _cb_wrapper(channel, method, properties, body): - parent_span = tracer.extract(opentracing.Format.HTTP_HEADERS, properties.headers) + parent_span = tracer.extract(opentracing.Format.HTTP_HEADERS, properties.headers, + disable_w3c_trace_context=True) with tracer.start_active_span("rabbitmq", child_of=parent_span) as scope: try: @@ -145,7 +148,8 @@ def _consume(gen): (method_frame, properties, body) = yilded - parent_span = tracer.extract(opentracing.Format.HTTP_HEADERS, properties.headers) + parent_span = tracer.extract(opentracing.Format.HTTP_HEADERS, properties.headers, + disable_w3c_trace_context=True) with tracer.start_active_span("rabbitmq", child_of=parent_span) as scope: try: _extract_consumer_tags(scope.span, diff --git a/instana/instrumentation/pyramid/tweens.py b/instana/instrumentation/pyramid/tweens.py index dd5479b0..e4285019 100644 --- a/instana/instrumentation/pyramid/tweens.py +++ b/instana/instrumentation/pyramid/tweens.py @@ -39,7 +39,8 @@ def __call__(self, request): scope.span.set_tag("http.header.%s" % custom_header, request.headers[h]) if len(request.query_string): - scrubbed_params = strip_secrets_from_query(request.query_string, agent.options.secrets_matcher, agent.options.secrets_list) + scrubbed_params = strip_secrets_from_query(request.query_string, agent.options.secrets_matcher, + agent.options.secrets_list) scope.span.set_tag("http.params", scrubbed_params) response = None diff --git a/instana/instrumentation/sudsjurko.py b/instana/instrumentation/sudsjurko.py index b0aa81e9..0e3e504d 100644 --- a/instana/instrumentation/sudsjurko.py +++ b/instana/instrumentation/sudsjurko.py @@ -13,13 +13,14 @@ from ..util.traceutils import get_active_tracer try: - import suds # noqa + import suds # noqa if (LooseVersion(suds.version.__version__) <= LooseVersion('0.6')): class_method = 'SoapClient.send' else: class_method = '_SoapClient.send' + @wrapt.patch_function_wrapper('suds.client', class_method) def send_with_instana(wrapped, instance, args, kwargs): active_tracer = get_active_tracer() @@ -46,6 +47,7 @@ def send_with_instana(wrapped, instance, args, kwargs): scope.span.set_tag(ext.HTTP_STATUS_CODE, 200) return rv + logger.debug("Instrumenting suds-jurko") except ImportError: pass diff --git a/instana/instrumentation/tornado/client.py b/instana/instrumentation/tornado/client.py index 66f99c86..12f32911 100644 --- a/instana/instrumentation/tornado/client.py +++ b/instana/instrumentation/tornado/client.py @@ -52,7 +52,8 @@ def fetch_with_instana(wrapped, instance, argv, kwargs): # Query param scrubbing parts = request.url.split('?') if len(parts) > 1: - cleaned_qp = strip_secrets_from_query(parts[1], agent.options.secrets_matcher, agent.options.secrets_list) + cleaned_qp = strip_secrets_from_query(parts[1], agent.options.secrets_matcher, + agent.options.secrets_list) scope.span.set_tag("http.params", cleaned_qp) scope.span.set_tag("http.url", parts[0]) @@ -69,6 +70,7 @@ def fetch_with_instana(wrapped, instance, argv, kwargs): logger.debug("tornado fetch", exc_info=True) raise + def finish_tracing(future, scope): try: response = future.result() @@ -84,4 +86,3 @@ def finish_tracing(future, scope): logger.debug("Instrumenting tornado client") except ImportError: pass - diff --git a/instana/instrumentation/tornado/server.py b/instana/instrumentation/tornado/server.py index 89b3599e..6666bb3e 100644 --- a/instana/instrumentation/tornado/server.py +++ b/instana/instrumentation/tornado/server.py @@ -29,12 +29,14 @@ def execute_with_instana(wrapped, instance, argv, kwargs): with tracer_stack_context(): ctx = None if hasattr(instance.request.headers, '__dict__') and '_dict' in instance.request.headers.__dict__: - ctx = tornado_tracer.extract(opentracing.Format.HTTP_HEADERS, instance.request.headers.__dict__['_dict']) + ctx = tornado_tracer.extract(opentracing.Format.HTTP_HEADERS, + instance.request.headers.__dict__['_dict']) scope = tornado_tracer.start_active_span('tornado-server', child_of=ctx) # Query param scrubbing if instance.request.query is not None and len(instance.request.query) > 0: - cleaned_qp = strip_secrets_from_query(instance.request.query, agent.options.secrets_matcher, agent.options.secrets_list) + cleaned_qp = strip_secrets_from_query(instance.request.query, agent.options.secrets_matcher, + agent.options.secrets_list) scope.span.set_tag("http.params", cleaned_qp) url = "%s://%s%s" % (instance.request.protocol, instance.request.host, instance.request.path) @@ -47,7 +49,8 @@ def execute_with_instana(wrapped, instance, argv, kwargs): if agent.options.extra_http_headers is not None: for custom_header in agent.options.extra_http_headers: if custom_header in instance.request.headers: - scope.span.set_tag("http.header.%s" % custom_header, instance.request.headers[custom_header]) + scope.span.set_tag("http.header.%s" % custom_header, + instance.request.headers[custom_header]) setattr(instance.request, "_instana", scope) @@ -91,6 +94,7 @@ def on_finish_with_instana(wrapped, instance, argv, kwargs): except Exception: logger.debug("tornado on_finish", exc_info=True) + @wrapt.patch_function_wrapper('tornado.web', 'RequestHandler.log_exception') def log_exception_with_instana(wrapped, instance, argv, kwargs): try: @@ -105,7 +109,7 @@ def log_exception_with_instana(wrapped, instance, argv, kwargs): except Exception: logger.debug("tornado log_exception", exc_info=True) + logger.debug("Instrumenting tornado server") except ImportError: pass - diff --git a/instana/instrumentation/urllib3.py b/instana/instrumentation/urllib3.py index f783c23f..3c1924c0 100644 --- a/instana/instrumentation/urllib3.py +++ b/instana/instrumentation/urllib3.py @@ -15,6 +15,7 @@ try: import urllib3 + def collect(instance, args, kwargs): """ Build and return a fully qualified URL for this request """ kvs = dict() @@ -36,7 +37,8 @@ def collect(instance, args, kwargs): parts = kvs['path'].split('?') kvs['path'] = parts[0] if len(parts) == 2: - kvs['query'] = strip_secrets_from_query(parts[1], agent.options.secrets_matcher, agent.options.secrets_list) + kvs['query'] = strip_secrets_from_query(parts[1], agent.options.secrets_matcher, + agent.options.secrets_list) if type(instance) is urllib3.connectionpool.HTTPSConnectionPool: kvs['url'] = 'https://%s:%d%s' % (kvs['host'], kvs['port'], kvs['path']) @@ -48,6 +50,7 @@ def collect(instance, args, kwargs): else: return kvs + def collect_response(scope, response): try: scope.span.set_tag(ext.HTTP_STATUS_CODE, response.status) @@ -62,6 +65,7 @@ def collect_response(scope, response): except Exception: logger.debug("collect_response", exc_info=True) + @wrapt.patch_function_wrapper('urllib3', 'HTTPConnectionPool.urlopen') def urlopen_with_instana(wrapped, instance, args, kwargs): active_tracer = get_active_tracer() @@ -92,6 +96,7 @@ def urlopen_with_instana(wrapped, instance, args, kwargs): scope.span.mark_as_errored({'message': e}) raise + logger.debug("Instrumenting urllib3") except ImportError: pass diff --git a/instana/propagators/base_propagator.py b/instana/propagators/base_propagator.py index 44e9461d..b33fcb22 100644 --- a/instana/propagators/base_propagator.py +++ b/instana/propagators/base_propagator.py @@ -5,13 +5,17 @@ import sys -from ..log import logger -from ..util.ids import header_to_id -from ..span_context import SpanContext +from instana.log import logger +from instana.util.ids import header_to_id, header_to_long_id +from instana.span_context import SpanContext +from instana.w3c_trace_context.traceparent import Traceparent +from instana.w3c_trace_context.tracestate import Tracestate +import os PY2 = sys.version_info[0] == 2 PY3 = sys.version_info[0] == 3 + # The carrier can be a dict or a list. # Using the trace header as an example, it can be in the following forms # for extraction: @@ -25,101 +29,263 @@ # X-Instana-T -class BasePropagator(): - UC_HEADER_KEY_T = 'X-INSTANA-T' - UC_HEADER_KEY_S = 'X-INSTANA-S' - UC_HEADER_KEY_L = 'X-INSTANA-L' - UC_HEADER_KEY_SYNTHETIC = 'X-INSTANA-SYNTHETIC' - +class BasePropagator(object): HEADER_KEY_T = 'X-INSTANA-T' HEADER_KEY_S = 'X-INSTANA-S' HEADER_KEY_L = 'X-INSTANA-L' HEADER_KEY_SYNTHETIC = 'X-INSTANA-SYNTHETIC' + HEADER_KEY_TRACEPARENT = "traceparent" + HEADER_KEY_TRACESTATE = "tracestate" LC_HEADER_KEY_T = 'x-instana-t' LC_HEADER_KEY_S = 'x-instana-s' LC_HEADER_KEY_L = 'x-instana-l' LC_HEADER_KEY_SYNTHETIC = 'x-instana-synthetic' - ALT_HEADER_KEY_T = 'HTTP_X_INSTANA_T' - ALT_HEADER_KEY_S = 'HTTP_X_INSTANA_S' - ALT_HEADER_KEY_L = 'HTTP_X_INSTANA_L' ALT_LC_HEADER_KEY_T = 'http_x_instana_t' ALT_LC_HEADER_KEY_S = 'http_x_instana_s' ALT_LC_HEADER_KEY_L = 'http_x_instana_l' ALT_LC_HEADER_KEY_SYNTHETIC = 'http_x_instana_synthetic' + ALT_HEADER_KEY_TRACEPARENT = "http_traceparent" + ALT_HEADER_KEY_TRACESTATE = "http_tracestate" + + # ByteArray variations + B_HEADER_KEY_T = b'x-instana-t' + B_HEADER_KEY_S = b'x-instana-s' + B_HEADER_KEY_L = b'x-instana-l' + B_HEADER_KEY_SYNTHETIC = b'x-instana-synthetic' + B_HEADER_SERVER_TIMING = b'server-timing' + B_HEADER_KEY_TRACEPARENT = b'traceparent' + B_HEADER_KEY_TRACESTATE = b'tracestate' + + B_ALT_LC_HEADER_KEY_T = b'http_x_instana_t' + B_ALT_LC_HEADER_KEY_S = b'http_x_instana_s' + B_ALT_LC_HEADER_KEY_L = b'http_x_instana_l' + B_ALT_LC_HEADER_KEY_SYNTHETIC = b'http_x_instana_synthetic' + B_ALT_HEADER_KEY_TRACEPARENT = b'http_traceparent' + B_ALT_HEADER_KEY_TRACESTATE = b'http_tracestate' - def extract(self, carrier): + def __init__(self): + self._tp = Traceparent() + self._ts = Tracestate() + + @staticmethod + def _extract_headers_dict(carrier): + """ + This method converts the incoming carrier into a dict + :param carrier: + :return: dc dictionary """ - Search carrier for the *HEADER* keys and return a SpanContext or None + try: + if isinstance(carrier, dict): + dc = carrier + elif hasattr(carrier, "__dict__"): + dc = carrier.__dict__ + else: + dc = dict(carrier) + except Exception: + logger.debug("extract: Couldn't convert %s", carrier) + dc = None - Note: Extract is on the base class since it never really varies in task regardless - of the propagator in uses. + return dc - :param carrier: The dict or list potentially containing context - :return: SpanContext or None + @staticmethod + def _get_ctx_level(level): """ - trace_id = None - span_id = None - level = 1 - synthetic = False - dc = None + Extract the level value and return it, as it may include correlation values + :param level: + :return: + """ + try: + ctx_level = int(level.split(",")[0]) if level else 1 + except Exception: + ctx_level = 1 + return ctx_level + @staticmethod + def _set_correlation_properties(level, ctx): + """ + Set the correlation values if they are present + :param level: + :param ctx: + :return: + """ try: - # Attempt to convert incoming into a dict - try: - if isinstance(carrier, dict): - dc = carrier - elif hasattr(carrier, "__dict__"): - dc = carrier.__dict__ - else: - dc = dict(carrier) - except Exception: - logger.debug("extract: Couln't convert %s", carrier) - - if dc is None: - return None + ctx.correlation_type = level.split(",")[1].split("correlationType=")[1].split(";")[0] + if "correlationId" in level: + ctx.correlation_id = level.split(",")[1].split("correlationId=")[1].split(";")[0] + except Exception: + logger.debug("extract instana correlation type/id error:", exc_info=True) - # Headers can exist in the standard X-Instana-T/S format or the alternate HTTP_X_INSTANA_T/S style - # We do a case insensitive search to cover all possible variations of incoming headers. - for key in dc.keys(): - lc_key = None - - if PY3 is True and isinstance(key, bytes): - lc_key = key.decode("utf-8").lower() - else: - lc_key = key.lower() - - if self.LC_HEADER_KEY_T == lc_key: - trace_id = header_to_id(dc[key]) - elif self.LC_HEADER_KEY_S == lc_key: - span_id = header_to_id(dc[key]) - elif self.LC_HEADER_KEY_L == lc_key: - level = dc[key] - elif self.LC_HEADER_KEY_SYNTHETIC == lc_key: - synthetic = dc[key] in ['1', b'1'] - - elif self.ALT_LC_HEADER_KEY_T == lc_key: - trace_id = header_to_id(dc[key]) - elif self.ALT_LC_HEADER_KEY_S == lc_key: - span_id = header_to_id(dc[key]) - elif self.ALT_LC_HEADER_KEY_L == lc_key: - level = dc[key] - elif self.ALT_LC_HEADER_KEY_SYNTHETIC == lc_key: - synthetic = dc[key] in ['1', b'1'] - - ctx = None - if trace_id is not None and span_id is not None: - ctx = SpanContext(span_id=span_id, - trace_id=trace_id, - level=level, - baggage={}, - sampled=True, - synthetic=synthetic) - elif synthetic: - ctx = SpanContext(synthetic=synthetic) + def _get_participating_trace_context(self, span_context): + """ + This method is called for getting the updated traceparent and tracestate values + :param span_context: + :return: traceparent, tracestate + """ + if span_context.long_trace_id and not span_context.trace_parent: + tp_trace_id = span_context.long_trace_id + else: + tp_trace_id = span_context.trace_id + traceparent = span_context.traceparent + tracestate = span_context.tracestate + traceparent = self._tp.update_traceparent(traceparent, tp_trace_id, span_context.span_id, span_context.level) + tracestate = self._ts.update_tracestate(tracestate, span_context.trace_id, span_context.span_id) + return traceparent, tracestate - return ctx + def __determine_span_context(self, trace_id, span_id, level, synthetic, traceparent, tracestate, + disable_w3c_trace_context): + """ + This method determines the span context depending on a set of conditions being met + Detailed description of the conditions can be found here: + https://github.com/instana/technical-documentation/tree/master/tracing/specification#http-processing-for-instana-tracers + :param trace_id: instana trace id + :param span_id: instana span id + :param level: instana level + :param synthetic: instana synthetic + :param traceparent: + :param tracestate: + :param disable_w3c_trace_context: flag used to enable w3c trace context only on HTTP requests + :return: ctx + """ + correlation = False + disable_traceparent = os.environ.get("INSTANA_DISABLE_W3C_TRACE_CORRELATION", "") + instana_ancestor = None + ctx = SpanContext() + if level and "correlationType" in level: + trace_id, span_id = [None] * 2 + correlation = True + + ctx_level = self._get_ctx_level(level) + + if trace_id and span_id: + ctx.trace_id = trace_id[-16:] # only the last 16 chars + ctx.span_id = span_id[-16:] # only the last 16 chars + ctx.level = ctx_level + ctx.synthetic = synthetic is not None + + if len(trace_id) > 16: + ctx.long_trace_id = trace_id + + elif not disable_w3c_trace_context and traceparent and trace_id is None and span_id is None: + _, tp_trace_id, tp_parent_id, _ = self._tp.get_traceparent_fields(traceparent) + + if tracestate and "in=" in tracestate: + instana_ancestor = self._ts.get_instana_ancestor(tracestate) + + if disable_traceparent == "": + ctx.trace_id = tp_trace_id[-16:] + ctx.span_id = tp_parent_id + ctx.level = ctx_level + ctx.synthetic = synthetic is not None + ctx.trace_parent = True + ctx.instana_ancestor = instana_ancestor + ctx.long_trace_id = tp_trace_id + else: + if instana_ancestor: + ctx.trace_id = instana_ancestor.t + ctx.span_id = instana_ancestor.p + ctx.level = ctx_level + ctx.synthetic = synthetic is not None + + elif synthetic: + ctx.synthetic = synthetic + + if correlation: + self._set_correlation_properties(level, ctx) + + if traceparent: + ctx.traceparent = traceparent + ctx.tracestate = tracestate + + return ctx + + def __extract_instana_headers(self, dc): + """ + Search carrier for the *HEADER* keys and return the tracing key-values + + :param dc: The dict or list potentially containing context + :return: trace_id, span_id, level, synthetic + """ + trace_id, span_id, level, synthetic = [None] * 4 + + # Headers can exist in the standard X-Instana-T/S format or the alternate HTTP_X_INSTANA_T/S style + try: + trace_id = dc.get(self.LC_HEADER_KEY_T) or dc.get(self.ALT_LC_HEADER_KEY_T) or dc.get( + self.B_HEADER_KEY_T) or dc.get(self.B_ALT_LC_HEADER_KEY_T) + if trace_id: + trace_id = header_to_long_id(trace_id) + + span_id = dc.get(self.LC_HEADER_KEY_S) or dc.get(self.ALT_LC_HEADER_KEY_S) or dc.get( + self.B_HEADER_KEY_S) or dc.get(self.B_ALT_LC_HEADER_KEY_S) + if span_id: + span_id = header_to_id(span_id) + + level = dc.get(self.LC_HEADER_KEY_L) or dc.get(self.ALT_LC_HEADER_KEY_L) or dc.get( + self.B_HEADER_KEY_L) or dc.get(self.B_ALT_LC_HEADER_KEY_L) + if level and PY3 is True and isinstance(level, bytes): + level = level.decode("utf-8") + + synthetic = dc.get(self.LC_HEADER_KEY_SYNTHETIC) or dc.get(self.ALT_LC_HEADER_KEY_SYNTHETIC) or dc.get( + self.B_HEADER_KEY_SYNTHETIC) or dc.get(self.B_ALT_LC_HEADER_KEY_SYNTHETIC) + if synthetic: + synthetic = synthetic in ['1', b'1'] except Exception: logger.debug("extract error:", exc_info=True) + + return trace_id, span_id, level, synthetic + + def __extract_w3c_trace_context_headers(self, dc): + """ + Search carrier for the *HEADER* keys and return the tracing key-values + + :param dc: The dict or list potentially containing context + :return: traceparent, tracestate + """ + traceparent, tracestate = [None] * 2 + + try: + traceparent = dc.get(self.HEADER_KEY_TRACEPARENT) or dc.get(self.ALT_HEADER_KEY_TRACEPARENT) or dc.get( + self.B_HEADER_KEY_TRACEPARENT) or dc.get(self.B_ALT_HEADER_KEY_TRACEPARENT) + if traceparent and PY3 is True and isinstance(traceparent, bytes): + traceparent = traceparent.decode("utf-8") + + tracestate = dc.get(self.HEADER_KEY_TRACESTATE) or dc.get(self.ALT_HEADER_KEY_TRACESTATE) or dc.get( + self.B_HEADER_KEY_TRACESTATE) or dc.get(self.B_ALT_HEADER_KEY_TRACESTATE) + if tracestate and PY3 is True and isinstance(tracestate, bytes): + tracestate = tracestate.decode("utf-8") + + except Exception: + logger.debug("extract error:", exc_info=True) + + return traceparent, tracestate + + def extract(self, carrier, disable_w3c_trace_context=False): + """ + This method overrides the one of the Baseclass as with the introduction of W3C trace context for the HTTP + requests more extracting steps and logic was required + :param disable_w3c_trace_context: + :param carrier: + :return: the context or None + """ + try: + traceparent, tracestate = [None] * 2 + headers = self._extract_headers_dict(carrier=carrier) + if headers is None: + return None + headers = {k.lower(): v for k, v in headers.items()} + + trace_id, span_id, level, synthetic = self.__extract_instana_headers(dc=headers) + if not disable_w3c_trace_context: + traceparent, tracestate = self.__extract_w3c_trace_context_headers(dc=headers) + + if traceparent: + traceparent = self._tp.validate(traceparent) + + ctx = self.__determine_span_context(trace_id, span_id, level, synthetic, traceparent, tracestate, + disable_w3c_trace_context) + + return ctx + except Exception: + logger.debug("extract error:", exc_info=True) \ No newline at end of file diff --git a/instana/propagators/binary_propagator.py b/instana/propagators/binary_propagator.py index f22a691e..cf3d7926 100644 --- a/instana/propagators/binary_propagator.py +++ b/instana/propagators/binary_propagator.py @@ -3,8 +3,8 @@ from __future__ import absolute_import -from ..log import logger -from .base_propagator import BasePropagator +from instana.log import logger +from instana.propagators.base_propagator import BasePropagator class BinaryPropagator(BasePropagator): @@ -12,36 +12,63 @@ class BinaryPropagator(BasePropagator): A Propagator for BINARY. The BINARY format represents SpanContexts in an opaque bytearray carrier. """ - + # ByteArray variations from base class HEADER_KEY_T = b'x-instana-t' HEADER_KEY_S = b'x-instana-s' HEADER_KEY_L = b'x-instana-l' HEADER_SERVER_TIMING = b'server-timing' + HEADER_KEY_TRACEPARENT = b'traceparent' + HEADER_KEY_TRACESTATE = b'tracestate' + + def __init__(self): + super(BinaryPropagator, self).__init__() - def inject(self, span_context, carrier): + def inject(self, span_context, carrier, disable_w3c_trace_context=True): try: trace_id = str.encode(span_context.trace_id) span_id = str.encode(span_context.span_id) - level = str.encode("1") + level = str.encode(str(span_context.level)) server_timing = str.encode("intid;desc=%s" % span_context.trace_id) + if disable_w3c_trace_context: + traceparent, tracestate = [None] * 2 + else: + traceparent, tracestate = self._get_participating_trace_context(span_context) + try: + traceparent = str.encode(traceparent) + tracestate = str.encode(tracestate) + except Exception: + traceparent, tracestate = [None] * 2 + if isinstance(carrier, dict) or hasattr(carrier, "__dict__"): + if traceparent and tracestate: + carrier[self.HEADER_KEY_TRACEPARENT] = traceparent + carrier[self.HEADER_KEY_TRACESTATE] = tracestate carrier[self.HEADER_KEY_T] = trace_id carrier[self.HEADER_KEY_S] = span_id carrier[self.HEADER_KEY_L] = level carrier[self.HEADER_SERVER_TIMING] = server_timing elif isinstance(carrier, list): + if traceparent and tracestate: + carrier.append((self.HEADER_KEY_TRACEPARENT, traceparent)) + carrier.append((self.HEADER_KEY_TRACESTATE, tracestate)) carrier.append((self.HEADER_KEY_T, trace_id)) carrier.append((self.HEADER_KEY_S, span_id)) carrier.append((self.HEADER_KEY_L, level)) carrier.append((self.HEADER_SERVER_TIMING, server_timing)) elif isinstance(carrier, tuple): + if traceparent and tracestate: + carrier = carrier.__add__(((self.HEADER_KEY_TRACEPARENT, traceparent),)) + carrier = carrier.__add__(((self.HEADER_KEY_TRACESTATE, tracestate),)) carrier = carrier.__add__(((self.HEADER_KEY_T, trace_id),)) carrier = carrier.__add__(((self.HEADER_KEY_S, span_id),)) carrier = carrier.__add__(((self.HEADER_KEY_L, level),)) carrier = carrier.__add__(((self.HEADER_SERVER_TIMING, server_timing),)) elif hasattr(carrier, '__setitem__'): + if traceparent and tracestate: + carrier.__setitem__(self.HEADER_KEY_TRACEPARENT, traceparent) + carrier.__setitem__(self.HEADER_KEY_TRACESTATE, tracestate) carrier.__setitem__(self.HEADER_KEY_T, trace_id) carrier.__setitem__(self.HEADER_KEY_S, span_id) carrier.__setitem__(self.HEADER_KEY_L, level) @@ -52,3 +79,5 @@ def inject(self, span_context, carrier): return carrier except Exception: logger.debug("inject error:", exc_info=True) + + diff --git a/instana/propagators/http_propagator.py b/instana/propagators/http_propagator.py index f4c9ca4a..4e2b22cf 100644 --- a/instana/propagators/http_propagator.py +++ b/instana/propagators/http_propagator.py @@ -3,13 +3,9 @@ from __future__ import absolute_import -import sys +from instana.log import logger +from instana.propagators.base_propagator import BasePropagator -from ..log import logger -from .base_propagator import BasePropagator - -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 class HTTPPropagator(BasePropagator): """ @@ -18,20 +14,39 @@ class HTTPPropagator(BasePropagator): The HTTP_HEADERS format deals with key-values with string to string mapping. The character set should be restricted to HTTP compatible. """ - def inject(self, span_context, carrier): + + def __init__(self): + super(HTTPPropagator, self).__init__() + + def inject(self, span_context, carrier, disable_w3c_trace_context=False): try: trace_id = span_context.trace_id span_id = span_context.span_id + level = span_context.level + + if disable_w3c_trace_context: + traceparent, tracestate = [None] * 2 + else: + traceparent, tracestate = self._get_participating_trace_context(span_context) if isinstance(carrier, dict) or hasattr(carrier, "__dict__"): + if traceparent and tracestate: + carrier[self.HEADER_KEY_TRACEPARENT] = traceparent + carrier[self.HEADER_KEY_TRACESTATE] = tracestate carrier[self.HEADER_KEY_T] = trace_id carrier[self.HEADER_KEY_S] = span_id carrier[self.HEADER_KEY_L] = "1" elif isinstance(carrier, list): + if traceparent and tracestate: + carrier.append((self.HEADER_KEY_TRACEPARENT, traceparent)) + carrier.append((self.HEADER_KEY_TRACESTATE, tracestate)) carrier.append((self.HEADER_KEY_T, trace_id)) carrier.append((self.HEADER_KEY_S, span_id)) carrier.append((self.HEADER_KEY_L, "1")) elif hasattr(carrier, '__setitem__'): + if traceparent and tracestate: + carrier.__setitem__(self.HEADER_KEY_TRACEPARENT, traceparent) + carrier.__setitem__(self.HEADER_KEY_TRACESTATE, tracestate) carrier.__setitem__(self.HEADER_KEY_T, trace_id) carrier.__setitem__(self.HEADER_KEY_S, span_id) carrier.__setitem__(self.HEADER_KEY_L, "1") @@ -40,3 +55,13 @@ def inject(self, span_context, carrier): except Exception: logger.debug("inject error:", exc_info=True) + + + + + + + + + + diff --git a/instana/propagators/text_propagator.py b/instana/propagators/text_propagator.py index be0c553e..02af14a5 100644 --- a/instana/propagators/text_propagator.py +++ b/instana/propagators/text_propagator.py @@ -3,8 +3,8 @@ from __future__ import absolute_import -from ..log import logger -from .base_propagator import BasePropagator +from instana.log import logger +from instana.propagators.base_propagator import BasePropagator class TextPropagator(BasePropagator): @@ -15,7 +15,7 @@ class TextPropagator(BasePropagator): The character set is unrestricted. """ - def inject(self, span_context, carrier): + def inject(self, span_context, carrier, disable_w3c_trace_context=True): try: trace_id = span_context.trace_id span_id = span_context.span_id diff --git a/instana/recorder.py b/instana/recorder.py index 037e2614..6ff0699e 100644 --- a/instana/recorder.py +++ b/instana/recorder.py @@ -26,7 +26,7 @@ class StanRecorder(object): "gcps-consumer", "log", "memcache", "mongo", "mysql", "postgres", "pymongo", "rabbitmq", "redis","render", "rpc-client", "rpc-server", "sqlalchemy", "soap", - "tornado-client", "tornado-server", "urllib3", "wsgi") + "tornado-client", "tornado-server", "urllib3", "wsgi", "asgi") # Recorder thread for collection/reporting of spans thread = None diff --git a/instana/span.py b/instana/span.py index 0cebd032..bd33d67c 100644 --- a/instana/span.py +++ b/instana/span.py @@ -117,6 +117,18 @@ def __init__(self, span, source, service_name, **kwargs): self.__dict__.update(kwargs) + def _populate_extra_span_attributes(self, span): + if span.context.trace_parent: + self.tp = span.context.trace_parent + if span.context.instana_ancestor: + self.ia = span.context.instana_ancestor + if span.context.long_trace_id: + self.lt = span.context.long_trace_id + if span.context.correlation_type: + self.crtp = span.context.correlation_type + if span.context.correlation_id: + self.crid = span.context.correlation_id + def _validate_tags(self, tags): """ This method will loop through a set of tags to validate each key and value. @@ -231,15 +243,15 @@ def get_span_kind(self, span): class RegisteredSpan(BaseSpan): - HTTP_SPANS = ("aiohttp-client", "aiohttp-server", "asgi", "django", "http", "soap", "tornado-client", - "tornado-server", "urllib3", "wsgi") + HTTP_SPANS = ("aiohttp-client", "aiohttp-server", "django", "http", "soap", "tornado-client", + "tornado-server", "urllib3", "wsgi", "asgi") EXIT_SPANS = ("aiohttp-client", "boto3", "cassandra", "celery-client", "couchbase", "log", "memcache", "mongo", "mysql", "postgres", "rabbitmq", "redis", "rpc-client", "sqlalchemy", "soap", "tornado-client", "urllib3", "pymongo", "gcs", "gcps-producer") ENTRY_SPANS = ("aiohttp-server", "aws.lambda.entry", "celery-worker", "django", "wsgi", "rabbitmq", - "rpc-server", "tornado-server", "gcps-consumer") + "rpc-server", "tornado-server", "gcps-consumer", "asgi") LOCAL_SPANS = ("render") @@ -253,6 +265,7 @@ def __init__(self, span, source, service_name, **kwargs): # entry self._populate_entry_span_data(span) self.data["service"] = service_name + self._populate_extra_span_attributes(span) elif span.operation_name in self.EXIT_SPANS: self.k = 2 # exit self._populate_exit_span_data(span) diff --git a/instana/span_context.py b/instana/span_context.py index 9e4312eb..25c505a5 100644 --- a/instana/span_context.py +++ b/instana/span_context.py @@ -10,7 +10,8 @@ def __init__( baggage=None, sampled=True, level=1, - synthetic=False): + synthetic=False + ): self.level = level self.trace_id = trace_id @@ -19,6 +20,70 @@ def __init__( self.synthetic = synthetic self._baggage = baggage or {} + self.trace_parent = None # true/false flag + self.instana_ancestor = None + self.long_trace_id = None + self.correlation_type = None + self.correlation_id = None + self.traceparent = None # temporary storage of the validated traceparent header of the incoming request + self.tracestate = None # temporary storage of the tracestate header + + @property + def traceparent(self): + return self._traceparent + + @traceparent.setter + def traceparent(self, value): + self._traceparent = value + + @property + def tracestate(self): + return self._tracestate + + @tracestate.setter + def tracestate(self, value): + self._tracestate = value + + @property + def trace_parent(self): + return self._trace_parent + + @trace_parent.setter + def trace_parent(self, value): + self._trace_parent = value + + @property + def instana_ancestor(self): + return self._instana_ancestor + + @instana_ancestor.setter + def instana_ancestor(self, value): + self._instana_ancestor = value + + @property + def long_trace_id(self): + return self._long_trace_id + + @long_trace_id.setter + def long_trace_id(self, value): + self._long_trace_id = value + + @property + def correlation_type(self): + return self._correlation_type + + @correlation_type.setter + def correlation_type(self, value): + self._correlation_type = value + + @property + def correlation_id(self): + return self._correlation_id + + @correlation_id.setter + def correlation_id(self, value): + self._correlation_id = value + @property def baggage(self): return self._baggage diff --git a/instana/tracer.py b/instana/tracer.py index bbf3c2d7..fd2e97ef 100644 --- a/instana/tracer.py +++ b/instana/tracer.py @@ -89,9 +89,21 @@ def start_span(self, ctx._baggage = parent_ctx._baggage.copy() ctx.trace_id = parent_ctx.trace_id ctx.sampled = parent_ctx.sampled + ctx.long_trace_id = parent_ctx.long_trace_id + ctx.trace_parent = parent_ctx.trace_parent + ctx.instana_ancestor = parent_ctx.instana_ancestor + ctx.correlation_type = parent_ctx.correlation_type + ctx.correlation_id = parent_ctx.correlation_id + ctx.traceparent = parent_ctx.traceparent + ctx.tracestate = parent_ctx.tracestate else: ctx.trace_id = gid ctx.sampled = self.sampler.sampled(ctx.trace_id) + if parent_ctx is not None: + ctx.correlation_type = parent_ctx.correlation_type + ctx.correlation_id = parent_ctx.correlation_id + ctx.traceparent = parent_ctx.traceparent + ctx.tracestate = parent_ctx.tracestate # Tie it all together span = InstanaSpan(self, @@ -109,15 +121,15 @@ def start_span(self, return span - def inject(self, span_context, format, carrier): + def inject(self, span_context, format, carrier, disable_w3c_trace_context=False): if format in self._propagators: - return self._propagators[format].inject(span_context, carrier) + return self._propagators[format].inject(span_context, carrier, disable_w3c_trace_context) raise ot.UnsupportedFormatException() - def extract(self, format, carrier): + def extract(self, format, carrier, disable_w3c_trace_context=False): if format in self._propagators: - return self._propagators[format].extract(carrier) + return self._propagators[format].extract(carrier, disable_w3c_trace_context) raise ot.UnsupportedFormatException() diff --git a/instana/util/ids.py b/instana/util/ids.py index 01749934..4449916b 100644 --- a/instana/util/ids.py +++ b/instana/util/ids.py @@ -19,6 +19,7 @@ else: string_types = str + def generate_id(): """ Generate a 64bit base 16 ID for use as a Span or Trace ID """ global _current_pid @@ -35,6 +36,35 @@ def generate_id(): return new_id +def header_to_long_id(header): + """ + We can receive headers in the following formats: + 1. unsigned base 16 hex string (or bytes) of variable length + 2. [eventual] + + :param header: the header to analyze, validate and convert (if needed) + :return: a valid ID to be used internal to the tracer + """ + if PY3 is True and isinstance(header, bytes): + header = header.decode('utf-8') + + if not isinstance(header, string_types): + return BAD_ID + + try: + # Test that header is truly a hexadecimal value before we try to convert + int(header, 16) + + length = len(header) + if length < 16: + # Left pad ID with zeros + header = header.zfill(16) + + return header + except ValueError: + return BAD_ID + + def header_to_id(header): """ We can receive headers in the following formats: @@ -61,7 +91,6 @@ def header_to_id(header): elif length > 16: # Phase 0: Discard everything but the last 16byte header = header[-16:] - return header except ValueError: return BAD_ID diff --git a/instana/version.py b/instana/version.py index 7cc1a6b0..19afcaaf 100644 --- a/instana/version.py +++ b/instana/version.py @@ -3,4 +3,4 @@ # Module version file. Used by setup.py and snapshot reporting. -VERSION = '1.34.1' +VERSION = '1.35.0' diff --git a/instana/w3c_trace_context/__init__.py b/instana/w3c_trace_context/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/instana/w3c_trace_context/traceparent.py b/instana/w3c_trace_context/traceparent.py new file mode 100644 index 00000000..a3876a9c --- /dev/null +++ b/instana/w3c_trace_context/traceparent.py @@ -0,0 +1,71 @@ +# (c) Copyright IBM Corp. 2021 +# (c) Copyright Instana Inc. 2021 + +from ..log import logger +import re + + +class Traceparent: + SPECIFICATION_VERSION = "00" + TRACEPARENT_REGEX = re.compile("^[0-9a-f]{2}-(?!0{32})([0-9a-f]{32})-(?!0{16})([0-9a-f]{16})-[0-9a-f]{2}") + + def validate(self, traceparent): + """ + Method used to validate the traceparent header + :param traceparent: string + :return: traceparent or None + """ + try: + if self.TRACEPARENT_REGEX.match(traceparent): + return traceparent + except Exception: + logger.debug("traceparent does not follow version {} specification".format(self.SPECIFICATION_VERSION)) + return None + + @staticmethod + def get_traceparent_fields(traceparent): + """ + Parses the validated traceparent header into its fields and returns the fields + :param traceparent: the original validated traceparent header + :return: version, trace_id, parent_id, trace_flags + """ + try: + traceparent_properties = traceparent.split("-") + version = traceparent_properties[0] + trace_id = traceparent_properties[1] + parent_id = traceparent_properties[2] + trace_flags = traceparent_properties[3] + return version, trace_id, parent_id, trace_flags + except Exception: # This method is intended to be called with a version 00 validated traceparent + # This exception handling is added just for making sure we do not throw any unhandled exception + # if somebody calls the method in the future without a validated traceparent + return None, None, None, None + + def update_traceparent(self, traceparent, in_trace_id, in_span_id, level): + """ + This method updates the traceparent header or generates one if there was no traceparent incoming header or it + was invalid + :param traceparent: the original validated traceparent header + :param in_trace_id: instana trace id, used when there is no preexisting trace_id from the traceparent header + :param in_span_id: instana span id, used to update the parent id of the traceparent header + :param level: instana level, used to determine the value of sampled flag of the traceparent header + :return: the updated traceparent header + """ + mask = 1 << 0 + trace_flags = 0 + if traceparent is None: # modify the trace_id part only when it was not present at all + trace_id = in_trace_id.zfill(32) + version = self.SPECIFICATION_VERSION + else: + version, trace_id, _, trace_flags = self.get_traceparent_fields(traceparent) + trace_flags = int(trace_flags, 16) + + parent_id = in_span_id.zfill(16) + trace_flags = (trace_flags & ~mask) | ((level << 0) & mask) + trace_flags = format(trace_flags, '0>2x') + + traceparent = "{version}-{traceid}-{parentid}-{trace_flags}".format(version=version, + traceid=trace_id, + parentid=parent_id, + trace_flags=trace_flags) + return traceparent diff --git a/instana/w3c_trace_context/tracestate.py b/instana/w3c_trace_context/tracestate.py new file mode 100644 index 00000000..b1d066ea --- /dev/null +++ b/instana/w3c_trace_context/tracestate.py @@ -0,0 +1,82 @@ +# (c) Copyright IBM Corp. 2021 +# (c) Copyright Instana Inc. 2021 + +from ..log import logger + + +class InstanaAncestor: + def __init__(self, trace_id, parent_id): + self.t = trace_id + self.p = parent_id + + +class Tracestate: + MAX_NUMBER_OF_LIST_MEMBERS = 32 + REMOVE_ENTRIES_LARGER_THAN = 128 + + @staticmethod + def get_instana_ancestor(tracestate): + """ + Constructs the instana ancestor object and returns it + :param tracestate: the original tracestate value + :return: instana ancestor instance + """ + try: + in_list_member = tracestate.strip().split("in=")[1].split(",")[0] + + ia = InstanaAncestor(trace_id=in_list_member.split(";")[0], + parent_id=in_list_member.split(";")[1]) + return ia + + except Exception: + logger.debug("extract instana ancestor error:", exc_info=True) + return None + + def update_tracestate(self, tracestate, in_trace_id, in_span_id): + """ + Method to update the tracestate property with the instana trace_id and span_id + + :param tracestate: original tracestate header + :param in_trace_id: instana trace_id + :param in_span_id: instana parent_id + :return: tracestate updated + """ + try: + span_id = in_span_id.zfill(16) # if span_id is shorter than 16 characters we prepend zeros + instana_tracestate = "in={};{}".format(in_trace_id, span_id) + if tracestate is None or tracestate == "": + tracestate = instana_tracestate + else: + # remove the existing in= entry + if "in=" in tracestate: + splitted = tracestate.split("in=") + before_in = splitted[0] + after_in = splitted[1].split(",")[1:] + tracestate = '{}{}'.format(before_in, ",".join(after_in)) + # tracestate can contain a max of 32 list members, if it contains up to 31 + # we can safely add the instana one without the need to truncate anything + if len(tracestate.split(",")) <= self.MAX_NUMBER_OF_LIST_MEMBERS - 1: + tracestate = "{},{}".format(instana_tracestate, tracestate) + else: + list_members = tracestate.split(",") + list_members_to_remove = len(list_members) - self.MAX_NUMBER_OF_LIST_MEMBERS + 1 + # Number 1 priority members to be removed are the ones larger than 128 characters + for i, m in reversed(list(enumerate(list_members))): + if len(m) > self.REMOVE_ENTRIES_LARGER_THAN: + list_members.pop(i) + list_members_to_remove -= 1 + if list_members_to_remove == 0: + break + # if there are still more than 31 list members remaining, we remove as many members + # from the end as necessary to remain just 31 list members + while list_members_to_remove > 0: + list_members.pop() + list_members_to_remove -= 1 + # update the tracestate containing just 31 list members + tracestate = ",".join(list_members) + # adding instana as first list member, total of 32 list members + tracestate = "{},{}".format(instana_tracestate, tracestate) + except Exception: + logger.debug("Something went wrong while updating tracestate: {}:".format(tracestate), exc_info=True) + + return tracestate diff --git a/tests/apps/flask_app/app.py b/tests/apps/flask_app/app.py index 2a043323..e6f18aeb 100755 --- a/tests/apps/flask_app/app.py +++ b/tests/apps/flask_app/app.py @@ -19,7 +19,7 @@ # in test sets that don't install/test for it. pass -from ...helpers import testenv +from tests.helpers import testenv from instana.singletons import tracer logging.basicConfig(level=logging.WARNING) diff --git a/tests/apps/sanic_app/server.py b/tests/apps/sanic_app/server.py index 1780e2bb..2cf712fc 100644 --- a/tests/apps/sanic_app/server.py +++ b/tests/apps/sanic_app/server.py @@ -3,8 +3,8 @@ from sanic import Sanic from sanic.exceptions import SanicException -from .simpleview import SimpleView -from .name import NameView +from tests.apps.sanic_app.simpleview import SimpleView +from tests.apps.sanic_app.name import NameView from sanic.response import text import instana diff --git a/tests/clients/test_pika.py b/tests/clients/test_pika.py index 931bb512..bfa9740c 100644 --- a/tests/clients/test_pika.py +++ b/tests/clients/test_pika.py @@ -13,6 +13,7 @@ from ..helpers import testenv from instana.singletons import tracer + class _TestPika(unittest.TestCase): @staticmethod @mock.patch('pika.connection.Connection') @@ -35,6 +36,7 @@ def tearDown(self): del self._on_openok_callback del self.obj + class TestPikaChannel(_TestPika): def _create_obj(self): return pika.channel.Channel(self.connection, 1, self._on_openok_callback) @@ -78,10 +80,10 @@ def test_basic_publish(self, send_method, _unused): pika.spec.Basic.Publish( exchange="test.exchange", routing_key="test.queue"), (pika.spec.BasicProperties(headers={ - "X-INSTANA-T": rabbitmq_span.t, - "X-INSTANA-S": rabbitmq_span.s, - "X-INSTANA-L": "1" - }), b"Hello!")) + "X-INSTANA-T": rabbitmq_span.t, + "X-INSTANA-S": rabbitmq_span.s, + "X-INSTANA-L": "1" + }), b"Hello!")) @mock.patch('pika.spec.Basic.Publish') @mock.patch('pika.channel.Channel._send_method') @@ -106,11 +108,11 @@ def test_basic_publish_with_headers(self, send_method, _unused): pika.spec.Basic.Publish( exchange="test.exchange", routing_key="test.queue"), (pika.spec.BasicProperties(headers={ - "X-Custom-1": "test", - "X-INSTANA-T": rabbitmq_span.t, - "X-INSTANA-S": rabbitmq_span.s, - "X-INSTANA-L": "1" - }), b"Hello!")) + "X-Custom-1": "test", + "X-INSTANA-T": rabbitmq_span.t, + "X-INSTANA-S": rabbitmq_span.s, + "X-INSTANA-L": "1" + }), b"Hello!")) @mock.patch('pika.spec.Basic.Get') def test_basic_get(self, _unused): @@ -262,6 +264,7 @@ def test_basic_consume_with_trace_context(self, _unused): self.assertIsNotNone(rabbitmq_span.s) self.assertNotEqual(rabbitmq_span.p, rabbitmq_span.s) + class TestPikaBlockingChannel(_TestPika): @mock.patch('pika.channel.Channel', spec=pika.channel.Channel) def _create_obj(self, channel_impl): @@ -282,6 +285,7 @@ def _generate_delivery(self, consumer_tag, properties, body): def test_consume(self): consumed_deliveries = [] + def __consume(): for delivery in self.obj.consume("test.queue", inactivity_timeout=3.0): # Skip deliveries generated due to inactivity @@ -331,6 +335,7 @@ def __consume(): def test_consume_with_trace_context(self): consumed_deliveries = [] + def __consume(): for delivery in self.obj.consume("test.queue", inactivity_timeout=3.0): # Skip deliveries generated due to inactivity diff --git a/tests/frameworks/test_django.py b/tests/frameworks/test_django.py index bd743c91..b4011304 100644 --- a/tests/frameworks/test_django.py +++ b/tests/frameworks/test_django.py @@ -7,7 +7,7 @@ from django.apps import apps from ..apps.app_django import INSTALLED_APPS from django.contrib.staticfiles.testing import StaticLiveServerTestCase - +import os from instana.singletons import agent, tracer from ..helpers import fail_with_message_and_span_dump, get_first_span_by_filter, drop_log_spans_from_list @@ -24,7 +24,7 @@ def setUp(self): def tearDown(self): """ Do nothing for now """ - return None + os.environ["INSTANA_DISABLE_W3C_TRACE_CORRELATION"] = "" def test_basic_request(self): with tracer.start_active_span('test'): @@ -310,6 +310,8 @@ def test_with_incoming_context(self): request_headers = dict() request_headers['X-INSTANA-T'] = '1' request_headers['X-INSTANA-S'] = '1' + request_headers['traceparent'] = '01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01-788777' + request_headers['tracestate'] = 'rojo=00f067aa0ba902b7,in=a3ce929d0e0e4736;8357ccd9da194656,congo=t61rcWkgMzE' response = self.http.request('GET', self.live_server_url + '/', headers=request_headers) @@ -335,6 +337,153 @@ def test_with_incoming_context(self): assert ('X-INSTANA-L' in response.headers) self.assertEqual('1', response.headers['X-INSTANA-L']) + assert ('traceparent' in response.headers) + self.assertEqual('01-4bf92f3577b34da6a3ce929d0e0e4736-{}-01'.format(django_span.s), + response.headers['traceparent']) + + assert ('tracestate' in response.headers) + self.assertEqual( + 'in={};{},rojo=00f067aa0ba902b7,congo=t61rcWkgMzE'.format( + django_span.t, django_span.s), response.headers['tracestate']) + server_timing_value = "intid;desc=%s" % django_span.t + assert ('Server-Timing' in response.headers) + self.assertEqual(server_timing_value, response.headers['Server-Timing']) + + def test_with_incoming_context_and_correlation(self): + request_headers = dict() + request_headers['X-INSTANA-T'] = '1' + request_headers['X-INSTANA-S'] = '1' + request_headers['X-INSTANA-L'] = '1, correlationType=web; correlationId=1234567890abcdef' + request_headers['traceparent'] = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' + request_headers['tracestate'] = 'rojo=00f067aa0ba902b7,in=a3ce929d0e0e4736;8357ccd9da194656,congo=t61rcWkgMzE' + + response = self.http.request('GET', self.live_server_url + '/', headers=request_headers) + + assert response + self.assertEqual(200, response.status) + + spans = self.recorder.queued_spans() + self.assertEqual(1, len(spans)) + + django_span = spans[0] + + self.assertEqual(django_span.t, 'a3ce929d0e0e4736') + self.assertEqual(django_span.p, '00f067aa0ba902b7') + self.assertEqual(django_span.ia.t, 'a3ce929d0e0e4736') + self.assertEqual(django_span.ia.p, '8357ccd9da194656') + self.assertEqual(django_span.lt, '4bf92f3577b34da6a3ce929d0e0e4736') + self.assertEqual(django_span.tp, True) + self.assertEqual(django_span.crtp, 'web') + self.assertEqual(django_span.crid, '1234567890abcdef') + + assert ('X-INSTANA-T' in response.headers) + assert (int(response.headers['X-INSTANA-T'], 16)) + self.assertEqual(django_span.t, response.headers['X-INSTANA-T']) + + assert ('X-INSTANA-S' in response.headers) + assert (int(response.headers['X-INSTANA-S'], 16)) + self.assertEqual(django_span.s, response.headers['X-INSTANA-S']) + + assert ('X-INSTANA-L' in response.headers) + self.assertEqual('1', response.headers['X-INSTANA-L']) + + assert ('traceparent' in response.headers) + self.assertEqual('00-4bf92f3577b34da6a3ce929d0e0e4736-{}-01'.format(django_span.s), + response.headers['traceparent']) + + assert ('tracestate' in response.headers) + self.assertEqual( + 'in={};{},rojo=00f067aa0ba902b7,congo=t61rcWkgMzE'.format( + django_span.t, django_span.s), response.headers['tracestate']) + server_timing_value = "intid;desc=%s" % django_span.t + assert ('Server-Timing' in response.headers) + self.assertEqual(server_timing_value, response.headers['Server-Timing']) + + def test_with_incoming_traceparent_tracestate(self): + request_headers = dict() + request_headers['traceparent'] = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' + request_headers['tracestate'] = 'rojo=00f067aa0ba902b7,in=a3ce929d0e0e4736;8357ccd9da194656,congo=t61rcWkgMzE' + + response = self.http.request('GET', self.live_server_url + '/', headers=request_headers) + + assert response + self.assertEqual(200, response.status) + + spans = self.recorder.queued_spans() + self.assertEqual(1, len(spans)) + + django_span = spans[0] + + self.assertEqual(django_span.t, 'a3ce929d0e0e4736') # last 16 chars from traceparent trace_id + self.assertEqual(django_span.p, '00f067aa0ba902b7') + self.assertEqual(django_span.ia.t, 'a3ce929d0e0e4736') + self.assertEqual(django_span.ia.p, '8357ccd9da194656') + self.assertEqual(django_span.lt, '4bf92f3577b34da6a3ce929d0e0e4736') + self.assertEqual(django_span.tp, True) + + assert ('X-INSTANA-T' in response.headers) + assert (int(response.headers['X-INSTANA-T'], 16)) + self.assertEqual(django_span.t, response.headers['X-INSTANA-T']) + + assert ('X-INSTANA-S' in response.headers) + assert (int(response.headers['X-INSTANA-S'], 16)) + self.assertEqual(django_span.s, response.headers['X-INSTANA-S']) + + assert ('X-INSTANA-L' in response.headers) + self.assertEqual('1', response.headers['X-INSTANA-L']) + + assert ('traceparent' in response.headers) + self.assertEqual('00-4bf92f3577b34da6a3ce929d0e0e4736-{}-01'.format(django_span.s), + response.headers['traceparent']) + + assert ('tracestate' in response.headers) + self.assertEqual( + 'in=a3ce929d0e0e4736;{},rojo=00f067aa0ba902b7,congo=t61rcWkgMzE'.format( + django_span.s), response.headers['tracestate']) + + server_timing_value = "intid;desc=%s" % django_span.t + assert ('Server-Timing' in response.headers) + self.assertEqual(server_timing_value, response.headers['Server-Timing']) + + def test_with_incoming_traceparent_tracestate_disable_traceparent(self): + os.environ["INSTANA_DISABLE_W3C_TRACE_CORRELATION"] = "1" + request_headers = dict() + request_headers['traceparent'] = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' + request_headers['tracestate'] = 'rojo=00f067aa0ba902b7,in=a3ce929d0e0e4736;8357ccd9da194656,congo=t61rcWkgMzE' + + response = self.http.request('GET', self.live_server_url + '/', headers=request_headers) + + assert response + self.assertEqual(200, response.status) + + spans = self.recorder.queued_spans() + self.assertEqual(1, len(spans)) + + django_span = spans[0] + + self.assertEqual(django_span.t, 'a3ce929d0e0e4736') # last 16 chars from traceparent trace_id + self.assertEqual(django_span.p, '8357ccd9da194656') + + assert ('X-INSTANA-T' in response.headers) + assert (int(response.headers['X-INSTANA-T'], 16)) + self.assertEqual(django_span.t, response.headers['X-INSTANA-T']) + + assert ('X-INSTANA-S' in response.headers) + assert (int(response.headers['X-INSTANA-S'], 16)) + self.assertEqual(django_span.s, response.headers['X-INSTANA-S']) + + assert ('X-INSTANA-L' in response.headers) + self.assertEqual('1', response.headers['X-INSTANA-L']) + + assert ('traceparent' in response.headers) + self.assertEqual('00-4bf92f3577b34da6a3ce929d0e0e4736-{}-01'.format(django_span.s), + response.headers['traceparent']) + + assert ('tracestate' in response.headers) + self.assertEqual( + 'in=a3ce929d0e0e4736;{},rojo=00f067aa0ba902b7,congo=t61rcWkgMzE'.format( + django_span.s), response.headers['tracestate']) + server_timing_value = "intid;desc=%s" % django_span.t assert ('Server-Timing' in response.headers) self.assertEqual(server_timing_value, response.headers['Server-Timing']) diff --git a/tests/frameworks/test_fastapi.py b/tests/frameworks/test_fastapi.py index f290f89a..9765db7f 100644 --- a/tests/frameworks/test_fastapi.py +++ b/tests/frameworks/test_fastapi.py @@ -11,6 +11,7 @@ from ..helpers import testenv from ..helpers import get_first_span_by_filter + @pytest.fixture(scope="module") def server(): from tests.apps.fastapi_app import launch_fastapi @@ -18,7 +19,8 @@ def server(): proc.start() time.sleep(2) yield - proc.kill() # Kill server after tests + proc.kill() # Kill server after tests + def test_vanilla_get(server): result = requests.get(testenv["fastapi_server"] + '/') @@ -33,7 +35,7 @@ def test_vanilla_get(server): spans = tracer.recorder.queued_spans() # FastAPI instrumentation (like all instrumentation) _always_ traces unless told otherwise assert len(spans) == 1 - assert spans[0].n == 'sdk' + assert spans[0].n == 'asgi' def test_basic_get(server): @@ -48,19 +50,19 @@ def test_basic_get(server): span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'test' test_span = get_first_span_by_filter(spans, span_filter) - assert(test_span) + assert (test_span) span_filter = lambda span: span.n == "urllib3" urllib3_span = get_first_span_by_filter(spans, span_filter) - assert(urllib3_span) + assert (urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == "asgi" asgi_span = get_first_span_by_filter(spans, span_filter) - assert(asgi_span) + assert (asgi_span) - assert(test_span.t == urllib3_span.t == asgi_span.t) - assert(asgi_span.p == urllib3_span.s) - assert(urllib3_span.p == test_span.s) + assert (test_span.t == urllib3_span.t == asgi_span.t) + assert (asgi_span.p == urllib3_span.s) + assert (urllib3_span.p == test_span.s) assert "X-INSTANA-T" in result.headers assert result.headers["X-INSTANA-T"] == asgi_span.t @@ -71,14 +73,15 @@ def test_basic_get(server): assert "Server-Timing" in result.headers assert result.headers["Server-Timing"] == ("intid;desc=%s" % asgi_span.t) - assert(asgi_span.ec == None) - assert(asgi_span.data['sdk']['custom']['tags']['http.host'] == '127.0.0.1') - assert(asgi_span.data['sdk']['custom']['tags']['http.path'] == '/') - assert(asgi_span.data['sdk']['custom']['tags']['http.path_tpl'] == '/') - assert(asgi_span.data['sdk']['custom']['tags']['http.method'] == 'GET') - assert(asgi_span.data['sdk']['custom']['tags']['http.status_code'] == 200) - assert('http.error' not in asgi_span.data['sdk']['custom']['tags']) - assert('http.params' not in asgi_span.data['sdk']['custom']['tags']) + assert (asgi_span.ec == None) + assert (asgi_span.data['http']['host'] == '127.0.0.1') + assert (asgi_span.data['http']['path'] == '/') + assert (asgi_span.data['http']['path_tpl'] == '/') + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 200) + assert (asgi_span.data['http']['error'] is None) + assert (asgi_span.data['http']['params'] is None) + def test_400(server): result = None @@ -92,19 +95,19 @@ def test_400(server): span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'test' test_span = get_first_span_by_filter(spans, span_filter) - assert(test_span) + assert (test_span) span_filter = lambda span: span.n == "urllib3" urllib3_span = get_first_span_by_filter(spans, span_filter) - assert(urllib3_span) + assert (urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == 'asgi' asgi_span = get_first_span_by_filter(spans, span_filter) - assert(asgi_span) + assert (asgi_span) - assert(test_span.t == urllib3_span.t == asgi_span.t) - assert(asgi_span.p == urllib3_span.s) - assert(urllib3_span.p == test_span.s) + assert (test_span.t == urllib3_span.t == asgi_span.t) + assert (asgi_span.p == urllib3_span.s) + assert (urllib3_span.p == test_span.s) assert "X-INSTANA-T" in result.headers assert result.headers["X-INSTANA-T"] == asgi_span.t @@ -115,14 +118,14 @@ def test_400(server): assert "Server-Timing" in result.headers assert result.headers["Server-Timing"] == ("intid;desc=%s" % asgi_span.t) - assert(asgi_span.ec == None) - assert(asgi_span.data['sdk']['custom']['tags']['http.host'] == '127.0.0.1') - assert(asgi_span.data['sdk']['custom']['tags']['http.path'] == '/400') - assert(asgi_span.data['sdk']['custom']['tags']['http.path_tpl'] == '/400') - assert(asgi_span.data['sdk']['custom']['tags']['http.method'] == 'GET') - assert(asgi_span.data['sdk']['custom']['tags']['http.status_code'] == 400) - assert('http.error' not in asgi_span.data['sdk']['custom']['tags']) - assert('http.params' not in asgi_span.data['sdk']['custom']['tags']) + assert (asgi_span.ec == None) + assert (asgi_span.data['http']['host'] == '127.0.0.1') + assert (asgi_span.data['http']['path'] == '/400') + assert (asgi_span.data['http']['path_tpl'] == '/400') + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 400) + assert (asgi_span.data['http']['error'] is None) + assert (asgi_span.data['http']['params'] is None) def test_500(server): result = None @@ -136,19 +139,19 @@ def test_500(server): span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'test' test_span = get_first_span_by_filter(spans, span_filter) - assert(test_span) + assert (test_span) span_filter = lambda span: span.n == "urllib3" urllib3_span = get_first_span_by_filter(spans, span_filter) - assert(urllib3_span) + assert (urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == 'asgi' asgi_span = get_first_span_by_filter(spans, span_filter) - assert(asgi_span) + assert (asgi_span) - assert(test_span.t == urllib3_span.t == asgi_span.t) - assert(asgi_span.p == urllib3_span.s) - assert(urllib3_span.p == test_span.s) + assert (test_span.t == urllib3_span.t == asgi_span.t) + assert (asgi_span.p == urllib3_span.s) + assert (urllib3_span.p == test_span.s) assert "X-INSTANA-T" in result.headers assert result.headers["X-INSTANA-T"] == asgi_span.t @@ -159,14 +162,14 @@ def test_500(server): assert "Server-Timing" in result.headers assert result.headers["Server-Timing"] == ("intid;desc=%s" % asgi_span.t) - assert(asgi_span.ec == 1) - assert(asgi_span.data['sdk']['custom']['tags']['http.host'] == '127.0.0.1') - assert(asgi_span.data['sdk']['custom']['tags']['http.path'] == '/500') - assert(asgi_span.data['sdk']['custom']['tags']['http.path_tpl'] == '/500') - assert(asgi_span.data['sdk']['custom']['tags']['http.method'] == 'GET') - assert(asgi_span.data['sdk']['custom']['tags']['http.status_code'] == 500) - assert(asgi_span.data['sdk']['custom']['tags']['http.error'] == '500 response') - assert('http.params' not in asgi_span.data['sdk']['custom']['tags']) + assert (asgi_span.ec == 1) + assert (asgi_span.data['http']['host'] == '127.0.0.1') + assert (asgi_span.data['http']['path'] == '/500') + assert (asgi_span.data['http']['path_tpl'] == '/500') + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 500) + assert (asgi_span.data['http']['error'] == '500 response') + assert (asgi_span.data['http']['params'] is None) def test_path_templates(server): result = None @@ -180,19 +183,19 @@ def test_path_templates(server): span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'test' test_span = get_first_span_by_filter(spans, span_filter) - assert(test_span) + assert (test_span) span_filter = lambda span: span.n == "urllib3" urllib3_span = get_first_span_by_filter(spans, span_filter) - assert(urllib3_span) + assert (urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == 'asgi' asgi_span = get_first_span_by_filter(spans, span_filter) - assert(asgi_span) + assert (asgi_span) - assert(test_span.t == urllib3_span.t == asgi_span.t) - assert(asgi_span.p == urllib3_span.s) - assert(urllib3_span.p == test_span.s) + assert (test_span.t == urllib3_span.t == asgi_span.t) + assert (asgi_span.p == urllib3_span.s) + assert (urllib3_span.p == test_span.s) assert "X-INSTANA-T" in result.headers assert result.headers["X-INSTANA-T"] == asgi_span.t @@ -203,14 +206,15 @@ def test_path_templates(server): assert "Server-Timing" in result.headers assert result.headers["Server-Timing"] == ("intid;desc=%s" % asgi_span.t) - assert(asgi_span.ec == None) - assert(asgi_span.data['sdk']['custom']['tags']['http.host'] == '127.0.0.1') - assert(asgi_span.data['sdk']['custom']['tags']['http.path'] == '/users/1') - assert(asgi_span.data['sdk']['custom']['tags']['http.path_tpl'] == '/users/{user_id}') - assert(asgi_span.data['sdk']['custom']['tags']['http.method'] == 'GET') - assert(asgi_span.data['sdk']['custom']['tags']['http.status_code'] == 200) - assert('http.error' not in asgi_span.data['sdk']['custom']['tags']) - assert('http.params' not in asgi_span.data['sdk']['custom']['tags']) + assert (asgi_span.ec == None) + assert (asgi_span.data['http']['host'] == '127.0.0.1') + assert (asgi_span.data['http']['path'] == '/users/1') + assert (asgi_span.data['http']['path_tpl'] == '/users/{user_id}') + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 200) + assert (asgi_span.data['http']['error'] is None) + assert (asgi_span.data['http']['params'] is None) + def test_secret_scrubbing(server): result = None @@ -224,19 +228,19 @@ def test_secret_scrubbing(server): span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'test' test_span = get_first_span_by_filter(spans, span_filter) - assert(test_span) + assert (test_span) span_filter = lambda span: span.n == "urllib3" urllib3_span = get_first_span_by_filter(spans, span_filter) - assert(urllib3_span) + assert (urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == 'asgi' asgi_span = get_first_span_by_filter(spans, span_filter) - assert(asgi_span) + assert (asgi_span) - assert(test_span.t == urllib3_span.t == asgi_span.t) - assert(asgi_span.p == urllib3_span.s) - assert(urllib3_span.p == test_span.s) + assert (test_span.t == urllib3_span.t == asgi_span.t) + assert (asgi_span.p == urllib3_span.s) + assert (urllib3_span.p == test_span.s) assert "X-INSTANA-T" in result.headers assert result.headers["X-INSTANA-T"] == asgi_span.t @@ -247,15 +251,14 @@ def test_secret_scrubbing(server): assert "Server-Timing" in result.headers assert result.headers["Server-Timing"] == ("intid;desc=%s" % asgi_span.t) - assert(asgi_span.ec == None) - assert(asgi_span.data['sdk']['custom']['tags']['http.host'] == '127.0.0.1') - assert(asgi_span.data['sdk']['custom']['tags']['http.path'] == '/') - assert(asgi_span.data['sdk']['custom']['tags']['http.path_tpl'] == '/') - assert(asgi_span.data['sdk']['custom']['tags']['http.params'] == 'secret=') - assert(asgi_span.data['sdk']['custom']['tags']['http.method'] == 'GET') - assert(asgi_span.data['sdk']['custom']['tags']['http.status_code'] == 200) - assert('http.error' not in asgi_span.data['sdk']['custom']['tags']) - + assert (asgi_span.ec == None) + assert (asgi_span.data['http']['host'] == '127.0.0.1') + assert (asgi_span.data['http']['path'] == '/') + assert (asgi_span.data['http']['path_tpl'] == '/') + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 200) + assert (asgi_span.data['http']['error'] is None) + assert (asgi_span.data['http']['params'] == 'secret=') def test_synthetic_request(server): request_headers = { @@ -271,19 +274,19 @@ def test_synthetic_request(server): span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'test' test_span = get_first_span_by_filter(spans, span_filter) - assert(test_span) + assert (test_span) span_filter = lambda span: span.n == "urllib3" urllib3_span = get_first_span_by_filter(spans, span_filter) - assert(urllib3_span) + assert (urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == 'asgi' asgi_span = get_first_span_by_filter(spans, span_filter) - assert(asgi_span) + assert (asgi_span) - assert(test_span.t == urllib3_span.t == asgi_span.t) - assert(asgi_span.p == urllib3_span.s) - assert(urllib3_span.p == test_span.s) + assert (test_span.t == urllib3_span.t == asgi_span.t) + assert (asgi_span.p == urllib3_span.s) + assert (urllib3_span.p == test_span.s) assert "X-INSTANA-T" in result.headers assert result.headers["X-INSTANA-T"] == asgi_span.t @@ -294,18 +297,19 @@ def test_synthetic_request(server): assert "Server-Timing" in result.headers assert result.headers["Server-Timing"] == ("intid;desc=%s" % asgi_span.t) - assert(asgi_span.ec == None) - assert(asgi_span.data['sdk']['custom']['tags']['http.host'] == '127.0.0.1') - assert(asgi_span.data['sdk']['custom']['tags']['http.path'] == '/') - assert(asgi_span.data['sdk']['custom']['tags']['http.path_tpl'] == '/') - assert(asgi_span.data['sdk']['custom']['tags']['http.method'] == 'GET') - assert(asgi_span.data['sdk']['custom']['tags']['http.status_code'] == 200) - assert('http.error' not in asgi_span.data['sdk']['custom']['tags']) - assert('http.params' not in asgi_span.data['sdk']['custom']['tags']) + assert (asgi_span.ec == None) + assert (asgi_span.data['http']['host'] == '127.0.0.1') + assert (asgi_span.data['http']['path'] == '/') + assert (asgi_span.data['http']['path_tpl'] == '/') + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 200) + assert (asgi_span.data['http']['error'] is None) + assert (asgi_span.data['http']['params'] is None) + + assert (asgi_span.sy) + assert (urllib3_span.sy is None) + assert (test_span.sy is None) - assert(asgi_span.sy) - assert(urllib3_span.sy is None) - assert(test_span.sy is None) def test_custom_header_capture(server): from instana.singletons import agent @@ -326,19 +330,19 @@ def test_custom_header_capture(server): span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'test' test_span = get_first_span_by_filter(spans, span_filter) - assert(test_span) + assert (test_span) span_filter = lambda span: span.n == "urllib3" urllib3_span = get_first_span_by_filter(spans, span_filter) - assert(urllib3_span) + assert (urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == 'asgi' asgi_span = get_first_span_by_filter(spans, span_filter) - assert(asgi_span) + assert (asgi_span) - assert(test_span.t == urllib3_span.t == asgi_span.t) - assert(asgi_span.p == urllib3_span.s) - assert(urllib3_span.p == test_span.s) + assert (test_span.t == urllib3_span.t == asgi_span.t) + assert (asgi_span.p == urllib3_span.s) + assert (urllib3_span.p == test_span.s) assert "X-INSTANA-T" in result.headers assert result.headers["X-INSTANA-T"] == asgi_span.t @@ -349,16 +353,16 @@ def test_custom_header_capture(server): assert "Server-Timing" in result.headers assert result.headers["Server-Timing"] == ("intid;desc=%s" % asgi_span.t) - assert(asgi_span.ec == None) - assert(asgi_span.data['sdk']['custom']['tags']['http.host'] == '127.0.0.1') - assert(asgi_span.data['sdk']['custom']['tags']['http.path'] == '/') - assert(asgi_span.data['sdk']['custom']['tags']['http.path_tpl'] == '/') - assert(asgi_span.data['sdk']['custom']['tags']['http.method'] == 'GET') - assert(asgi_span.data['sdk']['custom']['tags']['http.status_code'] == 200) - assert('http.error' not in asgi_span.data['sdk']['custom']['tags']) - assert('http.params' not in asgi_span.data['sdk']['custom']['tags']) - - assert("http.header.X-Capture-This" in asgi_span.data["sdk"]["custom"]['tags']) - assert("this" == asgi_span.data["sdk"]["custom"]['tags']["http.header.X-Capture-This"]) - assert("http.header.X-Capture-That" in asgi_span.data["sdk"]["custom"]['tags']) - assert("that" == asgi_span.data["sdk"]["custom"]['tags']["http.header.X-Capture-That"]) + assert (asgi_span.ec == None) + assert (asgi_span.data['http']['host'] == '127.0.0.1') + assert (asgi_span.data['http']['path'] == '/') + assert (asgi_span.data['http']['path_tpl'] == '/') + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 200) + assert (asgi_span.data['http']['error'] is None) + assert (asgi_span.data['http']['params'] is None) + + assert ("X-Capture-This" in asgi_span.data["http"]["header"]) + assert ("this" == asgi_span.data["http"]["header"]["X-Capture-This"]) + assert ("X-Capture-That" in asgi_span.data["http"]["header"]) + assert ("that" == asgi_span.data["http"]["header"]["X-Capture-That"]) diff --git a/tests/frameworks/test_sanic.py b/tests/frameworks/test_sanic.py index d0dd0091..00f5c85a 100644 --- a/tests/frameworks/test_sanic.py +++ b/tests/frameworks/test_sanic.py @@ -39,7 +39,7 @@ def test_vanilla_get(self): self.assertIn("Server-Timing", result.headers) spans = tracer.recorder.queued_spans() self.assertEqual(len(spans), 1) - self.assertEqual(spans[0].n, 'sdk') + self.assertEqual(spans[0].n, 'asgi') def test_basic_get(self): result = None @@ -59,7 +59,7 @@ def test_basic_get(self): urllib3_span = get_first_span_by_filter(spans, span_filter) self.assertIsNotNone(urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == 'asgi' asgi_span = get_first_span_by_filter(spans, span_filter) self.assertIsNotNone(asgi_span) @@ -76,13 +76,13 @@ def test_basic_get(self): self.assertEqual(result.headers["Server-Timing"], ("intid;desc=%s" % asgi_span.t)) self.assertIsNone(asgi_span.ec) - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.host'], '127.0.0.1:1337') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.path'], '/') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.path_tpl'], '/') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.method'], 'GET') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.status_code'], 200) - self.assertNotIn('http.error', asgi_span.data['sdk']['custom']['tags']) - self.assertNotIn('http.params', asgi_span.data['sdk']['custom']['tags']) + assert (asgi_span.data['http']['host'] == '127.0.0.1:1337') + assert (asgi_span.data['http']['path'] == '/') + assert (asgi_span.data['http']['path_tpl'] == '/') + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 200) + assert (asgi_span.data['http']['error'] is None) + assert (asgi_span.data['http']['params'] is None) def test_404(self): result = None @@ -102,7 +102,7 @@ def test_404(self): urllib3_span = get_first_span_by_filter(spans, span_filter) self.assertIsNotNone(urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == 'asgi' asgi_span = get_first_span_by_filter(spans, span_filter) self.assertIsNotNone(asgi_span) @@ -119,13 +119,13 @@ def test_404(self): self.assertEqual(result.headers["Server-Timing"], ("intid;desc=%s" % asgi_span.t)) self.assertIsNone(asgi_span.ec) - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.host'], '127.0.0.1:1337') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.path'], '/foo/not_an_int') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.method'], 'GET') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.status_code'], 404) - self.assertNotIn('http.error', asgi_span.data['sdk']['custom']['tags']) - self.assertNotIn('http.params', asgi_span.data['sdk']['custom']['tags']) - self.assertNotIn('http.path_tpl', asgi_span.data['sdk']['custom']['tags']) + assert (asgi_span.data['http']['host'] == '127.0.0.1:1337') + assert (asgi_span.data['http']['path'] == '/foo/not_an_int') + assert (asgi_span.data['http']['path_tpl'] is None) + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 404) + assert (asgi_span.data['http']['error'] is None) + assert (asgi_span.data['http']['params'] is None) def test_500(self): result = None @@ -145,7 +145,7 @@ def test_500(self): urllib3_span = get_first_span_by_filter(spans, span_filter) self.assertIsNotNone(urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == 'asgi' asgi_span = get_first_span_by_filter(spans, span_filter) self.assertIsNotNone(asgi_span) @@ -162,14 +162,13 @@ def test_500(self): self.assertEqual(result.headers["Server-Timing"], ("intid;desc=%s" % asgi_span.t)) self.assertEqual(asgi_span.ec, 1) - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.host'], '127.0.0.1:1337') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.path'], '/test_request_args') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.path_tpl'], '/test_request_args') - - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.method'], 'GET') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.status_code'], 500) - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.error'], 'Something went wrong.') - self.assertNotIn('http.params', asgi_span.data['sdk']['custom']['tags']) + assert (asgi_span.data['http']['host'] == '127.0.0.1:1337') + assert (asgi_span.data['http']['path'] == '/test_request_args') + assert (asgi_span.data['http']['path_tpl'] == '/test_request_args') + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 500) + assert (asgi_span.data['http']['error'] == 'Something went wrong.') + assert (asgi_span.data['http']['params'] is None) def test_path_templates(self): result = None @@ -189,7 +188,7 @@ def test_path_templates(self): urllib3_span = get_first_span_by_filter(spans, span_filter) self.assertIsNotNone(urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == 'asgi' asgi_span = get_first_span_by_filter(spans, span_filter) self.assertIsNotNone(asgi_span) @@ -206,13 +205,14 @@ def test_path_templates(self): self.assertEqual(result.headers["Server-Timing"], ("intid;desc=%s" % asgi_span.t)) self.assertIsNone(asgi_span.ec) - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.host'], '127.0.0.1:1337') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.path'], '/foo/1') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.path_tpl'], '/foo/') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.method'], 'GET') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.status_code'], 200) - self.assertNotIn('http.error', asgi_span.data['sdk']['custom']['tags']) - self.assertNotIn('http.params', asgi_span.data['sdk']['custom']['tags']) + assert (asgi_span.data['http']['host'] == '127.0.0.1:1337') + assert (asgi_span.data['http']['path'] == '/foo/1') + assert (asgi_span.data['http']['path_tpl'] == '/foo/') + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 200) + assert (asgi_span.data['http']['error'] is None) + assert (asgi_span.data['http']['params'] is None) + def test_secret_scrubbing(self): result = None @@ -232,7 +232,7 @@ def test_secret_scrubbing(self): urllib3_span = get_first_span_by_filter(spans, span_filter) self.assertIsNotNone(urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == 'asgi' asgi_span = get_first_span_by_filter(spans, span_filter) self.assertIsNotNone(asgi_span) @@ -249,13 +249,13 @@ def test_secret_scrubbing(self): self.assertEqual(result.headers["Server-Timing"], ("intid;desc=%s" % asgi_span.t)) self.assertIsNone(asgi_span.ec) - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.host'], '127.0.0.1:1337') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.path'], '/') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.path_tpl'], '/') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.method'], 'GET') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.status_code'], 200) - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.params'], 'secret=') - self.assertNotIn('http.error', asgi_span.data['sdk']['custom']['tags']) + assert (asgi_span.data['http']['host'] == '127.0.0.1:1337') + assert (asgi_span.data['http']['path'] == '/') + assert (asgi_span.data['http']['path_tpl'] == '/') + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 200) + assert (asgi_span.data['http']['error'] is None) + assert (asgi_span.data['http']['params'] == 'secret=') def test_synthetic_request(self): request_headers = { @@ -277,7 +277,7 @@ def test_synthetic_request(self): urllib3_span = get_first_span_by_filter(spans, span_filter) self.assertIsNotNone(urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == 'asgi' asgi_span = get_first_span_by_filter(spans, span_filter) self.assertIsNotNone(asgi_span) @@ -294,13 +294,13 @@ def test_synthetic_request(self): self.assertEqual(result.headers["Server-Timing"], ("intid;desc=%s" % asgi_span.t)) self.assertIsNone(asgi_span.ec) - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.host'], '127.0.0.1:1337') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.path'], '/') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.path_tpl'], '/') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.method'], 'GET') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.status_code'], 200) - self.assertNotIn('http.error', asgi_span.data['sdk']['custom']['tags']) - self.assertNotIn('http.params', asgi_span.data['sdk']['custom']['tags']) + assert (asgi_span.data['http']['host'] == '127.0.0.1:1337') + assert (asgi_span.data['http']['path'] == '/') + assert (asgi_span.data['http']['path_tpl'] == '/') + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 200) + assert (asgi_span.data['http']['error'] is None) + assert (asgi_span.data['http']['params'] is None) self.assertIsNotNone(asgi_span.sy) self.assertIsNone(urllib3_span.sy) @@ -327,7 +327,7 @@ def test_custom_header_capture(self): urllib3_span = get_first_span_by_filter(spans, span_filter) self.assertIsNotNone(urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == 'asgi' asgi_span = get_first_span_by_filter(spans, span_filter) self.assertIsNotNone(asgi_span) @@ -344,15 +344,15 @@ def test_custom_header_capture(self): self.assertEqual(result.headers["Server-Timing"], ("intid;desc=%s" % asgi_span.t)) self.assertIsNone(asgi_span.ec) - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.host'], '127.0.0.1:1337') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.path'], '/') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.path_tpl'], '/') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.method'], 'GET') - self.assertEqual(asgi_span.data['sdk']['custom']['tags']['http.status_code'], 200) - self.assertNotIn('http.error', asgi_span.data['sdk']['custom']['tags']) - self.assertNotIn('http.params', asgi_span.data['sdk']['custom']['tags']) - - self.assertIn("http.header.X-Capture-This", asgi_span.data["sdk"]["custom"]['tags']) - self.assertEqual("this", asgi_span.data["sdk"]["custom"]['tags']["http.header.X-Capture-This"]) - self.assertIn("http.header.X-Capture-That", asgi_span.data["sdk"]["custom"]['tags']) - self.assertEqual("that", asgi_span.data["sdk"]["custom"]['tags']["http.header.X-Capture-That"]) + assert (asgi_span.data['http']['host'] == '127.0.0.1:1337') + assert (asgi_span.data['http']['path'] == '/') + assert (asgi_span.data['http']['path_tpl'] == '/') + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 200) + assert (asgi_span.data['http']['error'] is None) + assert (asgi_span.data['http']['params'] is None) + + assert ("X-Capture-This" in asgi_span.data["http"]["header"]) + assert ("this" == asgi_span.data["http"]["header"]["X-Capture-This"]) + assert ("X-Capture-That" in asgi_span.data["http"]["header"]) + assert ("that" == asgi_span.data["http"]["header"]["X-Capture-That"]) diff --git a/tests/frameworks/test_starlette.py b/tests/frameworks/test_starlette.py index 33cc7b00..72736e84 100644 --- a/tests/frameworks/test_starlette.py +++ b/tests/frameworks/test_starlette.py @@ -26,7 +26,7 @@ def test_vanilla_get(server): spans = tracer.recorder.queued_spans() # Starlette instrumentation (like all instrumentation) _always_ traces unless told otherwise assert len(spans) == 1 - assert spans[0].n == 'sdk' + assert spans[0].n == 'asgi' assert "X-INSTANA-T" in result.headers assert "X-INSTANA-S" in result.headers @@ -52,7 +52,7 @@ def test_basic_get(server): urllib3_span = get_first_span_by_filter(spans, span_filter) assert(urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == 'asgi' asgi_span = get_first_span_by_filter(spans, span_filter) assert(asgi_span) @@ -70,13 +70,13 @@ def test_basic_get(server): assert result.headers["Server-Timing"] == ("intid;desc=%s" % asgi_span.t) assert(asgi_span.ec == None) - assert(asgi_span.data['sdk']['custom']['tags']['http.host'] == '127.0.0.1') - assert(asgi_span.data['sdk']['custom']['tags']['http.path'] == '/') - assert(asgi_span.data['sdk']['custom']['tags']['http.path_tpl'] == '/') - assert(asgi_span.data['sdk']['custom']['tags']['http.method'] == 'GET') - assert(asgi_span.data['sdk']['custom']['tags']['http.status_code'] == 200) - assert('http.error' not in asgi_span.data['sdk']['custom']['tags']) - assert('http.params' not in asgi_span.data['sdk']['custom']['tags']) + assert (asgi_span.data['http']['host'] == '127.0.0.1') + assert (asgi_span.data['http']['path'] == '/') + assert (asgi_span.data['http']['path_tpl'] == '/') + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 200) + assert (asgi_span.data['http']['error'] is None) + assert (asgi_span.data['http']['params'] is None) def test_path_templates(server): result = None @@ -96,7 +96,7 @@ def test_path_templates(server): urllib3_span = get_first_span_by_filter(spans, span_filter) assert(urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == 'asgi' asgi_span = get_first_span_by_filter(spans, span_filter) assert(asgi_span) @@ -114,13 +114,13 @@ def test_path_templates(server): assert result.headers["Server-Timing"] == ("intid;desc=%s" % asgi_span.t) assert(asgi_span.ec == None) - assert(asgi_span.data['sdk']['custom']['tags']['http.host'] == '127.0.0.1') - assert(asgi_span.data['sdk']['custom']['tags']['http.path'] == '/users/1') - assert(asgi_span.data['sdk']['custom']['tags']['http.path_tpl'] == '/users/{user_id}') - assert(asgi_span.data['sdk']['custom']['tags']['http.method'] == 'GET') - assert(asgi_span.data['sdk']['custom']['tags']['http.status_code'] == 200) - assert('http.error' not in asgi_span.data['sdk']['custom']['tags']) - assert('http.params' not in asgi_span.data['sdk']['custom']['tags']) + assert (asgi_span.data['http']['host'] == '127.0.0.1') + assert (asgi_span.data['http']['path'] == '/users/1') + assert (asgi_span.data['http']['path_tpl'] == '/users/{user_id}') + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 200) + assert (asgi_span.data['http']['error'] is None) + assert (asgi_span.data['http']['params'] is None) def test_secret_scrubbing(server): result = None @@ -140,7 +140,7 @@ def test_secret_scrubbing(server): urllib3_span = get_first_span_by_filter(spans, span_filter) assert(urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == 'asgi' asgi_span = get_first_span_by_filter(spans, span_filter) assert(asgi_span) @@ -158,13 +158,13 @@ def test_secret_scrubbing(server): assert result.headers["Server-Timing"] == ("intid;desc=%s" % asgi_span.t) assert(asgi_span.ec == None) - assert(asgi_span.data['sdk']['custom']['tags']['http.host'] == '127.0.0.1') - assert(asgi_span.data['sdk']['custom']['tags']['http.path'] == '/') - assert(asgi_span.data['sdk']['custom']['tags']['http.path_tpl'] == '/') - assert(asgi_span.data['sdk']['custom']['tags']['http.method'] == 'GET') - assert(asgi_span.data['sdk']['custom']['tags']['http.params'] == 'secret=') - assert(asgi_span.data['sdk']['custom']['tags']['http.status_code'] == 200) - assert('http.error' not in asgi_span.data['sdk']['custom']['tags']) + assert (asgi_span.data['http']['host'] == '127.0.0.1') + assert (asgi_span.data['http']['path'] == '/') + assert (asgi_span.data['http']['path_tpl'] == '/') + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 200) + assert (asgi_span.data['http']['error'] is None) + assert (asgi_span.data['http']['params'] == 'secret=') def test_synthetic_request(server): request_headers = { @@ -186,7 +186,7 @@ def test_synthetic_request(server): urllib3_span = get_first_span_by_filter(spans, span_filter) assert(urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == 'asgi' asgi_span = get_first_span_by_filter(spans, span_filter) assert(asgi_span) @@ -204,13 +204,13 @@ def test_synthetic_request(server): assert result.headers["Server-Timing"] == ("intid;desc=%s" % asgi_span.t) assert(asgi_span.ec == None) - assert(asgi_span.data['sdk']['custom']['tags']['http.host'] == '127.0.0.1') - assert(asgi_span.data['sdk']['custom']['tags']['http.path'] == '/') - assert(asgi_span.data['sdk']['custom']['tags']['http.path_tpl'] == '/') - assert(asgi_span.data['sdk']['custom']['tags']['http.method'] == 'GET') - assert(asgi_span.data['sdk']['custom']['tags']['http.status_code'] == 200) - assert('http.error' not in asgi_span.data['sdk']['custom']['tags']) - assert('http.params' not in asgi_span.data['sdk']['custom']['tags']) + assert (asgi_span.data['http']['host'] == '127.0.0.1') + assert (asgi_span.data['http']['path'] == '/') + assert (asgi_span.data['http']['path_tpl'] == '/') + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 200) + assert (asgi_span.data['http']['error'] is None) + assert (asgi_span.data['http']['params'] is None) assert(asgi_span.sy) assert(urllib3_span.sy is None) @@ -241,7 +241,7 @@ def test_custom_header_capture(server): urllib3_span = get_first_span_by_filter(spans, span_filter) assert(urllib3_span) - span_filter = lambda span: span.n == "sdk" and span.data['sdk']['name'] == 'asgi' + span_filter = lambda span: span.n == 'asgi' asgi_span = get_first_span_by_filter(spans, span_filter) assert(asgi_span) @@ -259,15 +259,15 @@ def test_custom_header_capture(server): assert result.headers["Server-Timing"] == ("intid;desc=%s" % asgi_span.t) assert(asgi_span.ec == None) - assert(asgi_span.data['sdk']['custom']['tags']['http.host'] == '127.0.0.1') - assert(asgi_span.data['sdk']['custom']['tags']['http.path'] == '/') - assert(asgi_span.data['sdk']['custom']['tags']['http.path_tpl'] == '/') - assert(asgi_span.data['sdk']['custom']['tags']['http.method'] == 'GET') - assert(asgi_span.data['sdk']['custom']['tags']['http.status_code'] == 200) - assert('http.error' not in asgi_span.data['sdk']['custom']['tags']) - assert('http.params' not in asgi_span.data['sdk']['custom']['tags']) - - assert("http.header.X-Capture-This" in asgi_span.data["sdk"]["custom"]['tags']) - assert("this" == asgi_span.data["sdk"]["custom"]['tags']["http.header.X-Capture-This"]) - assert("http.header.X-Capture-That" in asgi_span.data["sdk"]["custom"]['tags']) - assert("that" == asgi_span.data["sdk"]["custom"]['tags']["http.header.X-Capture-That"]) + assert (asgi_span.data['http']['host'] == '127.0.0.1') + assert (asgi_span.data['http']['path'] == '/') + assert (asgi_span.data['http']['path_tpl'] == '/') + assert (asgi_span.data['http']['method'] == 'GET') + assert (asgi_span.data['http']['status'] == 200) + assert (asgi_span.data['http']['error'] is None) + assert (asgi_span.data['http']['params'] is None) + + assert ("X-Capture-This" in asgi_span.data["http"]["header"]) + assert ("this" == asgi_span.data["http"]["header"]["X-Capture-This"]) + assert ("X-Capture-That" in asgi_span.data["http"]["header"]) + assert ("that" == asgi_span.data["http"]["header"]["X-Capture-That"]) diff --git a/tests/opentracing/test_ot_propagators.py b/tests/opentracing/test_ot_propagators.py index f6949a7c..dc92839a 100644 --- a/tests/opentracing/test_ot_propagators.py +++ b/tests/opentracing/test_ot_propagators.py @@ -113,14 +113,16 @@ def test_http_extract_synthetic_only(): assert ctx.synthetic -def test_http_no_context_extract(): +def test_http_default_context_extract(): ot.tracer = InstanaTracer() carrier = {} ctx = ot.tracer.extract(ot.Format.HTTP_HEADERS, carrier) - assert ctx is None - + assert isinstance(ctx, SpanContext) + assert ctx.trace_id is None + assert ctx.span_id is None + assert ctx.synthetic is False def test_http_128bit_headers(): ot.tracer = InstanaTracer() @@ -195,13 +197,16 @@ def test_text_mixed_case_extract(): assert ctx.span_id == '0000000000000001' -def test_text_no_context_extract(): +def test_text_default_context_extract(): ot.tracer = InstanaTracer() carrier = {} ctx = ot.tracer.extract(ot.Format.TEXT_MAP, carrier) - assert ctx is None + assert isinstance(ctx, SpanContext) + assert ctx.trace_id is None + assert ctx.span_id is None + assert ctx.synthetic is False def test_text_128bit_headers(): @@ -279,13 +284,16 @@ def test_binary_mixed_case_extract(): assert ctx.synthetic -def test_binary_no_context_extract(): +def test_binary_default_context_extract(): ot.tracer = InstanaTracer() carrier = {} ctx = ot.tracer.extract(ot.Format.BINARY, carrier) - assert ctx is None + assert isinstance(ctx, SpanContext) + assert ctx.trace_id is None + assert ctx.span_id is None + assert ctx.synthetic is False def test_binary_128bit_headers(): diff --git a/tests/propagators/__init__.py b/tests/propagators/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/propagators/test_binary_propagator.py b/tests/propagators/test_binary_propagator.py new file mode 100644 index 00000000..d96b97a3 --- /dev/null +++ b/tests/propagators/test_binary_propagator.py @@ -0,0 +1,73 @@ +# (c) Copyright IBM Corp. 2021 +# (c) Copyright Instana Inc. 2021 + +from instana.propagators.binary_propagator import BinaryPropagator +from instana.span_context import SpanContext +import unittest + + +class TestBinaryPropagator(unittest.TestCase): + def setUp(self): + self.bp = BinaryPropagator() + + def test_inject_carrier_dict(self): + carrier = {} + ctx = SpanContext(span_id="1234567890abcdef", trace_id="1234d0e0e4736234", + level=1, baggage={}, sampled=True, + synthetic=False) + carrier = self.bp.inject(ctx, carrier) + self.assertEqual(carrier[b'x-instana-t'], b"1234d0e0e4736234") + + def test_inject_carrier_dict_w3c_True(self): + carrier = {} + ctx = SpanContext(span_id="1234567890abcdef", trace_id="1234d0e0e4736234", + level=1, baggage={}, sampled=True, + synthetic=False) + carrier = self.bp.inject(ctx, carrier, disable_w3c_trace_context=False) + self.assertEqual(carrier[b'x-instana-t'], b"1234d0e0e4736234") + self.assertEqual(carrier[b'traceparent'], b'00-00000000000000001234d0e0e4736234-1234567890abcdef-01') + self.assertEqual(carrier[b'tracestate'], b'in=1234d0e0e4736234;1234567890abcdef') + + def test_inject_carrier_list(self): + carrier = [] + ctx = SpanContext(span_id="1234567890abcdef", trace_id="1234d0e0e4736234", + level=1, baggage={}, sampled=True, + synthetic=False) + carrier = self.bp.inject(ctx, carrier) + self.assertEqual(carrier[0], (b'x-instana-t', b'1234d0e0e4736234')) + + def test_inject_carrier_list_w3c_True(self): + carrier = [] + ctx = SpanContext(span_id="1234567890abcdef", trace_id="1234d0e0e4736234", + level=1, baggage={}, sampled=True, + synthetic=False) + carrier = self.bp.inject(ctx, carrier, disable_w3c_trace_context=False) + self.assertEqual(carrier[2], (b'x-instana-t', b'1234d0e0e4736234')) + self.assertEqual(carrier[0], (b'traceparent', b'00-00000000000000001234d0e0e4736234-1234567890abcdef-01')) + self.assertEqual(carrier[1], (b'tracestate', b'in=1234d0e0e4736234;1234567890abcdef')) + + def test_inject_carrier_tupple(self): + carrier = () + ctx = SpanContext(span_id="1234567890abcdef", trace_id="1234d0e0e4736234", + level=1, baggage={}, sampled=True, + synthetic=False) + carrier = self.bp.inject(ctx, carrier) + self.assertEqual(carrier[0], (b'x-instana-t', b'1234d0e0e4736234')) + + def test_inject_carrier_tupple_w3c_True(self): + carrier = () + ctx = SpanContext(span_id="1234567890abcdef", trace_id="1234d0e0e4736234", + level=1, baggage={}, sampled=True, + synthetic=False) + carrier = self.bp.inject(ctx, carrier, disable_w3c_trace_context=False) + self.assertEqual(carrier[2], (b'x-instana-t', b'1234d0e0e4736234')) + self.assertEqual(carrier[0], (b'traceparent', b'00-00000000000000001234d0e0e4736234-1234567890abcdef-01')) + self.assertEqual(carrier[1], (b'tracestate', b'in=1234d0e0e4736234;1234567890abcdef')) + + def test_inject_carrier_set_exception(self): + carrier = set() + ctx = SpanContext(span_id="1234567890abcdef", trace_id="1234d0e0e4736234", + level=1, baggage={}, sampled=True, + synthetic=False) + carrier = self.bp.inject(ctx, carrier) + self.assertIsNone(carrier) \ No newline at end of file diff --git a/tests/propagators/test_http_propagator.py b/tests/propagators/test_http_propagator.py new file mode 100644 index 00000000..159c971a --- /dev/null +++ b/tests/propagators/test_http_propagator.py @@ -0,0 +1,166 @@ +# (c) Copyright IBM Corp. 2021 +# (c) Copyright Instana Inc. 2021 + +from instana.propagators.http_propagator import HTTPPropagator +from instana.w3c_trace_context.traceparent import Traceparent +from instana.span_context import SpanContext +from mock import patch +import unittest + + +class TestHTTPPropagatorTC(unittest.TestCase): + def setUp(self): + self.hptc = HTTPPropagator() + + @patch.object(Traceparent, "get_traceparent_fields") + @patch.object(Traceparent, "validate") + def test_extract_carrier_dict(self, mock_validate, mock_get_traceparent_fields): + carrier = { + 'traceparent': '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01', + 'tracestate': 'congo=t61rcWkgMzE', + 'X-INSTANA-T': '1234d0e0e4736234', + 'X-INSTANA-S': '1234567890abcdef', + 'X-INSTANA-L': '1, correlationType=web; correlationId=1234567890abcdef' + } + mock_validate.return_value = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' + mock_get_traceparent_fields.return_value = ["00", "4bf92f3577b34da6a3ce929d0e0e4736", "00f067aa0ba902b7", "01"] + ctx = self.hptc.extract(carrier) + self.assertEqual(ctx.correlation_id, '1234567890abcdef') + self.assertEqual(ctx.correlation_type, "web") + self.assertIsNone(ctx.instana_ancestor) + self.assertEqual(ctx.level, 1) + self.assertEqual(ctx.long_trace_id, "4bf92f3577b34da6a3ce929d0e0e4736") + self.assertEqual(ctx.span_id, "00f067aa0ba902b7") + self.assertFalse(ctx.synthetic) + self.assertEqual(ctx.trace_id, "a3ce929d0e0e4736") # 16 last chars from traceparent trace_id + self.assertTrue(ctx.trace_parent) + self.assertEqual(ctx.traceparent, '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01') + self.assertEqual(ctx.tracestate, 'congo=t61rcWkgMzE') + + @patch.object(Traceparent, "get_traceparent_fields") + @patch.object(Traceparent, "validate") + def test_extract_carrier_list(self, mock_validate, mock_get_traceparent_fields): + carrier = [('user-agent', 'python-requests/2.23.0'), ('accept-encoding', 'gzip, deflate'), + ('accept', '*/*'), ('connection', 'keep-alive'), + ('traceparent', '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'), + ('tracestate', 'congo=t61rcWkgMzE'), + ('X-INSTANA-T', '1234d0e0e4736234'), + ('X-INSTANA-S', '1234567890abcdef'), + ('X-INSTANA-L', '1')] + + mock_validate.return_value = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' + mock_get_traceparent_fields.return_value = ["00", "4bf92f3577b34da6a3ce929d0e0e4736", "00f067aa0ba902b7", "01"] + ctx = self.hptc.extract(carrier) + self.assertIsNone(ctx.correlation_id) + self.assertIsNone(ctx.correlation_type) + self.assertIsNone(ctx.instana_ancestor) + self.assertEqual(ctx.level, 1) + self.assertIsNone(ctx.long_trace_id) + self.assertEqual(ctx.span_id, "1234567890abcdef") + self.assertFalse(ctx.synthetic) + self.assertEqual(ctx.trace_id, "1234d0e0e4736234") # 16 last chars from traceparent trace_id + self.assertIsNone(ctx.trace_parent) + self.assertEqual(ctx.traceparent, '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01') + self.assertEqual(ctx.tracestate, 'congo=t61rcWkgMzE') + + @patch.object(Traceparent, "validate") + def test_extract_carrier_dict_validate_Exception_None_returned(self, mock_validate): + """ + In this test case the traceparent header fails the validation, so traceparent and tracestate are not gonna used + Additionally because in the instana L header the correlation flags are present we need to start a new ctx and + the present values of 'X-INSTANA-T', 'X-INSTANA-S' headers should no be used. This means the ctx should be None + :param mock_validate: + :return: + """ + carrier = { + 'traceparent': '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01', + 'tracestate': 'congo=t61rcWkgMzE', + 'X-INSTANA-T': '1234d0e0e4736234', + 'X-INSTANA-S': '1234567890abcdef', + 'X-INSTANA-L': '1, correlationType=web; correlationId=1234567890abcdef' + } + mock_validate.return_value = None + ctx = self.hptc.extract(carrier) + self.assertTrue(isinstance(ctx, SpanContext)) + assert ctx.trace_id is None + assert ctx.span_id is None + assert ctx.synthetic is False + self.assertEqual(ctx.correlation_id, "1234567890abcdef") + self.assertEqual(ctx.correlation_type, "web") + + @patch.object(Traceparent, "validate") + def test_extract_fake_exception(self, mock_validate): + carrier = { + 'traceparent': '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01', + 'tracestate': 'congo=t61rcWkgMzE', + 'X-INSTANA-T': '1234d0e0e4736234', + 'X-INSTANA-S': '1234567890abcdef', + 'X-INSTANA-L': '1, correlationType=web; correlationId=1234567890abcdef' + } + mock_validate.side_effect = Exception + ctx = self.hptc.extract(carrier) + self.assertIsNone(ctx) + + @patch.object(Traceparent, "get_traceparent_fields") + @patch.object(Traceparent, "validate") + def test_extract_carrier_dict_corrupted_level_header(self, mock_validate, mock_get_traceparent_fields): + """ + In this test case the traceparent header fails the validation, so traceparent and tracestate are not gonna used + Additionally because in the instana L header the correlation flags are present we need to start a new ctx and + the present values of 'X-INSTANA-T', 'X-INSTANA-S' headers should no be used. This means the ctx should be None + :param mock_validate: + :return: + """ + carrier = { + 'traceparent': '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01', + 'tracestate': 'congo=t61rcWkgMzE', + 'X-INSTANA-T': '1234d0e0e4736234', + 'X-INSTANA-S': '1234567890abcdef', + 'X-INSTANA-L': '1, correlationTypeweb; correlationId1234567890abcdef' + } + mock_validate.return_value = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' + mock_get_traceparent_fields.return_value = ["00", "4bf92f3577b34da6a3ce929d0e0e4736", "00f067aa0ba902b7", "01"] + ctx = self.hptc.extract(carrier) + self.assertIsNone(ctx.correlation_id) + self.assertIsNone(ctx.correlation_type) + self.assertIsNone(ctx.instana_ancestor) + self.assertEqual(ctx.level, 1) + self.assertEqual(ctx.long_trace_id, '4bf92f3577b34da6a3ce929d0e0e4736') + self.assertEqual(ctx.span_id, "00f067aa0ba902b7") + self.assertFalse(ctx.synthetic) + self.assertEqual(ctx.trace_id, "a3ce929d0e0e4736") # 16 last chars from traceparent trace_id + self.assertTrue(ctx.trace_parent) + self.assertEqual(ctx.traceparent, '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01') + self.assertEqual(ctx.tracestate, 'congo=t61rcWkgMzE') + + @patch.object(Traceparent, "get_traceparent_fields") + @patch.object(Traceparent, "validate") + def test_extract_carrier_dict_level_header_not_splitable(self, mock_validate, mock_get_traceparent_fields): + """ + In this test case the traceparent header fails the validation, so traceparent and tracestate are not gonna used + Additionally because in the instana L header the correlation flags are present we need to start a new ctx and + the present values of 'X-INSTANA-T', 'X-INSTANA-S' headers should no be used. This means the ctx should be None + :param mock_validate: + :return: + """ + carrier = { + 'traceparent': '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01', + 'tracestate': 'congo=t61rcWkgMzE', + 'X-INSTANA-T': '1234d0e0e4736234', + 'X-INSTANA-S': '1234567890abcdef', + 'X-INSTANA-L': ['1'] + } + mock_validate.return_value = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' + mock_get_traceparent_fields.return_value = ["00", "4bf92f3577b34da6a3ce929d0e0e4736", "00f067aa0ba902b7", "01"] + ctx = self.hptc.extract(carrier) + self.assertIsNone(ctx.correlation_id) + self.assertIsNone(ctx.correlation_type) + self.assertIsNone(ctx.instana_ancestor) + self.assertEqual(ctx.level, 1) + self.assertIsNone(ctx.long_trace_id) + self.assertEqual(ctx.span_id, "1234567890abcdef") + self.assertFalse(ctx.synthetic) + self.assertEqual(ctx.trace_id, "1234d0e0e4736234") + self.assertIsNone(ctx.trace_parent) + self.assertEqual(ctx.traceparent, '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01') + self.assertEqual(ctx.tracestate, 'congo=t61rcWkgMzE') \ No newline at end of file diff --git a/tests/test_id_management.py b/tests/test_id_management.py index 69529e1e..69412e44 100644 --- a/tests/test_id_management.py +++ b/tests/test_id_management.py @@ -24,38 +24,38 @@ def test_id_generation(): def test_various_header_to_id_conversion(): # Get a hex string to test against & convert header_id = instana.util.ids.generate_id() - converted_id = instana.util.ids.header_to_id(header_id) + converted_id = instana.util.ids.header_to_long_id(header_id) assert(header_id == converted_id) # Hex value - result should be left padded - result = instana.util.ids.header_to_id('abcdef') + result = instana.util.ids.header_to_long_id('abcdef') assert('0000000000abcdef' == result) # Hex value - result = instana.util.ids.header_to_id('0123456789abcdef') + result = instana.util.ids.header_to_long_id('0123456789abcdef') assert('0123456789abcdef' == result) # Very long incoming header should just return the rightmost 16 bytes - result = instana.util.ids.header_to_id('0x0123456789abcdef0123456789abcdef') - assert('0123456789abcdef' == result) + result = instana.util.ids.header_to_long_id('0x0123456789abcdef0123456789abcdef') + assert('0x0123456789abcdef0123456789abcdef' == result) def test_header_to_id_conversion_with_bogus_header(): # Bogus nil arg - bogus_result = instana.util.ids.header_to_id(None) + bogus_result = instana.util.ids.header_to_long_id(None) assert(instana.util.ids.BAD_ID == bogus_result) # Bogus Integer arg - bogus_result = instana.util.ids.header_to_id(1234) + bogus_result = instana.util.ids.header_to_long_id(1234) assert(instana.util.ids.BAD_ID == bogus_result) # Bogus Array arg - bogus_result = instana.util.ids.header_to_id([1234]) + bogus_result = instana.util.ids.header_to_long_id([1234]) assert(instana.util.ids.BAD_ID == bogus_result) # Bogus Hex Values in String - bogus_result = instana.util.ids.header_to_id('0xZZZZZZ') + bogus_result = instana.util.ids.header_to_long_id('0xZZZZZZ') assert(instana.util.ids.BAD_ID == bogus_result) - bogus_result = instana.util.ids.header_to_id('ZZZZZZ') + bogus_result = instana.util.ids.header_to_long_id('ZZZZZZ') assert(instana.util.ids.BAD_ID == bogus_result) diff --git a/tests/w3c_trace_context/__init__.py b/tests/w3c_trace_context/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/w3c_trace_context/test_traceparent.py b/tests/w3c_trace_context/test_traceparent.py new file mode 100644 index 00000000..11c29381 --- /dev/null +++ b/tests/w3c_trace_context/test_traceparent.py @@ -0,0 +1,56 @@ +# (c) Copyright IBM Corp. 2021 +# (c) Copyright Instana Inc. 2021 + +from instana.w3c_trace_context.traceparent import Traceparent +import unittest + + +class TestTraceparent(unittest.TestCase): + def setUp(self): + self.tp = Traceparent() + + def test_validate_valid(self): + traceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" + self.assertEqual(traceparent, self.tp.validate(traceparent)) + + def test_validate_invalid_traceparent(self): + traceparent = "00-4bxxxxx3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" + self.assertIsNone(self.tp.validate(traceparent)) + + def test_validate_traceparent_None(self): + traceparent = None + self.assertIsNone(self.tp.validate(traceparent)) + + def test_get_traceparent_fields(self): + traceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" + version, trace_id, parent_id, trace_flags = self.tp.get_traceparent_fields(traceparent) + self.assertEqual(trace_id, "4bf92f3577b34da6a3ce929d0e0e4736") + self.assertEqual(parent_id, "00f067aa0ba902b7") + + def test_get_traceparent_fields_None_input(self): + traceparent = None + version, trace_id, parent_id, trace_flags = self.tp.get_traceparent_fields(traceparent) + self.assertIsNone(trace_id) + self.assertIsNone(parent_id) + + def test_get_traceparent_fields_string_input_no_dash(self): + traceparent = "invalid" + version, trace_id, parent_id, trace_flags = self.tp.get_traceparent_fields(traceparent) + self.assertIsNone(trace_id) + self.assertIsNone(parent_id) + + def test_update_traceparent(self): + traceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" + in_trace_id = "1234d0e0e4736234" + in_span_id = "1234567890abcdef" + level = 1 + expected_traceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-1234567890abcdef-01" + self.assertEqual(expected_traceparent, self.tp.update_traceparent(traceparent, in_trace_id, in_span_id, level)) + + def test_update_traceparent_None(self): + traceparent = None + in_trace_id = "1234d0e0e4736234" + in_span_id = "7890abcdef" + level = 0 + expected_traceparent = "00-00000000000000001234d0e0e4736234-0000007890abcdef-00" + self.assertEqual(expected_traceparent, self.tp.update_traceparent(traceparent, in_trace_id, in_span_id, level)) diff --git a/tests/w3c_trace_context/test_tracestate.py b/tests/w3c_trace_context/test_tracestate.py new file mode 100644 index 00000000..8bc0ce22 --- /dev/null +++ b/tests/w3c_trace_context/test_tracestate.py @@ -0,0 +1,71 @@ +# (c) Copyright IBM Corp. 2021 +# (c) Copyright Instana Inc. 2021 + +from instana.w3c_trace_context.tracestate import Tracestate +import unittest + + +class TestTracestate(unittest.TestCase): + def setUp(self): + self.ts = Tracestate() + + def test_get_instana_ancestor(self): + tracestate = "congo=t61rcWkgMzE,in=1234d0e0e4736234;1234567890abcdef" + ia = self.ts.get_instana_ancestor(tracestate) + self.assertEqual(ia.t, "1234d0e0e4736234") + self.assertEqual(ia.p, "1234567890abcdef") + + def test_get_instana_ancestor_no_in(self): + tracestate = "congo=t61rcWkgMzE" + self.assertIsNone(self.ts.get_instana_ancestor(tracestate)) + + def test_get_instana_ancestor_tracestate_None(self): + tracestate = None + self.assertIsNone(self.ts.get_instana_ancestor(tracestate)) + + def test_update_tracestate(self): + tracestate = "congo=t61rcWkgMzE" + in_trace_id = "1234d0e0e4736234" + in_span_id = "1234567890abcdef" + expected_tracestate = "in=1234d0e0e4736234;1234567890abcdef,congo=t61rcWkgMzE" + self.assertEqual(expected_tracestate, self.ts.update_tracestate(tracestate, in_trace_id, in_span_id)) + + def test_update_tracestate_None(self): + tracestate = None + in_trace_id = "1234d0e0e4736234" + in_span_id = "1234567890abcdef" + expected_tracestate = "in=1234d0e0e4736234;1234567890abcdef" + self.assertEqual(expected_tracestate, self.ts.update_tracestate(tracestate, in_trace_id, in_span_id)) + + def test_update_tracestate_more_than_32_members_already(self): + tracestate = "congo=t61rcWkgMzE,robo=1221213jdfjkdsfjsd,alpha=5889fnjkllllllll," \ + "beta=aslsdklkljfdshasfaskkfnnnsdsd,gamadeltaepsilonpirpsigma=125646845613675451535445155126666fgsdfdsfjsdfhsdfsdsdsaddfasfdfdsfdsfsd;qwertyuiopasdfghjklzxcvbnm1234567890," \ + "b=121,c=23344,d=asd,e=ldkfj,f=1212121,g=sadahsda,h=jjhdada,i=eerjrjrr,j=sadsasd,k=44444,l=dadadad," \ + "m=rrrr,n=3424jdg,p=ffss,q=12,r=3,s=5,t=u5,u=43,v=gj,w=wew,x=23123,y=sdf,z=kasdl,aa=dsdas,ab=res," \ + "ac=trwa,ad=kll,ae=pds" + in_trace_id = "1234d0e0e4736234" + in_span_id = "1234567890abcdef" + expected_tracestate = "in=1234d0e0e4736234;1234567890abcdef,congo=t61rcWkgMzE,robo=1221213jdfjkdsfjsd," \ + "alpha=5889fnjkllllllll,beta=aslsdklkljfdshasfaskkfnnnsdsd,b=121,c=23344,d=asd,e=ldkfj," \ + "f=1212121,g=sadahsda,h=jjhdada,i=eerjrjrr,j=sadsasd,k=44444,l=dadadad,m=rrrr,n=3424jdg," \ + "p=ffss,q=12,r=3,s=5,t=u5,u=43,v=gj,w=wew,x=23123,y=sdf,z=kasdl,aa=dsdas,ab=res,ac=trwa" + actual_tracestate = self.ts.update_tracestate(tracestate, in_trace_id, in_span_id) + self.assertEqual(len(tracestate.split(",")), 34) # input had 34 list members + self.assertEqual(len(actual_tracestate.split(",")), 32) # output has 32 list members, 3 removed and 1 added + self.assertEqual(expected_tracestate, actual_tracestate) + self.assertNotIn("gamadeltaepsilonpirpsigma", + actual_tracestate) # member longer than 128 characters gets removed + + def test_update_tracestate_empty_string(self): + tracestate = "" + in_trace_id = "1234d0e0e4736234" + in_span_id = "1234567890abcdef" + expected_tracestate = "in=1234d0e0e4736234;1234567890abcdef" + self.assertEqual(expected_tracestate, self.ts.update_tracestate(tracestate, in_trace_id, in_span_id)) + + def test_update_tracestate_exception(self): + tracestate = [] + in_trace_id = "1234d0e0e4736234" + in_span_id = "1234567890abcdef" + expected_tracestate = [] + self.assertEqual(expected_tracestate, self.ts.update_tracestate(tracestate, in_trace_id, in_span_id)) \ No newline at end of file