ddtrace 3.11.0rc2__cp38-cp38-win32.whl → 3.11.0rc3__cp38-cp38-win32.whl
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.
Potentially problematic release.
This version of ddtrace might be problematic. Click here for more details.
- ddtrace/_trace/sampling_rule.py +25 -33
- ddtrace/_trace/trace_handlers.py +9 -49
- ddtrace/_trace/utils_botocore/span_tags.py +48 -0
- ddtrace/_version.py +2 -2
- ddtrace/appsec/_constants.py +7 -0
- ddtrace/appsec/_handlers.py +11 -0
- ddtrace/appsec/_processor.py +1 -1
- ddtrace/contrib/internal/aiobotocore/patch.py +8 -0
- ddtrace/contrib/internal/boto/patch.py +14 -0
- ddtrace/contrib/internal/botocore/services/bedrock.py +3 -27
- ddtrace/contrib/internal/django/patch.py +31 -8
- ddtrace/contrib/internal/google_genai/_utils.py +2 -2
- ddtrace/contrib/internal/google_genai/patch.py +7 -7
- ddtrace/contrib/internal/google_generativeai/patch.py +7 -5
- ddtrace/contrib/internal/openai_agents/patch.py +44 -1
- ddtrace/contrib/internal/pytest/_plugin_v2.py +1 -1
- ddtrace/contrib/internal/vertexai/patch.py +7 -5
- ddtrace/ext/ci.py +20 -0
- ddtrace/ext/git.py +66 -11
- ddtrace/internal/_encoding.cp38-win32.pyd +0 -0
- ddtrace/internal/_rand.cp38-win32.pyd +0 -0
- ddtrace/internal/_tagset.cp38-win32.pyd +0 -0
- ddtrace/internal/_threads.cp38-win32.pyd +0 -0
- ddtrace/internal/ci_visibility/encoder.py +126 -55
- ddtrace/internal/datadog/profiling/dd_wrapper-unknown-amd64.dll +0 -0
- ddtrace/internal/datadog/profiling/ddup/_ddup.cp38-win32.pyd +0 -0
- ddtrace/internal/datadog/profiling/ddup/dd_wrapper-unknown-amd64.dll +0 -0
- ddtrace/internal/endpoints.py +76 -0
- ddtrace/internal/native/_native.cp38-win32.pyd +0 -0
- ddtrace/internal/schema/processor.py +6 -2
- ddtrace/internal/telemetry/metrics_namespaces.cp38-win32.pyd +0 -0
- ddtrace/internal/telemetry/writer.py +18 -0
- ddtrace/llmobs/_constants.py +1 -0
- ddtrace/llmobs/_experiment.py +6 -0
- ddtrace/llmobs/_integrations/crewai.py +52 -3
- ddtrace/llmobs/_integrations/gemini.py +7 -7
- ddtrace/llmobs/_integrations/google_genai.py +10 -10
- ddtrace/llmobs/_integrations/{google_genai_utils.py → google_utils.py} +103 -7
- ddtrace/llmobs/_integrations/openai_agents.py +145 -0
- ddtrace/llmobs/_integrations/pydantic_ai.py +67 -26
- ddtrace/llmobs/_integrations/utils.py +68 -158
- ddtrace/llmobs/_integrations/vertexai.py +8 -8
- ddtrace/llmobs/_llmobs.py +5 -1
- ddtrace/llmobs/_utils.py +21 -0
- ddtrace/profiling/_threading.cp38-win32.pyd +0 -0
- ddtrace/profiling/collector/_memalloc.cp38-win32.pyd +0 -0
- ddtrace/profiling/collector/_task.cp38-win32.pyd +0 -0
- ddtrace/profiling/collector/_traceback.cp38-win32.pyd +0 -0
- ddtrace/profiling/collector/stack.cp38-win32.pyd +0 -0
- ddtrace/settings/asm.py +9 -2
- ddtrace/vendor/psutil/_psutil_windows.cp38-win32.pyd +0 -0
- {ddtrace-3.11.0rc2.dist-info → ddtrace-3.11.0rc3.dist-info}/METADATA +1 -1
- {ddtrace-3.11.0rc2.dist-info → ddtrace-3.11.0rc3.dist-info}/RECORD +60 -59
- {ddtrace-3.11.0rc2.dist-info → ddtrace-3.11.0rc3.dist-info}/LICENSE +0 -0
- {ddtrace-3.11.0rc2.dist-info → ddtrace-3.11.0rc3.dist-info}/LICENSE.Apache +0 -0
- {ddtrace-3.11.0rc2.dist-info → ddtrace-3.11.0rc3.dist-info}/LICENSE.BSD3 +0 -0
- {ddtrace-3.11.0rc2.dist-info → ddtrace-3.11.0rc3.dist-info}/NOTICE +0 -0
- {ddtrace-3.11.0rc2.dist-info → ddtrace-3.11.0rc3.dist-info}/WHEEL +0 -0
- {ddtrace-3.11.0rc2.dist-info → ddtrace-3.11.0rc3.dist-info}/entry_points.txt +0 -0
- {ddtrace-3.11.0rc2.dist-info → ddtrace-3.11.0rc3.dist-info}/top_level.txt +0 -0
ddtrace/_trace/sampling_rule.py
CHANGED
@@ -60,7 +60,7 @@ class SamplingRule(object):
|
|
60
60
|
number of characters, and "?" meaning any one character. If all tags specified in a SamplingRule are
|
61
61
|
matches with a given span, that span is considered to have matching tags with the rule.
|
62
62
|
"""
|
63
|
-
self.sample_rate =
|
63
|
+
self.sample_rate = min(1.0, max(0.0, float(sample_rate)))
|
64
64
|
# since span.py converts None to 'None' for tags, and does not accept 'None' for metrics
|
65
65
|
# we can just create a GlobMatcher for 'None' and it will match properly
|
66
66
|
self._tag_value_matchers = (
|
@@ -111,46 +111,38 @@ class SamplingRule(object):
|
|
111
111
|
:returns: Whether this span matches or not
|
112
112
|
:rtype: :obj:`bool`
|
113
113
|
"""
|
114
|
-
tags_match
|
115
|
-
return tags_match and self._matches((span.service, span.name, span.resource))
|
114
|
+
return self.tags_match(span) and self._matches((span.service, span.name, span.resource))
|
116
115
|
|
117
116
|
def tags_match(self, span: Span) -> bool:
|
118
|
-
|
119
|
-
|
120
|
-
tag_match = self.check_tags(span.get_tags(), span.get_metrics())
|
121
|
-
return tag_match
|
117
|
+
if not self._tag_value_matchers:
|
118
|
+
return True
|
122
119
|
|
123
|
-
|
124
|
-
|
120
|
+
meta = span._meta or {}
|
121
|
+
metrics = span._metrics or {}
|
122
|
+
if not meta and not metrics:
|
125
123
|
return False
|
126
124
|
|
127
|
-
|
128
|
-
|
129
|
-
value = meta.get(tag_key)
|
130
|
-
# it's because we're not checking metrics first before continuing
|
125
|
+
for tag_key, pattern in self._tag_value_matchers.items():
|
126
|
+
value = meta.get(tag_key, metrics.get(tag_key))
|
131
127
|
if value is None:
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
#
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
tag_match = self._tag_value_matchers[tag_key].match(str(value))
|
149
|
-
# if we don't match with all specified tags for a rule, it's not a match
|
150
|
-
if tag_match is False:
|
128
|
+
# If the tag is not present, we failed the match
|
129
|
+
# (Metrics and meta do not support the value None)
|
130
|
+
return False
|
131
|
+
|
132
|
+
if isinstance(value, float):
|
133
|
+
# Floats: Convert floats that represent integers to int for matching. This is because
|
134
|
+
# SamplingRules only support integers for matfching or glob patterns.
|
135
|
+
if value.is_integer():
|
136
|
+
value = int(value)
|
137
|
+
elif set(pattern.pattern) - {"?", "*"}:
|
138
|
+
# Only match floats to patterns that only contain wildcards (ex: * or ?*)
|
139
|
+
# This is because we do not want to match floats to patterns like `23.*`.
|
140
|
+
return False
|
141
|
+
|
142
|
+
if not pattern.match(str(value)):
|
151
143
|
return False
|
152
144
|
|
153
|
-
return
|
145
|
+
return True
|
154
146
|
|
155
147
|
def sample(self, span):
|
156
148
|
"""
|
ddtrace/_trace/trace_handlers.py
CHANGED
@@ -661,13 +661,9 @@ def _on_botocore_patched_bedrock_api_call_started(ctx, request_params):
|
|
661
661
|
|
662
662
|
span.set_tag_str("bedrock.request.model_provider", ctx["model_provider"])
|
663
663
|
span.set_tag_str("bedrock.request.model", ctx["model_name"])
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
v = integration.trunc(str(v))
|
668
|
-
span.set_tag_str("bedrock.request.{}".format(k), str(v))
|
669
|
-
if k == "n":
|
670
|
-
ctx.set_item("num_generations", str(v))
|
664
|
+
|
665
|
+
if "n" in request_params:
|
666
|
+
ctx.set_item("num_generations", str(request_params["n"]))
|
671
667
|
|
672
668
|
|
673
669
|
def _on_botocore_patched_bedrock_api_call_exception(ctx, exc_info):
|
@@ -680,16 +676,6 @@ def _on_botocore_patched_bedrock_api_call_exception(ctx, exc_info):
|
|
680
676
|
span.finish()
|
681
677
|
|
682
678
|
|
683
|
-
def _on_botocore_patched_bedrock_api_call_success(ctx, reqid, latency, input_token_count, output_token_count):
|
684
|
-
span = ctx.span
|
685
|
-
span.set_tag_str("bedrock.response.id", reqid)
|
686
|
-
span.set_tag_str("bedrock.response.duration", latency)
|
687
|
-
if input_token_count:
|
688
|
-
span.set_metric("bedrock.response.usage.prompt_tokens", int(input_token_count))
|
689
|
-
if output_token_count:
|
690
|
-
span.set_metric("bedrock.response.usage.completion_tokens", int(output_token_count))
|
691
|
-
|
692
|
-
|
693
679
|
def _propagate_context(ctx, headers):
|
694
680
|
distributed_tracing_enabled = ctx["integration_config"].distributed_tracing_enabled
|
695
681
|
span = ctx.span
|
@@ -731,38 +717,13 @@ def _on_botocore_bedrock_process_response_converse(
|
|
731
717
|
def _on_botocore_bedrock_process_response(
|
732
718
|
ctx: core.ExecutionContext,
|
733
719
|
formatted_response: Dict[str, Any],
|
734
|
-
metadata: Dict[str, Any],
|
735
|
-
body: Dict[str, List[Dict]],
|
736
|
-
should_set_choice_ids: bool,
|
737
720
|
) -> None:
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
integration = ctx["bedrock_integration"]
|
745
|
-
if metadata is not None:
|
746
|
-
for k, v in metadata.items():
|
747
|
-
if k in ["usage.completion_tokens", "usage.prompt_tokens"] and v:
|
748
|
-
span.set_metric("bedrock.response.{}".format(k), int(v))
|
749
|
-
else:
|
750
|
-
span.set_tag_str("bedrock.{}".format(k), str(v))
|
751
|
-
if "embed" in model_name:
|
752
|
-
span.set_metric("bedrock.response.embedding_length", len(formatted_response["text"][0]))
|
753
|
-
span.finish()
|
754
|
-
return
|
755
|
-
for i in range(len(formatted_response["text"])):
|
756
|
-
if integration.is_pc_sampled_span(span):
|
757
|
-
span.set_tag_str(
|
758
|
-
"bedrock.response.choices.{}.text".format(i),
|
759
|
-
integration.trunc(str(formatted_response["text"][i])),
|
760
|
-
)
|
761
|
-
span.set_tag_str(
|
762
|
-
"bedrock.response.choices.{}.finish_reason".format(i), str(formatted_response["finish_reason"][i])
|
763
|
-
)
|
764
|
-
integration.llmobs_set_tags(span, args=[ctx], kwargs={}, response=formatted_response)
|
765
|
-
span.finish()
|
721
|
+
with ctx.span as span:
|
722
|
+
model_name = ctx["model_name"]
|
723
|
+
integration = ctx["bedrock_integration"]
|
724
|
+
if "embed" in model_name:
|
725
|
+
return
|
726
|
+
integration.llmobs_set_tags(span, args=[ctx], kwargs={}, response=formatted_response)
|
766
727
|
|
767
728
|
|
768
729
|
def _on_botocore_sqs_recvmessage_post(
|
@@ -931,7 +892,6 @@ def listen():
|
|
931
892
|
core.on("botocore.client_context.update_messages", _on_botocore_update_messages)
|
932
893
|
core.on("botocore.patched_bedrock_api_call.started", _on_botocore_patched_bedrock_api_call_started)
|
933
894
|
core.on("botocore.patched_bedrock_api_call.exception", _on_botocore_patched_bedrock_api_call_exception)
|
934
|
-
core.on("botocore.patched_bedrock_api_call.success", _on_botocore_patched_bedrock_api_call_success)
|
935
895
|
core.on("botocore.bedrock.process_response", _on_botocore_bedrock_process_response)
|
936
896
|
core.on("botocore.bedrock.process_response_converse", _on_botocore_bedrock_process_response_converse)
|
937
897
|
core.on("botocore.sqs.ReceiveMessage.post", _on_botocore_sqs_recvmessage_post)
|
@@ -12,11 +12,52 @@ from ddtrace.ext import SpanKind
|
|
12
12
|
from ddtrace.ext import aws
|
13
13
|
from ddtrace.ext import http
|
14
14
|
from ddtrace.internal.constants import COMPONENT
|
15
|
+
from ddtrace.internal.serverless import in_aws_lambda
|
15
16
|
from ddtrace.internal.utils.formats import deep_getattr
|
16
17
|
|
17
18
|
|
18
19
|
_PAYLOAD_TAGGER = AWSPayloadTagging()
|
19
20
|
|
21
|
+
SERVICE_MAP = {
|
22
|
+
"eventbridge": "events",
|
23
|
+
"events": "events",
|
24
|
+
"sqs": "sqs",
|
25
|
+
"sns": "sns",
|
26
|
+
"kinesis": "kinesis",
|
27
|
+
"dynamodb": "dynamodb",
|
28
|
+
"dynamodbdocument": "dynamodb",
|
29
|
+
}
|
30
|
+
|
31
|
+
|
32
|
+
# Helper to build AWS hostname from service, region and parameters
|
33
|
+
def _derive_peer_hostname(service: str, region: str, params: Optional[Dict[str, Any]] = None) -> Optional[str]:
|
34
|
+
"""Return hostname for given AWS service according to Datadog peer hostname rules.
|
35
|
+
|
36
|
+
Only returns hostnames for specific AWS services:
|
37
|
+
- eventbridge/events -> events.<region>.amazonaws.com
|
38
|
+
- sqs -> sqs.<region>.amazonaws.com
|
39
|
+
- sns -> sns.<region>.amazonaws.com
|
40
|
+
- kinesis -> kinesis.<region>.amazonaws.com
|
41
|
+
- dynamodb -> dynamodb.<region>.amazonaws.com
|
42
|
+
- s3 -> <bucket>.s3.<region>.amazonaws.com (if Bucket param present)
|
43
|
+
s3.<region>.amazonaws.com (otherwise)
|
44
|
+
|
45
|
+
Other services return ``None``.
|
46
|
+
"""
|
47
|
+
|
48
|
+
if not region:
|
49
|
+
return None
|
50
|
+
|
51
|
+
aws_service = service.lower()
|
52
|
+
|
53
|
+
if aws_service == "s3":
|
54
|
+
bucket = params.get("Bucket") if params else None
|
55
|
+
return f"{bucket}.s3.{region}.amazonaws.com" if bucket else f"s3.{region}.amazonaws.com"
|
56
|
+
|
57
|
+
mapped = SERVICE_MAP.get(aws_service)
|
58
|
+
|
59
|
+
return f"{mapped}.{region}.amazonaws.com" if mapped else None
|
60
|
+
|
20
61
|
|
21
62
|
def set_botocore_patched_api_call_span_tags(span: Span, instance, args, params, endpoint_name, operation):
|
22
63
|
span.set_tag_str(COMPONENT, config.botocore.integration_name)
|
@@ -51,6 +92,13 @@ def set_botocore_patched_api_call_span_tags(span: Span, instance, args, params,
|
|
51
92
|
span.set_tag_str("aws.region", region_name)
|
52
93
|
span.set_tag_str("region", region_name)
|
53
94
|
|
95
|
+
# Derive peer hostname only in serverless environments to avoid
|
96
|
+
# unnecessary tag noise in traditional hosts/containers.
|
97
|
+
if in_aws_lambda():
|
98
|
+
hostname = _derive_peer_hostname(endpoint_name, region_name, params)
|
99
|
+
if hostname:
|
100
|
+
span.set_tag_str("peer.service", hostname)
|
101
|
+
|
54
102
|
|
55
103
|
def set_botocore_response_metadata_tags(
|
56
104
|
span: Span, result: Dict[str, Any], is_error_code_fn: Optional[Callable] = None
|
ddtrace/_version.py
CHANGED
@@ -17,5 +17,5 @@ __version__: str
|
|
17
17
|
__version_tuple__: VERSION_TUPLE
|
18
18
|
version_tuple: VERSION_TUPLE
|
19
19
|
|
20
|
-
__version__ = version = '3.11.
|
21
|
-
__version_tuple__ = version_tuple = (3, 11, 0, '
|
20
|
+
__version__ = version = '3.11.0rc3'
|
21
|
+
__version_tuple__ = version_tuple = (3, 11, 0, 'rc3')
|
ddtrace/appsec/_constants.py
CHANGED
@@ -275,6 +275,12 @@ class API_SECURITY(metaclass=Constant_Class):
|
|
275
275
|
SAMPLE_RATE: Literal["DD_API_SECURITY_REQUEST_SAMPLE_RATE"] = "DD_API_SECURITY_REQUEST_SAMPLE_RATE"
|
276
276
|
SAMPLE_DELAY: Literal["DD_API_SECURITY_SAMPLE_DELAY"] = "DD_API_SECURITY_SAMPLE_DELAY"
|
277
277
|
MAX_PAYLOAD_SIZE: Literal[0x1000000] = 0x1000000 # 16MB maximum size
|
278
|
+
ENDPOINT_COLLECTION: Literal[
|
279
|
+
"DD_API_SECURITY_ENDPOINT_COLLECTION_ENABLED"
|
280
|
+
] = "DD_API_SECURITY_ENDPOINT_COLLECTION_ENABLED"
|
281
|
+
ENDPOINT_COLLECTION_LIMIT: Literal[
|
282
|
+
"DD_API_SECURITY_ENDPOINT_COLLECTION_MESSAGE_LIMIT"
|
283
|
+
] = "DD_API_SECURITY_ENDPOINT_COLLECTION_MESSAGE_LIMIT"
|
278
284
|
|
279
285
|
|
280
286
|
class WAF_CONTEXT_NAMES(metaclass=Constant_Class):
|
@@ -348,6 +354,7 @@ class DEFAULT(metaclass=Constant_Class):
|
|
348
354
|
r"|ey[I-L][\w=-]+\.(ey[I-L][\w=-]+(?:\.[\w.+\/=-]+)?)|[\-]{5}BEGIN[a-z\s]+PRIVATE\sKEY[\-]{5}([^\-]+)[\-]"
|
349
355
|
r"{5}END[a-z\s]+PRIVATE\sKEY|ssh-rsa\s*([a-z0-9\/\.+]{100,})"
|
350
356
|
)
|
357
|
+
ENDPOINT_COLLECTION_LIMIT = 300
|
351
358
|
|
352
359
|
|
353
360
|
class EXPLOIT_PREVENTION(metaclass=Constant_Class):
|
ddtrace/appsec/_handlers.py
CHANGED
@@ -4,6 +4,7 @@ import json
|
|
4
4
|
from typing import Any
|
5
5
|
from typing import Dict
|
6
6
|
from typing import Optional
|
7
|
+
from typing import Union
|
7
8
|
|
8
9
|
import xmltodict
|
9
10
|
|
@@ -11,6 +12,7 @@ from ddtrace._trace.span import Span
|
|
11
12
|
from ddtrace.appsec._asm_request_context import _call_waf
|
12
13
|
from ddtrace.appsec._asm_request_context import _call_waf_first
|
13
14
|
from ddtrace.appsec._asm_request_context import get_blocked
|
15
|
+
from ddtrace.appsec._asm_request_context import set_body_response
|
14
16
|
from ddtrace.appsec._constants import SPAN_DATA_NAMES
|
15
17
|
from ddtrace.appsec._http_utils import extract_cookies_from_headers
|
16
18
|
from ddtrace.appsec._http_utils import normalize_headers
|
@@ -157,6 +159,14 @@ def _on_lambda_start_response(
|
|
157
159
|
_call_waf(("aws_lambda",))
|
158
160
|
|
159
161
|
|
162
|
+
def _on_lambda_parse_body(
|
163
|
+
response_body: Optional[Union[str, Dict[str, Any]]],
|
164
|
+
):
|
165
|
+
if asm_config._api_security_feature_active:
|
166
|
+
if response_body:
|
167
|
+
set_body_response(response_body)
|
168
|
+
|
169
|
+
|
160
170
|
# ASGI
|
161
171
|
|
162
172
|
|
@@ -408,6 +418,7 @@ def listen():
|
|
408
418
|
|
409
419
|
core.on("aws_lambda.start_request", _on_lambda_start_request)
|
410
420
|
core.on("aws_lambda.start_response", _on_lambda_start_response)
|
421
|
+
core.on("aws_lambda.parse_body", _on_lambda_parse_body)
|
411
422
|
|
412
423
|
core.on("grpc.server.response.message", _on_grpc_server_response)
|
413
424
|
core.on("grpc.server.data", _on_grpc_server_data)
|
ddtrace/appsec/_processor.py
CHANGED
@@ -189,7 +189,7 @@ class AppSecSpanProcessor(SpanProcessor):
|
|
189
189
|
if skip_event:
|
190
190
|
core.discard_item("appsec_skip_next_lambda_event")
|
191
191
|
log.debug(
|
192
|
-
"appsec: ignoring unsupported
|
192
|
+
"appsec: ignoring unsupported lambda event",
|
193
193
|
)
|
194
194
|
span.set_metric(APPSEC.UNSUPPORTED_EVENT_TYPE, 1.0)
|
195
195
|
return
|
@@ -5,6 +5,7 @@ import aiobotocore.client
|
|
5
5
|
import wrapt
|
6
6
|
|
7
7
|
from ddtrace import config
|
8
|
+
from ddtrace._trace.utils_botocore.span_tags import _derive_peer_hostname
|
8
9
|
from ddtrace.constants import _SPAN_MEASURED_KEY
|
9
10
|
from ddtrace.constants import SPAN_KIND
|
10
11
|
from ddtrace.contrib.internal.trace_utils import ext_service
|
@@ -16,6 +17,7 @@ from ddtrace.ext import http
|
|
16
17
|
from ddtrace.internal.constants import COMPONENT
|
17
18
|
from ddtrace.internal.schema import schematize_cloud_api_operation
|
18
19
|
from ddtrace.internal.schema import schematize_service_name
|
20
|
+
from ddtrace.internal.serverless import in_aws_lambda
|
19
21
|
from ddtrace.internal.utils import ArgumentError
|
20
22
|
from ddtrace.internal.utils import get_argument_value
|
21
23
|
from ddtrace.internal.utils.formats import asbool
|
@@ -145,6 +147,12 @@ async def _wrapped_api_call(original_func, instance, args, kwargs):
|
|
145
147
|
|
146
148
|
region_name = deep_getattr(instance, "meta.region_name")
|
147
149
|
|
150
|
+
if in_aws_lambda():
|
151
|
+
# Derive the peer hostname now that we have both service and region.
|
152
|
+
hostname = _derive_peer_hostname(endpoint_name, region_name, params)
|
153
|
+
if hostname:
|
154
|
+
span.set_tag_str("peer.service", hostname)
|
155
|
+
|
148
156
|
meta = {
|
149
157
|
"aws.agent": "aiobotocore",
|
150
158
|
"aws.operation": operation,
|
@@ -7,6 +7,7 @@ import boto.connection
|
|
7
7
|
import wrapt
|
8
8
|
|
9
9
|
from ddtrace import config
|
10
|
+
from ddtrace._trace.utils_botocore.span_tags import _derive_peer_hostname
|
10
11
|
from ddtrace.constants import _SPAN_MEASURED_KEY
|
11
12
|
from ddtrace.constants import SPAN_KIND
|
12
13
|
from ddtrace.ext import SpanKind
|
@@ -16,6 +17,7 @@ from ddtrace.ext import http
|
|
16
17
|
from ddtrace.internal.constants import COMPONENT
|
17
18
|
from ddtrace.internal.schema import schematize_cloud_api_operation
|
18
19
|
from ddtrace.internal.schema import schematize_service_name
|
20
|
+
from ddtrace.internal.serverless import in_aws_lambda
|
19
21
|
from ddtrace.internal.utils import get_argument_value
|
20
22
|
from ddtrace.internal.utils.formats import asbool
|
21
23
|
from ddtrace.internal.utils.wrappers import unwrap
|
@@ -122,6 +124,12 @@ def patched_query_request(original_func, instance, args, kwargs):
|
|
122
124
|
meta[aws.REGION] = region_name
|
123
125
|
meta[aws.AWSREGION] = region_name
|
124
126
|
|
127
|
+
if in_aws_lambda():
|
128
|
+
# Derive the peer hostname now that we have both service and region.
|
129
|
+
hostname = _derive_peer_hostname(endpoint_name, region_name, params)
|
130
|
+
if hostname:
|
131
|
+
meta["peer.service"] = hostname
|
132
|
+
|
125
133
|
span.set_tags(meta)
|
126
134
|
|
127
135
|
# Original func returns a boto.connection.HTTPResponse object
|
@@ -183,6 +191,12 @@ def patched_auth_request(original_func, instance, args, kwargs):
|
|
183
191
|
meta[aws.REGION] = region_name
|
184
192
|
meta[aws.AWSREGION] = region_name
|
185
193
|
|
194
|
+
if in_aws_lambda():
|
195
|
+
# Derive the peer hostname
|
196
|
+
hostname = _derive_peer_hostname(endpoint_name, region_name, None)
|
197
|
+
if hostname:
|
198
|
+
meta["peer.service"] = hostname
|
199
|
+
|
186
200
|
span.set_tags(meta)
|
187
201
|
|
188
202
|
# Original func returns a boto.connection.HTTPResponse object
|
@@ -48,12 +48,9 @@ class TracedBotocoreStreamingBody(wrapt.ObjectProxy):
|
|
48
48
|
self._body.append(json.loads(body))
|
49
49
|
if self.__wrapped__.tell() == int(self.__wrapped__._content_length):
|
50
50
|
formatted_response = _extract_text_and_response_reason(self._execution_ctx, self._body[0])
|
51
|
-
model_provider = self._execution_ctx["model_provider"]
|
52
|
-
model_name = self._execution_ctx["model_name"]
|
53
|
-
should_set_choice_ids = model_provider == _COHERE and "embed" not in model_name
|
54
51
|
core.dispatch(
|
55
52
|
"botocore.bedrock.process_response",
|
56
|
-
[self._execution_ctx, formatted_response
|
53
|
+
[self._execution_ctx, formatted_response],
|
57
54
|
)
|
58
55
|
return body
|
59
56
|
except Exception:
|
@@ -67,12 +64,9 @@ class TracedBotocoreStreamingBody(wrapt.ObjectProxy):
|
|
67
64
|
for line in lines:
|
68
65
|
self._body.append(json.loads(line))
|
69
66
|
formatted_response = _extract_text_and_response_reason(self._execution_ctx, self._body[0])
|
70
|
-
model_provider = self._execution_ctx["model_provider"]
|
71
|
-
model_name = self._execution_ctx["model_name"]
|
72
|
-
should_set_choice_ids = model_provider == _COHERE and "embed" not in model_name
|
73
67
|
core.dispatch(
|
74
68
|
"botocore.bedrock.process_response",
|
75
|
-
[self._execution_ctx, formatted_response
|
69
|
+
[self._execution_ctx, formatted_response],
|
76
70
|
)
|
77
71
|
return lines
|
78
72
|
except Exception:
|
@@ -93,16 +87,10 @@ class TracedBotocoreStreamingBody(wrapt.ObjectProxy):
|
|
93
87
|
finally:
|
94
88
|
if exception_raised:
|
95
89
|
return
|
96
|
-
metadata = _extract_streamed_response_metadata(self._execution_ctx, self._body)
|
97
90
|
formatted_response = _extract_streamed_response(self._execution_ctx, self._body)
|
98
|
-
model_provider = self._execution_ctx["model_provider"]
|
99
|
-
model_name = self._execution_ctx["model_name"]
|
100
|
-
should_set_choice_ids = (
|
101
|
-
model_provider == _COHERE and "is_finished" not in self._body[0] and "embed" not in model_name
|
102
|
-
)
|
103
91
|
core.dispatch(
|
104
92
|
"botocore.bedrock.process_response",
|
105
|
-
[self._execution_ctx, formatted_response
|
93
|
+
[self._execution_ctx, formatted_response],
|
106
94
|
)
|
107
95
|
|
108
96
|
|
@@ -443,18 +431,6 @@ def handle_bedrock_response(
|
|
443
431
|
safe_token_count(cache_write_tokens),
|
444
432
|
)
|
445
433
|
|
446
|
-
# for both converse & invoke, dispatch success event to store basic metrics
|
447
|
-
core.dispatch(
|
448
|
-
"botocore.patched_bedrock_api_call.success",
|
449
|
-
[
|
450
|
-
ctx,
|
451
|
-
str(metadata.get("RequestId", "")),
|
452
|
-
request_latency,
|
453
|
-
str(input_tokens),
|
454
|
-
str(output_tokens),
|
455
|
-
],
|
456
|
-
)
|
457
|
-
|
458
434
|
if ctx["resource"] == "Converse":
|
459
435
|
core.dispatch("botocore.bedrock.process_response_converse", [ctx, result])
|
460
436
|
return result
|
@@ -49,6 +49,7 @@ from ddtrace.internal.utils.formats import asbool
|
|
49
49
|
from ddtrace.internal.utils.importlib import func_name
|
50
50
|
from ddtrace.propagation._database_monitoring import _DBM_Propagator
|
51
51
|
from ddtrace.settings.asm import config as asm_config
|
52
|
+
from ddtrace.settings.asm import endpoint_collection
|
52
53
|
from ddtrace.settings.integration import IntegrationConfig
|
53
54
|
from ddtrace.trace import Pin
|
54
55
|
from ddtrace.vendor.packaging.version import parse as parse_version
|
@@ -81,6 +82,7 @@ config._add(
|
|
81
82
|
use_legacy_resource_format=asbool(os.getenv("DD_DJANGO_USE_LEGACY_RESOURCE_FORMAT", default=False)),
|
82
83
|
_trace_asgi_websocket=os.getenv("DD_ASGI_TRACE_WEBSOCKET", default=False),
|
83
84
|
obfuscate_404_resource=os.getenv("DD_ASGI_OBFUSCATE_404_RESOURCE", default=False),
|
85
|
+
views={},
|
84
86
|
),
|
85
87
|
)
|
86
88
|
|
@@ -598,7 +600,7 @@ def traced_template_render(django, pin, wrapped, instance, args, kwargs):
|
|
598
600
|
return wrapped(*args, **kwargs)
|
599
601
|
|
600
602
|
|
601
|
-
def instrument_view(django, view):
|
603
|
+
def instrument_view(django, view, path=None):
|
602
604
|
"""
|
603
605
|
Helper to wrap Django views.
|
604
606
|
|
@@ -608,10 +610,24 @@ def instrument_view(django, view):
|
|
608
610
|
for cls in reversed(getmro(view)):
|
609
611
|
_instrument_view(django, cls)
|
610
612
|
|
611
|
-
return _instrument_view(django, view)
|
613
|
+
return _instrument_view(django, view, path=path)
|
612
614
|
|
613
615
|
|
614
|
-
def
|
616
|
+
def extract_request_method_list(view):
|
617
|
+
try:
|
618
|
+
while "view_func" in view.__code__.co_freevars:
|
619
|
+
view = view.__closure__[view.__code__.co_freevars.index("view_func")].cell_contents
|
620
|
+
if "request_method_list" in view.__code__.co_freevars:
|
621
|
+
return view.__closure__[view.__code__.co_freevars.index("request_method_list")].cell_contents
|
622
|
+
return []
|
623
|
+
except Exception:
|
624
|
+
return []
|
625
|
+
|
626
|
+
|
627
|
+
_DEFAULT_METHODS = ("get", "delete", "post", "options", "head")
|
628
|
+
|
629
|
+
|
630
|
+
def _instrument_view(django, view, path=None):
|
615
631
|
"""Helper to wrap Django views."""
|
616
632
|
from . import utils
|
617
633
|
|
@@ -620,9 +636,14 @@ def _instrument_view(django, view):
|
|
620
636
|
return view
|
621
637
|
|
622
638
|
# Patch view HTTP methods and lifecycle methods
|
623
|
-
|
639
|
+
|
640
|
+
http_method_names = getattr(view, "http_method_names", ())
|
641
|
+
request_method_list = extract_request_method_list(view) or http_method_names
|
642
|
+
if path is not None:
|
643
|
+
for method in request_method_list or ["*"]:
|
644
|
+
endpoint_collection.add_endpoint(method, path, operation_name="django.request")
|
624
645
|
lifecycle_methods = ("setup", "dispatch", "http_method_not_allowed")
|
625
|
-
for name in list(
|
646
|
+
for name in list(request_method_list or _DEFAULT_METHODS) + list(lifecycle_methods):
|
626
647
|
try:
|
627
648
|
func = getattr(view, name, None)
|
628
649
|
if not func or isinstance(func, wrapt.ObjectProxy):
|
@@ -665,18 +686,20 @@ def traced_urls_path(django, pin, wrapped, instance, args, kwargs):
|
|
665
686
|
try:
|
666
687
|
from_args = False
|
667
688
|
view = kwargs.pop("view", None)
|
689
|
+
path = kwargs.pop("path", None)
|
668
690
|
if view is None:
|
669
691
|
view = args[1]
|
670
692
|
from_args = True
|
693
|
+
if path is None:
|
694
|
+
path = args[0]
|
671
695
|
|
672
696
|
core.dispatch("service_entrypoint.patch", (unwrap(view),))
|
673
|
-
|
674
697
|
if from_args:
|
675
698
|
args = list(args)
|
676
|
-
args[1] = instrument_view(django, view)
|
699
|
+
args[1] = instrument_view(django, view, path=path)
|
677
700
|
args = tuple(args)
|
678
701
|
else:
|
679
|
-
kwargs["view"] = instrument_view(django, view)
|
702
|
+
kwargs["view"] = instrument_view(django, view, path=path)
|
680
703
|
except Exception:
|
681
704
|
log.debug("Failed to instrument Django url path %r %r", args, kwargs, exc_info=True)
|
682
705
|
return wrapped(*args, **kwargs)
|
@@ -6,7 +6,7 @@ from typing import Optional
|
|
6
6
|
|
7
7
|
import wrapt
|
8
8
|
|
9
|
-
from ddtrace.llmobs._integrations.
|
9
|
+
from ddtrace.llmobs._integrations.google_utils import GOOGLE_GENAI_DEFAULT_MODEL_ROLE
|
10
10
|
from ddtrace.llmobs._utils import _get_attr
|
11
11
|
|
12
12
|
|
@@ -32,7 +32,7 @@ def _join_chunks(chunks: List[Any]) -> Optional[Dict[str, Any]]:
|
|
32
32
|
continue
|
33
33
|
|
34
34
|
if role is None:
|
35
|
-
role = _get_attr(content, "role",
|
35
|
+
role = _get_attr(content, "role", GOOGLE_GENAI_DEFAULT_MODEL_ROLE)
|
36
36
|
|
37
37
|
parts = _get_attr(content, "parts", [])
|
38
38
|
for part in parts:
|
@@ -9,7 +9,7 @@ from ddtrace.contrib.internal.trace_utils import unwrap
|
|
9
9
|
from ddtrace.contrib.internal.trace_utils import with_traced_module
|
10
10
|
from ddtrace.contrib.internal.trace_utils import wrap
|
11
11
|
from ddtrace.llmobs._integrations import GoogleGenAIIntegration
|
12
|
-
from ddtrace.llmobs._integrations.
|
12
|
+
from ddtrace.llmobs._integrations.google_utils import extract_provider_and_model_name
|
13
13
|
from ddtrace.trace import Pin
|
14
14
|
|
15
15
|
|
@@ -27,7 +27,7 @@ def get_version() -> str:
|
|
27
27
|
@with_traced_module
|
28
28
|
def traced_generate(genai, pin, func, instance, args, kwargs):
|
29
29
|
integration = genai._datadog_integration
|
30
|
-
provider_name, model_name = extract_provider_and_model_name(kwargs)
|
30
|
+
provider_name, model_name = extract_provider_and_model_name(kwargs=kwargs)
|
31
31
|
with integration.trace(
|
32
32
|
pin,
|
33
33
|
"%s.%s" % (instance.__class__.__name__, func.__name__),
|
@@ -46,7 +46,7 @@ def traced_generate(genai, pin, func, instance, args, kwargs):
|
|
46
46
|
@with_traced_module
|
47
47
|
async def traced_async_generate(genai, pin, func, instance, args, kwargs):
|
48
48
|
integration = genai._datadog_integration
|
49
|
-
provider_name, model_name = extract_provider_and_model_name(kwargs)
|
49
|
+
provider_name, model_name = extract_provider_and_model_name(kwargs=kwargs)
|
50
50
|
with integration.trace(
|
51
51
|
pin,
|
52
52
|
"%s.%s" % (instance.__class__.__name__, func.__name__),
|
@@ -65,7 +65,7 @@ async def traced_async_generate(genai, pin, func, instance, args, kwargs):
|
|
65
65
|
@with_traced_module
|
66
66
|
def traced_generate_stream(genai, pin, func, instance, args, kwargs):
|
67
67
|
integration = genai._datadog_integration
|
68
|
-
provider_name, model_name = extract_provider_and_model_name(kwargs)
|
68
|
+
provider_name, model_name = extract_provider_and_model_name(kwargs=kwargs)
|
69
69
|
span = integration.trace(
|
70
70
|
pin,
|
71
71
|
"%s.%s" % (instance.__class__.__name__, func.__name__),
|
@@ -86,7 +86,7 @@ def traced_generate_stream(genai, pin, func, instance, args, kwargs):
|
|
86
86
|
@with_traced_module
|
87
87
|
async def traced_async_generate_stream(genai, pin, func, instance, args, kwargs):
|
88
88
|
integration = genai._datadog_integration
|
89
|
-
provider_name, model_name = extract_provider_and_model_name(kwargs)
|
89
|
+
provider_name, model_name = extract_provider_and_model_name(kwargs=kwargs)
|
90
90
|
span = integration.trace(
|
91
91
|
pin,
|
92
92
|
"%s.%s" % (instance.__class__.__name__, func.__name__),
|
@@ -107,7 +107,7 @@ async def traced_async_generate_stream(genai, pin, func, instance, args, kwargs)
|
|
107
107
|
@with_traced_module
|
108
108
|
def traced_embed_content(genai, pin, func, instance, args, kwargs):
|
109
109
|
integration = genai._datadog_integration
|
110
|
-
provider_name, model_name = extract_provider_and_model_name(kwargs)
|
110
|
+
provider_name, model_name = extract_provider_and_model_name(kwargs=kwargs)
|
111
111
|
with integration.trace(
|
112
112
|
pin,
|
113
113
|
"%s.%s" % (instance.__class__.__name__, func.__name__),
|
@@ -126,7 +126,7 @@ def traced_embed_content(genai, pin, func, instance, args, kwargs):
|
|
126
126
|
@with_traced_module
|
127
127
|
async def traced_async_embed_content(genai, pin, func, instance, args, kwargs):
|
128
128
|
integration = genai._datadog_integration
|
129
|
-
provider_name, model_name = extract_provider_and_model_name(kwargs)
|
129
|
+
provider_name, model_name = extract_provider_and_model_name(kwargs=kwargs)
|
130
130
|
with integration.trace(
|
131
131
|
pin,
|
132
132
|
"%s.%s" % (instance.__class__.__name__, func.__name__),
|
@@ -11,7 +11,7 @@ from ddtrace.contrib.internal.trace_utils import unwrap
|
|
11
11
|
from ddtrace.contrib.internal.trace_utils import with_traced_module
|
12
12
|
from ddtrace.contrib.internal.trace_utils import wrap
|
13
13
|
from ddtrace.llmobs._integrations import GeminiIntegration
|
14
|
-
from ddtrace.llmobs._integrations.
|
14
|
+
from ddtrace.llmobs._integrations.google_utils import extract_provider_and_model_name
|
15
15
|
from ddtrace.trace import Pin
|
16
16
|
|
17
17
|
|
@@ -40,11 +40,12 @@ def traced_generate(genai, pin, func, instance, args, kwargs):
|
|
40
40
|
integration = genai._datadog_integration
|
41
41
|
stream = kwargs.get("stream", False)
|
42
42
|
generations = None
|
43
|
+
provider_name, model_name = extract_provider_and_model_name(instance=instance, model_name_attr="model_name")
|
43
44
|
span = integration.trace(
|
44
45
|
pin,
|
45
46
|
"%s.%s" % (instance.__class__.__name__, func.__name__),
|
46
|
-
provider=
|
47
|
-
model=
|
47
|
+
provider=provider_name,
|
48
|
+
model=model_name,
|
48
49
|
submit_to_llmobs=True,
|
49
50
|
)
|
50
51
|
try:
|
@@ -68,11 +69,12 @@ async def traced_agenerate(genai, pin, func, instance, args, kwargs):
|
|
68
69
|
integration = genai._datadog_integration
|
69
70
|
stream = kwargs.get("stream", False)
|
70
71
|
generations = None
|
72
|
+
provider_name, model_name = extract_provider_and_model_name(instance=instance, model_name_attr="model_name")
|
71
73
|
span = integration.trace(
|
72
74
|
pin,
|
73
75
|
"%s.%s" % (instance.__class__.__name__, func.__name__),
|
74
|
-
provider=
|
75
|
-
model=
|
76
|
+
provider=provider_name,
|
77
|
+
model=model_name,
|
76
78
|
submit_to_llmobs=True,
|
77
79
|
)
|
78
80
|
try:
|