Skip to content

Commit 63bd818

Browse files
authored
Merge pull request #19 from DataDog/swf/submit-invocations-errors
Submit metrics for invocations and errors
2 parents 14cd4ef + eb206fd commit 63bd818

File tree

8 files changed

+316
-46
lines changed

8 files changed

+316
-46
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# CHANGELOG
22

3+
# Version: 7 / 2019-10-24
4+
5+
- Increment `aws.lambda.enhanced.invocations` and `aws.lambda.enhanced.errors` metrics for each invocation if `DD_ENHANCED_METRICS` env var is set to true.
6+
37
# Version: 6 / 2019-09-16
48

59
- Support `DD_LOGS_INJECTION` for trace and log correlation

datadog_lambda/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# The minor version corresponds to the Lambda layer version.
22
# E.g.,, version 0.5.0 gets packaged into layer version 5.
3-
__version__ = '0.6.0'
3+
__version__ = '0.7.0'
44

55

66
import os

datadog_lambda/cold_start.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
_cold_start = True
2+
_lambda_container_initialized = False
3+
4+
5+
def set_cold_start():
6+
"""Set the value of the cold start global
7+
8+
This should be executed once per Lambda execution before the execution
9+
"""
10+
global _cold_start
11+
global _lambda_container_initialized
12+
_cold_start = not _lambda_container_initialized
13+
_lambda_container_initialized = True
14+
15+
16+
def is_cold_start():
17+
"""Returns the value of the global cold_start
18+
"""
19+
return _cold_start
20+
21+
22+
def get_cold_start_tag():
23+
"""Returns the cold start tag to be used in metrics
24+
"""
25+
return "cold_start:{}".format(str(is_cold_start()).lower())

datadog_lambda/metric.py

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
from datadog import api
1515
from datadog.threadstats import ThreadStats
1616
from datadog_lambda import __version__
17+
from datadog_lambda.cold_start import get_cold_start_tag
18+
from datadog_lambda.tags import parse_lambda_tags_from_arn
19+
20+
21+
ENHANCED_METRICS_NAMESPACE_PREFIX = "aws.lambda.enhanced"
1722

1823
logger = logging.getLogger(__name__)
1924

@@ -54,39 +59,71 @@ def lambda_metric(metric_name, value, timestamp=None, tags=None):
5459
background thread.
5560
"""
5661
tags = _tag_dd_lambda_layer(tags)
57-
if os.environ.get('DD_FLUSH_TO_LOG', '').lower() == 'true':
58-
logger.debug('Sending metric %s to Datadog via log forwarder', metric_name)
59-
print(json.dumps({
60-
'm': metric_name,
61-
'v': value,
62-
'e': timestamp or int(time.time()),
63-
't': tags
64-
}))
65-
else:
66-
logger.debug('Sending metric %s to Datadog via lambda layer', metric_name)
67-
lambda_stats.distribution(
68-
metric_name, value, timestamp=timestamp, tags=tags
62+
if os.environ.get("DD_FLUSH_TO_LOG", "").lower() == "true":
63+
logger.debug("Sending metric %s to Datadog via log forwarder", metric_name)
64+
print(
65+
json.dumps(
66+
{
67+
"m": metric_name,
68+
"v": value,
69+
"e": timestamp or int(time.time()),
70+
"t": tags,
71+
}
72+
)
6973
)
74+
else:
75+
logger.debug("Sending metric %s to Datadog via lambda layer", metric_name)
76+
lambda_stats.distribution(metric_name, value, timestamp=timestamp, tags=tags)
77+
78+
79+
def are_enhanced_metrics_enabled():
80+
"""Check env var to find if enhanced metrics should be submitted
81+
"""
82+
return os.environ.get("DD_ENHANCED_METRICS", "false").lower() == "true"
83+
84+
85+
def submit_invocations_metric(lambda_arn):
86+
"""Increment aws.lambda.enhanced.invocations by 1
87+
"""
88+
if not are_enhanced_metrics_enabled():
89+
return
90+
91+
lambda_metric(
92+
"{}.invocations".format(ENHANCED_METRICS_NAMESPACE_PREFIX),
93+
1,
94+
tags=parse_lambda_tags_from_arn(lambda_arn) + [get_cold_start_tag()],
95+
)
96+
97+
98+
def submit_errors_metric(lambda_arn):
99+
"""Increment aws.lambda.enhanced.errors by 1
100+
"""
101+
if not are_enhanced_metrics_enabled():
102+
return
103+
104+
lambda_metric(
105+
"{}.errors".format(ENHANCED_METRICS_NAMESPACE_PREFIX),
106+
1,
107+
tags=parse_lambda_tags_from_arn(lambda_arn) + [get_cold_start_tag()],
108+
)
70109

71110

72111
# Decrypt code should run once and variables stored outside of the function
73112
# handler so that these are decrypted once per container
74-
DD_KMS_API_KEY = os.environ.get('DD_KMS_API_KEY', '')
113+
DD_KMS_API_KEY = os.environ.get("DD_KMS_API_KEY", "")
75114
if DD_KMS_API_KEY:
76-
DD_KMS_API_KEY = boto3.client('kms').decrypt(
115+
DD_KMS_API_KEY = boto3.client("kms").decrypt(
77116
CiphertextBlob=base64.b64decode(DD_KMS_API_KEY)
78-
)['Plaintext']
117+
)["Plaintext"]
79118

80119
# Set API Key and Host in the module, so they only set once per container
81120
api._api_key = os.environ.get(
82-
'DATADOG_API_KEY',
83-
os.environ.get('DD_API_KEY', DD_KMS_API_KEY),
121+
"DATADOG_API_KEY", os.environ.get("DD_API_KEY", DD_KMS_API_KEY)
84122
)
85-
logger.debug('Setting DATADOG_API_KEY of length %d', len(api._api_key))
123+
logger.debug("Setting DATADOG_API_KEY of length %d", len(api._api_key))
86124

87125
# Set DATADOG_HOST, to send data to a non-default Datadog datacenter
88126
api._api_host = os.environ.get(
89-
'DATADOG_HOST',
90-
'https://api.' + os.environ.get('DD_SITE', 'datadoghq.com')
127+
"DATADOG_HOST", "https://api." + os.environ.get("DD_SITE", "datadoghq.com")
91128
)
92-
logger.debug('Setting DATADOG_HOST to %s', api._api_host)
129+
logger.debug("Setting DATADOG_HOST to %s", api._api_host)

datadog_lambda/tags.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
def parse_lambda_tags_from_arn(arn):
2+
"""Generate the list of lambda tags based on the data in the arn
3+
Args:
4+
arn (str): Lambda ARN.
5+
ex: arn:aws:lambda:us-east-1:123597598159:function:my-lambda[:optional-version]
6+
"""
7+
# Cap the number of times to split
8+
split_arn = arn.split(":")
9+
10+
# If ARN includes version / alias at the end, drop it
11+
if len(split_arn) > 7:
12+
split_arn = split_arn[:7]
13+
14+
_, _, _, region, account_id, _, function_name = split_arn
15+
16+
return [
17+
"region:{}".format(region),
18+
"account_id:{}".format(account_id),
19+
"functionname:{}".format(function_name),
20+
]

datadog_lambda/wrapper.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,20 @@
77
import logging
88
import traceback
99

10-
from datadog_lambda.metric import lambda_stats
10+
from datadog_lambda.cold_start import set_cold_start
11+
from datadog_lambda.metric import (
12+
lambda_stats,
13+
submit_invocations_metric,
14+
submit_errors_metric,
15+
)
1116
from datadog_lambda.patch import patch_all
1217
from datadog_lambda.tracing import (
1318
extract_dd_trace_context,
1419
set_correlation_ids,
1520
inject_correlation_ids,
1621
)
1722

23+
1824
logger = logging.getLogger(__name__)
1925

2026

@@ -40,19 +46,22 @@ class _LambdaDecorator(object):
4046

4147
def __init__(self, func):
4248
self.func = func
43-
self.flush_to_log = os.environ.get('DD_FLUSH_TO_LOG', '').lower() == 'true'
44-
self.logs_injection = os.environ.get('DD_LOGS_INJECTION', '').lower() == 'true'
49+
self.flush_to_log = os.environ.get("DD_FLUSH_TO_LOG", "").lower() == "true"
50+
self.logs_injection = os.environ.get("DD_LOGS_INJECTION", "").lower() == "true"
4551

4652
# Inject trace correlation ids to logs
4753
if self.logs_injection:
4854
inject_correlation_ids()
4955

5056
# Patch HTTP clients to propagate Datadog trace context
5157
patch_all()
52-
logger.debug('datadog_lambda_wrapper initialized')
58+
logger.debug("datadog_lambda_wrapper initialized")
5359

5460
def _before(self, event, context):
61+
set_cold_start()
62+
5563
try:
64+
submit_invocations_metric(context.invoked_function_arn)
5665
# Extract Datadog trace context from incoming requests
5766
extract_dd_trace_context(event)
5867

@@ -72,6 +81,9 @@ def __call__(self, event, context):
7281
self._before(event, context)
7382
try:
7483
return self.func(event, context)
84+
except Exception:
85+
submit_errors_metric(context.invoked_function_arn)
86+
raise
7587
finally:
7688
self._after(event, context)
7789

tests/test_tags.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import unittest
2+
3+
from datadog_lambda.tags import parse_lambda_tags_from_arn
4+
5+
6+
class TestMetricTags(unittest.TestCase):
7+
def test_parse_lambda_tags_from_arn(self):
8+
self.assertListEqual(
9+
parse_lambda_tags_from_arn(
10+
"arn:aws:lambda:us-east-1:1234597598159:function:swf-hello-test"
11+
),
12+
[
13+
"region:us-east-1",
14+
"account_id:1234597598159",
15+
"functionname:swf-hello-test",
16+
],
17+
)
18+
19+
self.assertListEqual(
20+
parse_lambda_tags_from_arn(
21+
"arn:aws:lambda:us-west-1:1234597598159:function:other-function:function-alias"
22+
),
23+
[
24+
"region:us-west-1",
25+
"account_id:1234597598159",
26+
"functionname:other-function",
27+
],
28+
)
29+

0 commit comments

Comments
 (0)