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.
@@ -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