atomicshop 2.16.28__py3-none-any.whl → 2.16.30__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.

Files changed (31) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/archiver/zips.py +5 -6
  3. atomicshop/basics/strings.py +2 -2
  4. atomicshop/dns.py +8 -6
  5. atomicshop/file_io/file_io.py +7 -7
  6. atomicshop/filesystem.py +6 -7
  7. atomicshop/http_parse.py +82 -67
  8. atomicshop/mitm/connection_thread_worker.py +187 -231
  9. atomicshop/mitm/engines/__parent/parser___parent.py +1 -4
  10. atomicshop/mitm/engines/__parent/recorder___parent.py +6 -2
  11. atomicshop/mitm/message.py +29 -20
  12. atomicshop/mitm/mitm_main.py +36 -32
  13. atomicshop/print_api.py +13 -53
  14. atomicshop/system_resource_monitor.py +8 -8
  15. atomicshop/system_resources.py +8 -8
  16. atomicshop/web.py +10 -10
  17. atomicshop/wrappers/loggingw/filters.py +23 -0
  18. atomicshop/wrappers/loggingw/handlers.py +20 -0
  19. atomicshop/wrappers/loggingw/loggingw.py +130 -7
  20. atomicshop/wrappers/playwrightw/engine.py +6 -7
  21. atomicshop/wrappers/playwrightw/waits.py +9 -7
  22. atomicshop/wrappers/socketw/dns_server.py +2 -2
  23. atomicshop/wrappers/socketw/sender.py +56 -28
  24. atomicshop/wrappers/socketw/sni.py +27 -16
  25. atomicshop/wrappers/socketw/socket_client.py +6 -5
  26. atomicshop/wrappers/socketw/socket_wrapper.py +49 -14
  27. {atomicshop-2.16.28.dist-info → atomicshop-2.16.30.dist-info}/METADATA +1 -1
  28. {atomicshop-2.16.28.dist-info → atomicshop-2.16.30.dist-info}/RECORD +31 -31
  29. {atomicshop-2.16.28.dist-info → atomicshop-2.16.30.dist-info}/LICENSE.txt +0 -0
  30. {atomicshop-2.16.28.dist-info → atomicshop-2.16.30.dist-info}/WHEEL +0 -0
  31. {atomicshop-2.16.28.dist-info → atomicshop-2.16.30.dist-info}/top_level.txt +0 -0
@@ -2,11 +2,13 @@ import logging
2
2
  import os
3
3
  from typing import Literal, Union
4
4
  import datetime
5
+ import contextlib
6
+ import threading
5
7
 
6
8
  from . import loggers, handlers
7
9
  from ...file_io import csvs
8
- from ...basics import tracebacks
9
- from ...print_api import print_api
10
+ from ...basics import tracebacks, ansi_escape_codes
11
+ from ...import print_api
10
12
 
11
13
 
12
14
  class LoggingwLoggerAlreadyExistsError(Exception):
@@ -274,25 +276,146 @@ def is_logger_exists(logger_name: str) -> bool:
274
276
  return loggers.is_logger_exists(logger_name)
275
277
 
276
278
 
279
+ def find_the_parent_logger_with_stream_handler(logger: logging.Logger) -> logging.Logger:
280
+ """
281
+ Function to find the parent logger with StreamHandler.
282
+ Example:
283
+ logger_name = "parent.child.grandchild"
284
+ 'parent' logger has StreamHandler, but 'child' and 'grandchild' don't.
285
+ This function will return the 'parent' logger, since both 'child' and 'grandchild' will inherit the
286
+ StreamHandler from the 'parent' logger.
287
+
288
+ :param logger: Logger to find the parent logger with StreamHandler.
289
+ :return: Parent logger with StreamHandler.
290
+ """
291
+
292
+ # Start with current logger to see if it has a stream handler.
293
+ current_logger = logger
294
+ found: bool = False
295
+ while current_logger and not current_logger.handlers:
296
+ for handler in current_logger.handlers:
297
+ if isinstance(handler, logging.StreamHandler):
298
+ found = True
299
+ break
300
+
301
+ if not found:
302
+ # If the current logger doesn't have the stream handler, let's move to the parent.
303
+ current_logger = current_logger.parent
304
+
305
+ return current_logger
306
+
307
+
308
+ @contextlib.contextmanager
309
+ def _temporary_change_logger_stream_handler_color(logger: logging.Logger, color: str):
310
+ """
311
+ THIS IS ONLY FOR REFERENCE, for better result use the 'temporary_change_logger_stream_handler_emit_color' function.
312
+ If there are several threads that use this logger, there could be a problem, since unwanted messages
313
+ could be colored with the color of the other thread. 'temporary_change_logger_stream_handler_emit_color' is thread
314
+ safe and will color only the messages from the current thread.
315
+
316
+ Context manager to temporarily change the color of the logger's StreamHandler formatter.
317
+
318
+ Example:
319
+ with temporary_change_logger_stream_handler_color(logger, color):
320
+ # Do something with the temporary color.
321
+ pass
322
+ """
323
+
324
+ # Find the current or the topmost logger's StreamHandler.
325
+ # Could be that it is a child logger inherits its handlers from the parent.
326
+ logger_with_handlers = find_the_parent_logger_with_stream_handler(logger)
327
+
328
+ found_stream_handler = None
329
+ for handler in logger_with_handlers.handlers:
330
+ if isinstance(handler, logging.StreamHandler):
331
+ found_stream_handler = handler
332
+ break
333
+
334
+ # Save the original formatter
335
+ original_formatter = found_stream_handler.formatter
336
+ original_formatter_string = handlers.get_formatter_string(found_stream_handler)
337
+
338
+ # Create a colored formatter for errors
339
+ color_formatter = logging.Formatter(
340
+ ansi_escape_codes.get_colors_basic_dict(color) + original_formatter_string +
341
+ ansi_escape_codes.ColorsBasic.END)
342
+
343
+ # thread_id = threading.get_ident()
344
+ # color_filter = filters.ThreadColorLogFilter(color, thread_id)
345
+ # found_stream_handler.addFilter(color_filter)
346
+ try:
347
+ found_stream_handler.setFormatter(color_formatter)
348
+ yield
349
+ finally:
350
+ found_stream_handler.setFormatter(original_formatter)
351
+ # found_stream_handler.removeFilter(color_filter)
352
+
353
+
354
+ # Thread-local storage to store color codes per thread
355
+ thread_local = threading.local()
356
+
357
+
358
+ @contextlib.contextmanager
359
+ def temporary_change_logger_stream_handler_emit_color(logger: logging.Logger, color: str):
360
+ """Context manager to temporarily set the color code for log messages in the current thread."""
361
+
362
+ # Find the current or the topmost logger's StreamHandler.
363
+ # Could be that it is a child logger inherits its handlers from the parent.
364
+ logger_with_handlers = find_the_parent_logger_with_stream_handler(logger)
365
+
366
+ found_stream_handler = None
367
+ for handler in logger_with_handlers.handlers:
368
+ if isinstance(handler, logging.StreamHandler):
369
+ found_stream_handler = handler
370
+ break
371
+
372
+ # Save the original emit method of the stream handler
373
+ original_emit = found_stream_handler.emit
374
+
375
+ def emit_with_color(record):
376
+ original_msg = record.msg
377
+ # Check if the current thread has a color code
378
+ if getattr(thread_local, 'color', None):
379
+ record.msg = (
380
+ ansi_escape_codes.get_colors_basic_dict(color) + original_msg +
381
+ ansi_escape_codes.ColorsBasic.END)
382
+ original_emit(record) # Call the original emit method
383
+ record.msg = original_msg # Restore the original message for other handlers
384
+
385
+ # Replace the emit method with our custom method
386
+ found_stream_handler.emit = emit_with_color
387
+
388
+ # Set the color code in thread-local storage for this thread
389
+ thread_local.color = color
390
+
391
+ try:
392
+ yield
393
+ finally:
394
+ # Restore the original emit method after the context manager is exited
395
+ found_stream_handler.emit = original_emit
396
+ # Clear the color code from thread-local storage
397
+ thread_local.color = None
398
+
399
+
277
400
  class ExceptionCsvLogger:
278
401
  def __init__(
279
402
  self,
280
403
  logger_name: str,
281
- custom_header: str = None,
282
- directory_path: str = None
404
+ directory_path: str = None,
405
+ custom_header: str = None
283
406
  ):
284
407
  """
285
408
  Initialize the ExceptionCsvLogger object.
286
409
 
287
410
  :param logger_name: Name of the logger.
411
+ :param directory_path: Directory path where the log file will be saved.
412
+ You can leave it as None, but if the logger doesn't exist, you will get an exception.
288
413
  :param custom_header: Custom header to write to the log file.
289
414
  If None, the default header will be used: "timestamp,exception", since that what is written to the log file.
290
415
  If you want to add more columns to the csv file, you can provide a custom header:
291
416
  "custom1,custom2,custom3".
292
417
  These will be added to the default header as:
293
418
  "timestamp,custom1,custom2,custom3,exception".
294
- :param directory_path: Directory path where the log file will be saved.
295
- You can leave it as None, but if the logger doesn't exist, you will get an exception.
296
419
  """
297
420
 
298
421
  if custom_header:
@@ -352,7 +475,7 @@ class ExceptionCsvLogger:
352
475
  self.logger.info(output_csv_line)
353
476
 
354
477
  if stdout:
355
- print_api('', error_type=True, color="red", traceback_string=True)
478
+ print_api.print_api('', error_type=True, color="red", traceback_string=True)
356
479
 
357
480
  def get_logger(self):
358
481
  return self.logger
@@ -5,11 +5,10 @@ import random
5
5
  import getpass
6
6
  from tempfile import gettempdir
7
7
 
8
- from ...print_api import print_api
9
8
  from ...keyboard_press import send_alt_tab
10
- from ... import filesystem
9
+ from ... import filesystem, print_api
11
10
 
12
- # Web automation library.
11
+ # noinspection PyPackageRequirements
13
12
  from playwright.sync_api import sync_playwright
14
13
  # Stealth options for playwright. External.
15
14
  from playwright_stealth import stealth_sync
@@ -242,7 +241,7 @@ class PlaywrightEngine:
242
241
  for i in range(element_count):
243
242
  string_current = string_current + element.nth(i).text_content()
244
243
 
245
- print_api(f'Current element text of [{locator_string}]: {string_current}', rtl=True)
244
+ print_api.print_api(f'Current element text of [{locator_string}]: {string_current}', rtl=True)
246
245
 
247
246
  # If text from previous cycle isn't the same as text from current cycle, then put the new value to the
248
247
  # previous one and return 'True' since the text really changed.
@@ -312,7 +311,7 @@ class PlaywrightEngine:
312
311
  # Nullifying 'string_previous', so new loop will not have the same one as previous loop in case of error.
313
312
  self.string_previous = str()
314
313
 
315
- print_api('Finished execution Time: ' + str(datetime.datetime.now()), **kwargs)
316
- print_api('Waiting minutes: ' + str(time_to_sleep_minutes), **kwargs)
314
+ print_api.print_api('Finished execution Time: ' + str(datetime.datetime.now()), **kwargs)
315
+ print_api.print_api('Waiting minutes: ' + str(time_to_sleep_minutes), **kwargs)
317
316
  time.sleep(time_to_sleep_minutes * 60)
318
- print_api('-----------------------------------------', **kwargs)
317
+ print_api.print_api('-----------------------------------------', **kwargs)
@@ -1,7 +1,9 @@
1
- from atomicshop.print_api import print_api
1
+ from ... import print_api
2
2
 
3
+ # noinspection PyPackageRequirements
3
4
  from playwright.sync_api import expect
4
5
  # This is from official docs: https://playwright.dev/python/docs/api/class-timeouterror
6
+ # noinspection PyPackageRequirements
5
7
  from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
6
8
 
7
9
 
@@ -124,7 +126,7 @@ def network_fully_idle(page, timeout: int = 2000, print_kwargs: dict = None) ->
124
126
  # 'page.expect_response' will wait for the response to be received, and then return the response object.
125
127
  # When timeout is reached, it will raise a TimeoutError, which will break the while loop.
126
128
  with page.expect_response("**/*", timeout=timeout) as response_info:
127
- print_api(response_info.value, **print_kwargs)
129
+ print_api.print_api(response_info.value, **print_kwargs)
128
130
  except PlaywrightTimeoutError:
129
131
  break
130
132
 
@@ -151,13 +153,13 @@ def maximum_idle(page, print_kwargs: dict = None) -> None:
151
153
  :return: None
152
154
  """
153
155
 
154
- print_api('Before wait_for_load', **print_kwargs)
156
+ print_api.print_api('Before wait_for_load', **print_kwargs)
155
157
  load(page)
156
- print_api('After wait_for_load, Before wait_for_domcontentloaded', **print_kwargs)
158
+ print_api.print_api('After wait_for_load, Before wait_for_domcontentloaded', **print_kwargs)
157
159
  domcontentloaded(page)
158
- print_api('After wait_for_domcontentloaded', **print_kwargs)
160
+ print_api.print_api('After wait_for_domcontentloaded', **print_kwargs)
159
161
  # For some reason 'networkidle' can result in timeout errors, so currently this is disabled.
160
162
  # networkidle(page)
161
- print_api('Before wait_for_network_fully_idle', **print_kwargs)
163
+ print_api.print_api('Before wait_for_network_fully_idle', **print_kwargs)
162
164
  network_fully_idle(page, print_kwargs=print_kwargs)
163
- print_api('After wait_for_network_fully_idle', **print_kwargs)
165
+ print_api.print_api('After wait_for_network_fully_idle', **print_kwargs)
@@ -317,7 +317,7 @@ class DnsServer:
317
317
 
318
318
  if self.resolve_to_tcp_server_all_domains:
319
319
  message = "Routing all domains to Built-in TCP Server."
320
- print_api(message, logger=self.logger, color='green')
320
+ print_api(message, logger=self.logger, color='blue')
321
321
 
322
322
  if self.resolve_regular:
323
323
  message = f"Routing all domains to Live DNS Service: {self.forwarding_dns_service_ipv4}"
@@ -589,7 +589,7 @@ class DnsServer:
589
589
  google_dns_ipv4_socket.recvfrom(self.buffer_size_receive)
590
590
  except TimeoutError as function_exception_object:
591
591
  print_api(function_exception_object, logger=self.logger, logger_method='error',
592
- traceback_string=True)
592
+ traceback_string=True, oneline=True)
593
593
  google_dns_ipv4_socket.close()
594
594
  counter += 1
595
595
  # Pass the exception.
@@ -4,6 +4,9 @@ from pathlib import Path
4
4
 
5
5
  from ...print_api import print_api
6
6
  from ..loggingw import loggingw
7
+ from ...basics import tracebacks
8
+
9
+ from . import base, ssl_base
7
10
 
8
11
 
9
12
  class Sender:
@@ -29,18 +32,22 @@ class Sender:
29
32
  # until other side receives all, so there's no way knowing how much data was sent. Returns "None" on
30
33
  # Success though.
31
34
 
32
- # Defining function result variable which will mean if the socket was closed or not.
33
- # True means everything is fine
34
- function_result: bool = True
35
+ # The error string that will be returned by the function in case of error.
36
+ # If returned None then everything is fine.
37
+ # noinspection PyTypeChecker
38
+ function_result_error: str = None
35
39
  # Current amount of bytes sent is 0, since we didn't start yet
36
40
  total_sent_bytes = 0
37
41
 
42
+ # noinspection PyTypeChecker
43
+ error_message: str = None
38
44
  try:
39
45
  # Getting byte length of current message
40
46
  current_message_length = len(self.class_message)
41
47
 
42
- self.logger.info(f"Sending message to "
43
- f"{self.ssl_socket.getpeername()[0]}:{self.ssl_socket.getpeername()[1]}")
48
+ self.logger.info(
49
+ f"Sending message to "
50
+ f"{self.ssl_socket.getpeername()[0]}:{self.ssl_socket.getpeername()[1]}")
44
51
 
45
52
  # Looping through "socket.send()" method while total sent bytes are less than message length
46
53
  while total_sent_bytes < current_message_length:
@@ -48,9 +55,11 @@ class Sender:
48
55
  sent_bytes = self.ssl_socket.send(self.class_message[total_sent_bytes:])
49
56
  # If there were only "0" bytes sent, then connection on the other side was terminated
50
57
  if sent_bytes == 0:
51
- self.logger.info(f"Sent {sent_bytes} bytes - connection is down... Could send only "
52
- f"{total_sent_bytes} bytes out of {current_message_length}. Closing socket...")
53
- function_result = False
58
+ error_message = (
59
+ f"Sent {sent_bytes} bytes - connection is down... Could send only "
60
+ f"{total_sent_bytes} bytes out of {current_message_length}. Closing socket...")
61
+ self.logger.info(error_message)
62
+ function_result_error = error_message
54
63
  break
55
64
 
56
65
  # Adding amount of currently sent bytes to the total amount of bytes sent
@@ -59,23 +68,42 @@ class Sender:
59
68
 
60
69
  # At this point the sending loop finished successfully
61
70
  self.logger.info(f"Sent the message to destination.")
62
- except ConnectionResetError:
63
- message = "* Couldn't reach the server - Connection was reset. Exiting..."
64
- print_api(message, logger=self.logger, logger_method='critical', traceback_string=True)
65
- # Since the connection is down, it will be handled in thread_worker_main
66
- function_result = False
67
- pass
68
- except ssl.SSLEOFError:
69
- message = "SSLError on send, Exiting..."
70
- print_api(message, logger=self.logger, logger_method='critical', traceback_string=True, oneline=True)
71
- # Since the connection is down, it will be handled in thread_worker_main
72
- function_result = False
73
- pass
74
- except ssl.SSLZeroReturnError:
75
- message = "TLS/SSL connection has been closed (EOF), Exiting..."
76
- print_api(message, logger=self.logger, logger_method='critical', traceback_string=True, oneline=True)
77
- # Since the connection is down, it will be handled in thread_worker_main
78
- function_result = False
79
- pass
80
-
81
- return function_result
71
+ except ConnectionResetError as e:
72
+ destination_address, destination_port = base.get_destination_address_from_socket(self.ssl_socket)
73
+ if self.ssl_socket.server_hostname:
74
+ destination_address = self.ssl_socket.server_hostname
75
+ destination: str = f'{destination_address}:{destination_port}'
76
+
77
+ error_class_type = str(type(e)).replace("<class '", '').replace("'>", '')
78
+ exception_error = tracebacks.get_as_string(one_line=True)
79
+ error_message = (f"Socket Send: {destination}: Error, Couldn't reach the server - Connection was reset | "
80
+ f"{error_class_type}: {exception_error}")
81
+ except (ssl.SSLEOFError, ssl.SSLZeroReturnError, ssl.SSLWantWriteError, TimeoutError) as e:
82
+ destination_address, destination_port = base.get_destination_address_from_socket(self.ssl_socket)
83
+ if self.ssl_socket.server_hostname:
84
+ destination_address = self.ssl_socket.server_hostname
85
+ destination: str = f'{destination_address}:{destination_port}'
86
+
87
+ error_class_type = str(type(e)).replace("<class '", '').replace("'>", '')
88
+ exception_error = tracebacks.get_as_string(one_line=True)
89
+ error_message = f"Socket Send: {destination}: {error_class_type}: {exception_error}"
90
+ except Exception as e:
91
+ destination_address, destination_port = base.get_destination_address_from_socket(self.ssl_socket)
92
+ if self.ssl_socket.server_hostname:
93
+ destination_address = self.ssl_socket.server_hostname
94
+ destination: str = f'{destination_address}:{destination_port}'
95
+
96
+ error_class_type = str(type(e)).replace("<class '", '').replace("'>", '')
97
+ exception_error = tracebacks.get_as_string(one_line=True)
98
+ if 'ssl' in error_class_type.lower():
99
+ error_message = (f"Socket Send: {destination}: "
100
+ f"SSL UNDOCUMENTED Exception: {error_class_type}{exception_error}")
101
+ else:
102
+ error_message = (f"Socket Send: {destination}: "
103
+ f"Error, UNDOCUMENTED Exception: {error_class_type}{exception_error}")
104
+
105
+ if error_message:
106
+ print_api(error_message, logger=self.logger, logger_method='error')
107
+ function_result_error = error_message
108
+
109
+ return function_result_error
@@ -1,10 +1,12 @@
1
1
  import ssl
2
2
  from dataclasses import dataclass
3
3
 
4
- from . import certificator, creator
4
+ from ..loggingw import loggingw
5
5
  from ...domains import get_domain_without_first_subdomain_if_no_subdomain_return_as_is
6
6
  from ...print_api import print_api
7
7
 
8
+ from . import certificator, creator
9
+
8
10
 
9
11
  @dataclass
10
12
  class SNIReceivedParameters:
@@ -43,7 +45,8 @@ class SNISetup:
43
45
  forwarding_dns_service_ipv4_list___only_for_localhost: list,
44
46
  tls: bool,
45
47
  domain_from_dns_server: str = None,
46
- skip_extension_id_list: list = None
48
+ skip_extension_id_list: list = None,
49
+ exceptions_logger: loggingw.ExceptionCsvLogger = None
47
50
  ):
48
51
  self.ca_certificate_name = ca_certificate_name
49
52
  self.ca_certificate_filepath = ca_certificate_filepath
@@ -68,6 +71,7 @@ class SNISetup:
68
71
  self.domain_from_dns_server: str = domain_from_dns_server
69
72
  self.skip_extension_id_list = skip_extension_id_list
70
73
  self.tls = tls
74
+ self.exceptions_logger = exceptions_logger
71
75
 
72
76
  self.certificator_instance = None
73
77
 
@@ -154,7 +158,8 @@ class SNISetup:
154
158
  sni_create_server_certificate_for_each_domain=self.sni_create_server_certificate_for_each_domain,
155
159
  certificator_instance=self.certificator_instance,
156
160
  domain_from_dns_server=self.domain_from_dns_server,
157
- default_certificate_domain_list=self.default_certificate_domain_list
161
+ default_certificate_domain_list=self.default_certificate_domain_list,
162
+ exceptions_logger=self.exceptions_logger
158
163
  )
159
164
  ssl_context.set_servername_callback(
160
165
  sni_handler_instance.setup_sni_callback(print_kwargs=print_kwargs))
@@ -172,7 +177,8 @@ class SNIHandler:
172
177
  sni_create_server_certificate_for_each_domain: bool,
173
178
  certificator_instance: certificator.Certificator,
174
179
  domain_from_dns_server: str,
175
- default_certificate_domain_list: list
180
+ default_certificate_domain_list: list,
181
+ exceptions_logger: loggingw.ExceptionCsvLogger
176
182
  ):
177
183
  self.sni_use_default_callback_function_extended = sni_use_default_callback_function_extended
178
184
  self.sni_add_new_domains_to_default_server_certificate = sni_add_new_domains_to_default_server_certificate
@@ -180,6 +186,7 @@ class SNIHandler:
180
186
  self.certificator_instance = certificator_instance
181
187
  self.domain_from_dns_server: str = domain_from_dns_server
182
188
  self.default_certificate_domain_list = default_certificate_domain_list
189
+ self.exceptions_logger = exceptions_logger
183
190
 
184
191
  # noinspection PyTypeChecker
185
192
  self.sni_received_parameters: SNIReceivedParameters = None
@@ -202,18 +209,22 @@ class SNIHandler:
202
209
  sni_ssl_socket: ssl.SSLSocket,
203
210
  sni_destination_name: str,
204
211
  sni_ssl_context: ssl.SSLContext):
205
- # Set 'server_hostname' for the socket.
206
- sni_ssl_socket.server_hostname = sni_destination_name
207
-
208
- # If 'sni_execute_extended' was set to True.
209
- if self.sni_use_default_callback_function_extended:
210
- self.sni_received_parameters = SNIReceivedParameters(
211
- ssl_socket=sni_ssl_socket,
212
- destination_name=sni_destination_name,
213
- ssl_context=sni_ssl_context
214
- )
215
-
216
- self.sni_handle_extended(print_kwargs=print_kwargs)
212
+
213
+ try:
214
+ # Set 'server_hostname' for the socket.
215
+ sni_ssl_socket.server_hostname = sni_destination_name
216
+
217
+ # If 'sni_execute_extended' was set to True.
218
+ if self.sni_use_default_callback_function_extended:
219
+ self.sni_received_parameters = SNIReceivedParameters(
220
+ ssl_socket=sni_ssl_socket,
221
+ destination_name=sni_destination_name,
222
+ ssl_context=sni_ssl_context
223
+ )
224
+
225
+ self.sni_handle_extended(print_kwargs=print_kwargs)
226
+ except Exception as e:
227
+ self.exceptions_logger.write(e)
217
228
 
218
229
  return sni_handle
219
230
 
@@ -5,7 +5,10 @@ from typing import Literal, Union
5
5
  import logging
6
6
  from pathlib import Path
7
7
 
8
+ # noinspection PyPackageRequirements
8
9
  from cryptography import x509
10
+ # noinspection PyPackageRequirements
11
+ import dns.resolver
9
12
 
10
13
  from . import creator
11
14
  from .receiver import Receiver
@@ -16,8 +19,6 @@ from ..loggingw import loggingw
16
19
  from ...print_api import print_api
17
20
  from ...file_io import file_io
18
21
 
19
- import dns.resolver
20
-
21
22
 
22
23
  class SocketClient:
23
24
  def __init__(
@@ -221,12 +222,12 @@ class SocketClient:
221
222
  f"[{self.service_name}] resolves to ip: [{self.connection_ip}]. Pulled IP from the socket.")
222
223
 
223
224
  # Send the data received from the client to the service over socket
224
- function_data_sent = Sender(
225
+ error_on_send: str = Sender(
225
226
  ssl_socket=self.socket_instance, class_message=request_bytes, logger=self.logger).send()
226
227
 
227
228
  # If the socket disconnected on data send
228
- if not function_data_sent:
229
- error_string = "Service socket closed on data send"
229
+ if error_on_send:
230
+ error_string = f"Service socket closed on data send: {error_on_send}"
230
231
 
231
232
  # We'll close the socket and nullify the object
232
233
  self.close_socket()
@@ -2,6 +2,7 @@ import threading
2
2
  import select
3
3
  from typing import Literal, Union
4
4
  from pathlib import Path
5
+ import logging
5
6
 
6
7
  from ..psutilw import networks
7
8
  from ..certauthw import certauthw
@@ -63,7 +64,8 @@ class SocketWrapper:
63
64
  ],
64
65
  None
65
66
  ] = None,
66
- logger=None,
67
+ logger: logging.Logger = None,
68
+ exceptions_logger: loggingw.ExceptionCsvLogger = None,
67
69
  statistics_logs_directory: str = None,
68
70
  request_domain_from_dns_server_queue: queues.NonBlockQueue = None
69
71
  ):
@@ -144,7 +146,11 @@ class SocketWrapper:
144
146
  :param ssh_pass: string, SSH password that will be used to connect to remote host.
145
147
  :param ssh_script_to_execute: string, script that will be executed to get the process name on ssh remote host.
146
148
  :param logger: logging.Logger object, logger object that will be used to log messages.
147
- If not provided, logger will be created with default settings.
149
+ If not provided, logger will be created with default settings saving logs to the
150
+ 'statistics_logs_directory'.
151
+ :param exceptions_logger: loggingw.ExceptionCsvLogger object, logger object that will be used to log exceptions.
152
+ If not provided, logger will be created with default settings and will save exceptions to the
153
+ 'statistics_logs_directory'.
148
154
  :param statistics_logs_directory: string, path to directory where daily statistics.csv files will be stored.
149
155
  After you initialize the SocketWrapper object, you can get the statistics_writer object from it and use it
150
156
  to write statistics to the file in a worker thread.
@@ -231,6 +237,14 @@ class SocketWrapper:
231
237
  formatter_filehandler='DEFAULT'
232
238
  )
233
239
 
240
+ if not exceptions_logger:
241
+ self.exceptions_logger = loggingw.ExceptionCsvLogger(
242
+ logger_name='SocketWrapperExceptions',
243
+ directory_path=self.statistics_logs_directory
244
+ )
245
+ else:
246
+ self.exceptions_logger = exceptions_logger
247
+
234
248
  self.test_config()
235
249
 
236
250
  def test_config(self):
@@ -412,7 +426,6 @@ class SocketWrapper:
412
426
  listening_socket_list = self.listening_sockets
413
427
 
414
428
  while True:
415
- # noinspection PyBroadException
416
429
  try:
417
430
  # Using "select.select" which is currently the only API function that works on all
418
431
  # operating system types: Windows / Linux / BSD.
@@ -432,7 +445,8 @@ class SocketWrapper:
432
445
  # Wait from any connection on "accept()".
433
446
  # 'client_socket' is socket or ssl socket, 'client_address' is a tuple (ip_address, port).
434
447
  client_socket, client_address, accept_error_message = accepter.accept_connection_with_error(
435
- listening_socket_object, domain_from_dns_server=domain_from_dns_server, print_kwargs={'logger': self.logger})
448
+ listening_socket_object, domain_from_dns_server=domain_from_dns_server,
449
+ print_kwargs={'logger': self.logger})
436
450
 
437
451
  # This is the earliest stage to ask for process name.
438
452
  # SSH Remote / LOCALHOST script execution to identify process section.
@@ -488,7 +502,8 @@ class SocketWrapper:
488
502
  domain_from_dns_server=domain_from_dns_server,
489
503
  forwarding_dns_service_ipv4_list___only_for_localhost=(
490
504
  self.forwarding_dns_service_ipv4_list___only_for_localhost),
491
- tls=is_tls
505
+ tls=is_tls,
506
+ exceptions_logger=self.exceptions_logger
492
507
  )
493
508
 
494
509
  ssl_client_socket, accept_error_message = \
@@ -521,11 +536,15 @@ class SocketWrapper:
521
536
  # If 'pass_function_reference_to_thread' was set to 'False', execute the callable passed function
522
537
  # as is.
523
538
  if not pass_function_reference_to_thread:
524
- reference_function_name(thread_args)
539
+ before_socket_thread_worker(
540
+ callable_function=reference_function_name, thread_args=thread_args,
541
+ exceptions_logger=self.exceptions_logger)
525
542
  # If 'pass_function_reference_to_thread' was set to 'True', execute the callable function reference
526
543
  # in a new thread.
527
544
  else:
528
- self._send_accepted_socket_to_thread(reference_function_name, thread_args)
545
+ self._send_accepted_socket_to_thread(
546
+ before_socket_thread_worker,
547
+ reference_args=(reference_function_name, thread_args, self.exceptions_logger))
529
548
  # Else, if no client_socket was opened during, accept, then print the error.
530
549
  else:
531
550
  # Write statistics after accept.
@@ -533,11 +552,8 @@ class SocketWrapper:
533
552
  error_message=accept_error_message,
534
553
  host=domain_from_dns_server,
535
554
  process_name=process_name)
536
- except Exception:
537
- print_api("Undocumented exception in while loop of listening sockets.", error_type=True,
538
- logger_method="error", traceback_string=True, logger=self.logger)
539
- pass
540
- continue
555
+ except Exception as e:
556
+ self.exceptions_logger.write(e)
541
557
 
542
558
  def _send_accepted_socket_to_thread(self, thread_function_name, reference_args=()):
543
559
  # Creating thread for each socket
@@ -547,7 +563,26 @@ class SocketWrapper:
547
563
  # Append to list of threads, so they can be "joined" later
548
564
  self.threads_list.append(thread_current)
549
565
 
550
- # 'reference_args[0]' is the client socket.
551
- client_address = base.get_source_address_from_socket(reference_args[0])
566
+ # 'reference_args[1][0]' is the client socket.
567
+ client_address = base.get_source_address_from_socket(reference_args[1][0])
552
568
 
553
569
  self.logger.info(f"Accepted connection, thread created {client_address}. Continue listening...")
570
+
571
+
572
+ def before_socket_thread_worker(
573
+ callable_function: callable,
574
+ thread_args: tuple,
575
+ exceptions_logger: loggingw.ExceptionCsvLogger = None
576
+ ):
577
+ """
578
+ Function that will be executed before the thread is started.
579
+ :param callable_function: callable, function that will be executed in the thread.
580
+ :param thread_args: tuple, arguments that will be passed to the function.
581
+ :param exceptions_logger: loggingw.ExceptionCsvLogger, logger object that will be used to log exceptions.
582
+ :return:
583
+ """
584
+
585
+ try:
586
+ callable_function(*thread_args)
587
+ except Exception as e:
588
+ exceptions_logger.write(e)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: atomicshop
3
- Version: 2.16.28
3
+ Version: 2.16.30
4
4
  Summary: Atomic functions and classes to make developer life easier
5
5
  Author: Denis Kras
6
6
  License: MIT License