-
Notifications
You must be signed in to change notification settings - Fork 672
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 properly on end #358
Conversation
@condorcet, please give this a try. |
Codecov Report
@@ Coverage Diff @@
## master #358 +/- ##
==========================================
+ Coverage 84.82% 84.96% +0.14%
==========================================
Files 38 38
Lines 1845 1889 +44
Branches 217 217
==========================================
+ Hits 1565 1605 +40
- Misses 214 219 +5
+ Partials 66 65 -1
Continue to review full report at Codecov.
|
self.end_time = end_time if end_time is not None else time_ns() | ||
|
||
if has_ended: |
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.
should the has_ended
check be moved up as well?
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 think it was outside of the lock originally, I'll look into this.
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 have noticed a few unusual things regarding the things we have in self._lock
(like this). I suggest we handle them in a separate PR.
It's not directly addressing to the PR but I think it's better to consider these improvements:
So we wouldn't spam in stdout about every log, but it can show unexpected warnings in code that mustn't produce warnings, critical, etc. |
Yes! this is a great idea. |
@@ -259,14 +259,15 @@ def end(self, end_time: Optional[int] = None) -> None: | |||
raise RuntimeError("Calling end() on a not started span.") |
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.
a random aside, but there's a lot of stuff in this lock that we can probably move out (e.g. the check to see if we're recording traces).
Not sure what sort of contention we'll see here, but keeping the locks tight is a good thing.
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.
Nice!
@@ -259,14 +259,15 @@ def end(self, end_time: Optional[int] = None) -> None: | |||
raise RuntimeError("Calling end() on a not started span.") | |||
has_ended = self.end_time is not None | |||
if not has_ended: | |||
if self.status is None: | |||
self.status = Status(canonical_code=StatusCanonicalCode.OK) |
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.
is it possible for a status to be none, even if an end_time exists?
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 think we should not permit that. I think that self.end_time
is a fine indicator that the span has ended (we might want to have a more clear attribute, like self.is_ended
). The idea here is that every ended span should have a status. I'll look into this to make sure that I am not leaving a loophole where that situation (having an ended span with None
status) can happen.
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.
Ok, end_time
is part of our public API. What we don't want is a user setting this manually to some value while leaving the span status being None
in an unended weird state. I'll move this into a property
so that it can only be set by calling end
(same thing with start_time
). I'll move this into another PR.
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 am addressing this issue in #363.
Ok, good, @toumorokoshi and @codeboten have raised important issues related to these changes. I think is better to move them into another PRs to fix this issue now (I say it is pretty urgent since this is a bug, the status is not being set) and to no longer block @condorcet. I have added also @condorcet's logging enhancements in our tests. |
@@ -545,7 +549,29 @@ def test_span_override_start_and_end_time(self): | |||
def test_ended_span(self): | |||
""""Events, attributes are not allowed after span is ended""" | |||
|
|||
with self.tracer.start_as_current_span("root") as root: | |||
class ContextWrapper: |
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.
@ocelotl Wow! Cool, but looks complicated :) I think this test can be simplified: we can wrap code with assertLogs
only once and check how many logs we have at the end. Something like this:
def test_ended_span(self):
""""Events, attributes are not allowed after span is ended"""
with self.assertLogs(level=WARNING) as _logs:
with self.tracer.start_as_current_span("root") as root:
...
self.assertEqual(len(_logs.records), 6)
Further if we really need we can check every log in records. But now counting logs is enough in my opinion.
What do you think?
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 say we leave it as it is 🙂 . It is more precise to handle each warning individually and even as it is a bit complicated right now, it serves its purpose well.
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.
Wouldn't it be possible to add an independent test just for it avoiding an ad-hoc class?
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.
Maybe, but how? 🤔 The class introduced here is just there to help catch a specific warning without affecting the catching of other warnings. I'm not sure how an independent test can help with this. It would be great if we can come up with a simpler implementation that can handle each warning independently and specifically, but I have no other ideas on how to achieve this 🤷♂️.
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.
Based on @condorcet's idea, you could create a specific test case for it:
def test_warning_end_span(self):
with self.assertLogs(level=WARNING) as _logs:
with self.tracer.start_as_current_span("root") as root:
root.end()
self.assertEqual(len(_logs.records), 1)
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 have a second thought and I think this test can be removed, it's testing if there is a warning when the context goes out, so it calls the span.end()
, we already have a test to check if the context calls span.end()
. The test if span.end()
prints a warning is already present on this PR.
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.
Another way is just using start_span
manually instead of wrapping code by start_as_current_span
context manager that spam with warning when try to end()
span. For testing purpose It will be enough.
Something like this:
def test_ended_span(self):
""""Events, attributes are not allowed after span is ended"""
root = self.tracer.start_span("root")
# everything should be empty at the beginning
self.assertEqual(len(root.attributes), 0)
...
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.
Ok, refactored, please let me know what you think now @condorcet, @mauriciovasquezbernal. 👍
Well, this PR is actually just fixing #357. The changes to introduced by this PR to fix that issue are very small, just 5 lines here and here. The rest of the changes are mostly in one test suite. I can't split the fix from the test suite changes because the test suite changes are needed for them to pass again. |
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.
Just a comment about a test that I think should be removed, otherwise LGTM.
self.assertEqual(len(root.attributes), 0) | ||
self.assertEqual(len(root.events), 0) | ||
self.assertEqual(len(root.links), 0) | ||
# Test that end() is called when the context manager is exited |
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 think this test is duplicated.
Test that a span is ended by the context manager is done here
self.assertIsNotNone(root.end_time) |
Test that
ends()
prints a warning when called twice is handled in this PR.
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.
Ok, I see what you mean, updated.
Great, thanks! |
Based on the changes of open-telemetry#358 Signed-off-by: Daniel González Lopes <danielgonzalezlopes@gmail.com>
Fixes #357