datadog_lambda 6.107.0__tar.gz → 6.109.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/PKG-INFO +1 -1
  2. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/api.py +14 -8
  3. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/dogstatsd.py +17 -10
  4. datadog_lambda-6.109.0/datadog_lambda/fips.py +19 -0
  5. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/metric.py +96 -65
  6. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/stats_writer.py +1 -1
  7. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/statsd_writer.py +3 -3
  8. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/thread_stats_writer.py +2 -1
  9. datadog_lambda-6.109.0/datadog_lambda/version.py +1 -0
  10. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/wrapper.py +1 -8
  11. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/pyproject.toml +1 -1
  12. datadog_lambda-6.107.0/datadog_lambda/version.py +0 -1
  13. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/LICENSE +0 -0
  14. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/LICENSE-3rdparty.csv +0 -0
  15. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/NOTICE +0 -0
  16. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/README.md +0 -0
  17. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/__init__.py +0 -0
  18. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/cold_start.py +0 -0
  19. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/constants.py +0 -0
  20. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/extension.py +0 -0
  21. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/handler.py +0 -0
  22. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/logger.py +0 -0
  23. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/module_name.py +0 -0
  24. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/patch.py +0 -0
  25. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/span_pointers.py +0 -0
  26. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/tag_object.py +0 -0
  27. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/tags.py +0 -0
  28. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/tracing.py +0 -0
  29. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/trigger.py +0 -0
  30. {datadog_lambda-6.107.0 → datadog_lambda-6.109.0}/datadog_lambda/xray.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: datadog_lambda
3
- Version: 6.107.0
3
+ Version: 6.109.0
4
4
  Summary: The Datadog AWS Lambda Library
5
5
  Home-page: https://github.com/DataDog/datadog-lambda-python
6
6
  License: Apache-2.0
@@ -1,5 +1,7 @@
1
- import os
2
1
  import logging
2
+ import os
3
+
4
+ from datadog_lambda.fips import fips_mode_enabled
3
5
 
4
6
  logger = logging.getLogger(__name__)
5
7
  KMS_ENCRYPTION_CONTEXT_KEY = "LambdaFunctionName"
@@ -7,9 +9,10 @@ api_key = None
7
9
 
8
10
 
9
11
  def decrypt_kms_api_key(kms_client, ciphertext):
10
- from botocore.exceptions import ClientError
11
12
  import base64
12
13
 
14
+ from botocore.exceptions import ClientError
15
+
13
16
  """
14
17
  Decodes and deciphers the base64-encoded ciphertext given as a parameter using KMS.
15
18
  For this to work properly, the Lambda function must have the appropriate IAM permissions.
@@ -63,10 +66,9 @@ def get_api_key() -> str:
63
66
  DD_API_KEY = os.environ.get("DD_API_KEY", os.environ.get("DATADOG_API_KEY", ""))
64
67
 
65
68
  LAMBDA_REGION = os.environ.get("AWS_REGION", "")
66
- is_gov_region = LAMBDA_REGION.startswith("us-gov-")
67
- if is_gov_region:
69
+ if fips_mode_enabled:
68
70
  logger.debug(
69
- "Govcloud region detected. Using FIPs endpoints for secrets management."
71
+ "FIPS mode is enabled, using FIPS endpoints for secrets management."
70
72
  )
71
73
 
72
74
  if DD_API_KEY_SECRET_ARN:
@@ -80,7 +82,7 @@ def get_api_key() -> str:
80
82
  return ""
81
83
  endpoint_url = (
82
84
  f"https://secretsmanager-fips.{secrets_region}.amazonaws.com"
83
- if is_gov_region
85
+ if fips_mode_enabled
84
86
  else None
85
87
  )
86
88
  secrets_manager_client = _boto3_client(
@@ -92,7 +94,9 @@ def get_api_key() -> str:
92
94
  elif DD_API_KEY_SSM_NAME:
93
95
  # SSM endpoints: https://docs.aws.amazon.com/general/latest/gr/ssm.html
94
96
  fips_endpoint = (
95
- f"https://ssm-fips.{LAMBDA_REGION}.amazonaws.com" if is_gov_region else None
97
+ f"https://ssm-fips.{LAMBDA_REGION}.amazonaws.com"
98
+ if fips_mode_enabled
99
+ else None
96
100
  )
97
101
  ssm_client = _boto3_client("ssm", endpoint_url=fips_endpoint)
98
102
  api_key = ssm_client.get_parameter(
@@ -101,7 +105,9 @@ def get_api_key() -> str:
101
105
  elif DD_KMS_API_KEY:
102
106
  # KMS endpoints: https://docs.aws.amazon.com/general/latest/gr/kms.html
103
107
  fips_endpoint = (
104
- f"https://kms-fips.{LAMBDA_REGION}.amazonaws.com" if is_gov_region else None
108
+ f"https://kms-fips.{LAMBDA_REGION}.amazonaws.com"
109
+ if fips_mode_enabled
110
+ else None
105
111
  )
106
112
  kms_client = _boto3_client("kms", endpoint_url=fips_endpoint)
107
113
  api_key = decrypt_kms_api_key(kms_client, DD_KMS_API_KEY)
@@ -1,11 +1,10 @@
1
+ import errno
1
2
  import logging
2
3
  import os
3
- import socket
4
- import errno
5
4
  import re
5
+ import socket
6
6
  from threading import Lock
7
7
 
8
-
9
8
  MIN_SEND_BUFFER_SIZE = 32 * 1024
10
9
  log = logging.getLogger("datadog_lambda.dogstatsd")
11
10
 
@@ -55,14 +54,21 @@ class DogStatsd(object):
55
54
 
56
55
  return sock
57
56
 
58
- def distribution(self, metric, value, tags=None):
57
+ def distribution(self, metric, value, tags=None, timestamp=None):
59
58
  """
60
- Send a global distribution value, optionally setting tags.
59
+ Send a global distribution value, optionally setting tags. The optional
60
+ timestamp should be an integer representing seconds since the epoch
61
+ (January 1, 1970, 00:00:00 UTC).
61
62
 
62
63
  >>> statsd.distribution("uploaded.file.size", 1445)
63
64
  >>> statsd.distribution("album.photo.count", 26, tags=["gender:female"])
65
+ >>> statsd.distribution(
66
+ >>> "historic.file.count",
67
+ >>> 5,
68
+ >>> timestamp=int(datetime(2020, 2, 14, 12, 0, 0).timestamp()),
69
+ >>> )
64
70
  """
65
- self._report(metric, "d", value, tags)
71
+ self._report(metric, "d", value, tags, timestamp)
66
72
 
67
73
  def close_socket(self):
68
74
  """
@@ -84,20 +90,21 @@ class DogStatsd(object):
84
90
  for tag in tag_list
85
91
  ]
86
92
 
87
- def _serialize_metric(self, metric, metric_type, value, tags):
93
+ def _serialize_metric(self, metric, metric_type, value, tags, timestamp):
88
94
  # Create/format the metric packet
89
- return "%s:%s|%s%s" % (
95
+ return "%s:%s|%s%s%s" % (
90
96
  metric,
91
97
  value,
92
98
  metric_type,
93
99
  ("|#" + ",".join(self.normalize_tags(tags))) if tags else "",
100
+ ("|T" + str(int(timestamp))) if timestamp is not None else "",
94
101
  )
95
102
 
96
- def _report(self, metric, metric_type, value, tags):
103
+ def _report(self, metric, metric_type, value, tags, timestamp):
97
104
  if value is None:
98
105
  return
99
106
 
100
- payload = self._serialize_metric(metric, metric_type, value, tags)
107
+ payload = self._serialize_metric(metric, metric_type, value, tags, timestamp)
101
108
 
102
109
  # Send it
103
110
  self._send_to_server(payload)
@@ -0,0 +1,19 @@
1
+ import logging
2
+ import os
3
+
4
+ is_gov_region = os.environ.get("AWS_REGION", "").startswith("us-gov-")
5
+
6
+ fips_mode_enabled = (
7
+ os.environ.get(
8
+ "DD_LAMBDA_FIPS_MODE",
9
+ "true" if is_gov_region else "false",
10
+ ).lower()
11
+ == "true"
12
+ )
13
+
14
+ if is_gov_region or fips_mode_enabled:
15
+ logger = logging.getLogger(__name__)
16
+ logger.debug(
17
+ "Python Lambda Layer FIPS mode is %s.",
18
+ "enabled" if fips_mode_enabled else "not enabled",
19
+ )
@@ -3,37 +3,66 @@
3
3
  # This product includes software developed at Datadog (https://www.datadoghq.com/).
4
4
  # Copyright 2019 Datadog, Inc.
5
5
 
6
+ import enum
7
+ import logging
6
8
  import os
7
9
  import time
8
- import logging
9
- import ujson as json
10
10
  from datetime import datetime, timedelta
11
11
 
12
+ import ujson as json
13
+
12
14
  from datadog_lambda.extension import should_use_extension
13
- from datadog_lambda.tags import get_enhanced_metrics_tags, dd_lambda_layer_tag
15
+ from datadog_lambda.fips import fips_mode_enabled
16
+ from datadog_lambda.tags import dd_lambda_layer_tag, get_enhanced_metrics_tags
14
17
 
15
18
  logger = logging.getLogger(__name__)
16
19
 
17
- lambda_stats = None
18
- extension_thread_stats = None
19
20
 
20
- flush_in_thread = os.environ.get("DD_FLUSH_IN_THREAD", "").lower() == "true"
21
+ class MetricsHandler(enum.Enum):
22
+ EXTENSION = "extension"
23
+ FORWARDER = "forwarder"
24
+ DATADOG_API = "datadog_api"
25
+ NO_METRICS = "no_metrics"
26
+
27
+
28
+ def _select_metrics_handler():
29
+ if should_use_extension:
30
+ return MetricsHandler.EXTENSION
31
+ if os.environ.get("DD_FLUSH_TO_LOG", "").lower() == "true":
32
+ return MetricsHandler.FORWARDER
33
+
34
+ if fips_mode_enabled:
35
+ logger.debug(
36
+ "With FIPS mode enabled, the Datadog API metrics handler is unavailable."
37
+ )
38
+ return MetricsHandler.NO_METRICS
39
+
40
+ return MetricsHandler.DATADOG_API
41
+
21
42
 
22
- if should_use_extension:
43
+ metrics_handler = _select_metrics_handler()
44
+ logger.debug("identified primary metrics handler as %s", metrics_handler)
45
+
46
+
47
+ lambda_stats = None
48
+ if metrics_handler == MetricsHandler.EXTENSION:
23
49
  from datadog_lambda.statsd_writer import StatsDWriter
24
50
 
25
51
  lambda_stats = StatsDWriter()
26
- else:
52
+
53
+ elif metrics_handler == MetricsHandler.DATADOG_API:
27
54
  # Periodical flushing in a background thread is NOT guaranteed to succeed
28
55
  # and leads to data loss. When disabled, metrics are only flushed at the
29
56
  # end of invocation. To make metrics submitted from a long-running Lambda
30
57
  # function available sooner, consider using the Datadog Lambda extension.
31
- from datadog_lambda.thread_stats_writer import ThreadStatsWriter
32
58
  from datadog_lambda.api import init_api
59
+ from datadog_lambda.thread_stats_writer import ThreadStatsWriter
33
60
 
61
+ flush_in_thread = os.environ.get("DD_FLUSH_IN_THREAD", "").lower() == "true"
34
62
  init_api()
35
63
  lambda_stats = ThreadStatsWriter(flush_in_thread)
36
64
 
65
+
37
66
  enhanced_metrics_enabled = (
38
67
  os.environ.get("DD_ENHANCED_METRICS", "true").lower() == "true"
39
68
  )
@@ -44,16 +73,19 @@ def lambda_metric(metric_name, value, timestamp=None, tags=None, force_async=Fal
44
73
  Submit a data point to Datadog distribution metrics.
45
74
  https://docs.datadoghq.com/graphing/metrics/distributions/
46
75
 
47
- When DD_FLUSH_TO_LOG is True, write metric to log, and
48
- wait for the Datadog Log Forwarder Lambda function to submit
49
- the metrics asynchronously.
76
+ If the Datadog Lambda Extension is present, metrics are submitted to its
77
+ dogstatsd endpoint.
78
+
79
+ When DD_FLUSH_TO_LOG is True or force_async is True, write metric to log,
80
+ and wait for the Datadog Log Forwarder Lambda function to submit the
81
+ metrics asynchronously.
50
82
 
51
83
  Otherwise, the metrics will be submitted to the Datadog API
52
84
  periodically and at the end of the function execution in a
53
85
  background thread.
54
86
 
55
- Note that if the extension is present, it will override the DD_FLUSH_TO_LOG value
56
- and always use the layer to send metrics to the extension
87
+ Note that if the extension is present, it will override the DD_FLUSH_TO_LOG
88
+ value and always use the layer to send metrics to the extension
57
89
  """
58
90
  if not metric_name or not isinstance(metric_name, str):
59
91
  logger.warning(
@@ -71,56 +103,66 @@ def lambda_metric(metric_name, value, timestamp=None, tags=None, force_async=Fal
71
103
  )
72
104
  return
73
105
 
74
- flush_to_logs = os.environ.get("DD_FLUSH_TO_LOG", "").lower() == "true"
75
106
  tags = [] if tags is None else list(tags)
76
107
  tags.append(dd_lambda_layer_tag)
77
108
 
78
- if should_use_extension and timestamp is not None:
79
- # The extension does not support timestamps for distributions so we create a
80
- # a thread stats writer to submit metrics with timestamps to the API
81
- timestamp_ceiling = int(
82
- (datetime.now() - timedelta(hours=4)).timestamp()
83
- ) # 4 hours ago
84
- if isinstance(timestamp, datetime):
85
- timestamp = int(timestamp.timestamp())
86
- if timestamp_ceiling > timestamp:
87
- logger.warning(
88
- "Timestamp %s is older than 4 hours, not submitting metric %s",
89
- timestamp,
90
- metric_name,
91
- )
92
- return
93
- global extension_thread_stats
94
- if extension_thread_stats is None:
95
- from datadog_lambda.thread_stats_writer import ThreadStatsWriter
96
- from datadog_lambda.api import init_api
97
-
98
- init_api()
99
- extension_thread_stats = ThreadStatsWriter(flush_in_thread)
100
-
101
- extension_thread_stats.distribution(
102
- metric_name, value, tags=tags, timestamp=timestamp
103
- )
104
- return
109
+ if metrics_handler == MetricsHandler.EXTENSION:
110
+ if timestamp is not None:
111
+ if isinstance(timestamp, datetime):
112
+ timestamp = int(timestamp.timestamp())
113
+
114
+ else:
115
+ try:
116
+ timestamp = int(timestamp)
117
+ except Exception:
118
+ logger.debug(
119
+ "Ignoring metric submission for metric '%s' because the timestamp cannot "
120
+ "be turned into an integer: %r",
121
+ metric_name,
122
+ timestamp,
123
+ )
124
+ return
125
+
126
+ timestamp_floor = int((datetime.now() - timedelta(hours=4)).timestamp())
127
+ if timestamp < timestamp_floor:
128
+ logger.warning(
129
+ "Timestamp %s is older than 4 hours, not submitting metric %s",
130
+ timestamp,
131
+ metric_name,
132
+ )
133
+ return
105
134
 
106
- if should_use_extension:
107
135
  logger.debug(
108
136
  "Sending metric %s value %s to Datadog via extension", metric_name, value
109
137
  )
110
138
  lambda_stats.distribution(metric_name, value, tags=tags, timestamp=timestamp)
139
+
140
+ elif force_async or (metrics_handler == MetricsHandler.FORWARDER):
141
+ write_metric_point_to_stdout(metric_name, value, timestamp=timestamp, tags=tags)
142
+
143
+ elif metrics_handler == MetricsHandler.DATADOG_API:
144
+ lambda_stats.distribution(metric_name, value, tags=tags, timestamp=timestamp)
145
+
146
+ elif metrics_handler == MetricsHandler.NO_METRICS:
147
+ logger.debug(
148
+ "Metric %s cannot be submitted because the metrics handler is disabled",
149
+ metric_name,
150
+ ),
151
+
111
152
  else:
112
- if flush_to_logs or force_async:
113
- write_metric_point_to_stdout(
114
- metric_name, value, timestamp=timestamp, tags=tags
115
- )
116
- else:
117
- lambda_stats.distribution(
118
- metric_name, value, tags=tags, timestamp=timestamp
119
- )
153
+ # This should be qutie impossible, but let's at least log a message if
154
+ # it somehow happens.
155
+ logger.debug(
156
+ "Metric %s cannot be submitted because the metrics handler is not configured: %s",
157
+ metric_name,
158
+ metrics_handler,
159
+ )
120
160
 
121
161
 
122
- def write_metric_point_to_stdout(metric_name, value, timestamp=None, tags=[]):
162
+ def write_metric_point_to_stdout(metric_name, value, timestamp=None, tags=None):
123
163
  """Writes the specified metric point to standard output"""
164
+ tags = tags or []
165
+
124
166
  logger.debug(
125
167
  "Sending metric %s value %s to Datadog via log forwarder", metric_name, value
126
168
  )
@@ -138,19 +180,8 @@ def write_metric_point_to_stdout(metric_name, value, timestamp=None, tags=[]):
138
180
 
139
181
 
140
182
  def flush_stats(lambda_context=None):
141
- lambda_stats.flush()
142
-
143
- if extension_thread_stats is not None:
144
- tags = None
145
- if lambda_context is not None:
146
- tags = get_enhanced_metrics_tags(lambda_context)
147
- split_arn = lambda_context.invoked_function_arn.split(":")
148
- if len(split_arn) > 7:
149
- # Get rid of the alias
150
- split_arn.pop()
151
- arn = ":".join(split_arn)
152
- tags.append("function_arn:" + arn)
153
- extension_thread_stats.flush(tags)
183
+ if lambda_stats is not None:
184
+ lambda_stats.flush()
154
185
 
155
186
 
156
187
  def submit_enhanced_metric(metric_name, lambda_context):
@@ -1,5 +1,5 @@
1
1
  class StatsWriter:
2
- def distribution(self, metric_name, value, tags=[], timestamp=None):
2
+ def distribution(self, metric_name, value, tags=None, timestamp=None):
3
3
  raise NotImplementedError()
4
4
 
5
5
  def flush(self):
@@ -1,5 +1,5 @@
1
- from datadog_lambda.stats_writer import StatsWriter
2
1
  from datadog_lambda.dogstatsd import statsd
2
+ from datadog_lambda.stats_writer import StatsWriter
3
3
 
4
4
 
5
5
  class StatsDWriter(StatsWriter):
@@ -7,8 +7,8 @@ class StatsDWriter(StatsWriter):
7
7
  Writes distribution metrics using StatsD protocol
8
8
  """
9
9
 
10
- def distribution(self, metric_name, value, tags=[], timestamp=None):
11
- statsd.distribution(metric_name, value, tags=tags)
10
+ def distribution(self, metric_name, value, tags=None, timestamp=None):
11
+ statsd.distribution(metric_name, value, tags=tags, timestamp=timestamp)
12
12
 
13
13
  def flush(self):
14
14
  pass
@@ -3,6 +3,7 @@ import logging
3
3
  # Make sure that this package would always be lazy-loaded/outside from the critical path
4
4
  # since underlying packages are quite heavy to load and useless when the extension is present
5
5
  from datadog.threadstats import ThreadStats
6
+
6
7
  from datadog_lambda.stats_writer import StatsWriter
7
8
 
8
9
  logger = logging.getLogger(__name__)
@@ -17,7 +18,7 @@ class ThreadStatsWriter(StatsWriter):
17
18
  self.thread_stats = ThreadStats(compress_payload=True)
18
19
  self.thread_stats.start(flush_in_thread=flush_in_thread)
19
20
 
20
- def distribution(self, metric_name, value, tags=[], timestamp=None):
21
+ def distribution(self, metric_name, value, tags=None, timestamp=None):
21
22
  self.thread_stats.distribution(
22
23
  metric_name, value, tags=tags, timestamp=timestamp
23
24
  )
@@ -0,0 +1 @@
1
+ __version__ = "6.109.0"
@@ -49,14 +49,10 @@ profiling_env_var = os.environ.get("DD_PROFILING_ENABLED", "false").lower() == "
49
49
  if profiling_env_var:
50
50
  from ddtrace.profiling import profiler
51
51
 
52
- llmobs_api_key = None
53
52
  llmobs_env_var = os.environ.get("DD_LLMOBS_ENABLED", "false").lower() in ("true", "1")
54
53
  if llmobs_env_var:
55
- from datadog_lambda.api import get_api_key
56
54
  from ddtrace.llmobs import LLMObs
57
55
 
58
- llmobs_api_key = get_api_key()
59
-
60
56
  logger = logging.getLogger(__name__)
61
57
 
62
58
  DD_FLUSH_TO_LOG = "DD_FLUSH_TO_LOG"
@@ -226,10 +222,7 @@ class _LambdaDecorator(object):
226
222
 
227
223
  # Enable LLM Observability
228
224
  if llmobs_env_var:
229
- LLMObs.enable(
230
- agentless_enabled=True,
231
- api_key=llmobs_api_key,
232
- )
225
+ LLMObs.enable()
233
226
 
234
227
  logger.debug("datadog_lambda_wrapper initialized")
235
228
  except Exception as e:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "datadog_lambda"
3
- version = "6.107.0"
3
+ version = "6.109.0"
4
4
  description = "The Datadog AWS Lambda Library"
5
5
  authors = ["Datadog, Inc. <dev@datadoghq.com>"]
6
6
  license = "Apache-2.0"
@@ -1 +0,0 @@
1
- __version__ = "6.107.0"