SwiftGUI_Logging 0.0.2__tar.gz → 0.1.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.
- {swiftgui_logging-0.0.2 → swiftgui_logging-0.1.0}/PKG-INFO +1 -1
- {swiftgui_logging-0.0.2 → swiftgui_logging-0.1.0}/pyproject.toml +1 -1
- swiftgui_logging-0.1.0/src/SwiftGUI_Logging/Configs/ExceptionLogging.py +69 -0
- swiftgui_logging-0.1.0/src/SwiftGUI_Logging/Configs/__init__.py +3 -0
- swiftgui_logging-0.1.0/src/SwiftGUI_Logging/ExceptionHandling.py +64 -0
- swiftgui_logging-0.1.0/src/SwiftGUI_Logging/MemoryHandlerRotatingBuffer.py +31 -0
- swiftgui_logging-0.1.0/src/SwiftGUI_Logging/Utils.py +12 -0
- swiftgui_logging-0.1.0/src/SwiftGUI_Logging/__init__.py +9 -0
- swiftgui_logging-0.0.2/src/SwiftGUI_Logging/__init__.py +0 -3
- swiftgui_logging-0.0.2/src/SwiftGUI_Logging/test.py +0 -0
- {swiftgui_logging-0.0.2 → swiftgui_logging-0.1.0}/LICENSE +0 -0
- {swiftgui_logging-0.0.2 → swiftgui_logging-0.1.0}/README.md +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import SwiftGUI_Logging as sgl
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import datetime as dt
|
|
5
|
+
import io
|
|
6
|
+
import shutil
|
|
7
|
+
|
|
8
|
+
def exceptions_to_file(
|
|
9
|
+
filepath: str | Path,
|
|
10
|
+
logger: str | logging.Logger = "",
|
|
11
|
+
buffer_size: int = 5000,
|
|
12
|
+
trigger_level: int = logging.ERROR,
|
|
13
|
+
log_level: int = logging.DEBUG,
|
|
14
|
+
reraise: bool = False,
|
|
15
|
+
datetime_format: str = "_%Y-%m-%d_%H-%M-%S",
|
|
16
|
+
formatter_format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
17
|
+
):
|
|
18
|
+
"""
|
|
19
|
+
Buffers the last n logging-entries.
|
|
20
|
+
If the program executes as expected, nothing happens.
|
|
21
|
+
If an exception crashes the program, the whole buffer is logged to a file, including the exception.
|
|
22
|
+
|
|
23
|
+
This is also true if the logger receives a report of loglevel higher than the specified trigger.
|
|
24
|
+
|
|
25
|
+
Every filesave generates its own file with a timestamp in the name.
|
|
26
|
+
|
|
27
|
+
:param filepath: Path to a log-file WITHOUT THE TIMESTAMP. The timestamp is added at the end before the suffix.
|
|
28
|
+
:param logger: You can specify which logger should receive the exception-logs.
|
|
29
|
+
:param buffer_size: How many reports are saved before the first ones are overritten again
|
|
30
|
+
:param trigger_level: Level at which the exceptions are treated. Reports at and above this level trigger a file-write
|
|
31
|
+
:param log_level: Logs below this level are ignored and not written to the file
|
|
32
|
+
:param reraise: True, if the exception should still be raised, even though it was logged. Good for debugging purposes
|
|
33
|
+
:param datetime_format: Format of the timestamp that extends the filename
|
|
34
|
+
:param formatter_format: Format of the log-entries in the file
|
|
35
|
+
:return:
|
|
36
|
+
"""
|
|
37
|
+
filepath = Path(filepath)
|
|
38
|
+
filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
|
|
40
|
+
filepath = filepath.parent / (filepath.stem + dt.now().strftime(datetime_format) + filepath.suffix)
|
|
41
|
+
|
|
42
|
+
if isinstance(logger, str):
|
|
43
|
+
logger = logging.getLogger(logger)
|
|
44
|
+
|
|
45
|
+
logger.setLevel(log_level)
|
|
46
|
+
|
|
47
|
+
stream = io.StringIO()
|
|
48
|
+
stream_handler = logging.StreamHandler(stream)
|
|
49
|
+
stream_handler.setFormatter(
|
|
50
|
+
logging.Formatter(formatter_format)
|
|
51
|
+
)
|
|
52
|
+
buffer_handler = sgl.MemoryHandlerRotatingBuffer(buffer_size, trigger_level, target=stream_handler)
|
|
53
|
+
|
|
54
|
+
logger.addHandler(buffer_handler)
|
|
55
|
+
|
|
56
|
+
def exception_occured(*_):
|
|
57
|
+
stream.seek(0)
|
|
58
|
+
if not stream.read(1):
|
|
59
|
+
# Nothing to report
|
|
60
|
+
print("Nothing to report")
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
with open(filepath, "w") as f:
|
|
64
|
+
stream.seek(0)
|
|
65
|
+
#f.write(stream.read())
|
|
66
|
+
shutil.copyfileobj(stream, f)
|
|
67
|
+
|
|
68
|
+
sgl.reroute_exceptions(logger, reraise=reraise, loglevel=trigger_level, pass_text_to_function=exception_occured)
|
|
69
|
+
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import logging.handlers
|
|
2
|
+
import traceback
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Callable, Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def reroute_exceptions(
|
|
8
|
+
logger: logging.Logger = logging.getLogger(),
|
|
9
|
+
loglevel: int = logging.CRITICAL,
|
|
10
|
+
*,
|
|
11
|
+
logger_warnings: logging.Logger = None,
|
|
12
|
+
loglevel_warnings: int = logging.WARNING,
|
|
13
|
+
reraise: bool = False,
|
|
14
|
+
print_to_console: bool = False,
|
|
15
|
+
pass_text_to_function: Callable[[str], Any] = None,
|
|
16
|
+
):
|
|
17
|
+
"""
|
|
18
|
+
Catch all unhandled exceptions and log them
|
|
19
|
+
|
|
20
|
+
:param logger: The logger where EXCEPTIONS go
|
|
21
|
+
:param loglevel: The loglevel for EXCEPTIONS
|
|
22
|
+
:param logger_warnings: The logger where WARNINGS go
|
|
23
|
+
:param loglevel_warnings: The level of logging for WARNINGS
|
|
24
|
+
:param reraise: True, if the exception should be raised again
|
|
25
|
+
:param pass_text_to_function: Pass a function/method and the exception-text is passed to it
|
|
26
|
+
:param print_to_console: True, if the text should be printed to the console using print(...)
|
|
27
|
+
:return:
|
|
28
|
+
"""
|
|
29
|
+
if logger_warnings is None:
|
|
30
|
+
logger_warnings = logger
|
|
31
|
+
|
|
32
|
+
if loglevel_warnings is None:
|
|
33
|
+
loglevel_warnings = loglevel
|
|
34
|
+
|
|
35
|
+
def catch(exctype, value, tb):
|
|
36
|
+
text = "".join(traceback.format_exception(exctype, value, tb))
|
|
37
|
+
|
|
38
|
+
if issubclass(exctype, Warning): # Warnings
|
|
39
|
+
if logger_warnings is not None:
|
|
40
|
+
logger_warnings.log(
|
|
41
|
+
loglevel_warnings,
|
|
42
|
+
text,
|
|
43
|
+
)
|
|
44
|
+
elif issubclass(exctype, Exception): # Real exceptions
|
|
45
|
+
if logger is not None:
|
|
46
|
+
logger.log(
|
|
47
|
+
loglevel,
|
|
48
|
+
text,
|
|
49
|
+
)
|
|
50
|
+
else:
|
|
51
|
+
# Keyboard interrupts and such
|
|
52
|
+
sys.__excepthook__(exctype, value, tb)
|
|
53
|
+
return # Not really necessary
|
|
54
|
+
|
|
55
|
+
if pass_text_to_function:
|
|
56
|
+
pass_text_to_function(text)
|
|
57
|
+
|
|
58
|
+
if print_to_console:
|
|
59
|
+
print(text)
|
|
60
|
+
|
|
61
|
+
if reraise:
|
|
62
|
+
sys.__excepthook__(exctype, value, tb)
|
|
63
|
+
|
|
64
|
+
sys.excepthook = catch
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import logging.handlers
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MemoryHandlerRotatingBuffer(logging.handlers.MemoryHandler):
|
|
5
|
+
|
|
6
|
+
def __init__(self, capacity, flushLevel=logging.ERROR, target=None):
|
|
7
|
+
"""
|
|
8
|
+
This handler saves the last n records.
|
|
9
|
+
Following records replace the oldest ones.
|
|
10
|
+
If something with a higher level than 'flushLevel' is logged, the handler passes all entries to another, specified handler.
|
|
11
|
+
|
|
12
|
+
THE BUFFER STILL FLUSHES WHEN THE SCRIPT ENDS, I COULDN'T AVOID THAT...
|
|
13
|
+
Happy for suggestions.
|
|
14
|
+
|
|
15
|
+
:param capacity: How many records to buffer before the oldest ones get deleted
|
|
16
|
+
:param flushLevel: At which level of record the whole buffer is passed to the target-handler
|
|
17
|
+
:param target: Handler to receive all records if necessary
|
|
18
|
+
"""
|
|
19
|
+
super().__init__(capacity, flushLevel, target, flushOnClose=False)
|
|
20
|
+
|
|
21
|
+
def shouldFlush(self, record):
|
|
22
|
+
"""
|
|
23
|
+
New records are checked if the "flushing-condition" is met.
|
|
24
|
+
:param record:
|
|
25
|
+
:return:
|
|
26
|
+
"""
|
|
27
|
+
if len(self.buffer) > self.capacity: # Remove 0th element so the buffer doesn't "overflow"
|
|
28
|
+
self.buffer.pop(0)
|
|
29
|
+
|
|
30
|
+
return record.levelno >= self.flushLevel
|
|
31
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def disable_root_handlers():
|
|
5
|
+
"""
|
|
6
|
+
Remove all root handlers so the root handler does nothing.
|
|
7
|
+
Useful when you want to generate the logging-system from ground up
|
|
8
|
+
:return:
|
|
9
|
+
"""
|
|
10
|
+
logging.getLogger().handlers.clear()
|
|
11
|
+
logging.getLogger().addHandler(logging.NullHandler())
|
|
12
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|