Skip to content

[Tracing] Implement initial OTLP traces weblog tests#6363

Open
zacharycmontoya wants to merge 32 commits intomainfrom
zach.montoya/weblog-traces-otlp
Open

[Tracing] Implement initial OTLP traces weblog tests#6363
zacharycmontoya wants to merge 32 commits intomainfrom
zach.montoya/weblog-traces-otlp

Conversation

@zacharycmontoya
Copy link
Contributor

Motivation

We are seeing increased demand for exporting traces as OTLP from our DD SDKs (rather than Datadog-proprietary MessagePack), so we are prototyping and establishing requirements for generating OTLP traces payloads. This is only the first of a series of PRs to establish clear expectations for what the generated OTLP traces and trace stats will look like.

Changes

  • Scenario: Adds a new APM_TRACING_OTLP scenario to test the weblog application with the configuration needed for the DD SDK to export traces using OTLP. This also adds a include_opentelemetry property to the EndToEndScenario to set up the OpenTelemetry interface.
  • Tests: Adds tests/otel/test_tracing_otlp.py::Test_Otel_Tracing_OTLP::test_tracing to send a request to the weblog app using weblog.get("/") and asserts properties of the OTLP trace payload
  • Interfaces: Updates the OpenTelemetry interface with methods get_otel_spans and get_trace_stats to retrieve the OTLP payloads for test assertions

Workflow

  1. ⚠️ Create your PR as draft ⚠️
  2. Work on you PR until the CI passes
  3. Mark it as ready for review
    • Test logic is modified? -> Get a review from RFC owner.
    • Framework is modified, or non obvious usage of it -> get a review from R&P team

🚀 Once your PR is reviewed and the CI green, you can merge it!

🛟 #apm-shared-testing 🛟

Reviewer checklist

  • Anything but tests/ or manifests/ is modified ? I have the approval from R&P team
  • A docker base image is modified?
    • the relevant build-XXX-image label is present
  • A scenario is added, removed or renamed?

…ple.

Notably, this creates a new scenario APM_TRACING_OTLP to enable the environment variables needed to configure the SDK to export traces as OTLP.
@github-actions
Copy link
Contributor

github-actions bot commented Feb 20, 2026

CODEOWNERS have been resolved as:

tests/otel/test_tracing_otlp.py                                         @DataDog/system-tests-core
utils/proxy/traces/otlp_v1.py                                           @DataDog/system-tests-core
.github/workflows/run-end-to-end.yml                                    @DataDog/system-tests-core
manifests/cpp_nginx.yml                                                 @DataDog/dd-trace-cpp
manifests/dotnet.yml                                                    @DataDog/apm-dotnet @DataDog/asm-dotnet
manifests/golang.yml                                                    @DataDog/dd-trace-go-guild
manifests/java.yml                                                      @DataDog/asm-java @DataDog/apm-java
manifests/nodejs.yml                                                    @DataDog/dd-trace-js
manifests/php.yml                                                       @DataDog/apm-php @DataDog/asm-php
manifests/python.yml                                                    @DataDog/apm-python @DataDog/asm-python
manifests/ruby.yml                                                      @DataDog/ruby-guild @DataDog/asm-ruby
manifests/rust.yml                                                      @DataDog/apm-rust
utils/_context/_scenarios/__init__.py                                   @DataDog/system-tests-core
utils/_context/_scenarios/endtoend.py                                   @DataDog/system-tests-core
utils/dd_constants.py                                                   @DataDog/system-tests-core
utils/interfaces/_open_telemetry.py                                     @DataDog/system-tests-core
utils/proxy/_deserializer.py                                            @DataDog/system-tests-core
utils/scripts/ci_orchestrators/workflow_data.py                         @DataDog/system-tests-core

@datadog-official
Copy link

datadog-official bot commented Feb 20, 2026

⚠️ Tests

Fix all issues with BitsAI or with Cursor

⚠️ Warnings

🧪 18 Tests failed

tests.ai_guard.test_ai_guard_sdk.Test_ContentParts.test_content_parts[flask-poc] from system_tests_suite   View in Datadog   (Fix with Cursor)
assert 500 == 200
 +  where 500 = HttpResponse(status_code:500, headers:{'Server': 'gunicorn', 'Date': 'Thu, 26 Mar 2026 18:01:09 GMT', 'Connection': 'k...: '103'}, text:{"error":"Authentication credentials required: provide DD_API_KEY and DD_APP_KEY","type":"ValueError"}\n).status_code
 +    where HttpResponse(status_code:500, headers:{'Server': 'gunicorn', 'Date': 'Thu, 26 Mar 2026 18:01:09 GMT', 'Connection': 'k...: '103'}, text:{"error":"Authentication credentials required: provide DD_API_KEY and DD_APP_KEY","type":"ValueError"}\n) = <tests.ai_guard.test_ai_guard_sdk.Test_ContentParts object at 0x7f80f0799cd0>.r

self = <tests.ai_guard.test_ai_guard_sdk.Test_ContentParts object at 0x7f80f0799cd0>

    def test_content_parts(self):
        """Test AI Guard evaluation with multi-modal content parts.
    
        Validates that prompts with content part format (text + image_url) are:
...
tests.ai_guard.test_ai_guard_sdk.Test_ContentParts.test_content_parts[flask-poc] from system_tests_suite   View in Datadog   (Fix with Cursor)
assert 500 == 200
 +  where 500 = HttpResponse(status_code:500, headers:{'Server': 'gunicorn', 'Date': 'Thu, 26 Mar 2026 18:05:17 GMT', 'Connection': 'k...: '103'}, text:{"error":"Authentication credentials required: provide DD_API_KEY and DD_APP_KEY","type":"ValueError"}\n).status_code
 +    where HttpResponse(status_code:500, headers:{'Server': 'gunicorn', 'Date': 'Thu, 26 Mar 2026 18:05:17 GMT', 'Connection': 'k...: '103'}, text:{"error":"Authentication credentials required: provide DD_API_KEY and DD_APP_KEY","type":"ValueError"}\n) = <tests.ai_guard.test_ai_guard_sdk.Test_ContentParts object at 0x7fbfded2ef00>.r

self = <tests.ai_guard.test_ai_guard_sdk.Test_ContentParts object at 0x7fbfded2ef00>

    def test_content_parts(self):
        """Test AI Guard evaluation with multi-modal content parts.
    
        Validates that prompts with content part format (text + image_url) are:
...
tests.ai_guard.test_ai_guard_sdk.Test_Evaluation.test_abort[flask-poc] from system_tests_suite   View in Datadog   (Fix with Cursor)
assert False

self = <tests.ai_guard.test_ai_guard_sdk.Test_Evaluation object at 0x7fbfded2e810>

    def test_abort(self):
        """Test ABORT action for tool call attempting to read /etc/passwd.
        Expects 403 when blocking enabled, 200 when disabled.
        Span should have action="ABORT" and target="tool" with tool_name.
        """
        for block, request in self.r.items():
...
View all

ℹ️ Info

No other issues found (see more)

❄️ No new flaky tests detected

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 470194c | Docs | Datadog PR Page | Was this helpful? React with 👍/👎 or give us feedback!

…v, since users do not need this feature for OTLP export to work
…ing specifically:

- At runtime determine if the request is JSON
- If JSON, look up proto field names by their camelCase representation. Otherwise, look up field names by their snake_case representation
- If JSON, assert that the 'traceId' and 'spanId' fields are case-insensitive hexadecimal strings, rather than base64-encoded strings
- If JSON, assert that enums (e.g. span.kind and span.status.code) are encoded using an integer, not a string representation of the enum value name
- Regardless of protocol, get the time before and after the test HTTP request is issued, and assert that the span's reported 'start_time_unix_nano' and 'end_time_unix_nano' fall in this range
- Regardless of protocol, make the 'http.method' and 'http.status_code' span attribute assertions more flexible by also testing against their stable OpenTelemetry HTTP equivalents of 'http.request.method' and 'http.response.status_code', respectively
Since JSON must be expressed in lowerCamelCase (according to the OpenTelemetry spec), we can consolidate our parsing and assertions on that style of field names
….py to the proxy, in utils/proxy/traces/otlp_v1.py
… explain why other scenarios are facing issues with the "Library not ready" messages
@zacharycmontoya zacharycmontoya requested review from a team as code owners March 20, 2026 22:03
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5f73ad4c0b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +65 to +66
yield data.get("request"), content, span
break # Skip to next span

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return all matching OTLP spans for a request

Dropping out of the span loop after the first RID match causes get_otel_spans() to undercount spans when a single OTLP payload contains multiple matching spans in the same scopeSpans block (for example, server + framework spans that both carry the request user-agent tag). This can make tests/otel/test_tracing_otlp.py pass even when extra spans are exported, weakening the regression signal for this new scenario.

Useful? React with 👍 / 👎.

@zacharycmontoya
Copy link
Contributor Author

As far as I can tell, it's only the AI_GUARD scenario that is failing, not my new APM_TRACING_OTLP scenario. @cbeauchesne what do you think of these failures?

# Assert that the span fields match the expected values
span_start_time_ns = int(span["startTimeUnixNano"])
span_end_time_ns = int(span["endTimeUnixNano"])
assert span_start_time_ns >= self.start_time_ns
Copy link
Contributor

@ida613 ida613 Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line is flaky for me.

Claude says "self.start_time_ns is captured using time.time_ns() on the test host machine, while span_start_time_ns comes from the weblog container. If the container's clock is behind the host's clock (even by a small amount due to clock skew or Docker clock drift), the span start time will appear earlier than self.start_time_ns, failing the assertion".

Does it make any sense? or should I look into my code? :3

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm getting this issue too, I'll look into fixing this!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I think I've fixed it in the latest set of commits by making a call to the weblog container to get the time via Unix's date utility. Let me know if you're still experiencing this!

@cbeauchesne
Copy link
Collaborator

There are some recent changes on this scenario (#6445), @obordeau or @smola , do we expect to have some instability here?

@zacharycmontoya, waiting for a response, you can rebase. If it does not fix the issue, I'll look closely, and if needed, force-merge on your call.

@obordeau
Copy link
Contributor

obordeau commented Mar 23, 2026

There are some recent changes on this scenario (#6445), @obordeau or @smola , do we expect to have some instability here?

@zacharycmontoya, waiting for a response, you can rebase. If it does not fix the issue, I'll look closely, and if needed, force-merge on your call.

I think the SDS cassette broke (the JSON looks badly formatted) with this PR and made the AI Guard scenario failing. Looking on a fix :) cc @cbeauchesne

@obordeau
Copy link
Contributor

Gotta log off, trying to fix it here Fix AI Guard SDS cassette

@obordeau
Copy link
Contributor

Just merged the fix Fix AI Guard SDS system test, you can update your branch. Sorry for the inconvenience 🥲

if: always() && steps.build.outcome == 'success' && contains(inputs.scenarios, '"APM_TRACING_OTLP"')
run: ./run.sh APM_TRACING_OTLP
env:
DD_API_KEY: ${{ secrets.DD_API_KEY }}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory, we don't need this, you can use a fake key and it'll work the same (and if not, ping me, I'll fix that).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think you're right. I'll either remove this entirely or use a fake key, stay tuned for updates 😎

- Assert that resource attribute telemetry.sdk.name=datadog
- Assert that span attribute span.type=web
- Assert that span attribute operation.name is present
…on't implement the latest required span attributes
@zacharycmontoya zacharycmontoya force-pushed the zach.montoya/weblog-traces-otlp branch from 2538f35 to 470194c Compare March 26, 2026 16:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants