Skip to content

Commit f3d4013

Browse files
rashedmytPrabhakar Kumar
authored and
Prabhakar Kumar
committed
Introduces MWI_JUPYTER_LOG_LEVEL to enable logging of debug statements from MATLABKernel.
1 parent 5438de2 commit f3d4013

File tree

6 files changed

+269
-50
lines changed

6 files changed

+269
-50
lines changed

src/jupyter_matlab_kernel/__main__.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
# Copyright 2023 The MathWorks, Inc.
1+
# Copyright 2023-2024 The MathWorks, Inc.
22
# Use ipykernel infrastructure to launch the MATLAB Kernel.
33

44
if __name__ == "__main__":
55
from ipykernel.kernelapp import IPKernelApp
6+
from jupyter_matlab_kernel import mwi_logger
67
from jupyter_matlab_kernel.kernel import MATLABKernel
78

8-
IPKernelApp.launch_instance(kernel_class=MATLABKernel)
9+
logger = mwi_logger.get(init=True)
10+
11+
IPKernelApp.launch_instance(kernel_class=MATLABKernel, log=logger)

src/jupyter_matlab_kernel/kernel.py

+98-27
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2023 The MathWorks, Inc.
1+
# Copyright 2023-2024 The MathWorks, Inc.
22
# Implementation of MATLAB Kernel
33

44
# Import Python Standard Library
@@ -10,14 +10,13 @@
1010
import ipykernel.kernelbase
1111
import psutil
1212
import requests
13-
from requests.exceptions import HTTPError
14-
15-
from jupyter_matlab_kernel import mwi_comm_helpers
16-
from matlab_proxy import util as mwi_util
13+
from jupyter_matlab_kernel import mwi_comm_helpers, mwi_logger
1714
from matlab_proxy import settings as mwi_settings
18-
15+
from matlab_proxy import util as mwi_util
16+
from requests.exceptions import HTTPError
1917

2018
_MATLAB_STARTUP_TIMEOUT = mwi_settings.get_process_startup_timeout()
19+
_logger = mwi_logger.get()
2120

2221

2322
class MATLABConnectionError(Exception):
@@ -46,7 +45,7 @@ def is_jupyter_testing_enabled():
4645
return os.environ.get("MWI_JUPYTER_TEST", "false").lower() == "true"
4746

4847

49-
def start_matlab_proxy_for_testing():
48+
def start_matlab_proxy_for_testing(logger=_logger):
5049
"""
5150
Only used for testing purposes. Gets the matlab-proxy server configuration
5251
from environment variables and mocks the 'start_matlab_proxy' function
@@ -66,6 +65,8 @@ def start_matlab_proxy_for_testing():
6665
matlab_proxy_base_url = os.environ[mwi_env.get_env_name_base_url()]
6766
matlab_proxy_app_port = os.environ[mwi_env.get_env_name_app_port()]
6867

68+
logger.debug("Creating matlab-proxy URL for MATLABKernel testing.")
69+
6970
# '127.0.0.1' is used instead 'localhost' for testing since Windows machines consume
7071
# some time to resolve 'localhost' hostname
7172
url = "{protocol}://127.0.0.1:{port}{base_url}".format(
@@ -74,10 +75,14 @@ def start_matlab_proxy_for_testing():
7475
base_url=matlab_proxy_base_url,
7576
)
7677
headers = {}
78+
79+
logger.debug(f"matlab-proxy URL: {url}")
80+
logger.debug(f"headers: {headers}")
81+
7782
return url, matlab_proxy_base_url, headers
7883

7984

80-
def _start_matlab_proxy_using_jupyter(url, headers):
85+
def _start_matlab_proxy_using_jupyter(url, headers, logger=_logger):
8186
"""
8287
Start matlab-proxy using jupyter server which started the current kernel
8388
process by sending HTTP request to the endpoint registered through
@@ -94,16 +99,21 @@ def _start_matlab_proxy_using_jupyter(url, headers):
9499
# can be used to validate a proper response.
95100
matlab_proxy_index_page_identifier = "MWI_MATLAB_PROXY_IDENTIFIER"
96101

102+
logger.debug(
103+
f"Sending request to jupyter to start matlab-proxy at {url} with headers: {headers}"
104+
)
97105
# send request to the matlab-proxy endpoint to make sure it is available.
98106
# If matlab-proxy is not started, jupyter-server starts it at this point.
99107
resp = requests.get(url, headers=headers, verify=False)
108+
logger.debug(f"Received status code: {resp.status_code}")
109+
100110
return (
101111
resp.status_code == requests.codes.OK
102112
and matlab_proxy_index_page_identifier in resp.text
103113
)
104114

105115

106-
def start_matlab_proxy():
116+
def start_matlab_proxy(logger=_logger):
107117
"""
108118
Start matlab-proxy registered with the jupyter server which started the
109119
current kernel process.
@@ -122,7 +132,7 @@ def start_matlab_proxy():
122132
# launched by the tests and kernel would expect the configurations of this matlab-proxy
123133
# server which is provided through environment variables to 'start_matlab_proxy_for_testing'
124134
if is_jupyter_testing_enabled():
125-
return start_matlab_proxy_for_testing()
135+
return start_matlab_proxy_for_testing(logger)
126136

127137
nb_server_list = []
128138

@@ -150,17 +160,21 @@ def start_matlab_proxy():
150160
if mwi_util.system.is_windows() and is_virtual_env:
151161
jupyter_server_pid = psutil.Process(jupyter_server_pid).ppid()
152162

163+
logger.debug(f"Resolved jupyter server pid: {jupyter_server_pid}")
164+
153165
nb_server = dict()
154166
found_nb_server = False
155167
for server in nb_server_list:
156168
if server["pid"] == jupyter_server_pid:
169+
logger.debug("Jupyter server associated with this MATLAB Kernel found.")
157170
found_nb_server = True
158171
nb_server = server
159172
# Stop iterating over the server list
160173
break
161174

162175
# Error out if the server is not found!
163176
if found_nb_server == False:
177+
logger.error("Jupyter server associated with this MATLABKernel not found.")
164178
raise MATLABConnectionError(
165179
"""
166180
Error: MATLAB Kernel for Jupyter was unable to find the notebook server from which it was spawned!\n
@@ -170,6 +184,7 @@ def start_matlab_proxy():
170184

171185
# Verify that Password is disabled
172186
if nb_server["password"] is True:
187+
logger.error("Jupyter server uses password for authentication.")
173188
# TODO: To support passwords, we either need to acquire it from Jupyter or ask the user?
174189
raise MATLABConnectionError(
175190
"""
@@ -198,9 +213,16 @@ def start_matlab_proxy():
198213
else:
199214
headers = None
200215

201-
if _start_matlab_proxy_using_jupyter(url, headers):
216+
if _start_matlab_proxy_using_jupyter(url, headers, logger):
217+
logger.debug(
218+
f"Started matlab-proxy using jupyter at {url} with headers: {headers}"
219+
)
202220
return url, nb_server["base_url"], headers
203221

222+
logger.error(
223+
f"MATLABKernel could not communicate with matlab-proxy through Jupyter server"
224+
)
225+
logger.error(f"Jupyter server:\n{nb_server}")
204226
raise MATLABConnectionError(
205227
"""
206228
Error: MATLAB Kernel could not communicate with MATLAB.
@@ -236,14 +258,22 @@ class MATLABKernel(ipykernel.kernelbase.Kernel):
236258
def __init__(self, *args, **kwargs):
237259
# Call superclass constructor to initialize ipykernel infrastructure
238260
super(MATLABKernel, self).__init__(*args, **kwargs)
261+
262+
# Update log instance with kernel id. This helps in identifying logs from
263+
# multiple kernels which are running simultaneously
264+
self.log.debug(f"Initializing kernel with id: {self.ident}")
265+
self.log = self.log.getChild(f"{self.ident}")
266+
239267
try:
240268
# Start matlab-proxy using the jupyter-matlab-proxy registered endpoint.
241-
self.murl, self.server_base_url, self.headers = start_matlab_proxy()
269+
self.murl, self.server_base_url, self.headers = start_matlab_proxy(self.log)
242270
(
243271
self.is_matlab_licensed,
244272
self.matlab_status,
245273
self.matlab_proxy_has_error,
246-
) = mwi_comm_helpers.fetch_matlab_proxy_status(self.murl, self.headers)
274+
) = mwi_comm_helpers.fetch_matlab_proxy_status(
275+
self.murl, self.headers, self.log
276+
)
247277
except MATLABConnectionError as err:
248278
self.startup_error = err
249279

@@ -255,14 +285,20 @@ async def interrupt_request(self, stream, ident, parent):
255285
Custom handling of interrupt request sent by Jupyter. For more info, look at
256286
https://jupyter-client.readthedocs.io/en/stable/messaging.html#kernel-interrupt
257287
"""
288+
self.log.debug("Received interrupt request from Jupyter")
258289
try:
259290
# Send interrupt request to MATLAB
260-
mwi_comm_helpers.send_interrupt_request_to_matlab(self.murl, self.headers)
291+
mwi_comm_helpers.send_interrupt_request_to_matlab(
292+
self.murl, self.headers, self.log
293+
)
261294

262295
# Set the response to interrupt request.
263296
content = {"status": "ok"}
264297
except Exception as e:
265298
# Set the exception information as response to interrupt request
299+
self.log.error(
300+
f"Exception occurred while sending interrupt request to MATLAB: {e}"
301+
)
266302
content = {
267303
"status": "error",
268304
"ename": str(type(e).__name__),
@@ -286,6 +322,7 @@ def do_execute(
286322
Used by ipykernel infrastructure for execution. For more info, look at
287323
https://jupyter-client.readthedocs.io/en/stable/messaging.html#execute
288324
"""
325+
self.log.debug(f"Received execution request from Jupyter with code:\n{code}")
289326
try:
290327
# Complete one-time startup checks before sending request to MATLAB.
291328
# Blocking call, returns after MATLAB is started.
@@ -305,20 +342,30 @@ def do_execute(
305342
# Perform execution and categorization of outputs in MATLAB. Blocks
306343
# until execution results are received from MATLAB.
307344
outputs = mwi_comm_helpers.send_execution_request_to_matlab(
308-
self.murl, self.headers, code, self.ident
345+
self.murl, self.headers, code, self.ident, self.log
346+
)
347+
348+
self.log.debug(
349+
"Received outputs after execution in MATLAB. Clearing output area"
309350
)
310351

311352
# Clear the output area of the current cell. This removes any previous
312353
# outputs before publishing new outputs.
313354
self.display_output({"type": "clear_output", "content": {"wait": False}})
314355

315356
# Display all the outputs produced during the execution of code.
316-
for data in outputs:
357+
for idx in range(len(outputs)):
358+
data = outputs[idx]
359+
self.log.debug(f"Displaying output {idx+1}:\n{data}")
360+
317361
# Ignore empty values returned from MATLAB.
318362
if not data:
319363
continue
320364
self.display_output(data)
321365
except Exception as e:
366+
self.log.error(
367+
f"Exception occurred while processing execution request:\n{e}"
368+
)
322369
if isinstance(e, HTTPError):
323370
# If exception is an HTTPError, it means MATLAB is unavailable.
324371
# Replace the HTTPError with MATLABConnectionError to give
@@ -356,6 +403,9 @@ def do_complete(self, code, cursor_pos):
356403
user. For example, if matlab-proxy is not licensed, we cannot show
357404
the licensing window.
358405
"""
406+
self.log.debug(
407+
f"Received completion request from Jupyter with cursor position {cursor_pos} and code:\n{code}"
408+
)
359409
# Default completion results. It is modelled after ipkernel.py#do_complete
360410
# implementation to provide metadata for JupyterLab.
361411
completion_results = {
@@ -369,12 +419,16 @@ def do_complete(self, code, cursor_pos):
369419
# results are received from MATLAB or communication with MATLAB fails.
370420
try:
371421
completion_results = mwi_comm_helpers.send_completion_request_to_matlab(
372-
self.murl, self.headers, code, cursor_pos
422+
self.murl, self.headers, code, cursor_pos, self.log
423+
)
424+
except (MATLABConnectionError, HTTPError) as e:
425+
self.log.error(
426+
f"Exception occurred while sending shutdown request to MATLAB:\n{e}"
373427
)
374-
except (MATLABConnectionError, HTTPError):
375-
# Jupyter doesn't show the error messages to the user for this request.
376-
# Hence, we'll currently do nothing when an error occurs here.
377-
pass
428+
429+
self.log.debug(
430+
f"Received completion results from MATLAB:\n{completion_results}"
431+
)
378432

379433
return {
380434
"status": "ok",
@@ -413,14 +467,15 @@ def do_history(
413467
)
414468

415469
def do_shutdown(self, restart):
470+
self.log.debug("Received shutdown request from Jupyter")
416471
try:
417472
mwi_comm_helpers.send_shutdown_request_to_matlab(
418-
self.murl, self.headers, self.ident
473+
self.murl, self.headers, self.ident, self.log
474+
)
475+
except (MATLABConnectionError, HTTPError) as e:
476+
self.log.error(
477+
f"Exception occurred while sending shutdown request to MATLAB:\n{e}"
419478
)
420-
except (MATLABConnectionError, HTTPError):
421-
# Jupyter doesn't show the error messages to the user for this request.
422-
# Hence, we'll currently do nothing when an error occurs here.
423-
pass
424479

425480
return super().do_shutdown(restart)
426481

@@ -435,8 +490,10 @@ def perform_startup_checks(self):
435490
HTTPError, MATLABConnectionError: Occurs when matlab-proxy is not started or kernel cannot
436491
communicate with MATLAB.
437492
"""
493+
self.log.debug("Performing startup checks")
438494
# Incase an error occurred while kernel initialization, display it to the user.
439495
if self.startup_error is not None:
496+
self.log.error(f"Found a startup error: {self.startup_error}")
440497
raise self.startup_error
441498

442499
(
@@ -456,6 +513,9 @@ def perform_startup_checks(self):
456513
#
457514
# TODO: Find a workaround for users to be able to use our Jupyter kernel in VS Code.
458515
if not self.is_matlab_licensed:
516+
self.log.debug(
517+
"MATLAB is not licensed. Displaying HTML output to enable licensing."
518+
)
459519
self.display_output(
460520
{
461521
"type": "display_data",
@@ -469,6 +529,7 @@ def perform_startup_checks(self):
469529
)
470530

471531
# Wait until MATLAB is started before sending requests.
532+
self.log.debug("Waiting until MATLAB is started")
472533
timeout = 0
473534
while (
474535
self.matlab_status != "up"
@@ -477,6 +538,7 @@ def perform_startup_checks(self):
477538
):
478539
if self.is_matlab_licensed:
479540
if timeout == 0:
541+
self.log.debug("Licensing completed. Clearing output area")
480542
self.display_output(
481543
{"type": "clear_output", "content": {"wait": False}}
482544
)
@@ -500,9 +562,18 @@ def perform_startup_checks(self):
500562
# If MATLAB is not available after 15 seconds of licensing information
501563
# being available either through user input or through matlab-proxy cache,
502564
# then display connection error to the user.
503-
if timeout == _MATLAB_STARTUP_TIMEOUT or self.matlab_proxy_has_error:
565+
if timeout == _MATLAB_STARTUP_TIMEOUT:
566+
self.log.error(
567+
f"MATLAB has not started after {_MATLAB_STARTUP_TIMEOUT} seconds."
568+
)
569+
raise MATLABConnectionError
570+
571+
if self.matlab_proxy_has_error:
572+
self.log.error("matlab-proxy encountered error.")
504573
raise MATLABConnectionError
505574

575+
self.log.debug("MATLAB is running, startup checks completed.")
576+
506577
def display_output(self, out):
507578
"""
508579
Common function to send execution outputs to Jupyter UI.

src/jupyter_matlab_kernel/matlab/+jupyter/execute.m

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
% of a unique Live Script. Hence, each execution request can be considered as
1313
% creating and running a new Live Script file.
1414

15-
% Copyright 2023 The MathWorks, Inc.
15+
% Copyright 2023-2024 The MathWorks, Inc.
1616

1717
% Embed user MATLAB code in a try-catch block for MATLAB versions less than R2022b.
1818
% This is will disable inbuilt ErrorRecovery mechanism. Any exceptions created in

0 commit comments

Comments
 (0)