Skip to content

Commit 84a7257

Browse files
committed
flake + v1
1 parent 5313054 commit 84a7257

File tree

5 files changed

+138
-29
lines changed

5 files changed

+138
-29
lines changed

runtimes/v1/azure_functions_runtime_v1/utils/tracing.py

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
3-
43
import traceback
54

6-
from traceback import StackSummary, extract_tb
7-
from typing import List
8-
95

106
def extend_exception_message(exc: Exception, msg: str) -> Exception:
117
# Reconstruct exception message
128
# From: ImportModule: no module name
13-
# To: ImportModule: no module name. msg
9+
# To: ImportModule: no module name. msg
1410
old_tb = exc.__traceback__
1511
old_msg = getattr(exc, 'msg', None) or str(exc) or ''
1612
new_msg = (old_msg.rstrip('.') + '. ' + msg).rstrip()
@@ -19,26 +15,27 @@ def extend_exception_message(exc: Exception, msg: str) -> Exception:
1915

2016

2117
def marshall_exception_trace(exc: Exception) -> str:
22-
stack_summary: StackSummary = extract_tb(exc.__traceback__)
23-
if isinstance(exc, ModuleNotFoundError):
24-
stack_summary = _marshall_module_not_found_error(stack_summary)
25-
return ''.join(stack_summary.format())
26-
27-
28-
def _marshall_module_not_found_error(tbss: StackSummary) -> StackSummary:
29-
tbss = _remove_frame_from_stack(tbss, '<frozen importlib._bootstrap>')
30-
tbss = _remove_frame_from_stack(
31-
tbss, '<frozen importlib._bootstrap_external>')
32-
return tbss
33-
34-
35-
def _remove_frame_from_stack(tbss: StackSummary,
36-
framename: str) -> StackSummary:
37-
filtered_stack_list: List[traceback.FrameSummary] = \
38-
list(filter(lambda frame: getattr(frame,
39-
'filename') != framename, tbss))
40-
filtered_stack: StackSummary = StackSummary.from_list(filtered_stack_list)
41-
return filtered_stack
18+
try:
19+
# Use traceback.format_exception to capture the full exception chain
20+
# This includes __cause__ and __context__ chained exceptions
21+
full_traceback = traceback.format_exception(type(exc), exc, exc.__traceback__)
22+
23+
# If it's a ModuleNotFoundError, we might want to clean up the traceback
24+
if isinstance(exc, ModuleNotFoundError):
25+
# For consistency with the original logic, we'll still filter
26+
# but we need to work with the formatted strings
27+
filtered_lines = []
28+
for line in full_traceback:
29+
if '<frozen importlib._bootstrap>' not in line and \
30+
'<frozen importlib._bootstrap_external>' not in line:
31+
filtered_lines.append(line)
32+
return ''.join(filtered_lines) if filtered_lines else ''.join(
33+
full_traceback)
34+
35+
return ''.join(full_traceback)
36+
except Exception as sub_exc:
37+
return (f'Could not extract traceback. '
38+
f'Sub-exception: {type(sub_exc).__name__}: {str(sub_exc)}')
4239

4340

4441
def serialize_exception(exc: Exception, protos):
@@ -47,10 +44,12 @@ def serialize_exception(exc: Exception, protos):
4744
except Exception:
4845
message = ('Unhandled exception in function. '
4946
'Could not serialize original exception message.')
47+
5048
try:
5149
stack_trace = marshall_exception_trace(exc)
5250
except Exception:
5351
stack_trace = ''
52+
5453
return protos.RpcException(message=message, stack_trace=stack_trace)
5554

5655

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import unittest
5+
import traceback
6+
from azure_functions_runtime_v1.utils.tracing import (extend_exception_message,
7+
marshall_exception_trace,
8+
serialize_exception,
9+
serialize_exception_as_str)
10+
11+
12+
class MockProtos:
13+
class RpcException:
14+
def __init__(self, message, stack_trace):
15+
self.message = message
16+
self.stack_trace = stack_trace
17+
18+
19+
class TestExceptionUtils(unittest.TestCase):
20+
21+
def test_extend_exception_message_basic(self):
22+
exc = ValueError("Original message")
23+
new_msg = "Extra info"
24+
new_exc = extend_exception_message(exc, new_msg)
25+
self.assertIsInstance(new_exc, ValueError)
26+
self.assertIn("Original message", str(new_exc))
27+
self.assertIn("Extra info", str(new_exc))
28+
self.assertTrue(str(new_exc).endswith(new_msg))
29+
30+
def test_extend_exception_message_no_dot(self):
31+
exc = ValueError("Message without dot")
32+
new_exc = extend_exception_message(exc, "added")
33+
self.assertEqual(str(new_exc), "Message without dot. added")
34+
35+
def test_marshall_exception_trace_basic(self):
36+
try:
37+
raise ValueError("Test")
38+
except ValueError as exc:
39+
trace = marshall_exception_trace(exc)
40+
self.assertIn("ValueError: Test", trace)
41+
self.assertIn("raise ValueError", trace)
42+
43+
def test_marshall_exception_trace_module_not_found(self):
44+
try:
45+
import non_existent_module # noqa: F401
46+
except ModuleNotFoundError as exc:
47+
trace = marshall_exception_trace(exc)
48+
self.assertIn("ModuleNotFoundError", trace)
49+
self.assertNotIn("<frozen importlib._bootstrap>", trace)
50+
51+
def test_marshall_exception_trace_chained_exceptions(self):
52+
try:
53+
try:
54+
raise ValueError("Inner error")
55+
except ValueError as inner:
56+
raise RuntimeError("Outer error") from inner
57+
except RuntimeError as exc:
58+
trace = marshall_exception_trace(exc)
59+
# Outer exception must appear
60+
self.assertIn("RuntimeError: Outer error", trace)
61+
# Inner exception must also appear
62+
self.assertIn("ValueError: Inner error", trace)
63+
# Ensure 'The above exception was the direct cause' appears
64+
self.assertIn("The above exception was the direct cause", trace)
65+
66+
def test_serialize_exception_returns_rpc_exception(self):
67+
try:
68+
raise ValueError("Error for proto")
69+
except ValueError as exc:
70+
result = serialize_exception(exc, MockProtos)
71+
self.assertIsInstance(result, MockProtos.RpcException)
72+
self.assertIn("ValueError", result.message)
73+
self.assertIn("Error for proto", result.message)
74+
self.assertIn("raise ValueError", result.stack_trace)
75+
76+
def test_serialize_exception_as_str_basic(self):
77+
try:
78+
raise RuntimeError("Runtime issue")
79+
except RuntimeError as exc:
80+
result = serialize_exception_as_str(exc)
81+
self.assertIn("RuntimeError: Runtime issue", result)
82+
self.assertIn("Stack Trace:", result)
83+
self.assertIn("raise RuntimeError", result)
84+
85+
def test_serialize_exception_with_unserializable_exception(self):
86+
class BadExc(Exception):
87+
def __str__(self):
88+
raise ValueError("Cannot stringify")
89+
90+
exc = BadExc()
91+
result_str = serialize_exception_as_str(exc)
92+
self.assertIn("Could not serialize original exception message", result_str)
93+
94+
result_proto = serialize_exception(exc, MockProtos)
95+
self.assertIn("Could not serialize original exception message",
96+
result_proto.message)
97+
98+
def test_marshall_exception_trace_sub_exception(self):
99+
# Patch traceback.format_exception to raise inside marshall_exception_trace
100+
original_format_exception = traceback.format_exception
101+
102+
def bad_format(*args, **kwargs):
103+
raise RuntimeError("fail inside traceback")
104+
traceback.format_exception = bad_format
105+
try:
106+
exc = ValueError("test")
107+
result = marshall_exception_trace(exc)
108+
self.assertIn("Could not extract traceback", result)
109+
self.assertIn("RuntimeError", result)
110+
finally:
111+
traceback.format_exception = original_format_exception

runtimes/v2/azure_functions_runtime/utils/tracing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
def extend_exception_message(exc: Exception, msg: str) -> Exception:
77
# Reconstruct exception message
88
# From: ImportModule: no module name
9-
# To: ImportModule: no module name. msg
9+
# To: ImportModule: no module name. msg
1010
old_tb = exc.__traceback__
1111
old_msg = getattr(exc, 'msg', None) or str(exc) or ''
1212
new_msg = (old_msg.rstrip('.') + '. ' + msg).rstrip()

workers/azure_functions_worker/utils/tracing.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
def extend_exception_message(exc: Exception, msg: str) -> Exception:
77
# Reconstruct exception message
88
# From: ImportModule: no module name
9-
# To: ImportModule: no module name. msg
9+
# To: ImportModule: no module name. msg
1010
old_tb = exc.__traceback__
1111
old_msg = getattr(exc, 'msg', None) or str(exc) or ''
1212
new_msg = (old_msg.rstrip('.') + '. ' + msg).rstrip()
@@ -36,4 +36,3 @@ def marshall_exception_trace(exc: Exception) -> str:
3636
except Exception as sub_exc:
3737
return (f'Could not extract traceback. '
3838
f'Sub-exception: {type(sub_exc).__name__}: {str(sub_exc)}')
39-

workers/tests/unittests/test_tracing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import unittest
55
import traceback
66
from azure_functions_worker.utils.tracing import (extend_exception_message,
7-
marshall_exception_trace)
7+
marshall_exception_trace)
88
from azure_functions_worker.dispatcher import Dispatcher
99

1010

0 commit comments

Comments
 (0)