Skip to content

Commit ea2051c

Browse files
author
Guilherme Costa
committed
logger_manage.py async logger
Signed-off-by: Guilherme Costa <guiherme.costa@blockstec.com>
1 parent e852346 commit ea2051c

1 file changed

Lines changed: 139 additions & 0 deletions

File tree

‎BlocksScreen/logger_manager.py‎

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
from __future__ import annotations
2+
import json
3+
import logging
4+
import os
5+
import sys
6+
from logging.handlers import RotatingFileHandler, QueueHandler, QueueListener
7+
import contextvars
8+
from queue import SimpleQueue
9+
from typing import Optional, Dict, Any
10+
11+
_correlation_id = contextvars.ContextVar("correlation_id", default=None)
12+
13+
def set_correlation_id(corr_id: Optional[str]) -> None:
14+
_correlation_id.set(corr_id)
15+
16+
def get_correlation_id() -> Optional[str]:
17+
return _correlation_id.get()
18+
19+
class JsonFormatter(logging.Formatter):
20+
def __init__(self, static_fields: Optional[Dict[str, Any]] = None):
21+
super().__init__()
22+
self.static_fields = static_fields or {}
23+
24+
def format(self, record: logging.LogRecord) -> str:
25+
payload: Dict[str, Any] = {
26+
"level": record.levelname.lower(),
27+
"message": record.getMessage(),
28+
"logger": record.name,
29+
"module": record.module,
30+
"function": record.funcName,
31+
"line": record.lineno,
32+
"process": record.process,
33+
"thread": record.thread,
34+
}
35+
if self.static_fields:
36+
payload.update(self.static_fields)
37+
corr = getattr(record, "correlation_id", None)
38+
if corr:
39+
payload["correlation_id"] = corr
40+
for k, v in record.__dict__.items():
41+
if k in payload or k.startswith("_"):
42+
continue
43+
if k in ("args","msg","exc_info","exc_text","stack_info","msecs",
44+
"relativeCreated","levelno","pathname","filename",
45+
"threadName","processName","created","name","lineno",
46+
"funcName","module"):
47+
continue
48+
if isinstance(v, (str, int, float, bool)) or v is None:
49+
payload[k] = v
50+
else:
51+
try:
52+
json.dumps(v); payload[k] = v
53+
except Exception:
54+
payload[k] = repr(v)
55+
if record.exc_info:
56+
payload["exc_info"] = self.formatException(record.exc_info)
57+
if record.stack_info:
58+
payload["stack_info"] = self.formatStack(record.stack_info)
59+
return json.dumps(payload, ensure_ascii=False)
60+
61+
class HumanFormatter(logging.Formatter):
62+
def __init__(self, show_logger=True):
63+
fmt = "%(levelname)s - "
64+
fmt += "%(name)s - " if show_logger else ""
65+
fmt += "%(message)s"
66+
super().__init__(fmt)
67+
68+
class CorrelationAdapter(logging.LoggerAdapter):
69+
def __init__(self, logger: logging.Logger, static_fields: Optional[Dict[str, Any]] = None):
70+
super().__init__(logger, extra=static_fields or {})
71+
72+
def process(self, msg, kwargs):
73+
extra = dict(kwargs.get("extra", {}))
74+
if "correlation_id" not in extra:
75+
corr = get_correlation_id()
76+
if corr:
77+
extra["correlation_id"] = corr
78+
for k, v in self.extra.items():
79+
extra.setdefault(k, v)
80+
kwargs["extra"] = extra
81+
return msg, kwargs
82+
83+
def get_logger(
84+
name: str = "BlocksLogger",
85+
*,
86+
env: str = "dev",
87+
level: Optional[str] = None,
88+
log_file: Optional[str] = None,
89+
rotate_max_bytes: int = 2_000_000,
90+
rotate_backups: int = 3,
91+
json_console: bool = False,
92+
service: Optional[str] = None,
93+
) -> logging.LoggerAdapter:
94+
95+
eff_level = level.upper() if level else ("DEBUG" if env == "dev" else "INFO")
96+
lvl = getattr(logging, eff_level, logging.INFO)
97+
98+
logger = logging.getLogger(name)
99+
logger.setLevel(lvl)
100+
logger.propagate = False
101+
if logger.hasHandlers():
102+
logger.handlers.clear()
103+
104+
# Formatters
105+
human_fmt = HumanFormatter(show_logger=True)
106+
json_fmt = JsonFormatter(static_fields={"service": service or name, "env": env})
107+
108+
# Handlers reais
109+
handlers = []
110+
111+
console_handler = logging.StreamHandler(sys.stdout)
112+
console_handler.setLevel(lvl)
113+
console_handler.setFormatter(json_fmt if json_console else human_fmt)
114+
handlers.append(console_handler)
115+
116+
if log_file:
117+
os.makedirs(os.path.dirname(log_file), exist_ok=True)
118+
file_handler = RotatingFileHandler(
119+
log_file, maxBytes=rotate_max_bytes, backupCount=rotate_backups, encoding="utf-8"
120+
)
121+
file_handler.setLevel(lvl)
122+
file_handler.setFormatter(json_fmt)
123+
handlers.append(file_handler)
124+
125+
126+
127+
log_queue = SimpleQueue()
128+
queue_handler = QueueHandler(log_queue)
129+
queue_handler.setLevel(lvl)
130+
logger.addHandler(queue_handler)
131+
132+
listener = QueueListener(log_queue, *handlers, respect_handler_level=True)
133+
listener.start()
134+
135+
return CorrelationAdapter(logger, static_fields={"service": service or name, "env": env})
136+
137+
__all__ = [
138+
"get_logger", "set_correlation_id", "get_correlation_id",
139+
"JsonFormatter", "HumanFormatter", "CorrelationAdapter",

0 commit comments

Comments
 (0)