-
Notifications
You must be signed in to change notification settings - Fork 678
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ASGI: Capture custom request response headers #1004
Changes from 3 commits
6e668aa
8fbd374
c67907e
69d1a00
cc40a86
9b74daa
e8cc609
a6dfe79
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -115,7 +115,14 @@ def client_response_hook(span: Span, message: dict): | |
from opentelemetry.semconv.trace import SpanAttributes | ||
from opentelemetry.trace import Span, set_span_in_context | ||
from opentelemetry.trace.status import Status, StatusCode | ||
from opentelemetry.util.http import remove_url_credentials | ||
from opentelemetry.util.http import ( | ||
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST, | ||
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE, | ||
get_custom_headers, | ||
normalise_request_header_name, | ||
normalise_response_header_name, | ||
remove_url_credentials, | ||
) | ||
|
||
_ServerRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]] | ||
_ClientRequestHookT = typing.Optional[typing.Callable[[Span, dict], None]] | ||
|
@@ -223,6 +230,41 @@ def collect_request_attributes(scope): | |
return result | ||
|
||
|
||
def collect_custom_request_headers_attributes(scope): | ||
"""returns custom HTTP request headers to be added into SERVER span as span attributes | ||
Refer specification https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-request-and-response-headers""" | ||
|
||
attributes = {} | ||
custom_request_headers = get_custom_headers( | ||
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST | ||
) | ||
|
||
for header in custom_request_headers: | ||
values = asgi_getter.get(scope, header) | ||
if values: | ||
key = normalise_request_header_name(header) | ||
attributes.setdefault(key, []).extend(values) | ||
|
||
return attributes | ||
|
||
|
||
def collect_custom_response_headers_attributes(message): | ||
"""returns custom HTTP response headers to be added into SERVER span as span attributes | ||
Refer specification https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-request-and-response-headers""" | ||
attributes = {} | ||
custom_response_headers = get_custom_headers( | ||
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE | ||
) | ||
|
||
for header in custom_response_headers: | ||
values = asgi_getter.get(message, header) | ||
if values: | ||
key = normalise_response_header_name(header) | ||
attributes.setdefault(key, []).extend(values) | ||
|
||
return attributes | ||
|
||
|
||
def get_host_port_url_tuple(scope): | ||
"""Returns (host, port, full_url) tuple.""" | ||
server = scope.get("server") or ["0.0.0.0", 80] | ||
|
@@ -342,6 +384,13 @@ async def __call__(self, scope, receive, send): | |
for key, value in attributes.items(): | ||
current_span.set_attribute(key, value) | ||
|
||
if span.kind == trace.SpanKind.SERVER: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess
codeboten marked this conversation as resolved.
Show resolved
Hide resolved
|
||
custom_attributes = ( | ||
collect_custom_request_headers_attributes(scope) | ||
) | ||
if len(custom_attributes) > 0: | ||
current_span.set_attributes(custom_attributes) | ||
|
||
if callable(self.server_request_hook): | ||
self.server_request_hook(current_span, scope) | ||
|
||
|
@@ -395,6 +444,17 @@ async def otel_send(message): | |
set_status_code(server_span, 200) | ||
set_status_code(send_span, 200) | ||
send_span.set_attribute("type", message["type"]) | ||
if ( | ||
server_span.kind == trace.SpanKind.SERVER | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should check if server_span is a recording span before checking its kind. NonRecordingSpan do not contain kind attribute. (#998 was reported for wsgi based frameworks for the same) |
||
and "headers" in message | ||
): | ||
custom_response_attributes = ( | ||
collect_custom_response_headers_attributes(message) | ||
) | ||
if len(custom_response_attributes) > 0: | ||
server_span.set_attributes( | ||
custom_response_attributes | ||
) | ||
|
||
propagator = get_global_response_propagator() | ||
if propagator: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be ok, but please make sure any new public (non-underscore prefixed) function or symbol is really necessary and meant to be used directly by the user. If not, please prefix them with underscores.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand. This functions will be used by other frameworks like django or FastAPI to fetch the custom headers and add them to spans as attributes. (just like collect_request_attributes/add_response_attributes)