162 lines
4.1 KiB
Python
162 lines
4.1 KiB
Python
import sys
|
|
import codecs
|
|
import threading
|
|
import queue
|
|
from traceback import format_exc
|
|
from appPublic.timeUtils import timestampstr
|
|
from appPublic.Singleton import SingletonDecorator
|
|
import inspect
|
|
|
|
def my_function():
|
|
frame_info = inspect.currentframe()
|
|
caller_frame = frame_info.f_back
|
|
file_name = inspect.getframeinfo(caller_frame).filename
|
|
line_number = inspect.getframeinfo(caller_frame).lineno
|
|
|
|
|
|
@SingletonDecorator
|
|
class MyLogger:
|
|
levels={
|
|
"clientinfo":7,
|
|
"info":6,
|
|
"debug":5,
|
|
"warning":4,
|
|
"error":3,
|
|
"exception":2,
|
|
"critical":1
|
|
}
|
|
formater='%(timestamp)s[%(name)s][%(levelname)s][%(filename)s:%(lineno)s]%(message)s\n'
|
|
def __init__(self, name, levelname='debug', logfile=None):
|
|
self.name = name
|
|
self.levelname = levelname
|
|
self.level = self.levels.get(levelname)
|
|
self.logfile = logfile
|
|
self.logger = None
|
|
# Async write queue + background thread
|
|
self._q = queue.Queue(maxsize=10000)
|
|
self._worker = threading.Thread(target=self._write_loop, daemon=True)
|
|
self._worker.start()
|
|
|
|
def _get_logger(self):
|
|
"""Lazy open file handle, keep it open."""
|
|
if self.logger is not None:
|
|
return self.logger
|
|
if self.logfile:
|
|
self.logger = codecs.open(self.logfile, 'a', 'utf-8')
|
|
else:
|
|
self.logger = sys.stdout
|
|
return self.logger
|
|
|
|
def _write_loop(self):
|
|
"""Background thread: drain queue and write to file."""
|
|
fh = None
|
|
while True:
|
|
try:
|
|
item = self._q.get(timeout=1.0)
|
|
if item is None:
|
|
# Poison pill: shut down
|
|
if fh is not None:
|
|
try:
|
|
fh.flush()
|
|
if fh is not sys.stdout:
|
|
fh.close()
|
|
except Exception:
|
|
pass
|
|
break
|
|
if fh is None:
|
|
fh = self._get_logger()
|
|
fh.write(item)
|
|
# Only flush on critical/exception to avoid blocking on slow disks
|
|
if item.find('[exception]') >= 0 or item.find('[critical]') >= 0:
|
|
fh.flush()
|
|
except queue.Empty:
|
|
# Periodic flush to prevent data loss on crash
|
|
if fh is not None:
|
|
try:
|
|
fh.flush()
|
|
except Exception:
|
|
pass
|
|
except Exception:
|
|
pass
|
|
|
|
def log(self, levelname, message, frame_info):
|
|
caller_frame = frame_info.f_back
|
|
filename = inspect.getframeinfo(caller_frame).filename
|
|
lineno = inspect.getframeinfo(caller_frame).lineno
|
|
level = self.levels.get(levelname)
|
|
if level > self.level:
|
|
return
|
|
data = {
|
|
'timestamp': timestampstr(),
|
|
'name': self.name,
|
|
'levelname': levelname,
|
|
'message': message,
|
|
'filename': filename,
|
|
'lineno': lineno
|
|
}
|
|
s = self.formater % data
|
|
try:
|
|
self._q.put_nowait(s)
|
|
except queue.Full:
|
|
# Queue full: drop oldest (non-blocking, never stall the event loop)
|
|
try:
|
|
self._q.get_nowait()
|
|
self._q.put_nowait(s)
|
|
except Exception:
|
|
pass
|
|
|
|
def debug_params(name, d, maxlen=100):
|
|
"""Compact debug output: show all keys but truncate long values."""
|
|
if not isinstance(d, dict):
|
|
debug(f'{name}: {d}')
|
|
return
|
|
summary = {}
|
|
for k, v in d.items():
|
|
s = str(v)
|
|
if len(s) > maxlen:
|
|
summary[k] = s[:maxlen] + f'...({len(s)}ch)'
|
|
elif isinstance(v, list) and len(v) > 3:
|
|
summary[k] = f'[{len(v)} items]'
|
|
else:
|
|
summary[k] = v
|
|
debug(f'{name}: {summary}')
|
|
|
|
|
|
|
|
def clientinfo(message):
|
|
frame_info = inspect.currentframe()
|
|
logger = MyLogger('Test')
|
|
logger.log('clientinfo', message, frame_info)
|
|
|
|
def info(message):
|
|
frame_info = inspect.currentframe()
|
|
logger = MyLogger('Test')
|
|
logger.log('info', message, frame_info)
|
|
|
|
def debug(message):
|
|
frame_info = inspect.currentframe()
|
|
logger = MyLogger('Test')
|
|
logger.log('debug', message, frame_info)
|
|
|
|
def warning(message):
|
|
frame_info = inspect.currentframe()
|
|
logger = MyLogger('Test')
|
|
logger.log('warning', message, frame_info)
|
|
|
|
def error(message):
|
|
frame_info = inspect.currentframe()
|
|
logger = MyLogger('Test')
|
|
logger.log('error', message, frame_info)
|
|
|
|
def critical(message):
|
|
frame_info = inspect.currentframe()
|
|
logger = MyLogger('Test')
|
|
logger.log('critical', message, frame_info)
|
|
|
|
def exception(message):
|
|
frame_info = inspect.currentframe()
|
|
tb_msg = format_exc()
|
|
msg = f'{message}\n{tb_msg}'
|
|
logger = MyLogger('exception')
|
|
logger.log('exception', msg, frame_info)
|