matlab-proxy 0.20.0__py3-none-any.whl → 0.22.0__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 matlab-proxy might be problematic. Click here for more details.

Files changed (39) hide show
  1. matlab_proxy/app.py +1 -1
  2. matlab_proxy/constants.py +1 -0
  3. matlab_proxy/gui/asset-manifest.json +6 -6
  4. matlab_proxy/gui/index.html +1 -1
  5. matlab_proxy/gui/static/css/main.6cd0caba.css +13 -0
  6. matlab_proxy/gui/static/css/main.6cd0caba.css.map +1 -0
  7. matlab_proxy/gui/static/js/main.77e6cbaf.js +3 -0
  8. matlab_proxy/gui/static/js/{main.9c68c75c.js.LICENSE.txt → main.77e6cbaf.js.LICENSE.txt} +0 -2
  9. matlab_proxy/gui/static/js/main.77e6cbaf.js.map +1 -0
  10. matlab_proxy/settings.py +1 -1
  11. {matlab_proxy-0.20.0.dist-info → matlab_proxy-0.22.0.dist-info}/METADATA +2 -1
  12. {matlab_proxy-0.20.0.dist-info → matlab_proxy-0.22.0.dist-info}/RECORD +34 -17
  13. {matlab_proxy-0.20.0.dist-info → matlab_proxy-0.22.0.dist-info}/entry_points.txt +1 -0
  14. matlab_proxy-0.22.0.dist-info/top_level.txt +3 -0
  15. matlab_proxy_manager/__init__.py +6 -0
  16. matlab_proxy_manager/lib/__init__.py +1 -0
  17. matlab_proxy_manager/lib/api.py +295 -0
  18. matlab_proxy_manager/storage/__init__.py +1 -0
  19. matlab_proxy_manager/storage/file_repository.py +144 -0
  20. matlab_proxy_manager/storage/interface.py +62 -0
  21. matlab_proxy_manager/storage/server.py +144 -0
  22. matlab_proxy_manager/utils/__init__.py +1 -0
  23. matlab_proxy_manager/utils/auth.py +77 -0
  24. matlab_proxy_manager/utils/constants.py +5 -0
  25. matlab_proxy_manager/utils/environment_variables.py +46 -0
  26. matlab_proxy_manager/utils/helpers.py +286 -0
  27. matlab_proxy_manager/utils/logger.py +73 -0
  28. matlab_proxy_manager/web/__init__.py +1 -0
  29. matlab_proxy_manager/web/app.py +447 -0
  30. matlab_proxy_manager/web/monitor.py +45 -0
  31. matlab_proxy_manager/web/watcher.py +54 -0
  32. tests/unit/test_app.py +1 -1
  33. matlab_proxy/gui/static/css/main.da9c4eb8.css +0 -13
  34. matlab_proxy/gui/static/css/main.da9c4eb8.css.map +0 -1
  35. matlab_proxy/gui/static/js/main.9c68c75c.js +0 -3
  36. matlab_proxy/gui/static/js/main.9c68c75c.js.map +0 -1
  37. matlab_proxy-0.20.0.dist-info/top_level.txt +0 -2
  38. {matlab_proxy-0.20.0.dist-info → matlab_proxy-0.22.0.dist-info}/LICENSE.md +0 -0
  39. {matlab_proxy-0.20.0.dist-info → matlab_proxy-0.22.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,144 @@
1
+ # Copyright 2024 The MathWorks, Inc.
2
+ import json
3
+ from dataclasses import asdict, dataclass, field
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ from matlab_proxy_manager.utils import helpers, logger
8
+
9
+ log = logger.get()
10
+
11
+
12
+ @dataclass
13
+ class ServerProcess:
14
+ """
15
+ Represents a MATLAB server process with various attributes and methods
16
+ to manage its lifecycle.
17
+ """
18
+
19
+ server_url: Optional[str] = None
20
+ mwi_base_url: Optional[str] = None
21
+ headers: Optional[dict] = None
22
+ errors: Optional[list] = None
23
+ pid: Optional[int] = None
24
+ parent_pid: Optional[int] = None
25
+ absolute_url: Optional[str] = field(default=None)
26
+ id: Optional[str] = None
27
+ type: Optional[str] = None
28
+ mpm_auth_token: Optional[str] = None
29
+
30
+ def __post_init__(self) -> None:
31
+ if not self.absolute_url:
32
+ self.absolute_url = f"{self.server_url}{self.mwi_base_url}"
33
+
34
+ def __str__(self) -> str:
35
+ """
36
+ Returns a string representation of the ServerProcess instance.
37
+ """
38
+ return json.dumps(asdict(self))
39
+
40
+ def as_dict(self) -> dict:
41
+ """
42
+ Returns a dict representation of the ServerProcess instance.
43
+ """
44
+ return asdict(self)
45
+
46
+ @staticmethod
47
+ def instantiate_from_string(data: str) -> "ServerProcess":
48
+ """
49
+ Instantiates a ServerProcess object from a JSON string.
50
+
51
+ Args:
52
+ data (str): The JSON string representing a ServerProcess.
53
+
54
+ Returns:
55
+ ServerProcess: An instance of ServerProcess.
56
+
57
+ Raises:
58
+ ValueError: If the JSON string cannot be parsed or is missing required fields.
59
+ """
60
+ try:
61
+ full_dict = json.loads(data)
62
+ key = list(full_dict.keys())[0]
63
+ server = full_dict[key]
64
+ server_process = ServerProcess(
65
+ server_url=server["server_url"],
66
+ mwi_base_url=server["mwi_base_url"],
67
+ headers=server["headers"],
68
+ errors=server["errors"],
69
+ pid=server["pid"],
70
+ parent_pid=server["parent_pid"],
71
+ absolute_url=server["absolute_url"],
72
+ id=server["id"],
73
+ type=server["type"],
74
+ mpm_auth_token=server["mpm_auth_token"],
75
+ )
76
+ return server_process
77
+ except (json.JSONDecodeError, KeyError) as ex:
78
+ log.debug("Failed to instantiate server from %s: %s", data, ex)
79
+ raise ValueError(
80
+ "Invalid JSON string for ServerProcess instantiation"
81
+ ) from ex
82
+
83
+ def shutdown(self):
84
+ """
85
+ Shuts down the MATLAB proxy server by calling the shutdown_integration endpoint.
86
+ """
87
+ log.debug("Shutting down matlab proxy")
88
+ backend_server = self.absolute_url
89
+ url = f"{backend_server}/shutdown_integration"
90
+ try:
91
+ response = helpers.requests_retry_session(retries=1).delete(
92
+ url=url, headers=self.headers
93
+ )
94
+ shutdown_resp = response.json()
95
+ log.debug("Response from shutdown: %s", response.json())
96
+ return shutdown_resp
97
+ except Exception as e:
98
+ log.debug("Exception while shutting down matlab proxy: %s", e)
99
+ return None
100
+
101
+ def is_server_alive(self) -> bool:
102
+ """
103
+ Checks if the server process is alive and ready.
104
+
105
+ Returns:
106
+ bool: True if the server process is alive and ready, False otherwise.
107
+ """
108
+ return helpers.does_process_exist(self.pid) and helpers.is_server_ready(
109
+ self.absolute_url, retries=0
110
+ )
111
+
112
+ @staticmethod
113
+ def find_existing_server(data_dir, key: str) -> Optional["ServerProcess"]:
114
+ """
115
+ Finds an existing server process by reading the server configuration from a file.
116
+
117
+ Args:
118
+ data_dir (str): The directory where server configuration files are stored.
119
+ key (str): The key corresponding to the specific server configuration.
120
+
121
+ Returns:
122
+ Optional[ServerProcess]: An instance of ServerProcess if a valid configuration is found,
123
+ otherwise None.
124
+ """
125
+ key_dir = Path(data_dir, key)
126
+ server_process = None
127
+
128
+ # Return early if the directory is not found
129
+ if not key_dir.is_dir():
130
+ return server_process
131
+
132
+ files = list(key_dir.iterdir())
133
+ if not files:
134
+ return server_process
135
+
136
+ try:
137
+ with open(files[0], "r", encoding="utf-8") as file:
138
+ data = file.read().strip()
139
+ if data:
140
+ server_process = ServerProcess.instantiate_from_string(data)
141
+ except (OSError, ValueError) as ex:
142
+ log.debug("Exception while checking for existing server: %s", ex)
143
+
144
+ return server_process
@@ -0,0 +1 @@
1
+ # Copyright 2024 The MathWorks, Inc.
@@ -0,0 +1,77 @@
1
+ # Copyright 2024 The MathWorks, Inc.
2
+ from hmac import compare_digest
3
+
4
+ from aiohttp import web
5
+
6
+ from matlab_proxy_manager.utils import logger
7
+ from matlab_proxy_manager.utils.constants import HEADER_MWI_MPM_AUTH_TOKEN
8
+
9
+ log = logger.get()
10
+
11
+
12
+ async def authenticate_request(request):
13
+ """Authenticates incoming request by verifying whether the expected token is in the request.
14
+
15
+ The MWI-MPM-AUTH-TOKEN must be present in the request's headers.
16
+
17
+ Returns True/False based on whether the request is authenticated.
18
+ """
19
+
20
+ log.debug("<======== Authenticate request: %s", request)
21
+ return await _is_valid_token_in_headers(request)
22
+
23
+
24
+ def authenticate_access_decorator(endpoint):
25
+ """This decorator verifies that the request to an endpoint exposed by matlab-proxy-manager
26
+ contains the correct mpm auth token before servicing an endpoint."""
27
+
28
+ async def protect_endpoint(request):
29
+ """Passes request to the endpoint after validating the token
30
+
31
+ Args:
32
+ request (HTTPRequest) : Web Request to endpoint
33
+
34
+ Raises:
35
+ web.HTTPForbidden: Thrown when validation of token fails
36
+ """
37
+ if await authenticate_request(request):
38
+ # request is authentic, proceed to execute the endpoint
39
+ return await endpoint(request)
40
+ raise web.HTTPForbidden(reason="Unauthorized access!")
41
+
42
+ return protect_endpoint
43
+
44
+
45
+ async def _is_valid_token_in_headers(request):
46
+ """Checks the request headers for mpm auth token
47
+
48
+ Args:
49
+ request (HTTPRequest) : Used to access app settings
50
+
51
+ Returns:
52
+ Boolean : True if valid token is found, else False
53
+ """
54
+ headers = request.headers
55
+ if HEADER_MWI_MPM_AUTH_TOKEN in headers:
56
+ return await _is_valid_token(headers[HEADER_MWI_MPM_AUTH_TOKEN], request)
57
+
58
+ log.debug("Header: %s not found in request headers", HEADER_MWI_MPM_AUTH_TOKEN)
59
+ return False
60
+
61
+
62
+ async def _is_valid_token(token, request):
63
+ """Checks if token contains expected value.
64
+
65
+ Args:
66
+ token (str): Token string to validate
67
+ request (HTTPRequest) : Used to access app settings
68
+
69
+ Returns:
70
+ _type_: True if token is valid, false otherwise.
71
+ """
72
+ # Check if the token provided in the request matches the original token
73
+ # equivalent to a == b, but protects against timing attacks
74
+ saved_token = request.app["auth_token"]
75
+ is_valid = compare_digest(token, saved_token)
76
+ log.debug("Token validation %s ", "successful." if is_valid else "failed.")
77
+ return is_valid
@@ -0,0 +1,5 @@
1
+ # Copyright 2024 The MathWorks, Inc.
2
+
3
+ MWI_BASE_URL_PREFIX = "/matlab/"
4
+ HEADER_MWI_MPM_CONTEXT = "MWI-MPM-CONTEXT"
5
+ HEADER_MWI_MPM_AUTH_TOKEN = "MWI-MPM-AUTH-TOKEN"
@@ -0,0 +1,46 @@
1
+ # Copyright 2020-2024 The MathWorks, Inc.
2
+ """This file lists and exposes the environment variables which are used by proxy manager."""
3
+
4
+ import os
5
+
6
+
7
+ def _is_env_set_to_true(env_name: str) -> bool:
8
+ """Helper function that returns True if the environment variable specified is set to True.
9
+
10
+ Args:
11
+ env_name (str): Name of the environment variable to check the state for.
12
+
13
+ Returns:
14
+ bool: True if the environment variable's value matches(case-insensitive) the string "True"
15
+ """
16
+ return os.environ.get(env_name, "").lower() == "true"
17
+
18
+
19
+ def get_env_name_logging_level():
20
+ """Specifies the logging level used by app's loggers"""
21
+ return "MWI_MPM_LOG_LEVEL"
22
+
23
+
24
+ def get_env_name_enable_web_logging():
25
+ """Enable the logging of asyncio web traffic by setting to true"""
26
+ return "MWI_MPM_ENABLE_WEB_LOGGING"
27
+
28
+
29
+ def get_env_name_mwi_mpm_auth_token():
30
+ """Authentication environment variable for authenticating with proxy manager"""
31
+ return "MWI_MPM_AUTH_TOKEN"
32
+
33
+
34
+ def get_env_name_mwi_mpm_port():
35
+ """Used to specify the port on which to start proxy manager"""
36
+ return "MWI_MPM_PORT"
37
+
38
+
39
+ def get_env_name_mwi_mpm_parent_pid():
40
+ """Used to specify the parent pid for the proxy manager process"""
41
+ return "MWI_MPM_PARENT_PID"
42
+
43
+
44
+ def is_web_logging_enabled():
45
+ """Returns true if the web logging is required to be enabled"""
46
+ return _is_env_set_to_true(get_env_name_enable_web_logging())
@@ -0,0 +1,286 @@
1
+ # Copyright 2024 The MathWorks, Inc.
2
+ import os
3
+ import socket
4
+ import time
5
+ from pathlib import Path
6
+ from typing import Dict
7
+
8
+ import psutil
9
+ import requests
10
+ from aiohttp import web
11
+ from requests.adapters import HTTPAdapter
12
+ from urllib3.util.retry import Retry
13
+
14
+ from matlab_proxy import settings
15
+ from matlab_proxy_manager.storage.file_repository import FileRepository
16
+ from matlab_proxy_manager.utils import logger
17
+
18
+ log = logger.get()
19
+
20
+
21
+ def is_server_ready(url: str, retries: int = 2, backoff_factor=None) -> bool:
22
+ """
23
+ Check if the server at the given URL is ready.
24
+
25
+ Args:
26
+ url (str): The URL of the server.
27
+
28
+ Returns:
29
+ bool: True if the server is ready, False otherwise.
30
+ """
31
+ try:
32
+ matlab_proxy_index_page_identifier = "MWI_MATLAB_PROXY_IDENTIFIER"
33
+ resp = requests_retry_session(
34
+ retries=retries, backoff_factor=backoff_factor
35
+ ).get(f"{url}", verify=False)
36
+ log.debug("Response status code from server readiness: %s", resp.status_code)
37
+ return (
38
+ resp.status_code == requests.codes.OK
39
+ and matlab_proxy_index_page_identifier in resp.text
40
+ )
41
+ except Exception as e:
42
+ log.debug("Couldn't reach the server with error: %s", e)
43
+ return False
44
+
45
+
46
+ def requests_retry_session(
47
+ retries=3, backoff_factor=0.1, session=None
48
+ ) -> requests.Session:
49
+ """
50
+ Create a requests session with retry logic.
51
+
52
+ Args:
53
+ retries (int): The number of retries.
54
+ backoff_factor (float): The backoff factor for retries.
55
+ session (requests.Session, optional): An existing requests session.
56
+
57
+ Returns:
58
+ requests.Session: The requests session with retry logic.
59
+ """
60
+ session = session or requests.session()
61
+ retry = Retry(
62
+ total=retries,
63
+ read=retries,
64
+ connect=retries,
65
+ backoff_factor=backoff_factor,
66
+ allowed_methods=frozenset(["DELETE", "GET", "POST"]),
67
+ )
68
+ adapter = HTTPAdapter(max_retries=retry)
69
+ session.mount("http://", adapter)
70
+ session.mount("https://", adapter)
71
+ return session
72
+
73
+
74
+ def does_process_exist(pid: int) -> bool:
75
+ """
76
+ Checks if the parent process is alive.
77
+
78
+ Returns:
79
+ bool: True if the parent process is alive, False otherwise.
80
+ """
81
+ parent_status = pid is not None and psutil.pid_exists(int(pid))
82
+ log.debug("Parent liveness check returned: %s", parent_status)
83
+ return parent_status
84
+
85
+
86
+ def convert_mwi_env_vars_to_header_format(
87
+ env_vars: Dict[str, str], prefix: str
88
+ ) -> Dict[str, str]:
89
+ """
90
+ Parse and transform environment variables with a specific prefix.
91
+
92
+ Args:
93
+ env_vars (dict): The environment variables.
94
+ prefix (str): The prefix to filter the environment variables.
95
+
96
+ Returns:
97
+ dict: The transformed environment variables.
98
+ """
99
+ return {
100
+ key.replace("_", "-"): value
101
+ for key, value in env_vars.items()
102
+ if key.startswith(prefix)
103
+ }
104
+
105
+
106
+ def create_and_get_proxy_manager_data_dir() -> Path:
107
+ """
108
+ Create and get the proxy manager data directory.
109
+
110
+ Returns:
111
+ Path: The path to the proxy manager data directory.
112
+ """
113
+
114
+ config_dir = settings.get_mwi_config_folder(dev=False)
115
+ data_dir = Path(config_dir, "proxy_manager")
116
+ Path.mkdir(data_dir, parents=True, exist_ok=True)
117
+ return data_dir
118
+
119
+
120
+ async def delete_dangling_servers(app: web.Application) -> None:
121
+ """
122
+ Delete dangling matlab proxy servers that are no longer alive.
123
+
124
+ Args:
125
+ app (web.Application): aiohttp web application
126
+ """
127
+ is_delete_successful = _are_orphaned_servers_deleted(None)
128
+ log.debug("Deleted dangling matlab proxy servers: %s", is_delete_successful)
129
+
130
+
131
+ def _are_orphaned_servers_deleted(predicate: str) -> bool:
132
+ """
133
+ Get all the files under the proxy manager directory, check the status of the servers,
134
+ and delete orphaned servers and their corresponding files.
135
+
136
+ - Checks if the parent PID of each server is still alive. If not, sends a SIGKILL
137
+ to the server and deletes the corresponding file.
138
+ - Checks if the servers in those files are still alive by sending GET requests to
139
+ their absolute URLs. If not, deletes the corresponding file.
140
+
141
+ Returns:
142
+ bool: True if any server was deleted, False otherwise.
143
+ """
144
+ data_dir = create_and_get_proxy_manager_data_dir()
145
+ storage = FileRepository(data_dir)
146
+ servers: dict = storage.get_all()
147
+
148
+ def _matches_predicate(filename: str) -> bool:
149
+ return filename.split("_")[0] == predicate
150
+
151
+ # Checks only a subset of servers (that matches the parent_pid of the caller)
152
+ # to reduce the MATLAB proxy startup time
153
+ if predicate:
154
+ servers = {
155
+ filename: server
156
+ for filename, server in servers.items()
157
+ if _matches_predicate(filename)
158
+ }
159
+ if not servers:
160
+ log.debug("Parent pid not matched, nothing to cleanup")
161
+ return True
162
+
163
+ return _delete_server_and_file(storage, servers)
164
+
165
+
166
+ def _delete_server_and_file(storage, servers):
167
+ is_server_deleted = False
168
+ for filename, server in servers.items():
169
+ if not server.is_server_alive():
170
+ log.debug("Server is not alive, cleaning up files")
171
+ try:
172
+ storage.delete(os.path.basename(filename))
173
+ except Exception as ex:
174
+ log.debug("Failed to delete file: %s", ex)
175
+ is_server_deleted = True
176
+ elif not does_process_exist(server.parent_pid):
177
+ log.debug("Server's parent is gone, shutting down matlab proxy")
178
+ try:
179
+ server.shutdown()
180
+ except Exception as ex:
181
+ log.debug("Failed to shutdown the matlab proxy server: %s", ex)
182
+ finally:
183
+ # Ensures files are cleaned up even if shutdown fails
184
+ storage.delete(os.path.basename(filename))
185
+ is_server_deleted = True
186
+
187
+ return is_server_deleted
188
+
189
+
190
+ def poll_for_server_deletion() -> None:
191
+ """
192
+ Poll for server deletion for a specified timeout period.
193
+
194
+ This function continuously checks if orphaned servers are deleted within a
195
+ specified timeout period. If servers are deleted, it breaks out of the loop.
196
+
197
+ Logs the status of server deletion attempts.
198
+ """
199
+ timeout_in_seconds: int = 2
200
+ log.info("Interrupt/termination signal caught, cleaning up resources")
201
+ start_time = time.time()
202
+
203
+ while time.time() - start_time < timeout_in_seconds:
204
+ is_server_deleted = _are_orphaned_servers_deleted(None)
205
+ if is_server_deleted:
206
+ log.debug("Servers deleted, breaking out of loop")
207
+ break
208
+ log.debug("Servers not deleted, waiting")
209
+ # Sleep for a short interval before polling again
210
+ time.sleep(0.5)
211
+
212
+
213
+ def find_free_port() -> str:
214
+ """
215
+ Find a free port on the system.
216
+
217
+ This function creates a socket, binds it to an available port, retrieves
218
+ the port number, and then closes the socket.
219
+
220
+ Returns:
221
+ str: The free port number as a string.
222
+ """
223
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
224
+ s.bind(("", 0))
225
+ port = str(s.getsockname()[1])
226
+ s.close()
227
+ return port
228
+
229
+
230
+ def pre_load_from_state_file(data_dir: str) -> Dict[str, str]:
231
+ """
232
+ Pre-load server states from the state files in the specified data directory.
233
+
234
+ Args:
235
+ data_dir (Path): The directory containing the state files.
236
+
237
+ Returns:
238
+ Dict[str, Dict]: A dictionary with server IDs as keys and server states as values.
239
+ """
240
+ storage = FileRepository(data_dir)
241
+ servers = storage.get_all()
242
+ return {server.id: server.as_dict() for server in servers.values()}
243
+
244
+
245
+ def is_only_reference(file_path: str) -> bool:
246
+ """
247
+ Check if the specified file is the only file in its directory.
248
+
249
+ Args:
250
+ file_path (str): The path to the file.
251
+
252
+ Returns:
253
+ bool: True if the file is the only file in its directory, False otherwise.
254
+ """
255
+ parent_dir = Path(file_path).parent.absolute()
256
+ files = os.listdir(parent_dir)
257
+ return len(files) == 1 and files[0] == os.path.basename(file_path)
258
+
259
+
260
+ def create_state_file(data_dir, server_process, filename: str):
261
+ """
262
+ Create a state file in the specified data directory.
263
+
264
+ This function creates a state file for the given server process in the specified
265
+ data directory. It logs the process and handles any exceptions that might occur.
266
+
267
+ Args:
268
+ data_dir: The directory where the state file will be created.
269
+ server_process (ServerProcess): The server process for which the state file is created.
270
+ filename (str): The name of the state file to be created.
271
+
272
+ Raises:
273
+ IOError: If there is an error creating the state file or adding the server
274
+ process to the repository.
275
+ """
276
+ try:
277
+ storage = FileRepository(data_dir)
278
+ storage.add(server=server_process, filename=filename)
279
+ log.debug("State file %s created in %s", filename, data_dir)
280
+ except Exception as e:
281
+ log.error(
282
+ "Failed to create state file %s in %s, error: %s", filename, data_dir, e
283
+ )
284
+ raise IOError(
285
+ f"Failed to create state file {filename} in {data_dir}, error: {e}"
286
+ ) from e
@@ -0,0 +1,73 @@
1
+ # Copyright 2024 The MathWorks, Inc.
2
+ # Helper functions to access & control the logging behavior of the app
3
+
4
+ import logging
5
+ import os
6
+
7
+
8
+ # TODO: Consolidate all logging setup into a common module (MATLABProxy, MATLABKernel, MATLABProxyManager)
9
+ def get(init=False):
10
+ """Get the logger used by this application.
11
+ Set init=True to initialize the logger
12
+ Returns:
13
+ Logger: The logger used by this application.
14
+ """
15
+ if init is True:
16
+ return __set_logging_configuration()
17
+
18
+ return __get_mw_logger()
19
+
20
+
21
+ def __get_mw_logger_name():
22
+ """Name of logger used by the app
23
+
24
+ Returns:
25
+ String: The name of the Logger.
26
+ """
27
+ return "MATLABProxyManager"
28
+
29
+
30
+ def __get_mw_logger():
31
+ """Returns logger for use in this app.
32
+
33
+ Returns:
34
+ Logger: A logger object
35
+ """
36
+ return logging.getLogger(__get_mw_logger_name())
37
+
38
+
39
+ def __set_logging_configuration():
40
+ """Sets the logging environment for the app
41
+
42
+ Returns:
43
+ Logger: Logger object with the set configuration.
44
+ """
45
+ # query for user specified environment variables
46
+ log_level = os.getenv(__get_env_name_logging_level(), __get_default_log_level())
47
+
48
+ ## Set logging object
49
+ logger = __get_mw_logger()
50
+
51
+ # log_level is either set by environment or is the default value.
52
+ logger.info("Initializing logger with log_level: %s", log_level)
53
+ logger.setLevel(log_level)
54
+
55
+ # Allow other libraries used by this integration to
56
+ # also print their logs at the specified level
57
+ logging.basicConfig(level=log_level)
58
+
59
+ return logger
60
+
61
+
62
+ def __get_env_name_logging_level():
63
+ """Specifies the logging level used by app's loggers"""
64
+ return "MWI_MPM_LOG_LEVEL"
65
+
66
+
67
+ def __get_default_log_level():
68
+ """The default logging level used by this application.
69
+
70
+ Returns:
71
+ String: The default logging level
72
+ """
73
+ return "INFO"
@@ -0,0 +1 @@
1
+ # Copyright 2024 The MathWorks, Inc.