Skip to content

Commit decc33b

Browse files
follow aws logic of handler importing on auto instrument. Use the runtime's logic if possible (#215)
1 parent 6ce3562 commit decc33b

File tree

4 files changed

+135
-52
lines changed

4 files changed

+135
-52
lines changed
Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,34 @@
1-
import warnings
21
import os
3-
import importlib
42

5-
from lumigo_tracer import lumigo_tracer
3+
try:
4+
# Try to import AWS's current _get_handler logic
5+
from bootstrap import _get_handler as aws_get_handler
6+
except Exception:
7+
# Import a snapshot of _get_handler from python38 runtime
8+
from lumigo_tracer.libs.bootstrap import _get_handler as aws_get_handler
69

7-
with warnings.catch_warnings():
8-
warnings.filterwarnings("ignore", category=DeprecationWarning)
9-
import imp
10+
from lumigo_tracer import lumigo_tracer
1011

1112
ORIGINAL_HANDLER_KEY = "LUMIGO_ORIGINAL_HANDLER"
1213

1314

14-
def parse_handler():
15+
def get_original_handler():
1516
try:
16-
module_name, unit_name = os.environ[ORIGINAL_HANDLER_KEY].rsplit(".", 1)
17-
file_handle, pathname, desc = imp.find_module(module_name)
17+
return aws_get_handler(os.environ[ORIGINAL_HANDLER_KEY])
1818
except KeyError:
1919
raise Exception(
2020
"Could not find the original handler. Please contact Lumigo for more information."
2121
) from None
22-
except ValueError as e:
23-
raise ValueError(
24-
f"Runtime.MalformedHandlerName: Bad handler '{os.environ[ORIGINAL_HANDLER_KEY]}': {str(e)}"
25-
) from None
26-
return module_name, unit_name, file_handle, pathname, desc
2722

2823

2924
@lumigo_tracer()
3025
def _handler(*args, **kwargs):
31-
handler_module = ""
32-
try:
33-
handler_module, unit_name, file_handle, pathname, desc = parse_handler()
34-
original_module = imp.load_module(handler_module, file_handle, pathname, desc)
35-
except ImportError as e:
36-
raise ImportError(
37-
f"Runtime.ImportModuleError: Unable to import module '{handler_module}': {str(e)}"
38-
) from None
39-
except SyntaxError as e:
40-
raise SyntaxError(
41-
f"Runtime.UserCodeSyntaxError: Syntax error in module '{handler_module}': {str(e)}"
42-
) from None
43-
try:
44-
original_handler = getattr(original_module, unit_name)
45-
except AttributeError:
46-
raise Exception(
47-
f"Runtime.HandlerNotFound: Handler '{unit_name}' missing on module '{handler_module}'"
48-
) from None
26+
original_handler = get_original_handler()
4927
return original_handler(*args, **kwargs)
5028

5129

5230
try:
5331
# import handler during runtime initialization, as usual.
54-
importlib.import_module(parse_handler()[0])
32+
get_original_handler()
5533
except Exception:
5634
pass
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""
2+
Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
"""
4+
5+
import warnings
6+
7+
from lumigo_tracer.libs.lambda_runtime_exception import FaultException
8+
9+
10+
with warnings.catch_warnings():
11+
warnings.filterwarnings("ignore", category=DeprecationWarning)
12+
import imp
13+
14+
ERROR_LOG_LINE_TERMINATE = "\r"
15+
ERROR_LOG_IDENT = "\u00a0" # NO-BREAK SPACE U+00A0
16+
17+
18+
def _get_handler(handler):
19+
try:
20+
(modname, fname) = handler.rsplit(".", 1)
21+
except ValueError as e:
22+
fault = FaultException(
23+
FaultException.MALFORMED_HANDLER_NAME, "Bad handler '{}': {}".format(handler, str(e))
24+
)
25+
return make_fault_handler(fault)
26+
27+
file_handle, pathname, desc = None, None, None
28+
try:
29+
# Recursively loading handler in nested directories
30+
for segment in modname.split("."):
31+
if pathname is not None:
32+
pathname = [pathname]
33+
file_handle, pathname, desc = imp.find_module(segment, pathname)
34+
if file_handle is None:
35+
module_type = desc[2]
36+
if module_type == imp.C_BUILTIN:
37+
fault = FaultException(
38+
FaultException.BUILT_IN_MODULE_CONFLICT,
39+
"Cannot use built-in module {} as a handler module".format(modname),
40+
)
41+
request_handler = make_fault_handler(fault)
42+
return request_handler
43+
m = imp.load_module(modname, file_handle, pathname, desc)
44+
except ImportError as e:
45+
fault = FaultException(
46+
FaultException.IMPORT_MODULE_ERROR,
47+
"Unable to import module '{}': {}".format(modname, str(e)),
48+
)
49+
request_handler = make_fault_handler(fault)
50+
return request_handler
51+
except SyntaxError as e:
52+
trace = [' File "%s" Line %s\n %s' % (e.filename, e.lineno, e.text)]
53+
fault = FaultException(
54+
FaultException.USER_CODE_SYNTAX_ERROR,
55+
"Syntax error in module '{}': {}".format(modname, str(e)),
56+
trace,
57+
)
58+
request_handler = make_fault_handler(fault)
59+
return request_handler
60+
finally:
61+
if file_handle is not None:
62+
file_handle.close()
63+
64+
try:
65+
request_handler = getattr(m, fname)
66+
except AttributeError:
67+
fault = FaultException(
68+
FaultException.HANDLER_NOT_FOUND,
69+
"Handler '{}' missing on module '{}'".format(fname, modname),
70+
None,
71+
)
72+
request_handler = make_fault_handler(fault)
73+
return request_handler
74+
75+
76+
def make_fault_handler(fault):
77+
def result(*args):
78+
raise fault
79+
80+
return result
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""
2+
Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
"""
4+
5+
6+
class FaultException(Exception):
7+
MARSHAL_ERROR = "Runtime.MarshalError"
8+
UNMARSHAL_ERROR = "Runtime.UnmarshalError"
9+
USER_CODE_SYNTAX_ERROR = "Runtime.UserCodeSyntaxError"
10+
HANDLER_NOT_FOUND = "Runtime.HandlerNotFound"
11+
IMPORT_MODULE_ERROR = "Runtime.ImportModuleError"
12+
BUILT_IN_MODULE_CONFLICT = "Runtime.BuiltInModuleConflict"
13+
MALFORMED_HANDLER_NAME = "Runtime.MalformedHandlerName"
14+
LAMBDA_CONTEXT_UNMARSHAL_ERROR = "Runtime.LambdaContextUnmarshalError"
15+
16+
def __init__(self, exception_type, msg, trace=None):
17+
self.msg = msg
18+
self.exception_type = exception_type
19+
self.trace = trace

src/test/unit/test_auto_instrument_handler.py

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,46 +6,52 @@
66

77
import pytest
88
from lumigo_tracer.auto_instrument_handler import _handler, ORIGINAL_HANDLER_KEY
9+
from lumigo_tracer.libs.lambda_runtime_exception import FaultException
910

1011

1112
def abc(*args, **kwargs):
1213
return {"hello": "world"}
1314

1415

15-
def test_happy_flow(monkeypatch):
16+
def test_happy_flow(monkeypatch, context):
1617
monkeypatch.setenv(ORIGINAL_HANDLER_KEY, "test_auto_instrument_handler.abc")
17-
assert _handler({}, {}) == {"hello": "world"}
18+
assert _handler({}, context) == {"hello": "world"}
1819

1920

20-
def test_hierarchy_happy_flow(monkeypatch):
21+
def test_hierarchy_happy_flow(monkeypatch, context):
2122
monkeypatch.setenv(ORIGINAL_HANDLER_KEY, "lumigo_tracer/test_module/test.handler")
22-
assert _handler({}, {}) == {"hello": "world"}
23+
assert _handler({}, context) == {"hello": "world"}
2324

2425

25-
def test_import_error(monkeypatch):
26+
def test_hierarchy_happy_flow_with_dots(monkeypatch, context):
27+
monkeypatch.setenv(ORIGINAL_HANDLER_KEY, "lumigo_tracer.test_module.test.handler")
28+
assert _handler({}, context) == {"hello": "world"}
29+
30+
31+
def test_import_error(monkeypatch, context):
2632
monkeypatch.setenv(ORIGINAL_HANDLER_KEY, "blabla.not.exists")
2733

2834
try:
29-
_handler({}, {})
30-
except ImportError as e:
35+
_handler({}, context)
36+
except FaultException as e:
3137
# Note: We're not using pytest.raises in order to get the exception context
3238
assert "Runtime.ImportModuleError" in str(e)
3339
assert "another exception occurred" not in traceback.format_exc()
3440
else:
3541
assert False
3642

3743

38-
def test_no_env_handler_error(monkeypatch):
44+
def test_no_env_handler_error(monkeypatch, context):
3945
monkeypatch.delenv(ORIGINAL_HANDLER_KEY, None)
4046

4147
with pytest.raises(Exception) as e:
42-
_handler({}, {})
48+
_handler({}, context)
4349
assert "Could not find the original handler" in str(e.value)
4450

4551

4652
def test_error_in_original_handler_no_extra_exception_log(monkeypatch, context):
4753
monkeypatch.setattr(imp, "load_module", mock.Mock(side_effect=ZeroDivisionError))
48-
monkeypatch.setenv(ORIGINAL_HANDLER_KEY, "sys.exit")
54+
monkeypatch.setenv(ORIGINAL_HANDLER_KEY, "lumigo_tracer.test_module.test.handler")
4955

5056
try:
5157
_handler({}, context)
@@ -58,37 +64,37 @@ def test_error_in_original_handler_no_extra_exception_log(monkeypatch, context):
5864

5965
def test_error_in_original_handler_syntax_error(monkeypatch, context):
6066
monkeypatch.setattr(imp, "load_module", mock.Mock(side_effect=SyntaxError))
61-
monkeypatch.setenv(ORIGINAL_HANDLER_KEY, "sys.exit")
67+
monkeypatch.setenv(ORIGINAL_HANDLER_KEY, "lumigo_tracer.test_module.test.handler")
6268

6369
try:
6470
_handler({}, context)
65-
except SyntaxError as e:
71+
except FaultException as e:
6672
# Note: We're not using pytest.raises in order to get the exception context
6773
assert "Runtime.UserCodeSyntaxError" in str(e)
6874
assert "another exception occurred" not in traceback.format_exc()
6975
else:
7076
assert False
7177

7278

73-
def test_handler_bad_format(monkeypatch):
79+
def test_handler_bad_format(monkeypatch, context):
7480
monkeypatch.setenv(ORIGINAL_HANDLER_KEY, "no_method")
7581

7682
try:
77-
_handler({}, {})
78-
except ValueError as e:
83+
_handler({}, context)
84+
except FaultException as e:
7985
# Note: We're not using pytest.raises in order to get the exception context
8086
assert "Runtime.MalformedHandlerName" in str(e)
8187
assert "another exception occurred" not in traceback.format_exc()
8288
else:
8389
assert False
8490

8591

86-
def test_handler_not_found(monkeypatch):
87-
monkeypatch.setenv(ORIGINAL_HANDLER_KEY, "sys.not_found")
92+
def test_handler_not_found(monkeypatch, context):
93+
monkeypatch.setenv(ORIGINAL_HANDLER_KEY, "lumigo_tracer.not_found")
8894

8995
try:
90-
_handler({}, {})
91-
except Exception as e:
96+
_handler({}, context)
97+
except FaultException as e:
9298
# Note: We're not using pytest.raises in order to get the exception context
9399
assert "Runtime.HandlerNotFound" in str(e)
94100
assert "another exception occurred" not in traceback.format_exc()

0 commit comments

Comments
 (0)