ezpy_logs 0.2.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.
- ezpy_logs-0.2.0/.gitignore +11 -0
- ezpy_logs-0.2.0/CHANGELOG.md +45 -0
- ezpy_logs-0.2.0/PKG-INFO +157 -0
- ezpy_logs-0.2.0/README.md +139 -0
- ezpy_logs-0.2.0/ezpy_logs/LoggerFactory.py +108 -0
- ezpy_logs-0.2.0/ezpy_logs/__init__.py +5 -0
- ezpy_logs-0.2.0/ezpy_logs/_core.py +298 -0
- ezpy_logs-0.2.0/ezpy_logs/_format.py +97 -0
- ezpy_logs-0.2.0/ezpy_logs/_redact.py +85 -0
- ezpy_logs-0.2.0/ezpy_logs/py.typed +0 -0
- ezpy_logs-0.2.0/pyproject.toml +39 -0
- ezpy_logs-0.2.0/tests/__init__.py +0 -0
- ezpy_logs-0.2.0/tests/conftest.py +30 -0
- ezpy_logs-0.2.0/tests/test_logger.py +88 -0
- ezpy_logs-0.2.0/tests/test_setup.py +497 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 (unreleased)
|
|
4
|
+
|
|
5
|
+
New API: `ezpy_logs.setup(...)` once from the application, `ezpy_logs.get_logger(name)` everywhere.
|
|
6
|
+
|
|
7
|
+
Behaviour changes:
|
|
8
|
+
|
|
9
|
+
- **Files are written only after `setup()`.** `get_logger` never configures anything or creates
|
|
10
|
+
files. `LoggerFactory` keeps the old implicit setup in `./.logs` until 0.3, with a one-time
|
|
11
|
+
`DeprecationWarning`.
|
|
12
|
+
- **`setup()` replaces a `LoggerFactory` setup**; a second `setup()` with different arguments raises
|
|
13
|
+
instead of being silently ignored.
|
|
14
|
+
- **Handlers live on the root logger only**, and named loggers propagate. Third-party records now
|
|
15
|
+
reach the files; each record is written once.
|
|
16
|
+
- **The root level defaults to WARNING** (`level=` / `EZPY_LOGS_LEVEL`). Your own loggers go to
|
|
17
|
+
DEBUG through `app_loggers=[...]`. `LoggerFactory.getLogger(name)` still sets `name` to DEBUG.
|
|
18
|
+
- **UTC by default**: file names `2026-09-28T21-31-28Z_<pid>.log`, lines `2026-09-28T21:31:28.412Z`.
|
|
19
|
+
`tz="local"` keeps the machine's zone with its offset. (0.1 meant Paris time but used an invalid
|
|
20
|
+
zone name, so it was machine-local time.)
|
|
21
|
+
- **The pid is in archive file names**: parallel workers no longer share a file.
|
|
22
|
+
- **No colour when the stream is not a terminal**, or when `NO_COLOR` is set.
|
|
23
|
+
- **stdout takes every level below WARNING**, so custom levels such as SUCCESS=25 reach the terminal.
|
|
24
|
+
- **Old-log deletion is opt-in** (`retention_days=None` by default) and only touches `*.log` /
|
|
25
|
+
`*.jsonl` directly inside `<log_dir>/archive/` and `<log_dir>/archive_ERRORS/`: never symlinks,
|
|
26
|
+
never the current run's files, never anything else. It logs the count (INFO when non-zero). `LoggerFactory` keeps 0.1's 30-day retention, now with that narrower scope.
|
|
27
|
+
- **`Latest.log` / `Latest_ERRORS.log` are written on every run** (`latest=True`), not only under
|
|
28
|
+
pytest; off by default with `run_id`.
|
|
29
|
+
- `TqdmToLogger.flush` no longer logs empty lines.
|
|
30
|
+
|
|
31
|
+
New:
|
|
32
|
+
|
|
33
|
+
- JSON-lines sink (`json=True` / `EZPY_LOGS_JSON=1`).
|
|
34
|
+
- Run-scoped files: `setup(run_id="job-42")` writes `<log_dir>/job-42.log` and `.jsonl`.
|
|
35
|
+
- Best-effort secret redaction in every sink, plus `redact_values=[...]`. Key/value pairs are
|
|
36
|
+
caught under prefixed names (`GITHUB_TOKEN=`, `client_secret:`, `AWS_SECRET_ACCESS_KEY=`) and
|
|
37
|
+
quoted values with spaces; JSON extras under a sensitive key (`extra={"password": ...}`) are
|
|
38
|
+
redacted whole. `token_count=512` is left alone.
|
|
39
|
+
- Env configuration: `EZPY_LOGS_DIR`, `EZPY_LOGS_LEVEL`, `EZPY_LOGS_JSON`, `EZPY_LOGS_TZ`, `NO_COLOR`.
|
|
40
|
+
|
|
41
|
+
Packaging: no runtime dependencies (`colorama`, `python-dateutil` and `pytest` removed; pytest is a
|
|
42
|
+
dev dependency); ships `py.typed`; `hello.py` is no longer packaged.
|
|
43
|
+
|
|
44
|
+
Removed from `LoggerFactory`: `add_output_file`, `setup_logger` and the `replaced` attribute.
|
|
45
|
+
`is_setup`, `base_logging_directory` and `output_files` are now read-only views of the live setup.
|
ezpy_logs-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ezpy_logs
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: EzPy Logs: swiss knife logging for personal use
|
|
5
|
+
Project-URL: Homepage, https://github.com/ezalos/ezpy_logs
|
|
6
|
+
Project-URL: Repository, https://github.com/ezalos/ezpy_logs
|
|
7
|
+
Author-email: ezalos <ezalos@github.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Classifier: Topic :: System :: Logging
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# EZPY Logs
|
|
20
|
+
|
|
21
|
+
Package for my personal use, swiss knife logging :
|
|
22
|
+
- When logging, has a nice formatter with file line and datetime (ISO 8601, UTC by default)
|
|
23
|
+
- Colorful logger (only on a terminal; `NO_COLOR` turns it off)
|
|
24
|
+
- Easy import / use: `setup()` once, `get_logger()` everywhere
|
|
25
|
+
- Save to files, with errors, and optionally JSON lines for machines
|
|
26
|
+
- Best-effort secret redaction in every sink
|
|
27
|
+
- Opt-in deletion of its own old logs
|
|
28
|
+
|
|
29
|
+
No runtime dependencies.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
uv add ezpy-logs
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
```py
|
|
40
|
+
import ezpy_logs
|
|
41
|
+
|
|
42
|
+
logger = ezpy_logs.get_logger(__name__) # safe at module level: creates nothing
|
|
43
|
+
|
|
44
|
+
def main():
|
|
45
|
+
ezpy_logs.setup(app_loggers=["my_package"]) # once, from the application
|
|
46
|
+
logger.info("Hello from ezpy-logs!")
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
main()
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
uv run python hello.py
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Library code only calls `get_logger`. Nothing is configured and no file is written until
|
|
57
|
+
the application calls `setup()`.
|
|
58
|
+
|
|
59
|
+
### `setup()` arguments
|
|
60
|
+
|
|
61
|
+
| Argument | Default | Meaning |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `log_dir` | `$EZPY_LOGS_DIR`, else `.logs` | Where files go. |
|
|
64
|
+
| `level` | `$EZPY_LOGS_LEVEL`, else `WARNING` | Root level: every logger you don't own (urllib3, botocore...). |
|
|
65
|
+
| `app_loggers` | `()` | Your own logger names (usually your package); set to `app_level`. |
|
|
66
|
+
| `app_level` | `DEBUG` | Level of `app_loggers`. |
|
|
67
|
+
| `run_id` | `None` | Write `<log_dir>/<run_id>.log` (+ `.jsonl`) instead of timestamped archives. |
|
|
68
|
+
| `json` | `$EZPY_LOGS_JSON == "1"` | Also write JSON lines. |
|
|
69
|
+
| `tz` | `$EZPY_LOGS_TZ`, else `"utc"` | `"utc"` or `"local"` (with offset) for file names and text lines. JSON `ts` is always UTC. |
|
|
70
|
+
| `latest` | on, off with `run_id` | Also write `Latest.log` / `Latest_ERRORS.log`, overwritten by each run. |
|
|
71
|
+
| `retention_days` | `None` (never delete) | Delete this library's own `archive*/*.log`/`*.jsonl` older than N days. |
|
|
72
|
+
| `redact_values` | `()` | Literal secrets to redact (values under 8 characters are ignored). |
|
|
73
|
+
|
|
74
|
+
Calling `setup()` again with the same arguments does nothing; with different arguments it
|
|
75
|
+
raises `RuntimeError`.
|
|
76
|
+
|
|
77
|
+
Environment variables: `EZPY_LOGS_DIR`, `EZPY_LOGS_LEVEL`, `EZPY_LOGS_JSON=1`,
|
|
78
|
+
`EZPY_LOGS_TZ`, `NO_COLOR`.
|
|
79
|
+
|
|
80
|
+
### What each sink looks like
|
|
81
|
+
|
|
82
|
+
Terminal (DEBUG/INFO on stdout, WARNING and above on stderr):
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
INFO : 2026-09-28T21:46:49.393Z 14 ms [Thread 138181901391680] hello.py:12 || Hello from ezpy-logs!
|
|
86
|
+
WARNING : 2026-09-28T21:46:49.393Z 14 ms [Thread 138181901391680] hello.py:13 || token=[REDACTED:token] never reaches a sink
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Files, by default:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
.logs/archive/2026-09-28T21-46-49Z_1428568.log everything
|
|
93
|
+
.logs/archive/2026-09-28T21-46-49Z_1428568.jsonl everything, when json is on
|
|
94
|
+
.logs/archive_ERRORS/2026-09-28T21-46-49Z_1428568.log WARNING and above
|
|
95
|
+
.logs/Latest.log / Latest_ERRORS.log this run only
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
A text file line:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
INFO 2026-09-28T21:46:49.393Z | [14] Thread 138181901391680 || [/path/hello.py:12] main || Hello from ezpy-logs!
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
A JSON line (`extra={...}` fields are merged in; `exc` holds the traceback when there is one):
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{"ts": "2026-09-28T21:46:49.393Z", "level": "WARNING", "logger": "__main__", "msg": "token=[REDACTED:token] never reaches a sink", "file": "/path/hello.py", "line": 13, "func": "main", "pid": 1428568, "thread": 138181901391680, "job": 7}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Secret redaction is best effort
|
|
111
|
+
|
|
112
|
+
Every ezpy_logs sink redacts bearer tokens, `ya29.` Google OAuth tokens, `ghp_`/`github_pat_`,
|
|
113
|
+
`hf_`, `sk-`/`sk-ant-`, `AKIA` AWS keys, PEM private-key blocks, `password=`/`token=`/`secret=`
|
|
114
|
+
values, and the literal `redact_values`. Regexes cannot catch every secret, and a handler someone
|
|
115
|
+
else adds (Sentry, pytest's caplog) bypasses it: do not rely on it as a security boundary.
|
|
116
|
+
|
|
117
|
+
### With tqdm
|
|
118
|
+
|
|
119
|
+
```py
|
|
120
|
+
from tqdm import tqdm
|
|
121
|
+
import ezpy_logs
|
|
122
|
+
|
|
123
|
+
logger = ezpy_logs.get_logger(__name__)
|
|
124
|
+
for name in tqdm(files, file=ezpy_logs.TqdmToLogger(logger), miniters=1e4, maxinterval=float("inf")):
|
|
125
|
+
...
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Migrating from 0.1
|
|
129
|
+
|
|
130
|
+
`from ezpy_logs.LoggerFactory import LoggerFactory` still works until 0.3, including its
|
|
131
|
+
implicit setup in `./.logs` (it emits a `DeprecationWarning` once). A `setup()` call always
|
|
132
|
+
replaces a `LoggerFactory` setup. See `CHANGELOG.md` for every behaviour change.
|
|
133
|
+
|
|
134
|
+
## Dev
|
|
135
|
+
|
|
136
|
+
### Local env setup
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
uv sync
|
|
140
|
+
source .venv/bin/activate
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### TESTING
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
uv run pytest -v
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Publishing
|
|
150
|
+
|
|
151
|
+
Change version in `pyproject.toml`, add a `CHANGELOG.md` entry. `UV_PUBLISH_TOKEN` in `.envrc`
|
|
152
|
+
is a `pass://` ref (Agent vault, item `PIPY_TOKEN`), so publish through `secrets run`:
|
|
153
|
+
|
|
154
|
+
```sh
|
|
155
|
+
rip dist
|
|
156
|
+
uv build && secrets run -- uv publish
|
|
157
|
+
```
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# EZPY Logs
|
|
2
|
+
|
|
3
|
+
Package for my personal use, swiss knife logging :
|
|
4
|
+
- When logging, has a nice formatter with file line and datetime (ISO 8601, UTC by default)
|
|
5
|
+
- Colorful logger (only on a terminal; `NO_COLOR` turns it off)
|
|
6
|
+
- Easy import / use: `setup()` once, `get_logger()` everywhere
|
|
7
|
+
- Save to files, with errors, and optionally JSON lines for machines
|
|
8
|
+
- Best-effort secret redaction in every sink
|
|
9
|
+
- Opt-in deletion of its own old logs
|
|
10
|
+
|
|
11
|
+
No runtime dependencies.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
uv add ezpy-logs
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```py
|
|
22
|
+
import ezpy_logs
|
|
23
|
+
|
|
24
|
+
logger = ezpy_logs.get_logger(__name__) # safe at module level: creates nothing
|
|
25
|
+
|
|
26
|
+
def main():
|
|
27
|
+
ezpy_logs.setup(app_loggers=["my_package"]) # once, from the application
|
|
28
|
+
logger.info("Hello from ezpy-logs!")
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
main()
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
uv run python hello.py
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Library code only calls `get_logger`. Nothing is configured and no file is written until
|
|
39
|
+
the application calls `setup()`.
|
|
40
|
+
|
|
41
|
+
### `setup()` arguments
|
|
42
|
+
|
|
43
|
+
| Argument | Default | Meaning |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `log_dir` | `$EZPY_LOGS_DIR`, else `.logs` | Where files go. |
|
|
46
|
+
| `level` | `$EZPY_LOGS_LEVEL`, else `WARNING` | Root level: every logger you don't own (urllib3, botocore...). |
|
|
47
|
+
| `app_loggers` | `()` | Your own logger names (usually your package); set to `app_level`. |
|
|
48
|
+
| `app_level` | `DEBUG` | Level of `app_loggers`. |
|
|
49
|
+
| `run_id` | `None` | Write `<log_dir>/<run_id>.log` (+ `.jsonl`) instead of timestamped archives. |
|
|
50
|
+
| `json` | `$EZPY_LOGS_JSON == "1"` | Also write JSON lines. |
|
|
51
|
+
| `tz` | `$EZPY_LOGS_TZ`, else `"utc"` | `"utc"` or `"local"` (with offset) for file names and text lines. JSON `ts` is always UTC. |
|
|
52
|
+
| `latest` | on, off with `run_id` | Also write `Latest.log` / `Latest_ERRORS.log`, overwritten by each run. |
|
|
53
|
+
| `retention_days` | `None` (never delete) | Delete this library's own `archive*/*.log`/`*.jsonl` older than N days. |
|
|
54
|
+
| `redact_values` | `()` | Literal secrets to redact (values under 8 characters are ignored). |
|
|
55
|
+
|
|
56
|
+
Calling `setup()` again with the same arguments does nothing; with different arguments it
|
|
57
|
+
raises `RuntimeError`.
|
|
58
|
+
|
|
59
|
+
Environment variables: `EZPY_LOGS_DIR`, `EZPY_LOGS_LEVEL`, `EZPY_LOGS_JSON=1`,
|
|
60
|
+
`EZPY_LOGS_TZ`, `NO_COLOR`.
|
|
61
|
+
|
|
62
|
+
### What each sink looks like
|
|
63
|
+
|
|
64
|
+
Terminal (DEBUG/INFO on stdout, WARNING and above on stderr):
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
INFO : 2026-09-28T21:46:49.393Z 14 ms [Thread 138181901391680] hello.py:12 || Hello from ezpy-logs!
|
|
68
|
+
WARNING : 2026-09-28T21:46:49.393Z 14 ms [Thread 138181901391680] hello.py:13 || token=[REDACTED:token] never reaches a sink
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Files, by default:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
.logs/archive/2026-09-28T21-46-49Z_1428568.log everything
|
|
75
|
+
.logs/archive/2026-09-28T21-46-49Z_1428568.jsonl everything, when json is on
|
|
76
|
+
.logs/archive_ERRORS/2026-09-28T21-46-49Z_1428568.log WARNING and above
|
|
77
|
+
.logs/Latest.log / Latest_ERRORS.log this run only
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
A text file line:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
INFO 2026-09-28T21:46:49.393Z | [14] Thread 138181901391680 || [/path/hello.py:12] main || Hello from ezpy-logs!
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
A JSON line (`extra={...}` fields are merged in; `exc` holds the traceback when there is one):
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{"ts": "2026-09-28T21:46:49.393Z", "level": "WARNING", "logger": "__main__", "msg": "token=[REDACTED:token] never reaches a sink", "file": "/path/hello.py", "line": 13, "func": "main", "pid": 1428568, "thread": 138181901391680, "job": 7}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Secret redaction is best effort
|
|
93
|
+
|
|
94
|
+
Every ezpy_logs sink redacts bearer tokens, `ya29.` Google OAuth tokens, `ghp_`/`github_pat_`,
|
|
95
|
+
`hf_`, `sk-`/`sk-ant-`, `AKIA` AWS keys, PEM private-key blocks, `password=`/`token=`/`secret=`
|
|
96
|
+
values, and the literal `redact_values`. Regexes cannot catch every secret, and a handler someone
|
|
97
|
+
else adds (Sentry, pytest's caplog) bypasses it: do not rely on it as a security boundary.
|
|
98
|
+
|
|
99
|
+
### With tqdm
|
|
100
|
+
|
|
101
|
+
```py
|
|
102
|
+
from tqdm import tqdm
|
|
103
|
+
import ezpy_logs
|
|
104
|
+
|
|
105
|
+
logger = ezpy_logs.get_logger(__name__)
|
|
106
|
+
for name in tqdm(files, file=ezpy_logs.TqdmToLogger(logger), miniters=1e4, maxinterval=float("inf")):
|
|
107
|
+
...
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Migrating from 0.1
|
|
111
|
+
|
|
112
|
+
`from ezpy_logs.LoggerFactory import LoggerFactory` still works until 0.3, including its
|
|
113
|
+
implicit setup in `./.logs` (it emits a `DeprecationWarning` once). A `setup()` call always
|
|
114
|
+
replaces a `LoggerFactory` setup. See `CHANGELOG.md` for every behaviour change.
|
|
115
|
+
|
|
116
|
+
## Dev
|
|
117
|
+
|
|
118
|
+
### Local env setup
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
uv sync
|
|
122
|
+
source .venv/bin/activate
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### TESTING
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
uv run pytest -v
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Publishing
|
|
132
|
+
|
|
133
|
+
Change version in `pyproject.toml`, add a `CHANGELOG.md` entry. `UV_PUBLISH_TOKEN` in `.envrc`
|
|
134
|
+
is a `pass://` ref (Agent vault, item `PIPY_TOKEN`), so publish through `secrets run`:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
rip dist
|
|
138
|
+
uv build && secrets run -- uv publish
|
|
139
|
+
```
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# ABOUTME: Compatibility shim for the 0.1 API (LoggerFactory.getLogger / setup_LoggerFactory).
|
|
2
|
+
# ABOUTME: Keeps the old implicit setup until 0.3; a new-style ezpy_logs.setup() always replaces it.
|
|
3
|
+
import logging
|
|
4
|
+
import typing
|
|
5
|
+
import warnings
|
|
6
|
+
|
|
7
|
+
from ezpy_logs._core import (
|
|
8
|
+
DEFAULT_LOG_DIR,
|
|
9
|
+
LevelFilter,
|
|
10
|
+
TqdmToLogger,
|
|
11
|
+
_configure,
|
|
12
|
+
_resolve,
|
|
13
|
+
current_files,
|
|
14
|
+
delete_old_logs,
|
|
15
|
+
)
|
|
16
|
+
from ezpy_logs import _core
|
|
17
|
+
|
|
18
|
+
__all__ = ["BASE_LOG_DIR", "LevelFilter", "LoggerFactory", "TqdmToLogger", "remove_logs_older_than_n_days"]
|
|
19
|
+
|
|
20
|
+
MIN_LEVEL_FOR_LOGGERS = logging.DEBUG
|
|
21
|
+
BASE_LOG_DIR = DEFAULT_LOG_DIR
|
|
22
|
+
_LEGACY_RETENTION_DAYS = 30
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def remove_logs_older_than_n_days(root_dir: str, n_days: int = 30) -> int:
|
|
26
|
+
return delete_old_logs(root_dir, n_days)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class _LegacyState(type):
|
|
30
|
+
"""Class-level read-only views of the live setup, so they never go stale."""
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def is_setup(cls) -> bool:
|
|
34
|
+
return _core._state is not None
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def base_logging_directory(cls) -> str | None:
|
|
38
|
+
return _core._state.config.log_dir.as_posix() if _core._state else None
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def output_files(cls) -> list[tuple[str, int, int, str]]:
|
|
42
|
+
"""(path, min_level, max_level, mode) of the files currently written, as in 0.1."""
|
|
43
|
+
return [(p.as_posix(), low, high, mode) for p, low, high, mode in current_files()]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class LoggerFactory(metaclass=_LegacyState):
|
|
47
|
+
"""Deprecated: use `ezpy_logs.setup()` and `ezpy_logs.get_logger()`.
|
|
48
|
+
|
|
49
|
+
Legacy setups never fight a new-style one: `setup()` replaces them, and a legacy setup
|
|
50
|
+
after `setup()` is a no-op. Between themselves, the first legacy setup wins (0.1 behaviour).
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
setup_loggers: typing.ClassVar[list[str]] = []
|
|
54
|
+
_warned: typing.ClassVar[bool] = False
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def setup_LoggerFactory(cls, log_dir: str = BASE_LOG_DIR, clean_old_logs: bool = True):
|
|
58
|
+
if _core._state is None:
|
|
59
|
+
config = _resolve(
|
|
60
|
+
log_dir=log_dir,
|
|
61
|
+
level=None,
|
|
62
|
+
app_loggers=(),
|
|
63
|
+
app_level=logging.DEBUG,
|
|
64
|
+
run_id=None,
|
|
65
|
+
json=None,
|
|
66
|
+
tz=None,
|
|
67
|
+
latest=None,
|
|
68
|
+
retention_days=_LEGACY_RETENTION_DAYS if clean_old_logs else None,
|
|
69
|
+
redact_values=(),
|
|
70
|
+
)
|
|
71
|
+
_configure(config, legacy=True)
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def getLogger(
|
|
75
|
+
cls,
|
|
76
|
+
name: str = "should be __name__",
|
|
77
|
+
level=MIN_LEVEL_FOR_LOGGERS,
|
|
78
|
+
) -> logging.Logger:
|
|
79
|
+
if _core._state is None:
|
|
80
|
+
if not cls._warned:
|
|
81
|
+
cls._warned = True
|
|
82
|
+
warnings.warn(
|
|
83
|
+
"LoggerFactory.getLogger() set up logging implicitly in ./.logs; this "
|
|
84
|
+
"implicit setup goes away in ezpy_logs 0.3. Call ezpy_logs.setup() "
|
|
85
|
+
"from your application and use ezpy_logs.get_logger().",
|
|
86
|
+
DeprecationWarning,
|
|
87
|
+
stacklevel=2,
|
|
88
|
+
)
|
|
89
|
+
cls.setup_LoggerFactory()
|
|
90
|
+
logger = logging.getLogger(name)
|
|
91
|
+
logger.setLevel(level)
|
|
92
|
+
if name not in cls.setup_loggers:
|
|
93
|
+
cls.setup_loggers.append(name)
|
|
94
|
+
return logger
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def get_TqdmToLogger(cls, logger: logging.Logger, level=MIN_LEVEL_FOR_LOGGERS) -> TqdmToLogger:
|
|
98
|
+
return TqdmToLogger(logger, level)
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def change_level_of_all_loggers(cls, level):
|
|
102
|
+
for name in cls.setup_loggers:
|
|
103
|
+
logging.getLogger(name).setLevel(level)
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def _reset(cls) -> None:
|
|
107
|
+
cls.setup_loggers = []
|
|
108
|
+
cls._warned = False
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# ABOUTME: Public API of ezpy_logs: setup() once from the application, get_logger() everywhere.
|
|
2
|
+
# ABOUTME: LoggerFactory stays importable from ezpy_logs.LoggerFactory as a deprecated alias until 0.3.
|
|
3
|
+
from ezpy_logs._core import TqdmToLogger, delete_old_logs, get_logger, is_setup, setup
|
|
4
|
+
|
|
5
|
+
__all__ = ["TqdmToLogger", "delete_old_logs", "get_logger", "is_setup", "setup"]
|