Skip to content

Commit

Permalink
Falcon: Capture request/response headers as span attributes (#1003)
Browse files Browse the repository at this point in the history
  • Loading branch information
ashu658 authored Mar 21, 2022
1 parent e861b93 commit 59ca95d
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#999])(https://github.com/open-telemetry/opentelemetry-python-contrib/pull/999)
- `opentelemetry-instrumentation-tornado` Fix non-recording span bug
([#999])(https://github.com/open-telemetry/opentelemetry-python-contrib/pull/999)
- `opentelemetry-instrumentation-falcon` Falcon: Capture custom request/response headers in span attributes
([#1003])(https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1003)

## [1.10.0-0.29b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.10.0-0.29b0) - 2022-03-10

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ def __call__(self, env, start_response):
attributes = otel_wsgi.collect_request_attributes(env)
for key, value in attributes.items():
span.set_attribute(key, value)
if span.is_recording() and span.kind == trace.SpanKind.SERVER:
otel_wsgi.add_custom_request_headers(span, env)

activation = trace.use_span(span, end_on_exit=True)
activation.__enter__()
Expand Down Expand Up @@ -295,6 +297,10 @@ def process_response(
description=reason,
)
)
if span.is_recording() and span.kind == trace.SpanKind.SERVER:
otel_wsgi.add_custom_response_headers(
span, resp.headers.items()
)
except ValueError:
pass

Expand Down
15 changes: 15 additions & 0 deletions instrumentation/opentelemetry-instrumentation-falcon/tests/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ def on_get(self, req, resp):
print(non_existent_var) # noqa


class CustomResponseHeaderResource:
def on_get(self, _, resp):
# pylint: disable=no-member
resp.status = falcon.HTTP_201
resp.set_header("content-type", "text/plain; charset=utf-8")
resp.set_header("content-length", "0")
resp.set_header(
"my-custom-header", "my-custom-value-1,my-custom-header-2"
)
resp.set_header("dont-capture-me", "test-value")


def make_app():
if hasattr(falcon, "App"):
# Falcon 3
Expand All @@ -43,4 +55,7 @@ def make_app():
app.add_route("/hello", HelloWorldResource())
app.add_route("/ping", HelloWorldResource())
app.add_route("/error", ErrorResource())
app.add_route(
"/test_custom_response_headers", CustomResponseHeaderResource()
)
return app
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
from opentelemetry.test.test_base import TestBase
from opentelemetry.test.wsgitestutil import WsgiTestBase
from opentelemetry.trace import StatusCode
from opentelemetry.util.http import (
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST,
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE,
)

from .app import make_app

Expand Down Expand Up @@ -280,3 +284,105 @@ def test_mark_span_internal_in_presence_of_span_from_other_framework(self):
self.assertEqual(
span.parent.span_id, parent_span.get_span_context().span_id
)


@patch.dict(
"os.environ",
{
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST: "Custom-Test-Header-1,Custom-Test-Header-2,invalid-header",
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE: "content-type,content-length,my-custom-header,invalid-header",
},
)
class TestCustomRequestResponseHeaders(TestFalconBase):
def test_custom_request_header_added_in_server_span(self):
headers = {
"Custom-Test-Header-1": "Test Value 1",
"Custom-Test-Header-2": "TestValue2,TestValue3",
"Custom-Test-Header-3": "TestValue4",
}
self.client().simulate_request(
method="GET", path="/hello", headers=headers
)
span = self.memory_exporter.get_finished_spans()[0]
assert span.status.is_ok

expected = {
"http.request.header.custom_test_header_1": ("Test Value 1",),
"http.request.header.custom_test_header_2": (
"TestValue2,TestValue3",
),
}
not_expected = {
"http.request.header.custom_test_header_3": ("TestValue4",),
}

self.assertEqual(span.kind, trace.SpanKind.SERVER)
self.assertSpanHasAttributes(span, expected)
for key, _ in not_expected.items():
self.assertNotIn(key, span.attributes)

def test_custom_request_header_not_added_in_internal_span(self):
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("test", kind=trace.SpanKind.SERVER):
headers = {
"Custom-Test-Header-1": "Test Value 1",
"Custom-Test-Header-2": "TestValue2,TestValue3",
}
self.client().simulate_request(
method="GET", path="/hello", headers=headers
)
span = self.memory_exporter.get_finished_spans()[0]
assert span.status.is_ok
not_expected = {
"http.request.header.custom_test_header_1": ("Test Value 1",),
"http.request.header.custom_test_header_2": (
"TestValue2,TestValue3",
),
}
self.assertEqual(span.kind, trace.SpanKind.INTERNAL)
for key, _ in not_expected.items():
self.assertNotIn(key, span.attributes)

def test_custom_response_header_added_in_server_span(self):
self.client().simulate_request(
method="GET", path="/test_custom_response_headers"
)
span = self.memory_exporter.get_finished_spans()[0]
assert span.status.is_ok
expected = {
"http.response.header.content_type": (
"text/plain; charset=utf-8",
),
"http.response.header.content_length": ("0",),
"http.response.header.my_custom_header": (
"my-custom-value-1,my-custom-header-2",
),
}
not_expected = {
"http.response.header.dont_capture_me": ("test-value",)
}
self.assertEqual(span.kind, trace.SpanKind.SERVER)
self.assertSpanHasAttributes(span, expected)
for key, _ in not_expected.items():
self.assertNotIn(key, span.attributes)

def test_custom_response_header_not_added_in_internal_span(self):
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("test", kind=trace.SpanKind.SERVER):
self.client().simulate_request(
method="GET", path="/test_custom_response_headers"
)
span = self.memory_exporter.get_finished_spans()[0]
assert span.status.is_ok
not_expected = {
"http.response.header.content_type": (
"text/plain; charset=utf-8",
),
"http.response.header.content_length": ("0",),
"http.response.header.my_custom_header": (
"my-custom-value-1,my-custom-header-2",
),
}
self.assertEqual(span.kind, trace.SpanKind.INTERNAL)
for key, _ in not_expected.items():
self.assertNotIn(key, span.attributes)

0 comments on commit 59ca95d

Please sign in to comment.