Skip to content

Commit 2a1dd4f

Browse files
committed
logging: Improve the logging module.
* Add support for all format specifiers, support for `datefmt` using strftime, and support for Stream and File handlers. * Ports/boards that need to use `FileHandlers` should enable `MICROPY_PY_SYS_ATEXIT`, and enabled `MICROPY_PY_SYS_EXC_INFO` if using `logging.exception()`.
1 parent 038b4ac commit 2a1dd4f

File tree

3 files changed

+197
-73
lines changed

3 files changed

+197
-73
lines changed

python-stdlib/logging/example_logging.py

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,43 @@
11
import logging
22

3-
logging.basicConfig(level=logging.INFO)
4-
log = logging.getLogger("test")
5-
log.debug("Test message: %d(%s)", 100, "foobar")
6-
log.info("Test message2: %d(%s)", 100, "foobar")
7-
log.warning("Test message3: %d(%s)")
8-
log.error("Test message4")
9-
log.critical("Test message5")
10-
logging.info("Test message6")
3+
# Create logger
4+
logger = logging.getLogger(__name__)
5+
logger.setLevel(logging.DEBUG)
6+
7+
# Create console handler and set level to debug
8+
stream_handler = logging.StreamHandler()
9+
stream_handler.setLevel(logging.DEBUG)
10+
11+
# Create file handler and set level to error
12+
file_handler = logging.FileHandler("error.log", mode="w")
13+
file_handler.setLevel(logging.ERROR)
14+
15+
# Create a formatter
16+
formatter = logging.Formatter("%(asctime)s.%(msecs)03d - %(name)s - %(levelname)s - %(message)s")
17+
18+
# Add formatter to the handlers
19+
stream_handler.setFormatter(formatter)
20+
file_handler.setFormatter(formatter)
21+
22+
# Add handlers to logger
23+
logger.addHandler(stream_handler)
24+
logger.addHandler(file_handler)
25+
26+
# Log some messages
27+
logger.debug("debug message")
28+
logger.info("info message")
29+
logger.warning("warn message")
30+
logger.error("error message")
31+
logger.critical("critical message")
32+
logger.info("message %s %d", "arg", 5)
33+
logger.info("message %(foo)s %(bar)s", {"foo": 1, "bar": 20})
1134

1235
try:
1336
1 / 0
1437
except:
15-
log.exception("Some trouble (%s)", "expected")
16-
38+
logger.error("Some trouble (%s)", "expected")
1739

40+
# Custom handler example
1841
class MyHandler(logging.Handler):
1942
def emit(self, record):
2043
print("levelname=%(levelname)s name=%(name)s message=%(message)s" % record.__dict__)

python-stdlib/logging/logging.py

Lines changed: 163 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import sys
2+
import time
23

34
CRITICAL = 50
45
ERROR = 40
@@ -8,68 +9,119 @@
89
NOTSET = 0
910

1011
_level_dict = {
11-
CRITICAL: "CRIT",
12+
CRITICAL: "CRITICAL",
1213
ERROR: "ERROR",
13-
WARNING: "WARN",
14+
WARNING: "WARNING",
1415
INFO: "INFO",
1516
DEBUG: "DEBUG",
17+
NOTSET: "NOTSET",
1618
}
1719

20+
_loggers = {}
1821
_stream = sys.stderr
22+
_default_fmt = "%(levelname)s:%(name)s:%(message)s"
23+
_default_datefmt = "%Y-%m-%d %H:%M:%S"
1924

2025

2126
class LogRecord:
22-
def __init__(self):
23-
self.__dict__ = {}
24-
25-
def __getattr__(self, key):
26-
return self.__dict__[key]
27+
def set(self, name, level, message):
28+
self.name = name
29+
self.levelno = level
30+
self.levelname = _level_dict[level]
31+
self.message = message
32+
self.ct = time.time()
33+
self.msecs = int((self.ct - int(self.ct)) * 1000)
34+
self.asctime = None
2735

2836

2937
class Handler:
30-
def __init__(self):
31-
pass
38+
def __init__(self, level=NOTSET):
39+
self.level = level
40+
self.formatter = None
3241

33-
def setFormatter(self, fmtr):
42+
def close(self):
3443
pass
3544

45+
def setLevel(self, level):
46+
self.level = level
47+
48+
def setFormatter(self, formatter):
49+
self.formatter = formatter
3650

37-
class Logger:
51+
def format(self, record):
52+
return self.formatter.format(record)
53+
54+
55+
class StreamHandler(Handler):
56+
def __init__(self, stream=_stream):
57+
self.stream = stream
58+
self.terminator = "\n"
59+
60+
def close(self):
61+
if hasattr(self.stream, "flush"):
62+
self.stream.flush()
3863

39-
level = NOTSET
40-
handlers = []
41-
record = LogRecord()
64+
def emit(self, record):
65+
if record.levelno >= self.level:
66+
self.stream.write(self.format(record) + self.terminator)
4267

68+
69+
class FileHandler(StreamHandler):
70+
def __init__(self, filename, mode="a", encoding="UTF-8"):
71+
super().__init__(stream=open(filename, mode=mode, encoding=encoding))
72+
73+
def close(self):
74+
super().close()
75+
self.stream.close()
76+
77+
78+
class Formatter:
79+
def __init__(self, fmt=_default_fmt, datefmt=_default_datefmt):
80+
self.fmt = fmt
81+
self.datefmt = datefmt
82+
83+
def usesTime(self):
84+
return "asctime" in self.fmt
85+
86+
def formatTime(self, datefmt, record):
87+
if hasattr(time, "strftime"):
88+
return time.strftime(datefmt, time.localtime(record.ct))
89+
return ""
90+
91+
def format(self, record):
92+
if self.usesTime():
93+
record.asctime = self.formatTime(self.datefmt, record)
94+
return self.fmt % {
95+
"name": record.name,
96+
"message": record.message,
97+
"msecs": record.msecs,
98+
"asctime": record.asctime,
99+
"levelname": record.levelname,
100+
}
101+
102+
103+
class Logger:
43104
def __init__(self, name):
44105
self.name = name
45-
46-
def _level_str(self, level):
47-
l = _level_dict.get(level)
48-
if l is not None:
49-
return l
50-
return "LVL%s" % level
106+
self.level = NOTSET
107+
self.handlers = []
108+
self.record = LogRecord()
51109

52110
def setLevel(self, level):
53111
self.level = level
54112

55113
def isEnabledFor(self, level):
56-
return level >= (self.level or _level)
114+
return level >= self.level
57115

58116
def log(self, level, msg, *args):
59-
if self.isEnabledFor(level):
60-
levelname = self._level_str(level)
61-
if args:
62-
msg = msg % args
63-
if self.handlers:
64-
d = self.record.__dict__
65-
d["levelname"] = levelname
66-
d["levelno"] = level
67-
d["message"] = msg
68-
d["name"] = self.name
69-
for h in self.handlers:
70-
h.emit(self.record)
71-
else:
72-
print(levelname, ":", self.name, ":", msg, sep="", file=_stream)
117+
if self.isEnabledFor(level) and self.handlers:
118+
if args and isinstance(args[0], dict):
119+
args = args[0]
120+
for h in self.handlers:
121+
self.record.set(self.name, level, msg % args)
122+
h.emit(self.record)
123+
elif not self.handlers:
124+
print(msg, sep="", file=_stream)
73125

74126
def debug(self, msg, *args):
75127
self.log(DEBUG, msg, *args)
@@ -86,27 +138,17 @@ def error(self, msg, *args):
86138
def critical(self, msg, *args):
87139
self.log(CRITICAL, msg, *args)
88140

89-
def exc(self, e, msg, *args):
90-
self.log(ERROR, msg, *args)
91-
sys.print_exception(e, _stream)
92-
93141
def exception(self, msg, *args):
94-
self.exc(sys.exc_info()[1], msg, *args)
95-
96-
def addHandler(self, hndlr):
97-
self.handlers.append(hndlr)
98-
99-
100-
_level = INFO
101-
_loggers = {}
142+
self.log(ERROR, msg, *args)
143+
if hasattr(sys, "exc_info"):
144+
for h in filter(lambda h: isinstance(h, StreamHandler), self.handlers):
145+
sys.print_exception(sys.exc_info()[1], h.stream)
102146

147+
def addHandler(self, handler):
148+
self.handlers.append(handler)
103149

104-
def getLogger(name="root"):
105-
if name in _loggers:
106-
return _loggers[name]
107-
l = Logger(name)
108-
_loggers[name] = l
109-
return l
150+
def hasHandlers(self):
151+
return len(self.handlers) > 0
110152

111153

112154
def info(msg, *args):
@@ -117,12 +159,71 @@ def debug(msg, *args):
117159
getLogger().debug(msg, *args)
118160

119161

120-
def basicConfig(level=INFO, filename=None, stream=None, format=None):
121-
global _level, _stream
122-
_level = level
123-
if stream:
124-
_stream = stream
125-
if filename is not None:
126-
print("logging.basicConfig: filename arg is not supported")
127-
if format is not None:
128-
print("logging.basicConfig: format arg is not supported")
162+
def warning(msg, *args):
163+
getLogger().warning(msg, *args)
164+
165+
166+
def error(msg, *args):
167+
getLogger().error(msg, *args)
168+
169+
170+
def critical(msg, *args):
171+
getLogger().critical(msg, *args)
172+
173+
174+
def exception(msg, *args):
175+
getLogger().exception(msg, *args)
176+
177+
178+
def shutdown():
179+
for k, logger in _loggers.items():
180+
for h in logger.handlers:
181+
h.close()
182+
_loggers.pop(logger, None)
183+
184+
185+
if hasattr(sys, "atexit"):
186+
sys.atexit(shutdown)
187+
188+
189+
def addLevelName(level, name):
190+
_level_dict[level] = name
191+
192+
193+
def log(level, msg, *args):
194+
getLogger().log(level, msg, *args)
195+
196+
197+
def getLogger(name="root"):
198+
if name not in _loggers:
199+
_loggers[name] = Logger(name)
200+
if name == "root":
201+
basicConfig()
202+
return _loggers[name]
203+
204+
205+
def basicConfig(
206+
filename=None,
207+
filemode="a",
208+
format=_default_fmt,
209+
datefmt=_default_datefmt,
210+
level=WARNING,
211+
stream=_stream,
212+
encoding="UTF-8",
213+
force=False,
214+
):
215+
logger = getLogger()
216+
if force or not logger.handlers:
217+
for h in logger.handlers:
218+
h.close()
219+
220+
if filename is None:
221+
handler = StreamHandler(stream)
222+
else:
223+
handler = FileHandler(filename, filemode, encoding)
224+
225+
handler.setLevel(level)
226+
handler.setFormatter(Formatter(format, datefmt))
227+
228+
logger.setLevel(level)
229+
logger.addHandler(handler)

python-stdlib/logging/manifest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
metadata(version="0.3")
1+
metadata(version="0.4")
22

33
module("logging.py")

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy