-
Notifications
You must be signed in to change notification settings - Fork 624
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
WSGI fixes #148
WSGI fixes #148
Changes from all commits
2bbc8be
cfdcaef
48fa684
77e641b
28e69b5
fa7aa21
8329a2f
99fcb90
df9ea84
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 |
---|---|---|
|
@@ -44,14 +44,34 @@ def _add_request_attributes(span, environ): | |
span.set_attribute("component", "http") | ||
span.set_attribute("http.method", environ["REQUEST_METHOD"]) | ||
|
||
host = environ.get("HTTP_HOST") or environ["SERVER_NAME"] | ||
host = environ.get("HTTP_HOST") | ||
if not host: | ||
host = environ["SERVER_NAME"] | ||
port = environ["SERVER_PORT"] | ||
if ( | ||
port != "80" | ||
and environ["wsgi.url_scheme"] == "http" | ||
or port != "443" | ||
): | ||
host += ":" + port | ||
|
||
# NOTE: Nonstandard | ||
span.set_attribute("http.host", host) | ||
|
||
url = ( | ||
environ.get("REQUEST_URI") | ||
or environ.get("RAW_URI") | ||
or wsgiref_util.request_uri(environ, include_query=False) | ||
) | ||
url = environ.get("REQUEST_URI") or environ.get("RAW_URI") | ||
|
||
if url: | ||
if url[0] == "/": | ||
# We assume that no scheme-relative URLs will be in url here. | ||
# After all, if a request is made to http://myserver//foo, we may get | ||
# //foo which looks like scheme-relative but isn't. | ||
url = environ["wsgi.url_scheme"] + "://" + host + url | ||
elif not url.startswith(environ["wsgi.url_scheme"] + ":"): | ||
# Something fishy is in RAW_URL. Let's fall back to request_uri() | ||
url = wsgiref_util.request_uri(environ) | ||
else: | ||
url = wsgiref_util.request_uri(environ) | ||
|
||
span.set_attribute("http.url", url) | ||
|
||
@staticmethod | ||
|
@@ -85,24 +105,27 @@ def __call__(self, environ, start_response): | |
|
||
tracer = trace.tracer() | ||
path_info = environ["PATH_INFO"] or "/" | ||
parent_span = propagators.extract(get_header_from_environ, environ) | ||
parent_span = propagators.extract(_get_header_from_environ, environ) | ||
|
||
with tracer.start_span( | ||
span = tracer.create_span( | ||
path_info, parent_span, kind=trace.SpanKind.SERVER | ||
) as span: | ||
self._add_request_attributes(span, environ) | ||
start_response = self._create_start_response(span, start_response) | ||
) | ||
span.start() | ||
try: | ||
with tracer.use_span(span): | ||
self._add_request_attributes(span, environ) | ||
start_response = self._create_start_response( | ||
span, start_response | ||
) | ||
|
||
iterable = self.wsgi(environ, start_response) | ||
try: | ||
for yielded in iterable: | ||
yield yielded | ||
finally: | ||
if hasattr(iterable, "close"): | ||
iterable.close() | ||
iterable = self.wsgi(environ, start_response) | ||
return _end_span_after_iterating(iterable, span, tracer) | ||
except: # noqa | ||
span.end() | ||
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. This seems to be risky. If an exception happened right before 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 may want to change the API so that we don't raise on duplicate calls to I've got an open PR in specs about suppressing errors in the API. The goal is to avoid exactly this kind of thing -- crashing the application because we're using the instrumentation layer wrong. 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. Seems there is no way to do it right with the current API then. 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. Probably leave a TODO comment for now. 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. BTW, the current SDK only logs a warning, which is fine because having end called twice here should really be an edge case. 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. For |
||
raise | ||
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. Why raise again here instead of making this a 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 it was because the assumption that 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. If we decided to go with this approach, I'll be okay if we can put a comment like this :) # ATTENTION!!! HIGH VOLTAGE!!! 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. If we can determine if a span has ended, then 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'm all for a better approach if somebody finds one that still doesn't delay the app invocation. See also: #148 (comment) I can't think of a way that doesn't have a slim chance of either ending the span twice or not ending it at all. |
||
|
||
|
||
def get_header_from_environ( | ||
def _get_header_from_environ( | ||
environ: dict, header_name: str | ||
) -> typing.List[str]: | ||
"""Retrieve the header value from the wsgi environ dictionary. | ||
|
@@ -115,3 +138,18 @@ def get_header_from_environ( | |
if value: | ||
return [value] | ||
return [] | ||
|
||
|
||
# Put this in a subfunction to not delay the call to the wrapped | ||
# WSGI application (instrumentation should change the application | ||
# behavior as little as possible). | ||
def _end_span_after_iterating(iterable, span, tracer): | ||
try: | ||
with tracer.use_span(span): | ||
for yielded in iterable: | ||
yield yielded | ||
finally: | ||
close = getattr(iterable, "close", None) | ||
if close: | ||
close() | ||
span.end() |
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 logic appears to always attach port 80 in the presence of
http
. Is that intentional?example.com:80
The logic is equivalent to:
port != "443" or environ["wsgi.url_scheme"] == "http" and port != "80"
so all http ports will be reported?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.
Certainly not. I thought I had a unit test against exactly that. Will investigate.