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

Enable stderr logging if JUJU_DEBUG is set. #209

Merged
merged 7 commits into from Apr 13, 2020

Conversation

jameinel
Copy link
Member

@jameinel jameinel commented Apr 2, 2020

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.

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).
@jameinel
Copy link
Member Author

jameinel commented Apr 2, 2020

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

DEBUG   this is the message
WARNING  another message
CRITICAL the most important

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.

Copy link
Contributor

@facundobatista facundobatista left a 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!!

ops/log.py Outdated Show resolved Hide resolved
ops/log.py Outdated Show resolved Hide resolved
ops/main.py Show resolved Hide resolved
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)
Copy link
Contributor

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.

Copy link
Member Author

@jameinel jameinel Apr 4, 2020

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.
@facundobatista
Copy link
Contributor

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:

  • logger in debug level
  • also a handler to stderr

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

Copy link
Collaborator

@niemeyer niemeyer left a 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.
Copy link
Collaborator

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.

Copy link
Member Author

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:
Copy link
Collaborator

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

Copy link
Member Author

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.
Copy link
Contributor

@facundobatista facundobatista left a 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
Copy link
Contributor

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

Copy link
Member Author

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)
Copy link
Contributor

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.

Copy link
Member Author

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')
Copy link
Contributor

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.

Copy link
Member Author

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.

@jameinel jameinel merged commit ac86de8 into canonical:master Apr 13, 2020
VariableDeclared pushed a commit to VariableDeclared/operator that referenced this pull request Apr 15, 2020
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants