Skip to content

Commit 78e34fb

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 78e34fb

File tree

5 files changed

+214
-53
lines changed

5 files changed

+214
-53
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import logging
2+
3+
logging.warning("test")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import logging
2+
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})
34+
35+
try:
36+
1 / 0
37+
except:
38+
logger.error("Some trouble (%s)", "expected")
39+
40+
# Custom handler example
41+
class MyHandler(logging.Handler):
42+
def emit(self, record):
43+
print("levelname=%(levelname)s name=%(name)s message=%(message)s" % record.__dict__)
44+
45+
46+
logging.getLogger().addHandler(MyHandler())
47+
logging.info("Test message7")

python-stdlib/logging/logging.py

+163-52
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import sys
2+
import time
3+
4+
if hasattr(time, "strftime"):
5+
from time import strftime
26

37
CRITICAL = 50
48
ERROR = 40
@@ -8,46 +12,104 @@
812
NOTSET = 0
913

1014
_level_dict = {
11-
CRITICAL: "CRIT",
15+
CRITICAL: "CRITICAL",
1216
ERROR: "ERROR",
13-
WARNING: "WARN",
17+
WARNING: "WARNING",
1418
INFO: "INFO",
1519
DEBUG: "DEBUG",
20+
NOTSET: "NOTSET",
1621
}
1722

23+
_loggers = {}
1824
_stream = sys.stderr
25+
_level = INFO
26+
_default_fmt = "%(levelname)s:%(name)s:%(message)s"
27+
_default_datefmt = "%Y-%m-%d %H:%M:%S"
1928

2029

2130
class LogRecord:
22-
def __init__(self):
23-
self.__dict__ = {}
24-
25-
def __getattr__(self, key):
26-
return self.__dict__[key]
31+
def set(self, name, level, message):
32+
self.name = name
33+
self.levelno = level
34+
self.levelname = _level_dict[level]
35+
self.message = message
36+
self.ct = time.time()
37+
self.msecs = int((self.ct - int(self.ct)) * 1000)
38+
self.asctime = None
2739

2840

2941
class Handler:
30-
def __init__(self):
31-
pass
42+
def __init__(self, level=NOTSET):
43+
self.level = level
44+
self.formatter = None
3245

33-
def setFormatter(self, fmtr):
46+
def close(self):
3447
pass
3548

49+
def setLevel(self, level):
50+
self.level = level
3651

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

39-
level = NOTSET
40-
handlers = []
41-
record = LogRecord()
68+
def emit(self, record):
69+
if record.levelno >= self.level:
70+
self.stream.write(self.format(record) + self.terminator)
4271

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

52114
def setLevel(self, level):
53115
self.level = level
@@ -57,19 +119,16 @@ def isEnabledFor(self, level):
57119

58120
def log(self, level, msg, *args):
59121
if self.isEnabledFor(level):
60-
levelname = self._level_str(level)
61122
if args:
123+
if isinstance(args[0], dict):
124+
args = args[0]
62125
msg = msg % args
63126
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
69127
for h in self.handlers:
128+
self.record.set(self.name, level, msg)
70129
h.emit(self.record)
71130
else:
72-
print(levelname, ":", self.name, ":", msg, sep="", file=_stream)
131+
print(_level_dict[level], ":", self.name, ":", msg, sep="", file=_stream)
73132

74133
def debug(self, msg, *args):
75134
self.log(DEBUG, msg, *args)
@@ -86,43 +145,95 @@ def error(self, msg, *args):
86145
def critical(self, msg, *args):
87146
self.log(CRITICAL, msg, *args)
88147

89-
def exc(self, e, msg, *args):
148+
def exception(self, msg, *args):
90149
self.log(ERROR, msg, *args)
91-
sys.print_exception(e, _stream)
150+
if hasattr(sys, "exc_info"):
151+
sys.print_exception(sys.exc_info()[1], _stream)
92152

93-
def exception(self, msg, *args):
94-
self.exc(sys.exc_info()[1], msg, *args)
153+
def addHandler(self, handler):
154+
self.handlers.append(handler)
95155

96-
def addHandler(self, hndlr):
97-
self.handlers.append(hndlr)
156+
def hasHandlers(self):
157+
return len(self.handlers) > 0
98158

99159

100-
_level = INFO
101-
_loggers = {}
160+
def getLogger(name="root"):
161+
if name not in _loggers:
162+
_loggers[name] = Logger(name)
163+
if name == "root":
164+
basicConfig()
165+
return _loggers[name]
102166

103167

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
168+
def log(level, msg, *args):
169+
getLogger().log(level, msg, *args)
170+
171+
172+
def debug(msg, *args):
173+
getLogger().debug(msg, *args)
110174

111175

112176
def info(msg, *args):
113177
getLogger().info(msg, *args)
114178

115179

116-
def debug(msg, *args):
117-
getLogger().debug(msg, *args)
180+
def warning(msg, *args):
181+
getLogger().warning(msg, *args)
182+
183+
184+
def error(msg, *args):
185+
getLogger().error(msg, *args)
186+
187+
188+
def critical(msg, *args):
189+
getLogger().critical(msg, *args)
190+
191+
192+
def exception(msg, *args):
193+
getLogger().exception(msg, *args)
194+
195+
196+
def shutdown():
197+
for k, logger in _loggers.items():
198+
for h in logger.handlers:
199+
h.close()
200+
_loggers.pop(logger, None)
201+
202+
203+
def addLevelName(level, name):
204+
_level_dict[level] = name
205+
206+
207+
def basicConfig(
208+
filename=None,
209+
filemode="a",
210+
format=_default_fmt,
211+
datefmt=_default_datefmt,
212+
level=WARNING,
213+
stream=_stream,
214+
encoding="UTF-8",
215+
force=False,
216+
):
217+
if "root" not in _loggers:
218+
_loggers["root"] = Logger("root")
219+
220+
logger = _loggers["root"]
221+
222+
if force or not logger.handlers:
223+
for h in logger.handlers:
224+
h.close()
225+
226+
if filename is None:
227+
handler = StreamHandler(stream)
228+
else:
229+
handler = FileHandler(filename, filemode, encoding)
230+
231+
handler.setLevel(level)
232+
handler.setFormatter(Formatter(format, datefmt))
233+
234+
logger.setLevel(level)
235+
logger.addHandler(handler)
118236

119237

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")
238+
if hasattr(sys, "atexit"):
239+
sys.atexit(shutdown)

python-stdlib/logging/manifest.py

+1-1
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)