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