pygrlogging 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygrlogging-0.1.0/.gitignore +22 -0
- pygrlogging-0.1.0/PKG-INFO +5 -0
- pygrlogging-0.1.0/README.md +1 -0
- pygrlogging-0.1.0/pyproject.toml +23 -0
- pygrlogging-0.1.0/src/pygrlogging/filters.py +12 -0
- pygrlogging-0.1.0/src/pygrlogging/formatters.py +84 -0
- pygrlogging-0.1.0/src/pygrlogging/handlers.py +19 -0
- pygrlogging-0.1.0/src/pygrlogging/logging_config.py +110 -0
- pygrlogging-0.1.0/src/pygrlogging/settings_log.py +89 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
uv.lock
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# logs
|
|
15
|
+
logs/
|
|
16
|
+
logging.json
|
|
17
|
+
|
|
18
|
+
#python-version
|
|
19
|
+
.python-version
|
|
20
|
+
|
|
21
|
+
#environments
|
|
22
|
+
.env
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pygrlogging"
|
|
3
|
+
authors = [
|
|
4
|
+
{name = "ruizgabriel161",email = "ruizgabriel161@outlook.com"}
|
|
5
|
+
]
|
|
6
|
+
version = "0.1.0"
|
|
7
|
+
description = "Biblioteca para configurar Logs"
|
|
8
|
+
# ============================
|
|
9
|
+
# Build e Setuptools
|
|
10
|
+
# ============================
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["hatchling"]
|
|
14
|
+
build-backend = "hatchling.build"
|
|
15
|
+
|
|
16
|
+
[tool.hatch.build.targets.wheel]
|
|
17
|
+
packages = ['./src/pygrlogging']
|
|
18
|
+
|
|
19
|
+
[tool.ruff]
|
|
20
|
+
extend = '../../pyproject.toml'
|
|
21
|
+
|
|
22
|
+
[tool.pyright]
|
|
23
|
+
extends = '../../pyproject.toml'
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MaxLevelFilter(logging.Filter):
|
|
5
|
+
def __init__(self, max_level: str) -> None:
|
|
6
|
+
|
|
7
|
+
super().__init__()
|
|
8
|
+
|
|
9
|
+
self.max_level = logging.getLevelNamesMapping().get(max_level.upper(), 30)
|
|
10
|
+
|
|
11
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
12
|
+
return record.levelno <= self.max_level
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from datetime import datetime, timedelta, timezone
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, override
|
|
5
|
+
from zoneinfo import ZoneInfo
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
LOG_RECORD_KEYS = [
|
|
9
|
+
"name",
|
|
10
|
+
"msg",
|
|
11
|
+
"args",
|
|
12
|
+
"levelname",
|
|
13
|
+
"levelno",
|
|
14
|
+
"pathname",
|
|
15
|
+
"filename",
|
|
16
|
+
"module",
|
|
17
|
+
"exc_info",
|
|
18
|
+
"exc_text",
|
|
19
|
+
"stack_info",
|
|
20
|
+
"lineno",
|
|
21
|
+
"funcName",
|
|
22
|
+
"created",
|
|
23
|
+
"msecs",
|
|
24
|
+
"relativeCreated",
|
|
25
|
+
"thread",
|
|
26
|
+
"threadName",
|
|
27
|
+
"processName",
|
|
28
|
+
"process",
|
|
29
|
+
"taskName",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class JSONLogFormatter(logging.Formatter):
|
|
34
|
+
"""docstring for JSONLogFormatter."""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
include_keys: list[str] | None = None,
|
|
39
|
+
datefmt: str = "%Y-%m-%dT%H:%M:%S%z",
|
|
40
|
+
) -> None:
|
|
41
|
+
super(JSONLogFormatter, self).__init__()
|
|
42
|
+
self.include_keys = (
|
|
43
|
+
include_keys if include_keys is not None else LOG_RECORD_KEYS
|
|
44
|
+
)
|
|
45
|
+
self.datefmt = datefmt
|
|
46
|
+
|
|
47
|
+
@override
|
|
48
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
49
|
+
dict_record: dict[str, Any] = {
|
|
50
|
+
key: getattr(record, key)
|
|
51
|
+
for key in self.include_keys
|
|
52
|
+
if LOG_RECORD_KEYS and getattr(record, key, None) is not None
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if "created" in dict_record:
|
|
56
|
+
#sobreescreve o formato de data e hora
|
|
57
|
+
dict_record["created"] = self.formatTime(
|
|
58
|
+
record=record, datefmt=self.datefmt
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if "message" in self.include_keys:
|
|
62
|
+
#
|
|
63
|
+
dict_record["message"] = record.getMessage()
|
|
64
|
+
|
|
65
|
+
if "exc_info" in dict_record and record.exc_info:
|
|
66
|
+
dict_record["exc_info"] = self.formatException(record.exc_info)
|
|
67
|
+
|
|
68
|
+
if "stack_info" in dict_record and record.stack_info:
|
|
69
|
+
dict_record["exc_info"] = self.formatStack(record.stack_info)
|
|
70
|
+
|
|
71
|
+
for key, val in vars(record).items():
|
|
72
|
+
if key in LOG_RECORD_KEYS:
|
|
73
|
+
continue
|
|
74
|
+
dict_record[key] = val
|
|
75
|
+
|
|
76
|
+
return json.dumps(obj=dict_record, default=str)
|
|
77
|
+
|
|
78
|
+
@override
|
|
79
|
+
def formatTime(self, record: logging.LogRecord, datefmt: str | None = None) -> str:
|
|
80
|
+
tz_sp = timezone(timedelta(hours=-3))
|
|
81
|
+
date = datetime.fromtimestamp(record.created, tz=tz_sp)
|
|
82
|
+
if datefmt:
|
|
83
|
+
return date.strftime(datefmt)
|
|
84
|
+
return date.isoformat()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
from logging import LogRecord
|
|
3
|
+
from logging.handlers import QueueHandler
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.logging import RichHandler
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MyRichHandler(RichHandler):
|
|
12
|
+
def __init__(self, file: Literal['stdout', 'stderr'], **kwargs: Any) -> None:
|
|
13
|
+
super().__init__(**kwargs)
|
|
14
|
+
|
|
15
|
+
if file not in ['stdout', 'stderr']:
|
|
16
|
+
ValueError('file in not stdout or stderr')
|
|
17
|
+
|
|
18
|
+
console = Console(file=getattr(sys,file))
|
|
19
|
+
self.console = console
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from logging.config import dictConfig
|
|
5
|
+
from logging.handlers import QueueHandler, QueueListener
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LoggingConfig:
|
|
10
|
+
"""
|
|
11
|
+
Classe responsável por abstrair as configurações do logging
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
_instance = None
|
|
15
|
+
_initialized: bool = False
|
|
16
|
+
|
|
17
|
+
def __new__(cls, *args, **kwargs):
|
|
18
|
+
"""
|
|
19
|
+
__new__ Implementação do padrão singleton para evitar multiplas istânciais
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
cls: instância da classe
|
|
23
|
+
"""
|
|
24
|
+
if cls._instance is None:
|
|
25
|
+
cls._instance = super().__new__(cls)
|
|
26
|
+
return cls._instance
|
|
27
|
+
|
|
28
|
+
def __init__(self, config_file: str | Path, logs_dir: str | Path):
|
|
29
|
+
|
|
30
|
+
if self._initialized:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
self.config_file: Path = Path(config_file).resolve()
|
|
34
|
+
self.logs_dir = Path(logs_dir).resolve()
|
|
35
|
+
self._queue_handler: QueueHandler | None = None
|
|
36
|
+
self._queue_listener: QueueListener | None = None
|
|
37
|
+
self.logger: logging.Logger = logging.getLogger("config_setup")
|
|
38
|
+
self.logger.setLevel(logging.DEBUG)
|
|
39
|
+
self._setup_logging()
|
|
40
|
+
|
|
41
|
+
def _setup_logging(self) -> None:
|
|
42
|
+
"""
|
|
43
|
+
_setup_logging Método responsável a configuração dos logs da aplicação
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
config: dict = self._load_config()
|
|
48
|
+
|
|
49
|
+
# Carrega as configuração dos json para o log
|
|
50
|
+
dictConfig(config=config)
|
|
51
|
+
|
|
52
|
+
self._queue_handler = self._get_queue_handler()
|
|
53
|
+
|
|
54
|
+
self._setup_queue_listener()
|
|
55
|
+
self.logger.debug("Logging configurado com sucesso")
|
|
56
|
+
|
|
57
|
+
except Exception as e:
|
|
58
|
+
self.logger.error(f"Erro ao configurar logging: {e}")
|
|
59
|
+
raise
|
|
60
|
+
|
|
61
|
+
def _setup_queue_listener(self) -> None:
|
|
62
|
+
if self._queue_handler is not None:
|
|
63
|
+
self._queue_listener = self._queue_handler.listener
|
|
64
|
+
if self._queue_listener is not None:
|
|
65
|
+
# caso exista um handler queue inicia o listener
|
|
66
|
+
self._queue_listener.start()
|
|
67
|
+
atexit.register(self._stop_queue_listener)
|
|
68
|
+
|
|
69
|
+
def _stop_queue_listener(self) -> None:
|
|
70
|
+
if self._queue_listener is not None:
|
|
71
|
+
self._queue_listener.stop()
|
|
72
|
+
|
|
73
|
+
def _get_queue_handler(self) -> QueueHandler | None:
|
|
74
|
+
|
|
75
|
+
queue_handlers = [
|
|
76
|
+
handler
|
|
77
|
+
for handler in logging.getLogger().handlers
|
|
78
|
+
if isinstance(handler, QueueHandler)
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
if len(queue_handlers) > 1:
|
|
82
|
+
msg = "Não é permitido mais de um QueueHandler"
|
|
83
|
+
self.logger.exception(msg=msg)
|
|
84
|
+
raise RuntimeError(msg)
|
|
85
|
+
|
|
86
|
+
if len(queue_handlers) == 1:
|
|
87
|
+
return queue_handlers[0]
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
def _load_config(self) -> dict:
|
|
91
|
+
"""
|
|
92
|
+
_load_config Método para ler o json de configuração
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
dict: retorna o json em dicionário
|
|
96
|
+
"""
|
|
97
|
+
with self.config_file.open("r", encoding="utf8") as file:
|
|
98
|
+
return json.load(file)
|
|
99
|
+
|
|
100
|
+
def get_logger(self, name: str = "") -> logging.Logger:
|
|
101
|
+
"""
|
|
102
|
+
Retorna um logger configurado.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
name: Nome do logger (geralmente __name__)
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Logger configurado
|
|
109
|
+
"""
|
|
110
|
+
return logging.getLogger(name)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Literal, TypeAlias
|
|
3
|
+
|
|
4
|
+
from pydantic import field_validator
|
|
5
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
6
|
+
|
|
7
|
+
LogLevel: TypeAlias = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
8
|
+
|
|
9
|
+
class SettingsLooging(BaseSettings):
|
|
10
|
+
model_config: SettingsConfigDict = SettingsConfigDict(
|
|
11
|
+
env_file='.env', # determina onde está armazena as variaveis de ambiente,
|
|
12
|
+
extra='ignore', # permite variáveis desconhecidas,
|
|
13
|
+
case_sensitive=True
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
ROOT_DIR: Path = Path(".").resolve()
|
|
17
|
+
LOGS_DIR: Path = Path("logs")
|
|
18
|
+
LOGGING_CONFIG_JSON: Path = Path("logging.json")
|
|
19
|
+
|
|
20
|
+
# -------------------------
|
|
21
|
+
# Logger config
|
|
22
|
+
# -------------------------
|
|
23
|
+
|
|
24
|
+
SETUP_LOGGER_NAME: str = "setup"
|
|
25
|
+
SETUP_LOGGER_LEVEL: LogLevel = "WARNING"
|
|
26
|
+
DEFAULT_LOGGER_LEVEL: LogLevel = "WARNING"
|
|
27
|
+
|
|
28
|
+
@field_validator('LOGS_DIR')
|
|
29
|
+
@classmethod
|
|
30
|
+
def validate_logs_dir(cls, path: Path | str) -> Path:
|
|
31
|
+
'''
|
|
32
|
+
validate_logs_dir Método responsável por verificar e garantir a existência de uma página de logs
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
path (Path | str): caminho do arquivo
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Path: caminho da pasta de logs
|
|
39
|
+
'''
|
|
40
|
+
if isinstance(path, str):
|
|
41
|
+
path = Path(path).resolve()
|
|
42
|
+
|
|
43
|
+
if not path.is_dir():
|
|
44
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
return path
|
|
46
|
+
|
|
47
|
+
@field_validator('LOGGING_CONFIG_JSON')
|
|
48
|
+
@classmethod
|
|
49
|
+
def validate_logs_file(cls, path: Path) -> Path:
|
|
50
|
+
'''
|
|
51
|
+
validate_logs_file Método responsável por verificar a existência do arquivo de configuração
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
path (Path): Caminho do arquivo
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
FileNotFoundError: Caso o arquivo não for encontrado
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Path: retorna o caminho
|
|
61
|
+
'''
|
|
62
|
+
|
|
63
|
+
if isinstance(path, str):
|
|
64
|
+
path = Path(path).resolve()
|
|
65
|
+
if not path.is_file():
|
|
66
|
+
raise FileNotFoundError(path)
|
|
67
|
+
return path
|
|
68
|
+
|
|
69
|
+
@field_validator('DEFAULT_LOGGER_LEVEL')
|
|
70
|
+
@classmethod
|
|
71
|
+
def validate_level(cls, level: str) -> LogLevel:
|
|
72
|
+
'''
|
|
73
|
+
validate_level Método responsável por verificar o level do log
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
level (str): level padrão do log
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
ValueError: erro caso o level não exista
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
LogLevel: retorna o level padrão
|
|
83
|
+
'''
|
|
84
|
+
level = level.upper()
|
|
85
|
+
if level not in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]:
|
|
86
|
+
msg = 'Level escolhido não suportado pelo Logging'
|
|
87
|
+
raise ValueError(msg)
|
|
88
|
+
return level #pyright: ignore
|
|
89
|
+
|