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.
- atomicshop/__init__.py +1 -1
- atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
- atomicshop/a_mains/addons/process_list/compile.cmd +7 -0
- atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.dll +0 -0
- atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.exp +0 -0
- atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.lib +0 -0
- atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +8 -1
- atomicshop/a_mains/dns_gateway_setting.py +11 -0
- atomicshop/a_mains/get_local_tcp_ports.py +85 -0
- atomicshop/a_mains/github_wrapper.py +11 -0
- atomicshop/a_mains/install_ca_certificate.py +172 -0
- atomicshop/{addons/mains → a_mains}/msi_unpacker.py +3 -1
- atomicshop/a_mains/process_from_port.py +119 -0
- atomicshop/a_mains/set_default_dns_gateway.py +90 -0
- atomicshop/a_mains/update_config_toml.py +38 -0
- atomicshop/appointment_management.py +5 -3
- atomicshop/basics/ansi_escape_codes.py +3 -1
- atomicshop/basics/argparse_template.py +2 -0
- atomicshop/basics/booleans.py +27 -30
- atomicshop/basics/bytes_arrays.py +43 -0
- atomicshop/basics/classes.py +149 -1
- atomicshop/basics/dicts.py +12 -0
- atomicshop/basics/enums.py +2 -2
- atomicshop/basics/exceptions.py +5 -1
- atomicshop/basics/list_of_classes.py +29 -0
- atomicshop/basics/list_of_dicts.py +69 -5
- atomicshop/basics/lists.py +14 -0
- atomicshop/basics/multiprocesses.py +374 -50
- atomicshop/basics/package_module.py +10 -0
- atomicshop/basics/strings.py +160 -7
- atomicshop/basics/threads.py +14 -0
- atomicshop/basics/tracebacks.py +13 -4
- atomicshop/certificates.py +153 -52
- atomicshop/config_init.py +12 -7
- atomicshop/console_user_response.py +7 -14
- atomicshop/consoles.py +9 -0
- atomicshop/datetimes.py +98 -0
- atomicshop/diff_check.py +340 -40
- atomicshop/dns.py +128 -12
- atomicshop/etws/_pywintrace_fix.py +17 -0
- atomicshop/etws/const.py +38 -0
- atomicshop/etws/providers.py +21 -0
- atomicshop/etws/sessions.py +43 -0
- atomicshop/etws/trace.py +168 -0
- atomicshop/etws/traces/trace_dns.py +162 -0
- atomicshop/etws/traces/trace_sysmon_process_creation.py +126 -0
- atomicshop/etws/traces/trace_tcp.py +130 -0
- atomicshop/file_io/csvs.py +222 -24
- atomicshop/file_io/docxs.py +35 -18
- atomicshop/file_io/file_io.py +35 -19
- atomicshop/file_io/jsons.py +49 -0
- atomicshop/file_io/tomls.py +139 -0
- atomicshop/filesystem.py +864 -293
- atomicshop/get_process_list.py +133 -0
- atomicshop/{process_name_cmd.py → get_process_name_cmd_dll.py} +52 -19
- atomicshop/http_parse.py +149 -93
- atomicshop/ip_addresses.py +6 -1
- atomicshop/mitm/centered_settings.py +132 -0
- atomicshop/mitm/config_static.py +207 -0
- atomicshop/mitm/config_toml_editor.py +55 -0
- atomicshop/mitm/connection_thread_worker.py +875 -357
- atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
- atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
- atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
- atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
- atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
- atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
- atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
- atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
- atomicshop/mitm/engines/create_module_template.py +58 -14
- atomicshop/mitm/import_config.py +359 -139
- atomicshop/mitm/initialize_engines.py +160 -74
- atomicshop/mitm/message.py +64 -23
- atomicshop/mitm/mitm_main.py +892 -0
- atomicshop/mitm/recs_files.py +183 -0
- atomicshop/mitm/shared_functions.py +4 -10
- atomicshop/mitm/ssh_tester.py +82 -0
- atomicshop/mitm/statistic_analyzer.py +257 -166
- atomicshop/mitm/statistic_analyzer_helper/analyzer_helper.py +136 -0
- atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +525 -0
- atomicshop/monitor/change_monitor.py +96 -120
- atomicshop/monitor/checks/dns.py +139 -70
- atomicshop/monitor/checks/file.py +77 -0
- atomicshop/monitor/checks/network.py +81 -77
- atomicshop/monitor/checks/process_running.py +33 -34
- atomicshop/monitor/checks/url.py +94 -0
- atomicshop/networks.py +671 -0
- atomicshop/on_exit.py +205 -0
- atomicshop/package_mains_processor.py +84 -0
- atomicshop/permissions/permissions.py +22 -0
- atomicshop/permissions/ubuntu_permissions.py +239 -0
- atomicshop/permissions/win_permissions.py +33 -0
- atomicshop/print_api.py +24 -41
- atomicshop/process.py +63 -17
- atomicshop/process_poller/__init__.py +0 -0
- atomicshop/process_poller/pollers/__init__.py +0 -0
- atomicshop/process_poller/pollers/psutil_pywin32wmi_dll.py +95 -0
- atomicshop/process_poller/process_pool.py +207 -0
- atomicshop/process_poller/simple_process_pool.py +311 -0
- atomicshop/process_poller/tracer_base.py +45 -0
- atomicshop/process_poller/tracers/__init__.py +0 -0
- atomicshop/process_poller/tracers/event_log.py +46 -0
- atomicshop/process_poller/tracers/sysmon_etw.py +68 -0
- atomicshop/python_file_patcher.py +1 -1
- atomicshop/python_functions.py +27 -75
- atomicshop/question_answer_engine.py +2 -2
- atomicshop/scheduling.py +24 -5
- atomicshop/sound.py +4 -2
- atomicshop/speech_recognize.py +8 -0
- atomicshop/ssh_remote.py +158 -172
- atomicshop/startup/__init__.py +0 -0
- atomicshop/startup/win/__init__.py +0 -0
- atomicshop/startup/win/startup_folder.py +53 -0
- atomicshop/startup/win/task_scheduler.py +119 -0
- atomicshop/system_resource_monitor.py +61 -46
- atomicshop/system_resources.py +8 -8
- atomicshop/tempfiles.py +1 -2
- atomicshop/timer.py +30 -11
- atomicshop/urls.py +41 -0
- atomicshop/venvs.py +28 -0
- atomicshop/versioning.py +27 -0
- atomicshop/web.py +110 -25
- atomicshop/web_apis/__init__.py +0 -0
- atomicshop/web_apis/google_custom_search.py +44 -0
- atomicshop/web_apis/google_llm.py +188 -0
- atomicshop/websocket_parse.py +450 -0
- atomicshop/wrappers/certauthw/certauth.py +1 -0
- atomicshop/wrappers/cryptographyw.py +29 -8
- atomicshop/wrappers/ctyping/etw_winapi/__init__.py +0 -0
- atomicshop/wrappers/ctyping/etw_winapi/const.py +335 -0
- atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +393 -0
- atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
- atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
- atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +13 -9
- atomicshop/wrappers/ctyping/msi_windows_installer/tables.py +35 -0
- atomicshop/wrappers/ctyping/setup_device.py +466 -0
- atomicshop/wrappers/ctyping/win_console.py +39 -0
- atomicshop/wrappers/dockerw/dockerw.py +113 -2
- atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
- atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
- atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
- atomicshop/wrappers/factw/get_file_data.py +12 -5
- atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
- atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
- atomicshop/wrappers/factw/postgresql/firmware.py +4 -6
- atomicshop/wrappers/githubw.py +583 -51
- atomicshop/wrappers/loggingw/consts.py +49 -0
- atomicshop/wrappers/loggingw/filters.py +102 -0
- atomicshop/wrappers/loggingw/formatters.py +58 -71
- atomicshop/wrappers/loggingw/handlers.py +459 -40
- atomicshop/wrappers/loggingw/loggers.py +19 -0
- atomicshop/wrappers/loggingw/loggingw.py +1010 -178
- atomicshop/wrappers/loggingw/reading.py +344 -19
- atomicshop/wrappers/mongodbw/__init__.py +0 -0
- atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
- atomicshop/wrappers/mongodbw/mongodbw.py +1432 -0
- atomicshop/wrappers/netshw.py +271 -0
- atomicshop/wrappers/playwrightw/engine.py +34 -19
- atomicshop/wrappers/playwrightw/infra.py +5 -0
- atomicshop/wrappers/playwrightw/javascript.py +7 -3
- atomicshop/wrappers/playwrightw/keyboard.py +14 -0
- atomicshop/wrappers/playwrightw/scenarios.py +172 -5
- atomicshop/wrappers/playwrightw/waits.py +9 -7
- atomicshop/wrappers/powershell_networking.py +80 -0
- atomicshop/wrappers/psutilw/processes.py +81 -0
- atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
- atomicshop/wrappers/psutilw/psutilw.py +9 -0
- atomicshop/wrappers/pyopensslw.py +9 -2
- atomicshop/wrappers/pywin32w/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/cert_store.py +116 -0
- atomicshop/wrappers/pywin32w/console.py +34 -0
- atomicshop/wrappers/pywin32w/win_event_log/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribe.py +212 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +57 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +49 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/schannel_logging.py +97 -0
- atomicshop/wrappers/pywin32w/winshell.py +19 -0
- atomicshop/wrappers/pywin32w/wmis/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
- atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
- atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
- atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
- atomicshop/wrappers/socketw/accepter.py +21 -7
- atomicshop/wrappers/socketw/certificator.py +216 -150
- atomicshop/wrappers/socketw/creator.py +190 -50
- atomicshop/wrappers/socketw/dns_server.py +500 -173
- atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
- atomicshop/wrappers/socketw/process_getter.py +86 -0
- atomicshop/wrappers/socketw/receiver.py +144 -102
- atomicshop/wrappers/socketw/sender.py +65 -35
- atomicshop/wrappers/socketw/sni.py +334 -165
- atomicshop/wrappers/socketw/socket_base.py +134 -0
- atomicshop/wrappers/socketw/socket_client.py +137 -95
- atomicshop/wrappers/socketw/socket_server_tester.py +14 -9
- atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
- atomicshop/wrappers/socketw/ssl_base.py +15 -14
- atomicshop/wrappers/socketw/statistics_csv.py +148 -17
- atomicshop/wrappers/sysmonw.py +157 -0
- atomicshop/wrappers/ubuntu_terminal.py +65 -26
- atomicshop/wrappers/win_auditw.py +189 -0
- atomicshop/wrappers/winregw/__init__.py +0 -0
- atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
- atomicshop/wrappers/winregw/winreg_network.py +232 -0
- {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -49
- atomicshop-3.10.5.dist-info/RECORD +306 -0
- {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
- atomicshop/_basics_temp.py +0 -101
- atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
- atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
- atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
- atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
- atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
- atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
- atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
- atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
- atomicshop/addons/package_setup/Setup.cmd +0 -7
- atomicshop/addons/process_list/compile.cmd +0 -2
- atomicshop/addons/process_list/compiled/Win10x64/process_list.dll +0 -0
- atomicshop/addons/process_list/compiled/Win10x64/process_list.exp +0 -0
- atomicshop/addons/process_list/compiled/Win10x64/process_list.lib +0 -0
- atomicshop/archiver/_search_in_zip.py +0 -189
- atomicshop/archiver/archiver.py +0 -34
- atomicshop/archiver/search_in_archive.py +0 -250
- atomicshop/archiver/sevenz_app_w.py +0 -86
- atomicshop/archiver/sevenzs.py +0 -44
- atomicshop/archiver/zips.py +0 -293
- atomicshop/etw/dns_trace.py +0 -118
- atomicshop/etw/etw.py +0 -61
- atomicshop/file_types.py +0 -24
- atomicshop/mitm/engines/create_module_template_example.py +0 -13
- atomicshop/mitm/initialize_mitm_server.py +0 -240
- atomicshop/monitor/checks/hash.py +0 -44
- atomicshop/monitor/checks/hash_checks/file.py +0 -55
- atomicshop/monitor/checks/hash_checks/url.py +0 -62
- atomicshop/pbtkmultifile_argparse.py +0 -88
- atomicshop/permissions.py +0 -110
- atomicshop/process_poller.py +0 -237
- atomicshop/script_as_string_processor.py +0 -38
- atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
- atomicshop/ssh_scripts/process_from_port.py +0 -27
- atomicshop/wrappers/_process_wrapper_curl.py +0 -27
- atomicshop/wrappers/_process_wrapper_tar.py +0 -21
- atomicshop/wrappers/dockerw/install_docker.py +0 -209
- atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
- atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
- atomicshop/wrappers/ffmpegw.py +0 -125
- atomicshop/wrappers/loggingw/checks.py +0 -20
- atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
- atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
- atomicshop/wrappers/socketw/base.py +0 -59
- atomicshop/wrappers/socketw/get_process.py +0 -107
- atomicshop/wrappers/wslw.py +0 -191
- atomicshop-2.11.47.dist-info/RECORD +0 -251
- /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
- /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
- /atomicshop/{addons/mains → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
- /atomicshop/{addons/mains → a_mains}/search_for_hyperlinks_in_docx.py +0 -0
- /atomicshop/{archiver → etws}/__init__.py +0 -0
- /atomicshop/{etw → etws/traces}/__init__.py +0 -0
- /atomicshop/{monitor/checks/hash_checks → mitm/statistic_analyzer_helper}/__init__.py +0 -0
- /atomicshop/{wrappers/nodejsw → permissions}/__init__.py +0 -0
- /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
- {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
- {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,
|
|
19
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
|
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,
|
|
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(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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.
|