atomicshop 2.14.4__py3-none-any.whl → 2.14.6__py3-none-any.whl
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.
Potentially problematic release.
This version of atomicshop might be problematic. Click here for more details.
- atomicshop/__init__.py +1 -1
- atomicshop/datetimes.py +66 -0
- atomicshop/mitm/config_editor.py +37 -0
- atomicshop/mitm/initialize_engines.py +8 -2
- atomicshop/mitm/initialize_mitm_server.py +54 -12
- atomicshop/print_api.py +3 -2
- atomicshop/wrappers/loggingw/formatters.py +66 -40
- atomicshop/wrappers/loggingw/handlers.py +268 -15
- atomicshop/wrappers/loggingw/loggingw.py +132 -251
- atomicshop/wrappers/loggingw/reading.py +2 -2
- atomicshop/wrappers/psutilw/networks.py +40 -0
- atomicshop/wrappers/socketw/dns_server.py +16 -2
- {atomicshop-2.14.4.dist-info → atomicshop-2.14.6.dist-info}/METADATA +1 -1
- {atomicshop-2.14.4.dist-info → atomicshop-2.14.6.dist-info}/RECORD +17 -15
- {atomicshop-2.14.4.dist-info → atomicshop-2.14.6.dist-info}/LICENSE.txt +0 -0
- {atomicshop-2.14.4.dist-info → atomicshop-2.14.6.dist-info}/WHEEL +0 -0
- {atomicshop-2.14.4.dist-info → atomicshop-2.14.6.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,50 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from logging.handlers import TimedRotatingFileHandler, QueueListener, QueueHandler
|
|
3
|
+
from logging import FileHandler
|
|
4
|
+
import time
|
|
3
5
|
import re
|
|
4
6
|
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import queue
|
|
9
|
+
from typing import Literal, Union
|
|
10
|
+
import threading
|
|
11
|
+
from datetime import datetime
|
|
5
12
|
|
|
13
|
+
from . import loggers, formatters
|
|
14
|
+
from ... import datetimes, filesystem
|
|
6
15
|
|
|
7
|
-
|
|
16
|
+
|
|
17
|
+
DEFAULT_DATE_STRING_FORMAT: str = "%Y_%m_%d"
|
|
18
|
+
# Not used, only for the reference:
|
|
19
|
+
# _DEFAULT_DATE_REGEX_PATTERN: str = r"^\d{4}_\d{2}_\d{2}$"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ForceAtTimeRotationTimedRotatingFileHandler(TimedRotatingFileHandler):
|
|
23
|
+
def __init__(self, *args, **kwargs):
|
|
24
|
+
super().__init__(*args, **kwargs)
|
|
25
|
+
self._last_rotated_date = None
|
|
26
|
+
self._start_rotation_check()
|
|
27
|
+
|
|
28
|
+
def _start_rotation_check(self):
|
|
29
|
+
self._rotation_thread = threading.Thread(target=self._check_for_rotation)
|
|
30
|
+
self._rotation_thread.daemon = True
|
|
31
|
+
self._rotation_thread.start()
|
|
32
|
+
|
|
33
|
+
def _check_for_rotation(self):
|
|
34
|
+
while True:
|
|
35
|
+
now = datetime.now()
|
|
36
|
+
current_date = now.date()
|
|
37
|
+
# Check if it's midnight and the logs haven't been rotated today
|
|
38
|
+
if now.hour == 0 and now.minute == 0 and current_date != self._last_rotated_date:
|
|
39
|
+
self._last_rotated_date = current_date
|
|
40
|
+
self.doRollover()
|
|
41
|
+
time.sleep(0.1)
|
|
42
|
+
|
|
43
|
+
def doRollover(self):
|
|
44
|
+
super().doRollover()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TimedRotatingFileHandlerWithHeader(ForceAtTimeRotationTimedRotatingFileHandler):
|
|
8
48
|
"""
|
|
9
49
|
Custom TimedRotatingFileHandler that writes a header to the log file each time there is a file rotation.
|
|
10
50
|
Useful for writing CSV files.
|
|
@@ -31,6 +71,164 @@ class TimedRotatingFileHandlerWithHeader(TimedRotatingFileHandler):
|
|
|
31
71
|
super().emit(record)
|
|
32
72
|
|
|
33
73
|
|
|
74
|
+
def _process_formatter_attribute(
|
|
75
|
+
formatter: Union[
|
|
76
|
+
Literal['DEFAULT', 'MESSAGE'],
|
|
77
|
+
str,
|
|
78
|
+
None],
|
|
79
|
+
file_type: Union[
|
|
80
|
+
Literal['txt', 'csv', 'json'],
|
|
81
|
+
None] = None
|
|
82
|
+
):
|
|
83
|
+
"""
|
|
84
|
+
Function to process the formatter attribute.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
if formatter == 'DEFAULT' and file_type is None:
|
|
88
|
+
return formatters.DEFAULT_STREAM_FORMATTER
|
|
89
|
+
elif formatter == 'DEFAULT' and file_type == 'txt':
|
|
90
|
+
return formatters.DEFAULT_FORMATTER_TXT_FILE
|
|
91
|
+
elif formatter == 'DEFAULT' and file_type == 'csv':
|
|
92
|
+
return formatters.DEFAULT_FORMATTER_CSV_FILE
|
|
93
|
+
elif formatter == 'DEFAULT' and file_type == 'json':
|
|
94
|
+
return formatters.DEFAULT_MESSAGE_FORMATTER
|
|
95
|
+
elif formatter == 'MESSAGE':
|
|
96
|
+
return formatters.DEFAULT_MESSAGE_FORMATTER
|
|
97
|
+
else:
|
|
98
|
+
return formatter
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def add_stream_handler(
|
|
102
|
+
logger: logging.Logger,
|
|
103
|
+
logging_level: str = "DEBUG",
|
|
104
|
+
formatter: Union[
|
|
105
|
+
Literal['DEFAULT', 'MESSAGE'],
|
|
106
|
+
str,
|
|
107
|
+
None] = None,
|
|
108
|
+
formatter_use_nanoseconds: bool = False
|
|
109
|
+
):
|
|
110
|
+
"""
|
|
111
|
+
Function to add StreamHandler to logger.
|
|
112
|
+
Stream formatter will output messages to the console.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
# Getting the StreamHandler.
|
|
116
|
+
stream_handler = get_stream_handler()
|
|
117
|
+
# Setting log level for the handler, that will use the logger while initiated.
|
|
118
|
+
loggers.set_logging_level(stream_handler, logging_level)
|
|
119
|
+
|
|
120
|
+
# If formatter_message_only is set to True, then formatter will be used only for the 'message' part.
|
|
121
|
+
formatter = _process_formatter_attribute(formatter)
|
|
122
|
+
|
|
123
|
+
# If formatter was provided, then it will be used.
|
|
124
|
+
if formatter:
|
|
125
|
+
logging_formatter = formatters.get_logging_formatter_from_string(
|
|
126
|
+
formatter=formatter, use_nanoseconds=formatter_use_nanoseconds)
|
|
127
|
+
set_formatter(stream_handler, logging_formatter)
|
|
128
|
+
|
|
129
|
+
# Adding the handler to the main logger
|
|
130
|
+
loggers.add_handler(logger, stream_handler)
|
|
131
|
+
|
|
132
|
+
# Disable propagation from the 'root' logger, so we will not see the messages twice.
|
|
133
|
+
loggers.set_propagation(logger)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def add_timedfilehandler_with_queuehandler(
|
|
137
|
+
logger: logging.Logger,
|
|
138
|
+
file_path: str,
|
|
139
|
+
file_type: Literal[
|
|
140
|
+
'txt',
|
|
141
|
+
'csv',
|
|
142
|
+
'json'] = 'txt',
|
|
143
|
+
logging_level="DEBUG",
|
|
144
|
+
formatter: Union[
|
|
145
|
+
Literal['DEFAULT', 'MESSAGE'],
|
|
146
|
+
str,
|
|
147
|
+
None] = None,
|
|
148
|
+
formatter_use_nanoseconds: bool = False,
|
|
149
|
+
when: str = 'midnight',
|
|
150
|
+
interval: int = 1,
|
|
151
|
+
delay: bool = True,
|
|
152
|
+
encoding=None,
|
|
153
|
+
header: str = None
|
|
154
|
+
):
|
|
155
|
+
"""
|
|
156
|
+
Function to add TimedRotatingFileHandler and QueueHandler to logger.
|
|
157
|
+
TimedRotatingFileHandler will output messages to the file through QueueHandler.
|
|
158
|
+
This is needed, since TimedRotatingFileHandler is not thread-safe, though official docs say it is.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
# If file name wasn't provided we will use the logger name instead.
|
|
162
|
+
# if not file_name_no_extension:
|
|
163
|
+
# file_name_no_extension = logger.name
|
|
164
|
+
|
|
165
|
+
# Setting the TimedRotatingFileHandler, without adding it to the logger.
|
|
166
|
+
# It will be added to the QueueListener, which will use the TimedRotatingFileHandler to write logs.
|
|
167
|
+
# This is needed since there's a bug in TimedRotatingFileHandler, which won't let it be used with
|
|
168
|
+
# threads the same way it would be used for multiprocess.
|
|
169
|
+
|
|
170
|
+
# Creating file handler with log filename. At this stage the log file is created and locked by the handler,
|
|
171
|
+
# Unless we use "delay=True" to tell the class to write the file only if there's something to write.
|
|
172
|
+
|
|
173
|
+
filesystem.create_directory(os.path.dirname(file_path))
|
|
174
|
+
|
|
175
|
+
if file_type == "csv":
|
|
176
|
+
# If file extension is CSV, we'll set the header to the file.
|
|
177
|
+
# This is needed since the CSV file will be rotated, and we'll need to set the header each time.
|
|
178
|
+
# We'll use the custom TimedRotatingFileHandlerWithHeader class.
|
|
179
|
+
file_handler = get_timed_rotating_file_handler_with_header(
|
|
180
|
+
file_path, when=when, interval=interval, delay=delay, encoding=encoding, header=header)
|
|
181
|
+
else:
|
|
182
|
+
file_handler = get_timed_rotating_file_handler(
|
|
183
|
+
file_path, when=when, interval=interval, delay=delay, encoding=encoding)
|
|
184
|
+
|
|
185
|
+
loggers.set_logging_level(file_handler, logging_level)
|
|
186
|
+
|
|
187
|
+
formatter = _process_formatter_attribute(formatter, file_type=file_type)
|
|
188
|
+
|
|
189
|
+
# If formatter was passed to the function we'll add it to handler.
|
|
190
|
+
if formatter:
|
|
191
|
+
# Convert string to Formatter object. Moved to newer styling of python 3: style='{'
|
|
192
|
+
logging_formatter = formatters.get_logging_formatter_from_string(
|
|
193
|
+
formatter=formatter, use_nanoseconds=formatter_use_nanoseconds)
|
|
194
|
+
# Setting the formatter in file handler.
|
|
195
|
+
set_formatter(file_handler, logging_formatter)
|
|
196
|
+
|
|
197
|
+
# This function will change the suffix behavior of the rotated file name.
|
|
198
|
+
change_rotated_filename(file_handler)
|
|
199
|
+
|
|
200
|
+
queue_handler = start_queue_listener_for_file_handler_and_get_queue_handler(file_handler)
|
|
201
|
+
loggers.set_logging_level(queue_handler, logging_level)
|
|
202
|
+
|
|
203
|
+
# Add the QueueHandler to the logger.
|
|
204
|
+
loggers.add_handler(logger, queue_handler)
|
|
205
|
+
|
|
206
|
+
# Disable propagation from the 'root' logger, so we will not see the messages twice.
|
|
207
|
+
loggers.set_propagation(logger)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def start_queue_listener_for_file_handler_and_get_queue_handler(file_handler):
|
|
211
|
+
"""
|
|
212
|
+
Function to start QueueListener, which will put the logs from FileHandler to the Queue.
|
|
213
|
+
QueueHandler will get the logs from the Queue and put them to the file that was set in the FileHandler.
|
|
214
|
+
|
|
215
|
+
:param file_handler: FileHandler object.
|
|
216
|
+
:return: QueueHandler object.
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
# Create the Queue between threads. "-1" means that there can infinite number of items that can be
|
|
220
|
+
# put in the Queue. if integer is bigger than 0, it means that this will be the maximum
|
|
221
|
+
# number of items.
|
|
222
|
+
queue_object = queue.Queue(-1)
|
|
223
|
+
# Create QueueListener, which will put the logs from FileHandler to the Queue and put the logs to the queue.
|
|
224
|
+
start_queue_listener_for_file_handler(file_handler, queue_object)
|
|
225
|
+
|
|
226
|
+
return get_queue_handler(queue_object)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# BASE FUNCTIONS =======================================================================================================
|
|
230
|
+
|
|
231
|
+
|
|
34
232
|
def get_stream_handler() -> logging.StreamHandler:
|
|
35
233
|
"""
|
|
36
234
|
Function to get a StreamHandler.
|
|
@@ -44,7 +242,7 @@ def get_stream_handler() -> logging.StreamHandler:
|
|
|
44
242
|
|
|
45
243
|
def get_timed_rotating_file_handler(
|
|
46
244
|
log_file_path: str, when: str = "midnight", interval: int = 1, delay: bool = False, encoding=None
|
|
47
|
-
) ->
|
|
245
|
+
) -> ForceAtTimeRotationTimedRotatingFileHandler:
|
|
48
246
|
"""
|
|
49
247
|
Function to get a TimedRotatingFileHandler.
|
|
50
248
|
This handler will output messages to a file, rotating the log file at certain timed intervals.
|
|
@@ -62,7 +260,7 @@ def get_timed_rotating_file_handler(
|
|
|
62
260
|
:return: TimedRotatingFileHandler.
|
|
63
261
|
"""
|
|
64
262
|
|
|
65
|
-
return
|
|
263
|
+
return ForceAtTimeRotationTimedRotatingFileHandler(
|
|
66
264
|
filename=log_file_path, when=when, interval=interval, delay=delay, encoding=encoding)
|
|
67
265
|
|
|
68
266
|
|
|
@@ -150,7 +348,17 @@ def get_handler_name(handler: logging.Handler) -> str:
|
|
|
150
348
|
return handler.get_name()
|
|
151
349
|
|
|
152
350
|
|
|
153
|
-
def change_rotated_filename(
|
|
351
|
+
def change_rotated_filename(
|
|
352
|
+
file_handler: logging.Handler,
|
|
353
|
+
date_format_string: str = None
|
|
354
|
+
):
|
|
355
|
+
"""
|
|
356
|
+
Function to change the way TimedRotatingFileHandler managing the rotating filename.
|
|
357
|
+
|
|
358
|
+
:param file_handler: FileHandler to change the rotating filename for.
|
|
359
|
+
:param date_format_string: Date format string to use for the rotated log filename.
|
|
360
|
+
If None, the default 'DEFAULT_DATE_STRING_FORMAT' will be used.
|
|
361
|
+
"""
|
|
154
362
|
# Changing the way TimedRotatingFileHandler managing the rotating filename
|
|
155
363
|
# Default file suffix is only "Year_Month_Day" with addition of the dot (".") character to the
|
|
156
364
|
# "file name + extension" that you provide it. Example: log file name:
|
|
@@ -170,18 +378,43 @@ def change_rotated_filename(file_handler: logging.Handler, file_extension: str):
|
|
|
170
378
|
# file_handler.extMatch = re.compile(r"^\d{4}_\d{2}_\d{2}" + re.escape(log_file_extension) + r"$")
|
|
171
379
|
# file_handler.extMatch = re.compile(r"^\d{4}_\d{2}_\d{2}.txt$")
|
|
172
380
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
381
|
+
def callback_namer(name):
|
|
382
|
+
"""
|
|
383
|
+
Callback function to change the filename of the rotated log file on file rotation.
|
|
384
|
+
"""
|
|
385
|
+
# Currently the 'name' is full file path + '.' + logfile_suffix.
|
|
386
|
+
# Example: 'C:\\path\\to\\file.log._2021_12_24'
|
|
387
|
+
# Get the parent directory of the file: C:\path\to
|
|
388
|
+
parent_dir: str = str(Path(name).parent)
|
|
389
|
+
# Get the base filename without the extension: file.log
|
|
390
|
+
filename: str = Path(name).stem
|
|
391
|
+
# Get the date part of the filename: _2021_12_24
|
|
392
|
+
date_part: str = str(Path(name).suffix).replace(".", "")
|
|
393
|
+
# Get the file extension: log
|
|
394
|
+
file_extension: str = Path(filename).suffix
|
|
395
|
+
# Get the file name without the extension: file
|
|
396
|
+
file_stem: str = Path(filename).stem
|
|
397
|
+
|
|
398
|
+
return f"{parent_dir}{os.sep}{file_stem}{date_part}{file_extension}"
|
|
399
|
+
|
|
400
|
+
if date_format_string is None:
|
|
401
|
+
date_format_string = DEFAULT_DATE_STRING_FORMAT
|
|
402
|
+
|
|
403
|
+
# Construct the new suffix without the file extension
|
|
404
|
+
logfile_suffix = f"_{date_format_string}"
|
|
405
|
+
|
|
406
|
+
# Get regex pattern from string format.
|
|
407
|
+
# Example: '%Y_%m_%d' -> r'\d{4}_\d{2}_\d{2}'
|
|
408
|
+
date_regex_pattern = datetimes.datetime_format_to_regex(date_format_string)
|
|
409
|
+
|
|
410
|
+
# Regex pattern to match the rotated log filenames
|
|
411
|
+
logfile_regex_suffix = re.compile(date_regex_pattern)
|
|
412
|
+
|
|
413
|
+
# Update the handler's suffix to include the date format
|
|
183
414
|
file_handler.suffix = logfile_suffix
|
|
184
|
-
|
|
415
|
+
|
|
416
|
+
file_handler.namer = callback_namer
|
|
417
|
+
# Update the handler's extMatch regex to match the new filename format
|
|
185
418
|
file_handler.extMatch = logfile_regex_suffix
|
|
186
419
|
|
|
187
420
|
|
|
@@ -202,3 +435,23 @@ def has_handlers(logger: logging.Logger) -> bool:
|
|
|
202
435
|
return False
|
|
203
436
|
else:
|
|
204
437
|
return True
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def extract_datetime_format_from_file_handler(file_handler: FileHandler) -> Union[str, None]:
|
|
441
|
+
"""
|
|
442
|
+
Extract the datetime string formats from all TimedRotatingFileHandlers in the logger.
|
|
443
|
+
|
|
444
|
+
Args:
|
|
445
|
+
- logger: The logger instance.
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
- A list of datetime string formats used by the handlers.
|
|
449
|
+
"""
|
|
450
|
+
# Extract the suffix
|
|
451
|
+
suffix = getattr(file_handler, 'suffix', None)
|
|
452
|
+
if suffix:
|
|
453
|
+
datetime_format = datetimes.extract_datetime_format_from_string(suffix)
|
|
454
|
+
if datetime_format:
|
|
455
|
+
return datetime_format
|
|
456
|
+
|
|
457
|
+
return None
|