-
Notifications
You must be signed in to change notification settings - Fork 151
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
Use logging instead of print #142
Conversation
Codecov Report
@@ Coverage Diff @@
## master #142 +/- ##
==========================================
- Coverage 72.40% 71.90% -0.50%
==========================================
Files 98 98
Lines 7809 7828 +19
==========================================
- Hits 5654 5629 -25
- Misses 2155 2199 +44
Continue to review full report at Codecov.
|
@@ -21,6 +21,10 @@ | |||
from . import verification as vf | |||
from . import visualization as plt | |||
|
|||
logging.getLogger(__name__).addHandler(logging.NullHandler()) |
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 we can define the default handler set at INFO level to be equivalent to the print statement.
That is, no additional formatting. _logger.info(msg) equivalent to print(msg)
That will reduce the verbosity of the outputs.
We can add additional information (like time, level, etc) for the DEBUG level.
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 agree with @aperezhortal on this, we should try to mimic the current behaviour.
As a rule of thumb, we could also say that we use the INFO level for all print statements that are now unconditionally executed, while DEBUG can be used for those print statements that are currently conditional to a verbose argument or similar (e.g. the measure_time argument in the nowcast.steps() method).
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'm changing DEBUG
to INFO
in the next commit, thanks for the clarifications.
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.
Hi @fox91, thanks for the contribution! I really like the addition of the logging module.
I made a few comments about the changes implemented.
About the logging levels, DEBUG can be used to give information about details that are of interest for diagnosing purposes. While INFO to display information about the operation of a program (only high-level things that shows that the program operates normally).
Finally, what do you think if we can start with minimal formatting (especially for INFO levels):
[LEVEL] message
or just
message
@@ -21,6 +21,10 @@ | |||
from . import verification as vf | |||
from . import visualization as plt | |||
|
|||
logging.getLogger(__name__).addHandler(logging.NullHandler()) | |||
|
|||
_LOGGER = logging.getLogger(__name__) |
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.
_LOGGER should be lowercase ("_logger"). Uppercase variables normally used for constants.
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'm following pylint
suggestions but I'm probably wrong.
I'm changing to lowercase with the next commit.
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.
Pylint expects that global variables should be uppercase. However, we try to follow a different naming convention.
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 this can be another issue but: why not implement a pylintrc
file in the root of the project, add to travis and enforce all code to use it?
You mention Google’s python style guide: it give you a good start point with pylintrc.
@@ -149,8 +153,7 @@ def __getitem__(self, key): | |||
rcparams = json.loads(jsmin(f.read())) | |||
|
|||
if not rcparams.get("silent_import", False): | |||
print("Pysteps configuration file found at: " + params_file | |||
+ "\n") | |||
_LOGGER.debug("Pysteps configuration file found at: %s", params_file) |
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.
Here the logic could be:
If silent_import is True:
_logger.debug()
else:
_logger.info() # we want the behavior to be equivalent to print. This is important for runtime information.
If silent_import is True, the
@@ -79,7 +81,7 @@ def forecast(precip, velocity, num_timesteps, | |||
|
|||
if measure_time: | |||
computation_time = time.time() - start_time | |||
print(f"{computation_time:.2f} seconds.") | |||
_LOGGER.debug("%.2f seconds.", computation_time) |
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.
Better use INFO level 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.
Or we could remove the measure_time argument and log the computation time at DEBUG level, 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.
That's a good point. But we should leave the keyword argument and add a deprecation warning to maintain backward compatibility (at least for a few releases).
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.
Agreed ;-)
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.
So a warnings.warn
with PendingDeprecationWarning
for one versione, DeprecationWarning
for the next versione and total remove with the third version?
Or is best to use a DeprecationWarning
for the next two version and then remove?
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.
Good point. According to PEP-565:
PendingDeprecationWarning
is hidden by default for all code.DeprecationWarning
is reported by default for code that runs directly in the__main__
module (as such code is considered relatively unlikely to have a dedicated test suite), but hidden by default for code in other modules.FutureWarning
is reported by default for all code. The intended audience is users of applications written in Python, rather than other Python developers
So, since we want to target the users, it seems that FutureWarning is the way to go.
@@ -346,7 +349,7 @@ def forecast( | |||
} | |||
) | |||
|
|||
print("Estimating nowcast parameters...", end="") | |||
_LOGGER.debug("Estimating nowcast parameters...") |
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.
INFO level. This is indicating that the program is running correctly.
@@ -516,9 +519,9 @@ def estimator(R, parsglob=None, idxm=None, idxn=None): | |||
pars = None | |||
|
|||
if measure_time: | |||
print("%.2f seconds." % (time.time() - starttime)) | |||
_LOGGER.debug("%.2f seconds.", time.time() - starttime) |
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.
INFO level. This is indicating that the program is running correctly.
@@ -8,187 +8,215 @@ | |||
|
|||
""" |
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 is a good idea to leave the examples untouched before merging the PR. In that way they show the default behavior of the library.
But this was very useful to see how the logging module worked. 👍 Perhaps, we can add a new short example showing how the logging module can be used.
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 also support the idea of a dedicated example script showing how to use the logging module.
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.
Using the logging module instead of print you don't see any output in the console if you don't add the proper config inside the example.
I can revert the improvements (ex: main
function) but at minimum I need to add the logging.basicConfig
at the top of the examples.
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.
We can define a default configuration in the pysteps.__init__.py
that prints the INFO messages by default and add a set_loglevel function to pysteps as in matplotlib.
This is an example using matplotlib's logging implementation that logs the info messages by default. I was thinking of something along those lines.
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.
About that there is an open issue: matplotlib/matplotlib#13264 and a PR: matplotlib/matplotlib#13275
Personally I dislike and I'm not sure it's a good way to do the work.
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 not found any other library that uses logging
like matplotlib.
It's also true that I don't find any library that uses so many output messages like pySTEPS to be able to compare the code and evaluate how they implement the output during a normal run.
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.
Although it is not the same as matplotlib, TensorFlow seems to implement a default handler and an interface to control the logging level.
import logging
logger = tf.get_logger()
logger.setLevel(logging.ERROR)
# sphinx_gallery_thumbnail_number = 5 | ||
|
||
|
||
if __name__ == '__main__': |
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.
Avoid using if name == 'main' if possible. The examples should run sequentially as a script.
This will be useful in the future if we use jupyter notebooks for the examples.
|
||
def print_ar_params(PHI): | ||
def log_ar_params(PHI): |
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 in case, in addition to the log_ar_params function, leave the print_ar_params for backward compatibility. We can inform to the users with a Deprecation Warning that this function will be removed in a future release.
|
||
|
||
def print_corrcoefs(GAMMA): | ||
def log_corrcoefs(GAMMA): |
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 in case, in addition to the log_corrcoefs function, leave the print_corrcoefs for backward compatibility. We can inform to the users with a Deprecation Warning that this function will be removed in a future release.
|
||
# sphinx_gallery_thumbnail_number = 5 | ||
logging.getLogger('matplotlib').setLevel(logging.WARNING) |
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 out of curiosity, do you know what happens if you remove this line? Does matplotlib clutters the output with debug messages?
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.
In this specific example about a ten debug lines only related to internal matplotlib. Not too many but useless, so I preferred to hide them.
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 also really happy about the implementation of the logging module in pysteps, thank you @fox91 for this really nice contribution!
I don't have much to add on top of what @aperezhortal already mentioned. I would say that
now we mainly need to find out the right setup that we can use for all modules.
Hi @fox91, do you plan to work on this PR again ? I think it would make an excellent contribution to pysteps! |
Hi @dnerini, |
Very glad to hear that you'll be able to continue on this, @fox91, thanks a lot! |
Im eventually closing this PR as stale, but the linked issue (#140) has been included to the pystepsV2 project. |
This is the PR with the
logging
module implemented in thepysteps.nowcasts
module.if it seems okay I can proceed with the rest of the modules.
Fix #140 when is completed.