ddtrace 3.11.0rc1__cp310-cp310-musllinux_1_2_aarch64.whl → 3.11.0rc2__cp310-cp310-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.
Files changed (131) hide show
  1. ddtrace/_logger.py +5 -6
  2. ddtrace/_trace/product.py +1 -1
  3. ddtrace/_trace/trace_handlers.py +3 -1
  4. ddtrace/_version.py +2 -2
  5. ddtrace/appsec/_asm_request_context.py +3 -1
  6. ddtrace/appsec/_iast/_listener.py +12 -2
  7. ddtrace/contrib/integration_registry/registry.yaml +10 -0
  8. ddtrace/contrib/internal/avro/__init__.py +17 -0
  9. ddtrace/contrib/internal/azure_functions/patch.py +23 -12
  10. ddtrace/contrib/internal/azure_functions/utils.py +14 -0
  11. ddtrace/contrib/internal/botocore/__init__.py +153 -0
  12. ddtrace/contrib/{_freezegun.py → internal/freezegun/__init__.py} +1 -1
  13. ddtrace/contrib/internal/langchain/patch.py +11 -443
  14. ddtrace/contrib/internal/langchain/utils.py +0 -26
  15. ddtrace/contrib/internal/logbook/patch.py +1 -2
  16. ddtrace/contrib/internal/logging/patch.py +4 -7
  17. ddtrace/contrib/internal/loguru/patch.py +1 -3
  18. ddtrace/contrib/internal/protobuf/__init__.py +17 -0
  19. ddtrace/contrib/internal/pytest/__init__.py +62 -0
  20. ddtrace/contrib/internal/pytest/_plugin_v2.py +12 -3
  21. ddtrace/contrib/internal/pytest_bdd/__init__.py +23 -0
  22. ddtrace/contrib/internal/pytest_benchmark/__init__.py +3 -0
  23. ddtrace/contrib/internal/structlog/patch.py +2 -4
  24. ddtrace/contrib/internal/unittest/__init__.py +36 -0
  25. ddtrace/internal/_encoding.cpython-310-aarch64-linux-gnu.so +0 -0
  26. ddtrace/internal/_encoding.pyi +1 -1
  27. ddtrace/internal/ci_visibility/encoder.py +18 -12
  28. ddtrace/internal/ci_visibility/utils.py +4 -4
  29. ddtrace/internal/core/__init__.py +5 -2
  30. ddtrace/internal/test_visibility/coverage_lines.py +4 -4
  31. ddtrace/internal/writer/writer.py +24 -11
  32. ddtrace/llmobs/_constants.py +2 -0
  33. ddtrace/llmobs/_experiment.py +69 -10
  34. ddtrace/llmobs/_integrations/bedrock.py +4 -0
  35. ddtrace/llmobs/_integrations/bedrock_agents.py +5 -1
  36. ddtrace/llmobs/_integrations/langchain.py +29 -20
  37. ddtrace/llmobs/_llmobs.py +78 -13
  38. ddtrace/llmobs/_telemetry.py +20 -5
  39. ddtrace/llmobs/_utils.py +6 -0
  40. ddtrace/settings/_config.py +1 -2
  41. ddtrace/settings/profiling.py +0 -9
  42. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc2.dist-info}/METADATA +1 -1
  43. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc2.dist-info}/RECORD +126 -133
  44. ddtrace/contrib/_avro.py +0 -17
  45. ddtrace/contrib/_botocore.py +0 -153
  46. ddtrace/contrib/_protobuf.py +0 -17
  47. ddtrace/contrib/_pytest.py +0 -62
  48. ddtrace/contrib/_pytest_bdd.py +0 -23
  49. ddtrace/contrib/_pytest_benchmark.py +0 -3
  50. ddtrace/contrib/_unittest.py +0 -36
  51. /ddtrace/contrib/{_aiobotocore.py → internal/aiobotocore/__init__.py} +0 -0
  52. /ddtrace/contrib/{_aiohttp_jinja2.py → internal/aiohttp_jinja2/__init__.py} +0 -0
  53. /ddtrace/contrib/{_aiomysql.py → internal/aiomysql/__init__.py} +0 -0
  54. /ddtrace/contrib/{_aiopg.py → internal/aiopg/__init__.py} +0 -0
  55. /ddtrace/contrib/{_aioredis.py → internal/aioredis/__init__.py} +0 -0
  56. /ddtrace/contrib/{_algoliasearch.py → internal/algoliasearch/__init__.py} +0 -0
  57. /ddtrace/contrib/{_anthropic.py → internal/anthropic/__init__.py} +0 -0
  58. /ddtrace/contrib/{_aredis.py → internal/aredis/__init__.py} +0 -0
  59. /ddtrace/contrib/{_asyncio.py → internal/asyncio/__init__.py} +0 -0
  60. /ddtrace/contrib/{_asyncpg.py → internal/asyncpg/__init__.py} +0 -0
  61. /ddtrace/contrib/{_aws_lambda.py → internal/aws_lambda/__init__.py} +0 -0
  62. /ddtrace/contrib/{_azure_functions.py → internal/azure_functions/__init__.py} +0 -0
  63. /ddtrace/contrib/{_azure_servicebus.py → internal/azure_servicebus/__init__.py} +0 -0
  64. /ddtrace/contrib/{_boto.py → internal/boto/__init__.py} +0 -0
  65. /ddtrace/contrib/{_cassandra.py → internal/cassandra/__init__.py} +0 -0
  66. /ddtrace/contrib/{_consul.py → internal/consul/__init__.py} +0 -0
  67. /ddtrace/contrib/{_coverage.py → internal/coverage/__init__.py} +0 -0
  68. /ddtrace/contrib/{_crewai.py → internal/crewai/__init__.py} +0 -0
  69. /ddtrace/contrib/{_django.py → internal/django/__init__.py} +0 -0
  70. /ddtrace/contrib/{_dogpile_cache.py → internal/dogpile_cache/__init__.py} +0 -0
  71. /ddtrace/contrib/{_dramatiq.py → internal/dramatiq/__init__.py} +0 -0
  72. /ddtrace/contrib/{_elasticsearch.py → internal/elasticsearch/__init__.py} +0 -0
  73. /ddtrace/contrib/{_fastapi.py → internal/fastapi/__init__.py} +0 -0
  74. /ddtrace/contrib/{_flask.py → internal/flask/__init__.py} +0 -0
  75. /ddtrace/contrib/{_futures.py → internal/futures/__init__.py} +0 -0
  76. /ddtrace/contrib/{_gevent.py → internal/gevent/__init__.py} +0 -0
  77. /ddtrace/contrib/{_google_genai.py → internal/google_genai/__init__.py} +0 -0
  78. /ddtrace/contrib/{_google_generativeai.py → internal/google_generativeai/__init__.py} +0 -0
  79. /ddtrace/contrib/{_graphql.py → internal/graphql/__init__.py} +0 -0
  80. /ddtrace/contrib/{_grpc.py → internal/grpc/__init__.py} +0 -0
  81. /ddtrace/contrib/{_gunicorn.py → internal/gunicorn/__init__.py} +0 -0
  82. /ddtrace/contrib/{_httplib.py → internal/httplib/__init__.py} +0 -0
  83. /ddtrace/contrib/{_httpx.py → internal/httpx/__init__.py} +0 -0
  84. /ddtrace/contrib/{_jinja2.py → internal/jinja2/__init__.py} +0 -0
  85. /ddtrace/contrib/{_kafka.py → internal/kafka/__init__.py} +0 -0
  86. /ddtrace/contrib/{_kombu.py → internal/kombu/__init__.py} +0 -0
  87. /ddtrace/contrib/{_langchain.py → internal/langchain/__init__.py} +0 -0
  88. /ddtrace/contrib/{_langgraph.py → internal/langgraph/__init__.py} +0 -0
  89. /ddtrace/contrib/{_litellm.py → internal/litellm/__init__.py} +0 -0
  90. /ddtrace/contrib/{_logbook.py → internal/logbook/__init__.py} +0 -0
  91. /ddtrace/contrib/{_logging.py → internal/logging/__init__.py} +0 -0
  92. /ddtrace/contrib/{_loguru.py → internal/loguru/__init__.py} +0 -0
  93. /ddtrace/contrib/{_mako.py → internal/mako/__init__.py} +0 -0
  94. /ddtrace/contrib/{_mariadb.py → internal/mariadb/__init__.py} +0 -0
  95. /ddtrace/contrib/{_mcp.py → internal/mcp/__init__.py} +0 -0
  96. /ddtrace/contrib/{_molten.py → internal/molten/__init__.py} +0 -0
  97. /ddtrace/contrib/{_mongoengine.py → internal/mongoengine/__init__.py} +0 -0
  98. /ddtrace/contrib/{_mysql.py → internal/mysql/__init__.py} +0 -0
  99. /ddtrace/contrib/{_mysqldb.py → internal/mysqldb/__init__.py} +0 -0
  100. /ddtrace/contrib/{_openai.py → internal/openai/__init__.py} +0 -0
  101. /ddtrace/contrib/{_openai_agents.py → internal/openai_agents/__init__.py} +0 -0
  102. /ddtrace/contrib/{_psycopg.py → internal/psycopg/__init__.py} +0 -0
  103. /ddtrace/contrib/{_pydantic_ai.py → internal/pydantic_ai/__init__.py} +0 -0
  104. /ddtrace/contrib/{_pymemcache.py → internal/pymemcache/__init__.py} +0 -0
  105. /ddtrace/contrib/{_pymongo.py → internal/pymongo/__init__.py} +0 -0
  106. /ddtrace/contrib/{_pymysql.py → internal/pymysql/__init__.py} +0 -0
  107. /ddtrace/contrib/{_pynamodb.py → internal/pynamodb/__init__.py} +0 -0
  108. /ddtrace/contrib/{_pyodbc.py → internal/pyodbc/__init__.py} +0 -0
  109. /ddtrace/contrib/{_redis.py → internal/redis/__init__.py} +0 -0
  110. /ddtrace/contrib/{_rediscluster.py → internal/rediscluster/__init__.py} +0 -0
  111. /ddtrace/contrib/{_rq.py → internal/rq/__init__.py} +0 -0
  112. /ddtrace/contrib/{_sanic.py → internal/sanic/__init__.py} +0 -0
  113. /ddtrace/contrib/{_selenium.py → internal/selenium/__init__.py} +0 -0
  114. /ddtrace/contrib/{_snowflake.py → internal/snowflake/__init__.py} +0 -0
  115. /ddtrace/contrib/{_sqlite3.py → internal/sqlite3/__init__.py} +0 -0
  116. /ddtrace/contrib/{_starlette.py → internal/starlette/__init__.py} +0 -0
  117. /ddtrace/contrib/{_structlog.py → internal/structlog/__init__.py} +0 -0
  118. /ddtrace/contrib/{_subprocess.py → internal/subprocess/__init__.py} +0 -0
  119. /ddtrace/contrib/{_urllib.py → internal/urllib/__init__.py} +0 -0
  120. /ddtrace/contrib/{_urllib3.py → internal/urllib3/__init__.py} +0 -0
  121. /ddtrace/contrib/{_vertexai.py → internal/vertexai/__init__.py} +0 -0
  122. /ddtrace/contrib/{_vertica.py → internal/vertica/__init__.py} +0 -0
  123. /ddtrace/contrib/{_webbrowser.py → internal/webbrowser/__init__.py} +0 -0
  124. /ddtrace/contrib/{_yaaredis.py → internal/yaaredis/__init__.py} +0 -0
  125. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc2.dist-info}/WHEEL +0 -0
  126. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc2.dist-info}/entry_points.txt +0 -0
  127. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc2.dist-info}/licenses/LICENSE +0 -0
  128. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc2.dist-info}/licenses/LICENSE.Apache +0 -0
  129. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc2.dist-info}/licenses/LICENSE.BSD3 +0 -0
  130. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc2.dist-info}/licenses/NOTICE +0 -0
  131. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc2.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
 
@@ -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():
@@ -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
+ """
@@ -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
 
@@ -32,6 +32,7 @@ if TYPE_CHECKING: # pragma: no cover
32
32
  from typing import Dict # noqa:F401
33
33
  from typing import List # noqa:F401
34
34
  from typing import Optional # noqa:F401
35
+ from typing import Tuple # noqa:F401
35
36
 
36
37
  from ddtrace._trace.span import Span # noqa:F401
37
38
 
@@ -73,11 +74,10 @@ class CIVisibilityEncoderV01(BufferedEncoder):
73
74
  def encode(self):
74
75
  with self._lock:
75
76
  with StopWatch() as sw:
76
- payload = self._build_payload(self.buffer)
77
+ result_payloads = self._build_payload(self.buffer)
77
78
  record_endpoint_payload_events_serialization_time(endpoint=self.ENDPOINT_TYPE, seconds=sw.elapsed())
78
- buffer_size = len(self.buffer)
79
79
  self._init_buffer()
80
- return payload, buffer_size
80
+ return result_payloads
81
81
 
82
82
  def _get_parent_session(self, traces):
83
83
  for trace in traces:
@@ -87,6 +87,7 @@ class CIVisibilityEncoderV01(BufferedEncoder):
87
87
  return 0
88
88
 
89
89
  def _build_payload(self, traces):
90
+ # type: (List[List[Span]]) -> List[Tuple[Optional[bytes], int]]
90
91
  new_parent_session_span_id = self._get_parent_session(traces)
91
92
  is_not_xdist_worker = os.getenv("PYTEST_XDIST_WORKER") is None
92
93
  normalized_spans = [
@@ -96,20 +97,25 @@ class CIVisibilityEncoderV01(BufferedEncoder):
96
97
  if (is_not_xdist_worker or span.get_tag(EVENT_TYPE) != SESSION_TYPE)
97
98
  ]
98
99
  if not normalized_spans:
99
- return None
100
+ return []
100
101
  record_endpoint_payload_events_count(endpoint=ENDPOINT.TEST_CYCLE, count=len(normalized_spans))
101
102
 
102
103
  # TODO: Split the events in several payloads as needed to avoid hitting the intake's maximum payload size.
103
- return CIVisibilityEncoderV01._pack_payload(
104
- {"version": self.PAYLOAD_FORMAT_VERSION, "metadata": self._metadata, "events": normalized_spans}
105
- )
104
+ return [
105
+ (
106
+ CIVisibilityEncoderV01._pack_payload(
107
+ {"version": self.PAYLOAD_FORMAT_VERSION, "metadata": self._metadata, "events": normalized_spans}
108
+ ),
109
+ len(traces),
110
+ )
111
+ ]
106
112
 
107
113
  @staticmethod
108
114
  def _pack_payload(payload):
109
115
  return msgpack_packb(payload)
110
116
 
111
117
  def _convert_span(self, span, dd_origin, new_parent_session_span_id=0):
112
- # type: (Span, str, Optional[int]) -> Dict[str, Any]
118
+ # type: (Span, Optional[str], Optional[int]) -> Dict[str, Any]
113
119
  sp = JSONEncoderV2._span_to_dict(span)
114
120
  sp = JSONEncoderV2._normalize_span(sp)
115
121
  sp["type"] = span.get_tag(EVENT_TYPE) or span.span_type
@@ -230,14 +236,14 @@ class CIVisibilityCoverageEncoderV02(CIVisibilityEncoderV01):
230
236
  return msgpack_packb({"version": self.PAYLOAD_FORMAT_VERSION, "coverages": normalized_covs})
231
237
 
232
238
  def _build_payload(self, traces):
233
- # type: (List[List[Span]]) -> Optional[bytes]
239
+ # type: (List[List[Span]]) -> List[Tuple[Optional[bytes], int]]
234
240
  data = self._build_data(traces)
235
241
  if not data:
236
- return None
237
- return b"\r\n".join(self._build_body(data))
242
+ return []
243
+ return [(b"\r\n".join(self._build_body(data)), len(traces))]
238
244
 
239
245
  def _convert_span(self, span, dd_origin, new_parent_session_span_id=0):
240
- # type: (Span, str, Optional[int]) -> Dict[str, Any]
246
+ # type: (Span, Optional[str], Optional[int]) -> Dict[str, Any]
241
247
  # DEV: new_parent_session_span_id is unused here, but it is used in super class
242
248
  files: Dict[str, Any] = {}
243
249
 
@@ -112,11 +112,10 @@ def take_over_logger_stream_handler(remove_ddtrace_stream_handlers=True):
112
112
  log.debug("CIVisibility not taking over ddtrace logger because level is set to: %s", level)
113
113
  return
114
114
 
115
- root_logger = logging.getLogger()
115
+ ddtrace_logger = logging.getLogger("ddtrace")
116
116
 
117
117
  if remove_ddtrace_stream_handlers:
118
118
  log.debug("CIVisibility removing DDTrace logger handler")
119
- ddtrace_logger = logging.getLogger("ddtrace")
120
119
  for handler in list(ddtrace_logger.handlers):
121
120
  ddtrace_logger.removeHandler(handler)
122
121
  else:
@@ -136,8 +135,9 @@ def take_over_logger_stream_handler(remove_ddtrace_stream_handlers=True):
136
135
  log.warning("Invalid log level: %s", level)
137
136
  return
138
137
 
139
- root_logger.addHandler(ci_visibility_handler)
140
- root_logger.setLevel(min(root_logger.level, ci_visibility_handler.level))
138
+ ddtrace_logger.addHandler(ci_visibility_handler)
139
+ ddtrace_logger.setLevel(min(ddtrace_logger.level, ci_visibility_handler.level))
140
+ ddtrace_logger.propagate = False
141
141
 
142
142
  log.debug("logger setup complete")
143
143
 
@@ -197,9 +197,12 @@ class ExecutionContext(object):
197
197
  self._parent = value
198
198
 
199
199
  def __exit__(
200
- self, exc_type: Optional[type], exc_value: Optional[BaseException], traceback: Optional[types.TracebackType]
200
+ self,
201
+ exc_type: Optional[type],
202
+ exc_value: Optional[BaseException],
203
+ traceback: Optional[types.TracebackType],
201
204
  ) -> bool:
202
- dispatch("context.ended.%s" % self.identifier, (self,))
205
+ dispatch("context.ended.%s" % self.identifier, (self, (exc_type, exc_value, traceback)))
203
206
  if self._span is None:
204
207
  try:
205
208
  if self._token is not None:
@@ -41,8 +41,8 @@ class CoverageLines:
41
41
  def add(self, line_number: int):
42
42
  lines_byte = line_number // 8
43
43
 
44
- if lines_byte >= len(self._lines):
45
- self._lines.extend(bytearray(lines_byte - len(self._lines) + 1))
44
+ if lines_byte >= self._lines.__len__():
45
+ self._lines.extend(bytearray(lines_byte - self._lines.__len__() + 1))
46
46
 
47
47
  # DEV this fun bit allows us to trick ourselves into little-endianness, which is what the backend wants to see
48
48
  # in bytes
@@ -62,8 +62,8 @@ class CoverageLines:
62
62
 
63
63
  def update(self, other: "CoverageLines"):
64
64
  # Extend our lines if the other coverage has more lines
65
- if len(other._lines) > len(self._lines):
66
- self._lines.extend(bytearray(len(other._lines) - len(self._lines)))
65
+ if other._lines.__len__() > self._lines.__len__():
66
+ self._lines.extend(bytearray(other._lines.__len__() - self._lines.__len__()))
67
67
 
68
68
  for _byte_idx, _byte in enumerate(other._lines):
69
69
  self._lines[_byte_idx] |= _byte
@@ -385,13 +385,28 @@ class HTTPWriter(periodic.PeriodicService, TraceWriter):
385
385
  def _flush_queue_with_client(self, client: WriterClientBase, raise_exc: bool = False) -> None:
386
386
  n_traces = len(client.encoder)
387
387
  try:
388
- encoded, n_traces = client.encoder.encode()
389
-
390
- if encoded is None:
388
+ if not (encoded_traces := client.encoder.encode()):
391
389
  return
392
390
 
393
- # Should gzip the payload if intake accepts it
394
- if self._intake_accepts_gzip:
391
+ except Exception:
392
+ # FIXME(munir): if client.encoder raises an Exception n_traces may not be accurate due to race conditions
393
+ log.error("failed to encode trace with encoder %r", client.encoder, exc_info=True)
394
+ self._metrics_dist("encoder.dropped.traces", n_traces)
395
+ return
396
+
397
+ for payload in encoded_traces:
398
+ encoded_data, n_traces = payload
399
+ self._flush_single_payload(encoded_data, n_traces, client=client, raise_exc=raise_exc)
400
+
401
+ def _flush_single_payload(
402
+ self, encoded: Optional[bytes], n_traces: int, client: WriterClientBase, raise_exc: bool = False
403
+ ) -> None:
404
+ if encoded is None:
405
+ return
406
+
407
+ # Should gzip the payload if intake accepts it
408
+ if self._intake_accepts_gzip:
409
+ try:
395
410
  original_size = len(encoded)
396
411
  # Replace the value to send with the gzipped the value
397
412
  encoded = gzip.compress(encoded, compresslevel=6)
@@ -399,12 +414,10 @@ class HTTPWriter(periodic.PeriodicService, TraceWriter):
399
414
 
400
415
  # And add the header
401
416
  self._headers["Content-Encoding"] = "gzip"
402
-
403
- except Exception:
404
- # FIXME(munir): if client.encoder raises an Exception n_traces may not be accurate due to race conditions
405
- log.error("failed to encode trace with encoder %r", client.encoder, exc_info=True)
406
- self._metrics_dist("encoder.dropped.traces", n_traces)
407
- return
417
+ except Exception:
418
+ log.error("failed to compress traces with encoder %r", client.encoder, exc_info=True)
419
+ self._metrics_dist("encoder.dropped.traces", n_traces)
420
+ return
408
421
 
409
422
  try:
410
423
  self._send_payload_with_backoff(encoded, n_traces, client)
@@ -56,6 +56,7 @@ AGENTLESS_EXP_BASE_URL = "https://{}".format(EXP_SUBDOMAIN_NAME)
56
56
  EVP_PAYLOAD_SIZE_LIMIT = 5 << 20 # 5MB (actual limit is 5.1MB)
57
57
  EVP_EVENT_SIZE_LIMIT = (1 << 20) - 1024 # 999KB (actual limit is 1MB)
58
58
 
59
+ EXPERIMENT_CSV_FIELD_MAX_SIZE = 10 * 1024 * 1024
59
60
 
60
61
  DROPPED_IO_COLLECTION_ERROR = "dropped_io"
61
62
  DROPPED_VALUE_TEXT = "[This value has been dropped because this span's size exceeds the 1MB size limit.]"
@@ -97,3 +98,4 @@ PROXY_REQUEST = "llmobs.proxy_request"
97
98
 
98
99
  EXPERIMENT_ID_KEY = "_ml_obs.experiment_id"
99
100
  EXPERIMENT_EXPECTED_OUTPUT = "_ml_obs.meta.input.expected_output"
101
+ DEFAULT_PROJECT_NAME = "default-project"