Skip to content

Commit 06575c8

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 06575c8

File tree

3 files changed

+195
-74
lines changed

3 files changed

+195
-74
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: 161 additions & 63 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,116 @@
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

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

2024

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

2835

2936
class Handler:
30-
def __init__(self):
31-
pass
37+
def __init__(self, level=NOTSET):
38+
self.level = level
39+
self.formatter = None
3240

33-
def setFormatter(self, fmtr):
41+
def close(self):
3442
pass
3543

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

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

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

67+
68+
class FileHandler(StreamHandler):
69+
def __init__(self, filename, mode="a", encoding="UTF-8"):
70+
super().__init__(stream=open(filename, mode=mode, encoding=encoding))
71+
72+
def close(self):
73+
super().close()
74+
self.stream.close()
75+
76+
77+
class Formatter:
78+
def __init__(self, fmt=_default_fmt, datefmt=_default_datefmt):
79+
self.fmt = fmt
80+
self.datefmt = datefmt
81+
82+
def usesTime(self):
83+
return "asctime" in self.fmt
84+
85+
def formatTime(self, datefmt, record):
86+
if hasattr(time, "strftime"):
87+
return time.strftime(datefmt, time.localtime(record.ct))
88+
return ""
89+
90+
def format(self, record):
91+
if self.usesTime():
92+
record.asctime = self.formatTime(self.datefmt, record)
93+
return self.fmt % {
94+
"name": record.name,
95+
"message": record.message,
96+
"msecs": record.msecs,
97+
"asctime": record.asctime,
98+
"levelname": record.levelname,
99+
}
100+
101+
102+
class Logger:
43103
def __init__(self, name):
44104
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
105+
self.level = NOTSET
106+
self.handlers = []
107+
self.record = LogRecord()
51108

52109
def setLevel(self, level):
53110
self.level = level
54111

55112
def isEnabledFor(self, level):
56-
return level >= (self.level or _level)
113+
return level >= self.level
57114

58115
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)
116+
if self.isEnabledFor(level) and self.handlers:
117+
if args and isinstance(args[0], dict):
118+
args = args[0]
119+
for h in self.handlers:
120+
self.record.set(self.name, level, msg % args)
121+
h.emit(self.record)
73122

74123
def debug(self, msg, *args):
75124
self.log(DEBUG, msg, *args)
@@ -86,27 +135,17 @@ def error(self, msg, *args):
86135
def critical(self, msg, *args):
87136
self.log(CRITICAL, msg, *args)
88137

89-
def exc(self, e, msg, *args):
90-
self.log(ERROR, msg, *args)
91-
sys.print_exception(e, _stream)
92-
93138
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 = {}
139+
self.log(ERROR, msg, *args)
140+
if hasattr(sys, "exc_info"):
141+
for h in filter(lambda h: isinstance(h, StreamHandler), self.handlers):
142+
sys.print_exception(sys.exc_info()[1], h.stream)
102143

144+
def addHandler(self, handler):
145+
self.handlers.append(handler)
103146

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
147+
def hasHandlers(self):
148+
return len(self.handlers) > 0
110149

111150

112151
def info(msg, *args):
@@ -117,12 +156,71 @@ def debug(msg, *args):
117156
getLogger().debug(msg, *args)
118157

119158

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