jaylog 0.1.4__py3-none-any.whl
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.
- jaylog/__init__.py +5 -0
- jaylog/filters.py +7 -0
- jaylog/formatters.py +110 -0
- jaylog/handlers/__init__.py +4 -0
- jaylog/handlers/file_handler.py +51 -0
- jaylog/handlers/http_handler.py +79 -0
- jaylog/logger.py +116 -0
- jaylog/models.py +16 -0
- jaylog/settings.py +57 -0
- jaylog-0.1.4.dist-info/METADATA +120 -0
- jaylog-0.1.4.dist-info/RECORD +12 -0
- jaylog-0.1.4.dist-info/WHEEL +4 -0
jaylog/__init__.py
ADDED
jaylog/filters.py
ADDED
jaylog/formatters.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import getpass
|
|
2
|
+
import io
|
|
3
|
+
import logging
|
|
4
|
+
import socket
|
|
5
|
+
import sys
|
|
6
|
+
import traceback
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _get_host_info() -> tuple[str, str, str]:
|
|
11
|
+
hostname = socket.gethostname()
|
|
12
|
+
try:
|
|
13
|
+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
|
14
|
+
s.connect(("8.8.8.8", 80))
|
|
15
|
+
host_ip = s.getsockname()[0]
|
|
16
|
+
except OSError:
|
|
17
|
+
host_ip = "unknown"
|
|
18
|
+
try:
|
|
19
|
+
username = getpass.getuser()
|
|
20
|
+
except Exception:
|
|
21
|
+
username = "unknown"
|
|
22
|
+
return username, hostname, host_ip
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
_HOST_USERNAME, _HOSTNAME, _HOST_IP = _get_host_info()
|
|
26
|
+
|
|
27
|
+
_screenshot_enabled: bool = True
|
|
28
|
+
|
|
29
|
+
_MAX_BYTES = 1 * 1024 * 1024 # 1 MB
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def configure_screenshot(enabled: bool) -> None:
|
|
33
|
+
global _screenshot_enabled
|
|
34
|
+
_screenshot_enabled = enabled
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _capture_screenshot() -> bytes | None:
|
|
38
|
+
if not _screenshot_enabled:
|
|
39
|
+
return None
|
|
40
|
+
try:
|
|
41
|
+
from PIL import ImageGrab
|
|
42
|
+
|
|
43
|
+
img = ImageGrab.grab()
|
|
44
|
+
|
|
45
|
+
quality = 85
|
|
46
|
+
scale = 1.0
|
|
47
|
+
buf = io.BytesIO()
|
|
48
|
+
|
|
49
|
+
while True:
|
|
50
|
+
buf.seek(0)
|
|
51
|
+
buf.truncate()
|
|
52
|
+
|
|
53
|
+
current = img
|
|
54
|
+
if scale < 1.0:
|
|
55
|
+
w = int(img.width * scale)
|
|
56
|
+
h = int(img.height * scale)
|
|
57
|
+
current = img.resize((w, h))
|
|
58
|
+
|
|
59
|
+
current.save(buf, format="JPEG", quality=quality, optimize=True)
|
|
60
|
+
|
|
61
|
+
if buf.tell() <= _MAX_BYTES:
|
|
62
|
+
break
|
|
63
|
+
|
|
64
|
+
if quality > 30:
|
|
65
|
+
quality -= 10
|
|
66
|
+
elif scale > 0.5:
|
|
67
|
+
scale -= 0.1
|
|
68
|
+
else:
|
|
69
|
+
break
|
|
70
|
+
|
|
71
|
+
buf.seek(0)
|
|
72
|
+
return buf.read()
|
|
73
|
+
except Exception as exc:
|
|
74
|
+
print(f"[jaylog] screenshot: {exc}", file=sys.stderr)
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def build_log_entry_dict(record: logging.LogRecord) -> dict:
|
|
79
|
+
is_exception = getattr(record, "is_exception", False)
|
|
80
|
+
|
|
81
|
+
log_message = record.getMessage()
|
|
82
|
+
if is_exception and record.exc_info:
|
|
83
|
+
tb = "".join(traceback.format_exception(*record.exc_info)).strip()
|
|
84
|
+
log_message = f"{log_message}\n{tb}"
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
"log_timestamp": datetime.fromtimestamp(record.created, tz=timezone.utc).isoformat(),
|
|
88
|
+
"log_level": "EXCEPTION" if is_exception else record.levelname,
|
|
89
|
+
"is_exception": is_exception,
|
|
90
|
+
"log_message": log_message,
|
|
91
|
+
"service": record.name,
|
|
92
|
+
"username": _HOST_USERNAME,
|
|
93
|
+
"hostname": _HOSTNAME,
|
|
94
|
+
"ipv4": _HOST_IP,
|
|
95
|
+
"service_path": record.pathname,
|
|
96
|
+
"log_img": _capture_screenshot() if (record.levelno >= logging.ERROR or is_exception) else None,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class PlainTextFormatter(logging.Formatter):
|
|
101
|
+
"""Human-readable single-line formatter for .log files."""
|
|
102
|
+
|
|
103
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
104
|
+
entry = build_log_entry_dict(record)
|
|
105
|
+
line = (
|
|
106
|
+
f"{entry['log_timestamp']} [{entry['log_level']}]"
|
|
107
|
+
f" {entry['service']} {entry['hostname']}({entry['ipv4']}) {entry['username']}"
|
|
108
|
+
f" | {entry['log_message']}"
|
|
109
|
+
)
|
|
110
|
+
return line
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from logging.handlers import RotatingFileHandler
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from jaylog.formatters import PlainTextFormatter
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class JaylogFileHandler(RotatingFileHandler):
|
|
9
|
+
"""
|
|
10
|
+
Rotating file handler that:
|
|
11
|
+
- rotates when the file reaches `maxBytes` (default 5 MB)
|
|
12
|
+
- deletes backup files older than `retention_days` (default 7) after each rollover
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
filename: Path,
|
|
18
|
+
max_bytes: int = 5 * 1024 * 1024,
|
|
19
|
+
backup_count: int = 20,
|
|
20
|
+
retention_days: int = 7,
|
|
21
|
+
encoding: str = "utf-8",
|
|
22
|
+
) -> None:
|
|
23
|
+
filename.parent.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
super().__init__(
|
|
25
|
+
filename,
|
|
26
|
+
maxBytes=max_bytes,
|
|
27
|
+
backupCount=backup_count,
|
|
28
|
+
encoding=encoding,
|
|
29
|
+
)
|
|
30
|
+
self.retention_days = retention_days
|
|
31
|
+
self.setFormatter(PlainTextFormatter())
|
|
32
|
+
|
|
33
|
+
def doRollover(self) -> None:
|
|
34
|
+
super().doRollover()
|
|
35
|
+
self._purge_old_logs()
|
|
36
|
+
|
|
37
|
+
def _purge_old_logs(self) -> None:
|
|
38
|
+
base = Path(self.baseFilename)
|
|
39
|
+
cutoff = time.time() - self.retention_days * 86400
|
|
40
|
+
|
|
41
|
+
for entry in base.parent.iterdir():
|
|
42
|
+
if not entry.is_file():
|
|
43
|
+
continue
|
|
44
|
+
# Match rotated backups: app.log.1, app.log.2, ...
|
|
45
|
+
if entry.name == base.name or not entry.name.startswith(base.name):
|
|
46
|
+
continue
|
|
47
|
+
try:
|
|
48
|
+
if entry.stat().st_mtime < cutoff:
|
|
49
|
+
entry.unlink()
|
|
50
|
+
except OSError:
|
|
51
|
+
pass
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import warnings
|
|
4
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
import urllib3
|
|
8
|
+
|
|
9
|
+
from jaylog.formatters import build_log_entry_dict
|
|
10
|
+
from jaylog.settings import JaylogSettings
|
|
11
|
+
|
|
12
|
+
settings = JaylogSettings()
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
_JAYLOG_VERSION = version("jaylog")
|
|
16
|
+
except PackageNotFoundError:
|
|
17
|
+
_JAYLOG_VERSION = "unknown"
|
|
18
|
+
|
|
19
|
+
proxies = {
|
|
20
|
+
'http': settings.log_http_proxy,
|
|
21
|
+
'https': settings.log_http_proxy
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
session = requests.Session()
|
|
25
|
+
if settings.log_http_proxy:
|
|
26
|
+
session.proxies.update(proxies)
|
|
27
|
+
|
|
28
|
+
def _to_multipart(fields: dict) -> dict:
|
|
29
|
+
result = {}
|
|
30
|
+
for key, value in fields.items():
|
|
31
|
+
if value is None:
|
|
32
|
+
continue
|
|
33
|
+
if isinstance(value, bytes):
|
|
34
|
+
result[key] = ("screenshot.jpg", value, "image/jpeg")
|
|
35
|
+
elif isinstance(value, bool):
|
|
36
|
+
result[key] = (None, "true" if value else "false")
|
|
37
|
+
elif not isinstance(value, str):
|
|
38
|
+
result[key] = (None, json.dumps(value))
|
|
39
|
+
else:
|
|
40
|
+
result[key] = (None, value)
|
|
41
|
+
return result
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class JaylogHttpHandler(logging.Handler):
|
|
45
|
+
"""
|
|
46
|
+
HTTP handler that POSTs log records as multipart/form-data to a remote endpoint.
|
|
47
|
+
|
|
48
|
+
Non-blocking behaviour is guaranteed by the QueueListener that drives this
|
|
49
|
+
handler — emit() runs in the listener's background thread.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
endpoint: str,
|
|
55
|
+
api_key: str,
|
|
56
|
+
timeout: float = 5.0,
|
|
57
|
+
) -> None:
|
|
58
|
+
super().__init__()
|
|
59
|
+
self.endpoint = endpoint
|
|
60
|
+
self.timeout = timeout
|
|
61
|
+
self._session = session
|
|
62
|
+
self._session.headers["x-api-key"] = api_key
|
|
63
|
+
self._session.headers["x-jaylog-version"] = _JAYLOG_VERSION
|
|
64
|
+
|
|
65
|
+
def mapLogRecord(self, record: logging.LogRecord) -> dict:
|
|
66
|
+
return build_log_entry_dict(record)
|
|
67
|
+
|
|
68
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
69
|
+
try:
|
|
70
|
+
with warnings.catch_warnings():
|
|
71
|
+
warnings.simplefilter("ignore", urllib3.exceptions.InsecureRequestWarning)
|
|
72
|
+
self._session.post(
|
|
73
|
+
self.endpoint,
|
|
74
|
+
files=_to_multipart(self.mapLogRecord(record)),
|
|
75
|
+
timeout=self.timeout,
|
|
76
|
+
verify=False,
|
|
77
|
+
)
|
|
78
|
+
except Exception:
|
|
79
|
+
pass
|
jaylog/logger.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import signal
|
|
5
|
+
from logging.handlers import QueueHandler, QueueListener
|
|
6
|
+
from queue import Queue
|
|
7
|
+
|
|
8
|
+
from jaylog.filters import ExceptionFlagFilter
|
|
9
|
+
from jaylog.formatters import configure_screenshot
|
|
10
|
+
from jaylog.handlers.file_handler import JaylogFileHandler
|
|
11
|
+
from jaylog.handlers.http_handler import JaylogHttpHandler
|
|
12
|
+
from jaylog.settings import JaylogSettings
|
|
13
|
+
|
|
14
|
+
# Registry: name -> (logger, listener) so callers can shut down cleanly
|
|
15
|
+
_registry: dict[str, tuple[logging.Logger, QueueListener]] = {}
|
|
16
|
+
_shutdown_registered = False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _register_shutdown_hooks() -> None:
|
|
20
|
+
global _shutdown_registered
|
|
21
|
+
if _shutdown_registered:
|
|
22
|
+
return
|
|
23
|
+
_shutdown_registered = True
|
|
24
|
+
|
|
25
|
+
atexit.register(shutdown)
|
|
26
|
+
|
|
27
|
+
def _sigterm_handler(signum, frame): # noqa: ANN001
|
|
28
|
+
shutdown()
|
|
29
|
+
signal.signal(signal.SIGTERM, signal.SIG_DFL)
|
|
30
|
+
signal.raise_signal(signal.SIGTERM)
|
|
31
|
+
|
|
32
|
+
signal.signal(signal.SIGTERM, _sigterm_handler)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_logger(settings: JaylogSettings | None = None) -> logging.Logger:
|
|
36
|
+
"""
|
|
37
|
+
Return a configured logger.
|
|
38
|
+
|
|
39
|
+
The logger name is read from JAYLOG_LOGGER_NAME (default: "jaylog").
|
|
40
|
+
Calling this multiple times with the same logger name returns the **same**
|
|
41
|
+
logger without re-attaching handlers.
|
|
42
|
+
|
|
43
|
+
Architecture:
|
|
44
|
+
logger → QueueHandler → Queue → QueueListener → [FileHandler, HttpHandler?]
|
|
45
|
+
|
|
46
|
+
The QueueListener runs in a background thread so `emit()` never blocks the
|
|
47
|
+
calling thread.
|
|
48
|
+
"""
|
|
49
|
+
if settings is None:
|
|
50
|
+
settings = JaylogSettings()
|
|
51
|
+
|
|
52
|
+
name = settings.app_name
|
|
53
|
+
|
|
54
|
+
configure_screenshot(settings.log_screenshot_enabled)
|
|
55
|
+
|
|
56
|
+
if name in _registry:
|
|
57
|
+
return _registry[name][0]
|
|
58
|
+
|
|
59
|
+
# ------------------------------------------------------------------
|
|
60
|
+
# Build the actual (downstream) handlers
|
|
61
|
+
# ------------------------------------------------------------------
|
|
62
|
+
downstream: list[logging.Handler] = []
|
|
63
|
+
|
|
64
|
+
log_path = settings.log_dir / settings.log_filename
|
|
65
|
+
file_handler = JaylogFileHandler(
|
|
66
|
+
filename=log_path,
|
|
67
|
+
max_bytes=settings.log_max_bytes,
|
|
68
|
+
backup_count=settings.log_backup_count,
|
|
69
|
+
retention_days=settings.log_retention_days,
|
|
70
|
+
)
|
|
71
|
+
file_handler.setLevel(settings.log_level)
|
|
72
|
+
downstream.append(file_handler)
|
|
73
|
+
|
|
74
|
+
if settings.log_http_endpoint and settings.log_http_api_key:
|
|
75
|
+
http_handler = JaylogHttpHandler(
|
|
76
|
+
endpoint=settings.log_http_endpoint,
|
|
77
|
+
api_key=settings.log_http_api_key,
|
|
78
|
+
timeout=settings.log_http_timeout,
|
|
79
|
+
)
|
|
80
|
+
http_handler.setLevel(settings.log_level)
|
|
81
|
+
downstream.append(http_handler)
|
|
82
|
+
|
|
83
|
+
# ------------------------------------------------------------------
|
|
84
|
+
# Wire up the Queue + QueueListener
|
|
85
|
+
# ------------------------------------------------------------------
|
|
86
|
+
queue: Queue = Queue(maxsize=-1) # unbounded
|
|
87
|
+
queue_handler = QueueHandler(queue)
|
|
88
|
+
queue_handler.addFilter(ExceptionFlagFilter())
|
|
89
|
+
|
|
90
|
+
listener = QueueListener(queue, *downstream, respect_handler_level=True)
|
|
91
|
+
listener.start()
|
|
92
|
+
|
|
93
|
+
# ------------------------------------------------------------------
|
|
94
|
+
# Configure the logger
|
|
95
|
+
# ------------------------------------------------------------------
|
|
96
|
+
logger = logging.getLogger(name)
|
|
97
|
+
logger.setLevel(settings.log_level)
|
|
98
|
+
logger.addHandler(queue_handler)
|
|
99
|
+
logger.propagate = False
|
|
100
|
+
|
|
101
|
+
_registry[name] = (logger, listener)
|
|
102
|
+
_register_shutdown_hooks()
|
|
103
|
+
return logger
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def shutdown(name: str | None = None) -> None:
|
|
107
|
+
"""
|
|
108
|
+
Stop the QueueListener(s) gracefully, flushing any remaining records.
|
|
109
|
+
|
|
110
|
+
Pass a logger `name` to stop a single logger, or omit to stop all.
|
|
111
|
+
"""
|
|
112
|
+
targets = [name] if name else list(_registry.keys())
|
|
113
|
+
for n in targets:
|
|
114
|
+
if n in _registry:
|
|
115
|
+
_, listener = _registry.pop(n)
|
|
116
|
+
listener.stop()
|
jaylog/models.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LogEntry(BaseModel):
|
|
7
|
+
log_timestamp: datetime
|
|
8
|
+
log_level: str
|
|
9
|
+
is_exception: bool
|
|
10
|
+
log_message: str
|
|
11
|
+
service: str
|
|
12
|
+
username: str
|
|
13
|
+
hostname: str
|
|
14
|
+
ipv4: str
|
|
15
|
+
service_path: str
|
|
16
|
+
log_img: Optional[str] = None
|
jaylog/settings.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import computed_field, field_validator
|
|
5
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
6
|
+
|
|
7
|
+
from jaylog.formatters import _HOSTNAME, _HOST_USERNAME
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class JaylogSettings(BaseSettings):
|
|
11
|
+
model_config = SettingsConfigDict(
|
|
12
|
+
env_prefix="JAYLOG_",
|
|
13
|
+
env_file=('.env.logging', '.env'),
|
|
14
|
+
env_file_encoding='utf-8',
|
|
15
|
+
secrets_dir='secrets',
|
|
16
|
+
extra="ignore"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# App identity
|
|
20
|
+
app_name: str
|
|
21
|
+
log_dir: Path
|
|
22
|
+
|
|
23
|
+
secrets_dir: Path | None = None
|
|
24
|
+
|
|
25
|
+
# File handler
|
|
26
|
+
log_level: str = "INFO"
|
|
27
|
+
log_max_bytes: int = 5 * 1024 * 1024 # 5 MB
|
|
28
|
+
log_backup_count: int = 5
|
|
29
|
+
log_retention_days: int = 7
|
|
30
|
+
|
|
31
|
+
# HTTP handler
|
|
32
|
+
log_http_endpoint: Optional[str] = None
|
|
33
|
+
log_http_api_key: Optional[str] = None
|
|
34
|
+
log_http_timeout: float = 5.0
|
|
35
|
+
log_http_proxy: Optional[str] = None
|
|
36
|
+
|
|
37
|
+
# Screenshot (log_img field) — desativar com JAYLOG_LOG_SCREENSHOT_ENABLED=false
|
|
38
|
+
log_screenshot_enabled: bool = False
|
|
39
|
+
|
|
40
|
+
@field_validator("log_dir", mode="after")
|
|
41
|
+
@classmethod
|
|
42
|
+
def validate_log_dir(cls, v: Path) -> Path:
|
|
43
|
+
if v.exists() and not v.is_dir():
|
|
44
|
+
raise ValueError(f"JAYLOG_LOG_DIR '{v}' exists but is not a directory")
|
|
45
|
+
return v
|
|
46
|
+
|
|
47
|
+
@computed_field
|
|
48
|
+
@property
|
|
49
|
+
def log_filename(self) -> Path:
|
|
50
|
+
return Path(f"{self.app_name}_{_HOSTNAME}_{_HOST_USERNAME}.log")
|
|
51
|
+
|
|
52
|
+
def reload_secrets(self):
|
|
53
|
+
if self.secrets_dir:
|
|
54
|
+
if not self.secrets_dir.exists() or not self.secrets_dir.is_dir:
|
|
55
|
+
raise ValueError('SECRETS_DIR is not valid directory')
|
|
56
|
+
|
|
57
|
+
return JaylogSettings(_secrets_dir=self.secrets_dir) # ty:ignore[missing-argument, unknown-argument]
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: jaylog
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: Customized Python logging library with file rotation and HTTP forwarding
|
|
5
|
+
Author: Gpocas
|
|
6
|
+
Author-email: Gpocas <gpocas01@gmail.com>
|
|
7
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
8
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
9
|
+
Classifier: Natural Language :: Portuguese (Brazilian)
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Requires-Dist: pydantic>=2.0,<3.0
|
|
12
|
+
Requires-Dist: pydantic-settings>=2.0,<3.0
|
|
13
|
+
Requires-Dist: requests>=2.32,<3.0
|
|
14
|
+
Requires-Dist: pillow>=11.0,<13.0
|
|
15
|
+
Requires-Python: >=3.10, <3.14
|
|
16
|
+
Project-URL: repository, https://github.com/Gpocas/jaylog
|
|
17
|
+
Project-URL: Bug Tracker, https://github.com/Gpocas/jaylog/issues
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# jaylog
|
|
21
|
+
|
|
22
|
+
Biblioteca de logging para Python com rotação de arquivos e envio HTTP para um endpoint remoto.
|
|
23
|
+
|
|
24
|
+
## Instalação
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install -U --no-cache-dir git+https://github.com/Gpocas/jaylog.git
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Variáveis de ambiente
|
|
31
|
+
|
|
32
|
+
As variáveis usam o prefixo `JAYLOG_`. Podem ser definidas no ambiente do sistema ou em um arquivo `.env` / `.env.logging` na raiz do projeto.
|
|
33
|
+
|
|
34
|
+
| Variável | obrigatorio? | Padrão | Descrição |
|
|
35
|
+
| ------------------------------- | ------------ | --------- | ----------------------------------------------------------------------- |
|
|
36
|
+
| `JAYLOG_APP_NAME` | SIM | `null` | Nome do serviço/bot (usado no nome do arquivo de log) |
|
|
37
|
+
| `JAYLOG_LOG_DIR` | SIM | `null` | Caminho do diretório onde os arquivos de log serão salvos |
|
|
38
|
+
| `JAYLOG_LOG_LEVEL` | NÃO | `INFO` | Nível mínimo de log (`DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`) |
|
|
39
|
+
| `JAYLOG_LOG_MAX_BYTES` | NÃO | `5242880` | Tamanho máximo do arquivo de log antes de rotacionar (bytes) |
|
|
40
|
+
| `JAYLOG_LOG_BACKUP_COUNT` | NÃO | `5` | Quantidade de arquivos de backup mantidos após rotação |
|
|
41
|
+
| `JAYLOG_LOG_RETENTION_DAYS` | NÃO | `7` | Dias para manter arquivos de log antigos |
|
|
42
|
+
| `JAYLOG_LOG_HTTP_TIMEOUT` | NÃO | `5.0` | Timeout em segundos para o envio HTTP |
|
|
43
|
+
| `JAYLOG_LOG_HTTP_ENDPOINT` | NÃO | `null` | URL do endpoint que receberá os logs |
|
|
44
|
+
| `JAYLOG_LOG_HTTP_API_KEY` | NÃO | `null` | Chave de autenticação enviada no header `x-api-key` |
|
|
45
|
+
| `JAYLOG_LOG_HTTP_PROXY` | NÃO | `null` | URL do proxy para o envio HTTP (ex: `http:\\user:password@server:port`) |
|
|
46
|
+
| `JAYLOG_LOG_SCREENSHOT_ENABLED` | NÃO | `false` | Captura screenshot no momento do log (`true`/`false`, apenas Windows) |
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Uso Simples
|
|
50
|
+
|
|
51
|
+
__*.env.logging*__
|
|
52
|
+
```env
|
|
53
|
+
JAYLOG_APP_NAME=meu-bot
|
|
54
|
+
JAYLOG_LOG_DIR=C:\logs
|
|
55
|
+
```
|
|
56
|
+
__*main.py*__
|
|
57
|
+
```python
|
|
58
|
+
from jaylog import JaylogSettings, get_logger
|
|
59
|
+
|
|
60
|
+
logger = get_logger(JaylogSettings())
|
|
61
|
+
|
|
62
|
+
logger.info("Mensagem de log")
|
|
63
|
+
logger.error("Erro ao processar")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
## Alterando Caminho padrão do .env
|
|
68
|
+
|
|
69
|
+
__*development.env*__
|
|
70
|
+
```env
|
|
71
|
+
JAYLOG_APP_NAME=meu-bot
|
|
72
|
+
JAYLOG_LOG_DIR=C:\logs
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
__*main.py*__
|
|
76
|
+
```python
|
|
77
|
+
from jaylog import JaylogSettings, get_logger
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
settings = JaylogSettings(_env_file='development.env')
|
|
81
|
+
logger = get_logger(settings)
|
|
82
|
+
|
|
83
|
+
logger.info("Mensagem de log")
|
|
84
|
+
logger.error("Erro ao processar")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
## Preparando para produção
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
> [!IMPORTANT]
|
|
92
|
+
> **HTTP_ENDPOINT** e **HTTP_API_KEY** (opcionais) 📢
|
|
93
|
+
>
|
|
94
|
+
> - A configuração **HTTP_ENDPOINT** e **HTTP_API_KEY** não precisa ser feita em ambiente local ou de desenvolvimento
|
|
95
|
+
> - Se apenas uma das duas variaveis **HTTP_ENDPOINT** ou **HTTP_API_KEYS** for definida, o envio HTTP é ignorado.
|
|
96
|
+
> - Caso a aplicação execute em um ambiente que usa um proxy ntlm, defina `JAYLOG_LOG_HTTP_PROXY`
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
__*prodution.env*__
|
|
100
|
+
```env
|
|
101
|
+
JAYLOG_APP_NAME=mlleu-bot
|
|
102
|
+
JAYLOG_LOG_DIR=C:\logs
|
|
103
|
+
JAYLOG_LOG_HTTP_ENDPOINT=https://meu-backend.com/logs/add
|
|
104
|
+
JAYLOG_LOG_HTTP_API_KEY=minha-chave
|
|
105
|
+
JAYLOG_LOG_HTTP_PROXY=http://username:password@proxy.com:8080
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
__*main.py*__
|
|
109
|
+
```python
|
|
110
|
+
from jaylog import JaylogSettings, get_logger
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
settings = JaylogSettings(_env_file='prodution.env', _secrets_dir='/caminho/secrets/')
|
|
114
|
+
logger = get_logger(settings)
|
|
115
|
+
|
|
116
|
+
logger.info("Mensagem de log")
|
|
117
|
+
logger.error("Erro ao processar")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
jaylog/__init__.py,sha256=g4E1iDrLr37thM7SJ01yvw3gcXwnsAGOG-3sa1oi4vA,193
|
|
2
|
+
jaylog/filters.py,sha256=Eky1NvcSTyCgtr6R8BnyyKv6r1u-u2RvY4iZ4whBbtE,224
|
|
3
|
+
jaylog/formatters.py,sha256=s6IBkb7QRiH0gJlCgE7r7k9pCjX7EC0hmcwq-davD68,3058
|
|
4
|
+
jaylog/handlers/__init__.py,sha256=JxJ8Dy-ttJAZGC91gv73DAdithc8qYtIvwdzLMKT05E,142
|
|
5
|
+
jaylog/handlers/file_handler.py,sha256=p-kZRb4CN27z94w_MYTYULy8uE27t6_Wqo1Sp7jJwpc,1563
|
|
6
|
+
jaylog/handlers/http_handler.py,sha256=Sn3zCmgTOBwiivz8aq5VEXo0cDwhqOUPAWE6JFViUMc,2309
|
|
7
|
+
jaylog/logger.py,sha256=MGP33JiPZMlHsewDUYdAcDpzmXTjs8joUP7AnCY3oao,3879
|
|
8
|
+
jaylog/models.py,sha256=3B7tnFuLMfgmRuJbbqguyVvnpdGS7qLyY6E6_1C-m1w,332
|
|
9
|
+
jaylog/settings.py,sha256=YcUBCsW4YuHf1EwwHgTwDw6UwmC-u6sn-viZZhndlZo,1768
|
|
10
|
+
jaylog-0.1.4.dist-info/WHEEL,sha256=GuAqCqoyQuys5_R4zkHUJFlKXw4RpRLNzo31-ui90WQ,81
|
|
11
|
+
jaylog-0.1.4.dist-info/METADATA,sha256=iqI4pyMUttuCz1IlBUzkh-thpYMXhk6uOnOG9RnAR08,4526
|
|
12
|
+
jaylog-0.1.4.dist-info/RECORD,,
|