custom-python-logger 2.0.14__tar.gz → 3.0.1__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.
- {custom_python_logger-2.0.14/custom_python_logger.egg-info → custom_python_logger-3.0.1}/PKG-INFO +1 -1
- {custom_python_logger-2.0.14 → custom_python_logger-3.0.1}/custom_python_logger/logger.py +37 -82
- {custom_python_logger-2.0.14 → custom_python_logger-3.0.1/custom_python_logger.egg-info}/PKG-INFO +1 -1
- {custom_python_logger-2.0.14 → custom_python_logger-3.0.1}/custom_python_logger.egg-info/SOURCES.txt +0 -1
- {custom_python_logger-2.0.14 → custom_python_logger-3.0.1}/pyproject.toml +1 -1
- {custom_python_logger-2.0.14 → custom_python_logger-3.0.1}/tests/test_logger.py +4 -4
- {custom_python_logger-2.0.14 → custom_python_logger-3.0.1}/tests/test_logger_pytest.py +12 -11
- {custom_python_logger-2.0.14 → custom_python_logger-3.0.1}/tests/test_usage_example_pytest.py +3 -1
- custom_python_logger-2.0.14/requirements.txt +0 -8
- {custom_python_logger-2.0.14 → custom_python_logger-3.0.1}/LICENSE +0 -0
- {custom_python_logger-2.0.14 → custom_python_logger-3.0.1}/MANIFEST.in +0 -0
- {custom_python_logger-2.0.14 → custom_python_logger-3.0.1}/README.md +0 -0
- {custom_python_logger-2.0.14 → custom_python_logger-3.0.1}/custom_python_logger/__init__.py +0 -0
- {custom_python_logger-2.0.14 → custom_python_logger-3.0.1}/custom_python_logger/consts.py +0 -0
- {custom_python_logger-2.0.14 → custom_python_logger-3.0.1}/custom_python_logger.egg-info/dependency_links.txt +0 -0
- {custom_python_logger-2.0.14 → custom_python_logger-3.0.1}/custom_python_logger.egg-info/requires.txt +0 -0
- {custom_python_logger-2.0.14 → custom_python_logger-3.0.1}/custom_python_logger.egg-info/top_level.txt +0 -0
- {custom_python_logger-2.0.14 → custom_python_logger-3.0.1}/setup.cfg +0 -0
|
@@ -15,7 +15,7 @@ from custom_python_logger.consts import LOG_COLORS, CustomLoggerLevel
|
|
|
15
15
|
CUSTOM_LOGGER = "custom_logger"
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def json_pretty_format(data: Any, indent: int = 4, sort_keys: bool = True, default: Callable = None) -> str:
|
|
18
|
+
def json_pretty_format(data: Any, indent: int = 4, sort_keys: bool = True, default: Callable | None = None) -> str:
|
|
19
19
|
return json.dumps(data, indent=indent, sort_keys=sort_keys, default=default)
|
|
20
20
|
|
|
21
21
|
|
|
@@ -38,14 +38,14 @@ def get_project_path_by_file(markers: set[str] | None = None) -> Path:
|
|
|
38
38
|
raise RuntimeError(f'Project root with one of the markers: "{markers}" not found.')
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
def print_before_logger(project_name: str) -> None:
|
|
41
|
+
def print_before_logger(project_name: str, sleep_time: float = 0.3) -> None:
|
|
42
42
|
main_string = f'Start "{project_name}" Process'
|
|
43
43
|
|
|
44
44
|
number_of_ladder = "#" * len(f"### {main_string} ###")
|
|
45
45
|
print(f"\n{number_of_ladder}")
|
|
46
46
|
print(f"### {main_string} ###")
|
|
47
47
|
print(f"{number_of_ladder}\n")
|
|
48
|
-
time.sleep(
|
|
48
|
+
time.sleep(sleep_time)
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
class CustomLoggerAdapter(logging.LoggerAdapter):
|
|
@@ -59,17 +59,17 @@ class CustomLoggerAdapter(logging.LoggerAdapter):
|
|
|
59
59
|
kwargs.setdefault("stacklevel", 2)
|
|
60
60
|
self.log(CustomLoggerLevel.STEP.value, msg, *args, exc_info=False, **kwargs)
|
|
61
61
|
|
|
62
|
-
def success(self, msg: str, *args, **kwargs) -> None:
|
|
62
|
+
def success(self, msg: str, *args: Any, **kwargs: Any) -> None:
|
|
63
63
|
logging.addLevelName(CustomLoggerLevel.SUCCESS.value, "SUCCESS")
|
|
64
64
|
kwargs.setdefault("stacklevel", 2)
|
|
65
65
|
self.log(CustomLoggerLevel.SUCCESS.value, msg, *args, **kwargs)
|
|
66
66
|
|
|
67
|
-
def alert(self, msg: str, *args, **kwargs) -> None:
|
|
67
|
+
def alert(self, msg: str, *args: Any, **kwargs: Any) -> None:
|
|
68
68
|
logging.addLevelName(CustomLoggerLevel.ALERT.value, "ALERT")
|
|
69
69
|
kwargs.setdefault("stacklevel", 2)
|
|
70
70
|
self.log(CustomLoggerLevel.ALERT.value, msg, *args, **kwargs)
|
|
71
71
|
|
|
72
|
-
def trace(self, msg: str, *args, **kwargs) -> None:
|
|
72
|
+
def trace(self, msg: str, *args: Any, **kwargs: Any) -> None:
|
|
73
73
|
logging.addLevelName(CustomLoggerLevel.TRACE.value, "TRACE")
|
|
74
74
|
kwargs.setdefault("stacklevel", 2)
|
|
75
75
|
self.log(CustomLoggerLevel.TRACE.value, msg, *args, **kwargs)
|
|
@@ -80,77 +80,27 @@ def clear_existing_handlers(logger: Logger) -> None:
|
|
|
80
80
|
logger.removeHandler(handler)
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
def
|
|
84
|
-
|
|
85
|
-
log_file: bool,
|
|
86
|
-
log_file_path: str | None,
|
|
87
|
-
log_format: str,
|
|
88
|
-
) -> None:
|
|
89
|
-
if log_file and log_file_path is not None:
|
|
83
|
+
def add_file_handler(logger: Logger, log_file_path: str | None, log_format: str) -> None:
|
|
84
|
+
if log_file_path is not None:
|
|
90
85
|
log_file_formatter = logging.Formatter(log_format)
|
|
91
86
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if log_dir and not os.path.exists(log_dir):
|
|
95
|
-
try:
|
|
96
|
-
os.makedirs(log_dir)
|
|
97
|
-
except FileExistsError:
|
|
98
|
-
pass # Directory was created by another process
|
|
87
|
+
if log_dir := os.path.dirname(log_file_path):
|
|
88
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
99
89
|
|
|
100
90
|
file_handler = logging.FileHandler(log_file_path)
|
|
101
|
-
|
|
102
91
|
file_handler.setFormatter(log_file_formatter)
|
|
103
92
|
logger.addHandler(file_handler)
|
|
104
93
|
|
|
105
94
|
|
|
106
|
-
def
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
log_colors=LOG_COLORS,
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
console_handler = logging.StreamHandler()
|
|
114
|
-
console_handler.setFormatter(log_console_formatter)
|
|
115
|
-
logger.addHandler(console_handler)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def configure_logging(
|
|
119
|
-
log_format: str,
|
|
120
|
-
utc: bool,
|
|
121
|
-
log_file: bool = False,
|
|
122
|
-
log_file_path: str | None = None,
|
|
123
|
-
console_output: bool = True,
|
|
124
|
-
) -> None:
|
|
125
|
-
"""
|
|
126
|
-
Configure global logging settings.
|
|
127
|
-
|
|
128
|
-
Args:
|
|
129
|
-
log_format: Format string for log messages
|
|
130
|
-
utc: Whether to use UTC time for log timestamps
|
|
131
|
-
log_file: Whether to log to a file
|
|
132
|
-
log_file_path: Path to log file (if None, no file logging)
|
|
133
|
-
console_output: Whether to output logs to console
|
|
134
|
-
"""
|
|
135
|
-
if utc:
|
|
136
|
-
logging.Formatter.converter = time.gmtime
|
|
137
|
-
|
|
138
|
-
root_logger = logging.getLogger()
|
|
139
|
-
|
|
140
|
-
clear_existing_handlers(logger=root_logger)
|
|
141
|
-
|
|
142
|
-
add_file_handler_if_specified(
|
|
143
|
-
logger=root_logger,
|
|
144
|
-
log_file=log_file,
|
|
145
|
-
log_file_path=log_file_path,
|
|
146
|
-
log_format=log_format,
|
|
95
|
+
def add_console_handler(logger: Logger, log_format: str) -> None:
|
|
96
|
+
log_console_formatter = ColoredFormatter(
|
|
97
|
+
"%(log_color)s " + log_format,
|
|
98
|
+
log_colors=LOG_COLORS,
|
|
147
99
|
)
|
|
148
100
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
log_format=log_format,
|
|
153
|
-
)
|
|
101
|
+
console_handler = logging.StreamHandler()
|
|
102
|
+
console_handler.setFormatter(log_console_formatter)
|
|
103
|
+
logger.addHandler(console_handler)
|
|
154
104
|
|
|
155
105
|
|
|
156
106
|
def get_logger(name: str, log_level: int | None = None, extra: dict | None = None) -> CustomLoggerAdapter:
|
|
@@ -165,18 +115,18 @@ def get_logger(name: str, log_level: int | None = None, extra: dict | None = Non
|
|
|
165
115
|
return new_logger
|
|
166
116
|
|
|
167
117
|
|
|
168
|
-
def build_logger(
|
|
118
|
+
def build_logger( # pylint: disable=R0913
|
|
169
119
|
project_name: str,
|
|
170
120
|
extra: dict[str, Any] | None = None,
|
|
171
121
|
log_format: str = "%(asctime)s | %(levelname)-9s | l.%(levelno)s | %(name)s | %(filename)s:%(lineno)s | %(message)s", # pylint: disable=C0301
|
|
172
122
|
log_level: int = logging.DEBUG,
|
|
173
123
|
log_file: bool = False,
|
|
174
|
-
log_file_path: str = None,
|
|
124
|
+
log_file_path: str | None = None,
|
|
175
125
|
console_output: bool = True,
|
|
176
126
|
utc: bool = False,
|
|
177
|
-
) -> CustomLoggerAdapter
|
|
127
|
+
) -> CustomLoggerAdapter:
|
|
178
128
|
"""
|
|
179
|
-
|
|
129
|
+
Configure global logging settings and get a named logger with optional extra context.
|
|
180
130
|
|
|
181
131
|
Args:
|
|
182
132
|
project_name: Name of the project
|
|
@@ -190,17 +140,22 @@ def build_logger(
|
|
|
190
140
|
Returns:
|
|
191
141
|
Configured logger
|
|
192
142
|
"""
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
143
|
+
|
|
144
|
+
if utc:
|
|
145
|
+
logging.Formatter.converter = time.gmtime
|
|
146
|
+
|
|
147
|
+
root_logger = logging.getLogger()
|
|
148
|
+
clear_existing_handlers(logger=root_logger)
|
|
149
|
+
|
|
150
|
+
if console_output:
|
|
151
|
+
add_console_handler(logger=root_logger, log_format=log_format)
|
|
152
|
+
|
|
153
|
+
if log_file:
|
|
154
|
+
if not log_file_path:
|
|
155
|
+
log_file_path = f"{get_project_path_by_file()}/logs/{project_name}.log"
|
|
156
|
+
log_file_path = log_file_path.lower().replace(" ", "_")
|
|
157
|
+
add_file_handler(logger=root_logger, log_file_path=log_file_path, log_format=log_format)
|
|
158
|
+
|
|
204
159
|
logger = CustomLoggerAdapter(logging.getLogger(CUSTOM_LOGGER), extra)
|
|
205
160
|
logger.setLevel(log_level)
|
|
206
161
|
|
|
@@ -4,17 +4,17 @@ from custom_python_logger import build_logger
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class TestLogger(unittest.TestCase):
|
|
7
|
-
def test_logger_creation(self):
|
|
7
|
+
def test_logger_creation(self) -> None:
|
|
8
8
|
logger = build_logger(project_name="TestProject")
|
|
9
9
|
self.assertIsNotNone(logger)
|
|
10
|
-
self.assertEqual(logger.name, "
|
|
10
|
+
self.assertEqual(logger.name, "custom_logger.TestProject")
|
|
11
11
|
|
|
12
|
-
def test_step_log(self):
|
|
12
|
+
def test_step_log(self) -> None:
|
|
13
13
|
logger = build_logger(project_name="TestProject")
|
|
14
14
|
logger.step("Testing step log")
|
|
15
15
|
self.assertTrue(True) # pylint: disable=W1503
|
|
16
16
|
|
|
17
|
-
def test_exception_log(self):
|
|
17
|
+
def test_exception_log(self) -> None:
|
|
18
18
|
logger = build_logger(project_name="TestProject")
|
|
19
19
|
try:
|
|
20
20
|
raise ValueError("Test exception")
|
|
@@ -3,6 +3,7 @@ import logging
|
|
|
3
3
|
import os
|
|
4
4
|
import tempfile
|
|
5
5
|
import time
|
|
6
|
+
from collections.abc import Generator
|
|
6
7
|
|
|
7
8
|
import pytest
|
|
8
9
|
|
|
@@ -10,13 +11,13 @@ from custom_python_logger import CustomLoggerAdapter, build_logger, json_pretty_
|
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
@pytest.fixture
|
|
13
|
-
def temp_log_file():
|
|
14
|
+
def temp_log_file() -> Generator[str, None, None]:
|
|
14
15
|
with tempfile.NamedTemporaryFile(delete=False) as f:
|
|
15
16
|
yield f.name
|
|
16
17
|
os.remove(f.name)
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
def test_logger_creation():
|
|
20
|
+
def test_logger_creation() -> None:
|
|
20
21
|
logger = build_logger(project_name="PytestTest")
|
|
21
22
|
assert logger is not None
|
|
22
23
|
assert isinstance(logger, CustomLoggerAdapter)
|
|
@@ -24,7 +25,7 @@ def test_logger_creation():
|
|
|
24
25
|
assert hasattr(logger, "exception")
|
|
25
26
|
|
|
26
27
|
|
|
27
|
-
def test_step_log(caplog):
|
|
28
|
+
def test_step_log(caplog: pytest.LogCaptureFixture) -> None:
|
|
28
29
|
logger = build_logger(project_name="PytestTest", console_output=False)
|
|
29
30
|
if not isinstance(logger, CustomLoggerAdapter):
|
|
30
31
|
raise AssertionError("Logger is not a CustomLoggerAdapter")
|
|
@@ -36,7 +37,7 @@ def test_step_log(caplog):
|
|
|
36
37
|
assert any("STEP" in r.levelname for r in caplog.records)
|
|
37
38
|
|
|
38
39
|
|
|
39
|
-
def test_step_log_2(caplog):
|
|
40
|
+
def test_step_log_2(caplog: pytest.LogCaptureFixture) -> None:
|
|
40
41
|
logger = build_logger(project_name="TestProject", console_output=False)
|
|
41
42
|
if not isinstance(logger, CustomLoggerAdapter):
|
|
42
43
|
raise AssertionError("Logger is not a CustomLoggerAdapter")
|
|
@@ -47,7 +48,7 @@ def test_step_log_2(caplog):
|
|
|
47
48
|
assert any("STEP" in r.levelname for r in caplog.records)
|
|
48
49
|
|
|
49
50
|
|
|
50
|
-
def test_exception_log(caplog):
|
|
51
|
+
def test_exception_log(caplog: pytest.LogCaptureFixture) -> None:
|
|
51
52
|
logger = build_logger(project_name="PytestTest", console_output=False)
|
|
52
53
|
logging.getLogger().addHandler(caplog.handler)
|
|
53
54
|
with caplog.at_level(logging.ERROR):
|
|
@@ -59,7 +60,7 @@ def test_exception_log(caplog):
|
|
|
59
60
|
assert any("EXCEPTION" in r.levelname for r in caplog.records)
|
|
60
61
|
|
|
61
62
|
|
|
62
|
-
def test_exception_log_2(caplog):
|
|
63
|
+
def test_exception_log_2(caplog: pytest.LogCaptureFixture) -> None:
|
|
63
64
|
logger = build_logger(project_name="TestProject", console_output=False)
|
|
64
65
|
logging.getLogger().addHandler(caplog.handler)
|
|
65
66
|
with caplog.at_level(logging.ERROR):
|
|
@@ -71,7 +72,7 @@ def test_exception_log_2(caplog):
|
|
|
71
72
|
assert any("EXCEPTION" in r.levelname for r in caplog.records)
|
|
72
73
|
|
|
73
74
|
|
|
74
|
-
def test_log_to_file(temp_log_file): # pylint: disable=W0621
|
|
75
|
+
def test_log_to_file(temp_log_file: str) -> None: # pylint: disable=W0621
|
|
75
76
|
logger = build_logger(project_name="FileTest", log_file=True, log_file_path=temp_log_file)
|
|
76
77
|
logger.info("File log message")
|
|
77
78
|
time.sleep(0.1)
|
|
@@ -80,7 +81,7 @@ def test_log_to_file(temp_log_file): # pylint: disable=W0621
|
|
|
80
81
|
assert "File log message" in content
|
|
81
82
|
|
|
82
83
|
|
|
83
|
-
def test_utc_logging(temp_log_file): # pylint: disable=W0621
|
|
84
|
+
def test_utc_logging(temp_log_file: str) -> None: # pylint: disable=W0621
|
|
84
85
|
logger = build_logger(project_name="UTCTest", log_file=True, log_file_path=temp_log_file, utc=True)
|
|
85
86
|
logger.info("UTC log message")
|
|
86
87
|
time.sleep(0.1)
|
|
@@ -92,7 +93,7 @@ def test_utc_logging(temp_log_file): # pylint: disable=W0621
|
|
|
92
93
|
assert now_utc in content
|
|
93
94
|
|
|
94
95
|
|
|
95
|
-
def test_extra_context(caplog):
|
|
96
|
+
def test_extra_context(caplog: pytest.LogCaptureFixture) -> None:
|
|
96
97
|
logger = build_logger(project_name="ExtraTest", extra={"user": "pytest"}, console_output=False)
|
|
97
98
|
logging.getLogger().addHandler(caplog.handler)
|
|
98
99
|
with caplog.at_level(logging.INFO):
|
|
@@ -101,13 +102,13 @@ def test_extra_context(caplog):
|
|
|
101
102
|
# The extra field is not in the default format, but test that logger works with extra
|
|
102
103
|
|
|
103
104
|
|
|
104
|
-
def test_json_pretty_format():
|
|
105
|
+
def test_json_pretty_format() -> None:
|
|
105
106
|
data = {"a": 1, "b": 2}
|
|
106
107
|
result = json_pretty_format(data)
|
|
107
108
|
assert "{" in result and "a" in result and "b" in result
|
|
108
109
|
|
|
109
110
|
|
|
110
|
-
def test_yaml_pretty_format():
|
|
111
|
+
def test_yaml_pretty_format() -> None:
|
|
111
112
|
data = {"a": 1, "b": 2}
|
|
112
113
|
result = yaml_pretty_format(data)
|
|
113
114
|
assert "a:" in result and "b:" in result
|
{custom_python_logger-2.0.14 → custom_python_logger-3.0.1}/tests/test_usage_example_pytest.py
RENAMED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
1
3
|
from usage_example.example_1 import main
|
|
2
4
|
|
|
3
5
|
|
|
4
|
-
def test_usage_example_runs(monkeypatch):
|
|
6
|
+
def test_usage_example_runs(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
5
7
|
# Patch print and time.sleep to avoid side effects
|
|
6
8
|
monkeypatch.setattr("builtins.print", lambda *a, **k: None)
|
|
7
9
|
monkeypatch.setattr("time.sleep", lambda x: None)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|