Skip to content

Commit 0c8b405

Browse files
authored
Merge pull request #46 from DataDog/tian.chu/fix-dup-enhanced-metrics
Fix duplicate enhanced metrics
2 parents dc8a95a + 27fa5e0 commit 0c8b405

File tree

2 files changed

+66
-10
lines changed

2 files changed

+66
-10
lines changed

datadog_lambda/wrapper.py

+37-10
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,39 @@ def my_lambda_handle(event, context):
3838
"""
3939

4040

41+
class _NoopDecorator(object):
42+
43+
def __init__(self, func):
44+
self.func = func
45+
46+
def __call__(self, *args, **kwargs):
47+
return self.func(*args, **kwargs)
48+
49+
4150
class _LambdaDecorator(object):
4251
"""
4352
Decorator to automatically initialize Datadog API client, flush metrics,
4453
and extracts/injects trace context.
4554
"""
4655

56+
_instance = None
57+
_force_new = False
58+
59+
def __new__(cls, func):
60+
"""
61+
If the decorator is accidentally applied to multiple functions or
62+
the same function multiple times, only the first one takes effect.
63+
64+
If _force_new, always return a real decorator, useful for unit tests.
65+
"""
66+
if cls._force_new or cls._instance is None:
67+
cls._instance = super(_LambdaDecorator, cls).__new__(cls)
68+
return cls._instance
69+
else:
70+
return _NoopDecorator(func)
71+
4772
def __init__(self, func):
73+
"""Executes when the wrapped function gets wrapped"""
4874
self.func = func
4975
self.flush_to_log = os.environ.get("DD_FLUSH_TO_LOG", "").lower() == "true"
5076
self.logs_injection = (
@@ -59,6 +85,17 @@ def __init__(self, func):
5985
patch_all()
6086
logger.debug("datadog_lambda_wrapper initialized")
6187

88+
def __call__(self, event, context, **kwargs):
89+
"""Executes when the wrapped function gets called"""
90+
self._before(event, context)
91+
try:
92+
return self.func(event, context, **kwargs)
93+
except Exception:
94+
submit_errors_metric(context)
95+
raise
96+
finally:
97+
self._after(event, context)
98+
6299
def _before(self, event, context):
63100
set_cold_start()
64101
try:
@@ -78,15 +115,5 @@ def _after(self, event, context):
78115
except Exception:
79116
traceback.print_exc()
80117

81-
def __call__(self, event, context, **kwargs):
82-
self._before(event, context)
83-
try:
84-
return self.func(event, context, **kwargs)
85-
except Exception:
86-
submit_errors_metric(context)
87-
raise
88-
finally:
89-
self._after(event, context)
90-
91118

92119
datadog_lambda_wrapper = _LambdaDecorator

tests/test_wrapper.py

+29
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ def get_mock_context(
2424

2525
class TestDatadogLambdaWrapper(unittest.TestCase):
2626
def setUp(self):
27+
# Force @datadog_lambda_wrapper to always create a real
28+
# (not no-op) wrapper.
29+
datadog_lambda_wrapper._force_new = True
30+
2731
patcher = patch("datadog_lambda.metric.lambda_stats")
2832
self.mock_metric_lambda_stats = patcher.start()
2933
self.addCleanup(patcher.stop)
@@ -251,3 +255,28 @@ def lambda_handler(event, context):
251255
self.mock_write_metric_point_to_stdout.assert_not_called()
252256

253257
del os.environ["DD_ENHANCED_METRICS"]
258+
259+
def test_only_one_wrapper_in_use(self):
260+
patcher = patch("datadog_lambda.wrapper.submit_invocations_metric")
261+
self.mock_submit_invocations_metric = patcher.start()
262+
self.addCleanup(patcher.stop)
263+
264+
@datadog_lambda_wrapper
265+
def lambda_handler(event, context):
266+
lambda_metric("test.metric", 100)
267+
268+
# Turn off _force_new to emulate the nested wrapper scenario,
269+
# the second @datadog_lambda_wrapper should actually be no-op.
270+
datadog_lambda_wrapper._force_new = False
271+
272+
@datadog_lambda_wrapper
273+
def lambda_handler_wrapper(event, context):
274+
lambda_handler(event, context)
275+
276+
lambda_event = {}
277+
278+
lambda_handler_wrapper(lambda_event, get_mock_context())
279+
280+
self.mock_patch_all.assert_called_once()
281+
self.mock_wrapper_lambda_stats.flush.assert_called_once()
282+
self.mock_submit_invocations_metric.assert_called_once()

0 commit comments

Comments
 (0)