Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions manifests/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,10 @@ manifest:
component_version: <3.36.0
? tests/parametric/test_span_sampling.py::Test_Span_Sampling::test_root_span_selected_and_child_dropped_by_sss_when_dropping_policy_is_active016
: missing_feature (The .NET tracer sends the full trace to the agent anyways.)
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_export_fields: v3.50.0
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_logs_export_enabled: v3.50.0
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_metrics_export_enabled: v3.50.0
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_traces_export_enabled: v3.50.0
tests/parametric/test_telemetry.py::Test_Consistent_Configs: missing_feature
tests/parametric/test_telemetry.py::Test_Defaults: v2.49.0
tests/parametric/test_telemetry.py::Test_Defaults::test_library_settings:
Expand Down
4 changes: 4 additions & 0 deletions manifests/golang.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1510,6 +1510,10 @@ manifest:
tests/parametric/test_span_sampling.py::Test_Span_Sampling::test_single_rule_with_head_and_rule_trace_sampling_keep_019: # Modified by easy win activation script
- declaration: bug (APMAPI-1545)
component_version: <2.5.0
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_export_fields: v2.11.0-dev
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_logs_export_enabled: v2.11.0-dev
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_metrics_export_enabled: v2.11.0-dev
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_traces_export_enabled: v2.11.0-dev
tests/parametric/test_telemetry.py::Test_Consistent_Configs: missing_feature (APMAPI-745)
tests/parametric/test_telemetry.py::Test_Defaults: missing_feature
tests/parametric/test_telemetry.py::Test_Environment: missing_feature
Expand Down
4 changes: 4 additions & 0 deletions manifests/java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3974,6 +3974,10 @@ manifest:
tests/parametric/test_span_sampling.py::Test_Span_Sampling::test_single_rule_with_head_and_rule_trace_sampling_drop_020:
- component_version: ">=1.57.0"
declaration: flaky (APMAPI-1785)
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_export_fields: v1.65.0-SNAPSHOT
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_logs_export_enabled: v1.65.0-SNAPSHOT
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_metrics_export_enabled: v1.65.0-SNAPSHOT
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_traces_export_enabled: v1.65.0-SNAPSHOT
tests/parametric/test_telemetry.py::Test_Consistent_Configs: missing_feature
tests/parametric/test_telemetry.py::Test_Defaults: v1.61.0-SNAPSHOT # Normalization of telemetry keys updated in https://github.com/DataDog/dd-trace-java/pull/10823
tests/parametric/test_telemetry.py::Test_Environment: v1.61.0-SNAPSHOT # Normalization of telemetry keys updated in https://github.com/DataDog/dd-trace-java/pull/10823
Expand Down
5 changes: 5 additions & 0 deletions manifests/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ refs:
- &ref_5_111_0 '>=5.111.0'
- &ref_6_0_0 '>=6.0.0-pre'
- &ref_6_5_0 '>=6.5.0 || ^5.116.0'
- &ref_7_0_0 '>=7.0.0-pre'
manifest:
tests/ai_guard/test_ai_guard_sdk.py::Test_AIGuardEvent_Tag:
- weblog_declaration:
Expand Down Expand Up @@ -2250,6 +2251,10 @@ manifest:
: missing_feature (Not implemented)
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_default: *ref_6_0_0
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_diagnostic_agent_unreachable: missing_feature (Diagnostic messages not emitted when agent is unreachable)
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_export_fields: *ref_7_0_0
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_logs_export_enabled: *ref_7_0_0
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_metrics_export_enabled: *ref_7_0_0
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_traces_export_enabled: *ref_7_0_0
tests/parametric/test_telemetry.py::Test_Consistent_Configs: *ref_5_25_0
tests/parametric/test_telemetry.py::Test_Consistent_Configs::test_library_settings_2: missing_feature (Not implemented)
tests/parametric/test_telemetry.py::Test_Defaults: *ref_5_6_0
Expand Down
4 changes: 4 additions & 0 deletions manifests/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,10 @@ manifest:
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_default: incomplete_test_app (Not currently testable)
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_diagnostic_agent_unreachable: missing_feature (Diagnostic messages not emitted when agent is unreachable)
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_enabled: incomplete_test_app (Not currently testable)
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_export_fields: incomplete_test_app (Not currently testable)
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_logs_export_enabled: incomplete_test_app (Not currently testable)
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_metrics_export_enabled: incomplete_test_app (Not currently testable)
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_traces_export_enabled: incomplete_test_app (Not currently testable)
tests/parametric/test_telemetry.py::Test_Consistent_Configs: missing_feature
tests/parametric/test_telemetry.py::Test_Defaults: missing_feature
tests/parametric/test_telemetry.py::Test_Environment: missing_feature
Expand Down
4 changes: 4 additions & 0 deletions manifests/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2044,6 +2044,10 @@ manifest:
tests/parametric/test_span_sampling.py::Test_Span_Sampling::test_single_rule_with_head_and_rule_trace_sampling_keep_019: # Modified by easy win activation script
- declaration: bug (APMAPI-1545)
component_version: <4.3.1
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_export_fields: v4.13.0.dev
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_logs_export_enabled: v4.13.0.dev
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_metrics_export_enabled: v4.13.0.dev
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_traces_export_enabled: v4.13.0.dev
tests/parametric/test_telemetry.py::Test_Consistent_Configs: missing_feature
tests/parametric/test_telemetry.py::Test_Consistent_Configs::test_library_settings:
- declaration: missing_feature (Reports configurations with unexpected names)
Expand Down
4 changes: 4 additions & 0 deletions manifests/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2442,6 +2442,10 @@ manifest:
- declaration: bug (APMAPI-1545)
component_version: <2.20.0
tests/parametric/test_span_sampling.py::Test_Span_Sampling::test_special_glob_characters_span_sampling_sss002: "missing_feature (\"Issue: _dd.span_sampling.max_per_second is always set in Ruby\")"
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_export_fields: v2.40.0.dev
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_logs_export_enabled: v2.40.0.dev
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_metrics_export_enabled: v2.40.0.dev
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_traces_export_enabled: irrelevant (Ruby exports spans in Datadog native format and has no OTLP trace exporter)
tests/parametric/test_telemetry.py::Test_Consistent_Configs: missing_feature
tests/parametric/test_telemetry.py::Test_Defaults: missing_feature
tests/parametric/test_telemetry.py::Test_Environment: missing_feature
Expand Down
4 changes: 4 additions & 0 deletions manifests/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,10 @@ manifest:
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_default: missing_feature (Startup logs not implemented)
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_diagnostic_agent_unreachable: missing_feature (Diagnostic messages not implemented)
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_enabled: missing_feature (Startup logs not implemented)
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_export_fields: missing_feature (Startup logs not implemented)
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_logs_export_enabled: missing_feature (Startup logs not implemented)
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_metrics_export_enabled: missing_feature (Startup logs not implemented)
tests/parametric/test_startup_logs.py::Test_Startup_Logs::test_startup_logs_otlp_traces_export_enabled: missing_feature (Startup logs not implemented)
tests/parametric/test_telemetry.py::Test_Consistent_Configs: missing_feature
tests/parametric/test_telemetry.py::Test_Defaults: missing_feature
tests/parametric/test_telemetry.py::Test_Environment: missing_feature
Expand Down
193 changes: 185 additions & 8 deletions tests/parametric/test_startup_logs.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Test startup log behavior for tracer libraries across supported languages."""

import contextlib
import re
import time

import pytest

Expand All @@ -12,6 +14,59 @@
# Regex pattern for matching startup log entries across all tracer libraries
STARTUP_LOG_PATTERN = r"DATADOG (TRACER )?CONFIGURATION( - (CORE|TRACING|PROFILING|.*))?"

# Defense-in-depth caps for the container stderr read in _get_startup_logs: whichever limit is
# hit first stops the read, so an unbounded stream can't hang the suite. All are far above any
# real tracer's startup output, so they never truncate logs in normal operation.
_STARTUP_LOG_MAX_LINES = 50_000
_STARTUP_LOG_MAX_BYTES = 10 * 1024 * 1024 # 10 MiB
_STARTUP_LOG_MAX_SECONDS = 30.0

# Boolean fields every tracer publishes in its startup diagnostic configuration describing
# whether traces/metrics/logs are exported over OTLP.
OTLP_EXPORT_FIELDS = (
"otlp_traces_export_enabled",
"otlp_metrics_export_enabled",
"otlp_logs_export_enabled",
)


def _extract_otlp_export_fields(logs: str) -> dict[str, bool]:
"""Extract the three OTLP-export booleans from the tracer startup configuration line.

The startup diagnostic is NOT serialized uniformly across tracers:
- JSON, " - " marker separator: Node.js ("DATADOG TRACER CONFIGURATION - {json}"),
.NET ("DATADOG TRACER CONFIGURATION - {json}"), Ruby ("DATADOG CONFIGURATION - CORE - {json}").
- JSON, no " - " separator: Go ("DATADOG TRACER CONFIGURATION {json}"),
Java (SLF4J "DATADOG TRACER CONFIGURATION {json}").
- Python dict repr, NOT JSON: "- DATADOG TRACER CONFIGURATION - {dict}" produced by
"%s" % dict, so single-quoted keys and True/False/None -- json.loads would fail on it.

Rather than depend on a single per-language marker or full-object decoding (Python's payload
is not valid JSON, and the JSON payloads embed nested objects that make a naive brace match
fragile), the config line is located by field name and each boolean is read directly with a
quote-style- and casing-tolerant regex. An UNQUOTED true/false/True/False is required, so a
string-encoded value such as "true" would not match -- this preserves the guarantee that each
field is emitted as a real boolean.
"""
config_line = None
for line in logs.splitlines():
if OTLP_EXPORT_FIELDS[0] in line:
config_line = line
break
assert config_line is not None, (
f"No tracer startup configuration line containing '{OTLP_EXPORT_FIELDS[0]}' found. "
f"Logs (first 2000 chars): {logs[:2000]}"
)

fields: dict[str, bool] = {}
for name in OTLP_EXPORT_FIELDS:
match = re.search(rf"""["']?{name}["']?\s*:\s*(true|false|True|False)\b""", config_line)
assert match, (
f"Field '{name}' not present as a boolean in the startup configuration line. Config line: {config_line}"
)
fields[name] = match.group(1).lower() == "true"
return fields


def _get_dotnet_startup_logs(test_library: APMLibrary, *, required: bool = True) -> str | None:
"""Get .NET tracer startup logs from the container (dotnet-tracer-managed* file).
Expand Down Expand Up @@ -51,15 +106,58 @@ def _get_startup_logs(test_library: APMLibrary, *, required: bool = True) -> str
"""
if context.library == "dotnet":
return _get_dotnet_startup_logs(test_library, required=required)
else:
try:
logs = test_library.container.logs(stderr=True, stdout=False).decode("utf-8")
except Exception as e:
if required:
pytest.fail(f"Failed to retrieve container logs: {e}")
return None

return logs
# Bound the stderr read so a misbehaving tracer emitting an unbounded stream (e.g. a logging
# feedback loop) can't hang the suite -- a one-shot container.logs() read would chase that
# moving target forever. Stream the current snapshot (follow=False self-terminates at its end)
# and stop at the first of the line/byte/time caps.
#
# Read the whole bounded snapshot rather than stopping at the first startup-config marker:
# some tracers emit asserted-on content after it (Ruby logs "CONFIGURATION - CORE" at init,
# then "CONFIGURATION - TRACING" and its "Agent Error" line only after a flush). Startup logs
# are early, so the caps never truncate them in normal runs.
log_stream = None
truncated = False
buffer = bytearray()
line_count = 0
try:
# stream=True yields raw byte chunks (docker log frames), not lines; a line may span
# chunks, so we accumulate bytes and decode once at the end.
log_stream = test_library.container.logs(stream=True, stdout=False, stderr=True, follow=False)

deadline = time.monotonic() + _STARTUP_LOG_MAX_SECONDS
for chunk in log_stream:
if not chunk:
continue
buffer += chunk
line_count += chunk.count(b"\n")
if (
line_count >= _STARTUP_LOG_MAX_LINES
or len(buffer) >= _STARTUP_LOG_MAX_BYTES
or time.monotonic() >= deadline
):
truncated = True
break
except Exception as e:
if required:
pytest.fail(f"Failed to retrieve container logs: {e}")
return None
finally:
# Release the streaming socket promptly; we may have stopped mid-snapshot at a cap.
if log_stream is not None:
with contextlib.suppress(Exception):
log_stream.close()

if truncated:
# Never truncate silently: a cap only trips if a tracer is emitting an abnormally large
# (likely runaway) stderr stream, which is worth surfacing in CI output.
logger.warning(
f"Startup-log read hit a safety cap and was truncated at {line_count} lines / "
f"{len(buffer)} bytes; a tracer may be emitting an unbounded stderr stream."
)

# errors="replace" guards against a multi-byte character truncated at a cap boundary.
return buffer.decode("utf-8", errors="replace")


@scenarios.parametric
Expand Down Expand Up @@ -96,6 +194,85 @@ def test_startup_logs_enabled(self, test_library: APMLibrary):
f"Content (first 2000 chars): {logs[:2000]}"
)

@parametrize(
"library_env",
[{"DD_TRACE_STARTUP_LOGS": "true"}],
)
def test_startup_logs_otlp_export_fields(self, test_library: APMLibrary):
"""Verify the OTLP-export booleans are present, are booleans, and default to false.

No OTLP-related environment variables are set, so every implementing tracer must report
all three fields as False. DD_TRACE_STARTUP_LOGS=true only guarantees the diagnostic is
emitted; it does not affect the OTLP-export values. Uniform across implementing languages.
"""
with test_library:
logs = _get_startup_logs(test_library, required=True)

assert logs is not None
fields = _extract_otlp_export_fields(logs)
for name in OTLP_EXPORT_FIELDS:
assert fields[name] is False, (
f"Expected {name} to default to False (no OTLP env vars set), got {fields[name]!r}"
)

@parametrize(
"library_env",
[{"DD_TRACE_STARTUP_LOGS": "true", "OTEL_TRACES_EXPORTER": "otlp"}],
)
def test_startup_logs_otlp_traces_export_enabled(self, test_library: APMLibrary):
"""Verify otlp_traces_export_enabled is true when OTEL_TRACES_EXPORTER=otlp.

Gated (via manifests) to the tracers that support OTLP trace export. Ruby and PHP do not
support it, so they are marked irrelevant/incomplete rather than skipped in the body.
"""
with test_library:
logs = _get_startup_logs(test_library, required=True)

assert logs is not None
fields = _extract_otlp_export_fields(logs)
assert fields["otlp_traces_export_enabled"] is True, (
f"Expected otlp_traces_export_enabled to be True with OTEL_TRACES_EXPORTER=otlp, "
f"got {fields['otlp_traces_export_enabled']!r}"
)

@parametrize(
"library_env",
[
{
"DD_TRACE_STARTUP_LOGS": "true",
"DD_METRICS_OTEL_ENABLED": "true",
"DD_RUNTIME_METRICS_ENABLED": "true", # .NET only flips OTLP metrics when runtime metrics are on
}
],
)
def test_startup_logs_otlp_metrics_export_enabled(self, test_library: APMLibrary):
"""Verify otlp_metrics_export_enabled is true when DD_METRICS_OTEL_ENABLED=true."""
with test_library:
logs = _get_startup_logs(test_library, required=True)

assert logs is not None
fields = _extract_otlp_export_fields(logs)
assert fields["otlp_metrics_export_enabled"] is True, (
f"Expected otlp_metrics_export_enabled to be True with DD_METRICS_OTEL_ENABLED=true, "
f"got {fields['otlp_metrics_export_enabled']!r}"
)

@parametrize(
"library_env",
[{"DD_TRACE_STARTUP_LOGS": "true", "DD_LOGS_OTEL_ENABLED": "true"}],
)
def test_startup_logs_otlp_logs_export_enabled(self, test_library: APMLibrary):
"""Verify otlp_logs_export_enabled is true when DD_LOGS_OTEL_ENABLED=true."""
with test_library:
logs = _get_startup_logs(test_library, required=True)

assert logs is not None
fields = _extract_otlp_export_fields(logs)
assert fields["otlp_logs_export_enabled"] is True, (
f"Expected otlp_logs_export_enabled to be True with DD_LOGS_OTEL_ENABLED=true, "
f"got {fields['otlp_logs_export_enabled']!r}"
)

@parametrize(
"library_env",
[
Expand Down
Loading