From 8207aa224692c1f3eb72cc5cb8dc10251e5368d2 Mon Sep 17 00:00:00 2001 From: Sam Brenner <106700075+sabrenner@users.noreply.github.com> Date: Wed, 16 Apr 2025 13:59:37 -0400 Subject: [PATCH 01/17] revert llmobs api key and forced agentless (#585) --- datadog_lambda/wrapper.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/datadog_lambda/wrapper.py b/datadog_lambda/wrapper.py index 8c1914e3..e81b1baa 100644 --- a/datadog_lambda/wrapper.py +++ b/datadog_lambda/wrapper.py @@ -49,14 +49,10 @@ if profiling_env_var: from ddtrace.profiling import profiler -llmobs_api_key = None llmobs_env_var = os.environ.get("DD_LLMOBS_ENABLED", "false").lower() in ("true", "1") if llmobs_env_var: - from datadog_lambda.api import get_api_key from ddtrace.llmobs import LLMObs - llmobs_api_key = get_api_key() - logger = logging.getLogger(__name__) DD_FLUSH_TO_LOG = "DD_FLUSH_TO_LOG" @@ -226,10 +222,7 @@ def __init__(self, func): # Enable LLM Observability if llmobs_env_var: - LLMObs.enable( - agentless_enabled=True, - api_key=llmobs_api_key, - ) + LLMObs.enable() logger.debug("datadog_lambda_wrapper initialized") except Exception as e: From 33cd5bf9e05bd3e3a35450c42b522ce1280b9add Mon Sep 17 00:00:00 2001 From: Rey Abolofia Date: Thu, 24 Apr 2025 11:30:38 -0700 Subject: [PATCH 02/17] Add new region ap-southeast-7. (#586) --- ci/datasources/regions.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/datasources/regions.yaml b/ci/datasources/regions.yaml index 93816ce9..f74c62b8 100644 --- a/ci/datasources/regions.yaml +++ b/ci/datasources/regions.yaml @@ -12,6 +12,7 @@ regions: - code: "ap-southeast-3" - code: "ap-southeast-4" - code: "ap-southeast-5" + - code: "ap-southeast-7" - code: "ap-northeast-1" - code: "ap-northeast-2" - code: "ap-northeast-3" From 72caf0ae5d29f82649b57fb6075dcf9a4f7a436b Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Tue, 29 Apr 2025 14:10:13 -0400 Subject: [PATCH 03/17] add mx-central-1 region (#587) --- ci/datasources/regions.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/datasources/regions.yaml b/ci/datasources/regions.yaml index f74c62b8..d41bade3 100644 --- a/ci/datasources/regions.yaml +++ b/ci/datasources/regions.yaml @@ -29,4 +29,5 @@ regions: - code: "il-central-1" - code: "me-south-1" - code: "me-central-1" + - code: "mx-central-1" - code: "sa-east-1" From 1f8d3fd96a7dce844a3ecaea4ec2f41be1e73f56 Mon Sep 17 00:00:00 2001 From: Aleksandr Pasechnik Date: Tue, 6 May 2025 11:45:18 -0400 Subject: [PATCH 04/17] feat: Correct FIPS-mode metrics (#588) - Our `dogstatsd` client now supports timestamps for the metrics that it will send. - This unblocks us to always send metrics to the extension, even if they have a timestamp. Confirmed that this actually works now with both bottlecap and the go agent. - Refactored the metrics workflow to have an explicit choice of metrics handlers (Extension, Forwarder, Datadog API, or, for some FIPS usecases, No Handler). - Added a `DD_LAMBDA_FIPS_MODE` flag which allows FIPS-mode logic to be enabled in commercial regions or disabled in govcloud regions. - The new FIPS mode is used for Datadog API Key secret lookup and for metrics handling decisions. ### Breaking Change Since the `DD_LAMBDA_FIPS_MODE` defaults to `true` in govcloud, direct metrics submission there (without an Extension or a Forwarder) will now be disabled. --- datadog_lambda/api.py | 22 ++-- datadog_lambda/dogstatsd.py | 27 +++-- datadog_lambda/fips.py | 19 ++++ datadog_lambda/metric.py | 149 +++++++++++++++----------- datadog_lambda/stats_writer.py | 2 +- datadog_lambda/statsd_writer.py | 6 +- datadog_lambda/thread_stats_writer.py | 3 +- tests/test_api.py | 26 ++++- tests/test_dogstatsd.py | 20 ++-- tests/test_metric.py | 123 +++++++++++++-------- 10 files changed, 254 insertions(+), 143 deletions(-) create mode 100644 datadog_lambda/fips.py diff --git a/datadog_lambda/api.py b/datadog_lambda/api.py index fd3e2c17..d1cee4e4 100644 --- a/datadog_lambda/api.py +++ b/datadog_lambda/api.py @@ -1,5 +1,7 @@ -import os import logging +import os + +from datadog_lambda.fips import fips_mode_enabled logger = logging.getLogger(__name__) KMS_ENCRYPTION_CONTEXT_KEY = "LambdaFunctionName" @@ -7,9 +9,10 @@ def decrypt_kms_api_key(kms_client, ciphertext): - from botocore.exceptions import ClientError import base64 + from botocore.exceptions import ClientError + """ Decodes and deciphers the base64-encoded ciphertext given as a parameter using KMS. For this to work properly, the Lambda function must have the appropriate IAM permissions. @@ -63,10 +66,9 @@ def get_api_key() -> str: DD_API_KEY = os.environ.get("DD_API_KEY", os.environ.get("DATADOG_API_KEY", "")) LAMBDA_REGION = os.environ.get("AWS_REGION", "") - is_gov_region = LAMBDA_REGION.startswith("us-gov-") - if is_gov_region: + if fips_mode_enabled: logger.debug( - "Govcloud region detected. Using FIPs endpoints for secrets management." + "FIPS mode is enabled, using FIPS endpoints for secrets management." ) if DD_API_KEY_SECRET_ARN: @@ -80,7 +82,7 @@ def get_api_key() -> str: return "" endpoint_url = ( f"https://secretsmanager-fips.{secrets_region}.amazonaws.com" - if is_gov_region + if fips_mode_enabled else None ) secrets_manager_client = _boto3_client( @@ -92,7 +94,9 @@ def get_api_key() -> str: elif DD_API_KEY_SSM_NAME: # SSM endpoints: https://docs.aws.amazon.com/general/latest/gr/ssm.html fips_endpoint = ( - f"https://ssm-fips.{LAMBDA_REGION}.amazonaws.com" if is_gov_region else None + f"https://ssm-fips.{LAMBDA_REGION}.amazonaws.com" + if fips_mode_enabled + else None ) ssm_client = _boto3_client("ssm", endpoint_url=fips_endpoint) api_key = ssm_client.get_parameter( @@ -101,7 +105,9 @@ def get_api_key() -> str: elif DD_KMS_API_KEY: # KMS endpoints: https://docs.aws.amazon.com/general/latest/gr/kms.html fips_endpoint = ( - f"https://kms-fips.{LAMBDA_REGION}.amazonaws.com" if is_gov_region else None + f"https://kms-fips.{LAMBDA_REGION}.amazonaws.com" + if fips_mode_enabled + else None ) kms_client = _boto3_client("kms", endpoint_url=fips_endpoint) api_key = decrypt_kms_api_key(kms_client, DD_KMS_API_KEY) diff --git a/datadog_lambda/dogstatsd.py b/datadog_lambda/dogstatsd.py index a627492d..f30a2039 100644 --- a/datadog_lambda/dogstatsd.py +++ b/datadog_lambda/dogstatsd.py @@ -1,11 +1,10 @@ +import errno import logging import os -import socket -import errno import re +import socket from threading import Lock - MIN_SEND_BUFFER_SIZE = 32 * 1024 log = logging.getLogger("datadog_lambda.dogstatsd") @@ -55,14 +54,21 @@ def _get_udp_socket(cls, host, port): return sock - def distribution(self, metric, value, tags=None): + def distribution(self, metric, value, tags=None, timestamp=None): """ - Send a global distribution value, optionally setting tags. + Send a global distribution value, optionally setting tags. The optional + timestamp should be an integer representing seconds since the epoch + (January 1, 1970, 00:00:00 UTC). >>> statsd.distribution("uploaded.file.size", 1445) >>> statsd.distribution("album.photo.count", 26, tags=["gender:female"]) + >>> statsd.distribution( + >>> "historic.file.count", + >>> 5, + >>> timestamp=int(datetime(2020, 2, 14, 12, 0, 0).timestamp()), + >>> ) """ - self._report(metric, "d", value, tags) + self._report(metric, "d", value, tags, timestamp) def close_socket(self): """ @@ -84,20 +90,21 @@ def normalize_tags(self, tag_list): for tag in tag_list ] - def _serialize_metric(self, metric, metric_type, value, tags): + def _serialize_metric(self, metric, metric_type, value, tags, timestamp): # Create/format the metric packet - return "%s:%s|%s%s" % ( + return "%s:%s|%s%s%s" % ( metric, value, metric_type, ("|#" + ",".join(self.normalize_tags(tags))) if tags else "", + ("|T" + str(timestamp)) if timestamp is not None else "", ) - def _report(self, metric, metric_type, value, tags): + def _report(self, metric, metric_type, value, tags, timestamp): if value is None: return - payload = self._serialize_metric(metric, metric_type, value, tags) + payload = self._serialize_metric(metric, metric_type, value, tags, timestamp) # Send it self._send_to_server(payload) diff --git a/datadog_lambda/fips.py b/datadog_lambda/fips.py new file mode 100644 index 00000000..8442ddd9 --- /dev/null +++ b/datadog_lambda/fips.py @@ -0,0 +1,19 @@ +import logging +import os + +is_gov_region = os.environ.get("AWS_REGION", "").startswith("us-gov-") + +fips_mode_enabled = ( + os.environ.get( + "DD_LAMBDA_FIPS_MODE", + "true" if is_gov_region else "false", + ).lower() + == "true" +) + +if is_gov_region or fips_mode_enabled: + logger = logging.getLogger(__name__) + logger.debug( + "Python Lambda Layer FIPS mode is %s.", + "enabled" if fips_mode_enabled else "not enabled", + ) diff --git a/datadog_lambda/metric.py b/datadog_lambda/metric.py index f9c67a26..0c18b517 100644 --- a/datadog_lambda/metric.py +++ b/datadog_lambda/metric.py @@ -3,37 +3,66 @@ # This product includes software developed at Datadog (https://www.datadoghq.com/). # Copyright 2019 Datadog, Inc. +import enum +import logging import os import time -import logging -import ujson as json from datetime import datetime, timedelta +import ujson as json + from datadog_lambda.extension import should_use_extension -from datadog_lambda.tags import get_enhanced_metrics_tags, dd_lambda_layer_tag +from datadog_lambda.fips import fips_mode_enabled +from datadog_lambda.tags import dd_lambda_layer_tag, get_enhanced_metrics_tags logger = logging.getLogger(__name__) -lambda_stats = None -extension_thread_stats = None -flush_in_thread = os.environ.get("DD_FLUSH_IN_THREAD", "").lower() == "true" +class MetricsHandler(enum.Enum): + EXTENSION = "extension" + FORWARDER = "forwarder" + DATADOG_API = "datadog_api" + NO_METRICS = "no_metrics" + -if should_use_extension: +def _select_metrics_handler(): + if should_use_extension: + return MetricsHandler.EXTENSION + if os.environ.get("DD_FLUSH_TO_LOG", "").lower() == "true": + return MetricsHandler.FORWARDER + + if fips_mode_enabled: + logger.debug( + "With FIPS mode enabled, the Datadog API metrics handler is unavailable." + ) + return MetricsHandler.NO_METRICS + + return MetricsHandler.DATADOG_API + + +metrics_handler = _select_metrics_handler() +logger.debug("identified primary metrics handler as %s", metrics_handler) + + +lambda_stats = None +if metrics_handler == MetricsHandler.EXTENSION: from datadog_lambda.statsd_writer import StatsDWriter lambda_stats = StatsDWriter() -else: + +elif metrics_handler == MetricsHandler.DATADOG_API: # Periodical flushing in a background thread is NOT guaranteed to succeed # and leads to data loss. When disabled, metrics are only flushed at the # end of invocation. To make metrics submitted from a long-running Lambda # function available sooner, consider using the Datadog Lambda extension. - from datadog_lambda.thread_stats_writer import ThreadStatsWriter from datadog_lambda.api import init_api + from datadog_lambda.thread_stats_writer import ThreadStatsWriter + flush_in_thread = os.environ.get("DD_FLUSH_IN_THREAD", "").lower() == "true" init_api() lambda_stats = ThreadStatsWriter(flush_in_thread) + enhanced_metrics_enabled = ( os.environ.get("DD_ENHANCED_METRICS", "true").lower() == "true" ) @@ -44,16 +73,19 @@ def lambda_metric(metric_name, value, timestamp=None, tags=None, force_async=Fal Submit a data point to Datadog distribution metrics. https://docs.datadoghq.com/graphing/metrics/distributions/ - When DD_FLUSH_TO_LOG is True, write metric to log, and - wait for the Datadog Log Forwarder Lambda function to submit - the metrics asynchronously. + If the Datadog Lambda Extension is present, metrics are submitted to its + dogstatsd endpoint. + + When DD_FLUSH_TO_LOG is True or force_async is True, write metric to log, + and wait for the Datadog Log Forwarder Lambda function to submit the + metrics asynchronously. Otherwise, the metrics will be submitted to the Datadog API periodically and at the end of the function execution in a background thread. - Note that if the extension is present, it will override the DD_FLUSH_TO_LOG value - and always use the layer to send metrics to the extension + Note that if the extension is present, it will override the DD_FLUSH_TO_LOG + value and always use the layer to send metrics to the extension """ if not metric_name or not isinstance(metric_name, str): logger.warning( @@ -71,56 +103,54 @@ def lambda_metric(metric_name, value, timestamp=None, tags=None, force_async=Fal ) return - flush_to_logs = os.environ.get("DD_FLUSH_TO_LOG", "").lower() == "true" tags = [] if tags is None else list(tags) tags.append(dd_lambda_layer_tag) - if should_use_extension and timestamp is not None: - # The extension does not support timestamps for distributions so we create a - # a thread stats writer to submit metrics with timestamps to the API - timestamp_ceiling = int( - (datetime.now() - timedelta(hours=4)).timestamp() - ) # 4 hours ago - if isinstance(timestamp, datetime): - timestamp = int(timestamp.timestamp()) - if timestamp_ceiling > timestamp: - logger.warning( - "Timestamp %s is older than 4 hours, not submitting metric %s", - timestamp, - metric_name, - ) - return - global extension_thread_stats - if extension_thread_stats is None: - from datadog_lambda.thread_stats_writer import ThreadStatsWriter - from datadog_lambda.api import init_api - - init_api() - extension_thread_stats = ThreadStatsWriter(flush_in_thread) - - extension_thread_stats.distribution( - metric_name, value, tags=tags, timestamp=timestamp - ) - return + if metrics_handler == MetricsHandler.EXTENSION: + if timestamp is not None: + if isinstance(timestamp, datetime): + timestamp = int(timestamp.timestamp()) + + timestamp_floor = int((datetime.now() - timedelta(hours=4)).timestamp()) + if timestamp < timestamp_floor: + logger.warning( + "Timestamp %s is older than 4 hours, not submitting metric %s", + timestamp, + metric_name, + ) + return - if should_use_extension: logger.debug( "Sending metric %s value %s to Datadog via extension", metric_name, value ) lambda_stats.distribution(metric_name, value, tags=tags, timestamp=timestamp) + + elif force_async or (metrics_handler == MetricsHandler.FORWARDER): + write_metric_point_to_stdout(metric_name, value, timestamp=timestamp, tags=tags) + + elif metrics_handler == MetricsHandler.DATADOG_API: + lambda_stats.distribution(metric_name, value, tags=tags, timestamp=timestamp) + + elif metrics_handler == MetricsHandler.NO_METRICS: + logger.debug( + "Metric %s cannot be submitted because the metrics handler is disabled", + metric_name, + ), + else: - if flush_to_logs or force_async: - write_metric_point_to_stdout( - metric_name, value, timestamp=timestamp, tags=tags - ) - else: - lambda_stats.distribution( - metric_name, value, tags=tags, timestamp=timestamp - ) + # This should be qutie impossible, but let's at least log a message if + # it somehow happens. + logger.debug( + "Metric %s cannot be submitted because the metrics handler is not configured: %s", + metric_name, + metrics_handler, + ) -def write_metric_point_to_stdout(metric_name, value, timestamp=None, tags=[]): +def write_metric_point_to_stdout(metric_name, value, timestamp=None, tags=None): """Writes the specified metric point to standard output""" + tags = tags or [] + logger.debug( "Sending metric %s value %s to Datadog via log forwarder", metric_name, value ) @@ -138,19 +168,8 @@ def write_metric_point_to_stdout(metric_name, value, timestamp=None, tags=[]): def flush_stats(lambda_context=None): - lambda_stats.flush() - - if extension_thread_stats is not None: - tags = None - if lambda_context is not None: - tags = get_enhanced_metrics_tags(lambda_context) - split_arn = lambda_context.invoked_function_arn.split(":") - if len(split_arn) > 7: - # Get rid of the alias - split_arn.pop() - arn = ":".join(split_arn) - tags.append("function_arn:" + arn) - extension_thread_stats.flush(tags) + if lambda_stats is not None: + lambda_stats.flush() def submit_enhanced_metric(metric_name, lambda_context): diff --git a/datadog_lambda/stats_writer.py b/datadog_lambda/stats_writer.py index d3919c30..563b1ae9 100644 --- a/datadog_lambda/stats_writer.py +++ b/datadog_lambda/stats_writer.py @@ -1,5 +1,5 @@ class StatsWriter: - def distribution(self, metric_name, value, tags=[], timestamp=None): + def distribution(self, metric_name, value, tags=None, timestamp=None): raise NotImplementedError() def flush(self): diff --git a/datadog_lambda/statsd_writer.py b/datadog_lambda/statsd_writer.py index 33843dc6..4aaab8d5 100644 --- a/datadog_lambda/statsd_writer.py +++ b/datadog_lambda/statsd_writer.py @@ -1,5 +1,5 @@ -from datadog_lambda.stats_writer import StatsWriter from datadog_lambda.dogstatsd import statsd +from datadog_lambda.stats_writer import StatsWriter class StatsDWriter(StatsWriter): @@ -7,8 +7,8 @@ class StatsDWriter(StatsWriter): Writes distribution metrics using StatsD protocol """ - def distribution(self, metric_name, value, tags=[], timestamp=None): - statsd.distribution(metric_name, value, tags=tags) + def distribution(self, metric_name, value, tags=None, timestamp=None): + statsd.distribution(metric_name, value, tags=tags, timestamp=timestamp) def flush(self): pass diff --git a/datadog_lambda/thread_stats_writer.py b/datadog_lambda/thread_stats_writer.py index 422a9a0a..f21ee31f 100644 --- a/datadog_lambda/thread_stats_writer.py +++ b/datadog_lambda/thread_stats_writer.py @@ -3,6 +3,7 @@ # Make sure that this package would always be lazy-loaded/outside from the critical path # since underlying packages are quite heavy to load and useless when the extension is present from datadog.threadstats import ThreadStats + from datadog_lambda.stats_writer import StatsWriter logger = logging.getLogger(__name__) @@ -17,7 +18,7 @@ def __init__(self, flush_in_thread): self.thread_stats = ThreadStats(compress_payload=True) self.thread_stats.start(flush_in_thread=flush_in_thread) - def distribution(self, metric_name, value, tags=[], timestamp=None): + def distribution(self, metric_name, value, tags=None, timestamp=None): self.thread_stats.distribution( metric_name, value, tags=tags, timestamp=timestamp ) diff --git a/tests/test_api.py b/tests/test_api.py index c98d91eb..59ee4ee8 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,6 +1,6 @@ import os import unittest -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch import datadog_lambda.api as api @@ -22,6 +22,7 @@ def setUp(self): ) self.env_patcher.start() + @patch("datadog_lambda.api.fips_mode_enabled", True) @patch("botocore.session.Session.create_client") def test_secrets_manager_fips_endpoint(self, mock_boto3_client): mock_client = MagicMock() @@ -62,6 +63,28 @@ def test_secrets_manager_different_region(self, mock_boto3_client): ) self.assertEqual(api_key, "test-api-key") + @patch("datadog_lambda.api.fips_mode_enabled", True) + @patch("botocore.session.Session.create_client") + def test_secrets_manager_different_region_but_still_fips(self, mock_boto3_client): + mock_client = MagicMock() + mock_client.get_secret_value.return_value = {"SecretString": "test-api-key"} + mock_boto3_client.return_value = mock_client + + os.environ["AWS_REGION"] = "us-east-1" + os.environ[ + "DD_API_KEY_SECRET_ARN" + ] = "arn:aws:secretsmanager:us-west-1:1234567890:secret:key-name-123ABC" + + api_key = api.get_api_key() + + mock_boto3_client.assert_called_with( + "secretsmanager", + endpoint_url="https://secretsmanager-fips.us-west-1.amazonaws.com", + region_name="us-west-1", + ) + self.assertEqual(api_key, "test-api-key") + + @patch("datadog_lambda.api.fips_mode_enabled", True) @patch("botocore.session.Session.create_client") def test_ssm_fips_endpoint(self, mock_boto3_client): mock_client = MagicMock() @@ -80,6 +103,7 @@ def test_ssm_fips_endpoint(self, mock_boto3_client): ) self.assertEqual(api_key, "test-api-key") + @patch("datadog_lambda.api.fips_mode_enabled", True) @patch("botocore.session.Session.create_client") @patch("datadog_lambda.api.decrypt_kms_api_key") def test_kms_fips_endpoint(self, mock_decrypt_kms, mock_boto3_client): diff --git a/tests/test_dogstatsd.py b/tests/test_dogstatsd.py index 149e1a70..ea6afd48 100644 --- a/tests/test_dogstatsd.py +++ b/tests/test_dogstatsd.py @@ -1,5 +1,5 @@ -from collections import deque import unittest +from collections import deque from datadog_lambda.dogstatsd import statsd @@ -36,16 +36,20 @@ def test_init(self): self.assertEqual(statsd.port, 8125) self.assertEqual(statsd.encoding, "utf-8") - def test_distribution_no_tags(self): - statsd.distribution("my.test.metric", 3) + def _checkOnlyOneMetric(self, value): payload = self.recv() metrics = payload.split("\n") self.assertEqual(len(metrics), 1) - self.assertEqual("my.test.metric:3|d", metrics[0]) + self.assertEqual(value, metrics[0]) + + def test_distribution_no_tags(self): + statsd.distribution("my.test.metric", 3) + self._checkOnlyOneMetric("my.test.metric:3|d") def test_distribution_with_tags(self): statsd.distribution("my.test.tags.metric", 3, tags=["taga:valuea,tagb:valueb"]) - payload = self.recv() - metrics = payload.split("\n") - self.assertEqual(len(metrics), 1) - self.assertEqual("my.test.tags.metric:3|d|#taga:valuea_tagb:valueb", metrics[0]) + self._checkOnlyOneMetric("my.test.tags.metric:3|d|#taga:valuea_tagb:valueb") + + def test_distribution_with_timestamp(self): + statsd.distribution("my.test.timestamp.metric", 9, timestamp=123456789) + self._checkOnlyOneMetric("my.test.timestamp.metric:9|d|T123456789") diff --git a/tests/test_metric.py b/tests/test_metric.py index d10a0f0d..a4b0be2c 100644 --- a/tests/test_metric.py +++ b/tests/test_metric.py @@ -1,23 +1,33 @@ import os import unittest - -from unittest.mock import patch, call +from datetime import datetime, timedelta +from unittest.mock import call, patch from botocore.exceptions import ClientError as BotocoreClientError from datadog.api.exceptions import ClientError -from datetime import datetime, timedelta -from datadog_lambda.metric import lambda_metric, flush_stats -from datadog_lambda.api import decrypt_kms_api_key, KMS_ENCRYPTION_CONTEXT_KEY -from datadog_lambda.thread_stats_writer import ThreadStatsWriter +from datadog_lambda.api import KMS_ENCRYPTION_CONTEXT_KEY, decrypt_kms_api_key +from datadog_lambda.metric import ( + MetricsHandler, + _select_metrics_handler, + flush_stats, + lambda_metric, +) from datadog_lambda.tags import dd_lambda_layer_tag +from datadog_lambda.thread_stats_writer import ThreadStatsWriter class TestLambdaMetric(unittest.TestCase): def setUp(self): - patcher = patch("datadog_lambda.metric.lambda_stats") - self.mock_metric_lambda_stats = patcher.start() - self.addCleanup(patcher.stop) + lambda_stats_patcher = patch("datadog_lambda.metric.lambda_stats") + self.mock_metric_lambda_stats = lambda_stats_patcher.start() + self.addCleanup(lambda_stats_patcher.stop) + + stdout_metric_patcher = patch( + "datadog_lambda.metric.write_metric_point_to_stdout" + ) + self.mock_write_metric_point_to_stdout = stdout_metric_patcher.start() + self.addCleanup(stdout_metric_patcher.stop) def test_lambda_metric_tagged_with_dd_lambda_layer(self): lambda_metric("test", 1) @@ -35,67 +45,94 @@ def test_lambda_metric_tagged_with_dd_lambda_layer(self): # let's fake that the extension is present, this should override DD_FLUSH_TO_LOG @patch("datadog_lambda.metric.should_use_extension", True) - def test_lambda_metric_flush_to_log_with_extension(self): + def test_select_metrics_handler_extension_despite_flush_to_logs(self): + os.environ["DD_FLUSH_TO_LOG"] = "True" + self.assertEqual(MetricsHandler.EXTENSION, _select_metrics_handler()) + del os.environ["DD_FLUSH_TO_LOG"] + + @patch("datadog_lambda.metric.should_use_extension", False) + def test_select_metrics_handler_forwarder_when_flush_to_logs(self): os.environ["DD_FLUSH_TO_LOG"] = "True" + self.assertEqual(MetricsHandler.FORWARDER, _select_metrics_handler()) + del os.environ["DD_FLUSH_TO_LOG"] + + @patch("datadog_lambda.metric.should_use_extension", False) + def test_select_metrics_handler_dd_api_fallback(self): + os.environ["DD_FLUSH_TO_LOG"] = "False" + self.assertEqual(MetricsHandler.DATADOG_API, _select_metrics_handler()) + del os.environ["DD_FLUSH_TO_LOG"] + + @patch("datadog_lambda.metric.fips_mode_enabled", True) + @patch("datadog_lambda.metric.should_use_extension", False) + def test_select_metrics_handler_has_no_fallback_in_fips_mode(self): + os.environ["DD_FLUSH_TO_LOG"] = "False" + self.assertEqual(MetricsHandler.NO_METRICS, _select_metrics_handler()) + del os.environ["DD_FLUSH_TO_LOG"] + + @patch("datadog_lambda.metric.metrics_handler", MetricsHandler.EXTENSION) + def test_lambda_metric_goes_to_extension_with_extension_handler(self): lambda_metric("test", 1) self.mock_metric_lambda_stats.distribution.assert_has_calls( [call("test", 1, timestamp=None, tags=[dd_lambda_layer_tag])] ) - del os.environ["DD_FLUSH_TO_LOG"] - @patch("datadog_lambda.metric.should_use_extension", True) - def test_lambda_metric_timestamp_with_extension(self): - patcher = patch("datadog_lambda.metric.extension_thread_stats") - self.mock_metric_extension_thread_stats = patcher.start() - self.addCleanup(patcher.stop) + @patch("datadog_lambda.metric.metrics_handler", MetricsHandler.NO_METRICS) + def test_lambda_metric_has_nowhere_to_go_with_no_metrics_handler(self): + lambda_metric("test", 1) + self.mock_metric_lambda_stats.distribution.assert_not_called() + self.mock_write_metric_point_to_stdout.assert_not_called() + @patch("datadog_lambda.metric.metrics_handler", MetricsHandler.EXTENSION) + def test_lambda_metric_timestamp_with_extension(self): delta = timedelta(minutes=1) timestamp = int((datetime.now() - delta).timestamp()) lambda_metric("test_timestamp", 1, timestamp) - self.mock_metric_lambda_stats.distribution.assert_not_called() - self.mock_metric_extension_thread_stats.distribution.assert_called_with( - "test_timestamp", 1, timestamp=timestamp, tags=[dd_lambda_layer_tag] + self.mock_metric_lambda_stats.distribution.assert_has_calls( + [call("test_timestamp", 1, timestamp=timestamp, tags=[dd_lambda_layer_tag])] ) + self.mock_write_metric_point_to_stdout.assert_not_called() - @patch("datadog_lambda.metric.should_use_extension", True) + @patch("datadog_lambda.metric.metrics_handler", MetricsHandler.EXTENSION) def test_lambda_metric_datetime_with_extension(self): - patcher = patch("datadog_lambda.metric.extension_thread_stats") - self.mock_metric_extension_thread_stats = patcher.start() - self.addCleanup(patcher.stop) - - delta = timedelta(hours=5) + delta = timedelta(minutes=1) timestamp = datetime.now() - delta - lambda_metric("test_timestamp", 1, timestamp) - self.mock_metric_lambda_stats.distribution.assert_not_called() - self.mock_metric_extension_thread_stats.distribution.assert_not_called() + lambda_metric("test_datetime_timestamp", 0, timestamp) + self.mock_metric_lambda_stats.distribution.assert_has_calls( + [ + call( + "test_datetime_timestamp", + 0, + timestamp=int(timestamp.timestamp()), + tags=[dd_lambda_layer_tag], + ) + ] + ) + self.mock_write_metric_point_to_stdout.assert_not_called() - @patch("datadog_lambda.metric.should_use_extension", True) + @patch("datadog_lambda.metric.metrics_handler", MetricsHandler.EXTENSION) def test_lambda_metric_invalid_timestamp_with_extension(self): - patcher = patch("datadog_lambda.metric.extension_thread_stats") - self.mock_metric_extension_thread_stats = patcher.start() - self.addCleanup(patcher.stop) - delta = timedelta(hours=5) timestamp = int((datetime.now() - delta).timestamp()) lambda_metric("test_timestamp", 1, timestamp) self.mock_metric_lambda_stats.distribution.assert_not_called() - self.mock_metric_extension_thread_stats.distribution.assert_not_called() + self.mock_write_metric_point_to_stdout.assert_not_called() + @patch("datadog_lambda.metric.metrics_handler", MetricsHandler.FORWARDER) def test_lambda_metric_flush_to_log(self): - os.environ["DD_FLUSH_TO_LOG"] = "True" - lambda_metric("test", 1) self.mock_metric_lambda_stats.distribution.assert_not_called() - - del os.environ["DD_FLUSH_TO_LOG"] + self.mock_write_metric_point_to_stdout.assert_has_calls( + [call("test", 1, timestamp=None, tags=[dd_lambda_layer_tag])] + ) @patch("datadog_lambda.metric.logger.warning") def test_lambda_metric_invalid_metric_name_none(self, mock_logger_warning): lambda_metric(None, 1) self.mock_metric_lambda_stats.distribution.assert_not_called() + self.mock_write_metric_point_to_stdout.assert_not_called() mock_logger_warning.assert_called_once_with( "Ignoring metric submission. Invalid metric name: %s", None ) @@ -104,6 +141,7 @@ def test_lambda_metric_invalid_metric_name_none(self, mock_logger_warning): def test_lambda_metric_invalid_metric_name_not_string(self, mock_logger_warning): lambda_metric(123, 1) self.mock_metric_lambda_stats.distribution.assert_not_called() + self.mock_write_metric_point_to_stdout.assert_not_called() mock_logger_warning.assert_called_once_with( "Ignoring metric submission. Invalid metric name: %s", 123 ) @@ -112,6 +150,7 @@ def test_lambda_metric_invalid_metric_name_not_string(self, mock_logger_warning) def test_lambda_metric_non_numeric_value(self, mock_logger_warning): lambda_metric("test.non_numeric", "oops") self.mock_metric_lambda_stats.distribution.assert_not_called() + self.mock_write_metric_point_to_stdout.assert_not_called() mock_logger_warning.assert_called_once_with( "Ignoring metric submission for metric '%s' because the value is not numeric: %r", "test.non_numeric", @@ -127,10 +166,6 @@ def setUp(self): self.mock_threadstats_flush_distributions = patcher.start() self.addCleanup(patcher.stop) - patcher = patch("datadog_lambda.metric.extension_thread_stats") - self.mock_extension_thread_stats = patcher.start() - self.addCleanup(patcher.stop) - def test_retry_on_remote_disconnected(self): # Raise the RemoteDisconnected error lambda_stats = ThreadStatsWriter(True) @@ -209,10 +244,6 @@ def test_flush_temp_constant_tags(self): lambda_stats.thread_stats.constant_tags, original_constant_tags ) - def test_flush_stats_without_context(self): - flush_stats(lambda_context=None) - self.mock_extension_thread_stats.flush.assert_called_with(None) - MOCK_FUNCTION_NAME = "myFunction" From 8a01794b02244efab5814f52e442aacf71682aac Mon Sep 17 00:00:00 2001 From: Aleksandr Pasechnik Date: Wed, 7 May 2025 16:45:13 -0400 Subject: [PATCH 05/17] 6.108.0 Release Candidate (#589) --- datadog_lambda/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/datadog_lambda/version.py b/datadog_lambda/version.py index 702691d8..bcd37def 100644 --- a/datadog_lambda/version.py +++ b/datadog_lambda/version.py @@ -1 +1 @@ -__version__ = "6.107.0" +__version__ = "6.108.0" diff --git a/pyproject.toml b/pyproject.toml index 165a8cbe..8f16b438 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "datadog_lambda" -version = "6.107.0" +version = "6.108.0" description = "The Datadog AWS Lambda Library" authors = ["Datadog, Inc. "] license = "Apache-2.0" From 8ca58b40dcfa8ad4c2169a27ddca27b19c7fb348 Mon Sep 17 00:00:00 2001 From: Aleksandr Pasechnik Date: Thu, 8 May 2025 11:19:03 -0400 Subject: [PATCH 06/17] fix: timestamps we send to the extension should be integers (#590) Also added some defense in depth for our lower level statsd call. --- datadog_lambda/dogstatsd.py | 2 +- datadog_lambda/metric.py | 12 ++++++++++++ tests/test_dogstatsd.py | 4 ++++ tests/test_metric.py | 28 ++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/datadog_lambda/dogstatsd.py b/datadog_lambda/dogstatsd.py index f30a2039..a08e2592 100644 --- a/datadog_lambda/dogstatsd.py +++ b/datadog_lambda/dogstatsd.py @@ -97,7 +97,7 @@ def _serialize_metric(self, metric, metric_type, value, tags, timestamp): value, metric_type, ("|#" + ",".join(self.normalize_tags(tags))) if tags else "", - ("|T" + str(timestamp)) if timestamp is not None else "", + ("|T" + str(int(timestamp))) if timestamp is not None else "", ) def _report(self, metric, metric_type, value, tags, timestamp): diff --git a/datadog_lambda/metric.py b/datadog_lambda/metric.py index 0c18b517..c9b978d6 100644 --- a/datadog_lambda/metric.py +++ b/datadog_lambda/metric.py @@ -111,6 +111,18 @@ def lambda_metric(metric_name, value, timestamp=None, tags=None, force_async=Fal if isinstance(timestamp, datetime): timestamp = int(timestamp.timestamp()) + else: + try: + timestamp = int(timestamp) + except Exception: + logger.debug( + "Ignoring metric submission for metric '%s' because the timestamp cannot " + "be turned into an integer: %r", + metric_name, + timestamp, + ) + return + timestamp_floor = int((datetime.now() - timedelta(hours=4)).timestamp()) if timestamp < timestamp_floor: logger.warning( diff --git a/tests/test_dogstatsd.py b/tests/test_dogstatsd.py index ea6afd48..6fe79372 100644 --- a/tests/test_dogstatsd.py +++ b/tests/test_dogstatsd.py @@ -53,3 +53,7 @@ def test_distribution_with_tags(self): def test_distribution_with_timestamp(self): statsd.distribution("my.test.timestamp.metric", 9, timestamp=123456789) self._checkOnlyOneMetric("my.test.timestamp.metric:9|d|T123456789") + + def test_distribution_with_float_timestamp(self): + statsd.distribution("my.test.timestamp.metric", 9, timestamp=123456789.123) + self._checkOnlyOneMetric("my.test.timestamp.metric:9|d|T123456789") diff --git a/tests/test_metric.py b/tests/test_metric.py index a4b0be2c..e7dab2c3 100644 --- a/tests/test_metric.py +++ b/tests/test_metric.py @@ -111,6 +111,34 @@ def test_lambda_metric_datetime_with_extension(self): ) self.mock_write_metric_point_to_stdout.assert_not_called() + @patch("datadog_lambda.metric.metrics_handler", MetricsHandler.EXTENSION) + def test_lambda_metric_float_with_extension(self): + delta = timedelta(minutes=1) + timestamp_float = (datetime.now() - delta).timestamp() + timestamp_int = int(timestamp_float) + + lambda_metric("test_timestamp", 1, timestamp_float) + self.mock_metric_lambda_stats.distribution.assert_has_calls( + [ + call( + "test_timestamp", + 1, + timestamp=timestamp_int, + tags=[dd_lambda_layer_tag], + ) + ] + ) + self.mock_write_metric_point_to_stdout.assert_not_called() + + @patch("datadog_lambda.metric.metrics_handler", MetricsHandler.EXTENSION) + def test_lambda_metric_timestamp_junk_with_extension(self): + delta = timedelta(minutes=1) + timestamp = (datetime.now() - delta).isoformat() + + lambda_metric("test_timestamp", 1, timestamp) + self.mock_metric_lambda_stats.distribution.assert_not_called() + self.mock_write_metric_point_to_stdout.assert_not_called() + @patch("datadog_lambda.metric.metrics_handler", MetricsHandler.EXTENSION) def test_lambda_metric_invalid_timestamp_with_extension(self): delta = timedelta(hours=5) From 1226b2de560afc6a8fe3e631ad84960b5dd03eef Mon Sep 17 00:00:00 2001 From: Aleksandr Pasechnik Date: Fri, 9 May 2025 12:46:04 -0400 Subject: [PATCH 07/17] v6.109.0 (#591) --- datadog_lambda/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/datadog_lambda/version.py b/datadog_lambda/version.py index bcd37def..c3aaa6b7 100644 --- a/datadog_lambda/version.py +++ b/datadog_lambda/version.py @@ -1 +1 @@ -__version__ = "6.108.0" +__version__ = "6.109.0" diff --git a/pyproject.toml b/pyproject.toml index 8f16b438..cccef63e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "datadog_lambda" -version = "6.108.0" +version = "6.109.0" description = "The Datadog AWS Lambda Library" authors = ["Datadog, Inc. "] license = "Apache-2.0" From 16774731b8050e3355d24ffe6d917c656cf0c09f Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Fri, 9 May 2025 13:21:33 -0400 Subject: [PATCH 08/17] (feat): Enable Exception Replay in Lambda (#592) * import exception replay from tracer and enable if `DD_EXCEPTION_REPLAY_ENABLED=true` * lint --- datadog_lambda/wrapper.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/datadog_lambda/wrapper.py b/datadog_lambda/wrapper.py index e81b1baa..e5460118 100644 --- a/datadog_lambda/wrapper.py +++ b/datadog_lambda/wrapper.py @@ -53,6 +53,12 @@ if llmobs_env_var: from ddtrace.llmobs import LLMObs +exception_replay_env_var = os.environ.get( + "DD_EXCEPTION_REPLAY_ENABLED", "false" +).lower() in ("true", "1") +if exception_replay_env_var: + from ddtrace.debugging._exception.replay import SpanExceptionHandler + logger = logging.getLogger(__name__) DD_FLUSH_TO_LOG = "DD_FLUSH_TO_LOG" @@ -224,6 +230,11 @@ def __init__(self, func): if llmobs_env_var: LLMObs.enable() + # Enable Exception Replay + if exception_replay_env_var: + logger.debug("Enabling exception replay") + SpanExceptionHandler.enable() + logger.debug("datadog_lambda_wrapper initialized") except Exception as e: logger.error(format_err_with_traceback(e)) From 9b694f7e25c58e9cbffb97392145c6962747fa87 Mon Sep 17 00:00:00 2001 From: Joey Zhao <5253430+joeyzhao2018@users.noreply.github.com> Date: Tue, 13 May 2025 12:47:49 -0400 Subject: [PATCH 09/17] fix: safely getting all the values for trigger tags (#593) * solution2: safely getting all the values * lint * add comment back --- datadog_lambda/trigger.py | 45 +++++++++++++++++++++------ tests/test_trigger.py | 65 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 10 deletions(-) diff --git a/datadog_lambda/trigger.py b/datadog_lambda/trigger.py index 8090e36e..52978d4b 100644 --- a/datadog_lambda/trigger.py +++ b/datadog_lambda/trigger.py @@ -114,10 +114,14 @@ def parse_event_source(event: dict) -> _EventSource: event_source = None - request_context = event.get("requestContext") + # Get requestContext safely and ensure it's a dictionary + request_context = event.get("requestContext", {}) + if not isinstance(request_context, dict): + request_context = {} + if request_context and request_context.get("stage"): if "domainName" in request_context and detect_lambda_function_url_domain( - request_context.get("domainName") + request_context.get("domainName", "") ): return _EventSource(EventTypes.LAMBDA_FUNCTION_URL) event_source = _EventSource(EventTypes.API_GATEWAY) @@ -171,6 +175,8 @@ def parse_event_source(event: dict) -> _EventSource: def detect_lambda_function_url_domain(domain: str) -> bool: # e.g. "etsn5fibjr.lambda-url.eu-south-1.amazonaws.com" + if not isinstance(domain, str): + return False domain_parts = domain.split(".") if len(domain_parts) < 2: return False @@ -283,17 +289,28 @@ def extract_http_tags(event): Extracts HTTP facet tags from the triggering event """ http_tags = {} - request_context = event.get("requestContext") + + # Safely get request_context and ensure it's a dictionary + request_context = event.get("requestContext", {}) + if not isinstance(request_context, dict): + request_context = {} + path = event.get("path") method = event.get("httpMethod") + if request_context and request_context.get("stage"): - if request_context.get("domainName"): - http_tags["http.url"] = request_context.get("domainName") + domain_name = request_context.get("domainName") + if domain_name: + http_tags["http.url"] = domain_name path = request_context.get("path") method = request_context.get("httpMethod") + # Version 2.0 HTTP API Gateway - apigateway_v2_http = request_context.get("http") + apigateway_v2_http = request_context.get("http", {}) + if not isinstance(apigateway_v2_http, dict): + apigateway_v2_http = {} + if event.get("version") == "2.0" and apigateway_v2_http: path = apigateway_v2_http.get("path") method = apigateway_v2_http.get("method") @@ -303,15 +320,23 @@ def extract_http_tags(event): if method: http_tags["http.method"] = method - headers = event.get("headers") + # Safely get headers + headers = event.get("headers", {}) + if not isinstance(headers, dict): + headers = {} + if headers and headers.get("Referer"): http_tags["http.referer"] = headers.get("Referer") # Try to get `routeKey` from API GW v2; otherwise try to get `resource` from API GW v1 route = event.get("routeKey") or event.get("resource") - if route: - # "GET /my/endpoint" = > "/my/endpoint" - http_tags["http.route"] = route.split(" ")[-1] + if route and isinstance(route, str): + try: + # "GET /my/endpoint" = > "/my/endpoint" + http_tags["http.route"] = route.split(" ")[-1] + except Exception: + # If splitting fails, use the route as is + http_tags["http.route"] = route return http_tags diff --git a/tests/test_trigger.py b/tests/test_trigger.py index 9cb088f1..b4da7ff0 100644 --- a/tests/test_trigger.py +++ b/tests/test_trigger.py @@ -256,6 +256,30 @@ def test_event_source_unsupported(self): self.assertEqual(event_source.to_string(), "unknown") self.assertEqual(event_source_arn, None) + def test_event_source_with_non_dict_request_context(self): + # Test with requestContext as a string instead of a dict + event = {"requestContext": "not_a_dict"} + event_source = parse_event_source(event) + # Should still return a valid event source (unknown in this case) + self.assertEqual(event_source.to_string(), "unknown") + + def test_event_source_with_invalid_domain_name(self): + # Test with domainName that isn't a string + event = {"requestContext": {"stage": "prod", "domainName": 12345}} + event_source = parse_event_source(event) + # Should detect as API Gateway since stage is present + self.assertEqual(event_source.to_string(), "api-gateway") + + def test_detect_lambda_function_url_domain_with_invalid_input(self): + from datadog_lambda.trigger import detect_lambda_function_url_domain + + # Test with non-string input + self.assertFalse(detect_lambda_function_url_domain(None)) + self.assertFalse(detect_lambda_function_url_domain(12345)) + self.assertFalse(detect_lambda_function_url_domain({"not": "a-string"})) + # Test with string that would normally cause an exception when split + self.assertFalse(detect_lambda_function_url_domain("")) + class GetTriggerTags(unittest.TestCase): def test_extract_trigger_tags_api_gateway(self): @@ -530,6 +554,47 @@ def test_extract_trigger_tags_list_type_event(self): tags = extract_trigger_tags(event, ctx) self.assertEqual(tags, {}) + def test_extract_http_tags_with_invalid_request_context(self): + from datadog_lambda.trigger import extract_http_tags + + # Test with requestContext as a string instead of a dict + event = {"requestContext": "not_a_dict", "path": "/test", "httpMethod": "GET"} + http_tags = extract_http_tags(event) + # Should still extract valid tags from the event + self.assertEqual( + http_tags, {"http.url_details.path": "/test", "http.method": "GET"} + ) + + def test_extract_http_tags_with_invalid_apigateway_http(self): + from datadog_lambda.trigger import extract_http_tags + + # Test with http in requestContext that's not a dict + event = { + "requestContext": {"stage": "prod", "http": "not_a_dict"}, + "version": "2.0", + } + http_tags = extract_http_tags(event) + # Should not raise an exception + self.assertEqual(http_tags, {}) + + def test_extract_http_tags_with_invalid_headers(self): + from datadog_lambda.trigger import extract_http_tags + + # Test with headers that's not a dict + event = {"headers": "not_a_dict"} + http_tags = extract_http_tags(event) + # Should not raise an exception + self.assertEqual(http_tags, {}) + + def test_extract_http_tags_with_invalid_route(self): + from datadog_lambda.trigger import extract_http_tags + + # Test with routeKey that would cause a split error + event = {"routeKey": 12345} # Not a string + http_tags = extract_http_tags(event) + # Should not raise an exception + self.assertEqual(http_tags, {}) + class ExtractHTTPStatusCodeTag(unittest.TestCase): def test_extract_http_status_code_tag_from_response_dict(self): From b74068bbdb07be1e21b6ed34c73dd23c5a853f14 Mon Sep 17 00:00:00 2001 From: Joey Zhao <5253430+joeyzhao2018@users.noreply.github.com> Date: Wed, 14 May 2025 22:27:42 -0400 Subject: [PATCH 10/17] perf: fewer memory allocation (#597) * fewer memory allocation * Update datadog_lambda/trigger.py * Update datadog_lambda/trigger.py --- datadog_lambda/trigger.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/datadog_lambda/trigger.py b/datadog_lambda/trigger.py index 52978d4b..a2708a59 100644 --- a/datadog_lambda/trigger.py +++ b/datadog_lambda/trigger.py @@ -115,9 +115,9 @@ def parse_event_source(event: dict) -> _EventSource: event_source = None # Get requestContext safely and ensure it's a dictionary - request_context = event.get("requestContext", {}) + request_context = event.get("requestContext") if not isinstance(request_context, dict): - request_context = {} + request_context = None if request_context and request_context.get("stage"): if "domainName" in request_context and detect_lambda_function_url_domain( @@ -291,9 +291,9 @@ def extract_http_tags(event): http_tags = {} # Safely get request_context and ensure it's a dictionary - request_context = event.get("requestContext", {}) + request_context = event.get("requestContext") if not isinstance(request_context, dict): - request_context = {} + request_context = None path = event.get("path") method = event.get("httpMethod") From 1d6d28f318b403358689afa5de79d689a655eb4a Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Thu, 15 May 2025 08:57:26 -0400 Subject: [PATCH 11/17] Add Exception Replay env var to README (#599) * update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 03cd846a..658babc2 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Besides the environment variables supported by dd-trace-py, the datadog-lambda-p | DD_COLD_START_TRACE_SKIP_LIB | optionally skip creating Cold Start Spans for a comma-separated list of libraries. Useful to limit depth or skip known libraries. | `ddtrace.internal.compat,ddtrace.filters` | | DD_CAPTURE_LAMBDA_PAYLOAD | [Captures incoming and outgoing AWS Lambda payloads][1] in the Datadog APM spans for Lambda invocations. | `false` | | DD_CAPTURE_LAMBDA_PAYLOAD_MAX_DEPTH | Determines the level of detail captured from AWS Lambda payloads, which are then assigned as tags for the `aws.lambda` span. It specifies the nesting depth of the JSON payload structure to process. Once the specified maximum depth is reached, the tag's value is set to the stringified value of any nested elements beyond this level.
For example, given the input payload:
{
"lv1" : {
"lv2": {
"lv3": "val"
}
}
}
If the depth is set to `2`, the resulting tag's key is set to `function.request.lv1.lv2` and the value is `{\"lv3\": \"val\"}`.
If the depth is set to `0`, the resulting tag's key is set to `function.request` and value is `{\"lv1\":{\"lv2\":{\"lv3\": \"val\"}}}` | `10` | +| DD_EXCEPTION_REPLAY_ENABLED | When set to `true`, the Lambda will run with Error Tracking Exception Replay enabled, capturing local variables. | `false` | ## Opening Issues From 497aadc4dfb08f662559c18f8971da249be0dc62 Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Fri, 16 May 2025 14:22:28 -0400 Subject: [PATCH 12/17] fix flushing Exception Replay (#601) --- datadog_lambda/wrapper.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/datadog_lambda/wrapper.py b/datadog_lambda/wrapper.py index e5460118..86bbf04d 100644 --- a/datadog_lambda/wrapper.py +++ b/datadog_lambda/wrapper.py @@ -58,6 +58,7 @@ ).lower() in ("true", "1") if exception_replay_env_var: from ddtrace.debugging._exception.replay import SpanExceptionHandler + from ddtrace.debugging._uploader import LogsIntakeUploaderV1 logger = logging.getLogger(__name__) @@ -405,6 +406,10 @@ def _after(self, event, context): if llmobs_env_var: LLMObs.flush() + # Flush exception replay + if exception_replay_env_var: + LogsIntakeUploaderV1._instance.periodic() + if self.encode_authorizer_context and is_authorizer_response(self.response): self._inject_authorizer_span_headers( event.get("requestContext", {}).get("requestId") From 676446cf4998caeefcbac011ab23bde4af954b1e Mon Sep 17 00:00:00 2001 From: Joey Zhao <5253430+joeyzhao2018@users.noreply.github.com> Date: Fri, 23 May 2025 11:16:17 +0200 Subject: [PATCH 13/17] v6.110.0 (#602) --- datadog_lambda/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/datadog_lambda/version.py b/datadog_lambda/version.py index c3aaa6b7..9534f0c7 100644 --- a/datadog_lambda/version.py +++ b/datadog_lambda/version.py @@ -1 +1 @@ -__version__ = "6.109.0" +__version__ = "6.110.0" diff --git a/pyproject.toml b/pyproject.toml index cccef63e..ba5bcb17 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "datadog_lambda" -version = "6.109.0" +version = "6.110.0" description = "The Datadog AWS Lambda Library" authors = ["Datadog, Inc. "] license = "Apache-2.0" From 64f81daa97d589f1047d00000c31d85e24e4314d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Fri, 23 May 2025 16:15:23 +0200 Subject: [PATCH 14/17] fix: `http.url` tag to include protocol (#603) * update `http.url` in inferred spans * update `http.url` in trigger tags * fix `resource_names` update * update integration test --- datadog_lambda/tracing.py | 14 +++++--- datadog_lambda/trigger.py | 2 +- .../logs/async-metrics_python310.log | 12 +++---- .../logs/async-metrics_python311.log | 12 +++---- .../logs/async-metrics_python312.log | 12 +++---- .../logs/async-metrics_python313.log | 12 +++---- .../snapshots/logs/async-metrics_python38.log | 12 +++---- .../snapshots/logs/async-metrics_python39.log | 12 +++---- .../snapshots/logs/sync-metrics_python310.log | 12 +++---- .../snapshots/logs/sync-metrics_python311.log | 12 +++---- .../snapshots/logs/sync-metrics_python312.log | 12 +++---- .../snapshots/logs/sync-metrics_python313.log | 12 +++---- .../snapshots/logs/sync-metrics_python38.log | 12 +++---- .../snapshots/logs/sync-metrics_python39.log | 14 ++++---- tests/test_tracing.py | 36 +++++++++---------- tests/test_trigger.py | 12 +++---- tests/test_wrapper.py | 2 +- 17 files changed, 108 insertions(+), 104 deletions(-) diff --git a/datadog_lambda/tracing.py b/datadog_lambda/tracing.py index 9a27673c..4b6f300a 100644 --- a/datadog_lambda/tracing.py +++ b/datadog_lambda/tracing.py @@ -850,13 +850,14 @@ def create_inferred_span_from_lambda_function_url_event(event, context): http = request_context.get("http") method = http.get("method") if http else None path = http.get("path") if http else None + http_url = f"https://{domain}{path}" resource = f"{method} {path}" tags = { "operation_name": "aws.lambda.url", - "http.url": domain + path, + "http.url": http_url, "endpoint": path, "http.method": method, - "resource_names": domain + path, + "resource_names": resource, "request_id": context.aws_request_id, } request_time_epoch = request_context.get("timeEpoch") @@ -948,6 +949,7 @@ def create_inferred_span_from_api_gateway_websocket_event( request_context = event.get("requestContext") domain = request_context.get("domainName") endpoint = request_context.get("routeKey") + http_url = f"https://{domain}{endpoint}" api_id = request_context.get("apiId") service_name = determine_service_name( @@ -955,7 +957,7 @@ def create_inferred_span_from_api_gateway_websocket_event( ) tags = { "operation_name": "aws.apigateway.websocket", - "http.url": domain + endpoint, + "http.url": http_url, "endpoint": endpoint, "resource_names": endpoint, "apiid": api_id, @@ -1007,11 +1009,12 @@ def create_inferred_span_from_api_gateway_event( ) method = event.get("httpMethod") path = event.get("path") + http_url = f"https://{domain}{path}" resource_path = _get_resource_path(event, request_context) resource = f"{method} {resource_path}" tags = { "operation_name": "aws.apigateway.rest", - "http.url": domain + path, + "http.url": http_url, "endpoint": path, "http.method": method, "resource_names": resource, @@ -1073,12 +1076,13 @@ def create_inferred_span_from_http_api_event( http = request_context.get("http") or {} method = http.get("method") path = event.get("rawPath") + http_url = f"https://{domain}{path}" resource_path = _get_resource_path(event, request_context) resource = f"{method} {resource_path}" tags = { "operation_name": "aws.httpapi", "endpoint": path, - "http.url": domain + path, + "http.url": http_url, "http.method": http.get("method"), "http.protocol": http.get("protocol"), "http.source_ip": http.get("sourceIp"), diff --git a/datadog_lambda/trigger.py b/datadog_lambda/trigger.py index a2708a59..14cb06ac 100644 --- a/datadog_lambda/trigger.py +++ b/datadog_lambda/trigger.py @@ -301,7 +301,7 @@ def extract_http_tags(event): if request_context and request_context.get("stage"): domain_name = request_context.get("domainName") if domain_name: - http_tags["http.url"] = domain_name + http_tags["http.url"] = f"https://{domain_name}" path = request_context.get("path") method = request_context.get("httpMethod") diff --git a/tests/integration/snapshots/logs/async-metrics_python310.log b/tests/integration/snapshots/logs/async-metrics_python310.log index 24d3fb5b..0bd7237c 100644 --- a/tests/integration/snapshots/logs/async-metrics_python310.log +++ b/tests/integration/snapshots/logs/async-metrics_python310.log @@ -55,7 +55,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.rest", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com/", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com/", "endpoint": "/", "http.method": "GET", "resource_names": "GET /", @@ -103,7 +103,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com", "http.url_details.path": "/Prod/", "http.method": "GET", "http.route": "/", @@ -594,7 +594,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "_dd.origin": "lambda", "operation_name": "aws.httpapi", "endpoint": "/httpapi/get", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", "http.method": "GET", "http.protocol": "HTTP/1.1", "http.source_ip": "XXXX", @@ -644,7 +644,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX$default", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.url_details.path": "/httpapi/get", "http.method": "GET", "http.route": "/httpapi/get", @@ -1436,7 +1436,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.websocket", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com$default", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com$default", "endpoint": "$default", "resource_names": "$default", "apiid": "XXXX", @@ -1486,7 +1486,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.status_code": "200", "_dd.base_service": "integration-tests-python" }, diff --git a/tests/integration/snapshots/logs/async-metrics_python311.log b/tests/integration/snapshots/logs/async-metrics_python311.log index e4fa66bc..8550a062 100644 --- a/tests/integration/snapshots/logs/async-metrics_python311.log +++ b/tests/integration/snapshots/logs/async-metrics_python311.log @@ -55,7 +55,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.rest", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com/", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com/", "endpoint": "/", "http.method": "GET", "resource_names": "GET /", @@ -103,7 +103,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com", "http.url_details.path": "/Prod/", "http.method": "GET", "http.route": "/", @@ -594,7 +594,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "_dd.origin": "lambda", "operation_name": "aws.httpapi", "endpoint": "/httpapi/get", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", "http.method": "GET", "http.protocol": "HTTP/1.1", "http.source_ip": "XXXX", @@ -644,7 +644,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX$default", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.url_details.path": "/httpapi/get", "http.method": "GET", "http.route": "/httpapi/get", @@ -1436,7 +1436,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.websocket", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com$default", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com$default", "endpoint": "$default", "resource_names": "$default", "apiid": "XXXX", @@ -1486,7 +1486,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.status_code": "200", "_dd.base_service": "integration-tests-python" }, diff --git a/tests/integration/snapshots/logs/async-metrics_python312.log b/tests/integration/snapshots/logs/async-metrics_python312.log index 0d632c6c..57c318ab 100644 --- a/tests/integration/snapshots/logs/async-metrics_python312.log +++ b/tests/integration/snapshots/logs/async-metrics_python312.log @@ -55,7 +55,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.rest", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com/", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com/", "endpoint": "/", "http.method": "GET", "resource_names": "GET /", @@ -103,7 +103,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com", "http.url_details.path": "/Prod/", "http.method": "GET", "http.route": "/", @@ -594,7 +594,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "_dd.origin": "lambda", "operation_name": "aws.httpapi", "endpoint": "/httpapi/get", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", "http.method": "GET", "http.protocol": "HTTP/1.1", "http.source_ip": "XXXX", @@ -644,7 +644,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX$default", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.url_details.path": "/httpapi/get", "http.method": "GET", "http.route": "/httpapi/get", @@ -1436,7 +1436,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.websocket", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com$default", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com$default", "endpoint": "$default", "resource_names": "$default", "apiid": "XXXX", @@ -1486,7 +1486,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.status_code": "200", "_dd.base_service": "integration-tests-python" }, diff --git a/tests/integration/snapshots/logs/async-metrics_python313.log b/tests/integration/snapshots/logs/async-metrics_python313.log index 09070709..9204499b 100644 --- a/tests/integration/snapshots/logs/async-metrics_python313.log +++ b/tests/integration/snapshots/logs/async-metrics_python313.log @@ -55,7 +55,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.rest", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com/", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com/", "endpoint": "/", "http.method": "GET", "resource_names": "GET /", @@ -103,7 +103,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com", "http.url_details.path": "/Prod/", "http.method": "GET", "http.route": "/", @@ -594,7 +594,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "_dd.origin": "lambda", "operation_name": "aws.httpapi", "endpoint": "/httpapi/get", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", "http.method": "GET", "http.protocol": "HTTP/1.1", "http.source_ip": "XXXX", @@ -644,7 +644,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX$default", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.url_details.path": "/httpapi/get", "http.method": "GET", "http.route": "/httpapi/get", @@ -1436,7 +1436,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.websocket", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com$default", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com$default", "endpoint": "$default", "resource_names": "$default", "apiid": "XXXX", @@ -1486,7 +1486,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.status_code": "200", "_dd.base_service": "integration-tests-python" }, diff --git a/tests/integration/snapshots/logs/async-metrics_python38.log b/tests/integration/snapshots/logs/async-metrics_python38.log index 4a506930..e6df054c 100644 --- a/tests/integration/snapshots/logs/async-metrics_python38.log +++ b/tests/integration/snapshots/logs/async-metrics_python38.log @@ -55,7 +55,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.rest", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com/", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com/", "endpoint": "/", "http.method": "GET", "resource_names": "GET /", @@ -103,7 +103,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com", "http.url_details.path": "/Prod/", "http.method": "GET", "http.route": "/", @@ -594,7 +594,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "_dd.origin": "lambda", "operation_name": "aws.httpapi", "endpoint": "/httpapi/get", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", "http.method": "GET", "http.protocol": "HTTP/1.1", "http.source_ip": "XXXX", @@ -644,7 +644,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX$default", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.url_details.path": "/httpapi/get", "http.method": "GET", "http.route": "/httpapi/get", @@ -1436,7 +1436,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.websocket", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com$default", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com$default", "endpoint": "$default", "resource_names": "$default", "apiid": "XXXX", @@ -1486,7 +1486,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.status_code": "200", "_dd.base_service": "integration-tests-python" }, diff --git a/tests/integration/snapshots/logs/async-metrics_python39.log b/tests/integration/snapshots/logs/async-metrics_python39.log index 54081402..9bcb7a85 100644 --- a/tests/integration/snapshots/logs/async-metrics_python39.log +++ b/tests/integration/snapshots/logs/async-metrics_python39.log @@ -55,7 +55,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.rest", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com/", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com/", "endpoint": "/", "http.method": "GET", "resource_names": "GET /", @@ -103,7 +103,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com", "http.url_details.path": "/Prod/", "http.method": "GET", "http.route": "/", @@ -594,7 +594,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "_dd.origin": "lambda", "operation_name": "aws.httpapi", "endpoint": "/httpapi/get", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", "http.method": "GET", "http.protocol": "HTTP/1.1", "http.source_ip": "XXXX", @@ -644,7 +644,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX$default", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.url_details.path": "/httpapi/get", "http.method": "GET", "http.route": "/httpapi/get", @@ -1436,7 +1436,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.websocket", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com$default", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com$default", "endpoint": "$default", "resource_names": "$default", "apiid": "XXXX", @@ -1486,7 +1486,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.status_code": "200", "_dd.base_service": "integration-tests-python" }, diff --git a/tests/integration/snapshots/logs/sync-metrics_python310.log b/tests/integration/snapshots/logs/sync-metrics_python310.log index e2569775..40562a6d 100644 --- a/tests/integration/snapshots/logs/sync-metrics_python310.log +++ b/tests/integration/snapshots/logs/sync-metrics_python310.log @@ -35,7 +35,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.rest", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com/", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com/", "endpoint": "/", "http.method": "GET", "resource_names": "GET /", @@ -83,7 +83,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com", "http.url_details.path": "/Prod/", "http.method": "GET", "http.route": "/", @@ -631,7 +631,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "_dd.origin": "lambda", "operation_name": "aws.httpapi", "endpoint": "/httpapi/get", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", "http.method": "GET", "http.protocol": "HTTP/1.1", "http.source_ip": "XXXX", @@ -681,7 +681,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX$default", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.url_details.path": "/httpapi/get", "http.method": "GET", "http.route": "/httpapi/get", @@ -1568,7 +1568,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.websocket", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com$default", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com$default", "endpoint": "$default", "resource_names": "$default", "apiid": "XXXX", @@ -1618,7 +1618,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.status_code": "200", "_dd.base_service": "integration-tests-python" }, diff --git a/tests/integration/snapshots/logs/sync-metrics_python311.log b/tests/integration/snapshots/logs/sync-metrics_python311.log index 69d4a695..52ec4c85 100644 --- a/tests/integration/snapshots/logs/sync-metrics_python311.log +++ b/tests/integration/snapshots/logs/sync-metrics_python311.log @@ -35,7 +35,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.rest", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com/", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com/", "endpoint": "/", "http.method": "GET", "resource_names": "GET /", @@ -83,7 +83,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com", "http.url_details.path": "/Prod/", "http.method": "GET", "http.route": "/", @@ -631,7 +631,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "_dd.origin": "lambda", "operation_name": "aws.httpapi", "endpoint": "/httpapi/get", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", "http.method": "GET", "http.protocol": "HTTP/1.1", "http.source_ip": "XXXX", @@ -681,7 +681,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX$default", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.url_details.path": "/httpapi/get", "http.method": "GET", "http.route": "/httpapi/get", @@ -1568,7 +1568,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.websocket", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com$default", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com$default", "endpoint": "$default", "resource_names": "$default", "apiid": "XXXX", @@ -1618,7 +1618,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.status_code": "200", "_dd.base_service": "integration-tests-python" }, diff --git a/tests/integration/snapshots/logs/sync-metrics_python312.log b/tests/integration/snapshots/logs/sync-metrics_python312.log index 49bae0a2..3ec0f01f 100644 --- a/tests/integration/snapshots/logs/sync-metrics_python312.log +++ b/tests/integration/snapshots/logs/sync-metrics_python312.log @@ -35,7 +35,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.rest", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com/", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com/", "endpoint": "/", "http.method": "GET", "resource_names": "GET /", @@ -83,7 +83,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com", "http.url_details.path": "/Prod/", "http.method": "GET", "http.route": "/", @@ -631,7 +631,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "_dd.origin": "lambda", "operation_name": "aws.httpapi", "endpoint": "/httpapi/get", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", "http.method": "GET", "http.protocol": "HTTP/1.1", "http.source_ip": "XXXX", @@ -681,7 +681,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX$default", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.url_details.path": "/httpapi/get", "http.method": "GET", "http.route": "/httpapi/get", @@ -1568,7 +1568,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.websocket", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com$default", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com$default", "endpoint": "$default", "resource_names": "$default", "apiid": "XXXX", @@ -1618,7 +1618,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.status_code": "200", "_dd.base_service": "integration-tests-python" }, diff --git a/tests/integration/snapshots/logs/sync-metrics_python313.log b/tests/integration/snapshots/logs/sync-metrics_python313.log index 2f461f6f..d2c20dc0 100644 --- a/tests/integration/snapshots/logs/sync-metrics_python313.log +++ b/tests/integration/snapshots/logs/sync-metrics_python313.log @@ -35,7 +35,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.rest", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com/", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com/", "endpoint": "/", "http.method": "GET", "resource_names": "GET /", @@ -83,7 +83,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com", "http.url_details.path": "/Prod/", "http.method": "GET", "http.route": "/", @@ -631,7 +631,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "_dd.origin": "lambda", "operation_name": "aws.httpapi", "endpoint": "/httpapi/get", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", "http.method": "GET", "http.protocol": "HTTP/1.1", "http.source_ip": "XXXX", @@ -681,7 +681,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX$default", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.url_details.path": "/httpapi/get", "http.method": "GET", "http.route": "/httpapi/get", @@ -1568,7 +1568,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.websocket", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com$default", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com$default", "endpoint": "$default", "resource_names": "$default", "apiid": "XXXX", @@ -1618,7 +1618,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.status_code": "200", "_dd.base_service": "integration-tests-python" }, diff --git a/tests/integration/snapshots/logs/sync-metrics_python38.log b/tests/integration/snapshots/logs/sync-metrics_python38.log index 83e33d33..57a354a6 100644 --- a/tests/integration/snapshots/logs/sync-metrics_python38.log +++ b/tests/integration/snapshots/logs/sync-metrics_python38.log @@ -35,7 +35,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.rest", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com/", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com/", "endpoint": "/", "http.method": "GET", "resource_names": "GET /", @@ -83,7 +83,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com", "http.url_details.path": "/Prod/", "http.method": "GET", "http.route": "/", @@ -631,7 +631,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "_dd.origin": "lambda", "operation_name": "aws.httpapi", "endpoint": "/httpapi/get", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", "http.method": "GET", "http.protocol": "HTTP/1.1", "http.source_ip": "XXXX", @@ -681,7 +681,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX$default", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.url_details.path": "/httpapi/get", "http.method": "GET", "http.route": "/httpapi/get", @@ -1568,7 +1568,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.websocket", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com$default", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com$default", "endpoint": "$default", "resource_names": "$default", "apiid": "XXXX", @@ -1618,7 +1618,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.status_code": "200", "_dd.base_service": "integration-tests-python" }, diff --git a/tests/integration/snapshots/logs/sync-metrics_python39.log b/tests/integration/snapshots/logs/sync-metrics_python39.log index 0a433c34..8b7bb31b 100644 --- a/tests/integration/snapshots/logs/sync-metrics_python39.log +++ b/tests/integration/snapshots/logs/sync-metrics_python39.log @@ -35,7 +35,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.rest", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com/", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com/", "endpoint": "/", "http.method": "GET", "resource_names": "GET /", @@ -83,7 +83,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.us-east-2.amazonaws.com", + "http.url": "https://XXXX.execute-api.us-east-2.amazonaws.com", "http.url_details.path": "/Prod/", "http.method": "GET", "http.route": "/", @@ -377,7 +377,6 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A ] } HTTP POST https://api.datadoghq.com/api/v1/distribution_points Headers: ["Accept-Encoding:gzip, deflate","Accept:*/*","Connection:keep-alive","Content-Encoding:deflate","Content-Length:XXXX","Content-Type:application/json","DD-API-KEY:XXXX","User-Agent:datadogpy/XX (python XX; os linux; arch XXXX)","traceparent:XXX","tracestate:XXX -END Duration: XXXX ms Memory Used: XXXX MB { "traces": [ [ @@ -416,6 +415,7 @@ END Duration: XXXX ms Memory Used: XXXX MB ] ] } +END Duration: XXXX ms Memory Used: XXXX MB START { "m": "aws.lambda.enhanced.invocations", @@ -631,7 +631,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "_dd.origin": "lambda", "operation_name": "aws.httpapi", "endpoint": "/httpapi/get", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com/httpapi/get", "http.method": "GET", "http.protocol": "HTTP/1.1", "http.source_ip": "XXXX", @@ -681,7 +681,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX$default", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.url_details.path": "/httpapi/get", "http.method": "GET", "http.route": "/httpapi/get", @@ -1568,7 +1568,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "runtime-id": "XXXX", "_dd.origin": "lambda", "operation_name": "aws.apigateway.websocket", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com$default", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com$default", "endpoint": "$default", "resource_names": "$default", "apiid": "XXXX", @@ -1618,7 +1618,7 @@ HTTP GET https://www.datadoghq.com/ Headers: ["Accept-Encoding:gzip, deflate","A "span.name": "aws.lambda", "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "XXXX", - "http.url": "XXXX.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://XXXX.execute-api.eu-west-1.amazonaws.com", "http.status_code": "200", "_dd.base_service": "integration-tests-python" }, diff --git a/tests/test_tracing.py b/tests/test_tracing.py index 0a961a62..e38e4ecd 100644 --- a/tests/test_tracing.py +++ b/tests/test_tracing.py @@ -1730,7 +1730,7 @@ def __init__(self, service, start, span_type, parent_name=None, tags=None): "apiname": "1234567890", "endpoint": "/path/to/resource", "http.method": "POST", - "http.url": "70ixmpl4fl.execute-api.us-east-2.amazonaws.com/path/to/resource", + "http.url": "https://70ixmpl4fl.execute-api.us-east-2.amazonaws.com/path/to/resource", "operation_name": "aws.apigateway.rest", "request_id": "123", "resource_names": "POST /{proxy+}", @@ -1752,7 +1752,7 @@ def __init__(self, service, start, span_type, parent_name=None, tags=None): "apiname": "lgxbo6a518", "endpoint": "/http/get", "http.method": "GET", - "http.url": "lgxbo6a518.execute-api.eu-west-1.amazonaws.com/http/get", + "http.url": "https://lgxbo6a518.execute-api.eu-west-1.amazonaws.com/http/get", "operation_name": "aws.apigateway.rest", "request_id": "123", "resource_names": "GET /http/get", @@ -1774,7 +1774,7 @@ def __init__(self, service, start, span_type, parent_name=None, tags=None): "apiname": "lgxbo6a518", "endpoint": "/http/get", "http.method": "GET", - "http.url": "lgxbo6a518.execute-api.eu-west-1.amazonaws.com/http/get", + "http.url": "https://lgxbo6a518.execute-api.eu-west-1.amazonaws.com/http/get", "operation_name": "aws.apigateway.rest", "request_id": "123", "resource_names": "GET /http/get", @@ -1798,7 +1798,7 @@ def __init__(self, service, start, span_type, parent_name=None, tags=None): "http.method": "GET", "http.protocol": "HTTP/1.1", "http.source_ip": "38.122.226.210", - "http.url": "x02yirxc7a.execute-api.eu-west-1.amazonaws.com/httpapi/get", + "http.url": "https://x02yirxc7a.execute-api.eu-west-1.amazonaws.com/httpapi/get", "http.user_agent": "curl/7.64.1", "operation_name": "aws.httpapi", "request_id": "123", @@ -1821,7 +1821,7 @@ def __init__(self, service, start, span_type, parent_name=None, tags=None): "apiname": "mcwkra0ya4", "endpoint": "/user/42", "http.method": "GET", - "http.url": "mcwkra0ya4.execute-api.sa-east-1.amazonaws.com/user/42", + "http.url": "https://mcwkra0ya4.execute-api.sa-east-1.amazonaws.com/user/42", "operation_name": "aws.apigateway.rest", "request_id": "123", "resource_names": "GET /user/{id}", @@ -1843,7 +1843,7 @@ def __init__(self, service, start, span_type, parent_name=None, tags=None): "apiname": "9vj54we5ih", "endpoint": "/user/42", "http.method": "GET", - "http.url": "9vj54we5ih.execute-api.sa-east-1.amazonaws.com/user/42", + "http.url": "https://9vj54we5ih.execute-api.sa-east-1.amazonaws.com/user/42", "operation_name": "aws.httpapi", "request_id": "123", "resource_names": "GET /user/{id}", @@ -1866,7 +1866,7 @@ def __init__(self, service, start, span_type, parent_name=None, tags=None): "connection_id": "Fc5SzcoYGjQCJlg=", "endpoint": "$default", "event_type": "MESSAGE", - "http.url": "p62c47itsb.execute-api.eu-west-1.amazonaws.com$default", + "http.url": "https://p62c47itsb.execute-api.eu-west-1.amazonaws.com$default", "message_direction": "IN", "operation_name": "aws.apigateway.websocket", "request_id": "123", @@ -1890,7 +1890,7 @@ def __init__(self, service, start, span_type, parent_name=None, tags=None): "connection_id": "Fc2tgfl3mjQCJfA=", "endpoint": "$connect", "event_type": "CONNECT", - "http.url": "p62c47itsb.execute-api.eu-west-1.amazonaws.com$connect", + "http.url": "https://p62c47itsb.execute-api.eu-west-1.amazonaws.com$connect", "message_direction": "IN", "operation_name": "aws.apigateway.websocket", "request_id": "123", @@ -1914,7 +1914,7 @@ def __init__(self, service, start, span_type, parent_name=None, tags=None): "connection_id": "Fc2tgfl3mjQCJfA=", "endpoint": "$disconnect", "event_type": "DISCONNECT", - "http.url": "p62c47itsb.execute-api.eu-west-1.amazonaws.com$disconnect", + "http.url": "https://p62c47itsb.execute-api.eu-west-1.amazonaws.com$disconnect", "message_direction": "IN", "operation_name": "aws.apigateway.websocket", "request_id": "123", @@ -2112,7 +2112,7 @@ def __init__(self, service, start, span_type, parent_name=None, tags=None): "apiname": "None", "endpoint": "/path/to/resource", "http.method": "POST", - "http.url": "70ixmpl4fl.execute-api.us-east-2.amazonaws.com/path/to/resource", + "http.url": "https://70ixmpl4fl.execute-api.us-east-2.amazonaws.com/path/to/resource", "operation_name": "aws.apigateway.rest", "request_id": "123", "resource_names": "POST /{proxy+}", @@ -2135,7 +2135,7 @@ def __init__(self, service, start, span_type, parent_name=None, tags=None): "apiname": "amddr1rix9", "endpoint": "/hello", "http.method": "GET", - "http.url": "amddr1rix9.execute-api.eu-west-1.amazonaws.com/hello", + "http.url": "https://amddr1rix9.execute-api.eu-west-1.amazonaws.com/hello", "operation_name": "aws.apigateway.rest", "request_id": "123", "resource_names": "GET /hello", @@ -2157,7 +2157,7 @@ def __init__(self, service, start, span_type, parent_name=None, tags=None): "apiname": "amddr1rix9", "endpoint": "/hello", "http.method": "GET", - "http.url": "amddr1rix9.execute-api.eu-west-1.amazonaws.com/hello", + "http.url": "https://amddr1rix9.execute-api.eu-west-1.amazonaws.com/hello", "operation_name": "aws.apigateway.rest", "request_id": "123", "resource_names": "GET /hello", @@ -2180,7 +2180,7 @@ def __init__(self, service, start, span_type, parent_name=None, tags=None): "apiname": "amddr1rix9", "endpoint": "/hello", "http.method": "GET", - "http.url": "amddr1rix9.execute-api.eu-west-1.amazonaws.com/hello", + "http.url": "https://amddr1rix9.execute-api.eu-west-1.amazonaws.com/hello", "operation_name": "aws.apigateway.rest", "request_id": "123", "resource_names": "GET /hello", @@ -2202,7 +2202,7 @@ def __init__(self, service, start, span_type, parent_name=None, tags=None): "apiname": "amddr1rix9", "endpoint": "/hello", "http.method": "GET", - "http.url": "amddr1rix9.execute-api.eu-west-1.amazonaws.com/hello", + "http.url": "https://amddr1rix9.execute-api.eu-west-1.amazonaws.com/hello", "operation_name": "aws.apigateway.rest", "request_id": "123", "resource_names": "GET /hello", @@ -2224,7 +2224,7 @@ def __init__(self, service, start, span_type, parent_name=None, tags=None): "apiname": "amddr1rix9", "endpoint": "/hello", "http.method": "GET", - "http.url": "amddr1rix9.execute-api.eu-west-1.amazonaws.com/hello", + "http.url": "https://amddr1rix9.execute-api.eu-west-1.amazonaws.com/hello", "operation_name": "aws.httpapi", "request_id": "123", "resource_names": "GET /hello", @@ -2246,7 +2246,7 @@ def __init__(self, service, start, span_type, parent_name=None, tags=None): "apiname": "amddr1rix9", "endpoint": "/hello", "http.method": "GET", - "http.url": "amddr1rix9.execute-api.eu-west-1.amazonaws.com/hello", + "http.url": "https://amddr1rix9.execute-api.eu-west-1.amazonaws.com/hello", "operation_name": "aws.httpapi", "request_id": "123", "resource_names": "GET /hello", @@ -2270,7 +2270,7 @@ def __init__(self, service, start, span_type, parent_name=None, tags=None): "connection_id": "ZLr9QeNLmjQCIZA=", "endpoint": "$connect", "event_type": "CONNECT", - "http.url": "amddr1rix9.execute-api.eu-west-1.amazonaws.com$connect", + "http.url": "https://amddr1rix9.execute-api.eu-west-1.amazonaws.com$connect", "message_direction": "IN", "operation_name": "aws.apigateway.websocket", "request_id": "123", @@ -2294,7 +2294,7 @@ def __init__(self, service, start, span_type, parent_name=None, tags=None): "connection_id": "ZLwtceO1mjQCI8Q=", "endpoint": "main", "event_type": "MESSAGE", - "http.url": "amddr1rix9.execute-api.eu-west-1.amazonaws.commain", + "http.url": "https://amddr1rix9.execute-api.eu-west-1.amazonaws.commain", "message_direction": "IN", "operation_name": "aws.apigateway.websocket", "request_id": "123", diff --git a/tests/test_trigger.py b/tests/test_trigger.py index b4da7ff0..c12e8f5c 100644 --- a/tests/test_trigger.py +++ b/tests/test_trigger.py @@ -294,7 +294,7 @@ def test_extract_trigger_tags_api_gateway(self): { "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "arn:aws:apigateway:us-west-1::/restapis/1234567890/stages/prod", - "http.url": "70ixmpl4fl.execute-api.us-east-2.amazonaws.com", + "http.url": "https://70ixmpl4fl.execute-api.us-east-2.amazonaws.com", "http.url_details.path": "/prod/path/to/resource", "http.method": "POST", "http.route": "/{proxy+}", @@ -313,7 +313,7 @@ def test_extract_trigger_tags_api_gateway_non_proxy(self): { "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "arn:aws:apigateway:us-west-1::/restapis/lgxbo6a518/stages/dev", - "http.url": "lgxbo6a518.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://lgxbo6a518.execute-api.eu-west-1.amazonaws.com", "http.url_details.path": "/dev/http/get", "http.method": "GET", "http.route": "/http/get", @@ -332,7 +332,7 @@ def test_extract_trigger_tags_api_gateway_websocket_connect(self): { "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "arn:aws:apigateway:us-west-1::/restapis/p62c47itsb/stages/dev", - "http.url": "p62c47itsb.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://p62c47itsb.execute-api.eu-west-1.amazonaws.com", }, ) @@ -348,7 +348,7 @@ def test_extract_trigger_tags_api_gateway_websocket_default(self): { "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "arn:aws:apigateway:us-west-1::/restapis/p62c47itsb/stages/dev", - "http.url": "p62c47itsb.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://p62c47itsb.execute-api.eu-west-1.amazonaws.com", }, ) @@ -364,7 +364,7 @@ def test_extract_trigger_tags_api_gateway_websocket_disconnect(self): { "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "arn:aws:apigateway:us-west-1::/restapis/p62c47itsb/stages/dev", - "http.url": "p62c47itsb.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://p62c47itsb.execute-api.eu-west-1.amazonaws.com", }, ) @@ -380,7 +380,7 @@ def test_extract_trigger_tags_api_gateway_http_api(self): { "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "arn:aws:apigateway:us-west-1::/restapis/x02yirxc7a/stages/$default", - "http.url": "x02yirxc7a.execute-api.eu-west-1.amazonaws.com", + "http.url": "https://x02yirxc7a.execute-api.eu-west-1.amazonaws.com", "http.url_details.path": "/httpapi/get", "http.method": "GET", "http.route": "/httpapi/get", diff --git a/tests/test_wrapper.py b/tests/test_wrapper.py index 4b243036..f46b365e 100644 --- a/tests/test_wrapper.py +++ b/tests/test_wrapper.py @@ -283,7 +283,7 @@ def test_5xx_sends_errors_metric_and_set_tags(self, mock_extract_trigger_tags): mock_extract_trigger_tags.return_value = { "function_trigger.event_source": "api-gateway", "function_trigger.event_source_arn": "arn:aws:apigateway:us-west-1::/restapis/1234567890/stages/prod", - "http.url": "70ixmpl4fl.execute-api.us-east-2.amazonaws.com", + "http.url": "https://70ixmpl4fl.execute-api.us-east-2.amazonaws.com", "http.url_details.path": "/prod/path/to/resource", "http.method": "GET", } From 87f2314928fb839886aefe09cf6a1b7c7a0a7988 Mon Sep 17 00:00:00 2001 From: happynancee <120061598+happynancee@users.noreply.github.com> Date: Mon, 2 Jun 2025 07:50:36 -0700 Subject: [PATCH 15/17] update codeowners file and make apm serverless co-owners of repo (#596) --- .github/CODEOWNERS | 7 +------ CODEOWNERS | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 26b4b78e..a7f48dfe 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1 @@ -* @DataDog/serverless-aws -datadog_lambda/tracing.py @DataDog/apm-serverless -datadog_lambda/patch.py @DataDog/apm-serverless -datadog_lambda/span_points.py @DataDog/apm-serverless -datadog_lambda/cold_start.py @DataDog/apm-serverless -datadog_lambda/wrapper.py @DataDog/apm-serverless +* @DataDog/serverless-aws @DataDog/apm-serverless diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 index e340f1ed..00000000 --- a/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @DataDog/serverless \ No newline at end of file From 254466cb2c6d749211e74201572ce5a937ec82da Mon Sep 17 00:00:00 2001 From: Yiming Luo Date: Wed, 4 Jun 2025 12:34:10 -0400 Subject: [PATCH 16/17] chore: Use GitHub App for update-deps workflow (#605) --- .github/workflows/update_deps.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update_deps.yml b/.github/workflows/update_deps.yml index 31025402..33a524b2 100644 --- a/.github/workflows/update_deps.yml +++ b/.github/workflows/update_deps.yml @@ -3,14 +3,24 @@ name: update-deps on: schedule: - cron: "0 10 * * *" # Run at 10 am every day + workflow_dispatch: jobs: check: runs-on: ubuntu-latest + environment: + name: protected-main-env steps: + - name: Generate token + id: generate_token + uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + with: + app-id: ${{ secrets.GH_APP_ID }} + private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} + - uses: actions/checkout@v3 with: - ssh-key: ${{ secrets.SSH_PRIVATE_KEY }} + token: ${{ steps.generate_token.outputs.token }} - name: Set up Python uses: actions/setup-python@v4 From 6beb65d63c063ff6fc5e480e7bf076d29f2abf0c Mon Sep 17 00:00:00 2001 From: michael-zhao459 Date: Thu, 5 Jun 2025 15:24:42 -0400 Subject: [PATCH 17/17] feat: Enable sqs -> lambda support for DSM (#604) --------- Co-authored-by: Rey Abolofia --- datadog_lambda/dsm.py | 38 +++++++++++++ datadog_lambda/wrapper.py | 7 +++ tests/test_dsm.py | 112 ++++++++++++++++++++++++++++++++++++++ tests/test_wrapper.py | 60 ++++++++++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 datadog_lambda/dsm.py create mode 100644 tests/test_dsm.py diff --git a/datadog_lambda/dsm.py b/datadog_lambda/dsm.py new file mode 100644 index 00000000..427f5e47 --- /dev/null +++ b/datadog_lambda/dsm.py @@ -0,0 +1,38 @@ +from datadog_lambda import logger +from datadog_lambda.trigger import EventTypes + + +def set_dsm_context(event, event_source): + + if event_source.equals(EventTypes.SQS): + _dsm_set_sqs_context(event) + + +def _dsm_set_sqs_context(event): + from datadog_lambda.wrapper import format_err_with_traceback + from ddtrace.internal.datastreams import data_streams_processor + from ddtrace.internal.datastreams.processor import DsmPathwayCodec + from ddtrace.internal.datastreams.botocore import ( + get_datastreams_context, + calculate_sqs_payload_size, + ) + + records = event.get("Records") + if records is None: + return + processor = data_streams_processor() + + for record in records: + try: + queue_arn = record.get("eventSourceARN", "") + + contextjson = get_datastreams_context(record) + payload_size = calculate_sqs_payload_size(record) + + ctx = DsmPathwayCodec.decode(contextjson, processor) + ctx.set_checkpoint( + ["direction:in", f"topic:{queue_arn}", "type:sqs"], + payload_size=payload_size, + ) + except Exception as e: + logger.error(format_err_with_traceback(e)) diff --git a/datadog_lambda/wrapper.py b/datadog_lambda/wrapper.py index 86bbf04d..0e23b721 100644 --- a/datadog_lambda/wrapper.py +++ b/datadog_lambda/wrapper.py @@ -9,6 +9,7 @@ from importlib import import_module from time import time_ns +from datadog_lambda.dsm import set_dsm_context from datadog_lambda.extension import should_use_extension, flush_extension from datadog_lambda.cold_start import ( set_cold_start, @@ -79,6 +80,7 @@ DD_REQUESTS_SERVICE_NAME = "DD_REQUESTS_SERVICE_NAME" DD_SERVICE = "DD_SERVICE" DD_ENV = "DD_ENV" +DD_DATA_STREAMS_ENABLED = "DD_DATA_STREAMS_ENABLED" def get_env_as_int(env_key, default_value: int) -> int: @@ -190,6 +192,9 @@ def __init__(self, func): self.min_cold_start_trace_duration = get_env_as_int( DD_MIN_COLD_START_DURATION, 3 ) + self.data_streams_enabled = ( + os.environ.get(DD_DATA_STREAMS_ENABLED, "false").lower() == "true" + ) self.local_testing_mode = os.environ.get( DD_LOCAL_TEST, "false" ).lower() in ("true", "1") @@ -322,6 +327,8 @@ def _before(self, event, context): self.inferred_span = create_inferred_span( event, context, event_source, self.decode_authorizer_context ) + if self.data_streams_enabled: + set_dsm_context(event, event_source) self.span = create_function_execution_span( context=context, function_name=self.function_name, diff --git a/tests/test_dsm.py b/tests/test_dsm.py new file mode 100644 index 00000000..544212d8 --- /dev/null +++ b/tests/test_dsm.py @@ -0,0 +1,112 @@ +import unittest +from unittest.mock import patch, MagicMock + +from datadog_lambda.dsm import set_dsm_context, _dsm_set_sqs_context +from datadog_lambda.trigger import EventTypes, _EventSource + + +class TestDsmSQSContext(unittest.TestCase): + def setUp(self): + patcher = patch("datadog_lambda.dsm._dsm_set_sqs_context") + self.mock_dsm_set_sqs_context = patcher.start() + self.addCleanup(patcher.stop) + + patcher = patch("ddtrace.internal.datastreams.data_streams_processor") + self.mock_data_streams_processor = patcher.start() + self.addCleanup(patcher.stop) + + patcher = patch("ddtrace.internal.datastreams.botocore.get_datastreams_context") + self.mock_get_datastreams_context = patcher.start() + self.mock_get_datastreams_context.return_value = {} + self.addCleanup(patcher.stop) + + patcher = patch( + "ddtrace.internal.datastreams.botocore.calculate_sqs_payload_size" + ) + self.mock_calculate_sqs_payload_size = patcher.start() + self.mock_calculate_sqs_payload_size.return_value = 100 + self.addCleanup(patcher.stop) + + patcher = patch("ddtrace.internal.datastreams.processor.DsmPathwayCodec.decode") + self.mock_dsm_pathway_codec_decode = patcher.start() + self.addCleanup(patcher.stop) + + def test_non_sqs_event_source_does_nothing(self): + """Test that non-SQS event sources don't trigger DSM context setting""" + event = {} + # Use Unknown Event Source + event_source = _EventSource(EventTypes.UNKNOWN) + set_dsm_context(event, event_source) + + # DSM context should not be set for non-SQS events + self.mock_dsm_set_sqs_context.assert_not_called() + + def test_sqs_event_with_no_records_does_nothing(self): + """Test that events where Records is None don't trigger DSM processing""" + events_with_no_records = [ + {}, + {"Records": None}, + {"someOtherField": "value"}, + ] + + for event in events_with_no_records: + _dsm_set_sqs_context(event) + self.mock_data_streams_processor.assert_not_called() + + def test_sqs_event_triggers_dsm_sqs_context(self): + """Test that SQS event sources trigger the SQS-specific DSM context function""" + sqs_event = { + "Records": [ + { + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:my-queue", + "body": "Hello from SQS!", + } + ] + } + + event_source = _EventSource(EventTypes.SQS) + set_dsm_context(sqs_event, event_source) + + self.mock_dsm_set_sqs_context.assert_called_once_with(sqs_event) + + def test_sqs_multiple_records_process_each_record(self): + """Test that each record in an SQS event gets processed individually""" + multi_record_event = { + "Records": [ + { + "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:queue1", + "body": "Message 1", + }, + { + "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:queue2", + "body": "Message 2", + }, + { + "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:queue3", + "body": "Message 3", + }, + ] + } + + mock_context = MagicMock() + self.mock_dsm_pathway_codec_decode.return_value = mock_context + + _dsm_set_sqs_context(multi_record_event) + + self.assertEqual(mock_context.set_checkpoint.call_count, 3) + + calls = mock_context.set_checkpoint.call_args_list + expected_arns = [ + "arn:aws:sqs:us-east-1:123456789012:queue1", + "arn:aws:sqs:us-east-1:123456789012:queue2", + "arn:aws:sqs:us-east-1:123456789012:queue3", + ] + + for i, call in enumerate(calls): + args, kwargs = call + tags = args[0] + self.assertIn("direction:in", tags) + self.assertIn(f"topic:{expected_arns[i]}", tags) + self.assertIn("type:sqs", tags) + self.assertEqual(kwargs["payload_size"], 100) diff --git a/tests/test_wrapper.py b/tests/test_wrapper.py index f46b365e..f482fa3d 100644 --- a/tests/test_wrapper.py +++ b/tests/test_wrapper.py @@ -76,6 +76,10 @@ def setUp(self): self.mock_dd_lambda_layer_tag = patcher.start() self.addCleanup(patcher.stop) + patcher = patch("datadog_lambda.wrapper.set_dsm_context") + self.mock_set_dsm_context = patcher.start() + self.addCleanup(patcher.stop) + def test_datadog_lambda_wrapper(self): wrapper.dd_tracing_enabled = False @@ -563,6 +567,62 @@ def return_type_test(event, context): self.assertEqual(result, test_result) self.assertFalse(MockPrintExc.called) + def test_set_dsm_context_called_when_DSM_and_tracing_enabled(self): + os.environ["DD_DATA_STREAMS_ENABLED"] = "true" + wrapper.dd_tracing_enabled = True + + @wrapper.datadog_lambda_wrapper + def lambda_handler(event, context): + return "ok" + + result = lambda_handler({}, get_mock_context()) + self.assertEqual(result, "ok") + self.mock_set_dsm_context.assert_called_once() + + del os.environ["DD_DATA_STREAMS_ENABLED"] + + def test_set_dsm_context_not_called_when_only_DSM_enabled(self): + os.environ["DD_DATA_STREAMS_ENABLED"] = "true" + wrapper.dd_tracing_enabled = False + + @wrapper.datadog_lambda_wrapper + def lambda_handler(event, context): + return "ok" + + result = lambda_handler({}, get_mock_context()) + self.assertEqual(result, "ok") + self.mock_set_dsm_context.assert_not_called() + + del os.environ["DD_DATA_STREAMS_ENABLED"] + + def test_set_dsm_context_not_called_when_only_tracing_enabled(self): + os.environ["DD_DATA_STREAMS_ENABLED"] = "false" + wrapper.dd_tracing_enabled = True + + @wrapper.datadog_lambda_wrapper + def lambda_handler(event, context): + return "ok" + + result = lambda_handler({}, get_mock_context()) + self.assertEqual(result, "ok") + self.mock_set_dsm_context.assert_not_called() + + del os.environ["DD_DATA_STREAMS_ENABLED"] + + def test_set_dsm_context_not_called_when_tracing_and_DSM_disabled(self): + os.environ["DD_DATA_STREAMS_ENABLED"] = "false" + wrapper.dd_tracing_enabled = False + + @wrapper.datadog_lambda_wrapper + def lambda_handler(event, context): + return "ok" + + result = lambda_handler({}, get_mock_context()) + self.assertEqual(result, "ok") + self.mock_set_dsm_context.assert_not_called() + + del os.environ["DD_DATA_STREAMS_ENABLED"] + class TestLambdaDecoratorSettings(unittest.TestCase): def test_some_envs_should_depend_on_dd_tracing_enabled(self):