Skip to content

Submit metrics for invocations and errors #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 24, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions datadog_lambda/tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
def parse_lambda_tags_from_arn(arn):
"""Generate the list of lambda tags based on the data in the arn
Args:
arn (str): Lambda ARN.
ex: arn:aws:lambda:us-east-1:123597598159:function:my-lambda[:optional-version]
"""
# Cap the number of times to split
split_arn = arn.split(":")

# If ARN includes version / alias at the end, drop it
if len(split_arn) > 7:
split_arn = split_arn[:7]

_, _, _, region, account_id, _, function_name = split_arn

return [
"region:{}".format(region),
"account_id:{}".format(account_id),
"functionname:{}".format(function_name),
]


def get_tags_from_context(context, cold_start_request_id):
"""Uses properties of the Lambda context to create the list of tags

Args:
context (dict<str, multiple types>): context this Lambda was invoked with
cold_start_request_id (str): the first request ID to execute in this container

Returns:
tag list (str[]): list of string tags in key:value format
"""
tags = parse_lambda_tags_from_arn(context.invoked_function_arn)
tags.append("memorysize:{}".format(context.memory_limit_in_mb))

did_request_cold_start = cold_start_request_id == context.aws_request_id
cold_start_tag = "cold_start:{}".format(str(did_request_cold_start).lower())
tags.append(cold_start_tag)

return tags
32 changes: 28 additions & 4 deletions datadog_lambda/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@
import logging
import traceback

from datadog_lambda.metric import lambda_stats
from datadog_lambda.metric import lambda_stats, lambda_metric
from datadog_lambda.patch import patch_all
from datadog_lambda.tags import get_tags_from_context
from datadog_lambda.tracing import (
extract_dd_trace_context,
set_correlation_ids,
inject_correlation_ids,
)

ENHANCED_METRICS_NAMESPACE_PREFIX = "aws.lambda.enhanced"

logger = logging.getLogger(__name__)


Expand All @@ -31,6 +34,10 @@ def my_lambda_handle(event, context):
requests.get("https://www.datadoghq.com")
"""

# On the first run of this Lambda container this variable is set
# to the str value of the request ID, taken from the Lambda context
cold_start_request_id = None


class _LambdaDecorator(object):
"""
Expand All @@ -40,19 +47,29 @@ class _LambdaDecorator(object):

def __init__(self, func):
self.func = func
self.flush_to_log = os.environ.get('DD_FLUSH_TO_LOG', '').lower() == 'true'
self.logs_injection = os.environ.get('DD_LOGS_INJECTION', '').lower() == 'true'
self.flush_to_log = os.environ.get("DD_FLUSH_TO_LOG", "").lower() == "true"
self.logs_injection = os.environ.get("DD_LOGS_INJECTION", "").lower() == "true"

# Inject trace correlation ids to logs
if self.logs_injection:
inject_correlation_ids()

# Patch HTTP clients to propagate Datadog trace context
patch_all()
logger.debug('datadog_lambda_wrapper initialized')
logger.debug("datadog_lambda_wrapper initialized")

def _before(self, event, context):
global cold_start_request_id
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure context.aws_request_id stays the same for retried requests, which is gonna cause incorrect cold start? Also if you are not using the actual request id, how about?

# In a `cold_start.py` or `utils.py`?
initialized = False
cold_start = True

def set_cold_start():
    global initialized
    global cold_start
    cold_start = (initialized == False)
    initialized = True

def is_cold_start():
    global cold_start
    return cold_start

# In wrapper.py (here)
from utils import set_cold_start
def _before(self, event, context):
    set_cold_start()

# use is_cold_start in, say, metric.py
from utils import is_cold_start
is_cold_start()

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be better to prefix the globals with _ to mark them as "private".

# Assign this request ID as the cold start if there is no value yet
if cold_start_request_id is None:
cold_start_request_id = context.aws_request_id

try:
lambda_metric(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although this is a oneliner, I would make it a function in metric.py, to keep the wrapper focus on code flow rather than details.

"{}.invocations".format(ENHANCED_METRICS_NAMESPACE_PREFIX),
1,
tags=get_tags_from_context(context, cold_start_request_id),
)
# Extract Datadog trace context from incoming requests
extract_dd_trace_context(event)

Expand All @@ -72,6 +89,13 @@ def __call__(self, event, context):
self._before(event, context)
try:
return self.func(event, context)
except Exception:
lambda_metric(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although this is a oneliner, I would make it a function in metric.py, to keep the wrapper focus on code flow rather than details.

"{}.errors".format(ENHANCED_METRICS_NAMESPACE_PREFIX),
1,
tags=get_tags_from_context(context, cold_start_request_id),
)
raise
finally:
self._after(event, context)

Expand Down
60 changes: 60 additions & 0 deletions tests/test_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import unittest

from datadog_lambda.tags import parse_lambda_tags_from_arn, get_tags_from_context
from tests.test_wrapper import get_mock_context


class TestMetricTags(unittest.TestCase):
def test_parse_lambda_tags_from_arn(self):
self.assertListEqual(
parse_lambda_tags_from_arn(
"arn:aws:lambda:us-east-1:1234597598159:function:swf-hello-test"
),
[
"region:us-east-1",
"account_id:1234597598159",
"functionname:swf-hello-test",
],
)

self.assertListEqual(
parse_lambda_tags_from_arn(
"arn:aws:lambda:us-west-1:1234597598159:function:other-function:function-alias"
),
[
"region:us-west-1",
"account_id:1234597598159",
"functionname:other-function",
],
)

def test_get_tags_from_context(self):
cold_start_request_id = "first-request-id"
self.assertListEqual(
get_tags_from_context(
get_mock_context(aws_request_id=cold_start_request_id),
cold_start_request_id,
),
[
"region:us-west-1",
"account_id:123457598159",
"functionname:python-layer-test",
"memorysize:256",
"cold_start:true",
],
)

self.assertListEqual(
get_tags_from_context(
get_mock_context(aws_request_id="non-cold-start-request-id"),
cold_start_request_id,
),
[
"region:us-west-1",
"account_id:123457598159",
"functionname:python-layer-test",
"memorysize:256",
"cold_start:false",
],
)

Loading