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 properly on end #358

Merged
merged 11 commits into from
Jan 20, 2020
Merged

Conversation

ocelotl
Copy link
Contributor

@ocelotl ocelotl commented Jan 9, 2020

Fixes #357

@ocelotl ocelotl requested a review from a team as a code owner January 9, 2020 00:33
@ocelotl ocelotl self-assigned this Jan 9, 2020
@ocelotl ocelotl added the sdk Affects the SDK package. label Jan 9, 2020
@ocelotl
Copy link
Contributor Author

ocelotl commented Jan 9, 2020

@condorcet, please give this a try.

@codecov-io
Copy link

codecov-io commented Jan 9, 2020

Codecov Report

Merging #358 into master will increase coverage by 0.14%.
The diff coverage is 100%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
...emetry-sdk/src/opentelemetry/sdk/trace/__init__.py 90.12% <100%> (+0.41%) ⬆️
...ntelemetry-api/src/opentelemetry/trace/__init__.py 84.56% <0%> (+0.44%) ⬆️
...elemetry-api/src/opentelemetry/metrics/__init__.py 87.93% <0%> (+1.93%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f0ba817...eefcaa3. Read the comment docs.

self.end_time = end_time if end_time is not None else time_ns()

if has_ended:
Copy link
Contributor

@codeboten codeboten Jan 9, 2020

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

@condorcet
Copy link

It's not directly addressing to the PR but I think it's better to consider these improvements:

  1. Enable logging for tests. For example we can add
[pytest]
log_cli = true
log_cli_level = warning
  1. Wrap tests when we expect logs with something like this
...
with self.assertLogs(...):
    # call it a second time
    root.end()
    ...

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.

@ocelotl
Copy link
Contributor Author

ocelotl commented Jan 9, 2020

It's not directly addressing to the PR but I think it's better to consider these improvements:

  1. Enable logging for tests. For example we can add
[pytest]
log_cli = true
log_cli_level = warning
  1. Wrap tests when we expect logs with something like this
...
with self.assertLogs(...):
    # call it a second time
    root.end()
    ...

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.")
Copy link
Member

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.

Copy link
Member

@toumorokoshi toumorokoshi left a 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)
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

@ocelotl
Copy link
Contributor Author

ocelotl commented Jan 9, 2020

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:

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?

Copy link
Contributor Author

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.

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?

Copy link
Contributor Author

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 🤷‍♂️.

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)

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.

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)
        ...

Copy link
Contributor Author

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. 👍

@mauriciovasquezbernal
Copy link
Member

I think this PR is doing more than simply fixing #357, @ocelotl would you mind adding a description so it is easier to review?
Btw, given that there is some discussion going around this PR, maybe you want to split and provide a quick fix for #357 and then do other changes in a different PR.

@ocelotl
Copy link
Contributor Author

ocelotl commented Jan 14, 2020

I think this PR is doing more than simply fixing #357, @ocelotl would you mind adding a description so it is easier to review?
Btw, given that there is some discussion going around this PR, maybe you want to split and provide a quick fix for #357 and then do other changes in a different PR.

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.

Copy link
Member

@mauriciovasquezbernal mauriciovasquezbernal left a 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

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.

Copy link
Contributor Author

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.

@toumorokoshi toumorokoshi merged commit 4f71fba into open-telemetry:master Jan 20, 2020
@toumorokoshi
Copy link
Member

Great, thanks!

dgzlopes added a commit to dgzlopes/opentelemetry-python that referenced this pull request Jan 22, 2020
Based on the changes of open-telemetry#358

Signed-off-by: Daniel González Lopes <danielgonzalezlopes@gmail.com>
srikanthccv pushed a commit to srikanthccv/opentelemetry-python that referenced this pull request Nov 1, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
sdk Affects the SDK package.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Warning message Calling set_status() on an ended span in examples and tests
6 participants