-
Notifications
You must be signed in to change notification settings - Fork 18
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
Move pytest-logging functionality to pytest-catchlog. #20
Move pytest-logging functionality to pytest-catchlog. #20
Conversation
Thank you! I think your logic could and should be more integrated into the existing code. For example, I believe, it could be achieved without attaching one more handler and formatter. How about reusing |
LogCaptureHandler does not write to |
Class to add new hooks to pytest | ||
""" | ||
|
||
def pytest_register_logging_levels(self): |
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.
Do we really need to know all user-defined levels. What's about just calculating the desired level from the verbosity flag, using a fixed formula, like level = logging.ERROR - 10*v
?
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 don't think you can get a fixed formula to account for user defined levels... However, it just made me realize that python logging module does know about these levels, so I'll see if there's a way to avoid the custom hook.
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 don't think you can get a fixed formula to account for user defined levels...
Why not? User level are anyway somewhere between NOTSET and CRITICAL, more likely between NOTSET and DEBUG. So we could just take it into consideration, i.e. scale the range properly, for example:
ERROR (40) default
WARNING (30) -v
INFO (20) -vv
DEBUG (10) -vvv
CUSTOM (5) -vvvv
CUSTOM (2) -vvvvv
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 just let the user to customize it through pytest.ini
maybe...
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'd like to avoid guessing or getting close. The logging module should give use the available logging levels, though, libraries which are late imported could add log levels after the plugin has had a chance to translate verbosity to log level...
Then we should teach it to, depending on the |
logging.root.addHandler(CONSOLEHANDLER) | ||
# The root logging should have the lowest logging level to allow all messages | ||
# to be "passed" to the handlers | ||
logging.root.setLevel(1) |
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 shouldn't be done at the global script level, since it effectively forbids the module to be imported by anyone, including various tools.
Hmm... I'll move it... |
You could use |
Here's my issue with |
You're right, but I'm not sure that that behavior is desired for most users. Honestly, I can't imagine a case when I would be interested in what is happening outside my tests. Could you provide an example, please? @The-Compiler @eisensheng, what do you think? |
As I had mentioned on the issue filed against my plugin, I am sometimes, actually quite often, interested on some boiler plate logging that happens when preparing the test suite, that's why I needed logging support in pytest in the first place... |
I suggest keeping 2 handlers, which already exists, which takes care of catching the log messages, and one responsible for printing them to the console. In this case, CatchLogHandler wouldn't need to subclass stream handler since we would already have a stream handler installed... |
I'm also interested on logging that happens on libraries that I use with my own library, hence me setting the root logger level to 1, so we'd get all logging from all libraries that are used by the code being tested. |
catchlog handles this as well by setting up a handler that captures logs during setup phase, i.e. preparing and running necessary fixtures needed to run a test case. Isn't this what you need? |
I'm trying to see if I can "fit" your concerns into some usable code.... |
I mean, if you just need to log some boilerplate code that fits the standard pytest fixtures concept or setup hook wrappers, then everything should work fine already. @pytest.fixture
def fixx():
logger.error('FIXX')
def test_foo(caplog, fixx):
logger.info('test') In this example, both log lines are captured, yet by handlers created separately. |
Yes, I read that correctly, but I'm hacking another pytest plugin which needs logging, and I'm trying to confirm if that approach will serve that purpose as well, pytest-logging was going to facilitate logging to other plugins... |
So far, hook usage was dropped... |
On the second thought, it could be a good solution to keep a single handler instance attached to the logging subsystem (either streaming to This reflects how the standard stdout/stderr capturing works, to some extent. Also this could reduce the overhead of configuring the logging subsystem each time a test function runs. |
Well, I actually have to disagree with you... I'll explain. When I coded pytest-logging I was looking for an easy way to get all logging, before, during and after running the test suite, for all libraries involved. This is something that is most likely to be used while developing tests or while debugging why a test started to fail, not all the time. So either e attach 2 stream to CatchLogHandler(in case verbosity is higher than 1) and write to both streams, or, we have separate handlers(which actually serve 2 different purposes....) |
You seem to miss the point, I was talking on. I can understand both use-cases you described, and that's exactly what I meant by saying
What I'm also trying to say is that you won't need both handlers at once. But I was not suggesting to merge the logic providing these two distinct flavors into a single Handler class. There likely should be two classes, though only one of them is instantiated and attached as a logging handler. |
Ok, please review the code again. No hook usage(although that breaks pypy testing so I'm skipping those), and no global instantiation... Let me know if this is more likely to get merged of if there's still something else that I need to address... |
Honestly, I'd prefer to support the hooks as it previously did, but i's your call now... |
Then its the or that I'm missing. There might be tests which will need On 16 November 2015 at 16:12, Eldar Abusalimov [email protected]
Pedro Algarvio |
Yeah, its true. I meant only the part responsible for reporting logs in case of a failure, not I have pushed some experimental changes that I made two month ago as part of #7. They include separating Handlers responsible for reporting and inspecting. I should have given you that links first, that would probably clarify a lot: 00b394d and 12e7ce0. |
Let me know if there's anything else that you want me to address differently. |
logging.root.addHandler(self.console) | ||
# The root logging should have the lowest logging level to allow all | ||
# messages to be "passed" to the handlers | ||
logging.root.setLevel(1) |
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.
AFAIK, the lowest level is 0, i.e. logging.NOTSET
.
@s0undt3ch Sorry for the delayed response.
Honestly I don't like the hooks approach. But I have some thoughts about a similar ini-option: [pytest]
log_level_extra =
TRACE # infer proper number of -v from the level value
-vvvv:GARBAGE # or map the verbosity flag explicitly What do you think? |
You're very welcome, thank you for the efforts! |
I've updated the PR with your suggestions. I did however left some of the logic in a more verbose and commented way to better enlighten someone who's reading that for the first time. If you prefer, I'll shorten that too. |
# log messages are displayd) | ||
# verbosity=1 -v CRITICAL (pytest verbosity kicks in, but only | ||
# log messages with higher or equal | ||
# level to CRITICAL are shown) |
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.
Wait, but FATAL
is just an alias for CRITICAL
...
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.
Err... Then that I didn't include it in the first implementation.... Thanks. I'll fix.
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.
But why is the highest level not included at the first place? It would eliminate the need to start counting from 1 in the enumerate
call below, and let us use zero-based indices I was talking about yesterday...
Updated. |
Hi, Could you join our gitter chat now? I have some though I'd like to discuss Regards,
|
|
||
|
||
def pytest_configure(config): | ||
"""Always register the log catcher plugin with py.test or tests can't | ||
find the fixture function. | ||
""" | ||
verbosity = config.getoption('-v') | ||
if verbosity < 1: | ||
cli_handler_level = logging.FATAL |
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 guess logging.CRITICAL
is preferred over it's alias. The official docs used to use CRITICAL
.
What do think about moving the level range checking logic into the first loop, which parses ini-option? def pytest_configure(config):
available_levels = set([
logging.NOTSET,
logging.DEBUG,
logging.INFO,
logging.WARNING,
logging.ERROR,
logging.CRITICAL,
])
for level in get_option_ini(config, 'log_extra_levels'):
try:
level_num = int(getattr(logging, level, level))
except ValueError:
raise pytest.UsageError('...')
else:
if not (logging.NOTSET <= level_num <= logging.CRITICAL):
config.warn("'{0}' is ignored as not being in the valid range")
continue
available_levels.add(level_num)
log_levels = sorted(available_levels) After that, adjusting the level based on the verbosity becomes extremely simple and straightforward: base_idx = log_levels.index(logging.WARNING) # This is the default level.
verbosity = config.getoption('-v')
level_idx = base_idx - verbosity # also need to clip it into a valid range
cli_handler_level = log_levels[level_idx] And here's how I would do the range clipping (any of these):
|
|
Updated(without the clamping logic), see if you still prefer it in another way. |
level, | ||
logging.NOTSET, | ||
logging.CRITICAL)) | ||
continue |
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.
Excess (raise
above)
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.
Don't follow the excess part, its still a usage error and your config.warn
suggestion seemed too shy and easy to miss. You'd need to pass -rw
to see it right?
And, we should stick with the same error reporting for both situations. So either the usage error, or the warn. Unless I'm missing something, which is also possible.
So, which one?
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.
No, I just meant that continue
after raise
is never reached anyway.
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.
Oh. Oops. Yeah, absolutely right.
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.
And, we should stick with the same error reporting for both situations. So either the usage error, or the warn.
Makes sense to me. I agree with your choice.
Updated. |
Let's summarize remaining issues:
I think most of these (maybe except the README) can and should be addressed separately, after merging this PR. @s0undt3ch The one last thing I would ask you for is to squash the whole changeset into a single commit, or few commits like feature+tests+readme, as you wish. I'd also like to hear final comments from @eisensheng and @The-Compiler on this epic PR. |
# higher or equal level to WARNING) | ||
# verbosity=4 -vvv INFO | ||
# - ... etc | ||
handled_levels = dict(enumerate(sorted(available_levels, reverse=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.
I haven't followed the previous discussion, but intuitively to me it'd make sense to show CRITICAL
and ERROR
without -v
(maybe even WARNING
), and then have -v
or -vv
for INFO
. Not displaying errors without -v
sounds wrong, IMHO.
@s0undt3ch any news? |
Sorry, this Xmas season is kind of busy for me. I'll try to do what you asked today or tomorrow.. |
Thanks! If you're really that busy, I could fix the remaining issues after merging into |
17 months bold twins and the whole family coming over for Xmas...yeah, busy. Anyway, squashed. If you could take a look at the remaining issues, I'd be grateful. Thanks! And merry XMas! |
Merry XMas! |
I found a need to log to a file all logging that is happening, at a different level that what's being shown in the console. |
I think, making a separate PR is a better option anyway... |
So, would everyone be happy if:
|
Fixes #14