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
atomicshop/__init__.py
CHANGED
atomicshop/datetimes.py
CHANGED
|
@@ -3,6 +3,7 @@ from datetime import timedelta
|
|
|
3
3
|
import time
|
|
4
4
|
import random
|
|
5
5
|
import re
|
|
6
|
+
from typing import Union
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class MonthToNumber:
|
|
@@ -47,6 +48,28 @@ class MonthToNumber:
|
|
|
47
48
|
'דצמבר': '12'}
|
|
48
49
|
|
|
49
50
|
|
|
51
|
+
# Mapping of datetime format specifiers to regex patterns
|
|
52
|
+
DATE_TIME_STRING_FORMAT_SPECIFIERS_TO_REGEX: dict = {
|
|
53
|
+
'%Y': r'\d{4}', # Year with century
|
|
54
|
+
'%m': r'\d{2}', # Month as a zero-padded decimal number
|
|
55
|
+
'%d': r'\d{2}', # Day of the month as a zero-padded decimal number
|
|
56
|
+
'%H': r'\d{2}', # Hour (24-hour clock) as a zero-padded decimal number
|
|
57
|
+
'%I': r'\d{2}', # Hour (12-hour clock) as a zero-padded decimal number
|
|
58
|
+
'%M': r'\d{2}', # Minute as a zero-padded decimal number
|
|
59
|
+
'%S': r'\d{2}', # Second as a zero-padded decimal number
|
|
60
|
+
'%f': r'\d{6}', # Microsecond as a decimal number, zero-padded on the left
|
|
61
|
+
'%j': r'\d{3}', # Day of the year as a zero-padded decimal number
|
|
62
|
+
'%U': r'\d{2}', # Week number of the year (Sunday as the first day of the week)
|
|
63
|
+
'%W': r'\d{2}', # Week number of the year (Monday as the first day of the week)
|
|
64
|
+
'%w': r'\d', # Weekday as a decimal number (0 = Sunday, 6 = Saturday)
|
|
65
|
+
'%y': r'\d{2}', # Year without century
|
|
66
|
+
'%p': r'(AM|PM)', # AM or PM
|
|
67
|
+
'%z': r'[+-]\d{4}', # UTC offset in the form ±HHMM
|
|
68
|
+
'%Z': r'[A-Z]+', # Time zone name
|
|
69
|
+
'%%': r'%' # Literal '%'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
50
73
|
def get_datetime_from_complex_string_by_pattern(complex_string: str, date_pattern: str) -> tuple[datetime, str, float]:
|
|
51
74
|
"""
|
|
52
75
|
Function will get datetime object from a complex string by pattern.
|
|
@@ -71,6 +94,49 @@ def get_datetime_from_complex_string_by_pattern(complex_string: str, date_patter
|
|
|
71
94
|
raise ValueError("No valid date found in the string")
|
|
72
95
|
|
|
73
96
|
|
|
97
|
+
def datetime_format_to_regex(format_str: str) -> str:
|
|
98
|
+
"""
|
|
99
|
+
Convert a datetime format string to a regex pattern.
|
|
100
|
+
|
|
101
|
+
:param format_str: The datetime format string to convert.
|
|
102
|
+
:return: The regex pattern that matches the format string.
|
|
103
|
+
|
|
104
|
+
Example:
|
|
105
|
+
datetime_format_to_regex("%Y-%m-%d")
|
|
106
|
+
|
|
107
|
+
Output:
|
|
108
|
+
'^\\d{4}-\\d{2}-\\d{2}$'
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
# Escape all non-format characters
|
|
112
|
+
escaped_format_str = re.escape(format_str)
|
|
113
|
+
|
|
114
|
+
# Replace escaped format specifiers with their regex equivalents
|
|
115
|
+
for specifier, regex in DATE_TIME_STRING_FORMAT_SPECIFIERS_TO_REGEX.items():
|
|
116
|
+
escaped_format_str = escaped_format_str.replace(re.escape(specifier), regex)
|
|
117
|
+
|
|
118
|
+
# Return the full regex pattern with start and end anchors
|
|
119
|
+
return f"^{escaped_format_str}$"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def extract_datetime_format_from_string(complex_string: str) -> Union[str, None]:
|
|
123
|
+
"""
|
|
124
|
+
Extract the datetime format from the suffix used in TimedRotatingFileHandler.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
- suffix: The suffix string from the handler.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
- The datetime format string, or None if it cannot be determined.
|
|
131
|
+
"""
|
|
132
|
+
# Regular expression to match datetime format components in the suffix
|
|
133
|
+
datetime_format_regex = r"%[a-zA-Z]"
|
|
134
|
+
matches = re.findall(datetime_format_regex, complex_string)
|
|
135
|
+
if matches:
|
|
136
|
+
return "".join(matches)
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
|
|
74
140
|
def convert_single_digit_to_zero_padded(string: str):
|
|
75
141
|
"""
|
|
76
142
|
Function will check if string is a single character digit and will add zero in front of it.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import configparser
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CategoryNotFoundInConfigError(Exception):
|
|
5
|
+
pass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def edit_property(category: str, category_property: str, value: str, config_file_path: str) -> None:
|
|
9
|
+
"""
|
|
10
|
+
Edit a property in the config file.
|
|
11
|
+
|
|
12
|
+
:param category: str, Category in the config file.
|
|
13
|
+
:param category_property: str, Property in the category.
|
|
14
|
+
:param value: str, Value to set to the property.
|
|
15
|
+
:param config_file_path: str, Path to the config file.
|
|
16
|
+
|
|
17
|
+
:return: None.
|
|
18
|
+
|
|
19
|
+
-----------
|
|
20
|
+
|
|
21
|
+
Config Example:
|
|
22
|
+
[category]
|
|
23
|
+
category_property = value
|
|
24
|
+
"""
|
|
25
|
+
config = configparser.ConfigParser()
|
|
26
|
+
config.read(config_file_path)
|
|
27
|
+
|
|
28
|
+
if category not in config:
|
|
29
|
+
raise CategoryNotFoundInConfigError(f"Category '{category}' not found in the config file.")
|
|
30
|
+
|
|
31
|
+
# Change the value of the property if it is different from the current value.
|
|
32
|
+
current_value = config[category][category_property]
|
|
33
|
+
if current_value != value:
|
|
34
|
+
config[category][category_property] = value
|
|
35
|
+
|
|
36
|
+
with open(config_file_path, "w") as configfile:
|
|
37
|
+
config.write(configfile)
|
|
@@ -83,8 +83,14 @@ class ModuleCategory:
|
|
|
83
83
|
|
|
84
84
|
# Initiating logger for each engine by its name
|
|
85
85
|
# initiate_logger(current_module.engine_name, log_file_extension)
|
|
86
|
-
loggingw.
|
|
87
|
-
logger_name=self.engine_name,
|
|
86
|
+
loggingw.create_logger(
|
|
87
|
+
logger_name=self.engine_name,
|
|
88
|
+
directory_path=logs_path,
|
|
89
|
+
add_stream=True,
|
|
90
|
+
add_timedfile=True,
|
|
91
|
+
formatter_streamhandler='DEFAULT',
|
|
92
|
+
formatter_filehandler='DEFAULT'
|
|
93
|
+
)
|
|
88
94
|
|
|
89
95
|
|
|
90
96
|
# Assigning external class object by message domain received from client. If the domain is not in the list,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import sys
|
|
2
3
|
import threading
|
|
4
|
+
import time
|
|
3
5
|
|
|
4
6
|
# Importing atomicshop package to get the version of the package.
|
|
5
7
|
import atomicshop
|
|
@@ -10,7 +12,8 @@ from .connection_thread_worker import thread_worker_main
|
|
|
10
12
|
from .. import filesystem, queues
|
|
11
13
|
from ..python_functions import get_current_python_version_string, check_python_version_compliance
|
|
12
14
|
from ..wrappers.socketw.socket_wrapper import SocketWrapper
|
|
13
|
-
from ..wrappers.socketw
|
|
15
|
+
from ..wrappers.socketw import dns_server
|
|
16
|
+
from ..wrappers.psutilw import networks
|
|
14
17
|
from ..basics import dicts_nested
|
|
15
18
|
from ..wrappers.loggingw import loggingw
|
|
16
19
|
from ..print_api import print_api
|
|
@@ -41,8 +44,15 @@ def initialize_mitm_server(config_static):
|
|
|
41
44
|
config['certificates']['sni_server_certificate_from_server_socket_download_directory'])
|
|
42
45
|
|
|
43
46
|
# Create a logger that will log messages to file, Initiate System logger.
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
logger_name = "system"
|
|
48
|
+
system_logger = loggingw.create_logger(
|
|
49
|
+
logger_name=logger_name,
|
|
50
|
+
file_path=f'{config['log']['logs_path']}{os.sep}{logger_name}.txt',
|
|
51
|
+
add_stream=True,
|
|
52
|
+
add_timedfile=True,
|
|
53
|
+
formatter_streamhandler='DEFAULT',
|
|
54
|
+
formatter_filehandler='DEFAULT'
|
|
55
|
+
)
|
|
46
56
|
|
|
47
57
|
# Writing first log.
|
|
48
58
|
system_logger.info("======================================")
|
|
@@ -175,14 +185,24 @@ def initialize_mitm_server(config_static):
|
|
|
175
185
|
config_static.CONFIG_EXTENDED['certificates']['domains_all_times'] = list(domains_engine_list_full)
|
|
176
186
|
|
|
177
187
|
# Creating Statistics logger.
|
|
178
|
-
statistics_logger = loggingw.
|
|
179
|
-
logger_name="statistics",
|
|
180
|
-
|
|
188
|
+
statistics_logger = loggingw.create_logger(
|
|
189
|
+
logger_name="statistics",
|
|
190
|
+
directory_path=config['log']['logs_path'],
|
|
191
|
+
add_timedfile=True,
|
|
192
|
+
formatter_filehandler='MESSAGE',
|
|
193
|
+
file_type='csv',
|
|
194
|
+
header=STATISTICS_HEADER
|
|
181
195
|
)
|
|
182
196
|
|
|
183
197
|
network_logger_name = "network"
|
|
184
|
-
network_logger = loggingw.
|
|
185
|
-
logger_name=network_logger_name,
|
|
198
|
+
network_logger = loggingw.create_logger(
|
|
199
|
+
logger_name=network_logger_name,
|
|
200
|
+
directory_path=config['log']['logs_path'],
|
|
201
|
+
add_stream=True,
|
|
202
|
+
add_timedfile=True,
|
|
203
|
+
formatter_streamhandler='DEFAULT',
|
|
204
|
+
formatter_filehandler='DEFAULT'
|
|
205
|
+
)
|
|
186
206
|
system_logger.info(f"Loaded network logger: {network_logger}")
|
|
187
207
|
|
|
188
208
|
# Initiate Listener logger, which is a child of network logger, so he uses the same settings and handlers
|
|
@@ -194,19 +214,41 @@ def initialize_mitm_server(config_static):
|
|
|
194
214
|
|
|
195
215
|
# === Initialize DNS module ====================================================================================
|
|
196
216
|
if config['dns']['enable_dns_server']:
|
|
217
|
+
# Check if the DNS server port is in use.
|
|
218
|
+
port_in_use = networks.get_processes_using_port_list([config['dns']['listening_port']])
|
|
219
|
+
if port_in_use:
|
|
220
|
+
for port, process_info in port_in_use.items():
|
|
221
|
+
message = f"Port [{port}] is already in use by process: {process_info}"
|
|
222
|
+
print_api(message, error_type=True, logger_method='critical', logger=system_logger)
|
|
223
|
+
|
|
224
|
+
# Wait for the message to be printed and saved to file.
|
|
225
|
+
time.sleep(1)
|
|
226
|
+
sys.exit(1)
|
|
227
|
+
|
|
197
228
|
# before executing TCP sockets and after executing 'network' logger.
|
|
198
|
-
|
|
229
|
+
dns_server_instance = dns_server.DnsServer(config)
|
|
199
230
|
# Passing the engine domain list to DNS server to work with.
|
|
200
231
|
# 'list' function re-initializes the current list, or else it will be the same instance object.
|
|
201
|
-
|
|
232
|
+
dns_server_instance.domain_list = list(domains_engine_list_full)
|
|
202
233
|
|
|
203
|
-
|
|
234
|
+
dns_server_instance.request_domain_queue = domain_queue
|
|
204
235
|
# Initiate the thread.
|
|
205
|
-
threading.Thread(target=
|
|
236
|
+
dns_thread = threading.Thread(target=dns_server_instance.start)
|
|
237
|
+
dns_thread.daemon = True
|
|
238
|
+
dns_thread.start()
|
|
206
239
|
|
|
207
240
|
# === EOF Initialize DNS module ================================================================================
|
|
208
241
|
# === Initialize TCP Server ====================================================================================
|
|
209
242
|
if config['tcp']['enable_tcp_server']:
|
|
243
|
+
port_in_use = networks.get_processes_using_port_list(config['tcp']['listening_port_list'])
|
|
244
|
+
if port_in_use:
|
|
245
|
+
for port, process_info in port_in_use.items():
|
|
246
|
+
print_api(f"Port [{port}] is already in use by process: {process_info}", logger=system_logger,
|
|
247
|
+
error_type=True, logger_method='critical')
|
|
248
|
+
# Wait for the message to be printed and saved to file.
|
|
249
|
+
time.sleep(1)
|
|
250
|
+
sys.exit(1)
|
|
251
|
+
|
|
210
252
|
socket_wrapper = SocketWrapper(
|
|
211
253
|
config=dicts_nested.merge(config, config_static.CONFIG_EXTENDED), logger=listener_logger,
|
|
212
254
|
statistics_logger=statistics_logger)
|
atomicshop/print_api.py
CHANGED
|
@@ -99,15 +99,16 @@ def print_api(
|
|
|
99
99
|
# If 'logger.error' should be outputted to console, and 'color' wasn't selected, then set color to 'yellow'.
|
|
100
100
|
if logger_method == 'error' and not color:
|
|
101
101
|
color = 'yellow'
|
|
102
|
-
error_type = True
|
|
103
102
|
# If 'logger.critical' should be outputted to console, and 'color' wasn't selected, then set color to 'red'.
|
|
104
103
|
elif logger_method == 'critical' and not color:
|
|
105
104
|
color = 'red'
|
|
106
|
-
error_type = True
|
|
107
105
|
|
|
108
106
|
if color:
|
|
109
107
|
message = get_colors_basic_dict(color) + message + ColorsBasic.END
|
|
110
108
|
|
|
109
|
+
if logger_method == 'error' or logger_method == 'critical':
|
|
110
|
+
error_type = True
|
|
111
|
+
|
|
111
112
|
# If exception was raised and 'stderr=True'.
|
|
112
113
|
if sys.exc_info()[0] is not None and stderr:
|
|
113
114
|
# If 'traceback' is set to 'True', we'll output traceback of exception.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import time
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
# Log formatter, means how the log will look inside the file
|
|
@@ -8,32 +9,9 @@ import logging
|
|
|
8
9
|
|
|
9
10
|
# ".40" truncating the string to only 40 characters. Example: %(message).250s
|
|
10
11
|
|
|
11
|
-
# Adding '%(asctime)s.%(msecs)06f' will print milliseconds as well as nanoseconds:
|
|
12
|
-
# 2022-02-17 15:15:51,913.335562
|
|
13
|
-
# If you don't use custom 'datefmt' in your 'setFormatter' function,
|
|
14
|
-
# it will print duplicate milliseconds:
|
|
15
|
-
# 2022-02-17 15:15:51,913.913.335562
|
|
16
|
-
# The setting should be like:
|
|
17
|
-
# file_handler.setFormatter(logging.Formatter(log_formatter_file, datefmt='%Y-%m-%d,%H:%M:%S'))
|
|
18
|
-
# 's' stands for string. 'd' stands for digits, a.k.a. 'int'. 'f' stands for float.
|
|
19
|
-
|
|
20
|
-
# Old tryouts:
|
|
21
|
-
# log_formatter_file: str = f"%(asctime)s.%(msecs)06f | " \
|
|
22
|
-
# log_formatter_file: str = f"%(asctime)s.%(msecs) | " \
|
|
23
|
-
# f"%(levelname)-{len(log_header_level)}s | " \
|
|
24
|
-
# f"%(name)-{len(log_header_logger)}s | " \
|
|
25
|
-
# f"%(filename)-{len(log_header_script)}s : " \
|
|
26
|
-
# f"%(lineno)-{len(log_header_line)}d | " \
|
|
27
|
-
# "%(threadName)s: %(message)s"
|
|
28
|
-
# log_formatter_file: str = "{asctime}.{msecs:0<3.0f} | " \
|
|
29
|
-
# log_formatter_file: str = "{asctime}.{msecs:0>3.0f}.{msecs:0>.6f} | " \
|
|
30
|
-
|
|
31
|
-
# Old tryouts for reference:
|
|
32
|
-
# file_formatter = logging.Formatter(log_formatter_file, style='{')
|
|
33
|
-
# file_formatter.default_time_format = '%Y-%m-%d %H:%M:%S'
|
|
34
|
-
# file_formatter.default_msec_format = '%s,%03d'
|
|
35
|
-
# file_formatter.default_msec_format = '%s,%03f'
|
|
36
12
|
|
|
13
|
+
DEFAULT_STREAM_FORMATTER: str = "%(levelname)s | %(threadName)s | %(name)s | %(message)s"
|
|
14
|
+
DEFAULT_MESSAGE_FORMATTER: str = "%(message)s"
|
|
37
15
|
|
|
38
16
|
FORMAT_ELEMENT_TO_HEADER: dict = {
|
|
39
17
|
'asctime': 'Event Time [Y-M-D H:M:S]',
|
|
@@ -56,7 +34,7 @@ FORMAT_ELEMENT_TO_HEADER: dict = {
|
|
|
56
34
|
}
|
|
57
35
|
|
|
58
36
|
DEFAULT_FORMATTER_TXT_FILE: str = \
|
|
59
|
-
"{asctime}
|
|
37
|
+
"{asctime} | " \
|
|
60
38
|
"{levelname:<" + f"{len(FORMAT_ELEMENT_TO_HEADER['levelname'])}" + "s} | " \
|
|
61
39
|
"{name:<" + f"{len(FORMAT_ELEMENT_TO_HEADER['name'])}" + "s} | " \
|
|
62
40
|
"{filename:<" + f"{len(FORMAT_ELEMENT_TO_HEADER['filename'])}" + "s} : " \
|
|
@@ -64,7 +42,53 @@ DEFAULT_FORMATTER_TXT_FILE: str = \
|
|
|
64
42
|
"{threadName} | {message}"
|
|
65
43
|
|
|
66
44
|
DEFAULT_FORMATTER_CSV_FILE: str = \
|
|
67
|
-
'\"{asctime}
|
|
45
|
+
'\"{asctime}\",{levelname},{name},{filename},{lineno},{threadName},\"{message}\"'
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class NanosecondsFormatter(logging.Formatter):
|
|
49
|
+
def __init__(self, fmt=None, datefmt=None, style='%', use_nanoseconds=False):
|
|
50
|
+
super().__init__(fmt, datefmt, style)
|
|
51
|
+
self.use_nanoseconds = use_nanoseconds
|
|
52
|
+
|
|
53
|
+
def formatTime(self, record, datefmt=None):
|
|
54
|
+
ct = self.converter(record.created)
|
|
55
|
+
|
|
56
|
+
if datefmt:
|
|
57
|
+
# Remove unsupported %f from datefmt if present
|
|
58
|
+
if '%f' in datefmt:
|
|
59
|
+
datefmt = datefmt.replace('%f', '')
|
|
60
|
+
self.use_nanoseconds = True
|
|
61
|
+
else:
|
|
62
|
+
# Default time format if datefmt is not provided
|
|
63
|
+
datefmt = '%Y-%m-%d %H:%M:%S'
|
|
64
|
+
|
|
65
|
+
s = time.strftime(datefmt, ct)
|
|
66
|
+
|
|
67
|
+
if self.use_nanoseconds:
|
|
68
|
+
# Calculate nanoseconds from the fractional part of the timestamp
|
|
69
|
+
nanoseconds = f'{record.created:.9f}'.split('.')[1]
|
|
70
|
+
# Return the formatted string with nanoseconds appended
|
|
71
|
+
return f'{s}.{nanoseconds}'
|
|
72
|
+
else:
|
|
73
|
+
return s
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# if datefmt is None:
|
|
80
|
+
# # Use the default behavior if no datefmt is provided
|
|
81
|
+
# return super().formatTime(record, datefmt)
|
|
82
|
+
# elif '%f' in datefmt:
|
|
83
|
+
# # Format the time up to seconds
|
|
84
|
+
# base_time = time.strftime(datefmt.replace('%f', ''), ct)
|
|
85
|
+
# # Calculate nanoseconds from the fractional part of the timestamp
|
|
86
|
+
# nanoseconds = f'{record.created:.9f}'.split('.')[1]
|
|
87
|
+
# # Return the formatted string with nanoseconds appended
|
|
88
|
+
# return base_time + nanoseconds
|
|
89
|
+
# else:
|
|
90
|
+
# # Use the provided datefmt if it doesn't include %f
|
|
91
|
+
# return time.strftime(datefmt, ct)
|
|
68
92
|
|
|
69
93
|
|
|
70
94
|
class FormatterProcessor:
|
|
@@ -150,7 +174,11 @@ class FormatterProcessor:
|
|
|
150
174
|
|
|
151
175
|
|
|
152
176
|
def get_logging_formatter_from_string(
|
|
153
|
-
formatter: str,
|
|
177
|
+
formatter: str,
|
|
178
|
+
style=None,
|
|
179
|
+
datefmt=None,
|
|
180
|
+
use_nanoseconds: bool = False
|
|
181
|
+
) -> logging.Formatter:
|
|
154
182
|
"""
|
|
155
183
|
Function to get the logging formatter from the string.
|
|
156
184
|
|
|
@@ -160,12 +188,12 @@ def get_logging_formatter_from_string(
|
|
|
160
188
|
'%': will use the '%' style.
|
|
161
189
|
'{': will use the '{' style.
|
|
162
190
|
:param datefmt: string, date format of 'asctime' element. Default is None.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
191
|
+
We use custom formatter that can process the date format with nanoseconds:
|
|
192
|
+
'%Y-%m-%d %H:%M:%S.%f' -> '2021-01-01 00:00:00.000000000'
|
|
193
|
+
:param use_nanoseconds: bool, if set to True, the formatter will use nanoseconds instead of milliseconds.
|
|
194
|
+
This will print 'asctime' in the following format: '2021-01-01 00:00:00.000000000', instead of
|
|
195
|
+
'2021-01-01 00:00:00.000'.
|
|
196
|
+
|
|
169
197
|
:return: logging.Formatter, formatter.
|
|
170
198
|
"""
|
|
171
199
|
|
|
@@ -173,10 +201,8 @@ def get_logging_formatter_from_string(
|
|
|
173
201
|
if not style:
|
|
174
202
|
style = FormatterProcessor(formatter).get_style()['style']
|
|
175
203
|
|
|
176
|
-
# The regular 'datefmt' is '%Y-%m-%d,%H:%M:%S:%f'. If we want to use it with milliseconds 'msecs' element,
|
|
177
|
-
# we need to disable the duplicate milliseconds.
|
|
178
|
-
if disable_duplicate_ms:
|
|
179
|
-
datefmt = '%Y-%m-%d,%H:%M:%S'
|
|
180
|
-
|
|
181
204
|
# Create the logging formatter.
|
|
182
|
-
|
|
205
|
+
if use_nanoseconds or '%f' in datefmt:
|
|
206
|
+
return NanosecondsFormatter(formatter, style=style, datefmt=datefmt, use_nanoseconds=use_nanoseconds)
|
|
207
|
+
else:
|
|
208
|
+
return logging.Formatter(formatter, style=style, datefmt=datefmt)
|