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

DM-27351: --long-log does not work with butler command line command #408

Merged
merged 6 commits into from Nov 4, 2020

Conversation

n8pease
Copy link
Contributor

@n8pease n8pease commented Oct 28, 2020

No description provided.

normalLogFmt = "%c %p: %m%n"
lsstLog_longLogFmt = "%-5p %d{yyyy-MM-ddTHH:mm:ss.SSSZ} %c (%X{LABEL})(%F:%L)- %m%n"
lsstLog_normalLogFmt = "%c %p: %m%n"
pylog_longLogFmt = "%(asctime)s %(message)s"
Copy link
Contributor

@ktlim ktlim Oct 28, 2020

Choose a reason for hiding this comment

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

You don't think it's a good idea to emulate the lsst.log format and include things like %(levelname) and %(name) or even %(filename) and %(lineno)?
asctime is in local time by default. We should either append timezone information (possibly by changing logging.Formatter.default_time_format) or use GMT/UTC consistently (possibly by changing logging.Formatter.converter). https://developer.lsst.io/stack/logging.html#logging

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

Copy link
Contributor

Choose a reason for hiding this comment

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

If you do use gmtime() to use GMT, you should make sure to include the "Z" timezone specifier in the format; it's more important that the timezone be clear than that any particular timezone be chosen.

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 seems to work if it seems ok to you(?)

pylog_longLogFmt = "%(asctime)s %(message)s"
...
            logging.basicConfig(level=logging.INFO,
                                format=cls.pylog_longLogFmt,
                                datefmt="%Y-%m-%dT%H:%M:%S%z")

example output:

2020-10-28T22:07:49-0700 Opening YAML config file: resource://lsst.daf.butler/configs/registry.yaml
2020-10-28T22:07:49-0700 Opening YAML config file: resource://lsst.daf.butler/configs/storageClasses.yaml
2020-10-28T22:07:49-0700 Opening YAML config file: resource://lsst.daf.butler/configs/datastore.yaml

The only difference between this and the lsst.log output is that the time does not include milliseconds. As far as I can tell the time format string does not offer a milliseconds or microseconds option.

lsst.log example output

2020-10-28T22:05:43-0700 Opening YAML config file: resource://lsst.daf.butler/configs/registry.yaml
2020-10-28T22:05:43-0700 Opening YAML config file: resource://lsst.daf.butler/configs/storageClasses.yaml

Copy link
Contributor

Choose a reason for hiding this comment

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

That looks fine. My understanding is that the logging Formatter is what adds on milliseconds; we can probably do without it.

@@ -92,7 +94,8 @@ def initLog(cls, longlog):
pythonLogger.addHandler(cls._lsstLogHandler)
else:
cls._recordComponentSetting(None)
logging.basicConfig(level=logging.INFO)
logging.basicConfig(level=logging.INFO,
**(dict(format=cls.pylog_longLogFmt if longlog else dict())))
Copy link
Contributor

Choose a reason for hiding this comment

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

The alternative with an if/else statement is only two lines longer and may be clearer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ktlim I'm not sure I really like any of the ways I've tried optionally passing a variable (or not). Can you show me the code you're thinking of?

Copy link
Contributor

@ktlim ktlim Oct 29, 2020

Choose a reason for hiding this comment

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

Something simple like this:

if longlog:
    logging.basicConfig(level=logging.INFO, format=cls.pylog_longLogFmt)
else:
    logging.basicConfig(level=logging.INFO)

"""Verify the timestamp is in the log messages when the --long-log
flag is set."""
with self.runner.isolated_filesystem():
# The click test does not capture logging emitted from lsst.log
Copy link
Member

Choose a reason for hiding this comment

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

Butler tests should never be issuing lsst.log log messages so lsstLog is only ever true if someone runs these tests after they've setup some other LSST package. Neither Jenkins nor GitHub Actions will ever have lsst.log available. I'm now a bit worried about these tests -- no code inside butler should ever be issuing an lsst.log log message so how do we ever see the change? Does python logging change in click subcommands based on lsst.log existing?

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 test is run by test_cliClog.py in daf_butler as well as in test_cliLog.py in obs_base, so both variants are tested.

Copy link
Member

Choose a reason for hiding this comment

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

Can you help my future self by adding a comment here that says that this check is specifically because subclasses can enable lsst.log even if daf_butler does not use it?

@n8pease n8pease force-pushed the tickets/DM-27351 branch 6 times, most recently from 8c32999 to 31556ef Compare October 30, 2020 16:04
Copy link
Member

@timj timj left a comment

Choose a reason for hiding this comment

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

Looks fine. My main comment is my continual surprise that we always forward python logging to lsst.log but I can see that we have no choice.

# click.MultiCommand instead of the context.
# https://github.com/click-contrib/sphinx-click/issues/70
CliLog.initLog(longlog=False)
logging.debug("The passed-in context was not a click.Context, could not determine longlog or log"
Copy link
Member

Choose a reason for hiding this comment

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

maybe say long-log and log-level here to match the command line names?

else:
# This works around a bug in sphinx-click, where it passes in the
# click.MultiCommand instead of the context.
# https://github.com/click-contrib/sphinx-click/issues/70
Copy link
Member

Choose a reason for hiding this comment

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

If the fix is as easy as you imply on that issue, please file a PR. You'll probably get more traction with a PR than an issue.

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, I'll work on this.

is initialized with longlog=False."""

pylog_longLogFmt = "%(levelname)s %(asctime)s %(name)s %(filename)s:%(lineno)s - %(message)s"
"""The log format used when the lsst.log package is not importable and the
Copy link
Member

Choose a reason for hiding this comment

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

This is quite hard to understand without the hidden context below. Naively you would think that python logging should be configured regardless of lsst.log. Somehow we need to make it clearer that we actually bypass python logging if lsst.log is available -- not simply that we decide to use different log formats.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will add to the class docstring about this.

@@ -92,7 +110,12 @@ def initLog(cls, longlog):
pythonLogger.addHandler(cls._lsstLogHandler)
Copy link
Member

Choose a reason for hiding this comment

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

This line is why you have so much trouble in your tests capturing log messages. We need to make it much more explicit in documentation (at least for this function) that this is going on. It is there but a sphinx warning might make it clearer. I keep forgetting we do this. The reason we can't go the other way round is that I have no way to forward the C++ log messages back to python.

args = ("butler", "--log-level", "DEBUG", "--long-log", "create", "here")
else:
args = ("butler", "--log-level", "DEBUG", "create", "here")
result = subprocess.run(args, capture_output=True)
Copy link
Member

Choose a reason for hiding this comment

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

Does the normal click runner not work with lsst.log redirected to python logging? Maybe because one of the tests is using C++ logging? Or is it because click really is forking a subprocess and so logging can never be caught?

Copy link
Contributor Author

@n8pease n8pease Nov 3, 2020

Choose a reason for hiding this comment

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

Click does not fork. It sets up its own isolation environment, see https://github.com/pallets/click/blob/master/src/click/testing.py#L172.
There's something about changing the logging configuration, or the way that we do it, that is incompatible with the way click does its isolation environment. I worked on it for a little while but didn't figure out much more than that, and using subprocess as a workaround for the log test seemed better than spending a ton of time on it. FWIW I've verified click.testing does capture python logging output if you don't mess with the log config.
If you want I can dig in more(?)

Copy link
Member

Choose a reason for hiding this comment

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

I mainly wanted to know if you had tried the suggested context manager that redirects lsst.log to python logging.

Copy link
Contributor Author

@n8pease n8pease Nov 4, 2020

Choose a reason for hiding this comment

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

lsstLog.usePythongLogging() does allow click to capture the log output. Now, without using subprocesses I'm faced with the problem of re-initizliaing the logging system and new settings not taking.
I didn't have lsst.log importable. Still checking...

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, more like I thought I remembered: logging even with usePythongLogging does not get captured by click.

Copy link
Member

Choose a reason for hiding this comment

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

surely python logging is always going to be captured by click? You mean the real problem is that click test runner drops log messages completely and doesn't capture them in any way? Okay, that is very funny.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When the default python log handler is used python logging gets captured by the click test runner. When we add the lsst.log.LogHandler to the python logger then no logging is captured by click, whether we call lsst.log.Log.usePythonLogging() or not. I can investigate it more if you want(?) but Fabio has poked me about getting this into the weekly so he can use it, so I'd like to do it on a different ticket. (let me know if you do want?)

Copy link
Member

Choose a reason for hiding this comment

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

Ok. Ignore this issue then. You have a solution and you understand what the problem is.

@n8pease n8pease force-pushed the tickets/DM-27351 branch 2 times, most recently from b99ceb9 to 2340b3a Compare November 4, 2020 20:44
Also:
Fix a bug found applying the long log option.
Add a unit test.
There is a bug in sphinx-click that passes the MultiCommand into
list_commands instead of the context. This works around that by
not fetching log config out of the context if the context is not
a click.Context instance.
Warn that lsst.log handles python logging when available.
Describe when various formatter strings are chosen.
@n8pease n8pease merged commit 39b7668 into master Nov 4, 2020
@timj timj deleted the tickets/DM-27351 branch February 16, 2024 17:17
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