-
Notifications
You must be signed in to change notification settings - Fork 1
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
Removing log_and_raise
#158
Conversation
for more information, see https://pre-commit.ci
…ialCollegeLondon/virtual_rainforest into feature/kill_log_and_raise
Codecov Report
@@ Coverage Diff @@
## develop #158 +/- ##
===========================================
+ Coverage 94.95% 95.20% +0.24%
===========================================
Files 14 14
Lines 714 771 +57
===========================================
+ Hits 678 734 +56
- Misses 36 37 +1
📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
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.
LGTM! Had a couple of pretty minor comments but generally looks good
|
||
# Get the appropriate Validator class and then use it to update the data | ||
# array | ||
this_validator = validator_found[0] | ||
try: | ||
value = this_validator().run_validation(value, grid, **kwargs) | ||
except Exception as excep: | ||
log_and_raise(str(excep), excep.__class__) | ||
LOGGER.critical(excep) | ||
raise |
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 something specific be raised here (it seems to be everywhere else)?
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.
It automatically raises the except
'd exception.
LOGGER.critical(excep) | ||
raise | ||
|
||
#. A ``try`` block results in an exception and we want to change the exception type: |
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.
The below example doesn't actually change the exception type
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.
Huh. Good point!
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 is def a cleaner approach than the log_and_raise
business that came before and this PR could totally be merged as is. I do have some broader suggestions about how error handling is being done in VR though, which could be dealt with at another point.
Firstly, you're using the critical
log level everywhere when it should probably be error
. Critical errors are generally (I think) unrecoverable errors, but if you're re-raising an exception then it's not really unrecoverable by definition, because the caller could catch it and continue execution of the program. You could have places where you use the critical log level, but these really should be higher-level errors (e.g. "failed to load data files") at a higher level in the call stack, rather than lower-level stuff (e.g. "data file X is missing required property Y"), and it should really be the last thing you do before you terminate the program. It's fine to just call sys.exit(1)
if your error really is unrecoverable.
Secondly, while it's good that you've removed the log_and_raise
function, I think the general approach of "log an exception then (re-)raise it" isn't the best way to handle some of the errors you're dealing with. Let's take the validate_array
function, for example. There are three places where you raise errors in this function, logging the errors before doing so. validate_array
is currently only called in Data.__setitem__
. To my mind, if you are going to catch and re-raise errors for this (though I'm not sure that it's necessary), it would make more sense to do it there rather than inside validate_array
, e.g.:
try:
# Validate and store the data array
value, valid_dict = validate_dataarray(value=value, grid=self.grid)
self.data[key] = value
self.variable_validation[key] = valid_dict
except Exception as ex:
LOGGER.error(f"Data validation failed: {str(ex)}")
raise
Note that this also tells gives the user more context as to what the errors are actually about. That said, in this case, personally I would probably raise a new exception here (maybe from ex
), rather than logging the error and then the caller (or the caller's caller) can decide what to do with it.
An alternative approach to explicitly logging and reraising errors everywhere would be to write a custom handler for uncaught exceptions, by overriding sys.excepthook
. That way you'd only have to write this boilerplate error-handling code once, rather than every time any error happens.
In general, I'm not convinced that logging statements need to be put in the code every time an exception is raised anyway. If the exception isn't caught at any point in the call stack, then, yes, it should definitely go in your program log, but there are cleaner ways of achieving this than by having logging statements accompanying every time an error is raised (remember: DRY).* On the other hand, if an exception is caught, that kind of implies that the caller was expecting it, in which case, reporting the "error" isn't likely to be helpful anyway. Note that I'm not saying logging isn't important! For validate_dataarray
for example, I would imagine that if something goes wrong in there then the program will exit 100% of the time, in which case all of those errors would percolate all of the way up the call stack and be logged anyway. The extra effort of explicitly logging these errors where they are raised is unnecessary and clutters things.
* One way is overriding sys.excepthook
, as I suggested, but another way is to just wrap the entry point to your program in try/except
block and do any extra handling you want to there.
virtual_rainforest/entry_points.py
Outdated
else: | ||
raise SystemExit | ||
raise excep |
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 know this isn't the point of the PR, but this bit looks hacky to me.
For one thing, excep.code
isn't ever going to be 0. That would indicate success, but the function will only call sys.exit()
on error!
And it looks like parse_args()
will print usage information anyway: https://docs.python.org/3/library/argparse.html#prog
(If the information you want isn't being displayed in case of error, it might be that you haven't supplied all the relevant options to ArgumentParser
.)
Finally, if you really want to catch errors here and do something other than the default action then there is an option for it (which is cleaner than trapping calls to sys.exit
): https://docs.python.org/3/library/argparse.html#exit-on-error
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.
@jacobcook1995 Have to admit that I scratched my head a bit here and then did the cosmetic change. Thoughts?
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.
Weirdly, parse_args
does actually raise SystemExit
with an excep.code
of 0, so the except block does actually print stuff.
Do agree that the information being printed is redundant though, think when I wrote this I was concerned with making sure that all information was going into LOGGER
, but now realise that probably isn't a sensible concern. I think it makes sense to get rid of the whole try/except block here, as it's very hacky and doesn't print anything of use
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.
Have pushed a commit removing the try/except block here
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.
That does look cleaner 😁
@@ -210,6 +210,7 @@ def validate_dataarray( | |||
""" | |||
|
|||
validation_dict: dict[str, Optional[str]] = {} | |||
to_raise: Exception |
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 there a reason you need to declare this here?
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.
If I don't then mypy
objects to me re-using to_raise
because it has then acquired type ValueError
. And I preferred this to to_raise_2
😄
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.
Ohh I see. That makes sense!
Lots of stuff to think on there, @alexdewar. I think you're right about where we log and trap things and as you've noted elsewhere, our logging might be getting rather verbose. I think we want verbose logging some of the time, and we probably should be using I think we might want a bit more actually going on in the code before reviewing this, so I think we will merge but we should convert this comment to a discussion and start to flesh out a docs page on this. One thing I had in my head wrong is the
But no - it turns out |
Yeah, that sounds sensible. I agree with the docs re the As I said, I would probably raise a new, custom exception type here (e.g. |
Description
This PR removes the
logger.log_and_raise
function and replaces all instances of its use with explicitLOGGER.critical()
calls and then Exception raises (or re-raises in the case oftry: except:
blocks).I've also updated a couple of function/method docstrings that were missing
Raises:
sections and updated the docstrings forcore.logger
to describe expected LOGGER practice when Exceptions occur.Fixes #125
Type of change
Key checklist
pre-commit
checks:$ pre-commit run -a
$ poetry run pytest
Further checks