Skip to content

Add Datadog tracing interceptor contrib package - #1778

Open
eniko-dif wants to merge 1 commit into
temporalio:mainfrom
eniko-dif:eniko/datadog-tracing
Open

Add Datadog tracing interceptor contrib package#1778
eniko-dif wants to merge 1 commit into
temporalio:mainfrom
eniko-dif:eniko/datadog-tracing

Conversation

@eniko-dif

@eniko-dif eniko-dif commented Aug 24, 2026

Copy link
Copy Markdown

This is a port of Datadog's internal Python tracing interceptor, used internally at Datadog with the Python SDK, adapted for open-source contribution. It has feature parity with the Go SDK's Datadog tracing interceptor, including deterministic span/trace IDs and replay-safe span emission, so traces stay coherent across worker restarts regardless of which SDK produced them.

@CLAassistant

CLAassistant commented Aug 24, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@eniko-dif
eniko-dif marked this pull request as ready for review August 24, 2026 10:53
@eniko-dif
eniko-dif requested a review from a team as a code owner August 24, 2026 10:53
@tconley1428

Copy link
Copy Markdown
Contributor

We cannot review without CLA acceptance.

@tconley1428 tconley1428 self-assigned this Aug 25, 2026
)

__all__ = [
"DatadogTracingInterceptor",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

From our perspective I think it would be ideal to (perhaps additionally) expose it as a plugin. That has a few benefits: a consistent way of registering it with other plugins, and data on plugin usage in temporal cloud.

)
return DatadogTracingWorkflowInboundInterceptor

def _configure_workflow_tracing(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This doesn't really seem like a configure method since it just returns things.

input.unsafe_extern_functions[
"__temporal_datadog_configure_workflow_tracing"
] = self._configure_workflow_tracing
# NOTE: fork-specific workaround; reconsider if this integration is ever proposed upstream.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd have to play around with it to be sure, but I think you could potentially remove some or all of the extern functions if you use the plugin pattern to mark this and/or datadog itself as pass through:

        def workflow_runner(runner: WorkflowRunner | None) -> WorkflowRunner:
            if not runner:
                raise ValueError(
                    "No WorkflowRunner provided to the OpenTelemetry plugin."
                )

            # If in sandbox, add additional passthrough
            if isinstance(runner, SandboxedWorkflowRunner):
                return dataclasses.replace(
                    runner,
                    restrictions=runner.restrictions.with_passthrough_modules(
                        "opentelemetry"
                    ),
                )
            return runner

Above example from the otel plugin.


### Difference from the upstream OpenTelemetry interceptor

The upstream OpenTelemetry tracing interceptor works around the sandbox

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is true of the older interceptor, but not of the newer one.

results, but the interceptor code runs first — without the guard, a fresh span
would be emitted for every replayed command.

Queries and update validators are never suppressed: queries are not in

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just FYI if you wanted to use shared code at some point, is_replaying_history_events expresses this difference. It's false during query and update validators but true during workflow replay, so it is safe in both.

@tconley1428 tconley1428 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overall it seems like it works, my high level feedback is that it's better than the older otel interceptor in python, but doesn't provide quite as much functionality as the newer one. You can decide whether or not to try to accomplish that though.

The newer one allows the user to create custom otel spans during their workflow code, which I don't believe this allows for ddtrace. If that's something we'd eventually want to enable, we may want to do that now as it is a pretty big change to the design. If we only want these built in spans, this approach is likely suitable.

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.

3 participants