atomicshop 2.11.47__py3-none-any.whl → 3.10.5__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.
Files changed (268) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
  3. atomicshop/a_mains/addons/process_list/compile.cmd +7 -0
  4. atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.dll +0 -0
  5. atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.exp +0 -0
  6. atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.lib +0 -0
  7. atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +8 -1
  8. atomicshop/a_mains/dns_gateway_setting.py +11 -0
  9. atomicshop/a_mains/get_local_tcp_ports.py +85 -0
  10. atomicshop/a_mains/github_wrapper.py +11 -0
  11. atomicshop/a_mains/install_ca_certificate.py +172 -0
  12. atomicshop/{addons/mains → a_mains}/msi_unpacker.py +3 -1
  13. atomicshop/a_mains/process_from_port.py +119 -0
  14. atomicshop/a_mains/set_default_dns_gateway.py +90 -0
  15. atomicshop/a_mains/update_config_toml.py +38 -0
  16. atomicshop/appointment_management.py +5 -3
  17. atomicshop/basics/ansi_escape_codes.py +3 -1
  18. atomicshop/basics/argparse_template.py +2 -0
  19. atomicshop/basics/booleans.py +27 -30
  20. atomicshop/basics/bytes_arrays.py +43 -0
  21. atomicshop/basics/classes.py +149 -1
  22. atomicshop/basics/dicts.py +12 -0
  23. atomicshop/basics/enums.py +2 -2
  24. atomicshop/basics/exceptions.py +5 -1
  25. atomicshop/basics/list_of_classes.py +29 -0
  26. atomicshop/basics/list_of_dicts.py +69 -5
  27. atomicshop/basics/lists.py +14 -0
  28. atomicshop/basics/multiprocesses.py +374 -50
  29. atomicshop/basics/package_module.py +10 -0
  30. atomicshop/basics/strings.py +160 -7
  31. atomicshop/basics/threads.py +14 -0
  32. atomicshop/basics/tracebacks.py +13 -4
  33. atomicshop/certificates.py +153 -52
  34. atomicshop/config_init.py +12 -7
  35. atomicshop/console_user_response.py +7 -14
  36. atomicshop/consoles.py +9 -0
  37. atomicshop/datetimes.py +98 -0
  38. atomicshop/diff_check.py +340 -40
  39. atomicshop/dns.py +128 -12
  40. atomicshop/etws/_pywintrace_fix.py +17 -0
  41. atomicshop/etws/const.py +38 -0
  42. atomicshop/etws/providers.py +21 -0
  43. atomicshop/etws/sessions.py +43 -0
  44. atomicshop/etws/trace.py +168 -0
  45. atomicshop/etws/traces/trace_dns.py +162 -0
  46. atomicshop/etws/traces/trace_sysmon_process_creation.py +126 -0
  47. atomicshop/etws/traces/trace_tcp.py +130 -0
  48. atomicshop/file_io/csvs.py +222 -24
  49. atomicshop/file_io/docxs.py +35 -18
  50. atomicshop/file_io/file_io.py +35 -19
  51. atomicshop/file_io/jsons.py +49 -0
  52. atomicshop/file_io/tomls.py +139 -0
  53. atomicshop/filesystem.py +864 -293
  54. atomicshop/get_process_list.py +133 -0
  55. atomicshop/{process_name_cmd.py → get_process_name_cmd_dll.py} +52 -19
  56. atomicshop/http_parse.py +149 -93
  57. atomicshop/ip_addresses.py +6 -1
  58. atomicshop/mitm/centered_settings.py +132 -0
  59. atomicshop/mitm/config_static.py +207 -0
  60. atomicshop/mitm/config_toml_editor.py +55 -0
  61. atomicshop/mitm/connection_thread_worker.py +875 -357
  62. atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
  63. atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
  64. atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
  65. atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
  66. atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
  67. atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
  68. atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
  69. atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
  70. atomicshop/mitm/engines/create_module_template.py +58 -14
  71. atomicshop/mitm/import_config.py +359 -139
  72. atomicshop/mitm/initialize_engines.py +160 -74
  73. atomicshop/mitm/message.py +64 -23
  74. atomicshop/mitm/mitm_main.py +892 -0
  75. atomicshop/mitm/recs_files.py +183 -0
  76. atomicshop/mitm/shared_functions.py +4 -10
  77. atomicshop/mitm/ssh_tester.py +82 -0
  78. atomicshop/mitm/statistic_analyzer.py +257 -166
  79. atomicshop/mitm/statistic_analyzer_helper/analyzer_helper.py +136 -0
  80. atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +525 -0
  81. atomicshop/monitor/change_monitor.py +96 -120
  82. atomicshop/monitor/checks/dns.py +139 -70
  83. atomicshop/monitor/checks/file.py +77 -0
  84. atomicshop/monitor/checks/network.py +81 -77
  85. atomicshop/monitor/checks/process_running.py +33 -34
  86. atomicshop/monitor/checks/url.py +94 -0
  87. atomicshop/networks.py +671 -0
  88. atomicshop/on_exit.py +205 -0
  89. atomicshop/package_mains_processor.py +84 -0
  90. atomicshop/permissions/permissions.py +22 -0
  91. atomicshop/permissions/ubuntu_permissions.py +239 -0
  92. atomicshop/permissions/win_permissions.py +33 -0
  93. atomicshop/print_api.py +24 -41
  94. atomicshop/process.py +63 -17
  95. atomicshop/process_poller/__init__.py +0 -0
  96. atomicshop/process_poller/pollers/__init__.py +0 -0
  97. atomicshop/process_poller/pollers/psutil_pywin32wmi_dll.py +95 -0
  98. atomicshop/process_poller/process_pool.py +207 -0
  99. atomicshop/process_poller/simple_process_pool.py +311 -0
  100. atomicshop/process_poller/tracer_base.py +45 -0
  101. atomicshop/process_poller/tracers/__init__.py +0 -0
  102. atomicshop/process_poller/tracers/event_log.py +46 -0
  103. atomicshop/process_poller/tracers/sysmon_etw.py +68 -0
  104. atomicshop/python_file_patcher.py +1 -1
  105. atomicshop/python_functions.py +27 -75
  106. atomicshop/question_answer_engine.py +2 -2
  107. atomicshop/scheduling.py +24 -5
  108. atomicshop/sound.py +4 -2
  109. atomicshop/speech_recognize.py +8 -0
  110. atomicshop/ssh_remote.py +158 -172
  111. atomicshop/startup/__init__.py +0 -0
  112. atomicshop/startup/win/__init__.py +0 -0
  113. atomicshop/startup/win/startup_folder.py +53 -0
  114. atomicshop/startup/win/task_scheduler.py +119 -0
  115. atomicshop/system_resource_monitor.py +61 -46
  116. atomicshop/system_resources.py +8 -8
  117. atomicshop/tempfiles.py +1 -2
  118. atomicshop/timer.py +30 -11
  119. atomicshop/urls.py +41 -0
  120. atomicshop/venvs.py +28 -0
  121. atomicshop/versioning.py +27 -0
  122. atomicshop/web.py +110 -25
  123. atomicshop/web_apis/__init__.py +0 -0
  124. atomicshop/web_apis/google_custom_search.py +44 -0
  125. atomicshop/web_apis/google_llm.py +188 -0
  126. atomicshop/websocket_parse.py +450 -0
  127. atomicshop/wrappers/certauthw/certauth.py +1 -0
  128. atomicshop/wrappers/cryptographyw.py +29 -8
  129. atomicshop/wrappers/ctyping/etw_winapi/__init__.py +0 -0
  130. atomicshop/wrappers/ctyping/etw_winapi/const.py +335 -0
  131. atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +393 -0
  132. atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
  133. atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
  134. atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +13 -9
  135. atomicshop/wrappers/ctyping/msi_windows_installer/tables.py +35 -0
  136. atomicshop/wrappers/ctyping/setup_device.py +466 -0
  137. atomicshop/wrappers/ctyping/win_console.py +39 -0
  138. atomicshop/wrappers/dockerw/dockerw.py +113 -2
  139. atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
  140. atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
  141. atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
  142. atomicshop/wrappers/factw/get_file_data.py +12 -5
  143. atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
  144. atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
  145. atomicshop/wrappers/factw/postgresql/firmware.py +4 -6
  146. atomicshop/wrappers/githubw.py +583 -51
  147. atomicshop/wrappers/loggingw/consts.py +49 -0
  148. atomicshop/wrappers/loggingw/filters.py +102 -0
  149. atomicshop/wrappers/loggingw/formatters.py +58 -71
  150. atomicshop/wrappers/loggingw/handlers.py +459 -40
  151. atomicshop/wrappers/loggingw/loggers.py +19 -0
  152. atomicshop/wrappers/loggingw/loggingw.py +1010 -178
  153. atomicshop/wrappers/loggingw/reading.py +344 -19
  154. atomicshop/wrappers/mongodbw/__init__.py +0 -0
  155. atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
  156. atomicshop/wrappers/mongodbw/mongodbw.py +1432 -0
  157. atomicshop/wrappers/netshw.py +271 -0
  158. atomicshop/wrappers/playwrightw/engine.py +34 -19
  159. atomicshop/wrappers/playwrightw/infra.py +5 -0
  160. atomicshop/wrappers/playwrightw/javascript.py +7 -3
  161. atomicshop/wrappers/playwrightw/keyboard.py +14 -0
  162. atomicshop/wrappers/playwrightw/scenarios.py +172 -5
  163. atomicshop/wrappers/playwrightw/waits.py +9 -7
  164. atomicshop/wrappers/powershell_networking.py +80 -0
  165. atomicshop/wrappers/psutilw/processes.py +81 -0
  166. atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
  167. atomicshop/wrappers/psutilw/psutilw.py +9 -0
  168. atomicshop/wrappers/pyopensslw.py +9 -2
  169. atomicshop/wrappers/pywin32w/__init__.py +0 -0
  170. atomicshop/wrappers/pywin32w/cert_store.py +116 -0
  171. atomicshop/wrappers/pywin32w/console.py +34 -0
  172. atomicshop/wrappers/pywin32w/win_event_log/__init__.py +0 -0
  173. atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
  174. atomicshop/wrappers/pywin32w/win_event_log/subscribe.py +212 -0
  175. atomicshop/wrappers/pywin32w/win_event_log/subscribes/__init__.py +0 -0
  176. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +57 -0
  177. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +49 -0
  178. atomicshop/wrappers/pywin32w/win_event_log/subscribes/schannel_logging.py +97 -0
  179. atomicshop/wrappers/pywin32w/winshell.py +19 -0
  180. atomicshop/wrappers/pywin32w/wmis/__init__.py +0 -0
  181. atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
  182. atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
  183. atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
  184. atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
  185. atomicshop/wrappers/socketw/accepter.py +21 -7
  186. atomicshop/wrappers/socketw/certificator.py +216 -150
  187. atomicshop/wrappers/socketw/creator.py +190 -50
  188. atomicshop/wrappers/socketw/dns_server.py +500 -173
  189. atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
  190. atomicshop/wrappers/socketw/process_getter.py +86 -0
  191. atomicshop/wrappers/socketw/receiver.py +144 -102
  192. atomicshop/wrappers/socketw/sender.py +65 -35
  193. atomicshop/wrappers/socketw/sni.py +334 -165
  194. atomicshop/wrappers/socketw/socket_base.py +134 -0
  195. atomicshop/wrappers/socketw/socket_client.py +137 -95
  196. atomicshop/wrappers/socketw/socket_server_tester.py +14 -9
  197. atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
  198. atomicshop/wrappers/socketw/ssl_base.py +15 -14
  199. atomicshop/wrappers/socketw/statistics_csv.py +148 -17
  200. atomicshop/wrappers/sysmonw.py +157 -0
  201. atomicshop/wrappers/ubuntu_terminal.py +65 -26
  202. atomicshop/wrappers/win_auditw.py +189 -0
  203. atomicshop/wrappers/winregw/__init__.py +0 -0
  204. atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
  205. atomicshop/wrappers/winregw/winreg_network.py +232 -0
  206. {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -49
  207. atomicshop-3.10.5.dist-info/RECORD +306 -0
  208. {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
  209. atomicshop/_basics_temp.py +0 -101
  210. atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
  211. atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
  212. atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
  213. atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
  214. atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
  215. atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
  216. atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
  217. atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
  218. atomicshop/addons/package_setup/Setup.cmd +0 -7
  219. atomicshop/addons/process_list/compile.cmd +0 -2
  220. atomicshop/addons/process_list/compiled/Win10x64/process_list.dll +0 -0
  221. atomicshop/addons/process_list/compiled/Win10x64/process_list.exp +0 -0
  222. atomicshop/addons/process_list/compiled/Win10x64/process_list.lib +0 -0
  223. atomicshop/archiver/_search_in_zip.py +0 -189
  224. atomicshop/archiver/archiver.py +0 -34
  225. atomicshop/archiver/search_in_archive.py +0 -250
  226. atomicshop/archiver/sevenz_app_w.py +0 -86
  227. atomicshop/archiver/sevenzs.py +0 -44
  228. atomicshop/archiver/zips.py +0 -293
  229. atomicshop/etw/dns_trace.py +0 -118
  230. atomicshop/etw/etw.py +0 -61
  231. atomicshop/file_types.py +0 -24
  232. atomicshop/mitm/engines/create_module_template_example.py +0 -13
  233. atomicshop/mitm/initialize_mitm_server.py +0 -240
  234. atomicshop/monitor/checks/hash.py +0 -44
  235. atomicshop/monitor/checks/hash_checks/file.py +0 -55
  236. atomicshop/monitor/checks/hash_checks/url.py +0 -62
  237. atomicshop/pbtkmultifile_argparse.py +0 -88
  238. atomicshop/permissions.py +0 -110
  239. atomicshop/process_poller.py +0 -237
  240. atomicshop/script_as_string_processor.py +0 -38
  241. atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
  242. atomicshop/ssh_scripts/process_from_port.py +0 -27
  243. atomicshop/wrappers/_process_wrapper_curl.py +0 -27
  244. atomicshop/wrappers/_process_wrapper_tar.py +0 -21
  245. atomicshop/wrappers/dockerw/install_docker.py +0 -209
  246. atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
  247. atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
  248. atomicshop/wrappers/ffmpegw.py +0 -125
  249. atomicshop/wrappers/loggingw/checks.py +0 -20
  250. atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
  251. atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
  252. atomicshop/wrappers/socketw/base.py +0 -59
  253. atomicshop/wrappers/socketw/get_process.py +0 -107
  254. atomicshop/wrappers/wslw.py +0 -191
  255. atomicshop-2.11.47.dist-info/RECORD +0 -251
  256. /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
  257. /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
  258. /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
  259. /atomicshop/{addons/mains → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
  260. /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
  261. /atomicshop/{addons/mains → a_mains}/search_for_hyperlinks_in_docx.py +0 -0
  262. /atomicshop/{archiver → etws}/__init__.py +0 -0
  263. /atomicshop/{etw → etws/traces}/__init__.py +0 -0
  264. /atomicshop/{monitor/checks/hash_checks → mitm/statistic_analyzer_helper}/__init__.py +0 -0
  265. /atomicshop/{wrappers/nodejsw → permissions}/__init__.py +0 -0
  266. /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
  267. {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
  268. {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,167 @@
1
1
  import logging
2
2
  from logging.handlers import TimedRotatingFileHandler, QueueListener, QueueHandler
3
+ import time
3
4
  import re
5
+ import os
6
+ from pathlib import Path
7
+ import queue
8
+ from typing import Literal, Union
9
+ import threading
10
+ from datetime import datetime
11
+ import contextlib
12
+ import multiprocessing
13
+
14
+ from . import loggers, formatters, filters, consts
15
+ from ... import datetimes, filesystem
16
+
17
+
18
+ r"""
19
+ # Not used, only for the reference:
20
+ DEFAULT_DATE_STRING_FORMAT: str = "%Y_%m_%d"
21
+ DEFAULT_DATE_REGEX_PATTERN: str = r"^\d{4}_\d{2}_\d{2}$"
22
+ """
23
+
24
+
25
+ def _process_formatter_attribute(
26
+ formatter: Union[
27
+ Literal['DEFAULT', 'MESSAGE'],
28
+ str,
29
+ None],
30
+ file_type: Union[
31
+ Literal['txt', 'csv', 'json'],
32
+ None] = None
33
+ ):
34
+ """
35
+ Function to process the formatter attribute.
36
+ """
37
+
38
+ if formatter == 'DEFAULT' and file_type is None:
39
+ return consts.DEFAULT_STREAM_FORMATTER
40
+ elif formatter == 'DEFAULT' and file_type == 'txt':
41
+ return consts.DEFAULT_FORMATTER_TXT_FILE
42
+ elif formatter == 'DEFAULT' and file_type == 'csv':
43
+ return consts.DEFAULT_FORMATTER_CSV_FILE
44
+ elif formatter == 'DEFAULT' and file_type == 'json':
45
+ return consts.DEFAULT_MESSAGE_FORMATTER
46
+ elif formatter == 'MESSAGE':
47
+ return consts.DEFAULT_MESSAGE_FORMATTER
48
+ else:
49
+ return formatter
50
+
51
+
52
+ def get_stream_handler_extended(
53
+ logging_level: str = "DEBUG",
54
+ formatter: Union[
55
+ Literal['DEFAULT', 'MESSAGE'],
56
+ str,
57
+ None] = None,
58
+ formatter_use_nanoseconds: bool = False
59
+ ) -> logging.StreamHandler:
60
+ """
61
+ Function to get StreamHandler with extended configuration.
62
+ Stream formatter will output messages to the console.
63
+ """
64
+
65
+ # Getting the StreamHandler.
66
+ stream_handler = get_stream_handler()
67
+ # Setting log level for the handler, that will use the logger while initiated.
68
+ loggers.set_logging_level(stream_handler, logging_level)
69
+
70
+ # If formatter_message_only is set to True, then formatter will be used only for the 'message' part.
71
+ formatter = _process_formatter_attribute(formatter)
72
+
73
+ # If formatter was provided, then it will be used.
74
+ if formatter:
75
+ logging_formatter = formatters.get_logging_formatter_from_string(
76
+ formatter=formatter, use_nanoseconds=formatter_use_nanoseconds)
77
+ set_formatter(stream_handler, logging_formatter)
78
+
79
+ return stream_handler
80
+
81
+
82
+ # Function to start the interval-based rotation check
83
+ def _start_interval_rotation(handler):
84
+ # def check_rotation():
85
+ # while True:
86
+ # next_rollover = _calculate_next_rollover()
87
+ # while datetime.now() < next_rollover:
88
+ # time.sleep(0.1)
89
+ #
90
+ # # Check if the next_rollover has changed (indicating a rollover by an event)
91
+ # if _calculate_next_rollover() != next_rollover:
92
+ # next_rollover = _calculate_next_rollover()
93
+ # break
94
+ #
95
+ # # Perform manual rollover if needed
96
+ # if datetime.now() >= next_rollover:
97
+ # handler.doRollover()
98
+ #
99
+ # # handler.doRollover()
100
+ #
101
+ # def _calculate_next_rollover():
102
+ # return datetime.fromtimestamp(handler.rolloverAt)
103
+ def check_rotation():
104
+ last_rollover_at = handler.rolloverAt # Initial rollover time
105
+
106
+ while True:
107
+ current_time = datetime.now()
108
+ next_rollover = datetime.fromtimestamp(handler.rolloverAt)
109
+
110
+ # Check if the rollover time has passed and it hasn't been handled yet
111
+ if current_time >= next_rollover and handler.rolloverAt == last_rollover_at:
112
+ # Perform manual rollover
113
+ handler.doRollover()
114
+
115
+ # Update last_rollover_at to the new rolloverAt
116
+ last_rollover_at = handler.rolloverAt
117
+
118
+ # Sleep for a short interval before checking again
119
+ time.sleep(0.1)
120
+
121
+ rotation_thread = threading.Thread(target=check_rotation)
122
+ rotation_thread.daemon = True
123
+ rotation_thread.start()
124
+
125
+
126
+ def _wrap_do_rollover(handler, header):
127
+ original_do_rollover = handler.doRollover
128
+
129
+ def new_do_rollover():
130
+ original_do_rollover()
131
+ # After rollover, write the header
132
+ if header:
133
+ with open(handler.baseFilename, 'a') as f:
134
+ f.write(header + '\n')
135
+
136
+ handler.doRollover = new_do_rollover
137
+
138
+
139
+ def get_queue_handler_and_start_queue_listener_for_file_handler(file_handler):
140
+ """
141
+ Function to create QueueHandler and start QueueListener for the FileHandler.
142
+ The QueueListener, which will get the logs from the queue and use the FileHandler to write them to the
143
+ file.
144
+ The QueueHandler will put the logs to the queue.
145
+
146
+ :param file_handler: FileHandler object.
147
+ :return: QueueHandler object.
148
+ """
149
+
150
+ # Create the Queue between threads. "-1" means that there can infinite number of items that can be
151
+ # put in the Queue. if integer is bigger than 0, it means that this will be the maximum
152
+ # number of items.
153
+ queue_object = queue.Queue(-1)
154
+
155
+ # Create QueueListener, which will get the logs from the queue and use the FileHandler to write them to the file.
156
+ start_queue_listener_for_handlers((file_handler,), queue_object)
157
+
158
+ # Get the QueueHandler, which will put the logs to the queue.
159
+ queue_handler = get_queue_handler(queue_object)
160
+
161
+ return queue_handler
162
+
163
+
164
+ # BASE FUNCTIONS =======================================================================================================
4
165
 
5
166
 
6
167
  def get_stream_handler() -> logging.StreamHandler:
@@ -14,9 +175,15 @@ def get_stream_handler() -> logging.StreamHandler:
14
175
  return logging.StreamHandler()
15
176
 
16
177
 
178
+ # noinspection PyPep8Naming
17
179
  def get_timed_rotating_file_handler(
18
- log_file_path: str, when: str = "midnight", interval: int = 1, delay: bool = False, encoding=None
19
- ) -> logging.handlers.TimedRotatingFileHandler:
180
+ log_file_path: str,
181
+ when: str = "midnight",
182
+ interval: int = 1,
183
+ backupCount: int = 0,
184
+ delay: bool = False,
185
+ encoding=None
186
+ ) -> TimedRotatingFileHandler:
20
187
  """
21
188
  Function to get a TimedRotatingFileHandler.
22
189
  This handler will output messages to a file, rotating the log file at certain timed intervals.
@@ -29,28 +196,146 @@ def get_timed_rotating_file_handler(
29
196
  "D" - Days
30
197
  "midnight" - Roll over at midnight
31
198
  :param interval: Interval to rotate the log file.
199
+ :param backupCount: int, Number of backup files to keep. Default is 0.
200
+ If backupCount is > 0, when rollover is done, no more than backupCount files are kept, the oldest are deleted.
201
+ If backupCount is == 0, all the backup files will be kept.
32
202
  :param delay: bool, If set to True, the log file will be created only if there's something to write.
33
203
  :param encoding: Encoding to use for the log file. Same as for the TimeRotatingFileHandler, which uses Default None.
34
204
  :return: TimedRotatingFileHandler.
35
205
  """
36
206
 
37
207
  return TimedRotatingFileHandler(
38
- filename=log_file_path, when=when, interval=interval, delay=delay, encoding=encoding)
208
+ filename=log_file_path, when=when, interval=interval, backupCount=backupCount, delay=delay, encoding=encoding)
39
209
 
40
210
 
41
- def start_queue_listener_for_file_handler(
42
- file_handler: logging.FileHandler, queue_object) -> logging.handlers.QueueListener:
211
+ # noinspection PyPep8Naming
212
+ def get_timed_rotating_file_handler_extended(
213
+ file_path: str,
214
+ file_type: Literal[
215
+ 'txt',
216
+ 'csv',
217
+ 'json'] = 'txt',
218
+ logging_level="DEBUG",
219
+ formatter: Union[
220
+ Literal['DEFAULT', 'MESSAGE'],
221
+ str,
222
+ None] = None,
223
+ formatter_use_nanoseconds: bool = False,
224
+ rotate_at_rollover_time: bool = True,
225
+ rotation_date_format: str = None,
226
+ rotation_callback_namer_function: callable = None,
227
+ rotation_use_default_callback_namer_function: bool = True,
228
+ use_internal_queue_listener: bool = False,
229
+ when: str = 'midnight',
230
+ interval: int = 1,
231
+ delay: bool = True,
232
+ backupCount: int = 0,
233
+ encoding=None,
234
+ header: str = None
235
+ ) -> Union[TimedRotatingFileHandler, logging.handlers.QueueHandler]:
236
+ """
237
+ :param file_path: Path to the log file.
238
+ :param file_type: Type of the file. Possible values: 'txt', 'csv', 'json'.
239
+ :param logging_level: Logging level for the handler.
240
+ :param formatter: Formatter for the handler.
241
+ :param formatter_use_nanoseconds: If set to True, the formatter will use nanoseconds.
242
+ :param rotate_at_rollover_time: If set to True, the handler will rotate the log file at the rollover time.
243
+ :param rotation_date_format: Date format string to set to the handler's suffix.
244
+ :param rotation_callback_namer_function: Callback function to change the filename on rotation.
245
+ :param rotation_use_default_callback_namer_function: If set to True, the default callback namer function will be used
246
+ and the filename will be changed on rotation instead of using the default like this:
247
+ 'file.log.2021-12-24' -> 'file_2021-12-24.log'.
248
+ :param use_internal_queue_listener: If set to True, the handler will use internal QueueListener to write logs.
249
+ :param when: When to rotate the log file. Possible values:
250
+ "S" - Seconds
251
+ "M" - Minutes
252
+ "H" - Hours
253
+ "D" - Days
254
+ "midnight" - Roll over at midnight
255
+ :param use_internal_queue_listener: If set to True, the handler will use internal QueueListener to write logs.
256
+ Function to add TimedRotatingFileHandler and QueueHandler to logger.
257
+ TimedRotatingFileHandler will output messages to the file through QueueHandler.
258
+ This is needed, since TimedRotatingFileHandler is not thread-safe, though official docs say it is.
259
+ :param interval: Interval to rotate the log file.
260
+ :param delay: If set to True, the log file will be created only if there's something to write.
261
+ :param backupCount: Number of backup files to keep. Default is 0.
262
+ If backupCount is > 0, when rollover is done, no more than backupCount files are kept, the oldest are deleted.
263
+ If backupCount is == 0, all the backup files will be kept.
264
+ :param encoding: Encoding to use for the log file. Same as for the TimeRotatingFileHandler, which uses Default None.
265
+ :param header: Header to write to the log file.
266
+ :return: TimedRotatingFileHandler or QueueHandler (if, use_internal_queue_listener is set to True).
267
+ """
268
+
269
+ # Creating file handler with log filename. At this stage the log file is created and locked by the handler,
270
+ # Unless we use "delay=True" to tell the class to write the file only if there's something to write.
271
+
272
+ filesystem.create_directory(os.path.dirname(file_path))
273
+
274
+ file_handler = get_timed_rotating_file_handler(
275
+ file_path, when=when, interval=interval, delay=delay, backupCount=backupCount, encoding=encoding)
276
+
277
+ loggers.set_logging_level(file_handler, logging_level)
278
+
279
+ formatter = _process_formatter_attribute(formatter, file_type=file_type)
280
+
281
+ # If formatter was passed to the function we'll add it to handler.
282
+ # noinspection GrazieInspection
283
+ if formatter:
284
+ # Convert string to Formatter object. Moved to newer styling of python 3: style='{'.
285
+ logging_formatter = formatters.get_logging_formatter_from_string(
286
+ formatter=formatter, use_nanoseconds=formatter_use_nanoseconds)
287
+ # Setting the formatter in file handler.
288
+ set_formatter(file_handler, logging_formatter)
289
+
290
+ # This function will change the suffix behavior of the rotated file name.
291
+ change_rotated_filename(
292
+ file_handler=file_handler, date_format_string=rotation_date_format,
293
+ callback_namer_function=rotation_callback_namer_function,
294
+ use_default_callback_namer_function=rotation_use_default_callback_namer_function
295
+ )
296
+
297
+ # If header is set, we'll add the filter to the handler that will create the header on file rotation.
298
+ if header:
299
+ # Filter is added to write header on logger startup.
300
+ add_filter_to_handler(file_handler, filters.HeaderFilter(header, file_handler.baseFilename))
301
+ # Wrap the doRollover method to write the header after each rotation, since adding the filter
302
+ # will only write the header on log file creation.
303
+ _wrap_do_rollover(file_handler, header)
304
+
305
+ # Start the interval-based rotation forcing.
306
+ if rotate_at_rollover_time:
307
+ _start_interval_rotation(file_handler)
308
+
309
+ # Setting the TimedRotatingFileHandler, without adding it to the logger.
310
+ # It will be added to the QueueListener, which will use the TimedRotatingFileHandler to write logs.
311
+ # This is needed since there's a bug in TimedRotatingFileHandler, which won't let it be used with
312
+ # threads the same way it would be used for multiprocess.
313
+
314
+ # If internal queue listener is set to True, we'll start the QueueListener for the FileHandler.
315
+ if use_internal_queue_listener:
316
+ queue_handler = get_queue_handler_and_start_queue_listener_for_file_handler(file_handler)
317
+ loggers.set_logging_level(queue_handler, logging_level)
318
+ return queue_handler
319
+ else:
320
+ return file_handler
321
+
322
+
323
+ def start_queue_listener_for_handlers(
324
+ handlers: tuple[logging.Handler],
325
+ queue_object: Union[queue.Queue, multiprocessing.Queue]
326
+ ) -> logging.handlers.QueueListener:
43
327
  """
44
328
  Function to get a QueueListener for the FileHandler.
45
329
  This handler get the messages from the FileHandler and put them in the Queue.
46
330
 
47
- :param file_handler: FileHandler to get the messages from.
331
+ :param handlers: Tuple of handlers to put in the QueueListener.
332
+ For example, it can be (stream_handler, file_handler).
48
333
  :param queue_object: Queue object to put the messages in.
49
334
  :return: QueueListener.
50
335
  """
51
336
 
52
337
  # Create the QueueListener based on TimedRotatingFileHandler
53
- queue_listener = QueueListener(queue_object, file_handler)
338
+ queue_listener: logging.handlers.QueueListener = QueueListener(queue_object, *handlers)
54
339
  # Start the QueueListener. Each logger will have its own instance of the Queue
55
340
  queue_listener.start()
56
341
 
@@ -70,6 +355,25 @@ def get_queue_handler(queue_object) -> logging.handlers.QueueHandler:
70
355
  return QueueHandler(queue_object)
71
356
 
72
357
 
358
+ def get_queue_handler_extended(
359
+ queue_object: Union[
360
+ queue.Queue,
361
+ multiprocessing.Queue],
362
+ logging_level: str = "DEBUG"):
363
+ """
364
+ Function to get the QueueHandler.
365
+ QueueHandler of the logger will pass the logs to the Queue and the opposite QueueListener will write them
366
+ from the Queue to the file that was set in the FileHandler.
367
+ """
368
+
369
+ # Getting the QueueHandler.
370
+ queue_handler = get_queue_handler(queue_object)
371
+ # Setting log level for the handler, that will use the logger while initiated.
372
+ loggers.set_logging_level(queue_handler, logging_level)
373
+
374
+ return queue_handler
375
+
376
+
73
377
  def set_formatter(handler: logging.Handler, logging_formatter: logging.Formatter):
74
378
  """
75
379
  Function to set the formatter for the handler.
@@ -100,36 +404,151 @@ def get_handler_name(handler: logging.Handler) -> str:
100
404
  return handler.get_name()
101
405
 
102
406
 
103
- def change_rotated_filename(file_handler: logging.Handler, file_extension: str):
104
- # Changing the way TimedRotatingFileHandler managing the rotating filename
105
- # Default file suffix is only "Year_Month_Day" with addition of the dot (".") character to the
106
- # "file name + extension" that you provide it. Example: log file name:
107
- # test.log
108
- # After file is rotated at midnight, by default the old filename will be:
109
- # test.log.2021_12_24
110
- # And the log file of 25th, now will be "test.log".
111
- # So, Changing the file suffix to include the extension to the suffix, so it will be:
112
- # test.log.2021_12_24.log
113
- # file_handler.suffix = logfile_suffix
114
- # file_handler.suffix = "_%Y_%m_%d.txt"
115
- # This step will remove the created ".log." above before the suffix and the filename will look like:
116
- # test.2021_12_24.log
117
- # file_handler.namer = lambda name: name.replace(log_file_extension + ".", "") + log_file_extension
118
- # file_handler.namer = lambda name: name.replace(".txt.", "") + log_file_extension
119
- # This will recompile the string to tell the handler the length of the suffix parts
120
- # file_handler.extMatch = re.compile(r"^\d{4}_\d{2}_\d{2}" + re.escape(log_file_extension) + r"$")
121
- # file_handler.extMatch = re.compile(r"^\d{4}_\d{2}_\d{2}.txt$")
122
-
123
- # Set variables that are responsible for setting TimedRotatingFileHandler filename on rotation.
124
- # Log files time format, need only date
125
- format_date_log_filename: str = "%Y_%m_%d"
126
- # Log file suffix.
127
- logfile_suffix: str = "_" + format_date_log_filename + file_extension
128
- # Regex object to match the TimedRotatingFileHandler file name suffix.
129
- # "re.escape" is used to "escape" strings in regex and use them as is.
130
- logfile_regex_suffix = re.compile(r"^\d{4}_\d{2}_\d{2}" + re.escape(file_extension) + r"$")
131
-
132
- # Changing the setting that we set above
133
- file_handler.suffix = logfile_suffix
134
- file_handler.namer = lambda name: name.replace(file_extension + ".", "") + file_extension
135
- file_handler.extMatch = logfile_regex_suffix
407
+ def change_rotated_filename(
408
+ file_handler: logging.Handler,
409
+ date_format_string: str = None,
410
+ callback_namer_function: callable = None,
411
+ use_default_callback_namer_function: bool = True
412
+ ):
413
+ """
414
+ Function to change the way TimedRotatingFileHandler managing the rotating filename.
415
+
416
+ :param file_handler: FileHandler to change the rotating filename for.
417
+ :param date_format_string: Date format string to set to the handler's suffix.
418
+ :param callback_namer_function: Callback function to change the filename on rotation.
419
+ :param use_default_callback_namer_function: If set to True, the default callback namer function will be used
420
+ and the filename will be changed on rotation instead of using the default like this:
421
+ 'file.log.2021-12-24' -> 'file_2021-12-24.log'.
422
+
423
+ ---------------------
424
+
425
+ At this point, 'file_handler.suffix' is already '%Y-%m-%d' if 'when' is set to 'midnight'.
426
+ You can change it if you wish (example: '%Y_%m_%d'), the method is described below.
427
+ """
428
+
429
+ def callback_namer(name):
430
+ """
431
+ Callback function to change the filename of the rotated log file on file rotation.
432
+ """
433
+ # Currently the 'name' is full file path + '.' + logfile_suffix.
434
+ # Example: 'C:\\path\\to\\file.log.2021-12-24'
435
+ # Get the parent directory of the file: C:\path\to
436
+ parent_dir: str = str(Path(name).parent)
437
+ # Get the base filename without the extension: file.log
438
+ filename: str = Path(name).stem
439
+ # Get the date part of the filename: 2021-12-24
440
+ date_part: str = str(Path(name).suffix).replace(".", "")
441
+ # Get the file extension: log
442
+ file_extension: str = Path(filename).suffix
443
+ # Get the file name without the extension: file
444
+ file_stem: str = Path(filename).stem
445
+
446
+ return f"{parent_dir}{os.sep}{file_stem}_{date_part}{file_extension}"
447
+
448
+ def change_file_handler_suffix():
449
+ # Get regex pattern from string format.
450
+ # Example: '%Y_%m_%d' -> r'\d{4}_\d{2}_\d{2}'
451
+ date_regex_pattern = datetimes.datetime_format_to_regex(date_format_string)
452
+
453
+ # Regex pattern to match the rotated log filenames
454
+ logfile_regex_suffix = re.compile(date_regex_pattern)
455
+
456
+ # Update the handler's suffix to include the date format
457
+ file_handler.suffix = date_format_string
458
+
459
+ # Update the handler's extMatch regex to match the new filename format
460
+ file_handler.extMatch = logfile_regex_suffix
461
+
462
+ if use_default_callback_namer_function and callback_namer_function:
463
+ raise ValueError("You can't use both default and custom callback namer function.")
464
+ elif not use_default_callback_namer_function and not callback_namer_function:
465
+ raise ValueError(
466
+ "You need to provide a 'callback_namer_function' or our 'use_default_callback_namer_function'.")
467
+
468
+ if date_format_string:
469
+ change_file_handler_suffix()
470
+
471
+ # Set the callback function to change the filename on rotation.
472
+ if use_default_callback_namer_function:
473
+ file_handler.namer = callback_namer
474
+
475
+ if callback_namer_function:
476
+ file_handler.namer = callback_namer_function
477
+
478
+
479
+ def has_handlers(logger: logging.Logger) -> bool:
480
+ """
481
+ Function to check if the logger has handlers.
482
+ :param logger: Logger to check
483
+ :return: True if logger has handlers, False otherwise
484
+ """
485
+
486
+ # Omitted the usage of "hasHandlers()" method, since sometimes returned "True" even when there were no handlers
487
+ # Didn't research the issue much, just used the "len(logger.handlers)" to check how many handlers there are
488
+ # in the logger.
489
+ # if not logging.getLogger(function_module_name).hasHandlers():
490
+ # if len(logging.getLogger(function_module_name).handlers) == 0:
491
+
492
+ if len(logger.handlers) == 0:
493
+ return False
494
+ else:
495
+ return True
496
+
497
+
498
+ def extract_datetime_format_from_file_handler(file_handler: logging.FileHandler) -> Union[str, None]:
499
+ """
500
+ Extract the datetime string formats from all TimedRotatingFileHandlers in the logger.
501
+
502
+ Args:
503
+ - logger: The logger instance.
504
+
505
+ Returns:
506
+ - A list of datetime string formats used by the handlers.
507
+ """
508
+ # Extract the suffix
509
+ suffix = getattr(file_handler, 'suffix', None)
510
+ if suffix:
511
+ datetime_format = datetimes.extract_datetime_format_from_string(suffix)
512
+ if datetime_format:
513
+ return datetime_format
514
+
515
+ return None
516
+
517
+
518
+ def add_filter_to_handler(handler: logging.Handler, filter_object: logging.Filter):
519
+ """
520
+ Function to add a filter to the handler.
521
+ :param handler: Handler to add the filter to.
522
+ :param filter_object: Filter object to add to the handler.
523
+ """
524
+
525
+ handler.addFilter(filter_object)
526
+
527
+
528
+ def get_formatter_string(handler: logging.Handler) -> str:
529
+ """
530
+ Function to get the formatter string from the handler.
531
+ :param handler: Handler to get the formatter from.
532
+ :return: Formatter string.
533
+ """
534
+
535
+ return formatters.get_formatter_string(handler.formatter)
536
+
537
+
538
+ @contextlib.contextmanager
539
+ def temporary_change_formatter(handler: logging.Handler, formatter_string: str):
540
+ """
541
+ Context manager to temporarily change the formatter of the handler.
542
+
543
+ Example:
544
+ with temporary_change_formatter(handler, formatter_string):
545
+ # Do something with the temporary formatter.
546
+ pass
547
+ """
548
+ original_formatter = handler.formatter
549
+
550
+ try:
551
+ handler.setFormatter(logging.Formatter(formatter_string))
552
+ yield
553
+ finally:
554
+ handler.setFormatter(original_formatter)
@@ -14,6 +14,25 @@ import logging
14
14
  """
15
15
 
16
16
 
17
+ def is_logger_exists(
18
+ logger_name: str
19
+ ) -> bool:
20
+ """
21
+ Function to check if the logger exists.
22
+ :param logger_name: str, Name of the logger.
23
+ :return: bool, True if the logger exists, False otherwise.
24
+ """
25
+ return logger_name in logging.Logger.manager.loggerDict
26
+
27
+
28
+ def get_all_existing_loggers_names() -> list:
29
+ """
30
+ Function to get all existing loggers names.
31
+ :return: list, List of all existing loggers names.
32
+ """
33
+ return list(logging.Logger.manager.loggerDict.keys())
34
+
35
+
17
36
  def get_logger(logger_name: str) -> logging.Logger:
18
37
  """
19
38
  Function to get a logger.