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

Add exception translations to ring integration #136468

Merged
merged 9 commits into from
Feb 4, 2025

Conversation

sdb9696
Copy link
Contributor

@sdb9696 sdb9696 commented Jan 24, 2025

Proposed change

SSIA

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

  • This PR fixes or closes issue: fixes #
  • This PR is related to issue:
  • Link to documentation pull request:

Checklist

  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.

To help with the load of incoming pull requests:

Copy link
Member

@andrewsayre andrewsayre left a comment

Choose a reason for hiding this comment

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

Exception translations are meant to provide a user-friendly message back to the user on why something went wrong. I think you're mixing up the purpose of that with the desire to capture the technical reason that something failed. Log that instead.

You can also raise the HomeAssistantError (and derivatives) from an existing exception, which will capture the stack traceback and message in the logs.

homeassistant/components/ring/coordinator.py Outdated Show resolved Hide resolved
homeassistant/components/ring/coordinator.py Outdated Show resolved Hide resolved
@home-assistant
Copy link

Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍

Learn more about our pull request process.

@home-assistant home-assistant bot marked this pull request as draft January 24, 2025 20:27
@sdb9696
Copy link
Contributor Author

sdb9696 commented Jan 27, 2025

Exception translations are meant to provide a user-friendly message back to the user on why something went wrong. I think you're mixing up the purpose of that with the desire to capture the technical reason that something failed. Log that instead.

You can also raise the HomeAssistantError (and derivatives) from an existing exception, which will capture the stack traceback and message in the logs.

Hey @andrewsayre, have responded on this, let me know your thoughts. I have assumed the point you're making applies to all the exceptions, and not just the auth one in coordinator, but let me know if I've misunderstood that.

@sdb9696 sdb9696 marked this pull request as ready for review January 27, 2025 10:23
@home-assistant home-assistant bot requested a review from andrewsayre January 27, 2025 10:23
"message": "Error communicating with Ring API for device {device} - {func}: {exc}"
},
"sdp_m_line_index_required": {
"message": "The sdp_m_line_index is required for ring webrtc streaming from {device}"
Copy link
Member

Choose a reason for hiding this comment

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

And what does that mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is re-worded to "Error negotiating stream for {device}"

Copy link
Member

Choose a reason for hiding this comment

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

But like, reading this sounds like something was required, but what does that mean and how does someone get this? Like, I am no expert on camera streams, but if the technical reason is that something is required, that sounds like something that can be provided and then work?

Copy link
Contributor

@edenhaus edenhaus Jan 28, 2025

Choose a reason for hiding this comment

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

To give Joost some context, the sdp_m_line_index is the index of the sdp m config in the WebRTC offer.
The frontend will include it, when sending the candidates to HA. The spec defines that the default value is None as so it's possible that None is received

@andrewsayre
Copy link
Member

Hey @andrewsayre, have responded on this, let me know your thoughts. I have assumed the point you're making applies to all the exceptions, and not just the auth one in coordinator, but let me know if I've misunderstood that.

Thanks for your responses, @sdb9696! Yes, that applies to anything that would be user-facing in the UI. 😄 Here's some thoughts on what I'd like to see changed to move this PR forward:

  • You don't have to correct other instances where developer/implementation details are included in user-facing messages (such as in entity.py), but let's not continue that practice here.
  • I bet you can anticipate most of the common failure scenarios and provide a specific translation, which would negate the need to include or log further information. Everything else would be truly exceptional and the translation can be generic, like, "Unexpected error authenticating". To capture the details, log at debug so it will only show up when integration debugging is enabled, which I think is fine to bypass log suppression in the debugging context.
  • If the exceptions raised in the coordinator aren't shown to the user, then it doesn't really matter what it includes, and [probably] a translation doesn't add value. However, one consideration is that those exceptions might be shown in the future, so would be more future-proof to make them user-friendly now.

@joostlek any thing you want to change or add to what I said above? :)

@joostlek
Copy link
Member

To capture the details, log at debug so it will only show up when integration debugging is enabled

Well if something is truly exceptional, I don't expect it to be easy to reproduce, so I would opt to log with LOGGER.exception, so it will just put out the full exception in the logs

If the exceptions raised in the coordinator aren't shown to the user

I believe they are but I don't have any example at hand. But if they didn't they should

@sdb9696
Copy link
Contributor Author

sdb9696 commented Jan 28, 2025

I did some testing and the coordinator error does appear in the UI during setup.

I have updated the PR to exclude the technical details from the exception translations and instead use LOGGER.exception. To prevent the coordinator spamming the logs I moved _call_api to be an instance method of the coordinator so it can check last_update_success.

@edenhaus
Copy link
Contributor

edenhaus commented Jan 28, 2025

I have updated the PR to exclude the technical details from the exception translations and instead use LOGGER.exception.

This will log the error twice, which should be avoided. I saw that most exceptions will be created with from err, so the original error is included in the stacktrace

@sdb9696
Copy link
Contributor Author

sdb9696 commented Jan 28, 2025

There is no stack trace in the log for the base coordinator error. This is the log output from one of the test cases:

2025-01-28 13:36:56.879 ERROR    MainThread homeassistant.components.ring.coordinator:coordinator.py:105 Error calling async_update_devices: 
Traceback (most recent call last):
  File "/home/username/Code/sdb9696/core/homeassistant/components/ring/coordinator.py", line 80, in _call_api
    return await target(*args)
           ^^^^^^^^^^^^^^^^^^^
  File "/home/username/.local/share/uv/python/cpython-3.13.0-linux-x86_64-gnu/lib/python3.13/unittest/mock.py", line 2310, in _execute_mock_call
    raise effect
ring_doorbell.exceptions.RingError: Some internal error info
2025-01-28 13:36:56.879 ERROR    MainThread homeassistant.components.ring.coordinator:update_coordinator.py:405 Error fetching devices data: Error communicating with Ring API
2025-01-28 13:36:56.879 DEBUG    MainThread homeassistant.components.ring.coordinator:update_coordinator.py:453 Finished fetching devices data in 0.000 seconds (success: False)

The error from the base coordinator just contains the translation information.

Copy link
Member

@andrewsayre andrewsayre left a comment

Choose a reason for hiding this comment

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

Great job, @sdb9696! Two smaller things to look at and this is good to go!

homeassistant/components/ring/coordinator.py Outdated Show resolved Hide resolved
Comment on lines 211 to 212
assert log_msg not in caplog.text
assert str(error_type) not in caplog.text
Copy link
Member

Choose a reason for hiding this comment

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

Asserting the negative isn't great because the code could have not ran at all, or the translation strings could have been changed, and this would still pass. 😃

What about checking that the mocked API method was called and the coordinator's last_update_success==True or last_exception is None?

Change in the test below too.

Copy link
Contributor Author

@sdb9696 sdb9696 Jan 28, 2025

Choose a reason for hiding this comment

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

Agree with checking the mocked api method was called. Not sure how to get hold of the coordinator in the test though, never done that before.

Copy link
Member

Choose a reason for hiding this comment

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

You should be able to get to it from mock_config_entry.runtime_data.devices_coordinator. Type the entry to RingConfigEntry to get type hinting.

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 cool will try that out tomorrow, thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Asserting the negative isn't great because the code could have not ran at all, or the translation strings could have been changed, and this would still pass. 😃

What about checking that the mocked API method was called and the coordinator's last_update_success==True or last_exception is None?

Updated both tests to check that the method was called and check the coordinator.last_exception is correctly set. Have still kept the negative log check as checking last_update_success==True doesn't work, because the subsequent updates should not be succeeding for this test.

@home-assistant home-assistant bot marked this pull request as draft January 28, 2025 18:51
@sdb9696 sdb9696 marked this pull request as ready for review January 29, 2025 10:09
@home-assistant home-assistant bot requested a review from andrewsayre January 29, 2025 10:09
@edenhaus
Copy link
Contributor

There is no stack trace in the log for the base coordinator error. This is the log output from one of the test cases:

2025-01-28 13:36:56.879 ERROR    MainThread homeassistant.components.ring.coordinator:coordinator.py:105 Error calling async_update_devices: 
Traceback (most recent call last):
  File "/home/username/Code/sdb9696/core/homeassistant/components/ring/coordinator.py", line 80, in _call_api
    return await target(*args)
           ^^^^^^^^^^^^^^^^^^^
  File "/home/username/.local/share/uv/python/cpython-3.13.0-linux-x86_64-gnu/lib/python3.13/unittest/mock.py", line 2310, in _execute_mock_call
    raise effect
ring_doorbell.exceptions.RingError: Some internal error info
2025-01-28 13:36:56.879 ERROR    MainThread homeassistant.components.ring.coordinator:update_coordinator.py:405 Error fetching devices data: Error communicating with Ring API
2025-01-28 13:36:56.879 DEBUG    MainThread homeassistant.components.ring.coordinator:update_coordinator.py:453 Finished fetching devices data in 0.000 seconds (success: False)

The error from the base coordinator just contains the translation information.

It's currently implemented that on a UpdateFailed Exception, we are only logging the error message. Please see the base implementation of the coordinator to see what is logged on which exception.

except UpdateFailed as err:
self.last_exception = err
if self.last_update_success:
if log_failures:
self.logger.error("Error fetching %s data: %s", self.name, err)
self.last_update_success = False

except Exception as err:
self.last_exception = err
self.last_update_success = False
self.logger.exception("Unexpected error fetching %s data", self.name)

Will put this PR on the agenda of the next core meeting as I don't like to log errors twice. Please keep the PR in draft until I come back with a decision

@edenhaus edenhaus added the second-opinion-wanted Add this label when a reviewer needs a second opinion from another member. label Jan 29, 2025
@edenhaus edenhaus marked this pull request as draft January 29, 2025 10:29
@sdb9696
Copy link
Contributor Author

sdb9696 commented Jan 30, 2025

imo fwiw, I agree that it would be better to only log once and do logger.exception for the UpdateFailed exceptions. This satisfies the requirement highlighted in this PR not to include the exception in the user facing message, but also ensure the logs have useful information. This prevents the need to log twice, and also removes the need to duplicate the log spam suppression logic already present in the "only log once" logic in the update_coordinator.

Copy link
Contributor

@edenhaus edenhaus left a comment

Choose a reason for hiding this comment

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

We discussed this PR internally and concluded the following:

  • the "golden" Rule is log or rethrow. Therefore, we will not accept that the exception is logged before raising. Debug logs are fine if they add additional value/context. Only logging the error on debug level is not required as this will implemented.
  • Known/expected exceptions like AuthenticationError, Timeout should not log any stacktrace
  • I will create a separate PR, where I will update the update coordinator to log stacktrace of UpdateFailed on a debug level. So you have the stacktrace available during debugging.
  • Unknown errors should not be translated and not wrapped in any HA error. Instead they should bubble up, where they than will be logged with the stacktrace

I'm not familiar with the ring lib, so I'm unsure if RingError is a unexpected/unknown error or it that one will be also raised for expected ones.
Please adopt the PR

homeassistant/components/ring/camera.py Outdated Show resolved Hide resolved
homeassistant/components/ring/coordinator.py Outdated Show resolved Hide resolved
homeassistant/components/ring/coordinator.py Outdated Show resolved Hide resolved
homeassistant/components/ring/entity.py Outdated Show resolved Hide resolved
homeassistant/components/ring/entity.py Outdated Show resolved Hide resolved
@edenhaus edenhaus removed the second-opinion-wanted Add this label when a reviewer needs a second opinion from another member. label Jan 30, 2025
@sdb9696
Copy link
Contributor Author

sdb9696 commented Jan 31, 2025

That's all done. RingError is raised from the library for a mix of known and some unknown errors.

@sdb9696 sdb9696 marked this pull request as ready for review January 31, 2025 08:05
@home-assistant home-assistant bot requested a review from edenhaus January 31, 2025 08:05
Copy link
Member

@andrewsayre andrewsayre left a comment

Choose a reason for hiding this comment

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

Thanks for making the changes! Just a few smaller items.

homeassistant/components/ring/coordinator.py Outdated Show resolved Hide resolved
homeassistant/components/ring/strings.json Outdated Show resolved Hide resolved
homeassistant/components/ring/entity.py Outdated Show resolved Hide resolved
@home-assistant home-assistant bot marked this pull request as draft February 3, 2025 18:30
@sdb9696 sdb9696 marked this pull request as ready for review February 4, 2025 08:23
@home-assistant home-assistant bot requested a review from andrewsayre February 4, 2025 08:23
Copy link
Member

@andrewsayre andrewsayre left a comment

Choose a reason for hiding this comment

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

Thank you, looks good! I like how much more straightforward this code became! Nice work!

@andrewsayre andrewsayre dismissed edenhaus’s stale review February 4, 2025 15:14

Requested changes implemented; reviewed and approved by other member.

@andrewsayre andrewsayre merged commit 2f5816c into home-assistant:dev Feb 4, 2025
31 of 32 checks passed
@sdb9696
Copy link
Contributor Author

sdb9696 commented Feb 4, 2025

Thanks @andrewsayre !

@sdb9696 sdb9696 deleted the ring/exception_translations branch February 4, 2025 17:07
@github-actions github-actions bot locked and limited conversation to collaborators Feb 5, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants