Skip to content

Commit 9d960b6

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 9d960b6

File tree

5 files changed

+213
-83
lines changed

5 files changed

+213
-83
lines changed

python-stdlib/logging/example_logging.py

Lines changed: 0 additions & 24 deletions
This file was deleted.
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")

python-stdlib/logging/logging.py

Lines changed: 162 additions & 58 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,68 +12,119 @@
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+
_default_fmt = "%(levelname)s:%(name)s:%(message)s"
26+
_default_datefmt = "%Y-%m-%d %H:%M:%S"
1927

2028

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

2839

2940
class Handler:
30-
def __init__(self):
31-
pass
41+
def __init__(self, level=NOTSET):
42+
self.level = level
43+
self.formatter = None
3244

33-
def setFormatter(self, fmtr):
45+
def close(self):
3446
pass
3547

48+
def setLevel(self, level):
49+
self.level = level
3650

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

39-
level = NOTSET
40-
handlers = []
41-
record = LogRecord()
63+
def close(self):
64+
if hasattr(self.stream, "flush"):
65+
self.stream.flush()
4266

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

52113
def setLevel(self, level):
53114
self.level = level
54115

55116
def isEnabledFor(self, level):
56-
return level >= (self.level or _level)
117+
return level >= self.level
57118

58119
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)
120+
if self.isEnabledFor(level) and self.handlers:
121+
if args and isinstance(args[0], dict):
122+
args = args[0]
123+
for h in self.handlers:
124+
self.record.set(self.name, level, msg % args)
125+
h.emit(self.record)
126+
elif not self.handlers:
127+
print(msg, sep="", file=_stream)
73128

74129
def debug(self, msg, *args):
75130
self.log(DEBUG, msg, *args)
@@ -86,27 +141,29 @@ def error(self, msg, *args):
86141
def critical(self, msg, *args):
87142
self.log(CRITICAL, msg, *args)
88143

89-
def exc(self, e, msg, *args):
144+
def exception(self, msg, *args):
90145
self.log(ERROR, msg, *args)
91-
sys.print_exception(e, _stream)
146+
if hasattr(sys, "exc_info"):
147+
for h in filter(lambda h: isinstance(h, StreamHandler), self.handlers):
148+
sys.print_exception(sys.exc_info()[1], h.stream)
92149

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

96-
def addHandler(self, hndlr):
97-
self.handlers.append(hndlr)
153+
def hasHandlers(self):
154+
return len(self.handlers) > 0
98155

99156

100-
_level = INFO
101-
_loggers = {}
157+
def getLogger(name="root"):
158+
if name not in _loggers:
159+
_loggers[name] = Logger(name)
160+
if name == "root":
161+
basicConfig()
162+
return _loggers[name]
102163

103164

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
165+
def log(level, msg, *args):
166+
getLogger().log(level, msg, *args)
110167

111168

112169
def info(msg, *args):
@@ -117,12 +174,59 @@ def debug(msg, *args):
117174
getLogger().debug(msg, *args)
118175

119176

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