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
Conversation
31e59e5
to
acba8af
Compare
python/lsst/daf/butler/cli/cliLog.py
Outdated
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" |
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.
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
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.
ok
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 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.
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 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
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 looks fine. My understanding is that the logging Formatter is what adds on milliseconds; we can probably do without it.
python/lsst/daf/butler/cli/cliLog.py
Outdated
@@ -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()))) |
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 alternative with an if
/else
statement is only two lines longer and may be clearer.
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.
@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?
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.
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 |
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.
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?
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 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.
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.
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?
8c32999
to
31556ef
Compare
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.
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.
python/lsst/daf/butler/cli/butler.py
Outdated
# 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" |
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 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 |
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 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.
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.
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 |
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 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.
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 will add to the class docstring about this.
@@ -92,7 +110,12 @@ def initLog(cls, longlog): | |||
pythonLogger.addHandler(cls._lsstLogHandler) |
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 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) |
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.
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?
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.
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(?)
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 mainly wanted to know if you had tried the suggested context manager that redirects lsst.log to python logging.
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.
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...
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.
ok, more like I thought I remembered: logging even with usePythongLogging
does not get captured by click.
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.
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.
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.
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?)
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.
Ok. Ignore this issue then. You have a solution and you understand what the problem is.
b99ceb9
to
2340b3a
Compare
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.
2340b3a
to
f6ab362
Compare
No description provided.