Skip to content

Commit

Permalink
AwsLambdaInstrumentor handles and re-raises handler function exception (
Browse files Browse the repository at this point in the history
  • Loading branch information
tammy-baylis-swi authored Feb 22, 2024
1 parent 2518a4a commit efb327d
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#2136](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2136))
- `opentelemetry-resource-detector-azure` Suppress instrumentation for `urllib` call
([#2178](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2178))
- AwsLambdaInstrumentor handles and re-raises function exception ([#2245](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2245))

## Version 1.22.0/0.43b0 (2023-12-14)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def custom_event_context_extractor(lambda_event):
get_tracer_provider,
)
from opentelemetry.trace.propagation import get_current_span
from opentelemetry.trace.status import Status, StatusCode

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -278,6 +279,7 @@ def _set_api_gateway_v2_proxy_attributes(
return span


# pylint: disable=too-many-statements
def _instrument(
wrapped_module_name,
wrapped_function_name,
Expand All @@ -287,6 +289,8 @@ def _instrument(
disable_aws_context_propagation: bool = False,
meter_provider: MeterProvider = None,
):
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
def _instrumented_lambda_handler_call( # noqa pylint: disable=too-many-branches
call_wrapped, instance, args, kwargs
):
Expand Down Expand Up @@ -350,7 +354,13 @@ def _instrumented_lambda_handler_call( # noqa pylint: disable=too-many-branches
lambda_context.aws_request_id,
)

result = call_wrapped(*args, **kwargs)
exception = None
try:
result = call_wrapped(*args, **kwargs)
except Exception as exc: # pylint: disable=W0703
exception = exc
span.set_status(Status(StatusCode.ERROR))
span.record_exception(exception)

# If the request came from an API Gateway, extract http attributes from the event
# https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/instrumentation/aws-lambda.md#api-gateway
Expand Down Expand Up @@ -398,6 +408,9 @@ def _instrumented_lambda_handler_call( # noqa pylint: disable=too-many-branches
"MeterProvider was missing `force_flush` method. This is necessary in case of a Lambda freeze and would exist in the OTel SDK implementation."
)

if exception is not None:
raise exception.with_traceback(exception.__traceback__)

return result

wrap_function_wrapper(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ def handler(event, context):

def rest_api_handler(event, context):
return {"statusCode": 200, "body": "200 ok"}


def handler_exc(event, context):
raise Exception("500 internal server error")
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from opentelemetry.semconv.resource import ResourceAttributes
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.test.test_base import TestBase
from opentelemetry.trace import NoOpTracerProvider, SpanKind
from opentelemetry.trace import NoOpTracerProvider, SpanKind, StatusCode
from opentelemetry.trace.propagation.tracecontext import (
TraceContextTextMapPropagator,
)
Expand Down Expand Up @@ -410,6 +410,27 @@ def test_lambda_handles_list_event(self):

assert spans

def test_lambda_handles_handler_exception(self):
exc_env_patch = mock.patch.dict(
"os.environ",
{_HANDLER: "tests.mocks.lambda_function.handler_exc"},
)
exc_env_patch.start()
AwsLambdaInstrumentor().instrument()
# instrumentor re-raises the exception
with self.assertRaises(Exception):
mock_execute_lambda()

spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1)
span = spans[0]
self.assertEqual(span.status.status_code, StatusCode.ERROR)
self.assertEqual(len(span.events), 1)
event = span.events[0]
self.assertEqual(event.name, "exception")

exc_env_patch.stop()

def test_uninstrument(self):
AwsLambdaInstrumentor().instrument()

Expand Down

0 comments on commit efb327d

Please sign in to comment.