Skip to content

Commit e16aa66

Browse files
Merge branch 'main' into rithika.narayan/update-asm-blocked-response
2 parents 6edde05 + 49710e6 commit e16aa66

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+4109
-149
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
strategy:
4444
max-parallel: 4
4545
matrix:
46-
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
46+
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
4747

4848
steps:
4949
- name: Checkout

.github/workflows/build_layer.yml

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,80 @@ name: Build Layers for system-Tests
22

33
on:
44
workflow_dispatch:
5+
pull_request:
56
push:
67
branches:
78
- "main"
89

910
jobs:
11+
get-ddtrace-run-id:
12+
runs-on: ubuntu-latest
13+
outputs:
14+
run-id: ${{ steps.get-ddtrace-run-id.outputs.run_id }}
15+
steps:
16+
- name: Resolve Run ID of latest dd-trace-py build
17+
id: get-ddtrace-run-id
18+
env:
19+
GH_TOKEN: ${{ github.token }}
20+
run: |
21+
RUN_ID=$(gh run list \
22+
--repo DataDog/dd-trace-py \
23+
--workflow build_deploy.yml \
24+
--branch main \
25+
--status success \
26+
--limit 1 \
27+
--json databaseId \
28+
--jq '.[0].databaseId')
29+
30+
echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT
31+
1032
build:
1133
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
1234

35+
needs: get-ddtrace-run-id
36+
1337
strategy:
1438
fail-fast: false
1539
matrix:
1640
arch: [arm64, amd64]
17-
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
41+
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
1842

1943
steps:
2044
- name: Checkout
2145
uses: actions/checkout@v4
2246

47+
- name: Build artifact name
48+
id: build-artifact-name
49+
run: |
50+
if [ "${{ matrix.arch }}" == "amd64" ]; then
51+
ARCH="x86_64"
52+
else
53+
ARCH="aarch64"
54+
fi
55+
56+
VER="${{ matrix.python_version }}"
57+
PY_VERSION_NO_DOT="${VER//./}"
58+
59+
echo "artifact_name=wheels-cp${PY_VERSION_NO_DOT}-manylinux_${ARCH}" >> $GITHUB_OUTPUT
60+
61+
- name: Download ddtrace Wheel
62+
uses: actions/download-artifact@v4
63+
with:
64+
name: ${{ steps.build-artifact-name.outputs.artifact_name }}
65+
repository: DataDog/dd-trace-py
66+
run-id: ${{ needs.get-ddtrace-run-id.outputs.run-id }}
67+
github-token: ${{ secrets.GITHUB_TOKEN }}
68+
path: ./artifacts
69+
70+
- name: Find ddtrace Wheel
71+
id: find-ddtrace-wheel
72+
run: |
73+
echo "wheel_path=$(find ./artifacts -name "*.whl" | head -n 1)" >> $GITHUB_OUTPUT
74+
2375
- name: Patch pyproject.toml
2476
run: |
25-
echo "Patching pyproject.toml to use main branch of dd-trace-py"
26-
sed -i 's|^ddtrace =.*$|ddtrace = { git = "https://github.com/DataDog/dd-trace-py.git" }|' pyproject.toml
77+
echo "Patching pyproject.toml to use latest build of dd-trace-py"
78+
sed -i 's|^ddtrace =.*$|ddtrace = { file = "${{ steps.find-ddtrace-wheel.outputs.wheel_path }}" }|' pyproject.toml
2779
2880
- name: Build layer for Python ${{ matrix.python_version }} on ${{ matrix.arch }}
2981
run: |

.github/workflows/update-snapshots.yml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
name: update-snapshots
22

33
on:
4-
schedule:
5-
- cron: "0 15 * * *" # every day 11am EST
4+
workflow_dispatch:
65

76
jobs:
87
check:
@@ -47,8 +46,19 @@ jobs:
4746
working-directory: tests/integration
4847
run: yarn install
4948

50-
- name: Update Snapshots
49+
- name: Update Snapshots (amd64)
5150
env:
51+
ARCH: amd64
52+
UPDATE_SNAPSHOTS: true
53+
BUILD_LAYERS: true
54+
DD_API_KEY: ${{ secrets.DD_API_KEY }}
55+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
56+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
57+
run: ./scripts/run_integration_tests.sh
58+
59+
- name: Update Snapshots (arm64)
60+
env:
61+
ARCH: arm64
5262
UPDATE_SNAPSHOTS: true
5363
BUILD_LAYERS: true
5464
DD_API_KEY: ${{ secrets.DD_API_KEY }}

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ RUN set -eux; \
1515
rm gcc && ln -s gcc10-gcc gcc; \
1616
rm g++ && ln -s gcc10-g++ g++; \
1717
rm cc && ln -s gcc10-cc cc; \
18+
rm c++ && ln -s gcc10-c++ c++; \
1819
fi
1920

2021
# Add Rust compiler which is needed to build dd-trace-py from source
@@ -32,6 +33,7 @@ RUN rm -rf ./python/lib/$runtime/site-packages/botocore*
3233
RUN rm -rf ./python/lib/$runtime/site-packages/setuptools
3334
RUN rm -rf ./python/lib/$runtime/site-packages/jsonschema/tests
3435
RUN rm -f ./python/lib/$runtime/site-packages/ddtrace/appsec/_iast/_ast/iastpatch*.so
36+
RUN rm -rf ./python/lib/$runtime/site-packages/ddtrace/appsec/_iast/_taint_tracking/_vendor
3537
RUN rm -f ./python/lib/$runtime/site-packages/ddtrace/appsec/_iast/_taint_tracking/*.so
3638
RUN rm -f ./python/lib/$runtime/site-packages/ddtrace/appsec/_iast/_stacktrace*.so
3739
# remove *.dist-info directories except any entry_points.txt files and METADATA files required for Appsec Software Composition Analysis

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
[![Slack](https://chat.datadoghq.com/badge.svg?bg=632CA6)](https://chat.datadoghq.com/)
77
[![License](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/DataDog/datadog-lambda-python/blob/main/LICENSE)
88

9-
Datadog Lambda Library for Python (3.8, 3.9, 3.10, 3.11, 3.12, and 3.13) enables [enhanced Lambda metrics](https://docs.datadoghq.com/serverless/enhanced_lambda_metrics), [distributed tracing](https://docs.datadoghq.com/serverless/distributed_tracing), and [custom metric submission](https://docs.datadoghq.com/serverless/custom_metrics) from AWS Lambda functions.
9+
Datadog Lambda Library for Python (3.8, 3.9, 3.10, 3.11, 3.12, 3.13, and 3.14) enables [enhanced Lambda metrics](https://docs.datadoghq.com/serverless/enhanced_lambda_metrics), [distributed tracing](https://docs.datadoghq.com/serverless/distributed_tracing), and [custom metric submission](https://docs.datadoghq.com/serverless/custom_metrics) from AWS Lambda functions.
1010

1111
## Installation
1212

ci/datasources/runtimes.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,11 @@ runtimes:
4747
python_version: "3.13"
4848
arch: "arm64"
4949
image: "3.13.0"
50+
- name: "python314"
51+
python_version: "3.14"
52+
arch: "amd64"
53+
image: "3.14.0"
54+
- name: "python314"
55+
python_version: "3.14"
56+
arch: "arm64"
57+
image: "3.14.0"

ci/publish_layers.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ AWS_CLI_PYTHON_VERSIONS=(
2323
"python3.12"
2424
"python3.13"
2525
"python3.13"
26+
"python3.14"
27+
"python3.14"
2628
)
2729
PYTHON_VERSIONS=(
2830
"3.8-amd64"
@@ -37,6 +39,8 @@ PYTHON_VERSIONS=(
3739
"3.12-arm64"
3840
"3.13-amd64"
3941
"3.13-arm64"
42+
"3.14-amd64"
43+
"3.14-arm64"
4044
)
4145
LAYER_PATHS=(
4246
".layers/datadog_lambda_py-amd64-3.8.zip"
@@ -51,6 +55,8 @@ LAYER_PATHS=(
5155
".layers/datadog_lambda_py-arm64-3.12.zip"
5256
".layers/datadog_lambda_py-amd64-3.13.zip"
5357
".layers/datadog_lambda_py-arm64-3.13.zip"
58+
".layers/datadog_lambda_py-amd64-3.14.zip"
59+
".layers/datadog_lambda_py-arm64-3.14.zip"
5460
)
5561
LAYERS=(
5662
"Datadog-Python38"
@@ -65,6 +71,8 @@ LAYERS=(
6571
"Datadog-Python312-ARM"
6672
"Datadog-Python313"
6773
"Datadog-Python313-ARM"
74+
"Datadog-Python314"
75+
"Datadog-Python314-ARM"
6876
)
6977
STAGES=('prod', 'sandbox', 'staging', 'gov-staging', 'gov-prod')
7078

datadog_lambda/api.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@
55

66
logger = logging.getLogger(__name__)
77
KMS_ENCRYPTION_CONTEXT_KEY = "LambdaFunctionName"
8+
SSM_FIPS_SUPPORTED_REGIONS = {
9+
"us-east-1",
10+
"us-east-2",
11+
"us-west-1",
12+
"us-west-2",
13+
"ca-central-1",
14+
"ca-west-1",
15+
}
816
api_key = None
917

1018

@@ -92,11 +100,18 @@ def get_api_key() -> str:
92100
)["SecretString"]
93101
elif DD_API_KEY_SSM_NAME:
94102
# SSM endpoints: https://docs.aws.amazon.com/general/latest/gr/ssm.html
95-
fips_endpoint = (
96-
f"https://ssm-fips.{LAMBDA_REGION}.amazonaws.com"
97-
if config.fips_mode_enabled
98-
else None
99-
)
103+
fips_endpoint = None
104+
if config.fips_mode_enabled:
105+
if LAMBDA_REGION in SSM_FIPS_SUPPORTED_REGIONS:
106+
fips_endpoint = f"https://ssm-fips.{LAMBDA_REGION}.amazonaws.com"
107+
else:
108+
# Log warning if SSM FIPS endpoint is not supported for commercial region
109+
if not config.is_gov_region:
110+
logger.warning(
111+
"FIPS mode is enabled, but '%s' does not support SSM FIPS endpoints. "
112+
"Using standard SSM endpoint.",
113+
LAMBDA_REGION,
114+
)
100115
ssm_client = _boto3_client("ssm", endpoint_url=fips_endpoint)
101116
api_key = ssm_client.get_parameter(
102117
Name=DD_API_KEY_SSM_NAME, WithDecryption=True

datadog_lambda/tag_object.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,6 @@ def _should_try_string(obj):
6262

6363
def _redact_val(k, v):
6464
split_key = k.split(".").pop() or k
65-
if split_key in redactable_keys:
65+
if split_key.lower() in redactable_keys:
6666
return "redacted"
6767
return v

datadog_lambda/tracing.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,7 @@ def create_inferred_span_from_lambda_function_url_event(event, context):
927927
InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="sync")
928928
if span:
929929
span.set_tags(tags)
930+
span.set_metric(InferredSpanInfo.METRIC, 1.0)
930931
span.start_ns = int(request_time_epoch * 1e6)
931932
return span
932933

@@ -1047,6 +1048,7 @@ def create_inferred_span_from_api_gateway_websocket_event(
10471048
span = tracer.trace("aws.apigateway.websocket", **args)
10481049
if span:
10491050
span.set_tags(tags)
1051+
span.set_metric(InferredSpanInfo.METRIC, 1.0)
10501052
span.start_ns = int(
10511053
finish_time_ns
10521054
if finish_time_ns is not None
@@ -1061,6 +1063,8 @@ def create_inferred_span_from_api_gateway_event(
10611063
event, context, decode_authorizer_context: bool = True
10621064
):
10631065
request_context = event.get("requestContext")
1066+
identity = request_context.get("identity")
1067+
10641068
domain = request_context.get("domainName", "")
10651069
api_id = request_context.get("apiId")
10661070
service_name = determine_service_name(
@@ -1072,11 +1076,11 @@ def create_inferred_span_from_api_gateway_event(
10721076
resource_path = _get_resource_path(event, request_context)
10731077
resource = f"{method} {resource_path}"
10741078
tags = {
1075-
"operation_name": "aws.apigateway.rest",
10761079
"http.url": http_url,
10771080
"endpoint": path,
10781081
"http.method": method,
10791082
"resource_names": resource,
1083+
"http.useragent": identity.get("userAgent"),
10801084
"span.kind": "server",
10811085
"apiid": api_id,
10821086
"apiname": api_id,
@@ -1091,7 +1095,7 @@ def create_inferred_span_from_api_gateway_event(
10911095
args = {
10921096
"service": service_name,
10931097
"resource": resource,
1094-
"span_type": "http",
1098+
"span_type": "web",
10951099
}
10961100
tracer.set_tags(_dd_origin)
10971101
upstream_authorizer_span = None
@@ -1103,6 +1107,7 @@ def create_inferred_span_from_api_gateway_event(
11031107
span = tracer.trace("aws.apigateway", **args)
11041108
if span:
11051109
span.set_tags(tags)
1110+
span.set_metric(InferredSpanInfo.METRIC, 1.0)
11061111
# start time pushed by the inserted authorizer span
11071112
span.start_ns = int(
11081113
finish_time_ns
@@ -1140,13 +1145,12 @@ def create_inferred_span_from_http_api_event(
11401145
resource_path = _get_resource_path(event, request_context)
11411146
resource = f"{method} {resource_path}"
11421147
tags = {
1143-
"operation_name": "aws.httpapi",
11441148
"endpoint": path,
11451149
"http.url": http_url,
11461150
"http.method": http.get("method"),
11471151
"http.protocol": http.get("protocol"),
11481152
"http.source_ip": http.get("sourceIp"),
1149-
"http.user_agent": http.get("userAgent"),
1153+
"http.useragent": http.get("userAgent"),
11501154
"resource_names": resource,
11511155
"request_id": context.aws_request_id,
11521156
"apiid": api_id,
@@ -1167,10 +1171,11 @@ def create_inferred_span_from_http_api_event(
11671171
Headers.Parent_Span_Finish_Time
11681172
)
11691173
span = tracer.trace(
1170-
"aws.httpapi", service=service_name, resource=resource, span_type="http"
1174+
"aws.httpapi", service=service_name, resource=resource, span_type="web"
11711175
)
11721176
if span:
11731177
span.set_tags(tags)
1178+
span.set_metric(InferredSpanInfo.METRIC, 1.0)
11741179
span.start_ns = int(inferred_span_start_ns)
11751180
return span
11761181

@@ -1237,6 +1242,7 @@ def create_inferred_span_from_sqs_event(event, context):
12371242
)
12381243
if span:
12391244
span.set_tags(tags)
1245+
span.set_metric(InferredSpanInfo.METRIC, 1.0)
12401246
span.start = start_time
12411247
if upstream_span:
12421248
span.parent_id = upstream_span.span_id
@@ -1278,6 +1284,7 @@ def create_inferred_span_from_sns_event(event, context):
12781284
)
12791285
if span:
12801286
span.set_tags(tags)
1287+
span.set_metric(InferredSpanInfo.METRIC, 1.0)
12811288
span.start = dt.replace(tzinfo=timezone.utc).timestamp()
12821289
return span
12831290

@@ -1313,6 +1320,7 @@ def create_inferred_span_from_kinesis_event(event, context):
13131320
)
13141321
if span:
13151322
span.set_tags(tags)
1323+
span.set_metric(InferredSpanInfo.METRIC, 1.0)
13161324
span.start = request_time_epoch
13171325
return span
13181326

@@ -1345,6 +1353,7 @@ def create_inferred_span_from_dynamodb_event(event, context):
13451353
)
13461354
if span:
13471355
span.set_tags(tags)
1356+
span.set_metric(InferredSpanInfo.METRIC, 1.0)
13481357

13491358
span.start = int(request_time_epoch)
13501359
return span
@@ -1381,6 +1390,7 @@ def create_inferred_span_from_s3_event(event, context):
13811390
)
13821391
if span:
13831392
span.set_tags(tags)
1393+
span.set_metric(InferredSpanInfo.METRIC, 1.0)
13841394
span.start = dt.replace(tzinfo=timezone.utc).timestamp()
13851395
return span
13861396

@@ -1421,10 +1431,11 @@ def create_inferred_span_from_eventbridge_event(event, context):
14211431
)
14221432
if span:
14231433
span.set_tags(tags)
1434+
span.set_metric(InferredSpanInfo.METRIC, 1.0)
14241435
span.start = dt.replace(tzinfo=timezone.utc).timestamp()
14251436

14261437
# Since inferred span will later parent Lambda, preserve Lambda's current parent
1427-
if dd_trace_context.span_id:
1438+
if dd_trace_context and getattr(dd_trace_context, "span_id", None):
14281439
span.parent_id = dd_trace_context.span_id
14291440

14301441
return span
@@ -1512,6 +1523,7 @@ class InferredSpanInfo(object):
15121523
BASE_NAME = "_inferred_span"
15131524
SYNCHRONICITY = f"{BASE_NAME}.synchronicity"
15141525
TAG_SOURCE = f"{BASE_NAME}.tag_source"
1526+
METRIC = f"_dd.{BASE_NAME}"
15151527

15161528
@staticmethod
15171529
def set_tags(

0 commit comments

Comments
 (0)