diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py index e0799834c..975f47f60 100644 --- a/netbox/extras/scripts.py +++ b/netbox/extras/scripts.py @@ -326,7 +326,7 @@ class BaseScript: self._current_test = None # Tracks the current test method being run (if any) # Initiate the log - self.logger = logging.getLogger(f"netbox.scripts.{self.__module__}.{self.__class__.__name__}") + self.logger = logging.getLogger(f"netbox.scripts.{self.full_name}") # Declare the placeholder for the current request self.request = None diff --git a/netbox/extras/tests/test_scripts.py b/netbox/extras/tests/test_scripts.py index 01bc84407..e84f1ba72 100644 --- a/netbox/extras/tests/test_scripts.py +++ b/netbox/extras/tests/test_scripts.py @@ -439,3 +439,33 @@ class ScriptModuleLoadingTestCase(TestCase): # The real circuits app must be untouched and remain an importable package self.assertIs(sys.modules['circuits'], circuits) self.assertTrue(hasattr(circuits, '__path__')) + + def test_script_logger_uses_public_module_name(self): + """ + A dynamically loaded script logs to the public netbox.scripts.. namespace. + """ + script_content = ( + b"from extras.scripts import Script\n\n\n" + b"class TestScript(Script):\n pass\n" + ) + + class _Storage: + def open(self, name, mode='rb'): + return io.BytesIO(script_content) + + module = ScriptModule(file_root='scripts', file_path='example.py') + namespaced_key = f'{SCRIPT_MODULE_NAME_PREFIX}example' + self.addCleanup(lambda: sys.modules.pop(namespaced_key, None)) + + with patch('extras.models.mixins.storages') as mock_storages: + mock_storages.__getitem__.return_value = _Storage() + script_class = module.get_module().TestScript + + # This is the name runscript and ScriptJob attach their handlers to + logger_name = f'netbox.scripts.{script_class.full_name}' + script = script_class() + self.assertEqual(script.logger.name, logger_name) + + with self.assertLogs(logger_name, 'INFO') as captured: + script.log_success('Start') + self.assertIn('Start', captured.output[0])