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.
@@ -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,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: pygrlogging
3
+ Version: 0.1.0
4
+ Summary: Biblioteca para configurar Logs
5
+ Author-email: ruizgabriel161 <ruizgabriel161@outlook.com>
@@ -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
+