ddtrace 3.11.0rc1__cp312-cp312-musllinux_1_2_aarch64.whl → 3.11.0rc3__cp312-cp312-musllinux_1_2_aarch64.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.

Files changed (159) hide show
  1. ddtrace/_logger.py +5 -6
  2. ddtrace/_trace/product.py +1 -1
  3. ddtrace/_trace/sampling_rule.py +25 -33
  4. ddtrace/_trace/trace_handlers.py +12 -50
  5. ddtrace/_trace/utils_botocore/span_tags.py +48 -0
  6. ddtrace/_version.py +2 -2
  7. ddtrace/appsec/_asm_request_context.py +3 -1
  8. ddtrace/appsec/_constants.py +7 -0
  9. ddtrace/appsec/_handlers.py +11 -0
  10. ddtrace/appsec/_iast/_listener.py +12 -2
  11. ddtrace/appsec/_processor.py +1 -1
  12. ddtrace/contrib/integration_registry/registry.yaml +10 -0
  13. ddtrace/contrib/internal/aiobotocore/patch.py +8 -0
  14. ddtrace/contrib/internal/avro/__init__.py +17 -0
  15. ddtrace/contrib/internal/azure_functions/patch.py +23 -12
  16. ddtrace/contrib/internal/azure_functions/utils.py +14 -0
  17. ddtrace/contrib/internal/boto/patch.py +14 -0
  18. ddtrace/contrib/internal/botocore/__init__.py +153 -0
  19. ddtrace/contrib/internal/botocore/services/bedrock.py +3 -27
  20. ddtrace/contrib/internal/django/patch.py +31 -8
  21. ddtrace/contrib/{_freezegun.py → internal/freezegun/__init__.py} +1 -1
  22. ddtrace/contrib/internal/google_genai/_utils.py +2 -2
  23. ddtrace/contrib/internal/google_genai/patch.py +7 -7
  24. ddtrace/contrib/internal/google_generativeai/patch.py +7 -5
  25. ddtrace/contrib/internal/langchain/patch.py +11 -443
  26. ddtrace/contrib/internal/langchain/utils.py +0 -26
  27. ddtrace/contrib/internal/logbook/patch.py +1 -2
  28. ddtrace/contrib/internal/logging/patch.py +4 -7
  29. ddtrace/contrib/internal/loguru/patch.py +1 -3
  30. ddtrace/contrib/internal/openai_agents/patch.py +44 -1
  31. ddtrace/contrib/internal/protobuf/__init__.py +17 -0
  32. ddtrace/contrib/internal/pytest/__init__.py +62 -0
  33. ddtrace/contrib/internal/pytest/_plugin_v2.py +13 -4
  34. ddtrace/contrib/internal/pytest_bdd/__init__.py +23 -0
  35. ddtrace/contrib/internal/pytest_benchmark/__init__.py +3 -0
  36. ddtrace/contrib/internal/structlog/patch.py +2 -4
  37. ddtrace/contrib/internal/unittest/__init__.py +36 -0
  38. ddtrace/contrib/internal/vertexai/patch.py +7 -5
  39. ddtrace/ext/ci.py +20 -0
  40. ddtrace/ext/git.py +66 -11
  41. ddtrace/internal/_encoding.cpython-312-aarch64-linux-musl.so +0 -0
  42. ddtrace/internal/_encoding.pyi +1 -1
  43. ddtrace/internal/ci_visibility/encoder.py +126 -49
  44. ddtrace/internal/ci_visibility/utils.py +4 -4
  45. ddtrace/internal/core/__init__.py +5 -2
  46. ddtrace/internal/endpoints.py +76 -0
  47. ddtrace/internal/schema/processor.py +6 -2
  48. ddtrace/internal/telemetry/writer.py +18 -0
  49. ddtrace/internal/test_visibility/coverage_lines.py +4 -4
  50. ddtrace/internal/writer/writer.py +24 -11
  51. ddtrace/llmobs/_constants.py +3 -0
  52. ddtrace/llmobs/_experiment.py +75 -10
  53. ddtrace/llmobs/_integrations/bedrock.py +4 -0
  54. ddtrace/llmobs/_integrations/bedrock_agents.py +5 -1
  55. ddtrace/llmobs/_integrations/crewai.py +52 -3
  56. ddtrace/llmobs/_integrations/gemini.py +7 -7
  57. ddtrace/llmobs/_integrations/google_genai.py +10 -10
  58. ddtrace/llmobs/_integrations/{google_genai_utils.py → google_utils.py} +103 -7
  59. ddtrace/llmobs/_integrations/langchain.py +29 -20
  60. ddtrace/llmobs/_integrations/openai_agents.py +145 -0
  61. ddtrace/llmobs/_integrations/pydantic_ai.py +67 -26
  62. ddtrace/llmobs/_integrations/utils.py +68 -158
  63. ddtrace/llmobs/_integrations/vertexai.py +8 -8
  64. ddtrace/llmobs/_llmobs.py +83 -14
  65. ddtrace/llmobs/_telemetry.py +20 -5
  66. ddtrace/llmobs/_utils.py +27 -0
  67. ddtrace/settings/_config.py +1 -2
  68. ddtrace/settings/asm.py +9 -2
  69. ddtrace/settings/profiling.py +0 -9
  70. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/METADATA +1 -1
  71. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/RECORD +154 -160
  72. ddtrace/contrib/_avro.py +0 -17
  73. ddtrace/contrib/_botocore.py +0 -153
  74. ddtrace/contrib/_protobuf.py +0 -17
  75. ddtrace/contrib/_pytest.py +0 -62
  76. ddtrace/contrib/_pytest_bdd.py +0 -23
  77. ddtrace/contrib/_pytest_benchmark.py +0 -3
  78. ddtrace/contrib/_unittest.py +0 -36
  79. /ddtrace/contrib/{_aiobotocore.py → internal/aiobotocore/__init__.py} +0 -0
  80. /ddtrace/contrib/{_aiohttp_jinja2.py → internal/aiohttp_jinja2/__init__.py} +0 -0
  81. /ddtrace/contrib/{_aiomysql.py → internal/aiomysql/__init__.py} +0 -0
  82. /ddtrace/contrib/{_aiopg.py → internal/aiopg/__init__.py} +0 -0
  83. /ddtrace/contrib/{_aioredis.py → internal/aioredis/__init__.py} +0 -0
  84. /ddtrace/contrib/{_algoliasearch.py → internal/algoliasearch/__init__.py} +0 -0
  85. /ddtrace/contrib/{_anthropic.py → internal/anthropic/__init__.py} +0 -0
  86. /ddtrace/contrib/{_aredis.py → internal/aredis/__init__.py} +0 -0
  87. /ddtrace/contrib/{_asyncio.py → internal/asyncio/__init__.py} +0 -0
  88. /ddtrace/contrib/{_asyncpg.py → internal/asyncpg/__init__.py} +0 -0
  89. /ddtrace/contrib/{_aws_lambda.py → internal/aws_lambda/__init__.py} +0 -0
  90. /ddtrace/contrib/{_azure_functions.py → internal/azure_functions/__init__.py} +0 -0
  91. /ddtrace/contrib/{_azure_servicebus.py → internal/azure_servicebus/__init__.py} +0 -0
  92. /ddtrace/contrib/{_boto.py → internal/boto/__init__.py} +0 -0
  93. /ddtrace/contrib/{_cassandra.py → internal/cassandra/__init__.py} +0 -0
  94. /ddtrace/contrib/{_consul.py → internal/consul/__init__.py} +0 -0
  95. /ddtrace/contrib/{_coverage.py → internal/coverage/__init__.py} +0 -0
  96. /ddtrace/contrib/{_crewai.py → internal/crewai/__init__.py} +0 -0
  97. /ddtrace/contrib/{_django.py → internal/django/__init__.py} +0 -0
  98. /ddtrace/contrib/{_dogpile_cache.py → internal/dogpile_cache/__init__.py} +0 -0
  99. /ddtrace/contrib/{_dramatiq.py → internal/dramatiq/__init__.py} +0 -0
  100. /ddtrace/contrib/{_elasticsearch.py → internal/elasticsearch/__init__.py} +0 -0
  101. /ddtrace/contrib/{_fastapi.py → internal/fastapi/__init__.py} +0 -0
  102. /ddtrace/contrib/{_flask.py → internal/flask/__init__.py} +0 -0
  103. /ddtrace/contrib/{_futures.py → internal/futures/__init__.py} +0 -0
  104. /ddtrace/contrib/{_gevent.py → internal/gevent/__init__.py} +0 -0
  105. /ddtrace/contrib/{_google_genai.py → internal/google_genai/__init__.py} +0 -0
  106. /ddtrace/contrib/{_google_generativeai.py → internal/google_generativeai/__init__.py} +0 -0
  107. /ddtrace/contrib/{_graphql.py → internal/graphql/__init__.py} +0 -0
  108. /ddtrace/contrib/{_grpc.py → internal/grpc/__init__.py} +0 -0
  109. /ddtrace/contrib/{_gunicorn.py → internal/gunicorn/__init__.py} +0 -0
  110. /ddtrace/contrib/{_httplib.py → internal/httplib/__init__.py} +0 -0
  111. /ddtrace/contrib/{_httpx.py → internal/httpx/__init__.py} +0 -0
  112. /ddtrace/contrib/{_jinja2.py → internal/jinja2/__init__.py} +0 -0
  113. /ddtrace/contrib/{_kafka.py → internal/kafka/__init__.py} +0 -0
  114. /ddtrace/contrib/{_kombu.py → internal/kombu/__init__.py} +0 -0
  115. /ddtrace/contrib/{_langchain.py → internal/langchain/__init__.py} +0 -0
  116. /ddtrace/contrib/{_langgraph.py → internal/langgraph/__init__.py} +0 -0
  117. /ddtrace/contrib/{_litellm.py → internal/litellm/__init__.py} +0 -0
  118. /ddtrace/contrib/{_logbook.py → internal/logbook/__init__.py} +0 -0
  119. /ddtrace/contrib/{_logging.py → internal/logging/__init__.py} +0 -0
  120. /ddtrace/contrib/{_loguru.py → internal/loguru/__init__.py} +0 -0
  121. /ddtrace/contrib/{_mako.py → internal/mako/__init__.py} +0 -0
  122. /ddtrace/contrib/{_mariadb.py → internal/mariadb/__init__.py} +0 -0
  123. /ddtrace/contrib/{_mcp.py → internal/mcp/__init__.py} +0 -0
  124. /ddtrace/contrib/{_molten.py → internal/molten/__init__.py} +0 -0
  125. /ddtrace/contrib/{_mongoengine.py → internal/mongoengine/__init__.py} +0 -0
  126. /ddtrace/contrib/{_mysql.py → internal/mysql/__init__.py} +0 -0
  127. /ddtrace/contrib/{_mysqldb.py → internal/mysqldb/__init__.py} +0 -0
  128. /ddtrace/contrib/{_openai.py → internal/openai/__init__.py} +0 -0
  129. /ddtrace/contrib/{_openai_agents.py → internal/openai_agents/__init__.py} +0 -0
  130. /ddtrace/contrib/{_psycopg.py → internal/psycopg/__init__.py} +0 -0
  131. /ddtrace/contrib/{_pydantic_ai.py → internal/pydantic_ai/__init__.py} +0 -0
  132. /ddtrace/contrib/{_pymemcache.py → internal/pymemcache/__init__.py} +0 -0
  133. /ddtrace/contrib/{_pymongo.py → internal/pymongo/__init__.py} +0 -0
  134. /ddtrace/contrib/{_pymysql.py → internal/pymysql/__init__.py} +0 -0
  135. /ddtrace/contrib/{_pynamodb.py → internal/pynamodb/__init__.py} +0 -0
  136. /ddtrace/contrib/{_pyodbc.py → internal/pyodbc/__init__.py} +0 -0
  137. /ddtrace/contrib/{_redis.py → internal/redis/__init__.py} +0 -0
  138. /ddtrace/contrib/{_rediscluster.py → internal/rediscluster/__init__.py} +0 -0
  139. /ddtrace/contrib/{_rq.py → internal/rq/__init__.py} +0 -0
  140. /ddtrace/contrib/{_sanic.py → internal/sanic/__init__.py} +0 -0
  141. /ddtrace/contrib/{_selenium.py → internal/selenium/__init__.py} +0 -0
  142. /ddtrace/contrib/{_snowflake.py → internal/snowflake/__init__.py} +0 -0
  143. /ddtrace/contrib/{_sqlite3.py → internal/sqlite3/__init__.py} +0 -0
  144. /ddtrace/contrib/{_starlette.py → internal/starlette/__init__.py} +0 -0
  145. /ddtrace/contrib/{_structlog.py → internal/structlog/__init__.py} +0 -0
  146. /ddtrace/contrib/{_subprocess.py → internal/subprocess/__init__.py} +0 -0
  147. /ddtrace/contrib/{_urllib.py → internal/urllib/__init__.py} +0 -0
  148. /ddtrace/contrib/{_urllib3.py → internal/urllib3/__init__.py} +0 -0
  149. /ddtrace/contrib/{_vertexai.py → internal/vertexai/__init__.py} +0 -0
  150. /ddtrace/contrib/{_vertica.py → internal/vertica/__init__.py} +0 -0
  151. /ddtrace/contrib/{_webbrowser.py → internal/webbrowser/__init__.py} +0 -0
  152. /ddtrace/contrib/{_yaaredis.py → internal/yaaredis/__init__.py} +0 -0
  153. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/WHEEL +0 -0
  154. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/entry_points.txt +0 -0
  155. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/licenses/LICENSE +0 -0
  156. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/licenses/LICENSE.Apache +0 -0
  157. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/licenses/LICENSE.BSD3 +0 -0
  158. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/licenses/NOTICE +0 -0
  159. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/top_level.txt +0 -0
@@ -106,32 +106,6 @@ def shared_stream(
106
106
  raise
107
107
 
108
108
 
109
- def tag_general_message_input(span, inputs, integration, langchain_core):
110
- if langchain_core and isinstance(inputs, langchain_core.prompt_values.PromptValue):
111
- inputs = inputs.to_messages()
112
- elif not isinstance(inputs, list):
113
- inputs = [inputs]
114
- for input_idx, inp in enumerate(inputs):
115
- if isinstance(inp, dict):
116
- span.set_tag_str(
117
- "langchain.request.messages.%d.content" % (input_idx),
118
- integration.trunc(str(inp.get("content", ""))),
119
- )
120
- role = inp.get("role")
121
- if role is not None:
122
- span.set_tag_str(
123
- "langchain.request.messages.%d.message_type" % (input_idx),
124
- str(inp.get("role", "")),
125
- )
126
- elif langchain_core and isinstance(inp, langchain_core.messages.BaseMessage):
127
- content = inp.content
128
- role = inp.__class__.__name__
129
- span.set_tag_str("langchain.request.messages.%d.content" % (input_idx), integration.trunc(str(content)))
130
- span.set_tag_str("langchain.request.messages.%d.message_type" % (input_idx), str(role))
131
- else:
132
- span.set_tag_str("langchain.request.messages.%d.content" % (input_idx), integration.trunc(str(inp)))
133
-
134
-
135
109
  def _get_chunk_callback(interface_type, args, kwargs):
136
110
  results = core.dispatch_with_results("langchain.stream.chunk.callback", (interface_type, args, kwargs))
137
111
  callbacks = []
@@ -5,7 +5,6 @@ from wrapt import wrap_function_wrapper as _w
5
5
 
6
6
  import ddtrace
7
7
  from ddtrace import config
8
- from ddtrace._logger import LogInjectionState
9
8
  from ddtrace.contrib.internal.trace_utils import unwrap as _u
10
9
  from ddtrace.internal.utils import get_argument_value
11
10
 
@@ -27,7 +26,7 @@ def _supported_versions() -> Dict[str, str]:
27
26
 
28
27
  def _w_process_record(func, instance, args, kwargs):
29
28
  # patch logger to include datadog info before logging
30
- if config._logs_injection != LogInjectionState.DISABLED:
29
+ if config._logs_injection:
31
30
  record = get_argument_value(args, kwargs, 0, "record")
32
31
  record.extra.update(ddtrace.tracer.get_log_correlation_context())
33
32
  return func(*args, **kwargs)
@@ -5,7 +5,6 @@ from wrapt import wrap_function_wrapper as _w
5
5
 
6
6
  import ddtrace
7
7
  from ddtrace import config
8
- from ddtrace._logger import LogInjectionState
9
8
  from ddtrace._logger import set_log_formatting
10
9
  from ddtrace.contrib.internal.trace_utils import unwrap as _u
11
10
  from ddtrace.internal.constants import LOG_ATTR_ENV
@@ -54,15 +53,13 @@ class DDLogRecord:
54
53
  def _w_makeRecord(func, instance, args, kwargs):
55
54
  # Get the LogRecord instance for this log
56
55
  record = func(*args, **kwargs)
57
- if config._logs_injection == LogInjectionState.DISABLED:
58
- # log injection is opt-in for non-structured logging
59
- return record
60
- record.__dict__.update(ddtrace.tracer.get_log_correlation_context())
56
+ if config._logs_injection:
57
+ record.__dict__.update(ddtrace.tracer.get_log_correlation_context())
61
58
  return record
62
59
 
63
60
 
64
61
  def _w_StrFormatStyle_format(func, instance, args, kwargs):
65
- if config._logs_injection != LogInjectionState.ENABLED:
62
+ if not config._logs_injection:
66
63
  return func(*args, **kwargs)
67
64
  # The format string "dd.service={dd.service}" expects
68
65
  # the record to have a "dd" property which is an object that
@@ -103,7 +100,7 @@ def patch():
103
100
  _w(logging.Logger, "makeRecord", _w_makeRecord)
104
101
  _w(logging.StrFormatStyle, "_format", _w_StrFormatStyle_format)
105
102
 
106
- if config._logs_injection == LogInjectionState.ENABLED:
103
+ if config._logs_injection:
107
104
  # Only set the formatter is DD_LOGS_INJECTION is set to True. We do not want to modify
108
105
  # unstructured logs if a user has not enabled logs injection.
109
106
  # Also, the Datadog log format must be set after the logging module has been patched,
@@ -5,7 +5,6 @@ from wrapt import wrap_function_wrapper as _w
5
5
 
6
6
  import ddtrace
7
7
  from ddtrace import config
8
- from ddtrace._logger import LogInjectionState
9
8
  from ddtrace.contrib.internal.trace_utils import unwrap as _u
10
9
 
11
10
 
@@ -25,12 +24,11 @@ def _supported_versions() -> Dict[str, str]:
25
24
 
26
25
 
27
26
  def _tracer_injection(event_dict):
28
- if config._logs_injection == LogInjectionState.DISABLED:
27
+ if not config._logs_injection:
29
28
  # log injection is opt-out for structured logging
30
29
  return event_dict
31
30
  event_dd_attributes = ddtrace.tracer.get_log_correlation_context()
32
31
  event_dict.update(event_dd_attributes)
33
-
34
32
  return event_dd_attributes
35
33
 
36
34
 
@@ -5,6 +5,10 @@ from agents.tracing import add_trace_processor
5
5
 
6
6
  from ddtrace import config
7
7
  from ddtrace.contrib.internal.openai_agents.processor import LLMObsTraceProcessor
8
+ from ddtrace.contrib.trace_utils import unwrap
9
+ from ddtrace.contrib.trace_utils import with_traced_module_async
10
+ from ddtrace.contrib.trace_utils import wrap
11
+ from ddtrace.internal.utils.version import parse_version
8
12
  from ddtrace.llmobs._integrations.openai_agents import OpenAIAgentsIntegration
9
13
  from ddtrace.trace import Pin
10
14
 
@@ -22,6 +26,29 @@ def _supported_versions() -> Dict[str, str]:
22
26
  return {"agents": ">=0.0.2"}
23
27
 
24
28
 
29
+ OPENAI_AGENTS_VERSION = parse_version(get_version())
30
+
31
+
32
+ @with_traced_module_async
33
+ async def patched_run_single_turn(agents, pin, func, instance, args, kwargs):
34
+ return await _patched_run_single_turn(agents, pin, func, instance, args, kwargs, agent_index=0)
35
+
36
+
37
+ @with_traced_module_async
38
+ async def patched_run_single_turn_streamed(agents, pin, func, instance, args, kwargs):
39
+ return await _patched_run_single_turn(agents, pin, func, instance, args, kwargs, agent_index=1)
40
+
41
+
42
+ async def _patched_run_single_turn(agents, pin, func, instance, args, kwargs, agent_index=0):
43
+ current_span = pin.tracer.current_span()
44
+ result = await func(*args, **kwargs)
45
+
46
+ integration = agents._datadog_integration
47
+ integration.tag_agent_manifest(current_span, args, kwargs, agent_index)
48
+
49
+ return result
50
+
51
+
25
52
  def patch():
26
53
  """
27
54
  Patch the instrumented methods
@@ -33,7 +60,16 @@ def patch():
33
60
 
34
61
  Pin().onto(agents)
35
62
 
36
- add_trace_processor(LLMObsTraceProcessor(OpenAIAgentsIntegration(integration_config=config.openai_agents)))
63
+ integration = OpenAIAgentsIntegration(integration_config=config.openai_agents)
64
+ add_trace_processor(LLMObsTraceProcessor(integration))
65
+ agents._datadog_integration = integration
66
+
67
+ if OPENAI_AGENTS_VERSION >= (0, 0, 19):
68
+ wrap(agents.run.AgentRunner, "_run_single_turn", patched_run_single_turn(agents))
69
+ wrap(agents.run.AgentRunner, "_run_single_turn_streamed", patched_run_single_turn_streamed(agents))
70
+ else:
71
+ wrap(agents.run.Runner, "_run_single_turn", patched_run_single_turn(agents))
72
+ wrap(agents.run.Runner, "_run_single_turn_streamed", patched_run_single_turn_streamed(agents))
37
73
 
38
74
 
39
75
  def unpatch():
@@ -44,3 +80,10 @@ def unpatch():
44
80
  return
45
81
 
46
82
  agents._datadog_patch = False
83
+
84
+ if OPENAI_AGENTS_VERSION >= (0, 0, 19):
85
+ unwrap(agents.run.AgentRunner, "_run_single_turn")
86
+ unwrap(agents.run.AgentRunner, "_run_single_turn_streamed")
87
+ else:
88
+ unwrap(agents.run.Runner, "_run_single_turn")
89
+ unwrap(agents.run.Runner, "_run_single_turn_streamed")
@@ -0,0 +1,17 @@
1
+ """
2
+ The Protobuf integration will trace all Protobuf read / write calls made with the ``google.protobuf``
3
+ library. This integration is enabled by default.
4
+
5
+ Enabling
6
+ ~~~~~~~~
7
+
8
+ The protobuf integration is enabled by default. Use
9
+ :func:`patch()<ddtrace.patch>` to enable the integration::
10
+
11
+ from ddtrace import patch
12
+ patch(protobuf=True)
13
+
14
+ Configuration
15
+ ~~~~~~~~~~~~~
16
+
17
+ """
@@ -0,0 +1,62 @@
1
+ """
2
+ The pytest integration traces test executions.
3
+
4
+ Enabling
5
+ ~~~~~~~~
6
+
7
+ Enable traced execution of tests using ``pytest`` runner by
8
+ running ``pytest --ddtrace`` or by modifying any configuration
9
+ file read by pytest (``pytest.ini``, ``setup.cfg``, ...)::
10
+
11
+ [pytest]
12
+ ddtrace = 1
13
+
14
+
15
+ If you need to disable it, the option ``--no-ddtrace`` will take
16
+ precedence over ``--ddtrace`` and (``pytest.ini``, ``setup.cfg``, ...)
17
+
18
+ You can enable all integrations by using the ``--ddtrace-patch-all`` option
19
+ alongside ``--ddtrace`` or by adding this to your configuration::
20
+
21
+ [pytest]
22
+ ddtrace = 1
23
+ ddtrace-patch-all = 1
24
+
25
+
26
+ .. note::
27
+ The ddtrace plugin for pytest has the side effect of importing the ddtrace
28
+ package and starting a global tracer.
29
+
30
+ If this is causing issues for your pytest runs where traced execution of
31
+ tests is not enabled, you can deactivate the plugin::
32
+
33
+ [pytest]
34
+ addopts = -p no:ddtrace
35
+
36
+ See the `pytest documentation
37
+ <https://docs.pytest.org/en/7.1.x/how-to/plugins.html#deactivating-unregistering-a-plugin-by-name>`_
38
+ for more details.
39
+
40
+
41
+ Global Configuration
42
+ ~~~~~~~~~~~~~~~~~~~~
43
+
44
+ .. py:data:: ddtrace.config.pytest["service"]
45
+
46
+ The service name reported by default for pytest traces.
47
+
48
+ This option can also be set with the integration specific ``DD_PYTEST_SERVICE`` environment
49
+ variable, or more generally with the `DD_SERVICE` environment variable.
50
+
51
+ Default: Name of the repository being tested, otherwise ``"pytest"`` if the repository name cannot be found.
52
+
53
+
54
+ .. py:data:: ddtrace.config.pytest["operation_name"]
55
+
56
+ The operation name reported by default for pytest traces.
57
+
58
+ This option can also be set with the ``DD_PYTEST_OPERATION_NAME`` environment
59
+ variable.
60
+
61
+ Default: ``"pytest.test"``
62
+ """
@@ -237,11 +237,12 @@ def _pytest_load_initial_conftests_pre_yield(early_config, parser, args):
237
237
  ModuleCodeCollector has a tangible impact on the time it takes to load modules, so it should only be installed if
238
238
  coverage collection is requested by the backend.
239
239
  """
240
+ take_over_logger_stream_handler()
241
+
240
242
  if not _is_enabled_early(early_config, args):
241
243
  return
242
244
 
243
245
  try:
244
- take_over_logger_stream_handler()
245
246
  dd_config.test_visibility.itr_skipping_level = ITR_SKIPPING_LEVEL.SUITE
246
247
  enable_test_visibility(config=dd_config.pytest)
247
248
  if InternalTestSession.should_collect_coverage():
@@ -344,7 +345,7 @@ def pytest_sessionstart(session: pytest.Session) -> None:
344
345
  test_impact_analysis="1" if _pytest_version_supports_itr() else None,
345
346
  test_management_quarantine="1",
346
347
  test_management_disable="1",
347
- test_management_attempt_to_fix="4" if _pytest_version_supports_attempt_to_fix() else None,
348
+ test_management_attempt_to_fix="5" if _pytest_version_supports_attempt_to_fix() else None,
348
349
  )
349
350
 
350
351
  InternalTestSession.discover(
@@ -824,8 +825,16 @@ def _pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None:
824
825
  run_coverage_report()
825
826
 
826
827
  lines_pct_value = _coverage_data.get(PCT_COVERED_KEY, None)
827
- if not isinstance(lines_pct_value, float):
828
- log.warning("Tried to add total covered percentage to session span but the format was unexpected")
828
+ if lines_pct_value is None:
829
+ log.debug("Unable to retrieve coverage data for the session span")
830
+ elif not isinstance(lines_pct_value, (float, int)):
831
+ t = type(lines_pct_value)
832
+ log.warning(
833
+ "Unexpected format for total covered percentage: type=%s.%s, value=%r",
834
+ t.__module__,
835
+ t.__name__,
836
+ lines_pct_value,
837
+ )
829
838
  else:
830
839
  InternalTestSession.set_covered_lines_pct(lines_pct_value)
831
840
 
@@ -0,0 +1,23 @@
1
+ """
2
+ The pytest-bdd integration traces executions of scenarios and steps.
3
+
4
+ Enabling
5
+ ~~~~~~~~
6
+
7
+ Please follow the instructions for enabling `pytest` integration.
8
+
9
+ .. note::
10
+ The ddtrace.pytest_bdd plugin for pytest-bdd has the side effect of importing
11
+ the ddtrace package and starting a global tracer.
12
+
13
+ If this is causing issues for your pytest-bdd runs where traced execution of
14
+ tests is not enabled, you can deactivate the plugin::
15
+
16
+ [pytest]
17
+ addopts = -p no:ddtrace.pytest_bdd
18
+
19
+ See the `pytest documentation
20
+ <https://docs.pytest.org/en/7.1.x/how-to/plugins.html#deactivating-unregistering-a-plugin-by-name>`_
21
+ for more details.
22
+
23
+ """
@@ -0,0 +1,3 @@
1
+ """
2
+ The pytest-benchmark integration traces executions of pytest benchmarks.
3
+ """
@@ -4,7 +4,6 @@ import structlog
4
4
 
5
5
  import ddtrace
6
6
  from ddtrace import config
7
- from ddtrace._logger import LogInjectionState
8
7
  from ddtrace.contrib.internal.trace_utils import unwrap as _u
9
8
  from ddtrace.contrib.internal.trace_utils import wrap as _w
10
9
  from ddtrace.internal.utils import get_argument_value
@@ -27,9 +26,8 @@ def _supported_versions() -> Dict[str, str]:
27
26
 
28
27
 
29
28
  def _tracer_injection(_, __, event_dict):
30
- if config._logs_injection == LogInjectionState.DISABLED:
31
- return event_dict
32
- event_dict.update(ddtrace.tracer.get_log_correlation_context())
29
+ if config._logs_injection:
30
+ event_dict.update(ddtrace.tracer.get_log_correlation_context())
33
31
  return event_dict
34
32
 
35
33
 
@@ -0,0 +1,36 @@
1
+ """
2
+ The unittest integration traces test executions.
3
+
4
+
5
+ Enabling
6
+ ~~~~~~~~
7
+
8
+ The unittest integration is enabled automatically when using
9
+ :ref:`ddtrace-run<ddtracerun>` or :ref:`import ddtrace.auto<ddtraceauto>`.
10
+
11
+ Alternately, use :func:`patch()<ddtrace.patch>` to manually enable the integration::
12
+
13
+ from ddtrace import patch
14
+ patch(unittest=True)
15
+
16
+ Global Configuration
17
+ ~~~~~~~~~~~~~~~~~~~~
18
+
19
+ .. py:data:: ddtrace.config.unittest["operation_name"]
20
+
21
+ The operation name reported by default for unittest traces.
22
+
23
+ This option can also be set with the ``DD_UNITTEST_OPERATION_NAME`` environment
24
+ variable.
25
+
26
+ Default: ``"unittest.test"``
27
+
28
+ .. py:data:: ddtrace.config.unittest["strict_naming"]
29
+
30
+ Requires all ``unittest`` tests to start with ``test`` as stated in the Python documentation
31
+
32
+ This option can also be set with the ``DD_CIVISIBILITY_UNITTEST_STRICT_NAMING`` environment
33
+ variable.
34
+
35
+ Default: ``True``
36
+ """
@@ -14,7 +14,7 @@ from ddtrace.contrib.internal.trace_utils import wrap
14
14
  from ddtrace.contrib.internal.vertexai._utils import TracedAsyncVertexAIStreamResponse
15
15
  from ddtrace.contrib.internal.vertexai._utils import TracedVertexAIStreamResponse
16
16
  from ddtrace.llmobs._integrations import VertexAIIntegration
17
- from ddtrace.llmobs._integrations.utils import extract_model_name_google
17
+ from ddtrace.llmobs._integrations.google_utils import extract_provider_and_model_name
18
18
  from ddtrace.trace import Pin
19
19
 
20
20
 
@@ -60,11 +60,12 @@ def _traced_generate(vertexai, pin, func, instance, args, kwargs, model_instance
60
60
  integration = vertexai._datadog_integration
61
61
  stream = kwargs.get("stream", False)
62
62
  generations = None
63
+ provider_name, model_name = extract_provider_and_model_name(instance=model_instance, model_name_attr="_model_name")
63
64
  span = integration.trace(
64
65
  pin,
65
66
  "%s.%s" % (instance.__class__.__name__, func.__name__),
66
- provider="google",
67
- model=extract_model_name_google(model_instance, "_model_name"),
67
+ provider=provider_name,
68
+ model=model_name,
68
69
  submit_to_llmobs=True,
69
70
  )
70
71
  # history must be copied since it is modified during the LLM interaction
@@ -92,11 +93,12 @@ async def _traced_agenerate(vertexai, pin, func, instance, args, kwargs, model_i
92
93
  integration = vertexai._datadog_integration
93
94
  stream = kwargs.get("stream", False)
94
95
  generations = None
96
+ provider_name, model_name = extract_provider_and_model_name(instance=model_instance, model_name_attr="_model_name")
95
97
  span = integration.trace(
96
98
  pin,
97
99
  "%s.%s" % (instance.__class__.__name__, func.__name__),
98
- provider="google",
99
- model=extract_model_name_google(model_instance, "_model_name"),
100
+ provider=provider_name,
101
+ model=model_name,
100
102
  submit_to_llmobs=True,
101
103
  )
102
104
  # history must be copied since it is modified during the LLM interaction
ddtrace/ext/ci.py CHANGED
@@ -105,6 +105,16 @@ def tags(env=None, cwd=None):
105
105
  break
106
106
 
107
107
  git_info = git.extract_git_metadata(cwd=cwd)
108
+
109
+ # Whenever the HEAD commit SHA is present in the tags that come from the CI provider, we assume that
110
+ # the CI provider added a commit on top of the user's HEAD commit (e.g., GitHub Actions add a merge
111
+ # commit when triggered by a pull request). In that case, we extract the metadata for that commit specifically
112
+ # and add it to the tags.
113
+ head_commit_sha = tags.get(git.COMMIT_HEAD_SHA)
114
+ if head_commit_sha:
115
+ git_head_info = git.extract_git_head_metadata(head_commit_sha=head_commit_sha, cwd=cwd)
116
+ git_info.update(git_head_info)
117
+
108
118
  try:
109
119
  git_info[WORKSPACE_PATH] = git.extract_workspace_path(cwd=cwd)
110
120
  except git.GitNotFoundError:
@@ -349,6 +359,15 @@ def extract_github_actions(env):
349
359
  github_run_id,
350
360
  )
351
361
 
362
+ git_commit_head_sha = None
363
+ if "GITHUB_EVENT_PATH" in env:
364
+ try:
365
+ with open(env["GITHUB_EVENT_PATH"]) as f:
366
+ github_event_data = json.load(f)
367
+ git_commit_head_sha = github_event_data.get("pull_request", {}).get("head", {}).get("sha")
368
+ except Exception as e:
369
+ log.error("Failed to read or parse GITHUB_EVENT_PATH: %s", e)
370
+
352
371
  env_vars = {
353
372
  "GITHUB_SERVER_URL": github_server_url,
354
373
  "GITHUB_REPOSITORY": github_repository,
@@ -362,6 +381,7 @@ def extract_github_actions(env):
362
381
  git.BRANCH: env.get("GITHUB_HEAD_REF") or env.get("GITHUB_REF"),
363
382
  git.COMMIT_SHA: git_commit_sha,
364
383
  git.REPOSITORY_URL: "{0}/{1}.git".format(github_server_url, github_repository),
384
+ git.COMMIT_HEAD_SHA: git_commit_head_sha,
365
385
  JOB_URL: "{0}/{1}/commit/{2}/checks".format(github_server_url, github_repository, git_commit_sha),
366
386
  PIPELINE_ID: github_run_id,
367
387
  PIPELINE_NAME: env.get("GITHUB_WORKFLOW"),
ddtrace/ext/git.py CHANGED
@@ -33,6 +33,30 @@ BRANCH = "git.branch"
33
33
  # Git Commit SHA
34
34
  COMMIT_SHA = "git.commit.sha"
35
35
 
36
+ # Git Commit HEAD SHA
37
+ COMMIT_HEAD_SHA = "git.commit.head.sha"
38
+
39
+ # Git Commit HEAD message
40
+ COMMIT_HEAD_MESSAGE = "git.commit.head.message"
41
+
42
+ # Git Commit HEAD author date
43
+ COMMIT_HEAD_AUTHOR_DATE = "git.commit.head.author.date"
44
+
45
+ # Git Commit HEAD author email
46
+ COMMIT_HEAD_AUTHOR_EMAIL = "git.commit.head.author.email"
47
+
48
+ # Git Commit HEAD author name
49
+ COMMIT_HEAD_AUTHOR_NAME = "git.commit.head.author.name"
50
+
51
+ # Git Commit HEAD committer date
52
+ COMMIT_HEAD_COMMITTER_DATE = "git.commit.head.committer.date"
53
+
54
+ # Git Commit HEAD committer email
55
+ COMMIT_HEAD_COMMITTER_EMAIL = "git.commit.head.committer.email"
56
+
57
+ # Git Commit HEAD committer name
58
+ COMMIT_HEAD_COMMITTER_NAME = "git.commit.head.committer.name"
59
+
36
60
  # Git Repository URL
37
61
  REPOSITORY_URL = "git.repository_url"
38
62
 
@@ -173,11 +197,12 @@ def _get_device_for_path(path):
173
197
  return os.stat(path).st_dev
174
198
 
175
199
 
176
- def _unshallow_repository_with_details(cwd=None, repo=None, refspec=None):
177
- # type (Optional[str], Optional[str], Optional[str]) -> _GitSubprocessDetails
200
+ def _unshallow_repository_with_details(
201
+ cwd: Optional[str] = None, repo: Optional[str] = None, refspec: Optional[str] = None, parent_only: bool = False
202
+ ) -> _GitSubprocessDetails:
178
203
  cmd = [
179
204
  "fetch",
180
- '--shallow-since="1 month ago"',
205
+ "--deepen=1" if parent_only else '--shallow-since="1 month ago"',
181
206
  "--update-shallow",
182
207
  "--filter=blob:none",
183
208
  "--recurse-submodules=no",
@@ -190,18 +215,22 @@ def _unshallow_repository_with_details(cwd=None, repo=None, refspec=None):
190
215
  return _git_subprocess_cmd_with_details(*cmd, cwd=cwd)
191
216
 
192
217
 
193
- def _unshallow_repository(cwd=None, repo=None, refspec=None):
194
- # type (Optional[str], Optional[str], Optional[str]) -> None
195
- _unshallow_repository_with_details(cwd, repo, refspec)
218
+ def _unshallow_repository(
219
+ cwd: Optional[str] = None,
220
+ repo: Optional[str] = None,
221
+ refspec: Optional[str] = None,
222
+ parent_only: bool = False,
223
+ ) -> None:
224
+ _unshallow_repository_with_details(cwd, repo, refspec, parent_only)
196
225
 
197
226
 
198
- def extract_user_info(cwd=None):
199
- # type: (Optional[str]) -> Dict[str, Tuple[str, str, str]]
227
+ def extract_user_info(cwd: Optional[str] = None, commit_sha: Optional[str] = None) -> Dict[str, Tuple[str, str, str]]:
200
228
  """Extract commit author info from the git repository in the current directory or one specified by ``cwd``."""
201
229
  # Note: `git show -s --format... --date...` is supported since git 2.1.4 onwards
202
- stdout = _git_subprocess_cmd(
203
- "show -s --format=%an|||%ae|||%ad|||%cn|||%ce|||%cd --date=format:%Y-%m-%dT%H:%M:%S%z", cwd=cwd
204
- )
230
+ cmd = "show -s --format=%an|||%ae|||%ad|||%cn|||%ce|||%cd --date=format:%Y-%m-%dT%H:%M:%S%z"
231
+ if commit_sha:
232
+ cmd += " " + commit_sha
233
+ stdout = _git_subprocess_cmd(cmd=cmd, cwd=cwd)
205
234
  author_name, author_email, author_date, committer_name, committer_email, committer_date = stdout.split("|||")
206
235
  return {
207
236
  "author": (author_name, author_email, author_date),
@@ -316,6 +345,32 @@ def extract_commit_sha(cwd=None):
316
345
  return commit_sha
317
346
 
318
347
 
348
+ def extract_git_head_metadata(head_commit_sha: str, cwd: Optional[str] = None) -> Dict[str, Optional[str]]:
349
+ tags: Dict[str, Optional[str]] = {}
350
+
351
+ is_shallow, *_ = _is_shallow_repository_with_details(cwd=cwd)
352
+ if is_shallow:
353
+ _unshallow_repository(cwd=cwd, repo=None, refspec=None, parent_only=True)
354
+
355
+ try:
356
+ users = extract_user_info(cwd=cwd, commit_sha=head_commit_sha)
357
+ tags[COMMIT_HEAD_AUTHOR_NAME] = users["author"][0]
358
+ tags[COMMIT_HEAD_AUTHOR_EMAIL] = users["author"][1]
359
+ tags[COMMIT_HEAD_AUTHOR_DATE] = users["author"][2]
360
+ tags[COMMIT_HEAD_COMMITTER_NAME] = users["committer"][0]
361
+ tags[COMMIT_HEAD_COMMITTER_EMAIL] = users["committer"][1]
362
+ tags[COMMIT_HEAD_COMMITTER_DATE] = users["committer"][2]
363
+ tags[COMMIT_HEAD_MESSAGE] = _git_subprocess_cmd(" ".join(("log -n 1 --format=%B", head_commit_sha)), cwd)
364
+ except GitNotFoundError:
365
+ log.error("Git executable not found, cannot extract git metadata.")
366
+ except ValueError as e:
367
+ debug_mode = log.isEnabledFor(logging.DEBUG)
368
+ stderr = str(e)
369
+ log.error("Error extracting git metadata: %s", stderr, exc_info=debug_mode)
370
+
371
+ return tags
372
+
373
+
319
374
  def extract_git_metadata(cwd=None):
320
375
  # type: (Optional[str]) -> Dict[str, Optional[str]]
321
376
  """Extract git commit metadata."""
@@ -23,7 +23,7 @@ class BufferedEncoder(object):
23
23
  def __init__(self, max_size: int, max_item_size: int) -> None: ...
24
24
  def __len__(self) -> int: ...
25
25
  def put(self, item: Any) -> None: ...
26
- def encode(self) -> Tuple[Optional[bytes], int]: ...
26
+ def encode(self) -> List[Tuple[Optional[bytes], int]]: ...
27
27
  @property
28
28
  def size(self) -> int: ...
29
29