pyqt-logging-manager 1.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.
- pyqt_logging_manager-1.0.0/PKG-INFO +404 -0
- pyqt_logging_manager-1.0.0/README.md +394 -0
- pyqt_logging_manager-1.0.0/pyproject.toml +23 -0
- pyqt_logging_manager-1.0.0/pyqt_logging_manager/__init__.py +39 -0
- pyqt_logging_manager-1.0.0/pyqt_logging_manager/logging_helpers.py +808 -0
- pyqt_logging_manager-1.0.0/pyqt_logging_manager.egg-info/PKG-INFO +404 -0
- pyqt_logging_manager-1.0.0/pyqt_logging_manager.egg-info/SOURCES.txt +9 -0
- pyqt_logging_manager-1.0.0/pyqt_logging_manager.egg-info/dependency_links.txt +1 -0
- pyqt_logging_manager-1.0.0/pyqt_logging_manager.egg-info/requires.txt +1 -0
- pyqt_logging_manager-1.0.0/pyqt_logging_manager.egg-info/top_level.txt +1 -0
- pyqt_logging_manager-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyqt-logging-manager
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Reusable logging utilities for PyQt6 applications
|
|
5
|
+
Author-email: RogerGdot <rogergdot@gmail.com>
|
|
6
|
+
Project-URL: Repository, https://gitlab.dlr.de/fm/fks/thermoelektrik/python-lib/pyqt-logging-manager
|
|
7
|
+
Requires-Python: >=3.13
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: pyqt6
|
|
10
|
+
|
|
11
|
+
# pyqt-logging-manager
|
|
12
|
+
|
|
13
|
+
Reusable logging utilities for PyQt6 applications, developed at DLR.
|
|
14
|
+
|
|
15
|
+
This package wraps the complete logging setup - file handlers, GUI handler, and history dialogs - in one `LoggingManager` class that can be reused in PyQt6 projects without project-specific changes.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Contents
|
|
20
|
+
|
|
21
|
+
- [Features](#features)
|
|
22
|
+
- [Requirements](#requirements)
|
|
23
|
+
- [Installation](#installation)
|
|
24
|
+
- [Quick Start](#quick-start)
|
|
25
|
+
- [LoggingManager API](#loggingmanager-api)
|
|
26
|
+
- [LogColumn](#logcolumn)
|
|
27
|
+
- [Additional Classes And Functions](#additional-classes-and-functions)
|
|
28
|
+
- [Examples](#examples)
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **`LoggingManager`**: initialize once and let the manager wire everything up.
|
|
35
|
+
- **RotatingFileHandler** for all file handlers, with configurable maximum file size and backup count.
|
|
36
|
+
- **Optional handlers** for main logs, error logs, and debug logs.
|
|
37
|
+
- **GUI integration** through `QTextEdit` with level-based color coding.
|
|
38
|
+
- **Log history dialogs** with filter buttons (All / Warnings + Errors / Errors Only).
|
|
39
|
+
- **Structured formatter configuration** through the `LogColumn` enum instead of raw format strings.
|
|
40
|
+
- **Thread-safe GUI logging**: `GuiLogger` forwards records from worker threads to the GUI thread through a Qt signal.
|
|
41
|
+
- **No project-specific code** and no external runtime dependency besides PyQt6.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Requirements
|
|
46
|
+
|
|
47
|
+
- Python >= 3.13
|
|
48
|
+
- PyQt6
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
### Editable Install From A Local Checkout
|
|
55
|
+
|
|
56
|
+
Use this during development so package changes are immediately visible in consuming projects.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Clone the package from GitLab
|
|
60
|
+
git clone git@gitlab.dlr.de:fm/fks/thermoelektrik/python-lib/pyqt-logging-manager.git
|
|
61
|
+
|
|
62
|
+
# Install it into the target project after activating its virtual environment
|
|
63
|
+
pip install -e /path/to/pyqt-logging-manager
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Install Directly From GitLab
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install git+ssh://git@gitlab.dlr.de/fm/fks/thermoelektrik/python-lib/pyqt-logging-manager.git
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Install A Specific Version Or Tag
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install git+ssh://git@gitlab.dlr.de/fm/fks/thermoelektrik/python-lib/pyqt-logging-manager.git@v0.1.0
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Quick Start
|
|
81
|
+
|
|
82
|
+
### Minimal GUI-Only Usage
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from pyqt_logging_manager import LoggingManager
|
|
86
|
+
|
|
87
|
+
class MainWindow(QMainWindow):
|
|
88
|
+
def __init__(self):
|
|
89
|
+
super().__init__()
|
|
90
|
+
uic.loadUi("my_app.ui", self)
|
|
91
|
+
|
|
92
|
+
self.log_manager = LoggingManager(system_name="MyTool")
|
|
93
|
+
self.log_manager.connect_widget(self.plainLog) # QTextEdit from the UI
|
|
94
|
+
|
|
95
|
+
logger = self.log_manager.get_logger("MAIN")
|
|
96
|
+
logger.info("Application started")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Standard File Configuration
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from pyqt_logging_manager import LoggingManager
|
|
103
|
+
|
|
104
|
+
self.log_manager = LoggingManager(
|
|
105
|
+
system_name="MyApplication",
|
|
106
|
+
system_version="1.2.0",
|
|
107
|
+
log_dir="logs/",
|
|
108
|
+
)
|
|
109
|
+
self.log_manager.connect_widget(self.plainLog)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Creates: `logs/MyApplication.log`
|
|
113
|
+
|
|
114
|
+
### Full Configuration
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from pyqt_logging_manager import LoggingManager, LogColumn
|
|
118
|
+
import logging
|
|
119
|
+
|
|
120
|
+
self.log_manager = LoggingManager(
|
|
121
|
+
system_name="MyApplication",
|
|
122
|
+
system_version="1.2.0",
|
|
123
|
+
log_dir="logs/",
|
|
124
|
+
level=logging.INFO,
|
|
125
|
+
delete_old_logs=True,
|
|
126
|
+
error_log=True, # -> logs/MyApplication_Error.log
|
|
127
|
+
debug=True, # -> logs/MyApplication_Debug.log
|
|
128
|
+
max_bytes=20_000_000, # 20 MB per file
|
|
129
|
+
backup_count=5, # max. 5 backup files
|
|
130
|
+
columns=[
|
|
131
|
+
(LogColumn.TIMESTAMP, 32),
|
|
132
|
+
(LogColumn.LEVEL, 10),
|
|
133
|
+
(LogColumn.LOGGER, 16),
|
|
134
|
+
(LogColumn.MESSAGE, 0),
|
|
135
|
+
],
|
|
136
|
+
separator="\t",
|
|
137
|
+
)
|
|
138
|
+
self.log_manager.connect_widget(self.plainLog)
|
|
139
|
+
|
|
140
|
+
# Run custom logic for warnings, for example opening a dialog
|
|
141
|
+
self.log_manager.connect_extra_slot(self._on_warning)
|
|
142
|
+
|
|
143
|
+
logger = self.log_manager.get_logger("MAIN")
|
|
144
|
+
logger.info("Ready")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## LoggingManager API
|
|
150
|
+
|
|
151
|
+
### Constructor
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
LoggingManager(
|
|
155
|
+
system_name: str,
|
|
156
|
+
system_version: str = "",
|
|
157
|
+
log_dir: str | None = None,
|
|
158
|
+
level: int = logging.INFO,
|
|
159
|
+
delete_old_logs: bool = True,
|
|
160
|
+
error_log: bool = False,
|
|
161
|
+
debug: bool = False,
|
|
162
|
+
max_bytes: int = 20_000_000,
|
|
163
|
+
backup_count: int = 5,
|
|
164
|
+
formatter_string: str | None = None,
|
|
165
|
+
formatter_width: int | None = None,
|
|
166
|
+
columns: list[tuple[LogColumn, int]] | None = None,
|
|
167
|
+
separator: str = "\t",
|
|
168
|
+
)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
| Parameter | Type | Default | Description |
|
|
172
|
+
|---|---|---|---|
|
|
173
|
+
| `system_name` | `str` | - | Application name used for file names and log headers. |
|
|
174
|
+
| `system_version` | `str` | `""` | Optional version shown in the log header. |
|
|
175
|
+
| `log_dir` | `str \| None` | `None` | Directory for log files. `None` means GUI-only logging. |
|
|
176
|
+
| `level` | `int` | `logging.INFO` | Minimum root logger level. |
|
|
177
|
+
| `delete_old_logs` | `bool` | `True` | Delete log files and backups at startup. |
|
|
178
|
+
| `error_log` | `bool` | `False` | Create a separate error FileHandler for `WARNING` and above. |
|
|
179
|
+
| `debug` | `bool` | `False` | Create a debug RotatingFileHandler for `DEBUG` and above. |
|
|
180
|
+
| `max_bytes` | `int` | `20_000_000` | Maximum file size in bytes for all file handlers. |
|
|
181
|
+
| `backup_count` | `int` | `5` | Maximum number of backup files per handler. |
|
|
182
|
+
| `formatter_string` | `str \| None` | `None` | Raw Python logging format string. Overrides `columns`. |
|
|
183
|
+
| `formatter_width` | `int \| None` | `128` | Automatic line wrap width. |
|
|
184
|
+
| `columns` | `list \| None` | `None` | Structured column definition; see `LogColumn`. |
|
|
185
|
+
| `separator` | `str` | `"\t"` | Separator between columns. |
|
|
186
|
+
|
|
187
|
+
**Formatter priority:** `formatter_string` > `columns` > default format.
|
|
188
|
+
|
|
189
|
+
### Methods
|
|
190
|
+
|
|
191
|
+
#### `connect_widget(widget: QTextEdit) -> None`
|
|
192
|
+
|
|
193
|
+
Connects a `QTextEdit` as GUI log output. In PyQt6 projects, call this after `loadUi()`.
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
self.log_manager.connect_widget(self.plainLog)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### `get_logger(name: str) -> logging.Logger`
|
|
200
|
+
|
|
201
|
+
Returns a named logger. All loggers automatically propagate to all registered handlers.
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
logger = self.log_manager.get_logger("CALC_ENGINE")
|
|
205
|
+
logger.warning("Temperature outside the expected range")
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
#### `connect_extra_slot(slot) -> None`
|
|
209
|
+
|
|
210
|
+
Connects an additional slot to the `GuiLogger` signal. Call this after `connect_widget()`.
|
|
211
|
+
|
|
212
|
+
The slot must have the signature `(message: str, level: int, levelname: str)`.
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
def _on_warning(self, _msg: str, level: int, _name: str) -> None:
|
|
216
|
+
if level >= logging.WARNING:
|
|
217
|
+
self.log_manager.show_errors(parent=self)
|
|
218
|
+
|
|
219
|
+
self.log_manager.connect_extra_slot(self._on_warning)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### `show_history(parent: QWidget | None = None) -> None`
|
|
223
|
+
|
|
224
|
+
Opens the log history dialog with all records and filter buttons. If it is already open, it is brought to the foreground.
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
self.log_manager.show_history(parent=self)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
#### `show_errors(parent: QWidget | None = None) -> None`
|
|
231
|
+
|
|
232
|
+
Opens the error log dialog for warnings and errors only. If it is already open, it is brought to the foreground.
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
self.log_manager.show_errors(parent=self)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Attributes
|
|
239
|
+
|
|
240
|
+
| Attribute | Type | Description |
|
|
241
|
+
|---|---|---|
|
|
242
|
+
| `log_records` | `list[tuple[str, int, str]]` | All log records as `(message, level, levelname)`. |
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## LogColumn
|
|
247
|
+
|
|
248
|
+
Instead of a raw format string, the formatter can be configured with the `LogColumn` enum and column widths.
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
from pyqt_logging_manager import LogColumn
|
|
252
|
+
|
|
253
|
+
columns=[
|
|
254
|
+
(LogColumn.TIMESTAMP, 32), # Timestamp, 32 characters
|
|
255
|
+
(LogColumn.LEVEL, 10), # Log level, 10 characters
|
|
256
|
+
(LogColumn.LOGGER, 16), # Logger name, 16 characters
|
|
257
|
+
(LogColumn.MESSAGE, 0), # Message, no fixed width
|
|
258
|
+
]
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Width `0`** means no padding; the field is as wide as needed.
|
|
262
|
+
|
|
263
|
+
### Available Column Types
|
|
264
|
+
|
|
265
|
+
| `LogColumn.X` | Content | Example Value |
|
|
266
|
+
|---|---|---|
|
|
267
|
+
| `TIMESTAMP` | Date and time | `2026-05-05 14:32:01` |
|
|
268
|
+
| `LEVEL` | Log level | `INFO`, `WARNING`, `ERROR` |
|
|
269
|
+
| `LOGGER` | Logger name | `MAIN`, `CALC_ENGINE` |
|
|
270
|
+
| `MESSAGE` | Message | `Calculation finished` |
|
|
271
|
+
| `FILE` | Source file without path | `calc_engine.py` |
|
|
272
|
+
| `LINE` | Line number | `217` |
|
|
273
|
+
| `FUNCTION` | Function name | `run_segment` |
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Additional Classes And Functions
|
|
278
|
+
|
|
279
|
+
### `GuiLogger`
|
|
280
|
+
|
|
281
|
+
Qt-based `logging.Handler` that forwards records as `pyqtSignal(str, int, str)`. It is used internally by `LoggingManager`, but can also be used directly.
|
|
282
|
+
|
|
283
|
+
### `WrappingFormatter`
|
|
284
|
+
|
|
285
|
+
`logging.Formatter` with automatic line wrapping. Continuation lines are indented below the first message line.
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
formatter = WrappingFormatter(
|
|
289
|
+
fmt="%(asctime)s\t%(levelname)s\t%(message)s",
|
|
290
|
+
width=100,
|
|
291
|
+
)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### `LogHistoryDialog`
|
|
295
|
+
|
|
296
|
+
`QDialog` for displaying and filtering log records. It supports two modes:
|
|
297
|
+
|
|
298
|
+
- `mode="all"`: all records with filter buttons (All / Warnings + Errors / Errors Only).
|
|
299
|
+
- `mode="errors"`: warnings and errors only, with a checkbox to hide warnings.
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
dialog = LogHistoryDialog(self.log_manager.log_records, mode="all", parent=self)
|
|
303
|
+
dialog.show()
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### `setup_exception_hook`
|
|
307
|
+
|
|
308
|
+
Installs a global handler for uncaught exceptions.
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
from pyqt_logging_manager import setup_exception_hook
|
|
312
|
+
|
|
313
|
+
setup_exception_hook(
|
|
314
|
+
logger=self.log_manager.get_logger("MAIN"),
|
|
315
|
+
show_dialog=True,
|
|
316
|
+
parent=self,
|
|
317
|
+
)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### `add_measurement_log_handler` / `remove_measurement_log_handler`
|
|
321
|
+
|
|
322
|
+
Adds a temporary FileHandler for a measurement and removes it afterwards.
|
|
323
|
+
|
|
324
|
+
```python
|
|
325
|
+
from pyqt_logging_manager import add_measurement_log_handler, remove_measurement_log_handler
|
|
326
|
+
|
|
327
|
+
# Start measurement
|
|
328
|
+
handler = add_measurement_log_handler(
|
|
329
|
+
log_dir="measurements/2026-05-05/",
|
|
330
|
+
measurement_name="Run_01",
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
# ... measurement is running ...
|
|
334
|
+
|
|
335
|
+
# End measurement
|
|
336
|
+
remove_measurement_log_handler(handler)
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Examples
|
|
342
|
+
|
|
343
|
+
### Small GUI Tool Without Files
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
from pyqt_logging_manager import LoggingManager
|
|
347
|
+
|
|
348
|
+
self.log_manager = LoggingManager("MyTool")
|
|
349
|
+
self.log_manager.connect_widget(self.logWidget)
|
|
350
|
+
self.log_manager.get_logger("MAIN").info("Started")
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Application With All Handlers
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
import logging
|
|
357
|
+
from pyqt_logging_manager import LoggingManager, LogColumn, setup_exception_hook
|
|
358
|
+
|
|
359
|
+
self.log_manager = LoggingManager(
|
|
360
|
+
system_name="MySystem",
|
|
361
|
+
system_version="2.0.0",
|
|
362
|
+
log_dir="logs/",
|
|
363
|
+
error_log=True,
|
|
364
|
+
debug=True,
|
|
365
|
+
columns=[
|
|
366
|
+
(LogColumn.TIMESTAMP, 24),
|
|
367
|
+
(LogColumn.LEVEL, 8),
|
|
368
|
+
(LogColumn.LOGGER, 20),
|
|
369
|
+
(LogColumn.MESSAGE, 0),
|
|
370
|
+
],
|
|
371
|
+
)
|
|
372
|
+
self.log_manager.connect_widget(self.plainLog)
|
|
373
|
+
self.log_manager.connect_extra_slot(self._auto_open_errors)
|
|
374
|
+
|
|
375
|
+
setup_exception_hook(
|
|
376
|
+
logger=self.log_manager.get_logger("MAIN"),
|
|
377
|
+
show_dialog=True,
|
|
378
|
+
parent=self,
|
|
379
|
+
)
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Loggers In Other Modules
|
|
383
|
+
|
|
384
|
+
```python
|
|
385
|
+
# In calc_engine.py; no LoggingManager import required
|
|
386
|
+
import logging
|
|
387
|
+
|
|
388
|
+
class CalcEngine:
|
|
389
|
+
def __init__(self):
|
|
390
|
+
self.logger = logging.getLogger("CALC_ENGINE")
|
|
391
|
+
|
|
392
|
+
def run(self):
|
|
393
|
+
self.logger.info("Calculation starts")
|
|
394
|
+
# ...
|
|
395
|
+
self.logger.debug("Segment 5 finished")
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
All records automatically propagate to the root logger and therefore to all handlers managed by `LoggingManager`.
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## License
|
|
403
|
+
|
|
404
|
+
Internal - DLR FM-KP
|