69 lines
1.8 KiB
Python
69 lines
1.8 KiB
Python
import sys
|
|
import time
|
|
|
|
LEVELS = ("DEBUG", "INFO", "WARNING", "ERROR")
|
|
|
|
|
|
class Record:
|
|
__slots__ = ("ts", "level", "msg", "context")
|
|
|
|
def __init__(self, ts, level, msg, context):
|
|
self.ts = ts
|
|
self.level = level
|
|
self.msg = msg
|
|
self.context = context
|
|
|
|
def render(self):
|
|
ctx = " ".join(f"{k}={v}" for k, v in sorted(self.context.items()))
|
|
return f"{self.ts:.6f} {self.level:<7} {self.msg} {ctx}".rstrip()
|
|
|
|
|
|
class BufferedLogger:
|
|
def __init__(self, name, level="INFO", stream=None, capacity=64):
|
|
self.name = name
|
|
self.stream = stream if stream is not None else sys.stderr
|
|
self.capacity = capacity
|
|
self._context = {}
|
|
self._buffer = []
|
|
self.set_level(level)
|
|
|
|
def set_level(self, level):
|
|
level = level.upper()
|
|
if level not in LEVELS:
|
|
raise ValueError(f"unknown level {level!r}")
|
|
self.level = level
|
|
|
|
def push(self, **fields):
|
|
self._context.update(fields)
|
|
|
|
def clear(self):
|
|
self._context.clear()
|
|
|
|
def _enabled(self, level):
|
|
return LEVELS.index(level) >= LEVELS.index(self.level)
|
|
|
|
def _log(self, level, msg):
|
|
if not self._enabled(level):
|
|
return
|
|
self._buffer.append(Record(time.time(), level, msg, dict(self._context)))
|
|
if len(self._buffer) >= self.capacity:
|
|
self.flush()
|
|
|
|
def debug(self, msg):
|
|
self._log("DEBUG", msg)
|
|
|
|
def info(self, msg):
|
|
self._log("INFO", msg)
|
|
|
|
def warning(self, msg):
|
|
self._log("WARNING", msg)
|
|
|
|
def error(self, msg):
|
|
self._log("ERROR", msg)
|
|
|
|
def flush(self):
|
|
for rec in self._buffer:
|
|
self.stream.write(f"[{self.name}] {rec.render()}\n")
|
|
self.stream.flush()
|
|
self._buffer.clear()
|