custom-python-logger 3.0.2__tar.gz → 4.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-3.0.2/custom_python_logger.egg-info → custom_python_logger-4.0.1}/PKG-INFO +50 -5
- {custom_python_logger-3.0.2 → custom_python_logger-4.0.1}/README.md +49 -4
- {custom_python_logger-3.0.2 → custom_python_logger-4.0.1}/custom_python_logger/__init__.py +7 -0
- {custom_python_logger-3.0.2 → custom_python_logger-4.0.1}/custom_python_logger/consts.py +5 -0
- {custom_python_logger-3.0.2 → custom_python_logger-4.0.1}/custom_python_logger/logger.py +33 -3
- {custom_python_logger-3.0.2 → custom_python_logger-4.0.1/custom_python_logger.egg-info}/PKG-INFO +50 -5
- {custom_python_logger-3.0.2 → custom_python_logger-4.0.1}/custom_python_logger.egg-info/SOURCES.txt +1 -0
- {custom_python_logger-3.0.2 → custom_python_logger-4.0.1}/pyproject.toml +1 -1
- custom_python_logger-4.0.1/tests/test_short_path_filter.py +161 -0
- {custom_python_logger-3.0.2 → custom_python_logger-4.0.1}/LICENSE +0 -0
- {custom_python_logger-3.0.2 → custom_python_logger-4.0.1}/MANIFEST.in +0 -0
- {custom_python_logger-3.0.2 → custom_python_logger-4.0.1}/custom_python_logger.egg-info/dependency_links.txt +0 -0
- {custom_python_logger-3.0.2 → custom_python_logger-4.0.1}/custom_python_logger.egg-info/requires.txt +0 -0
- {custom_python_logger-3.0.2 → custom_python_logger-4.0.1}/custom_python_logger.egg-info/top_level.txt +0 -0
- {custom_python_logger-3.0.2 → custom_python_logger-4.0.1}/setup.cfg +0 -0
- {custom_python_logger-3.0.2 → custom_python_logger-4.0.1}/tests/test_logger.py +0 -0
- {custom_python_logger-3.0.2 → custom_python_logger-4.0.1}/tests/test_logger_pytest.py +0 -0
- {custom_python_logger-3.0.2 → custom_python_logger-4.0.1}/tests/test_usage_example_pytest.py +0 -0
{custom_python_logger-3.0.2/custom_python_logger.egg-info → custom_python_logger-4.0.1}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: custom-python-logger
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.1
|
|
4
4
|
Summary: A custom logger with color support and additional features.
|
|
5
5
|
Author: Avi Zaguri
|
|
6
6
|
License: MIT
|
|
@@ -42,6 +42,7 @@ Easily integrate structured, readable, and context-rich logging into your Python
|
|
|
42
42
|
- ✅ **Contextual Logging**: Add extra fields (like user, environment, etc.) to every log message.
|
|
43
43
|
- ✅ **UTC Support**: Optionally log timestamps in UTC for consistency across environments.
|
|
44
44
|
- ✅ **Pretty Formatting**: Built-in helpers for pretty-printing JSON and YAML data in logs.
|
|
45
|
+
- ✅ **Short Path Display**: Automatically trims log file paths to project-relative or `.venv`-relative format for cleaner output.
|
|
45
46
|
- ✅ **Easy Integration**: Simple API for getting a ready-to-use logger anywhere in your codebase.
|
|
46
47
|
|
|
47
48
|
---
|
|
@@ -80,6 +81,7 @@ logger.critical("This is a critical message.")
|
|
|
80
81
|
```
|
|
81
82
|
|
|
82
83
|
#### Advanced Usage
|
|
84
|
+
|
|
83
85
|
- Log to a file:
|
|
84
86
|
```python
|
|
85
87
|
from custom_python_logger import build_logger
|
|
@@ -111,19 +113,62 @@ logger.critical("This is a critical message.")
|
|
|
111
113
|
logger.info(yaml_pretty_format({'foo': 'bar'}))
|
|
112
114
|
```
|
|
113
115
|
|
|
114
|
-
-
|
|
116
|
+
- Use an existing logger with a custom name:
|
|
115
117
|
```python
|
|
116
118
|
from custom_python_logger import get_logger
|
|
117
119
|
|
|
118
120
|
logger = get_logger('some-name')
|
|
119
121
|
|
|
120
|
-
logger.debug("This is a debug message.")
|
|
121
|
-
logger.info("This is an info message.")
|
|
122
|
-
logger.step("This is a step message.")
|
|
122
|
+
logger.debug("This is a debug message.")
|
|
123
|
+
logger.info("This is an info message.")
|
|
124
|
+
logger.step("This is a step message.")
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
- Use a custom log format:
|
|
128
|
+
```python
|
|
129
|
+
from custom_python_logger import build_logger, LOG_FORMAT_FILENAME, LOG_FORMAT_SHORTPATH
|
|
130
|
+
|
|
131
|
+
# Default — shows project-relative or .venv-relative path:
|
|
132
|
+
# 2026-05-18 | INFO | l.20 | my_app | my_project/app/main.py:42 | message
|
|
133
|
+
logger = build_logger(project_name='MyApp', log_format=LOG_FORMAT_SHORTPATH)
|
|
134
|
+
|
|
135
|
+
# Classic — shows filename only (no path):
|
|
136
|
+
# 2026-05-18 | INFO | l.20 | my_app | main.py:42 | message
|
|
137
|
+
logger = build_logger(project_name='MyApp', log_format=LOG_FORMAT_FILENAME)
|
|
123
138
|
```
|
|
124
139
|
|
|
125
140
|
---
|
|
126
141
|
|
|
142
|
+
## 🗂️ Short Path Display
|
|
143
|
+
|
|
144
|
+
By default, `build_logger` uses `LOG_FORMAT_SHORTPATH`, which trims the file path in every log line:
|
|
145
|
+
|
|
146
|
+
| Path type | Raw `record.pathname` | Displayed as |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| Project file | `/home/user/my_project/app/main.py` | `my_project/app/main.py` |
|
|
149
|
+
| Dependency in `.venv` | `/home/user/my_project/.venv/lib/python3.13/site-packages/urllib3/pool.py` | `.venv/lib/python3.13/site-packages/urllib3/pool.py` |
|
|
150
|
+
| Unrecognised path | `/tmp/some_script.py` | `/tmp/some_script.py` (full path) |
|
|
151
|
+
|
|
152
|
+
### Setting your project name
|
|
153
|
+
|
|
154
|
+
The short-path logic uses the `PROJECT_NAME` environment variable to identify your project root.
|
|
155
|
+
Set it in your `.env` file (loaded automatically on import) or export it before running:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# .env
|
|
159
|
+
PROJECT_NAME=my_project
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# or inline
|
|
164
|
+
PROJECT_NAME=my_project python main.py
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
> **Note:** `custom-python-logger` calls `load_dotenv()` on import, which reads your `.env` file automatically.
|
|
168
|
+
> If you set `PROJECT_NAME` programmatically, do so **before** importing `custom_python_logger` to ensure it takes effect.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
127
172
|
## 🤝 Contributing
|
|
128
173
|
If you have a helpful tool, pattern, or improvement to suggest:
|
|
129
174
|
Fork the repo <br>
|
|
@@ -20,6 +20,7 @@ Easily integrate structured, readable, and context-rich logging into your Python
|
|
|
20
20
|
- ✅ **Contextual Logging**: Add extra fields (like user, environment, etc.) to every log message.
|
|
21
21
|
- ✅ **UTC Support**: Optionally log timestamps in UTC for consistency across environments.
|
|
22
22
|
- ✅ **Pretty Formatting**: Built-in helpers for pretty-printing JSON and YAML data in logs.
|
|
23
|
+
- ✅ **Short Path Display**: Automatically trims log file paths to project-relative or `.venv`-relative format for cleaner output.
|
|
23
24
|
- ✅ **Easy Integration**: Simple API for getting a ready-to-use logger anywhere in your codebase.
|
|
24
25
|
|
|
25
26
|
---
|
|
@@ -58,6 +59,7 @@ logger.critical("This is a critical message.")
|
|
|
58
59
|
```
|
|
59
60
|
|
|
60
61
|
#### Advanced Usage
|
|
62
|
+
|
|
61
63
|
- Log to a file:
|
|
62
64
|
```python
|
|
63
65
|
from custom_python_logger import build_logger
|
|
@@ -89,19 +91,62 @@ logger.critical("This is a critical message.")
|
|
|
89
91
|
logger.info(yaml_pretty_format({'foo': 'bar'}))
|
|
90
92
|
```
|
|
91
93
|
|
|
92
|
-
-
|
|
94
|
+
- Use an existing logger with a custom name:
|
|
93
95
|
```python
|
|
94
96
|
from custom_python_logger import get_logger
|
|
95
97
|
|
|
96
98
|
logger = get_logger('some-name')
|
|
97
99
|
|
|
98
|
-
logger.debug("This is a debug message.")
|
|
99
|
-
logger.info("This is an info message.")
|
|
100
|
-
logger.step("This is a step message.")
|
|
100
|
+
logger.debug("This is a debug message.")
|
|
101
|
+
logger.info("This is an info message.")
|
|
102
|
+
logger.step("This is a step message.")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- Use a custom log format:
|
|
106
|
+
```python
|
|
107
|
+
from custom_python_logger import build_logger, LOG_FORMAT_FILENAME, LOG_FORMAT_SHORTPATH
|
|
108
|
+
|
|
109
|
+
# Default — shows project-relative or .venv-relative path:
|
|
110
|
+
# 2026-05-18 | INFO | l.20 | my_app | my_project/app/main.py:42 | message
|
|
111
|
+
logger = build_logger(project_name='MyApp', log_format=LOG_FORMAT_SHORTPATH)
|
|
112
|
+
|
|
113
|
+
# Classic — shows filename only (no path):
|
|
114
|
+
# 2026-05-18 | INFO | l.20 | my_app | main.py:42 | message
|
|
115
|
+
logger = build_logger(project_name='MyApp', log_format=LOG_FORMAT_FILENAME)
|
|
101
116
|
```
|
|
102
117
|
|
|
103
118
|
---
|
|
104
119
|
|
|
120
|
+
## 🗂️ Short Path Display
|
|
121
|
+
|
|
122
|
+
By default, `build_logger` uses `LOG_FORMAT_SHORTPATH`, which trims the file path in every log line:
|
|
123
|
+
|
|
124
|
+
| Path type | Raw `record.pathname` | Displayed as |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| Project file | `/home/user/my_project/app/main.py` | `my_project/app/main.py` |
|
|
127
|
+
| Dependency in `.venv` | `/home/user/my_project/.venv/lib/python3.13/site-packages/urllib3/pool.py` | `.venv/lib/python3.13/site-packages/urllib3/pool.py` |
|
|
128
|
+
| Unrecognised path | `/tmp/some_script.py` | `/tmp/some_script.py` (full path) |
|
|
129
|
+
|
|
130
|
+
### Setting your project name
|
|
131
|
+
|
|
132
|
+
The short-path logic uses the `PROJECT_NAME` environment variable to identify your project root.
|
|
133
|
+
Set it in your `.env` file (loaded automatically on import) or export it before running:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# .env
|
|
137
|
+
PROJECT_NAME=my_project
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# or inline
|
|
142
|
+
PROJECT_NAME=my_project python main.py
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
> **Note:** `custom-python-logger` calls `load_dotenv()` on import, which reads your `.env` file automatically.
|
|
146
|
+
> If you set `PROJECT_NAME` programmatically, do so **before** importing `custom_python_logger` to ensure it takes effect.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
105
150
|
## 🤝 Contributing
|
|
106
151
|
If you have a helpful tool, pattern, or improvement to suggest:
|
|
107
152
|
Fork the repo <br>
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
from dotenv import load_dotenv
|
|
2
|
+
|
|
3
|
+
from custom_python_logger.consts import LOG_FORMAT_FILENAME, LOG_FORMAT_SHORTPATH
|
|
1
4
|
from custom_python_logger.logger import (
|
|
2
5
|
CustomLoggerAdapter,
|
|
3
6
|
CustomLoggerLevel,
|
|
@@ -7,6 +10,8 @@ from custom_python_logger.logger import (
|
|
|
7
10
|
yaml_pretty_format,
|
|
8
11
|
)
|
|
9
12
|
|
|
13
|
+
load_dotenv()
|
|
14
|
+
|
|
10
15
|
__all__ = [
|
|
11
16
|
"CustomLoggerAdapter",
|
|
12
17
|
"CustomLoggerLevel",
|
|
@@ -14,4 +19,6 @@ __all__ = [
|
|
|
14
19
|
"get_logger",
|
|
15
20
|
"json_pretty_format",
|
|
16
21
|
"yaml_pretty_format",
|
|
22
|
+
"LOG_FORMAT_SHORTPATH",
|
|
23
|
+
"LOG_FORMAT_FILENAME",
|
|
17
24
|
]
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
|
|
3
|
+
LOG_FORMAT_FILENAME = "%(asctime)s | %(levelname)-9s | l.%(levelno)s | %(name)s | %(filename)s:%(lineno)s | %(message)s"
|
|
4
|
+
LOG_FORMAT_SHORTPATH = (
|
|
5
|
+
"%(asctime)s | %(levelname)-9s | l.%(levelno)s | %(name)s | %(shortpath)s:%(lineno)s | %(message)s"
|
|
6
|
+
)
|
|
7
|
+
|
|
3
8
|
LOG_COLORS = {
|
|
4
9
|
"DEBUG": "white",
|
|
5
10
|
"INFO": "green",
|
|
@@ -10,7 +10,7 @@ from typing import Any
|
|
|
10
10
|
import yaml
|
|
11
11
|
from colorlog import ColoredFormatter
|
|
12
12
|
|
|
13
|
-
from custom_python_logger.consts import LOG_COLORS, CustomLoggerLevel
|
|
13
|
+
from custom_python_logger.consts import LOG_COLORS, LOG_FORMAT_FILENAME, CustomLoggerLevel
|
|
14
14
|
|
|
15
15
|
CUSTOM_LOGGER = "custom_logger"
|
|
16
16
|
|
|
@@ -48,6 +48,30 @@ def print_before_logger(project_name: str, sleep_time: float = 0.3) -> None:
|
|
|
48
48
|
time.sleep(sleep_time)
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
class _ShortPathFilter(logging.Filter):
|
|
52
|
+
def __init__(self) -> None:
|
|
53
|
+
super().__init__()
|
|
54
|
+
self._project_name = os.getenv("PROJECT_NAME")
|
|
55
|
+
|
|
56
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
57
|
+
if self._project_name and self._project_name in record.pathname:
|
|
58
|
+
record.shortpath = self._project_name + record.pathname.rsplit(self._project_name, 1)[1]
|
|
59
|
+
else:
|
|
60
|
+
record.shortpath = record.pathname
|
|
61
|
+
|
|
62
|
+
if ".venv" in record.pathname:
|
|
63
|
+
record.shortpath = ".venv" + record.pathname.rsplit(".venv", 1)[1]
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class _StripNamespaceFilter(logging.Filter):
|
|
68
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
69
|
+
prefix = CUSTOM_LOGGER + "."
|
|
70
|
+
if record.name.startswith(prefix):
|
|
71
|
+
record.name = record.name[len(prefix) :]
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
|
|
51
75
|
class CustomLoggerAdapter(logging.LoggerAdapter):
|
|
52
76
|
def exception(self, msg: str, *args: Any, **kwargs: Any) -> None:
|
|
53
77
|
logging.addLevelName(CustomLoggerLevel.EXCEPTION.value, "EXCEPTION")
|
|
@@ -105,8 +129,11 @@ def add_console_handler(logger: Logger, log_format: str) -> None:
|
|
|
105
129
|
|
|
106
130
|
def get_logger(name: str, log_level: int | None = None, extra: dict | None = None) -> CustomLoggerAdapter:
|
|
107
131
|
custom_logger = logging.getLogger(CUSTOM_LOGGER)
|
|
132
|
+
|
|
108
133
|
full_name = f"{CUSTOM_LOGGER}.{name}"
|
|
109
|
-
|
|
134
|
+
underlying = logging.getLogger(full_name)
|
|
135
|
+
underlying.addFilter(_StripNamespaceFilter())
|
|
136
|
+
new_logger = CustomLoggerAdapter(underlying, extra=extra)
|
|
110
137
|
|
|
111
138
|
if log_level is None:
|
|
112
139
|
log_level = custom_logger.level
|
|
@@ -118,7 +145,7 @@ def get_logger(name: str, log_level: int | None = None, extra: dict | None = Non
|
|
|
118
145
|
def build_logger( # pylint: disable=R0913
|
|
119
146
|
project_name: str,
|
|
120
147
|
extra: dict[str, Any] | None = None,
|
|
121
|
-
log_format: str =
|
|
148
|
+
log_format: str = LOG_FORMAT_FILENAME,
|
|
122
149
|
log_level: int = logging.DEBUG,
|
|
123
150
|
log_file: bool = False,
|
|
124
151
|
log_file_path: str | None = None,
|
|
@@ -156,6 +183,9 @@ def build_logger( # pylint: disable=R0913
|
|
|
156
183
|
log_file_path = log_file_path.lower().replace(" ", "_")
|
|
157
184
|
add_file_handler(logger=root_logger, log_file_path=log_file_path, log_format=log_format)
|
|
158
185
|
|
|
186
|
+
for handler in root_logger.handlers:
|
|
187
|
+
handler.addFilter(_ShortPathFilter())
|
|
188
|
+
|
|
159
189
|
logger = CustomLoggerAdapter(logging.getLogger(CUSTOM_LOGGER), extra)
|
|
160
190
|
logger.setLevel(log_level)
|
|
161
191
|
|
{custom_python_logger-3.0.2 → custom_python_logger-4.0.1/custom_python_logger.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: custom-python-logger
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.1
|
|
4
4
|
Summary: A custom logger with color support and additional features.
|
|
5
5
|
Author: Avi Zaguri
|
|
6
6
|
License: MIT
|
|
@@ -42,6 +42,7 @@ Easily integrate structured, readable, and context-rich logging into your Python
|
|
|
42
42
|
- ✅ **Contextual Logging**: Add extra fields (like user, environment, etc.) to every log message.
|
|
43
43
|
- ✅ **UTC Support**: Optionally log timestamps in UTC for consistency across environments.
|
|
44
44
|
- ✅ **Pretty Formatting**: Built-in helpers for pretty-printing JSON and YAML data in logs.
|
|
45
|
+
- ✅ **Short Path Display**: Automatically trims log file paths to project-relative or `.venv`-relative format for cleaner output.
|
|
45
46
|
- ✅ **Easy Integration**: Simple API for getting a ready-to-use logger anywhere in your codebase.
|
|
46
47
|
|
|
47
48
|
---
|
|
@@ -80,6 +81,7 @@ logger.critical("This is a critical message.")
|
|
|
80
81
|
```
|
|
81
82
|
|
|
82
83
|
#### Advanced Usage
|
|
84
|
+
|
|
83
85
|
- Log to a file:
|
|
84
86
|
```python
|
|
85
87
|
from custom_python_logger import build_logger
|
|
@@ -111,19 +113,62 @@ logger.critical("This is a critical message.")
|
|
|
111
113
|
logger.info(yaml_pretty_format({'foo': 'bar'}))
|
|
112
114
|
```
|
|
113
115
|
|
|
114
|
-
-
|
|
116
|
+
- Use an existing logger with a custom name:
|
|
115
117
|
```python
|
|
116
118
|
from custom_python_logger import get_logger
|
|
117
119
|
|
|
118
120
|
logger = get_logger('some-name')
|
|
119
121
|
|
|
120
|
-
logger.debug("This is a debug message.")
|
|
121
|
-
logger.info("This is an info message.")
|
|
122
|
-
logger.step("This is a step message.")
|
|
122
|
+
logger.debug("This is a debug message.")
|
|
123
|
+
logger.info("This is an info message.")
|
|
124
|
+
logger.step("This is a step message.")
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
- Use a custom log format:
|
|
128
|
+
```python
|
|
129
|
+
from custom_python_logger import build_logger, LOG_FORMAT_FILENAME, LOG_FORMAT_SHORTPATH
|
|
130
|
+
|
|
131
|
+
# Default — shows project-relative or .venv-relative path:
|
|
132
|
+
# 2026-05-18 | INFO | l.20 | my_app | my_project/app/main.py:42 | message
|
|
133
|
+
logger = build_logger(project_name='MyApp', log_format=LOG_FORMAT_SHORTPATH)
|
|
134
|
+
|
|
135
|
+
# Classic — shows filename only (no path):
|
|
136
|
+
# 2026-05-18 | INFO | l.20 | my_app | main.py:42 | message
|
|
137
|
+
logger = build_logger(project_name='MyApp', log_format=LOG_FORMAT_FILENAME)
|
|
123
138
|
```
|
|
124
139
|
|
|
125
140
|
---
|
|
126
141
|
|
|
142
|
+
## 🗂️ Short Path Display
|
|
143
|
+
|
|
144
|
+
By default, `build_logger` uses `LOG_FORMAT_SHORTPATH`, which trims the file path in every log line:
|
|
145
|
+
|
|
146
|
+
| Path type | Raw `record.pathname` | Displayed as |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| Project file | `/home/user/my_project/app/main.py` | `my_project/app/main.py` |
|
|
149
|
+
| Dependency in `.venv` | `/home/user/my_project/.venv/lib/python3.13/site-packages/urllib3/pool.py` | `.venv/lib/python3.13/site-packages/urllib3/pool.py` |
|
|
150
|
+
| Unrecognised path | `/tmp/some_script.py` | `/tmp/some_script.py` (full path) |
|
|
151
|
+
|
|
152
|
+
### Setting your project name
|
|
153
|
+
|
|
154
|
+
The short-path logic uses the `PROJECT_NAME` environment variable to identify your project root.
|
|
155
|
+
Set it in your `.env` file (loaded automatically on import) or export it before running:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# .env
|
|
159
|
+
PROJECT_NAME=my_project
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# or inline
|
|
164
|
+
PROJECT_NAME=my_project python main.py
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
> **Note:** `custom-python-logger` calls `load_dotenv()` on import, which reads your `.env` file automatically.
|
|
168
|
+
> If you set `PROJECT_NAME` programmatically, do so **before** importing `custom_python_logger` to ensure it takes effect.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
127
172
|
## 🤝 Contributing
|
|
128
173
|
If you have a helpful tool, pattern, or improvement to suggest:
|
|
129
174
|
Fork the repo <br>
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# pylint: disable=E1101 # LogRecord.shortpath is a dynamic attribute set by _ShortPathFilter
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
5
|
+
from collections.abc import Generator
|
|
6
|
+
from unittest.mock import patch
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from custom_python_logger import build_logger
|
|
11
|
+
from custom_python_logger.logger import _ShortPathFilter
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _make_record(pathname: str, name: str = "custom_logger.test") -> logging.LogRecord:
|
|
15
|
+
record = logging.LogRecord(
|
|
16
|
+
name=name,
|
|
17
|
+
level=logging.DEBUG,
|
|
18
|
+
pathname=pathname,
|
|
19
|
+
lineno=1,
|
|
20
|
+
msg="test",
|
|
21
|
+
args=(),
|
|
22
|
+
exc_info=None,
|
|
23
|
+
)
|
|
24
|
+
record.pathname = pathname
|
|
25
|
+
return record
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def temp_log_file() -> Generator[str, None, None]: # pylint: disable=W0621
|
|
30
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".log") as f:
|
|
31
|
+
yield f.name
|
|
32
|
+
os.remove(f.name)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestShortPathFilterInit:
|
|
36
|
+
def test_caches_project_name_from_env(self) -> None:
|
|
37
|
+
with patch.dict(os.environ, {"PROJECT_NAME": "my_project"}):
|
|
38
|
+
f = _ShortPathFilter()
|
|
39
|
+
assert f._project_name == "my_project", f"Expected 'my_project', got {f._project_name}" # pylint: disable=W0212
|
|
40
|
+
|
|
41
|
+
def test_project_name_none_when_env_not_set(self) -> None:
|
|
42
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
43
|
+
f = _ShortPathFilter()
|
|
44
|
+
assert f._project_name is None, f"Expected None, got {f._project_name}" # pylint: disable=W0212
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TestShortPathFilterLogic:
|
|
48
|
+
def test_project_name_in_path_sets_relative_shortpath(self) -> None:
|
|
49
|
+
with patch.dict(os.environ, {"PROJECT_NAME": "my_project"}):
|
|
50
|
+
f = _ShortPathFilter()
|
|
51
|
+
record = _make_record("/home/user/my_project/app/main.py")
|
|
52
|
+
|
|
53
|
+
result = f.filter(record)
|
|
54
|
+
|
|
55
|
+
assert result is True, "filter() must always return True"
|
|
56
|
+
assert (
|
|
57
|
+
record.shortpath == "my_project/app/main.py"
|
|
58
|
+
), ( # pylint: disable=E1101
|
|
59
|
+
f"Expected 'my_project/app/main.py', got '{record.shortpath}'" # pylint: disable=E1101
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def test_project_name_not_in_path_falls_back_to_full_pathname(self) -> None:
|
|
63
|
+
with patch.dict(os.environ, {"PROJECT_NAME": "my_project"}):
|
|
64
|
+
f = _ShortPathFilter()
|
|
65
|
+
record = _make_record("/home/user/other_project/app/main.py")
|
|
66
|
+
|
|
67
|
+
f.filter(record)
|
|
68
|
+
|
|
69
|
+
assert (
|
|
70
|
+
record.shortpath == "/home/user/other_project/app/main.py"
|
|
71
|
+
), f"Expected full pathname, got '{record.shortpath}'" # pylint: disable=E1101 # pylint: disable=E1101
|
|
72
|
+
|
|
73
|
+
def test_no_project_name_env_falls_back_to_full_pathname(self) -> None:
|
|
74
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
75
|
+
f = _ShortPathFilter()
|
|
76
|
+
record = _make_record("/home/user/my_project/app/main.py")
|
|
77
|
+
|
|
78
|
+
f.filter(record)
|
|
79
|
+
|
|
80
|
+
assert (
|
|
81
|
+
record.shortpath == "/home/user/my_project/app/main.py"
|
|
82
|
+
), f"Expected full pathname, got '{record.shortpath}'" # pylint: disable=E1101 # pylint: disable=E1101
|
|
83
|
+
|
|
84
|
+
def test_venv_in_path_sets_venv_relative_shortpath(self) -> None:
|
|
85
|
+
with patch.dict(os.environ, {"PROJECT_NAME": "my_project"}):
|
|
86
|
+
f = _ShortPathFilter()
|
|
87
|
+
record = _make_record("/home/user/my_project/.venv/lib/python3.13/site-packages/urllib3/pool.py")
|
|
88
|
+
|
|
89
|
+
f.filter(record)
|
|
90
|
+
|
|
91
|
+
assert (
|
|
92
|
+
record.shortpath == ".venv/lib/python3.13/site-packages/urllib3/pool.py"
|
|
93
|
+
), f"Expected .venv-relative path, got '{record.shortpath}'" # pylint: disable=E1101 # pylint: disable=E1101
|
|
94
|
+
|
|
95
|
+
def test_venv_takes_precedence_over_project_name(self) -> None:
|
|
96
|
+
with patch.dict(os.environ, {"PROJECT_NAME": "my_project"}):
|
|
97
|
+
f = _ShortPathFilter()
|
|
98
|
+
record = _make_record("/home/user/my_project/.venv/lib/python3.13/site-packages/requests/api.py")
|
|
99
|
+
|
|
100
|
+
f.filter(record)
|
|
101
|
+
|
|
102
|
+
assert record.shortpath.startswith(
|
|
103
|
+
".venv/"
|
|
104
|
+
), f"Expected .venv-relative path, got '{record.shortpath}'" # pylint: disable=E1101 # pylint: disable=E1101
|
|
105
|
+
|
|
106
|
+
def test_filter_always_returns_true(self) -> None:
|
|
107
|
+
with patch.dict(os.environ, {"PROJECT_NAME": "my_project"}):
|
|
108
|
+
f = _ShortPathFilter()
|
|
109
|
+
|
|
110
|
+
for pathname in (
|
|
111
|
+
"/some/random/path.py",
|
|
112
|
+
"/my_project/app.py",
|
|
113
|
+
"/.venv/lib/pkg.py",
|
|
114
|
+
):
|
|
115
|
+
record = _make_record(pathname)
|
|
116
|
+
assert f.filter(record) is True, f"filter() returned False for '{pathname}'"
|
|
117
|
+
|
|
118
|
+
def test_shortpath_always_set_on_record(self) -> None:
|
|
119
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
120
|
+
f = _ShortPathFilter()
|
|
121
|
+
record = _make_record("/any/path/file.py")
|
|
122
|
+
|
|
123
|
+
f.filter(record)
|
|
124
|
+
|
|
125
|
+
assert hasattr(record, "shortpath"), "shortpath attribute must always be set on the record"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class TestBuildLoggerFilterIntegration:
|
|
129
|
+
def test_handler_has_short_path_filter_after_build(self) -> None:
|
|
130
|
+
root_logger = logging.getLogger()
|
|
131
|
+
build_logger(project_name="TestFilter", console_output=True)
|
|
132
|
+
|
|
133
|
+
filter_types = [type(f) for h in root_logger.handlers for f in h.filters]
|
|
134
|
+
assert _ShortPathFilter in filter_types, "Handlers must have _ShortPathFilter attached"
|
|
135
|
+
|
|
136
|
+
def test_calling_build_logger_twice_does_not_stack_filters(self) -> None:
|
|
137
|
+
build_logger(project_name="TestFilter", console_output=True)
|
|
138
|
+
build_logger(project_name="TestFilter", console_output=True)
|
|
139
|
+
|
|
140
|
+
root_logger = logging.getLogger()
|
|
141
|
+
for handler in root_logger.handlers:
|
|
142
|
+
short_path_filters = [f for f in handler.filters if isinstance(f, _ShortPathFilter)]
|
|
143
|
+
assert (
|
|
144
|
+
len(short_path_filters) == 1
|
|
145
|
+
), f"Expected exactly 1 _ShortPathFilter per handler, found {len(short_path_filters)}"
|
|
146
|
+
|
|
147
|
+
def test_shortpath_set_on_logged_records(self, temp_log_file: str) -> None: # pylint: disable=W0621
|
|
148
|
+
with patch.dict(os.environ, {"PROJECT_NAME": "custom-python-logger"}):
|
|
149
|
+
logger = build_logger(
|
|
150
|
+
project_name="ShortPathTest",
|
|
151
|
+
log_file=True,
|
|
152
|
+
log_file_path=temp_log_file,
|
|
153
|
+
console_output=False,
|
|
154
|
+
)
|
|
155
|
+
logger.info("shortpath test message")
|
|
156
|
+
|
|
157
|
+
with open(temp_log_file) as f:
|
|
158
|
+
content = f.read()
|
|
159
|
+
|
|
160
|
+
assert "shortpath test message" in content, "Log message not found in file"
|
|
161
|
+
assert "custom-python-logger/" in content, f"Expected project-relative path in log output, got:\n{content}"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{custom_python_logger-3.0.2 → custom_python_logger-4.0.1}/custom_python_logger.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{custom_python_logger-3.0.2 → custom_python_logger-4.0.1}/tests/test_usage_example_pytest.py
RENAMED
|
File without changes
|