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
Enable stderr logging if JUJU_DEBUG is set. #209
Conversation
When running under 'juju debug-hooks' JUJU_DEBUG is set, which we can use to detect that we should also print to stderr, so that people interactively running the charm can see what is going on. It also changes the default log level to DEBUG (though juju-log is still at INFO).
I feel like it might be a little odd to have lines starting with whitespace in stderr, but I do really like aligned content, I personally find it a lot easier to read. We could left align and have
Or we could just say that messages at a similar level will likely be emitted together (and so would naturally be aligned). The one caveat in my experience is INFO and DEBUG tend to alternate quite frequently. |
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 work! I added a couple of inline comments, some suggestions and a couple of change requests.
Thanks!!
test/test_log.py
Outdated
@@ -80,6 +86,32 @@ def test_handler_filtering(self): | |||
logger.warning('bar') | |||
self.assertEqual(self.backend.calls(), [('WARNING', 'bar')]) | |||
|
|||
def test_debug_logging(self): | |||
buffer = io.StringIO() | |||
ops.log.setup_root_logging(self.backend, debug=True, debug_stream=buffer) |
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.
Check test_infra.py
, l.43, for an easy way to test if something went to stdout/stderr.
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.
As a general guideline I'm much more in favor of code and tests that do dependency injection rather than patching a global object. (You can never run the latter in parallel, and if you ever make a mistake cleaning up you break other tests that are hard to debug.)
I can happily pass sys.stderr from where this is called if you feel it should be exercised live.
we could actually do it differently where we set debug_stream only when debug is true, and then take out the extra argument. So something like:
debug_stream = None
if 'JUJU_DEBUG' in os.environ:
debug_stream = sys.stderr
ops.log.setup_root_logging(self.backend, debug_stream=debug_stream)
That makes it an active parameter, and makes it easier to test.
This makes it easier to test, and contains the same amount of logic.
I feel like the previous version of the branch was more clear: if the envvar is set, the logging setup is indicated to go to debug mode, which means two decisions that belongs to the logging setup:
Now it feels mixed, it looks like which stream is decided outside the logging setup, and that stream is indicated to the logging setup, which when present, not only builds a handler for it (it's fine there) but also changes the level of the logger. Note that the logger can NOT have always the level in DEBUG, as this is what controls the developer; by default it's in INFO, they can switch it if want to produce a more verbose logging. Remember that the default output for the charm needs to be INFO level, and the juju handler will be in DEBUG, see this related issue (you may include a fix for it here, or not, but it needs to be considered for the final behaviour as a whole). |
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 some trivials.. LGTM when you and @facundobatista are happy.
ops/log.py
Outdated
"""Setup python logging to forward messages to juju-log. | ||
|
||
:param model_backend: a ModelBackend to use for juju_log. | ||
:param debug_stream: (optional) If set, debug messages will also be sent to this stream. |
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 "(optional)" and "If set," are redundant.
Is "(optional)" a standard/agreed way to specify that? If so, sounds good, otherwise we should either try to agree on that or drop it and allow the sentence to describe it.
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.
Since you raise the question, Sphinx seems to say that you should put , optional
into the type:
https://sphinx-rtd-tutorial.readthedocs.io/en/latest/docstrings.html
And ', defaults to' into the string description.
ops/main.py
Outdated
@@ -174,7 +174,10 @@ def main(charm_class): | |||
juju_event_name = '{}_action'.format(juju_event_name) | |||
|
|||
model_backend = ops.model.ModelBackend() | |||
setup_root_logging(model_backend) | |||
debug_stream = None | |||
if 'JUJU_DEBUG' in os.environ: |
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 we check its value (can it be both 1 or 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.
it is actually set to a path where the execution for the debug session exist ("/tmp/juju-debug-XXXX/"). So it isn't a simple boolean, it is just a variable I'm sure is only set during debug-hooks or debug-code.
I had been doing:
if os.getenv('JUJU_DEBUG'):
which would cater for JUJU_DEBUG='', though Juju itself would never set it to that.
The handler and where it goes is set by the logging infrastructure. In testing we just patch sys.stderr to read the output.
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.
Great work, thanks!!!
Added a couple of small comments inside.
I'm approving the PR, but PLEASE check the comment in the test section.
Thanks!!!
if debug: | ||
level = logging.DEBUG | ||
else: | ||
level = logging.INFO |
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 fine, but remember you can always write level = logging.DEBUG if debug else logging.INFO
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 do realize that newer python has the Ternary operator, but I don't find that it is actually easier to read. The problem is that (foo if blah else bing) doesn't have great punctuation to clarify what the result is. I suppose the 2 possibilities are at the very beginning or the very end.
@@ -174,7 +174,8 @@ def main(charm_class): | |||
juju_event_name = '{}_action'.format(juju_event_name) | |||
|
|||
model_backend = ops.model.ModelBackend() | |||
setup_root_logging(model_backend) | |||
debug = ('JUJU_DEBUG' in os.environ) |
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 fine if it's more clear to you, but parenthesis surrounding the expression are not really needed.
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 it is, very much, for clarity rather than need.
foo = 'string' more stuff
looks like the result is a string to me.
r"\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d,\d\d\d DEBUG debug message\n") | ||
logger.info('info message') | ||
logger.warning('warning message') | ||
logger.critical('critical message') |
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.
Any patching should be done around as less code as possible (similar to try/except code blocks).
So, the subsequent assertEqual
and assertRegex
should be de-indented.
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 subsequent ones aren't in the with statement. And all the logging needs to happen inside the with statement.
That said, it doesn't mean a lot to assert the debug statement before doing the rest, so I moved all of the logging inside, and have only the final asserts.
Addresses canonical#200 When running under 'juju debug-hooks' JUJU_DEBUG is set, which we can use to detect that we should also print to stderr, so that people interactively running the charm can see what is going on. It also changes the default log level to DEBUG (though juju-log is still at currently at INFO).
When running under 'juju debug-hooks' JUJU_DEBUG is set, which we can
use to detect that we should also print to stderr, so that people
interactively running the charm can see what is going on. It also
changes the default log level to DEBUG (though juju-log is still at
INFO).
This should address #200.
I chose to include the log level, and went with right-aligned, and fixed width so the messages end up left aligned. I don't terribly care, if people prefer a different format, this is all stderr, so it didn't make sense to me to include timestamps, etc. I also considered including the logger name, and/or file/line. Since it is stderr, I didn't want to overwhelm the output, but I'm flexible.