Skip to content

Commit e8dbc08

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 e8dbc08

File tree

4 files changed

+218
-95
lines changed

4 files changed

+218
-95
lines changed

python-stdlib/logging/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from .logging import Logger
2+
from .logging import Handler
3+
from .logging import Formatter
4+
from .logging import FileHandler
5+
from .logging import StreamHandler
6+
from .logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
7+
from .logging import debug, info, warning, error, critical
8+
from .logging import exception
9+
from .logging import shutdown
10+
from .logging import getLogger
11+
from .logging import basicConfig
12+
13+
import sys
14+
15+
if hasattr(sys, "atexit"):
16+
sys.atexit(shutdown)

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: 168 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,212 @@
11
import sys
2+
import time
23

3-
CRITICAL = 50
4-
ERROR = 40
5-
WARNING = 30
6-
INFO = 20
7-
DEBUG = 10
84
NOTSET = 0
5+
DEBUG = 10
6+
INFO = 20
7+
WARNING = 30
8+
ERROR = 40
9+
CRITICAL = 50
910

1011
_level_dict = {
11-
CRITICAL: "CRIT",
12-
ERROR: "ERROR",
13-
WARNING: "WARN",
14-
INFO: "INFO",
12+
NOTSET: "NOTSET",
1513
DEBUG: "DEBUG",
14+
INFO: "INFO",
15+
WARNING: "WARNING",
16+
ERROR: "ERROR",
17+
CRITICAL: "CRITICAL",
1618
}
1719

18-
_stream = sys.stderr
20+
loggers = {}
21+
default_fmt = "%(_level_dict)s:%(name)s:%(message)s"
22+
default_datefmt = "%Y-%m-%d %H:%M:%S"
1923

2024

21-
class LogRecord:
22-
def __init__(self):
23-
self.__dict__ = {}
25+
class Handler:
26+
def __init__(self, level=NOTSET):
27+
self.level = level
28+
self.formatter = None
2429

25-
def __getattr__(self, key):
26-
return self.__dict__[key]
30+
def close(self):
31+
pass
2732

33+
def setLevel(self, level):
34+
self.level = level
2835

29-
class Handler:
30-
def __init__(self):
31-
pass
36+
def setFormatter(self, formatter):
37+
self.formatter = formatter
3238

33-
def setFormatter(self, fmtr):
34-
pass
39+
def format(self, record):
40+
return self.formatter.format(record)
3541

3642

37-
class Logger:
43+
class StreamHandler(Handler):
44+
def __init__(self, stream=sys.stderr):
45+
self.stream = stream
46+
self.terminator = "\n"
3847

39-
level = NOTSET
40-
handlers = []
41-
record = LogRecord()
48+
def close(self):
49+
if hasattr(self.stream, "flush"):
50+
self.stream.flush()
4251

43-
def __init__(self, name):
52+
def emit(self, record):
53+
if record.levelno >= self.level:
54+
self.stream.write(self.format(record) + self.terminator)
55+
56+
57+
class FileHandler(StreamHandler):
58+
def __init__(self, filename, mode="a", encoding="UTF-8"):
59+
super().__init__(stream=open(filename, mode=mode, encoding=encoding))
60+
61+
def close(self):
62+
super().close()
63+
self.stream.close()
64+
65+
66+
class LogRecord:
67+
def set(self, name, level, message):
4468
self.name = name
69+
self.levelno = level
70+
self.levelname = _level_dict[level]
71+
self.message = message
72+
self.ct = time.time()
73+
self.msecs = int((self.ct - int(self.ct)) * 1000)
74+
self.asctime = None
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+
}
45100

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
101+
102+
class Logger:
103+
def __init__(self, name):
104+
self.name = name
105+
self.level = NOTSET
106+
self.handlers = []
107+
self.record = LogRecord()
51108

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

112+
def addHandler(self, handler):
113+
self.handlers.append(handler)
114+
115+
def hasHandlers(self):
116+
return len(self.handlers) > 0
117+
55118
def isEnabledFor(self, level):
56-
return level >= (self.level or _level)
119+
return level >= self.level
57120

58-
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)
121+
def debug(self, message, *args):
122+
self.log(DEBUG, message, *args)
73123

74-
def debug(self, msg, *args):
75-
self.log(DEBUG, msg, *args)
124+
def info(self, message, *args):
125+
self.log(INFO, message, *args)
76126

77-
def info(self, msg, *args):
78-
self.log(INFO, msg, *args)
127+
def warning(self, message, *args):
128+
self.log(WARNING, message, *args)
79129

80-
def warning(self, msg, *args):
81-
self.log(WARNING, msg, *args)
130+
def error(self, message, *args):
131+
self.log(ERROR, message, *args)
82132

83-
def error(self, msg, *args):
84-
self.log(ERROR, msg, *args)
133+
def critical(self, message, *args):
134+
self.log(CRITICAL, message, *args)
85135

86-
def critical(self, msg, *args):
87-
self.log(CRITICAL, msg, *args)
136+
def exception(self, message, *args):
137+
self.log(ERROR, message, *args)
138+
if hasattr(sys, "exc_info"):
139+
for h in filter(lambda h: isinstance(h, StreamHandler), self.handlers):
140+
sys.print_exception(sys.exc_info()[1], h.stream)
88141

89-
def exc(self, e, msg, *args):
90-
self.log(ERROR, msg, *args)
91-
sys.print_exception(e, _stream)
142+
def log(self, level, message, *args):
143+
if self.isEnabledFor(level) and self.handlers:
144+
if args and isinstance(args[0], dict):
145+
args = args[0]
146+
for h in self.handlers:
147+
self.record.set(self.name, level, message % args)
148+
h.emit(self.record)
92149

93-
def exception(self, msg, *args):
94-
self.exc(sys.exc_info()[1], msg, *args)
95150

96-
def addHandler(self, hndlr):
97-
self.handlers.append(hndlr)
151+
def debug(message, *args):
152+
getLogger().log(DEBUG, message, *args)
98153

99154

100-
_level = INFO
101-
_loggers = {}
155+
def info(message, *args):
156+
getLogger().log(INFO, message, *args)
102157

103158

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
159+
def warning(message, *args):
160+
getLogger().log(WARNING, message, *args)
110161

111162

112-
def info(msg, *args):
113-
getLogger().info(msg, *args)
163+
def error(message, *args):
164+
getLogger().log(ERROR, message, *args)
114165

115166

116-
def debug(msg, *args):
117-
getLogger().debug(msg, *args)
167+
def critical(message, *args):
168+
getLogger().log(CRITICAL, message, *args)
118169

119170

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")
171+
def exception(message, *args):
172+
getLogger().exception(message, *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+
def getLogger(name="root"):
183+
if name not in loggers:
184+
loggers[name] = Logger(name)
185+
return loggers[name]
186+
187+
188+
def basicConfig(
189+
filename=None,
190+
filemode="a",
191+
format=default_fmt,
192+
datefmt=default_datefmt,
193+
level=WARNING,
194+
stream=sys.stderr,
195+
encoding="UTF-8",
196+
force=False,
197+
):
198+
logger = getLogger()
199+
if force or not logger.handlers:
200+
for h in logger.handlers:
201+
h.close()
202+
203+
if filename is None:
204+
handler = StreamHandler(stream)
205+
else:
206+
handler = FileHandler(filename, filemode, encoding)
207+
208+
handler.setLevel(level)
209+
handler.setFormatter(Formatter(format, datefmt))
210+
211+
logger.setLevel(level)
212+
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