Skip to content
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

Set status code on ASGI server span #478

Merged
merged 3 commits into from
May 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased](https://github.com/open-telemetry/opentelemetry-python/compare/v1.2.0-0.21b0...HEAD)

### Changed
- `opentelemetry-instrumentation-asgi` Set the response status code on the server span
([#478](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/478))
- Fixed cases where description was used with non-error status code when creating Status objects.
([#504](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/504))

Expand All @@ -15,8 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#458](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/458))

## [0.21b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.2.0-0.21b0) - 2021-05-11
### Changed

### Changed
- `opentelemetry-propagator-ot-trace` Use `TraceFlags` object in `extract`
([#472](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/472))
- Set the `traced_request_attrs` of FalconInstrumentor by an argument correctly.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,10 @@ async def wrapped_send(message):
if send_span.is_recording():
if message["type"] == "http.response.start":
status_code = message["status"]
set_status_code(span, status_code)
set_status_code(send_span, status_code)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does send here mean sending a response back to the client?
Can the op represented by the outer span have multiple sends or receives?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know, the callable will be called once per connection, which is one request for HTTP. That should mean only one http.response.start event. Happy to make a change if that is wrong.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe multiple calls to receive and send can occur per HTTP call like from this issue. Should the parent span then reflect ANY error that occurs during the flow? If this is the case, we probably need the response code logic in the receive as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there are multiple send events, but the second one is the response body, and that event does not have another status code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@owais
As discussed in the SIG meeting on 05/13/2021, we were wondering why there is a parent span for the channel in the first place? Would it be possible to remove this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll do a deep dive into ASGI and get back to this. As far as I understand right now, I think the receive span should be the SERVER span and "send" span should be it's child or grandchild. Both of these spans should use links to establish a relation with the channel span. I need some time to dig into this and confirm if my understanding is correct.

elif message["type"] == "websocket.send":
set_status_code(span, 200)
set_status_code(send_span, 200)
send_span.set_attribute("type", message["type"])
await send(message)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ def validate_outputs(self, outputs, error=None, modifiers=None):
SpanAttributes.HTTP_URL: "http://127.0.0.1/",
SpanAttributes.NET_PEER_IP: "127.0.0.1",
SpanAttributes.NET_PEER_PORT: 32767,
SpanAttributes.HTTP_STATUS_CODE: 200,
},
},
]
Expand Down Expand Up @@ -303,15 +304,57 @@ def test_websocket(self):
span_list = self.memory_exporter.get_finished_spans()
self.assertEqual(len(span_list), 6)
expected = [
"/ asgi.websocket.receive",
"/ asgi.websocket.send",
"/ asgi.websocket.receive",
"/ asgi.websocket.send",
"/ asgi.websocket.receive",
"/ asgi",
{
"name": "/ asgi.websocket.receive",
"kind": trace_api.SpanKind.INTERNAL,
"attributes": {"type": "websocket.connect"},
},
{
"name": "/ asgi.websocket.send",
"kind": trace_api.SpanKind.INTERNAL,
"attributes": {"type": "websocket.accept"},
},
{
"name": "/ asgi.websocket.receive",
"kind": trace_api.SpanKind.INTERNAL,
"attributes": {
"type": "websocket.receive",
SpanAttributes.HTTP_STATUS_CODE: 200,
},
},
{
"name": "/ asgi.websocket.send",
"kind": trace_api.SpanKind.INTERNAL,
"attributes": {
"type": "websocket.send",
SpanAttributes.HTTP_STATUS_CODE: 200,
},
},
{
"name": "/ asgi.websocket.receive",
"kind": trace_api.SpanKind.INTERNAL,
"attributes": {"type": "websocket.disconnect"},
},
{
"name": "/ asgi",
"kind": trace_api.SpanKind.SERVER,
"attributes": {
SpanAttributes.HTTP_SCHEME: self.scope["scheme"],
SpanAttributes.NET_HOST_PORT: self.scope["server"][1],
SpanAttributes.HTTP_HOST: self.scope["server"][0],
SpanAttributes.HTTP_FLAVOR: self.scope["http_version"],
SpanAttributes.HTTP_TARGET: self.scope["path"],
SpanAttributes.HTTP_URL: f'{self.scope["scheme"]}://{self.scope["server"][0]}{self.scope["path"]}',
SpanAttributes.NET_PEER_IP: self.scope["client"][0],
SpanAttributes.NET_PEER_PORT: self.scope["client"][1],
SpanAttributes.HTTP_STATUS_CODE: 200,
},
},
]
actual = [span.name for span in span_list]
self.assertListEqual(actual, expected)
for span, expected in zip(span_list, expected):
self.assertEqual(span.name, expected["name"])
self.assertEqual(span.kind, expected["kind"])
self.assertDictEqual(dict(span.attributes), expected["attributes"])

def test_lifespan(self):
self.scope["type"] = "lifespan"
Expand Down