Skip to content

Commit d717b04

Browse files
iabdalkaderdpgeorge
authored andcommitted
logging: Improve the logging module.
Add support for all format specifiers, support for `datefmt` using (optional) 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 0051a5e commit d717b04

File tree

6 files changed

+229
-58
lines changed

6 files changed

+229
-58
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import logging
2+
3+
logging.warning("test")
Lines changed: 47 additions & 0 deletions
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")
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import logging, sys
2+
3+
logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
4+
for handler in logging.getLogger().handlers:
5+
handler.setFormatter(logging.Formatter("[%(levelname)s]:%(name)s:%(message)s"))
6+
logging.info("hello upy")
7+
logging.getLogger("child").info("hello 2")

python-stdlib/logging/logging.py

Lines changed: 171 additions & 57 deletions
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
3854

39-
level = NOTSET
40-
handlers = []
41-
record = LogRecord()
55+
def format(self, record):
56+
return self.formatter.format(record)
4257

43-
def __init__(self, name):
44-
self.name = name
4558

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
59+
class StreamHandler(Handler):
60+
def __init__(self, stream=None):
61+
self.stream = _stream if stream is None else stream
62+
self.terminator = "\n"
63+
64+
def close(self):
65+
if hasattr(self.stream, "flush"):
66+
self.stream.flush()
67+
68+
def emit(self, record):
69+
if record.levelno >= self.level:
70+
self.stream.write(self.format(record) + self.terminator)
71+
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=None, datefmt=None):
84+
self.fmt = _default_fmt if fmt is None else fmt
85+
self.datefmt = _default_datefmt if datefmt is None else 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:
108+
def __init__(self, name, level=NOTSET):
109+
self.name = name
110+
self.level = level
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
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)
126+
self.record.set(self.name, level, msg)
127+
handlers = self.handlers
128+
if not handlers:
129+
handlers = getLogger().handlers
130+
for h in handlers:
131+
h.emit(self.record)
73132

74133
def debug(self, msg, *args):
75134
self.log(DEBUG, msg, *args)
@@ -86,43 +145,98 @@ 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=None):
161+
if name is None:
162+
name = "root"
163+
if name not in _loggers:
164+
_loggers[name] = Logger(name)
165+
if name == "root":
166+
basicConfig()
167+
return _loggers[name]
168+
102169

170+
def log(level, msg, *args):
171+
getLogger().log(level, msg, *args)
103172

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
173+
174+
def debug(msg, *args):
175+
getLogger().debug(msg, *args)
110176

111177

112178
def info(msg, *args):
113179
getLogger().info(msg, *args)
114180

115181

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

119240

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

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)