Skip to content

Commit b1112b6

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 70e422d commit b1112b6

File tree

5 files changed

+209
-105
lines changed

5 files changed

+209
-105
lines changed

python-stdlib/logging/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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, exception
8+
from .logging import shutdown
9+
from .logging import getLogger
10+
from .logging import basicConfig
11+
12+
import sys
13+
14+
if hasattr(sys, "atexit"):
15+
sys.atexit(shutdown)

python-stdlib/logging/example_logging.py

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,39 @@
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)
116

12-
try:
13-
1 / 0
14-
except:
15-
log.exception("Some trouble (%s)", "expected")
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")
1617

18+
# Add formatter to the handlers
19+
stream_handler.setFormatter(formatter)
20+
file_handler.setFormatter(formatter)
1721

18-
class MyHandler(logging.Handler):
19-
def emit(self, record):
20-
print("levelname=%(levelname)s name=%(name)s message=%(message)s" % record.__dict__)
22+
# Add hadlers to logger
23+
logger.addHandler(stream_handler)
24+
logger.addHandler(file_handler)
2125

26+
# Lapplication' code
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.exception("Some trouble (%s)", "expected")
2239

23-
logging.getLogger().addHandler(MyHandler())
24-
logging.info("Test message7")

python-stdlib/logging/logging.py

Lines changed: 160 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,202 @@
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

10-
_level_dict = {
11-
CRITICAL: "CRIT",
12-
ERROR: "ERROR",
13-
WARNING: "WARN",
14-
INFO: "INFO",
11+
levelname = {
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 = "%(levelname)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.level >= 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 Record:
67+
def set(self, name, level, message):
4468
self.name = name
69+
self.level = level
70+
self.message = message
71+
self.ts = time.time()
72+
self.msecs = int((self.ts - int(self.ts)) * 1000)
73+
74+
75+
class Formatter:
76+
def __init__(self, fmt=default_fmt, datefmt=default_datefmt):
77+
self.fmt = fmt
78+
self.datefmt = datefmt
79+
80+
def formatTime(self, datefmt):
81+
if hasattr(time, "strftime"):
82+
return time.strftime(datefmt, time.localtime())
83+
return ""
84+
85+
def format(self, record):
86+
return self.fmt % {
87+
"name": record.name,
88+
"message": record.message,
89+
"msecs": record.msecs,
90+
"asctime": self.formatTime(self.datefmt),
91+
"levelname": levelname[record.level],
92+
}
4593

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
94+
95+
class Logger:
96+
def __init__(self, name):
97+
self.name = name
98+
self.level = NOTSET
99+
self.handlers = []
100+
self.record = Record()
51101

52102
def setLevel(self, level):
53103
self.level = level
54104

55-
def isEnabledFor(self, level):
56-
return level >= (self.level or _level)
105+
def addHandler(self, handler):
106+
self.handlers.append(handler)
57107

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)
108+
def hasHandlers(self):
109+
return len(self.handlers) > 0
73110

74-
def debug(self, msg, *args):
75-
self.log(DEBUG, msg, *args)
111+
def debug(self, message, *args, **kwargs):
112+
self.log(DEBUG, message, *args, **kwargs)
76113

77-
def info(self, msg, *args):
78-
self.log(INFO, msg, *args)
114+
def info(self, message, *args, **kwargs):
115+
self.log(INFO, message, *args, **kwargs)
79116

80-
def warning(self, msg, *args):
81-
self.log(WARNING, msg, *args)
117+
def warning(self, message, *args, **kwargs):
118+
self.log(WARNING, message, *args, **kwargs)
82119

83-
def error(self, msg, *args):
84-
self.log(ERROR, msg, *args)
120+
def error(self, message, *args, **kwargs):
121+
self.log(ERROR, message, *args, **kwargs)
85122

86-
def critical(self, msg, *args):
87-
self.log(CRITICAL, msg, *args)
123+
def critical(self, message, *args, **kwargs):
124+
self.log(CRITICAL, message, *args, **kwargs)
88125

89-
def exc(self, e, msg, *args):
90-
self.log(ERROR, msg, *args)
91-
sys.print_exception(e, _stream)
126+
def exception(self, message, *args, **kwargs):
127+
self.log(ERROR, message, *args, **kwargs)
128+
if hasattr(sys, "exc_info"):
129+
for h in filter(lambda h: isinstance(h, StreamHandler), self.handlers):
130+
sys.print_exception(sys.exc_info()[1], h.stream)
92131

93-
def exception(self, msg, *args):
94-
self.exc(sys.exc_info()[1], msg, *args)
132+
def log(self, level, message, *args, **kwargs):
133+
if level >= self.level:
134+
if args and isinstance(args[0], dict):
135+
args = args[0]
136+
for h in self.handlers:
137+
self.record.set(self.name, level, message % args)
138+
h.emit(self.record)
95139

96-
def addHandler(self, hndlr):
97-
self.handlers.append(hndlr)
98140

141+
def debug(message, *args, **kwargs):
142+
getLogger().log(DEBUG, message, *args, **kwargs)
99143

100-
_level = INFO
101-
_loggers = {}
102144

145+
def info(message, *args, **kwargs):
146+
getLogger().log(INFO, message, *args, **kwargs)
103147

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
148+
149+
def warning(message, *args, **kwargs):
150+
getLogger().log(WARNING, message, *args, **kwargs)
110151

111152

112-
def info(msg, *args):
113-
getLogger().info(msg, *args)
153+
def error(message, *args, **kwargs):
154+
getLogger().log(ERROR, message, *args, **kwargs)
114155

115156

116-
def debug(msg, *args):
117-
getLogger().debug(msg, *args)
157+
def critical(message, *args, **kwargs):
158+
getLogger().log(CRITICAL, message, *args, **kwargs)
118159

119160

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")
161+
def exception(message, *args, **kwargs):
162+
getLogger().exception(message, *args, **kwargs)
163+
164+
165+
def shutdown():
166+
for k, logger in loggers.items():
167+
for h in logger.handlers:
168+
h.close()
169+
loggers.pop(logger, None)
170+
171+
172+
def getLogger(name="root"):
173+
if name not in loggers:
174+
loggers[name] = Logger(name)
175+
return loggers[name]
176+
177+
178+
def basicConfig(
179+
filename=None,
180+
filemode="a",
181+
format=default_fmt,
182+
datefmt=default_datefmt,
183+
level=WARNING,
184+
stream=sys.stderr,
185+
encoding="UTF-8",
186+
force=False,
187+
):
188+
logger = getLogger()
189+
if force or not logger.handlers:
190+
for h in logger.handlers:
191+
h.close()
192+
193+
if filename is None:
194+
handler = StreamHandler(stream)
195+
else:
196+
handler = FileHandler(filename, filemode, encoding)
197+
198+
handler.setLevel(level)
199+
handler.setFormatter(Formatter(format, datefmt))
200+
201+
logger.setLevel(level)
202+
logger.addHandler(handler)

python-stdlib/logging/metadata.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
srctype = micropython-lib
22
type = module
3-
version = 0.3
3+
version = 0.4

python-stdlib/logging/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
setup(
1212
name="micropython-logging",
13-
version="0.3",
13+
version="0.4",
1414
description="logging module for MicroPython",
1515
long_description="This is a module reimplemented specifically for MicroPython standard library,\nwith efficient and lean design in mind. Note that this module is likely work\nin progress and likely supports just a subset of CPython's corresponding\nmodule. Please help with the development if you are interested in this\nmodule.",
1616
url="https://github.com/micropython/micropython-lib",

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