From 7c7c5cc99345d183a382292ba792e1d4b1bcd537 Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Wed, 15 Oct 2025 12:16:29 -0500 Subject: [PATCH 1/2] fix for 313 ai logging --- .../utils/wrappers.py | 1 + runtimes/v1/tests/unittests/test_wrappers.py | 119 ++++++++++++++++++ .../azure_functions_runtime/utils/wrappers.py | 1 + runtimes/v2/tests/unittests/test_wrappers.py | 118 +++++++++++++++++ 4 files changed, 239 insertions(+) create mode 100644 runtimes/v1/tests/unittests/test_wrappers.py create mode 100644 runtimes/v2/tests/unittests/test_wrappers.py diff --git a/runtimes/v1/azure_functions_runtime_v1/utils/wrappers.py b/runtimes/v1/azure_functions_runtime_v1/utils/wrappers.py index 5a1caca0c..35f900110 100644 --- a/runtimes/v1/azure_functions_runtime_v1/utils/wrappers.py +++ b/runtimes/v1/azure_functions_runtime_v1/utils/wrappers.py @@ -47,6 +47,7 @@ def call(*args, **kwargs): except expt_type as e: if debug_logs is not None: logger.error(debug_logs) + logger.exception("Error: %s, %s", e, message) raise extend_exception_message(e, message) return call return decorate diff --git a/runtimes/v1/tests/unittests/test_wrappers.py b/runtimes/v1/tests/unittests/test_wrappers.py new file mode 100644 index 000000000..bc055c679 --- /dev/null +++ b/runtimes/v1/tests/unittests/test_wrappers.py @@ -0,0 +1,119 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import unittest +from unittest.mock import patch, Mock + +from azure_functions_runtime_v1.utils import wrappers + + +class TestEnableFeatureBy(unittest.TestCase): + + @patch("wrappers.is_envvar_true", return_value=True) + def test_enable_feature_by_flag_true(self, mock_true): + func = Mock(return_value="enabled") + decorated = wrappers.enable_feature_by("FLAG")(func) + result = decorated() + self.assertEqual(result, "enabled") + func.assert_called_once() + + @patch("wrappers.is_envvar_true", return_value=False) + @patch("wrappers.is_envvar_false", return_value=True) + def test_enable_feature_by_flag_false(self, mock_false, mock_true): + func = Mock(return_value="enabled") + decorated = wrappers.enable_feature_by("FLAG", default="default")(func) + result = decorated() + self.assertEqual(result, "default") + func.assert_not_called() + + @patch("wrappers.is_envvar_true", return_value=False) + @patch("wrappers.is_envvar_false", return_value=False) + def test_enable_feature_by_flag_default_true(self, mock_false, mock_true): + func = Mock(return_value="enabled") + decorated = wrappers.enable_feature_by("FLAG", + default="default", + flag_default=True)(func) + result = decorated() + self.assertEqual(result, "enabled") + func.assert_called_once() + + +class TestDisableFeatureBy(unittest.TestCase): + + @patch("wrappers.is_envvar_true", return_value=True) + def test_disable_feature_by_flag_true(self, mock_true): + func = Mock(return_value="should_not_run") + decorated = wrappers.disable_feature_by("FLAG", default="default")(func) + result = decorated() + self.assertEqual(result, "default") + func.assert_not_called() + + @patch("wrappers.is_envvar_true", return_value=False) + @patch("wrappers.is_envvar_false", return_value=True) + def test_disable_feature_by_flag_false(self, mock_false, mock_true): + func = Mock(return_value="enabled") + decorated = wrappers.disable_feature_by("FLAG", default="default")(func) + result = decorated() + self.assertEqual(result, "enabled") + func.assert_called_once() + + @patch("wrappers.is_envvar_true", return_value=False) + @patch("wrappers.is_envvar_false", return_value=False) + def test_disable_feature_by_flag_default_true(self, mock_false, mock_true): + func = Mock(return_value="enabled") + decorated = wrappers.disable_feature_by("FLAG", + default="default", + flag_default=True)(func) + result = decorated() + self.assertEqual(result, "default") + func.assert_not_called() + + +class TestAttachMessageToException(unittest.TestCase): + + @patch("wrappers.logger") + @patch("wrappers.extend_exception_message") + def test_attach_message_to_exception_catches_and_logs(self, + mock_extend, + mock_logger): + mock_extend.side_effect = lambda e, msg: Exception(f"{e} {msg}") + + def faulty_func(): + raise ValueError("original error") + + decorated = wrappers.attach_message_to_exception(ValueError, + "extra info")(faulty_func) + + with self.assertRaises(Exception) as cm: + decorated() + + self.assertIn("extra info", str(cm.exception)) + mock_logger.exception.assert_called_once() + mock_extend.assert_called_once() + + @patch("wrappers.logger") + @patch("wrappers.extend_exception_message") + def test_attach_message_to_exception_with_debug_logs(self, + mock_extend, + mock_logger): + mock_extend.side_effect = lambda e, msg: Exception(f"{e} {msg}") + + def faulty_func(): + raise RuntimeError("oops") + + decorated = wrappers.attach_message_to_exception( + RuntimeError, + "debug message", + debug_logs="Debug info")(faulty_func) + + with self.assertRaises(Exception): + decorated() + + mock_logger.error.assert_called_with("Debug info") + mock_logger.exception.assert_called_once() + + def test_attach_message_to_exception_no_exception(self): + func = Mock(return_value="ok") + decorated = wrappers.attach_message_to_exception(ValueError, "extra")(func) + result = decorated() + self.assertEqual(result, "ok") + func.assert_called_once() diff --git a/runtimes/v2/azure_functions_runtime/utils/wrappers.py b/runtimes/v2/azure_functions_runtime/utils/wrappers.py index c6b7dc6fb..56c3c089c 100644 --- a/runtimes/v2/azure_functions_runtime/utils/wrappers.py +++ b/runtimes/v2/azure_functions_runtime/utils/wrappers.py @@ -47,6 +47,7 @@ def call(*args, **kwargs): except expt_type as e: if debug_logs is not None: logger.error(debug_logs) + logger.exception("Error: %s, %s", e, message) raise extend_exception_message(e, message) return call return decorate diff --git a/runtimes/v2/tests/unittests/test_wrappers.py b/runtimes/v2/tests/unittests/test_wrappers.py new file mode 100644 index 000000000..eccad4106 --- /dev/null +++ b/runtimes/v2/tests/unittests/test_wrappers.py @@ -0,0 +1,118 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import unittest +from unittest.mock import patch, Mock + +from azure_functions_runtime.utils import wrappers + + +class TestEnableFeatureBy(unittest.TestCase): + + @patch("wrappers.is_envvar_true", return_value=True) + def test_enable_feature_by_flag_true(self, mock_true): + func = Mock(return_value="enabled") + decorated = wrappers.enable_feature_by("FLAG")(func) + result = decorated() + self.assertEqual(result, "enabled") + func.assert_called_once() + + @patch("wrappers.is_envvar_true", return_value=False) + @patch("wrappers.is_envvar_false", return_value=True) + def test_enable_feature_by_flag_false(self, mock_false, mock_true): + func = Mock(return_value="enabled") + decorated = wrappers.enable_feature_by("FLAG", default="default")(func) + result = decorated() + self.assertEqual(result, "default") + func.assert_not_called() + + @patch("wrappers.is_envvar_true", return_value=False) + @patch("wrappers.is_envvar_false", return_value=False) + def test_enable_feature_by_flag_default_true(self, mock_false, mock_true): + func = Mock(return_value="enabled") + decorated = wrappers.enable_feature_by("FLAG", + default="default", + flag_default=True)(func) + result = decorated() + self.assertEqual(result, "enabled") + func.assert_called_once() + + +class TestDisableFeatureBy(unittest.TestCase): + + @patch("wrappers.is_envvar_true", return_value=True) + def test_disable_feature_by_flag_true(self, mock_true): + func = Mock(return_value="should_not_run") + decorated = wrappers.disable_feature_by("FLAG", default="default")(func) + result = decorated() + self.assertEqual(result, "default") + func.assert_not_called() + + @patch("wrappers.is_envvar_true", return_value=False) + @patch("wrappers.is_envvar_false", return_value=True) + def test_disable_feature_by_flag_false(self, mock_false, mock_true): + func = Mock(return_value="enabled") + decorated = wrappers.disable_feature_by("FLAG", default="default")(func) + result = decorated() + self.assertEqual(result, "enabled") + func.assert_called_once() + + @patch("wrappers.is_envvar_true", return_value=False) + @patch("wrappers.is_envvar_false", return_value=False) + def test_disable_feature_by_flag_default_true(self, mock_false, mock_true): + func = Mock(return_value="enabled") + decorated = wrappers.disable_feature_by("FLAG", + default="default", + flag_default=True)(func) + result = decorated() + self.assertEqual(result, "default") + func.assert_not_called() + + +class TestAttachMessageToException(unittest.TestCase): + + @patch("wrappers.logger") + @patch("wrappers.extend_exception_message") + def test_attach_message_to_exception_catches_and_logs( + self, mock_extend, mock_logger): + mock_extend.side_effect = lambda e, msg: Exception(f"{e} {msg}") + + def faulty_func(): + raise ValueError("original error") + + decorated = wrappers.attach_message_to_exception( + ValueError, "extra info")(faulty_func) + + with self.assertRaises(Exception) as cm: + decorated() + + self.assertIn("extra info", str(cm.exception)) + mock_logger.exception.assert_called_once() + mock_extend.assert_called_once() + + @patch("wrappers.logger") + @patch("wrappers.extend_exception_message") + def test_attach_message_to_exception_with_debug_logs(self, + mock_extend, + mock_logger): + mock_extend.side_effect = lambda e, msg: Exception(f"{e} {msg}") + + def faulty_func(): + raise RuntimeError("oops") + + decorated = wrappers.attach_message_to_exception( + RuntimeError, + "debug message", + debug_logs="Debug info")(faulty_func) + + with self.assertRaises(Exception): + decorated() + + mock_logger.error.assert_called_with("Debug info") + mock_logger.exception.assert_called_once() + + def test_attach_message_to_exception_no_exception(self): + func = Mock(return_value="ok") + decorated = wrappers.attach_message_to_exception(ValueError, "extra")(func) + result = decorated() + self.assertEqual(result, "ok") + func.assert_called_once() From 7f481f0b5ef86c122e4569cb1785b3cfde5a1b44 Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Wed, 15 Oct 2025 13:26:02 -0500 Subject: [PATCH 2/2] delete duplicated tests --- runtimes/v1/tests/unittests/test_wrappers.py | 119 ------------------- runtimes/v2/tests/unittests/test_wrappers.py | 118 ------------------ 2 files changed, 237 deletions(-) delete mode 100644 runtimes/v1/tests/unittests/test_wrappers.py delete mode 100644 runtimes/v2/tests/unittests/test_wrappers.py diff --git a/runtimes/v1/tests/unittests/test_wrappers.py b/runtimes/v1/tests/unittests/test_wrappers.py deleted file mode 100644 index bc055c679..000000000 --- a/runtimes/v1/tests/unittests/test_wrappers.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -import unittest -from unittest.mock import patch, Mock - -from azure_functions_runtime_v1.utils import wrappers - - -class TestEnableFeatureBy(unittest.TestCase): - - @patch("wrappers.is_envvar_true", return_value=True) - def test_enable_feature_by_flag_true(self, mock_true): - func = Mock(return_value="enabled") - decorated = wrappers.enable_feature_by("FLAG")(func) - result = decorated() - self.assertEqual(result, "enabled") - func.assert_called_once() - - @patch("wrappers.is_envvar_true", return_value=False) - @patch("wrappers.is_envvar_false", return_value=True) - def test_enable_feature_by_flag_false(self, mock_false, mock_true): - func = Mock(return_value="enabled") - decorated = wrappers.enable_feature_by("FLAG", default="default")(func) - result = decorated() - self.assertEqual(result, "default") - func.assert_not_called() - - @patch("wrappers.is_envvar_true", return_value=False) - @patch("wrappers.is_envvar_false", return_value=False) - def test_enable_feature_by_flag_default_true(self, mock_false, mock_true): - func = Mock(return_value="enabled") - decorated = wrappers.enable_feature_by("FLAG", - default="default", - flag_default=True)(func) - result = decorated() - self.assertEqual(result, "enabled") - func.assert_called_once() - - -class TestDisableFeatureBy(unittest.TestCase): - - @patch("wrappers.is_envvar_true", return_value=True) - def test_disable_feature_by_flag_true(self, mock_true): - func = Mock(return_value="should_not_run") - decorated = wrappers.disable_feature_by("FLAG", default="default")(func) - result = decorated() - self.assertEqual(result, "default") - func.assert_not_called() - - @patch("wrappers.is_envvar_true", return_value=False) - @patch("wrappers.is_envvar_false", return_value=True) - def test_disable_feature_by_flag_false(self, mock_false, mock_true): - func = Mock(return_value="enabled") - decorated = wrappers.disable_feature_by("FLAG", default="default")(func) - result = decorated() - self.assertEqual(result, "enabled") - func.assert_called_once() - - @patch("wrappers.is_envvar_true", return_value=False) - @patch("wrappers.is_envvar_false", return_value=False) - def test_disable_feature_by_flag_default_true(self, mock_false, mock_true): - func = Mock(return_value="enabled") - decorated = wrappers.disable_feature_by("FLAG", - default="default", - flag_default=True)(func) - result = decorated() - self.assertEqual(result, "default") - func.assert_not_called() - - -class TestAttachMessageToException(unittest.TestCase): - - @patch("wrappers.logger") - @patch("wrappers.extend_exception_message") - def test_attach_message_to_exception_catches_and_logs(self, - mock_extend, - mock_logger): - mock_extend.side_effect = lambda e, msg: Exception(f"{e} {msg}") - - def faulty_func(): - raise ValueError("original error") - - decorated = wrappers.attach_message_to_exception(ValueError, - "extra info")(faulty_func) - - with self.assertRaises(Exception) as cm: - decorated() - - self.assertIn("extra info", str(cm.exception)) - mock_logger.exception.assert_called_once() - mock_extend.assert_called_once() - - @patch("wrappers.logger") - @patch("wrappers.extend_exception_message") - def test_attach_message_to_exception_with_debug_logs(self, - mock_extend, - mock_logger): - mock_extend.side_effect = lambda e, msg: Exception(f"{e} {msg}") - - def faulty_func(): - raise RuntimeError("oops") - - decorated = wrappers.attach_message_to_exception( - RuntimeError, - "debug message", - debug_logs="Debug info")(faulty_func) - - with self.assertRaises(Exception): - decorated() - - mock_logger.error.assert_called_with("Debug info") - mock_logger.exception.assert_called_once() - - def test_attach_message_to_exception_no_exception(self): - func = Mock(return_value="ok") - decorated = wrappers.attach_message_to_exception(ValueError, "extra")(func) - result = decorated() - self.assertEqual(result, "ok") - func.assert_called_once() diff --git a/runtimes/v2/tests/unittests/test_wrappers.py b/runtimes/v2/tests/unittests/test_wrappers.py deleted file mode 100644 index eccad4106..000000000 --- a/runtimes/v2/tests/unittests/test_wrappers.py +++ /dev/null @@ -1,118 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -import unittest -from unittest.mock import patch, Mock - -from azure_functions_runtime.utils import wrappers - - -class TestEnableFeatureBy(unittest.TestCase): - - @patch("wrappers.is_envvar_true", return_value=True) - def test_enable_feature_by_flag_true(self, mock_true): - func = Mock(return_value="enabled") - decorated = wrappers.enable_feature_by("FLAG")(func) - result = decorated() - self.assertEqual(result, "enabled") - func.assert_called_once() - - @patch("wrappers.is_envvar_true", return_value=False) - @patch("wrappers.is_envvar_false", return_value=True) - def test_enable_feature_by_flag_false(self, mock_false, mock_true): - func = Mock(return_value="enabled") - decorated = wrappers.enable_feature_by("FLAG", default="default")(func) - result = decorated() - self.assertEqual(result, "default") - func.assert_not_called() - - @patch("wrappers.is_envvar_true", return_value=False) - @patch("wrappers.is_envvar_false", return_value=False) - def test_enable_feature_by_flag_default_true(self, mock_false, mock_true): - func = Mock(return_value="enabled") - decorated = wrappers.enable_feature_by("FLAG", - default="default", - flag_default=True)(func) - result = decorated() - self.assertEqual(result, "enabled") - func.assert_called_once() - - -class TestDisableFeatureBy(unittest.TestCase): - - @patch("wrappers.is_envvar_true", return_value=True) - def test_disable_feature_by_flag_true(self, mock_true): - func = Mock(return_value="should_not_run") - decorated = wrappers.disable_feature_by("FLAG", default="default")(func) - result = decorated() - self.assertEqual(result, "default") - func.assert_not_called() - - @patch("wrappers.is_envvar_true", return_value=False) - @patch("wrappers.is_envvar_false", return_value=True) - def test_disable_feature_by_flag_false(self, mock_false, mock_true): - func = Mock(return_value="enabled") - decorated = wrappers.disable_feature_by("FLAG", default="default")(func) - result = decorated() - self.assertEqual(result, "enabled") - func.assert_called_once() - - @patch("wrappers.is_envvar_true", return_value=False) - @patch("wrappers.is_envvar_false", return_value=False) - def test_disable_feature_by_flag_default_true(self, mock_false, mock_true): - func = Mock(return_value="enabled") - decorated = wrappers.disable_feature_by("FLAG", - default="default", - flag_default=True)(func) - result = decorated() - self.assertEqual(result, "default") - func.assert_not_called() - - -class TestAttachMessageToException(unittest.TestCase): - - @patch("wrappers.logger") - @patch("wrappers.extend_exception_message") - def test_attach_message_to_exception_catches_and_logs( - self, mock_extend, mock_logger): - mock_extend.side_effect = lambda e, msg: Exception(f"{e} {msg}") - - def faulty_func(): - raise ValueError("original error") - - decorated = wrappers.attach_message_to_exception( - ValueError, "extra info")(faulty_func) - - with self.assertRaises(Exception) as cm: - decorated() - - self.assertIn("extra info", str(cm.exception)) - mock_logger.exception.assert_called_once() - mock_extend.assert_called_once() - - @patch("wrappers.logger") - @patch("wrappers.extend_exception_message") - def test_attach_message_to_exception_with_debug_logs(self, - mock_extend, - mock_logger): - mock_extend.side_effect = lambda e, msg: Exception(f"{e} {msg}") - - def faulty_func(): - raise RuntimeError("oops") - - decorated = wrappers.attach_message_to_exception( - RuntimeError, - "debug message", - debug_logs="Debug info")(faulty_func) - - with self.assertRaises(Exception): - decorated() - - mock_logger.error.assert_called_with("Debug info") - mock_logger.exception.assert_called_once() - - def test_attach_message_to_exception_no_exception(self): - func = Mock(return_value="ok") - decorated = wrappers.attach_message_to_exception(ValueError, "extra")(func) - result = decorated() - self.assertEqual(result, "ok") - func.assert_called_once()