custom-python-logger 3.0.0__tar.gz → 4.0.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.
- {custom_python_logger-3.0.0/custom_python_logger.egg-info → custom_python_logger-4.0.0}/PKG-INFO +50 -11
- {custom_python_logger-3.0.0 → custom_python_logger-4.0.0}/README.md +49 -4
- {custom_python_logger-3.0.0 → custom_python_logger-4.0.0}/custom_python_logger/__init__.py +7 -0
- {custom_python_logger-3.0.0 → custom_python_logger-4.0.0}/custom_python_logger/consts.py +5 -0
- {custom_python_logger-3.0.0 → custom_python_logger-4.0.0}/custom_python_logger/logger.py +32 -27
- {custom_python_logger-3.0.0 → custom_python_logger-4.0.0/custom_python_logger.egg-info}/PKG-INFO +50 -11
- {custom_python_logger-3.0.0 → custom_python_logger-4.0.0}/custom_python_logger.egg-info/SOURCES.txt +1 -0
- custom_python_logger-4.0.0/custom_python_logger.egg-info/requires.txt +2 -0
- {custom_python_logger-3.0.0 → custom_python_logger-4.0.0}/pyproject.toml +6 -3
- custom_python_logger-4.0.0/tests/test_short_path_filter.py +161 -0
- custom_python_logger-3.0.0/custom_python_logger.egg-info/requires.txt +0 -8
- {custom_python_logger-3.0.0 → custom_python_logger-4.0.0}/LICENSE +0 -0
- {custom_python_logger-3.0.0 → custom_python_logger-4.0.0}/MANIFEST.in +0 -0
- {custom_python_logger-3.0.0 → custom_python_logger-4.0.0}/custom_python_logger.egg-info/dependency_links.txt +0 -0
- {custom_python_logger-3.0.0 → custom_python_logger-4.0.0}/custom_python_logger.egg-info/top_level.txt +0 -0
- {custom_python_logger-3.0.0 → custom_python_logger-4.0.0}/setup.cfg +0 -0
- {custom_python_logger-3.0.0 → custom_python_logger-4.0.0}/tests/test_logger.py +0 -0
- {custom_python_logger-3.0.0 → custom_python_logger-4.0.0}/tests/test_logger_pytest.py +0 -0
- {custom_python_logger-3.0.0 → custom_python_logger-4.0.0}/tests/test_usage_example_pytest.py +0 -0
{custom_python_logger-3.0.0/custom_python_logger.egg-info → custom_python_logger-4.0.0}/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.0
|
|
4
4
|
Summary: A custom logger with color support and additional features.
|
|
5
5
|
Author: Avi Zaguri
|
|
6
6
|
License: MIT
|
|
@@ -17,13 +17,7 @@ Requires-Python: >=3.12
|
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: colorlog>=6.10.1
|
|
20
|
-
Requires-Dist: pathlib>=1.0.1
|
|
21
|
-
Requires-Dist: pre-commit>=4.5.0
|
|
22
|
-
Requires-Dist: pytest>=9.0.1
|
|
23
|
-
Requires-Dist: python-dotenv>=1.2.1
|
|
24
20
|
Requires-Dist: pyyaml>=6.0.3
|
|
25
|
-
Requires-Dist: setuptools>=80.9.0
|
|
26
|
-
Requires-Dist: wheel>=0.45.1
|
|
27
21
|
Dynamic: license-file
|
|
28
22
|
|
|
29
23
|

|
|
@@ -48,6 +42,7 @@ Easily integrate structured, readable, and context-rich logging into your Python
|
|
|
48
42
|
- ✅ **Contextual Logging**: Add extra fields (like user, environment, etc.) to every log message.
|
|
49
43
|
- ✅ **UTC Support**: Optionally log timestamps in UTC for consistency across environments.
|
|
50
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.
|
|
51
46
|
- ✅ **Easy Integration**: Simple API for getting a ready-to-use logger anywhere in your codebase.
|
|
52
47
|
|
|
53
48
|
---
|
|
@@ -86,6 +81,7 @@ logger.critical("This is a critical message.")
|
|
|
86
81
|
```
|
|
87
82
|
|
|
88
83
|
#### Advanced Usage
|
|
84
|
+
|
|
89
85
|
- Log to a file:
|
|
90
86
|
```python
|
|
91
87
|
from custom_python_logger import build_logger
|
|
@@ -117,19 +113,62 @@ logger.critical("This is a critical message.")
|
|
|
117
113
|
logger.info(yaml_pretty_format({'foo': 'bar'}))
|
|
118
114
|
```
|
|
119
115
|
|
|
120
|
-
-
|
|
116
|
+
- Use an existing logger with a custom name:
|
|
121
117
|
```python
|
|
122
118
|
from custom_python_logger import get_logger
|
|
123
119
|
|
|
124
120
|
logger = get_logger('some-name')
|
|
125
121
|
|
|
126
|
-
logger.debug("This is a debug message.")
|
|
127
|
-
logger.info("This is an info message.")
|
|
128
|
-
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)
|
|
129
138
|
```
|
|
130
139
|
|
|
131
140
|
---
|
|
132
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
|
+
|
|
133
172
|
## 🤝 Contributing
|
|
134
173
|
If you have a helpful tool, pattern, or improvement to suggest:
|
|
135
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_SHORTPATH, CustomLoggerLevel
|
|
14
14
|
|
|
15
15
|
CUSTOM_LOGGER = "custom_logger"
|
|
16
16
|
|
|
@@ -48,6 +48,22 @@ 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
|
+
|
|
51
67
|
class CustomLoggerAdapter(logging.LoggerAdapter):
|
|
52
68
|
def exception(self, msg: str, *args: Any, **kwargs: Any) -> None:
|
|
53
69
|
logging.addLevelName(CustomLoggerLevel.EXCEPTION.value, "EXCEPTION")
|
|
@@ -80,11 +96,7 @@ def clear_existing_handlers(logger: Logger) -> None:
|
|
|
80
96
|
logger.removeHandler(handler)
|
|
81
97
|
|
|
82
98
|
|
|
83
|
-
def
|
|
84
|
-
logger: Logger,
|
|
85
|
-
log_file_path: str | None,
|
|
86
|
-
log_format: str,
|
|
87
|
-
) -> None:
|
|
99
|
+
def add_file_handler(logger: Logger, log_file_path: str | None, log_format: str) -> None:
|
|
88
100
|
if log_file_path is not None:
|
|
89
101
|
log_file_formatter = logging.Formatter(log_format)
|
|
90
102
|
|
|
@@ -96,16 +108,15 @@ def add_file_handler_if_specified(
|
|
|
96
108
|
logger.addHandler(file_handler)
|
|
97
109
|
|
|
98
110
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
)
|
|
111
|
+
def add_console_handler(logger: Logger, log_format: str) -> None:
|
|
112
|
+
log_console_formatter = ColoredFormatter(
|
|
113
|
+
"%(log_color)s " + log_format,
|
|
114
|
+
log_colors=LOG_COLORS,
|
|
115
|
+
)
|
|
105
116
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
117
|
+
console_handler = logging.StreamHandler()
|
|
118
|
+
console_handler.setFormatter(log_console_formatter)
|
|
119
|
+
logger.addHandler(console_handler)
|
|
109
120
|
|
|
110
121
|
|
|
111
122
|
def get_logger(name: str, log_level: int | None = None, extra: dict | None = None) -> CustomLoggerAdapter:
|
|
@@ -123,7 +134,7 @@ def get_logger(name: str, log_level: int | None = None, extra: dict | None = Non
|
|
|
123
134
|
def build_logger( # pylint: disable=R0913
|
|
124
135
|
project_name: str,
|
|
125
136
|
extra: dict[str, Any] | None = None,
|
|
126
|
-
log_format: str =
|
|
137
|
+
log_format: str = LOG_FORMAT_SHORTPATH,
|
|
127
138
|
log_level: int = logging.DEBUG,
|
|
128
139
|
log_file: bool = False,
|
|
129
140
|
log_file_path: str | None = None,
|
|
@@ -150,25 +161,19 @@ def build_logger( # pylint: disable=R0913
|
|
|
150
161
|
logging.Formatter.converter = time.gmtime
|
|
151
162
|
|
|
152
163
|
root_logger = logging.getLogger()
|
|
153
|
-
|
|
154
164
|
clear_existing_handlers(logger=root_logger)
|
|
155
165
|
|
|
156
|
-
|
|
157
|
-
logger=root_logger,
|
|
158
|
-
console_output=console_output,
|
|
159
|
-
log_format=log_format,
|
|
160
|
-
)
|
|
166
|
+
if console_output:
|
|
167
|
+
add_console_handler(logger=root_logger, log_format=log_format)
|
|
161
168
|
|
|
162
169
|
if log_file:
|
|
163
170
|
if not log_file_path:
|
|
164
171
|
log_file_path = f"{get_project_path_by_file()}/logs/{project_name}.log"
|
|
165
172
|
log_file_path = log_file_path.lower().replace(" ", "_")
|
|
173
|
+
add_file_handler(logger=root_logger, log_file_path=log_file_path, log_format=log_format)
|
|
166
174
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
log_file_path=log_file_path,
|
|
170
|
-
log_format=log_format,
|
|
171
|
-
)
|
|
175
|
+
for handler in root_logger.handlers:
|
|
176
|
+
handler.addFilter(_ShortPathFilter())
|
|
172
177
|
|
|
173
178
|
logger = CustomLoggerAdapter(logging.getLogger(CUSTOM_LOGGER), extra)
|
|
174
179
|
logger.setLevel(log_level)
|
{custom_python_logger-3.0.0 → custom_python_logger-4.0.0/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.0
|
|
4
4
|
Summary: A custom logger with color support and additional features.
|
|
5
5
|
Author: Avi Zaguri
|
|
6
6
|
License: MIT
|
|
@@ -17,13 +17,7 @@ Requires-Python: >=3.12
|
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: colorlog>=6.10.1
|
|
20
|
-
Requires-Dist: pathlib>=1.0.1
|
|
21
|
-
Requires-Dist: pre-commit>=4.5.0
|
|
22
|
-
Requires-Dist: pytest>=9.0.1
|
|
23
|
-
Requires-Dist: python-dotenv>=1.2.1
|
|
24
20
|
Requires-Dist: pyyaml>=6.0.3
|
|
25
|
-
Requires-Dist: setuptools>=80.9.0
|
|
26
|
-
Requires-Dist: wheel>=0.45.1
|
|
27
21
|
Dynamic: license-file
|
|
28
22
|
|
|
29
23
|

|
|
@@ -48,6 +42,7 @@ Easily integrate structured, readable, and context-rich logging into your Python
|
|
|
48
42
|
- ✅ **Contextual Logging**: Add extra fields (like user, environment, etc.) to every log message.
|
|
49
43
|
- ✅ **UTC Support**: Optionally log timestamps in UTC for consistency across environments.
|
|
50
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.
|
|
51
46
|
- ✅ **Easy Integration**: Simple API for getting a ready-to-use logger anywhere in your codebase.
|
|
52
47
|
|
|
53
48
|
---
|
|
@@ -86,6 +81,7 @@ logger.critical("This is a critical message.")
|
|
|
86
81
|
```
|
|
87
82
|
|
|
88
83
|
#### Advanced Usage
|
|
84
|
+
|
|
89
85
|
- Log to a file:
|
|
90
86
|
```python
|
|
91
87
|
from custom_python_logger import build_logger
|
|
@@ -117,19 +113,62 @@ logger.critical("This is a critical message.")
|
|
|
117
113
|
logger.info(yaml_pretty_format({'foo': 'bar'}))
|
|
118
114
|
```
|
|
119
115
|
|
|
120
|
-
-
|
|
116
|
+
- Use an existing logger with a custom name:
|
|
121
117
|
```python
|
|
122
118
|
from custom_python_logger import get_logger
|
|
123
119
|
|
|
124
120
|
logger = get_logger('some-name')
|
|
125
121
|
|
|
126
|
-
logger.debug("This is a debug message.")
|
|
127
|
-
logger.info("This is an info message.")
|
|
128
|
-
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)
|
|
129
138
|
```
|
|
130
139
|
|
|
131
140
|
---
|
|
132
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
|
+
|
|
133
172
|
## 🤝 Contributing
|
|
134
173
|
If you have a helpful tool, pattern, or improvement to suggest:
|
|
135
174
|
Fork the repo <br>
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "custom-python-logger"
|
|
7
|
-
version = "
|
|
7
|
+
version = "4.0.0"
|
|
8
8
|
description = "A custom logger with color support and additional features."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -24,11 +24,14 @@ classifiers = [
|
|
|
24
24
|
|
|
25
25
|
dependencies = [
|
|
26
26
|
"colorlog>=6.10.1",
|
|
27
|
-
"
|
|
27
|
+
"pyyaml>=6.0.3",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[dependency-groups]
|
|
31
|
+
dev = [
|
|
28
32
|
"pre-commit>=4.5.0",
|
|
29
33
|
"pytest>=9.0.1",
|
|
30
34
|
"python-dotenv>=1.2.1",
|
|
31
|
-
"pyyaml>=6.0.3",
|
|
32
35
|
"setuptools>=80.9.0",
|
|
33
36
|
"wheel>=0.45.1",
|
|
34
37
|
]
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{custom_python_logger-3.0.0 → custom_python_logger-4.0.0}/tests/test_usage_example_pytest.py
RENAMED
|
File without changes
|