-
Notifications
You must be signed in to change notification settings - Fork 1k
logging: Improve the logging module. #507
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
Conversation
b1112b6
to
ce952a6
Compare
Thanks for this, a cleanup of the logging module has been on my shortlist of todo so I'm quite interested. On an initial quick skim through the changelog this looks like a good broad cleanup, with sensible additions added without too much extra "code bulk". I've had a couple of early planning discussions with colleagues about this module and they always end up swinging back and forth between which features will be good for most users, but also small enough to fit on most ports/boards. At first glance I feel there's a pretty good balance here! the included handlers are fairly simple, but should also provide a good base (and example) to be extended upon by individual users to meet their project. I'm also inclined to think that more complex handlers (eg. size limited and timestamped rotating files) could happily go in separate packages. I'm keen to review and test in more depth in then coming days (hopefully) but in the mean time, can you confirm whether the included classes confirm to (a sunset of) the cpython logging module? By memory they look good, but just want to check. |
Yes, this conforms to CPython
That's a good idea, more handlers can be added in a separate module and imported in
|
b7a21aa
to
b414392
Compare
b414392
to
e8dbc08
Compare
e8dbc08
to
b6a2b3a
Compare
Updated. |
Does this run every time if hasattr(sys, "atexit"):
sys.atexit(shutdown) |
No, a module is only ever executed once, the first time it is imported. So that |
Yeah just wanted to double check. I think it's ready now, unless there's something else ? Would be nice if this is available too with the updated logging: #508 |
While at it could you perhaps add
and
? I'm using these for CPython compatibility. |
Testing this, I see that the basic use where you just start logging doesn't work (at least it doesn't match CPython). Eg: import logging
logging.warning('test') The above use-case should match CPython (ie the default logger should print out with the default formatting). |
If handlers are being added can a RotatingFileHandler be considered? Ported from cpython class RotatingFileHandler(Handler):
def __init__(self, filename, maxsize=128_000, backupCount=5):
if maxsize < 256:
raise ValueError("maxsize must be at least 256 B")
self.filename = filename
self.maxsize = maxsize
self.backupCount = backupCount
self.formatter = Formatter()
try:
# checks if file exists and prevents overwriting it
self.pos = os.stat(self.filename)[6]
self.file = open(filename, "r+")
except OSError:
self.pos = 0
self.file = open(filename, "w+")
self.file.seek(self.pos)
def shouldRollover(self, record):
"""
Determine if rollover should occur.
Basically, see if the supplied record would cause the file to exceed
the size limit we have.
"""
message = self.format(record)
if len(message) + self.pos > self.maxsize:
return True
else:
return False
def doRollover(self):
"""
Do a rollover.
"""
# Close current file first
self.file.close()
if self.backupCount > 0:
for i in range(self.backupCount - 1, 0, -1):
sfn = "%s.%d" % (self.filename, i)
dfn = "%s.%d" % (self.filename, i + 1)
try:
os.rename(sfn, dfn)
except:
print(f"Cant move file {sfn} to {dfn}")
dfn = self.filename + ".1"
try:
os.rename(self.filename, dfn)
except:
print(f"Couldnt move file {self.filename} to {dfn}")
print("Rollover complete")
self.pos = 0
self.file = open(self.filename, "w+")
self.file.seek(self.pos) |
b6a2b3a
to
5a8bf69
Compare
@dpgeorge Fixed, thanks.
@stinos Added.
@ThinkTransit Handlers will be added, just not in this PR, and probably in a separate package. |
5a8bf69
to
7b3bcca
Compare
Just a minor optimization, this will only init the root logger and only if it's not initialized. def getLogger(name="root"):
if name not in loggers:
loggers[name] = Logger(name)
if name == "root":
basicConfig()
return loggers[name] So basic example will init root logger:
Creating a logger wont:
|
ping @dpgeorge |
So this means the following code (with this PR as it currently is) won't print anything: >>> import logging
>>> logging.getLogger('log').warning('test') Whereas the code before this PR did print a message, and CPython does as well. I think it should work like it did before (and hence be CPython compatible). |
06575c8
to
f3ab1fb
Compare
It was Not CPython compatible because it printed a logger name, level etc.. where as CPython would just print the message, but now it is. There's no global Pushed an update to fix the other comments. |
f3ab1fb
to
2a1dd4f
Compare
Would it be better if #508 is included in |
dfcffa2
to
8200583
Compare
Alright, removed
Yes please do, log time is very important. |
8200583
to
9d960b6
Compare
This is getting better, however it still doesn't match the existing behaviour of this module or CPython for the existing example. The existing example with the existing library had this output:
Which is quite close to CPython's output run against that same example. With the new library here, that example has the output:
@iabdalkader please can you add back the existing example as a separate example file in the |
ef3305c
to
b2fa953
Compare
@dpgeorge I did, it's in $ mpremote mount . run examples/example_logging_1.py
Local directory . is mounted at /remote
INFO:test:Test message2: 100(foobar)
WARNING:test:Test message3: %d(%s)
ERROR:test:Test message4
CRITICAL:test:Test message5
INFO:root:Test message6
ERROR:test:Some trouble (expected)
Traceback (most recent call last):
File "<stdin>", line 13, in <module>
ZeroDivisionError: divide by zero
INFO:root:Test message7
levelname=INFO name=root message=Test message7
Note I'm aiming for max compatibility with CPython Not with the old logging library (which is missing basic things like |
b2fa953
to
78e34fb
Compare
Regarding CPython-compatibility: I only now ran this implementation against a bunch of code I have and it seems there are some things which might be worth making compatible since it's new code anyway. Sorry I'm somewhat late with this, and I probably didn't check everything, but here are some things I ran into:
|
78e34fb
to
19897d9
Compare
The above should be fixed now.
Not changing this because we'll then prefix it with an underscore, and it will be different anyway so I don't see the point. Note I'm reluctant to change anything else in this PR, unless it's a bug, I don't want to risk breaking something else which will result in another review/fix cycle, and I really need a usable logging in |
Yes I agree, it would be good to finalise on this PR, and anything else can be an extension done as a follow up. |
I understand, but in my opinion the very basics should at least work.
part deleted, didn't test with last version So I really think the minimal |
19897d9
to
82d14d3
Compare
When you first call
So Running your example on CPython does print something, not sure why, and more interesting if you change it to the following, it doesn't print anything. root = logging.getLogger('root')
logging.info('hello upy')
logging.basicConfig(level=logging.DEBUG)
logging.info('hello upy')
I think I forgot to push after the latest changes, using EDIT: Maybe by default CPython doesn't add any handlers to the root longer, in that case the above would print the log. |
Only because I ran it in CPython and it printed output :) But actually the code isn't a real usecase, I only started typing it because I wanted to show that the previous version which didn't clear all handlers was an issue. But with that fixed it's ok as far as I'm concerned.
It does but only upon the first logging statement issued. But I don't think that matters. What might matter though: in CPython
outputs
in CPython but
in MicroPython, and that last line only appears because there's a |
That's an interesting example that shows a shortcoming of both the old and new logger, yes the print statement outputs the second line instead of propagating the log to the higher loggers (and to the root logger which prints that second line on CPython). And this was indeed the old behavior which Damien wanted to maintain, but it also seemed to me like the hierarchy would be an overkill for MicroPython. Maybe a middle ground solution would be to call the root logger instead def log(self, level, msg, *args):
if self.isEnabledFor(level):
if args:
if isinstance(args[0], dict):
args = args[0]
msg = msg % args
self.record.set(self.name, level, msg)
handlers = self.handlers
if not handlers:
handlers = getLogger().handlers
for h in handlers:
h.emit(self.record) With the above now your example outputs: mpremote mount . run test2.py
Local directory . is mounted at /remote
[INFO]:root:hello upy
[INFO]:child:hello 2
Yes I can confirm this and everything in my previous comment should be fixed, I just forgot to push the changes sorry about the confusion. |
Good dea, definitely an improvement over current behavior. For a separate PR perhaps? |
82d14d3
to
238606d
Compare
Pushed the change since I retested all the examples anyway, and it seems fine. I guess this should be ready now @dpgeorge . |
238606d
to
d717b04
Compare
Thanks for the work on this, it looks really good now. The existing example still works (and works better, the last call in that now has the same behaviour as CPython), and the new examples also match CPython. Very good! |
Merged in d717b04 |
@dpgeorge That's great thanks for merging this and the review(s). Can we please also have the |
datefmt
using strftime, and support for Stream and File handlers.FileHandlers
should enableMICROPY_PY_SYS_ATEXIT
, and enableMICROPY_PY_SYS_EXC_INFO
if usinglogging.exception()
.