Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhanced Jinja Error Handling and Stack Trace Logging #149

Merged
merged 8 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 19 additions & 24 deletions nornir_nautobot/plugins/tasks/dispatcher/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
from nornir_napalm.plugins.tasks import napalm_configure, napalm_get
from nornir_netmiko.tasks import netmiko_send_command
from nornir_nautobot.exceptions import NornirNautobotException
from nornir_nautobot.utils.helpers import make_folder, is_truthy
from nornir_nautobot.utils.helpers import make_folder, get_stack_trace, is_truthy


_logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -148,6 +149,7 @@ def generate_config(
jinja_filters: Optional[dict] = None,
jinja_env: Optional[jinja2.Environment] = None,
) -> Result:
# pylint: disable=too-many-locals
"""A small wrapper around template_file Nornir task.

Args:
Expand All @@ -173,29 +175,22 @@ def generate_config(
jinja_env=jinja_env,
)[0].result
except NornirSubTaskError as exc:
if isinstance(exc.result.exception, jinja2.exceptions.UndefinedError): # pylint: disable=no-else-raise
error_msg = (
f"`E1010:` There was a jinja2.exceptions.UndefinedError error: ``{str(exc.result.exception)}``"
)
logger.error(error_msg, extra={"object": obj})
raise NornirNautobotException(error_msg)

elif isinstance(exc.result.exception, jinja2.TemplateSyntaxError):
error_msg = (f"`E1011:` There was a jinja2.TemplateSyntaxError error: ``{str(exc.result.exception)}``",)
logger.error(error_msg, extra={"object": obj})
raise NornirNautobotException(error_msg)

elif isinstance(exc.result.exception, jinja2.TemplateNotFound):
error_msg = f"`E1012:` There was an issue finding the template and a jinja2.TemplateNotFound error was raised: ``{str(exc.result.exception)}``"
logger.error(error_msg, extra={"object": obj})
raise NornirNautobotException(error_msg)

elif isinstance(exc.result.exception, jinja2.TemplateError):
error_msg = f"`E1013:` There was an issue general Jinja error: ``{str(exc.result.exception)}``"
logger.error(error_msg, extra={"object": obj})
raise NornirNautobotException(error_msg)

error_msg = f"`E1014:` Failed with an unknown issue. `{exc.result.exception}`"
stack_trace = get_stack_trace(exc.result.exception)

error_mapping = {
jinja2.exceptions.UndefinedError: ("E1010", "Undefined variable in Jinja2 template"),
jinja2.TemplateSyntaxError: ("E1011", "Syntax error in Jinja2 template"),
jinja2.TemplateNotFound: ("E1012", "Jinja2 template not found"),
jinja2.TemplateError: ("E1013", "General Jinja2 template error"),
}

for error, (code, message) in error_mapping.items():
if isinstance(exc.result.exception, error):
error_msg = f"`{code}:` {message} - ``{str(exc.result.exception)}``\n```\n{stack_trace}\n```"
logger.error(error_msg, extra={"object": obj})
raise NornirNautobotException(error_msg)

error_msg = f"`E1014:` Unknown error - `{exc.result.exception}`\n```\n{stack_trace}\n```"
logger.error(error_msg, extra={"object": obj})
raise NornirNautobotException(error_msg)

Expand Down
7 changes: 7 additions & 0 deletions nornir_nautobot/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import logging
import importlib
import traceback

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -33,6 +34,12 @@ def import_string(dotted_path):
return None


def get_stack_trace(exc: Exception) -> str:
"""Converts the provided exception's stack trace into a string."""
stack_trace_lines = traceback.format_exception(type(exc), exc, exc.__traceback__)
return "".join(stack_trace_lines)


def is_truthy(arg):
"""Convert "truthy" strings into Booleans.

Expand Down
Loading