diff --git a/aws_lambda_powertools/logging/formatter.py b/aws_lambda_powertools/logging/formatter.py index f04f7c87e73..a330b043f75 100644 --- a/aws_lambda_powertools/logging/formatter.py +++ b/aws_lambda_powertools/logging/formatter.py @@ -357,19 +357,25 @@ def _extract_log_message(self, log_record: logging.LogRecord) -> dict[str, Any] return message def _serialize_stacktrace(self, log_record: logging.LogRecord) -> LogStackTrace | None: - if log_record.exc_info: + # Check if the first element of exc_info has the __name__ attribute, + # which indicates it is likely an exception class or object. + # See: https://github.com/aws-powertools/powertools-lambda-python/issues/6358 + if isinstance(log_record.exc_info, tuple) and hasattr(log_record.exc_info[0], "__name__"): exception_info: LogStackTrace = { "type": log_record.exc_info[0].__name__, # type: ignore "value": log_record.exc_info[1], # type: ignore "module": log_record.exc_info[1].__class__.__module__, - "frames": [], + "frames": [ + { + "file": fs.filename, + "line": fs.lineno, + "function": fs.name, + "statement": fs.line, + } + for fs in traceback.extract_tb(log_record.exc_info[2]) + ], } - exception_info["frames"] = [ - {"file": fs.filename, "line": fs.lineno, "function": fs.name, "statement": fs.line} - for fs in traceback.extract_tb(log_record.exc_info[2]) - ] - return exception_info return None @@ -387,7 +393,7 @@ def _extract_log_exception(self, log_record: logging.LogRecord) -> tuple[str, st log_record: tuple[str, str] | tuple[None, None] Log record with constant traceback info and exception name """ - if log_record.exc_info: + if isinstance(log_record.exc_info, tuple) and hasattr(log_record.exc_info[0], "__name__"): return self.formatException(log_record.exc_info), log_record.exc_info[0].__name__ # type: ignore return None, None diff --git a/aws_lambda_powertools/utilities/parser/models/appsync.py b/aws_lambda_powertools/utilities/parser/models/appsync.py index fe65d932332..a483f597857 100644 --- a/aws_lambda_powertools/utilities/parser/models/appsync.py +++ b/aws_lambda_powertools/utilities/parser/models/appsync.py @@ -1,6 +1,8 @@ -from typing import Optional, List, Dict, Union, Any +from typing import Any, Dict, List, Optional, Union + from pydantic import BaseModel + class AppSyncIamIdentity(BaseModel): accountId: str cognitoIdentityPoolId: Optional[str] @@ -67,4 +69,4 @@ class AppSyncResolverEventModel(BaseModel): stash: Dict[str, Any] -AppSyncBatchResolverEventModel = List[AppSyncResolverEventModel] \ No newline at end of file +AppSyncBatchResolverEventModel = List[AppSyncResolverEventModel] diff --git a/tests/functional/logger/required_dependencies/test_logger.py b/tests/functional/logger/required_dependencies/test_logger.py index a508209b594..a33c59521ed 100644 --- a/tests/functional/logger/required_dependencies/test_logger.py +++ b/tests/functional/logger/required_dependencies/test_logger.py @@ -638,6 +638,19 @@ def test_logger_exception_extract_exception_name(stdout, service_name): assert "ValueError" == log["exception_name"] +def test_logger_exception_should_not_fail_with_exception_block(stdout, service_name): + # GIVEN Logger is initialized + logger = Logger(service=service_name, stream=stdout) + + # WHEN calling a logger.exception with a ValueError and outside of a try/except block + logger.exception("Received an exception") + + # THEN the log output should not contain "exception_name" or "exception" and not fail + log = capture_logging_output(stdout) + assert "exception_name" not in log + assert "exception" not in log + + def test_logger_set_correlation_id(lambda_context, stdout, service_name): # GIVEN logger = Logger(service=service_name, stream=stdout) diff --git a/tests/unit/parser/_pydantic/test_appsync.py b/tests/unit/parser/_pydantic/test_appsync.py index b8a57eaa7c3..06b73621445 100644 --- a/tests/unit/parser/_pydantic/test_appsync.py +++ b/tests/unit/parser/_pydantic/test_appsync.py @@ -1,9 +1,10 @@ import pytest -from aws_lambda_powertools.utilities.parser import parse, ValidationError +from aws_lambda_powertools.utilities.parser import ValidationError, parse from aws_lambda_powertools.utilities.parser.models import AppSyncResolverEventModel from tests.functional.utils import load_event + def test_appsync_event_model_parses_successfully(): """ Validate that a valid AppSync resolver event is correctly parsed by the model. @@ -24,4 +25,4 @@ def test_appsync_event_model_invalid_payload_raises(): """ invalid_event = {"invalid": "event"} with pytest.raises(ValidationError): - parse(event=invalid_event, model=AppSyncResolverEventModel) \ No newline at end of file + parse(event=invalid_event, model=AppSyncResolverEventModel)