diff --git a/src/instana/instrumentation/aiohttp/client.py b/src/instana/instrumentation/aiohttp/client.py
index 4b307dc4..667c2620 100644
--- a/src/instana/instrumentation/aiohttp/client.py
+++ b/src/instana/instrumentation/aiohttp/client.py
@@ -12,7 +12,7 @@
from instana.propagators.format import Format
from instana.singletons import agent
from instana.util.secrets import strip_secrets_from_query
-from instana.util.traceutils import get_tracer_tuple, tracing_is_off
+from instana.util.traceutils import get_tracer_tuple, tracing_is_off, extract_custom_headers
try:
import aiohttp
@@ -21,6 +21,7 @@
from aiohttp.client import ClientSession
from instana.span.span import InstanaSpan
+
async def stan_request_start(
session: "ClientSession", trace_config_ctx: SimpleNamespace, params
) -> Awaitable[None]:
@@ -35,6 +36,8 @@ async def stan_request_start(
span = tracer.start_span("aiohttp-client", span_context=parent_context)
+ extract_custom_headers(span, params.headers)
+
tracer.inject(span.context, Format.HTTP_HEADERS, params.headers)
parts = str(params.url).split("?")
@@ -59,13 +62,7 @@ async def stan_request_end(
SpanAttributes.HTTP_STATUS_CODE, params.response.status
)
- if agent.options.extra_http_headers:
- for custom_header in agent.options.extra_http_headers:
- if custom_header in params.response.headers:
- span.set_attribute(
- "http.header.%s" % custom_header,
- params.response.headers[custom_header],
- )
+ extract_custom_headers(span, params.response.headers)
if 500 <= params.response.status:
span.mark_as_errored({"http.error": params.response.reason})
diff --git a/src/instana/instrumentation/aiohttp/server.py b/src/instana/instrumentation/aiohttp/server.py
index d658641b..ff22ae6b 100644
--- a/src/instana/instrumentation/aiohttp/server.py
+++ b/src/instana/instrumentation/aiohttp/server.py
@@ -11,6 +11,7 @@
from instana.propagators.format import Format
from instana.singletons import agent, tracer
from instana.util.secrets import strip_secrets_from_query
+from instana.util.traceutils import extract_custom_headers
if TYPE_CHECKING:
from instana.span.span import InstanaSpan
@@ -46,14 +47,7 @@ async def stan_middleware(
span.set_attribute(SpanAttributes.HTTP_URL, parts[0])
span.set_attribute(SpanAttributes.HTTP_METHOD, request.method)
- # Custom header tracking support
- if agent.options.extra_http_headers:
- for custom_header in agent.options.extra_http_headers:
- if custom_header in request.headers:
- span.set_attribute(
- "http.header.%s" % custom_header,
- request.headers[custom_header],
- )
+ extract_custom_headers(span, request.headers)
response = None
try:
@@ -69,6 +63,9 @@ async def stan_middleware(
span.mark_as_errored()
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, response.status)
+
+ extract_custom_headers(span, response.headers)
+
tracer.inject(span.context, Format.HTTP_HEADERS, response.headers)
return response
diff --git a/src/instana/instrumentation/asgi.py b/src/instana/instrumentation/asgi.py
index ed0866ae..2831bb92 100644
--- a/src/instana/instrumentation/asgi.py
+++ b/src/instana/instrumentation/asgi.py
@@ -14,6 +14,7 @@
from instana.propagators.format import Format
from instana.singletons import agent, tracer
from instana.util.secrets import strip_secrets_from_query
+from instana.util.traceutils import extract_custom_headers
if TYPE_CHECKING:
from starlette.middleware.exceptions import ExceptionMiddleware
@@ -28,23 +29,6 @@ class InstanaASGIMiddleware:
def __init__(self, app: "ExceptionMiddleware") -> None:
self.app = app
- def _extract_custom_headers(
- self, span: "InstanaSpan", headers: Dict[str, Any]
- ) -> None:
- if agent.options.extra_http_headers is None:
- return
- try:
- for custom_header in agent.options.extra_http_headers:
- # Headers are in the following format: b'x-header-1'
- for header_pair in headers:
- if header_pair[0].decode("utf-8").lower() == custom_header.lower():
- span.set_attribute(
- f"http.header.{custom_header}",
- header_pair[1].decode("utf-8"),
- )
- except Exception:
- logger.debug("extract_custom_headers: ", exc_info=True)
-
def _collect_kvs(self, scope: Dict[str, Any], span: "InstanaSpan") -> None:
try:
span.set_attribute("span.kind", SpanKind.SERVER)
@@ -93,8 +77,8 @@ async def __call__(
with tracer.start_as_current_span("asgi", span_context=request_context) as span:
self._collect_kvs(scope, span)
- if "headers" in scope and agent.options.extra_http_headers:
- self._extract_custom_headers(span, scope["headers"])
+ if "headers" in scope:
+ extract_custom_headers(span, scope["headers"])
instana_send = self._send_with_instana(
span,
@@ -125,7 +109,7 @@ async def send_wrapper(response: Dict[str, Any]) -> Awaitable[None]:
headers = response.get("headers")
if headers:
- self._extract_custom_headers(current_span, headers)
+ extract_custom_headers(current_span, headers)
tracer.inject(current_span.context, Format.BINARY, headers)
except Exception:
logger.debug("ASGI send_wrapper error: ", exc_info=True)
diff --git a/src/instana/instrumentation/boto3_inst.py b/src/instana/instrumentation/boto3_inst.py
index fb7a3233..88e1c33f 100644
--- a/src/instana/instrumentation/boto3_inst.py
+++ b/src/instana/instrumentation/boto3_inst.py
@@ -10,7 +10,7 @@
from instana.log import logger
from instana.singletons import tracer, agent
-from instana.util.traceutils import get_tracer_tuple, tracing_is_off
+from instana.util.traceutils import get_tracer_tuple, tracing_is_off, extract_custom_headers
from instana.propagators.format import Format
from instana.span.span import get_current_span
@@ -23,21 +23,6 @@
import boto3
from boto3.s3 import inject
- def extract_custom_headers(
- span: "InstanaSpan", headers: Optional[Dict[str, Any]] = None
- ) -> None:
- if not agent.options.extra_http_headers or not headers:
- return
- try:
- for custom_header in agent.options.extra_http_headers:
- if custom_header in headers:
- span.set_attribute(
- "http.header.%s" % custom_header, headers[custom_header]
- )
-
- except Exception:
- logger.debug("extract_custom_headers: ", exc_info=True)
-
def lambda_inject_context(payload: Dict[str, Any], span: "InstanaSpan") -> None:
"""
When boto3 lambda client 'Invoke' is called, we want to inject the tracing context.
diff --git a/src/instana/instrumentation/django/middleware.py b/src/instana/instrumentation/django/middleware.py
index 4dc2e621..5e5b8419 100644
--- a/src/instana/instrumentation/django/middleware.py
+++ b/src/instana/instrumentation/django/middleware.py
@@ -13,10 +13,10 @@
from instana.log import logger
from instana.singletons import agent, tracer
from instana.util.secrets import strip_secrets_from_query
+ from instana.util.traceutils import extract_custom_headers
from instana.propagators.format import Format
if TYPE_CHECKING:
- from instana.span.span import InstanaSpan
from django.core.handlers.base import BaseHandler
from django.http import HttpRequest, HttpResponse
@@ -53,29 +53,6 @@ def __init__(
super(InstanaMiddleware, self).__init__(get_response)
self.get_response = get_response
- def _extract_custom_headers(
- self, span: "InstanaSpan", headers: Dict[str, Any], format: bool
- ) -> None:
- if agent.options.extra_http_headers is None:
- return
-
- try:
- for custom_header in agent.options.extra_http_headers:
- # Headers are available in this format: HTTP_X_CAPTURE_THIS
- django_header = (
- ("HTTP_" + custom_header.upper()).replace("-", "_")
- if format
- else custom_header
- )
-
- if django_header in headers:
- span.set_attribute(
- f"http.header.{custom_header}", headers[django_header]
- )
-
- except Exception:
- logger.debug("Instana middleware @ extract_custom_headers: ", exc_info=True)
-
def process_request(self, request: Type["HttpRequest"]) -> None:
try:
env = request.META
@@ -89,7 +66,7 @@ def process_request(self, request: Type["HttpRequest"]) -> None:
token = context.attach(ctx)
request.token = token
- self._extract_custom_headers(span, env, format=True)
+ extract_custom_headers(span, env, format=True)
request.span.set_attribute(SpanAttributes.HTTP_METHOD, request.method)
if "PATH_INFO" in env:
@@ -138,7 +115,7 @@ def process_response(
SpanAttributes.HTTP_STATUS_CODE, response.status_code
)
if hasattr(response, "headers"):
- self._extract_custom_headers(
+ extract_custom_headers(
request.span, response.headers, format=False
)
tracer.inject(request.span.context, Format.HTTP_HEADERS, response)
diff --git a/src/instana/instrumentation/flask/common.py b/src/instana/instrumentation/flask/common.py
index d55c2432..a0e6f6fb 100644
--- a/src/instana/instrumentation/flask/common.py
+++ b/src/instana/instrumentation/flask/common.py
@@ -10,23 +10,15 @@
from opentelemetry.semconv.trace import SpanAttributes
from instana.log import logger
-from instana.singletons import tracer, agent
+from instana.singletons import tracer
from instana.propagators.format import Format
-from instana.instrumentation.flask import signals_available
if TYPE_CHECKING:
- from instana.span.span import InstanaSpan
from werkzeug.exceptions import HTTPException
from flask.typing import ResponseReturnValue
from jinja2.environment import Template
- if signals_available:
- from werkzeug.datastructures.headers import Headers
- else:
- from werkzeug.datastructures import Headers
-
-
@wrapt.patch_function_wrapper('flask', 'templating._render')
def render_with_instana(
wrapped: Callable[..., str],
@@ -97,21 +89,3 @@ def handle_user_exception_with_instana(
logger.debug("handle_user_exception_with_instana:", exc_info=True)
return response
-
-
-def extract_custom_headers(
- span: "InstanaSpan", headers: Union[Dict[str, Any], "Headers"], format: bool
-) -> None:
- if agent.options.extra_http_headers is None:
- return
- try:
- for custom_header in agent.options.extra_http_headers:
- # Headers are available in this format: HTTP_X_CAPTURE_THIS
- flask_header = ('HTTP_' + custom_header.upper()).replace('-', '_') if format else custom_header
- if flask_header in headers:
- span.set_attribute(
- "http.header.%s" % custom_header, headers[flask_header]
- )
-
- except Exception:
- logger.debug("extract_custom_headers: ", exc_info=True)
diff --git a/src/instana/instrumentation/flask/vanilla.py b/src/instana/instrumentation/flask/vanilla.py
index 0dd49795..fed13f16 100644
--- a/src/instana/instrumentation/flask/vanilla.py
+++ b/src/instana/instrumentation/flask/vanilla.py
@@ -13,7 +13,7 @@
from instana.log import logger
from instana.singletons import agent, tracer
from instana.util.secrets import strip_secrets_from_query
-from instana.instrumentation.flask.common import extract_custom_headers
+from instana.util.traceutils import extract_custom_headers
from instana.propagators.format import Format
path_tpl_re = re.compile('<.*>')
diff --git a/src/instana/instrumentation/flask/with_blinker.py b/src/instana/instrumentation/flask/with_blinker.py
index cebe2ef3..df3af703 100644
--- a/src/instana/instrumentation/flask/with_blinker.py
+++ b/src/instana/instrumentation/flask/with_blinker.py
@@ -12,7 +12,7 @@
from instana.log import logger
from instana.util.secrets import strip_secrets_from_query
from instana.singletons import agent, tracer
-from instana.instrumentation.flask.common import extract_custom_headers
+from instana.util.traceutils import extract_custom_headers
from instana.propagators.format import Format
import flask
@@ -78,7 +78,7 @@ def request_finished_with_instana(
extract_custom_headers(span, response.headers, format=False)
tracer.inject(span.context, Format.HTTP_HEADERS, response.headers)
- except:
+ except Exception:
logger.debug("Flask request_finished_with_instana", exc_info=True)
finally:
if span and span.is_recording():
diff --git a/src/instana/instrumentation/pyramid.py b/src/instana/instrumentation/pyramid.py
index 230ebcc5..88c3e419 100644
--- a/src/instana/instrumentation/pyramid.py
+++ b/src/instana/instrumentation/pyramid.py
@@ -16,12 +16,12 @@
from instana.log import logger
from instana.singletons import tracer, agent
from instana.util.secrets import strip_secrets_from_query
+ from instana.util.traceutils import extract_custom_headers
from instana.propagators.format import Format
if TYPE_CHECKING:
from pyramid.request import Request
from pyramid.response import Response
- from instana.span.span import InstanaSpan
from pyramid.registry import Registry
class InstanaTweenFactory(object):
@@ -32,21 +32,6 @@ def __init__(
) -> None:
self.handler = handler
- def _extract_custom_headers(
- self, span: "InstanaSpan", headers: Dict[str, Any]
- ) -> None:
- if not agent.options.extra_http_headers:
- return
- try:
- for custom_header in agent.options.extra_http_headers:
- if custom_header in headers:
- span.set_attribute(
- f"http.header.{custom_header}", headers[custom_header]
- )
-
- except Exception:
- logger.debug("extract_custom_headers: ", exc_info=True)
-
def __call__(self, request: "Request") -> "Response":
ctx = tracer.extract(Format.HTTP_HEADERS, dict(request.headers))
@@ -56,7 +41,7 @@ def __call__(self, request: "Request") -> "Response":
span.set_attribute(SpanAttributes.HTTP_METHOD, request.method)
span.set_attribute(SpanAttributes.HTTP_URL, request.path)
- self._extract_custom_headers(span, request.headers)
+ extract_custom_headers(span, request.headers)
if len(request.query_string):
scrubbed_params = strip_secrets_from_query(
@@ -74,7 +59,7 @@ def __call__(self, request: "Request") -> "Response":
"http.path_tpl", request.matched_route.pattern
)
- self._extract_custom_headers(span, response.headers)
+ extract_custom_headers(span, response.headers)
tracer.inject(span.context, Format.HTTP_HEADERS, response.headers)
except HTTPException as e:
diff --git a/src/instana/instrumentation/sanic_inst.py b/src/instana/instrumentation/sanic_inst.py
index 97bdb8b9..72b0dc26 100644
--- a/src/instana/instrumentation/sanic_inst.py
+++ b/src/instana/instrumentation/sanic_inst.py
@@ -75,8 +75,7 @@ def request_with_instana(request: Request) -> None:
)
span.set_attribute("http.params", scrubbed_params)
- if agent.options.extra_http_headers:
- extract_custom_headers(span, headers)
+ extract_custom_headers(span, headers)
if hasattr(request, "uri_template") and request.uri_template:
span.set_attribute("http.path_tpl", request.uri_template)
except Exception:
@@ -113,8 +112,7 @@ def response_with_instana(request: Request, response: HTTPResponse) -> None:
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)
if hasattr(response, "headers"):
- if agent.options.extra_http_headers:
- extract_custom_headers(span, response.headers)
+ extract_custom_headers(span, response.headers)
tracer.inject(span.context, Format.HTTP_HEADERS, response.headers)
if span.is_recording():
diff --git a/src/instana/instrumentation/tornado/client.py b/src/instana/instrumentation/tornado/client.py
index e937db68..134c7f7e 100644
--- a/src/instana/instrumentation/tornado/client.py
+++ b/src/instana/instrumentation/tornado/client.py
@@ -6,15 +6,18 @@
import wrapt
import functools
+ from typing import TYPE_CHECKING, Dict, Any
from opentelemetry.semconv.trace import SpanAttributes
from instana.log import logger
from instana.singletons import agent, tracer
from instana.util.secrets import strip_secrets_from_query
+ from instana.util.traceutils import extract_custom_headers
from instana.propagators.format import Format
from instana.span.span import get_current_span
+
@wrapt.patch_function_wrapper('tornado.httpclient', 'AsyncHTTPClient.fetch')
def fetch_with_instana(wrapped, instance, argv, kwargs):
try:
@@ -41,6 +44,9 @@ def fetch_with_instana(wrapped, instance, argv, kwargs):
parent_context = parent_span.get_span_context() if parent_span else None
span = tracer.start_span("tornado-client", span_context=parent_context)
+
+ extract_custom_headers(span, request.headers)
+
tracer.inject(span.context, Format.HTTP_HEADERS, request.headers)
# Query param scrubbing
@@ -68,6 +74,8 @@ def finish_tracing(future, span):
try:
response = future.result()
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, response.code)
+
+ extract_custom_headers(span, response.headers)
except tornado.httpclient.HTTPClientError as e:
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, e.code)
span.record_exception(e)
diff --git a/src/instana/instrumentation/tornado/server.py b/src/instana/instrumentation/tornado/server.py
index dc373bc9..82266961 100644
--- a/src/instana/instrumentation/tornado/server.py
+++ b/src/instana/instrumentation/tornado/server.py
@@ -12,18 +12,9 @@
from instana.log import logger
from instana.singletons import agent, tracer
from instana.util.secrets import strip_secrets_from_query
+ from instana.util.traceutils import extract_custom_headers
from instana.propagators.format import Format
- def extract_custom_headers(span, headers):
- if not agent.options.extra_http_headers or not headers:
- return
- try:
- for custom_header in agent.options.extra_http_headers:
- if custom_header in headers:
- span.set_attribute("http.header.%s" % custom_header, headers[custom_header])
-
- except Exception:
- logger.debug("extract_custom_headers: ", exc_info=True)
@wrapt.patch_function_wrapper('tornado.web', 'RequestHandler._execute')
diff --git a/src/instana/instrumentation/urllib3.py b/src/instana/instrumentation/urllib3.py
index 00ee7648..4536d2be 100644
--- a/src/instana/instrumentation/urllib3.py
+++ b/src/instana/instrumentation/urllib3.py
@@ -11,7 +11,7 @@
from instana.propagators.format import Format
from instana.singletons import agent
from instana.util.secrets import strip_secrets_from_query
-from instana.util.traceutils import get_tracer_tuple, tracing_is_off
+from instana.util.traceutils import get_tracer_tuple, tracing_is_off, extract_custom_headers
if TYPE_CHECKING:
from instana.span.span import InstanaSpan
@@ -19,19 +19,6 @@
try:
import urllib3
- def _extract_custom_headers(span: "InstanaSpan", headers: Dict[str, Any]) -> None:
- if agent.options.extra_http_headers is None:
- return
-
- try:
- for custom_header in agent.options.extra_http_headers:
- if custom_header in headers:
- span.set_attribute(
- f"http.header.{custom_header}", headers[custom_header]
- )
- except Exception:
- logger.debug("urllib3 _extract_custom_headers error: ", exc_info=True)
-
def _collect_kvs(
instance: Union[
urllib3.connectionpool.HTTPConnectionPool,
@@ -82,7 +69,7 @@ def collect_response(
try:
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, response.status)
- _extract_custom_headers(span, response.headers)
+ extract_custom_headers(span, response.headers)
if 500 <= response.status:
span.mark_as_errored()
@@ -121,7 +108,7 @@ def urlopen_with_instana(
if "method" in kvs:
span.set_attribute(SpanAttributes.HTTP_METHOD, kvs["method"])
if "headers" in kwargs:
- _extract_custom_headers(span, kwargs["headers"])
+ extract_custom_headers(span, kwargs["headers"])
tracer.inject(span.context, Format.HTTP_HEADERS, kwargs["headers"])
response = wrapped(*args, **kwargs)
diff --git a/src/instana/instrumentation/wsgi.py b/src/instana/instrumentation/wsgi.py
index 5700b252..5ab7a2f7 100644
--- a/src/instana/instrumentation/wsgi.py
+++ b/src/instana/instrumentation/wsgi.py
@@ -4,6 +4,7 @@
"""
Instana WSGI Middleware
"""
+
from typing import Dict, Any, Callable, List, Tuple, Optional
from opentelemetry.semconv.trace import SpanAttributes
@@ -12,6 +13,7 @@
from instana.propagators.format import Format
from instana.singletons import agent, tracer
from instana.util.secrets import strip_secrets_from_query
+from instana.util.traceutils import extract_custom_headers
class InstanaWSGIMiddleware(object):
@@ -23,11 +25,22 @@ def __init__(self, app: object) -> None:
def __call__(self, environ: Dict[str, Any], start_response: Callable) -> object:
env = environ
- def new_start_response(status: str, headers: List[Tuple[object, ...]], exc_info: Optional[Exception] = None) -> object:
+ def new_start_response(
+ status: str,
+ headers: List[Tuple[object, ...]],
+ exc_info: Optional[Exception] = None,
+ ) -> object:
"""Modified start response with additional headers."""
+ extract_custom_headers(self.span, headers)
+
tracer.inject(self.span.context, Format.HTTP_HEADERS, headers)
- headers_str = [(header[0], str(header[1])) if not isinstance(header[1], str) else header for header in headers]
+ headers_str = [
+ (header[0], str(header[1]))
+ if not isinstance(header[1], str)
+ else header
+ for header in headers
+ ]
res = start_response(status, headers_str, exc_info)
sc = status.split(" ")[0]
@@ -47,14 +60,7 @@ def new_start_response(status: str, headers: List[Tuple[object, ...]], exc_info:
ctx = trace.set_span_in_context(self.span)
self.token = context.attach(ctx)
- if agent.options.extra_http_headers is not None:
- for custom_header in agent.options.extra_http_headers:
- # Headers are available in this format: HTTP_X_CAPTURE_THIS
- wsgi_header = ("HTTP_" + custom_header.upper()).replace("-", "_")
- if wsgi_header in env:
- self.span.set_attribute(
- "http.header.%s" % custom_header, env[wsgi_header]
- )
+ extract_custom_headers(self.span, env, format=True)
if "PATH_INFO" in env:
self.span.set_attribute("http.path", env["PATH_INFO"])
diff --git a/src/instana/util/traceutils.py b/src/instana/util/traceutils.py
index 06b821ca..a9f1849b 100644
--- a/src/instana/util/traceutils.py
+++ b/src/instana/util/traceutils.py
@@ -1,21 +1,37 @@
# (c) Copyright IBM Corp. 2021
# (c) Copyright Instana Inc. 2021
-from typing import Optional, Tuple
+from typing import Optional, Tuple, TYPE_CHECKING, Union, Dict, List, Any, Iterable
from instana.log import logger
from instana.singletons import agent, tracer
-from instana.span.span import InstanaSpan, get_current_span
+from instana.span.span import get_current_span
from instana.tracer import InstanaTracer
+if TYPE_CHECKING:
+ from instana.span.span import InstanaSpan
-def extract_custom_headers(tracing_span, headers) -> None:
+def extract_custom_headers(span: "InstanaSpan", headers: Optional[Union[Dict[str, Any], List[Tuple[object, ...]], Iterable]] = None, format: Optional[bool] = False) -> None:
+ if not headers:
+ return
try:
for custom_header in agent.options.extra_http_headers:
- # Headers are in the following format: b'x-header-1'
- for header_key, value in headers.items():
- if header_key.lower() == custom_header.lower():
- tracing_span.set_attribute(f"http.header.{custom_header}", value)
+ # Headers are available in the following formats: HTTP_X_CAPTURE_THIS, b'x-header-1', X-Capture-That
+ expected_header = (
+ ("HTTP_" + custom_header.upper()).replace("-", "_")
+ if format
+ else custom_header
+ )
+ for header in headers:
+ if isinstance(header, tuple):
+ header_key = header[0].decode("utf-8") if isinstance(header[0], bytes) else header[0]
+ header_val = header[1].decode("utf-8") if isinstance(header[1], bytes) else header[1]
+ if header_key.lower() == expected_header.lower():
+ span.set_attribute(
+ f"http.header.{custom_header}", header_val,
+ )
+ elif header.lower() == expected_header.lower():
+ span.set_attribute(f"http.header.{custom_header}", headers[expected_header])
except Exception:
logger.debug("extract_custom_headers: ", exc_info=True)
@@ -36,7 +52,7 @@ def get_active_tracer() -> Optional[InstanaTracer]:
def get_tracer_tuple() -> (
- Tuple[Optional[InstanaTracer], Optional[InstanaSpan], Optional[str]]
+ Tuple[Optional[InstanaTracer], Optional["InstanaSpan"], Optional[str]]
):
active_tracer = get_active_tracer()
current_span = get_current_span()
diff --git a/tests/apps/aiohttp_app/app.py b/tests/apps/aiohttp_app/app.py
index 44bdb1a3..f43ee59d 100755
--- a/tests/apps/aiohttp_app/app.py
+++ b/tests/apps/aiohttp_app/app.py
@@ -34,6 +34,11 @@ def raise_exception(request):
raise Exception("Simulated exception")
+def response_headers(request):
+ headers = {"X-Capture-This-Too": "this too", "X-Capture-That-Too": "that too"}
+ return web.Response(text="Stan wuz here with headers!", headers=headers)
+
+
def aiohttp_server():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
@@ -44,6 +49,7 @@ def aiohttp_server():
app.add_routes([web.get('/401', four_hundred_one)])
app.add_routes([web.get('/500', five_hundred)])
app.add_routes([web.get('/exception', raise_exception)])
+ app.add_routes([web.get('/response_headers', response_headers)])
runner = web.AppRunner(app)
loop.run_until_complete(runner.setup())
diff --git a/tests/apps/bottle_app/app.py b/tests/apps/bottle_app/app.py
index cd56c138..c0d29a3e 100644
--- a/tests/apps/bottle_app/app.py
+++ b/tests/apps/bottle_app/app.py
@@ -6,7 +6,7 @@
import logging
from wsgiref.simple_server import make_server
-from bottle import default_app
+from bottle import default_app, response
from tests.helpers import testenv
from instana.middleware import InstanaWSGIMiddleware
@@ -23,6 +23,12 @@
def hello():
return "
🐍 Hello Stan! 🦄
"
+@app.route("/response_headers")
+def response_headers():
+ response.set_header("X-Capture-This", "this")
+ response.set_header("X-Capture-That", "that")
+ return "Stan wuz here with headers!"
+
# Wrap the application with the Instana WSGI Middleware
app = InstanaWSGIMiddleware(app)
bottle_server = make_server('127.0.0.1', testenv["wsgi_port"], app)
diff --git a/tests/apps/starlette_app/app.py b/tests/apps/starlette_app/app.py
index 04878c12..baaf7f66 100644
--- a/tests/apps/starlette_app/app.py
+++ b/tests/apps/starlette_app/app.py
@@ -20,6 +20,11 @@ def user(request):
return PlainTextResponse("Hello, user id %s!" % user_id)
+def response_headers(request):
+ headers = {"X-Capture-This-Too": "this too", "X-Capture-That-Too": "that too"}
+ return PlainTextResponse("Stan wuz here with headers!", headers=headers)
+
+
async def websocket_endpoint(websocket):
await websocket.accept()
await websocket.send_text("Hello, websocket!")
@@ -33,6 +38,7 @@ def startup():
routes = [
Route("/", homepage),
Route("/users/{user_id}", user),
+ Route("/response_headers", response_headers),
WebSocketRoute("/ws", websocket_endpoint),
Mount("/static", StaticFiles(directory=dir_path + "/static")),
]
diff --git a/tests/clients/test_urllib3.py b/tests/clients/test_urllib3.py
index 77b49ece..642edd9a 100644
--- a/tests/clients/test_urllib3.py
+++ b/tests/clients/test_urllib3.py
@@ -12,7 +12,7 @@
import urllib3
from instana.instrumentation.urllib3 import (
_collect_kvs as collect_kvs,
- _extract_custom_headers as extract_custom_headers,
+ extract_custom_headers,
collect_response,
)
from instana.singletons import agent, tracer
@@ -971,7 +971,7 @@ def test_extract_custom_headers_exception(
monkeypatch.setattr(span, "set_attribute", Exception("mocked error"))
caplog.set_level(logging.DEBUG, logger="instana")
extract_custom_headers(span, request_headers)
- assert "urllib3 _extract_custom_headers error: " in caplog.messages
+ assert "extract_custom_headers: " in caplog.messages
def test_collect_response_exception(
self, span: "InstanaSpan", caplog: "LogCaptureFixture", monkeypatch
diff --git a/tests/frameworks/test_aiohttp_client.py b/tests/frameworks/test_aiohttp_client.py
index fd66ba17..3a2b29ea 100644
--- a/tests/frameworks/test_aiohttp_client.py
+++ b/tests/frameworks/test_aiohttp_client.py
@@ -501,3 +501,63 @@ async def test():
spans = self.recorder.queued_spans()
assert len(spans) == 3
+
+ def test_client_request_header_capture(self) -> None:
+ original_extra_http_headers = agent.options.extra_http_headers
+ agent.options.extra_http_headers = ["X-Capture-This-Too"]
+
+ request_headers = {
+ "X-Capture-This-Too": "Ok too",
+ }
+
+ async def test():
+ with tracer.start_as_current_span("test"):
+ async with aiohttp.ClientSession() as session:
+ return await self.fetch(
+ session, testenv["flask_server"] + "/", headers=request_headers
+ )
+
+ response = self.loop.run_until_complete(test())
+
+ spans = self.recorder.queued_spans()
+ assert len(spans) == 3
+
+ wsgi_span = spans[0]
+ aiohttp_span = spans[1]
+ test_span = spans[2]
+
+ # Same traceId
+ traceId = test_span.t
+ assert aiohttp_span.t == traceId
+ assert wsgi_span.t == traceId
+
+ # Parent relationships
+ assert aiohttp_span.p == test_span.s
+ assert wsgi_span.p == aiohttp_span.s
+
+ # Error logging
+ assert not test_span.ec
+ assert not aiohttp_span.ec
+ assert not wsgi_span.ec
+
+ assert aiohttp_span.n == "aiohttp-client"
+ assert aiohttp_span.data["http"]["status"] == 200
+ assert aiohttp_span.data["http"]["url"] == testenv["flask_server"] + "/"
+ assert aiohttp_span.data["http"]["method"] == "GET"
+ assert aiohttp_span.stack
+ assert isinstance(aiohttp_span.stack, list)
+ assert len(aiohttp_span.stack) > 1
+
+ assert "X-Capture-This-Too" in aiohttp_span.data["http"]["header"]
+ assert aiohttp_span.data["http"]["header"]["X-Capture-This-Too"] == "Ok too"
+
+ assert "X-INSTANA-T" in response.headers
+ assert response.headers["X-INSTANA-T"] == hex_id(traceId)
+ assert "X-INSTANA-S" in response.headers
+ assert response.headers["X-INSTANA-S"] == hex_id(wsgi_span.s)
+ assert "X-INSTANA-L" in response.headers
+ assert response.headers["X-INSTANA-L"] == "1"
+ assert "Server-Timing" in response.headers
+ assert response.headers["Server-Timing"] == f"intid;desc={hex_id(traceId)}"
+
+ agent.options.extra_http_headers = original_extra_http_headers
diff --git a/tests/frameworks/test_aiohttp_server.py b/tests/frameworks/test_aiohttp_server.py
index f9cb01e5..6c2ca672 100644
--- a/tests/frameworks/test_aiohttp_server.py
+++ b/tests/frameworks/test_aiohttp_server.py
@@ -207,7 +207,9 @@ async def test():
assert "Server-Timing" in response.headers
assert response.headers["Server-Timing"] == f"intid;desc={hex_id(traceId)}"
- def test_server_custom_header_capture(self):
+ def test_server_request_header_capture(self):
+ original_extra_http_headers = agent.options.extra_http_headers
+
async def test():
with tracer.start_as_current_span("test"):
async with aiohttp.ClientSession() as session:
@@ -272,6 +274,70 @@ async def test():
assert "X-Capture-That" in aioserver_span.data["http"]["header"]
assert aioserver_span.data["http"]["header"]["X-Capture-That"] == "that"
+ agent.options.extra_http_headers = original_extra_http_headers
+
+ def test_server_response_header_capture(self):
+ original_extra_http_headers = agent.options.extra_http_headers
+
+ async def test():
+ with tracer.start_as_current_span("test"):
+ async with aiohttp.ClientSession() as session:
+ # Hack together a manual custom headers list
+ agent.options.extra_http_headers = [
+ "X-Capture-This-Too",
+ "X-Capture-That-Too",
+ ]
+
+ return await self.fetch(
+ session,
+ testenv["aiohttp_server"] + "/response_headers"
+ )
+
+ response = self.loop.run_until_complete(test())
+
+ spans = self.recorder.queued_spans()
+ assert len(spans) == 3
+
+ aioserver_span = spans[0]
+ aioclient_span = spans[1]
+ test_span = spans[2]
+
+ # Same traceId
+ traceId = test_span.t
+ assert aioclient_span.t == traceId
+ assert aioserver_span.t == traceId
+
+ # Parent relationships
+ assert aioclient_span.p == test_span.s
+ assert aioserver_span.p == aioclient_span.s
+
+ # Error logging
+ assert not test_span.ec
+ assert not aioclient_span.ec
+ assert not aioserver_span.ec
+
+ assert aioserver_span.n == "aiohttp-server"
+ assert aioserver_span.data["http"]["status"] == 200
+ assert aioserver_span.data["http"]["url"] == f"{testenv['aiohttp_server']}/response_headers"
+ assert aioserver_span.data["http"]["method"] == "GET"
+ assert not aioserver_span.stack
+
+ assert "X-INSTANA-T" in response.headers
+ assert response.headers["X-INSTANA-T"] == hex_id(traceId)
+ assert "X-INSTANA-S" in response.headers
+ assert response.headers["X-INSTANA-S"] == hex_id(aioserver_span.s)
+ assert "X-INSTANA-L" in response.headers
+ assert response.headers["X-INSTANA-L"] == "1"
+ assert "Server-Timing" in response.headers
+ assert response.headers["Server-Timing"] == f"intid;desc={hex_id(traceId)}"
+
+ assert "X-Capture-This-Too" in aioserver_span.data["http"]["header"]
+ assert aioserver_span.data["http"]["header"]["X-Capture-This-Too"] == "this too"
+ assert "X-Capture-That-Too" in aioserver_span.data["http"]["header"]
+ assert aioserver_span.data["http"]["header"]["X-Capture-That-Too"] == "that too"
+
+ agent.options.extra_http_headers = original_extra_http_headers
+
def test_server_get_401(self):
async def test():
with tracer.start_as_current_span("test"):
diff --git a/tests/frameworks/test_flask.py b/tests/frameworks/test_flask.py
index 876fd2ba..d3a5f10e 100644
--- a/tests/frameworks/test_flask.py
+++ b/tests/frameworks/test_flask.py
@@ -20,7 +20,7 @@
from opentelemetry.trace import SpanKind
import tests.apps.flask_app
-from instana.singletons import tracer
+from instana.singletons import tracer, agent
from instana.span.span import get_current_span
from tests.helpers import testenv
@@ -1002,11 +1002,58 @@ def test_path_templates(self) -> None:
# We should have a reported path template for this route
assert "/users/{username}/sayhello" == wsgi_span.data["http"]["path_tpl"]
+ def test_request_header_capture(self) -> None:
+ # Hack together a manual custom headers list
+ original_extra_http_headers = agent.options.extra_http_headers
+ agent.options.extra_http_headers = ["X-Capture-This-Too", "X-Capture-That-Too"]
+
+ request_headers = {
+ "X-Capture-This-Too": "this too",
+ "X-Capture-That-Too": "that too",
+ }
+
+ with tracer.start_as_current_span("test"):
+ response = self.http.request(
+ "GET", testenv["flask_server"] + "/", headers=request_headers
+ )
+
+ assert response
+ assert response.status == 200
+
+ spans = self.recorder.queued_spans()
+ assert len(spans) == 3
+
+ wsgi_span = spans[0]
+ urllib3_span = spans[1]
+ test_span = spans[2]
+
+ # Same traceId
+ assert test_span.t == urllib3_span.t
+ assert urllib3_span.t == wsgi_span.t
+
+ # Parent relationships
+ assert urllib3_span.p == test_span.s
+ assert wsgi_span.p == urllib3_span.s
+
+ assert wsgi_span.ec is None
+ assert wsgi_span.stack is None
+
+ assert "/" == wsgi_span.data["http"]["url"]
+ assert "GET" == wsgi_span.data["http"]["method"]
+ assert 200 == wsgi_span.data["http"]["status"]
+
+ assert "X-Capture-This-Too" in wsgi_span.data["http"]["header"]
+ assert "this too" == wsgi_span.data["http"]["header"]["X-Capture-This-Too"]
+ assert "X-Capture-That-Too" in wsgi_span.data["http"]["header"]
+ assert "that too" == wsgi_span.data["http"]["header"]["X-Capture-That-Too"]
+
+ agent.options.extra_http_headers = original_extra_http_headers
+
+
def test_response_header_capture(self) -> None:
# Hack together a manual custom headers list
- from instana.singletons import agent
original_extra_http_headers = agent.options.extra_http_headers
- agent.options.extra_http_headers = [u'X-Capture-This', u'X-Capture-That']
+ agent.options.extra_http_headers = ["X-Capture-This", "X-Capture-That"]
with tracer.start_as_current_span("test"):
response = self.http.request('GET', testenv["flask_server"] + '/response_headers')
diff --git a/tests/frameworks/test_starlette.py b/tests/frameworks/test_starlette.py
index 1dd079f8..e332e024 100644
--- a/tests/frameworks/test_starlette.py
+++ b/tests/frameworks/test_starlette.py
@@ -23,6 +23,8 @@ def _resource(self) -> Generator[None, None, None]:
agent.options.extra_http_headers = [
"X-Capture-This",
"X-Capture-That",
+ "X-Capture-This-Too",
+ "X-Capture-That-Too"
]
# Clear all spans before a test run.
self.recorder = tracer.span_processor
@@ -245,7 +247,7 @@ def test_synthetic_request(self) -> None:
assert asgi_span.sy
assert not test_span.sy
- def test_custom_header_capture(self) -> None:
+ def test_request_header_capture(self) -> None:
with tracer.start_as_current_span("test") as span:
# As TestClient() is based on httpx, and we don't support it yet,
# we must pass the SDK trace_id and span_id to the ASGI server.
@@ -299,3 +301,56 @@ def test_custom_header_capture(self) -> None:
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"]
+
+ def test_response_header_capture(self) -> None:
+ with tracer.start_as_current_span("test") as span:
+ # As TestClient() is based on httpx, and we don't support it yet,
+ # we must pass the SDK trace_id and span_id to the ASGI server.
+ span_context = span.get_span_context()
+ headers = {
+ "X-INSTANA-T": hex_id(span_context.trace_id),
+ "X-INSTANA-S": hex_id(span_context.span_id),
+ }
+ result = self.client.get("/response_headers", headers=headers)
+
+ assert result
+ assert "X-INSTANA-T" in result.headers
+ assert "X-INSTANA-S" in result.headers
+ assert "X-INSTANA-L" in result.headers
+ assert "Server-Timing" in result.headers
+ assert result.headers["X-INSTANA-L"] == "1"
+
+ spans = self.recorder.queued_spans()
+ assert len(spans) == 2
+
+ span_filter = ( # noqa: E731
+ lambda span: span.n == "sdk" and span.data["sdk"]["name"] == "test"
+ )
+ test_span = get_first_span_by_filter(spans, span_filter)
+ assert test_span
+
+ span_filter = lambda span: span.n == "asgi" # noqa: E731
+ asgi_span = get_first_span_by_filter(spans, span_filter)
+ assert asgi_span
+
+ assert test_span.t == asgi_span.t
+ assert test_span.s == asgi_span.p
+
+ assert result.headers["X-INSTANA-T"] == hex_id(asgi_span.t)
+ assert result.headers["X-INSTANA-S"] == hex_id(asgi_span.s)
+ assert result.headers["X-INSTANA-L"] == "1"
+ assert result.headers["Server-Timing"] == f"intid;desc={hex_id(asgi_span.t)}"
+
+ assert not asgi_span.ec
+ assert asgi_span.data["http"]["host"] == "testserver"
+ assert asgi_span.data["http"]["path"] == "/response_headers"
+ assert asgi_span.data["http"]["path_tpl"] == "/response_headers"
+ assert asgi_span.data["http"]["method"] == "GET"
+ assert asgi_span.data["http"]["status"] == 200
+ assert not asgi_span.data["http"]["error"]
+ assert not asgi_span.data["http"]["params"]
+
+ assert "X-Capture-This-Too" in asgi_span.data["http"]["header"]
+ assert "this too" == asgi_span.data["http"]["header"]["X-Capture-This-Too"]
+ assert "X-Capture-That-Too" in asgi_span.data["http"]["header"]
+ assert "that too" == asgi_span.data["http"]["header"]["X-Capture-That-Too"]
diff --git a/tests/frameworks/test_tornado_client.py b/tests/frameworks/test_tornado_client.py
index 20ba1f0f..2c93afdc 100644
--- a/tests/frameworks/test_tornado_client.py
+++ b/tests/frameworks/test_tornado_client.py
@@ -8,7 +8,7 @@
import tornado
from tornado.httpclient import AsyncHTTPClient
-from instana.singletons import tracer
+from instana.singletons import tracer, agent
from instana.span.span import get_current_span
from instana.util.ids import hex_id
@@ -460,3 +460,142 @@ async def test():
assert response.headers["X-INSTANA-L"] == '1'
assert "Server-Timing" in response.headers
assert response.headers["Server-Timing"] == f"intid;desc={hex_id(traceId)}"
+
+ def test_request_header_capture(self) -> None:
+ # Hack together a manual custom headers list
+ original_extra_http_headers = agent.options.extra_http_headers
+ agent.options.extra_http_headers = ["X-Capture-This", "X-Capture-That"]
+
+ request_headers = {
+ "X-Capture-This": "this",
+ "X-Capture-That": "that",
+ }
+
+ async def test():
+ with tracer.start_as_current_span("test"):
+ return await self.http_client.fetch(testenv["tornado_server"] + "/", headers=request_headers)
+
+ response = tornado.ioloop.IOLoop.current().run_sync(test)
+ assert isinstance(response, tornado.httpclient.HTTPResponse)
+
+ time.sleep(0.5)
+ spans = self.recorder.queued_spans()
+
+ assert len(spans) == 3
+
+ server_span = get_first_span_by_name(spans, "tornado-server")
+ client_span = get_first_span_by_name(spans, "tornado-client")
+ test_span = get_first_span_by_name(spans, "sdk")
+
+ assert not get_current_span().is_recording()
+
+ # Same traceId
+ traceId = test_span.t
+ assert traceId == client_span.t
+ assert traceId == server_span.t
+
+ # Parent relationships
+ assert client_span.p == test_span.s
+ assert server_span.p == client_span.s
+
+ # Error logging
+ assert not test_span.ec
+ assert not client_span.ec
+ assert not server_span.ec
+
+ assert server_span.n == "tornado-server"
+ assert server_span.data["http"]["status"] == 200
+ assert testenv["tornado_server"] + "/" == server_span.data["http"]["url"]
+ assert not server_span.data["http"]["params"]
+ assert server_span.data["http"]["method"] == "GET"
+
+ assert client_span.n == "tornado-client"
+ assert client_span.data["http"]["status"] == 200
+ assert testenv["tornado_server"] + "/" == client_span.data["http"]["url"]
+ assert client_span.data["http"]["method"] == "GET"
+ assert client_span.stack
+ assert type(client_span.stack) is list
+ assert len(client_span.stack) > 1
+
+ assert "X-INSTANA-T" in response.headers
+ assert response.headers["X-INSTANA-T"] == hex_id(traceId)
+ assert "X-INSTANA-S" in response.headers
+ assert response.headers["X-INSTANA-S"] == hex_id(server_span.s)
+ assert "X-INSTANA-L" in response.headers
+ assert response.headers["X-INSTANA-L"] == '1'
+ assert "Server-Timing" in response.headers
+ assert response.headers["Server-Timing"] == f"intid;desc={hex_id(traceId)}"
+
+ assert "X-Capture-This" in client_span.data["http"]["header"]
+ assert client_span.data["http"]["header"]["X-Capture-This"] == "this"
+ assert "X-Capture-That" in client_span.data["http"]["header"]
+ assert client_span.data["http"]["header"]["X-Capture-That"] == "that"
+
+ agent.options.extra_http_headers = original_extra_http_headers
+
+ def test_response_header_capture(self) -> None:
+ # Hack together a manual custom headers list
+ original_extra_http_headers = agent.options.extra_http_headers
+ agent.options.extra_http_headers = ["X-Capture-This-Too", "X-Capture-That-Too"]
+
+ async def test():
+ with tracer.start_as_current_span("test"):
+ return await self.http_client.fetch(testenv["tornado_server"] + "/response_headers")
+
+ response = tornado.ioloop.IOLoop.current().run_sync(test)
+ assert isinstance(response, tornado.httpclient.HTTPResponse)
+
+ time.sleep(0.5)
+ spans = self.recorder.queued_spans()
+
+ assert len(spans) == 3
+
+ server_span = get_first_span_by_name(spans, "tornado-server")
+ client_span = get_first_span_by_name(spans, "tornado-client")
+ test_span = get_first_span_by_name(spans, "sdk")
+
+ assert not get_current_span().is_recording()
+
+ # Same traceId
+ traceId = test_span.t
+ assert traceId == client_span.t
+ assert traceId == server_span.t
+
+ # Parent relationships
+ assert client_span.p == test_span.s
+ assert server_span.p == client_span.s
+
+ # Error logging
+ assert not test_span.ec
+ assert not client_span.ec
+ assert not server_span.ec
+
+ assert server_span.n == "tornado-server"
+ assert server_span.data["http"]["status"] == 200
+ assert testenv["tornado_server"] + "/response_headers" == server_span.data["http"]["url"]
+ assert not server_span.data["http"]["params"]
+ assert server_span.data["http"]["method"] == "GET"
+
+ assert client_span.n == "tornado-client"
+ assert client_span.data["http"]["status"] == 200
+ assert testenv["tornado_server"] + "/response_headers" == client_span.data["http"]["url"]
+ assert client_span.data["http"]["method"] == "GET"
+ assert client_span.stack
+ assert type(client_span.stack) is list
+ assert len(client_span.stack) > 1
+
+ assert "X-INSTANA-T" in response.headers
+ assert response.headers["X-INSTANA-T"] == hex_id(traceId)
+ assert "X-INSTANA-S" in response.headers
+ assert response.headers["X-INSTANA-S"] == hex_id(server_span.s)
+ assert "X-INSTANA-L" in response.headers
+ assert response.headers["X-INSTANA-L"] == '1'
+ assert "Server-Timing" in response.headers
+ assert response.headers["Server-Timing"] == f"intid;desc={hex_id(traceId)}"
+
+ assert "X-Capture-This-Too" in client_span.data["http"]["header"]
+ assert client_span.data["http"]["header"]["X-Capture-This-Too"] == "this too"
+ assert "X-Capture-That-Too" in client_span.data["http"]["header"]
+ assert client_span.data["http"]["header"]["X-Capture-That-Too"] == "that too"
+
+ agent.options.extra_http_headers = original_extra_http_headers
diff --git a/tests/frameworks/test_wsgi.py b/tests/frameworks/test_wsgi.py
index e57f0485..882c5cfd 100644
--- a/tests/frameworks/test_wsgi.py
+++ b/tests/frameworks/test_wsgi.py
@@ -107,72 +107,6 @@ def test_synthetic_request(self) -> None:
assert urllib3_span.sy is None
assert test_span.sy is None
-
- def test_custom_header_capture(self) -> None:
- # Hack together a manual custom headers list
- agent.options.extra_http_headers = [u'X-Capture-This', u'X-Capture-That']
-
- request_headers = {}
- request_headers['X-Capture-This'] = 'this'
- request_headers['X-Capture-That'] = 'that'
-
- with tracer.start_as_current_span("test"):
- response = self.http.request('GET', testenv["wsgi_server"] + '/', headers=request_headers)
-
- spans = self.recorder.queued_spans()
-
- assert 3 == len(spans)
- assert get_current_span().is_recording() is False
-
- wsgi_span = spans[0]
- urllib3_span = spans[1]
- test_span = spans[2]
-
- assert response
- assert 200 == response.status
-
- assert 'X-INSTANA-T' in response.headers
- assert int(response.headers['X-INSTANA-T'], 16)
- assert response.headers["X-INSTANA-T"] == hex_id(wsgi_span.t)
-
- assert 'X-INSTANA-S' in response.headers
- assert int(response.headers['X-INSTANA-S'], 16)
- assert response.headers["X-INSTANA-S"] == hex_id(wsgi_span.s)
-
- assert 'X-INSTANA-L' in response.headers
- assert response.headers['X-INSTANA-L'] == '1'
-
- assert 'Server-Timing' in response.headers
- server_timing_value = f"intid;desc={hex_id(wsgi_span.t)}"
- assert response.headers['Server-Timing'] == server_timing_value
-
- # Same traceId
- assert test_span.t == urllib3_span.t
- assert urllib3_span.t == wsgi_span.t
-
- # Parent relationships
- assert urllib3_span.p == test_span.s
- assert wsgi_span.p == urllib3_span.s
-
- # Error logging
- assert test_span.ec is None
- assert urllib3_span.ec is None
- assert wsgi_span.ec is None
-
- # wsgi
- assert "wsgi" == wsgi_span.n
- assert '127.0.0.1:' + str(testenv["wsgi_port"]) == wsgi_span.data["http"]["host"]
- assert '/' == wsgi_span.data["http"]["path"]
- assert 'GET' == wsgi_span.data["http"]["method"]
- assert "200" == wsgi_span.data["http"]["status"]
- assert wsgi_span.data["http"]["error"] is None
- assert wsgi_span.stack is None
-
- assert "X-Capture-This" in wsgi_span.data["http"]["header"]
- assert "this" == wsgi_span.data["http"]["header"]["X-Capture-This"]
- assert "X-Capture-That" in wsgi_span.data["http"]["header"]
- assert "that" == wsgi_span.data["http"]["header"]["X-Capture-That"]
-
def test_secret_scrubbing(self) -> None:
with tracer.start_as_current_span("test"):
response = self.http.request('GET', testenv["wsgi_server"] + '/?secret=shhh')
@@ -297,33 +231,114 @@ def test_with_incoming_mixed_case_context(self) -> None:
server_timing_value = f"intid;desc={hex_id(wsgi_span.t)}"
assert response.headers['Server-Timing'] == server_timing_value
- def test_response_headers(self) -> None:
+ def test_response_header_capture(self) -> None:
+ # Hack together a manual custom headers list
+ original_extra_http_headers = agent.options.extra_http_headers
+ agent.options.extra_http_headers = ["X-Capture-This", "X-Capture-That"]
+
with tracer.start_as_current_span("test"):
- response = self.http.request('GET', testenv["wsgi_server"] + '/')
+ response = self.http.request(
+ "GET", testenv["wsgi_server"] + "/response_headers"
+ )
spans = self.recorder.queued_spans()
-
- assert 3 == len(spans)
- assert get_current_span().is_recording() is False
+ assert len(spans) == 3
wsgi_span = spans[0]
urllib3_span = spans[1]
test_span = spans[2]
assert response
- assert 200 == response.status
+ assert response.status == 200
- assert 'X-INSTANA-T' in response.headers
- assert int(response.headers['X-INSTANA-T'], 16)
- assert response.headers["X-INSTANA-T"] == hex_id(wsgi_span.t)
+ # Same traceId
+ assert test_span.t == urllib3_span.t
+ assert urllib3_span.t == wsgi_span.t
- assert 'X-INSTANA-S' in response.headers
- assert int(response.headers['X-INSTANA-S'], 16)
- assert response.headers["X-INSTANA-S"] == hex_id(wsgi_span.s)
+ # Parent relationships
+ assert urllib3_span.p == test_span.s
+ assert wsgi_span.p == urllib3_span.s
- assert 'X-INSTANA-L' in response.headers
- assert response.headers['X-INSTANA-L'] == '1'
+ # Synthetic
+ assert not wsgi_span.sy
+ assert not urllib3_span.sy
+ assert not test_span.sy
- assert 'Server-Timing' in response.headers
- server_timing_value = f"intid;desc={hex_id(wsgi_span.t)}"
- assert response.headers['Server-Timing'] == server_timing_value
+ # Error logging
+ assert not test_span.ec
+ assert not urllib3_span.ec
+ assert not wsgi_span.ec
+
+ # wsgi
+ assert wsgi_span.n == "wsgi"
+ assert wsgi_span.data["http"]["host"] == "127.0.0.1:" + str(testenv["wsgi_port"])
+ assert wsgi_span.data["http"]["path"] == "/response_headers"
+ assert wsgi_span.data["http"]["method"] == "GET"
+ assert wsgi_span.data["http"]["status"] == "200"
+ assert not wsgi_span.data["http"]["error"]
+
+ # custom headers
+ assert "X-Capture-This" in wsgi_span.data["http"]["header"]
+ assert wsgi_span.data["http"]["header"]["X-Capture-This"] == "this"
+ assert "X-Capture-That" in wsgi_span.data["http"]["header"]
+ assert wsgi_span.data["http"]["header"]["X-Capture-That"] == "that"
+
+ agent.options.extra_http_headers = original_extra_http_headers
+
+ def test_request_header_capture(self) -> None:
+ original_extra_http_headers = agent.options.extra_http_headers
+ agent.options.extra_http_headers = ["X-Capture-This-Too", "X-Capture-That-Too"]
+
+ request_headers = {
+ "X-Capture-This-Too": "this too",
+ "X-Capture-That-Too": "that too",
+ }
+
+ with tracer.start_as_current_span("test"):
+ response = self.http.request(
+ "GET", testenv["wsgi_server"] + "/", headers=request_headers
+ )
+
+ spans = self.recorder.queued_spans()
+ assert len(spans) == 3
+
+ wsgi_span = spans[0]
+ urllib3_span = spans[1]
+ test_span = spans[2]
+
+ assert response.status == 200
+
+ # Same traceId
+ assert test_span.t == urllib3_span.t
+ assert urllib3_span.t == wsgi_span.t
+
+ # Parent relationships
+ assert urllib3_span.p == test_span.s
+ assert wsgi_span.p == urllib3_span.s
+
+ # Synthetic
+ assert not wsgi_span.sy
+ assert not urllib3_span.sy
+ assert not test_span.sy
+
+ # Error logging
+ assert not test_span.ec
+ assert not urllib3_span.ec
+ assert not wsgi_span.ec
+
+ # wsgi
+ assert wsgi_span.n == "wsgi"
+ assert wsgi_span.data["http"]["host"] == "127.0.0.1:" + str(testenv["wsgi_port"])
+ assert wsgi_span.data["http"]["path"] == "/"
+ assert wsgi_span.data["http"]["method"] == "GET"
+ assert wsgi_span.data["http"]["status"] == "200"
+ assert not wsgi_span.data["http"]["error"]
+ assert not wsgi_span.stack
+
+ # custom headers
+ assert "X-Capture-This-Too" in wsgi_span.data["http"]["header"]
+ assert wsgi_span.data["http"]["header"]["X-Capture-This-Too"] == "this too"
+ assert "X-Capture-That-Too" in wsgi_span.data["http"]["header"]
+ assert wsgi_span.data["http"]["header"]["X-Capture-That-Too"] == "that too"
+
+ agent.options.extra_http_headers = original_extra_http_headers
diff --git a/tests/util/test_traceutils.py b/tests/util/test_traceutils.py
index a5505c1f..3cfc87c0 100644
--- a/tests/util/test_traceutils.py
+++ b/tests/util/test_traceutils.py
@@ -1,6 +1,6 @@
# (c) Copyright IBM Corp. 2024
-from unittest.mock import patch
+import pytest
from instana.singletons import agent, tracer
from instana.tracer import InstanaTracer
@@ -12,26 +12,61 @@
)
-def test_extract_custom_headers(span) -> None:
+@pytest.mark.parametrize(
+ "custom_headers, format",
+ [
+ (
+ {
+ "X-Capture-This-Too": "this too",
+ "X-Capture-That-Too": "that too",
+ },
+ False,
+ ),
+ (
+ {
+ "HTTP_X_CAPTURE_THIS_TOO": "this too",
+ "HTTP_X_CAPTURE_THAT_TOO": "that too",
+ },
+ True,
+ ),
+ (
+ [("X-CAPTURE-THIS-TOO", "this too"), ("x-capture-that-too", "that too")],
+ False,
+ ),
+ (
+ [
+ (b"X-Capture-This-Too", b"this too"),
+ (b"X-Capture-That-Too", b"that too"),
+ ],
+ False,
+ ),
+ (
+ [
+ ("HTTP_X_CAPTURE_THIS_TOO", "this too"),
+ ("HTTP_X_CAPTURE_THAT_TOO", "that too"),
+ ],
+ True,
+ ),
+ ],
+)
+def test_extract_custom_headers(span, custom_headers, format) -> None:
agent.options.extra_http_headers = ["X-Capture-This-Too", "X-Capture-That-Too"]
- request_headers = {
- "X-Capture-This-Too": "this too",
- "X-Capture-That-Too": "that too",
- }
- extract_custom_headers(span, request_headers)
+ extract_custom_headers(span, custom_headers, format=format)
assert len(span.attributes) == 2
assert span.attributes["http.header.X-Capture-This-Too"] == "this too"
assert span.attributes["http.header.X-Capture-That-Too"] == "that too"
-def test_get_activate_tracer() -> None:
+def test_get_activate_tracer(mocker) -> None:
assert not get_active_tracer()
with tracer.start_as_current_span("test"):
response = get_active_tracer()
assert isinstance(response, InstanaTracer)
assert response == tracer
- with patch("instana.span.span.InstanaSpan.is_recording", return_value=False):
+ with mocker.patch(
+ "instana.span.span.InstanaSpan.is_recording", return_value=False
+ ):
assert not get_active_tracer()