Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ The currently supported telemetry assertions are:
| `minimum_spans` | Minimum number of normalized spans; defaults to `1`. |
| `minimum_invocations` | Minimum canonical durable invocation span occurrences; identical spans are counted separately. Defaults to `1`. |
| `require_execution_correlation` | Require the durable execution ARN on the trace; defaults to `true`. |
| `require_unique_root_per_trace` | Reject any trace ID containing more than one distinct parentless span. Duplicate exports with the same span ID count once. |
| `require_all_spans` | Require every normalized span to match at least one span assertion. |
| `span_assertion_scope` | Limit complete span coverage to spans matching this partial selector. |
| `exact_attribute_prefixes` | Require assertions to enumerate every attribute under the listed prefixes. |
Expand Down Expand Up @@ -165,6 +166,13 @@ Keep `ExpectedExecutionHistory` and `ExpectedResult` focused on the execution
behavior needed to produce the telemetry. Keep `TelemetryAssertions` portable
across the complete exporter/backend support matrix.

Set `require_unique_root_per_trace: true` for trace-view requirements. A
backend may return a correlated subset without the upstream root, so the
assertion permits zero observed roots for a trace ID. It rejects two or more
distinct parentless spans because that creates a disconnected trace forest and
makes backend-derived entry point, duration, critical path, and sampling
behavior ambiguous.

The catalog uses separate requirements when two public plugins intentionally
produce different trace views. Invocation-view cases assert per-invocation
operation hierarchy. In execution-view cases, `Invocation` spans remain
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
PollingBackend,
)

_WORKFLOW_SERVICE_NAME = "Workflow"


def _metadata_attributes(metadata: Any) -> dict[str, Any]:
if not isinstance(metadata, Mapping):
Expand Down Expand Up @@ -119,10 +121,13 @@ def _lookup(self, query: TelemetryQuery) -> Trace | None:
if query.trace_id:
trace_ids = [query.trace_id]
else:
# ADOT names a parentless INTERNAL root segment after its span,
# so the durable Workflow trace is indexed separately from the
# ambient trace that uses the configured service name.
summary_request = {
"StartTime": query.started_at,
"EndTime": query.ended_at,
"FilterExpression": f'service("{query.service_name}")',
"FilterExpression": (f'service("{query.service_name}") OR service("{_WORKFLOW_SERVICE_NAME}")'),
}
while True:
response = self._client.get_trace_summaries(**summary_request)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,25 @@ def _invocation_count(trace: Trace) -> int:
)


def _unique_root_errors(trace: Trace) -> list[str]:
roots_by_trace: dict[str, dict[str, Span]] = {}
for span in trace.spans:
if span.parent_span_id is None:
roots_by_trace.setdefault(span.trace_id, {}).setdefault(span.span_id, span)

errors: list[str] = []
for trace_id, roots_by_id in sorted(roots_by_trace.items()):
if len(roots_by_id) <= 1:
continue
roots = sorted(
roots_by_id.values(),
key=lambda span: (span.start_time, span.span_id),
)
root_list = ", ".join(f"{span.name!r} ({span.span_id})" for span in roots)
errors.append(f"Trace {trace_id} contains {len(roots)} distinct root spans: {root_list}")
return errors


def _is_sequence(value: Any) -> bool:
return isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray))

Expand Down Expand Up @@ -635,6 +654,12 @@ def validate_trace(
if len(trace.spans) < minimum_spans:
errors.append(f"Expected at least {minimum_spans} span(s), found {len(trace.spans)}")

require_unique_root = assertions.get("require_unique_root_per_trace", False)
if not isinstance(require_unique_root, bool):
errors.append("require_unique_root_per_trace must be a boolean")
elif require_unique_root:
errors.extend(_unique_root_errors(trace))

timestamp_tolerance = _timestamp_tolerance(feature_disparities)
for span in trace.spans:
if span.start_time - span.end_time > timestamp_tolerance:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 4
require_execution_correlation: true
require_unique_root_per_trace: true
span_assertions:
- select:
name: Workflow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 8
require_execution_correlation: true
require_unique_root_per_trace: true
minimum_invocations: 2
span_assertions:
- select:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 6
require_execution_correlation: true
require_unique_root_per_trace: true
minimum_invocations: 3
allowed_execution_arns:
- ${EXECUTION_ARN}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 3
require_execution_correlation: true
require_unique_root_per_trace: true
span_assertions:
- select:
name: Workflow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 4
require_execution_correlation: true
require_unique_root_per_trace: true
span_assertions:
- select:
name: Workflow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 4
require_execution_correlation: true
require_unique_root_per_trace: true
span_assertions:
- select:
name: Workflow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 1
require_execution_correlation: true
require_unique_root_per_trace: true
span_assertions:
- select:
name: Invocation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 4
require_execution_correlation: true
require_unique_root_per_trace: true
span_assertions:
- select:
name: Workflow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 8
require_execution_correlation: true
require_unique_root_per_trace: true
minimum_invocations: 2
span_assertions:
- select:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 6
require_execution_correlation: true
require_unique_root_per_trace: true
minimum_invocations: 3
allowed_execution_arns:
- ${EXECUTION_ARN}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 1
require_execution_correlation: true
require_unique_root_per_trace: true
span_assertions:
- select:
name: Invocation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ TelemetryAssertions:
minimum_spans: 6
minimum_invocations: 2
require_execution_correlation: true
require_unique_root_per_trace: true
span_assertions:
- select:
name: Workflow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ TelemetryAssertions:
minimum_spans: 6
minimum_invocations: 2
require_execution_correlation: true
require_unique_root_per_trace: true
span_assertions:
- select:
name: Workflow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 4
require_execution_correlation: true
require_unique_root_per_trace: true
span_assertions:
- select:
name: Workflow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 5
require_execution_correlation: true
require_unique_root_per_trace: true
span_assertions:
- select:
name: Workflow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 9
require_execution_correlation: true
require_unique_root_per_trace: true
span_assertions:
- select:
name: Workflow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 9
require_execution_correlation: true
require_unique_root_per_trace: true
span_assertions:
- select:
name: Workflow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 6
require_execution_correlation: true
require_unique_root_per_trace: true
span_assertions:
- select:
name: Workflow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 6
require_execution_correlation: true
require_unique_root_per_trace: true
minimum_invocations: 2
span_assertions:
- select:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 4
require_execution_correlation: true
require_unique_root_per_trace: true
require_all_spans: true
span_assertion_scope:
attributes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ TelemetryAssertions:
minimum_spans: 10
minimum_invocations: 2
require_execution_correlation: true
require_unique_root_per_trace: true
require_all_spans: true
span_assertion_scope:
attributes:
Expand Down Expand Up @@ -144,9 +145,6 @@ TelemetryAssertions:
status: OK
service_name: ${SERVICE_NAME}
links:
- name: otel-callback
attributes:
durable.operation.id: ${CALLBACK_CONTEXT}
- name: Workflow
attributes:
durable.execution.arn: ${EXECUTION_ARN}
Expand Down Expand Up @@ -203,9 +201,6 @@ TelemetryAssertions:
status: OK
service_name: ${SERVICE_NAME}
links:
- name: "${/^(?:otel-callback(?: create callback id|-callback)|CALLBACK)$/}"
attributes:
durable.operation.id: ${CALLBACK1}
- name: Workflow
attributes:
durable.execution.arn: ${EXECUTION_ARN}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ TelemetryAssertions:
minimum_spans: 7
minimum_invocations: 3
require_execution_correlation: true
require_unique_root_per_trace: true
allowed_execution_arns:
- ${EXECUTION_ARN}
- ${TARGET_EXECUTION_ARN}
Expand Down Expand Up @@ -162,9 +163,6 @@ TelemetryAssertions:
status: OK
service_name: ${SERVICE_NAME}
links:
- name: otel-invoke
attributes:
durable.operation.id: ${INVOKE1}
- name: Workflow
attributes:
durable.execution.arn: ${EXECUTION_ARN}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 3
require_execution_correlation: true
require_unique_root_per_trace: true
require_all_spans: true
span_assertion_scope:
attributes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 4
require_execution_correlation: true
require_unique_root_per_trace: true
require_all_spans: true
span_assertion_scope:
attributes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 4
require_execution_correlation: true
require_unique_root_per_trace: true
require_all_spans: true
span_assertion_scope:
attributes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 2
require_execution_correlation: true
require_unique_root_per_trace: true
require_all_spans: true
span_assertion_scope:
attributes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 4
require_execution_correlation: true
require_unique_root_per_trace: true
require_all_spans: true
span_assertion_scope:
attributes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ TelemetryAssertions:
minimum_spans: 10
minimum_invocations: 2
require_execution_correlation: true
require_unique_root_per_trace: true
require_all_spans: true
span_assertion_scope:
attributes:
Expand Down Expand Up @@ -144,9 +145,6 @@ TelemetryAssertions:
status: ERROR
service_name: ${SERVICE_NAME}
links:
- name: otel-failed-callback
attributes:
durable.operation.id: ${FAILED_CALLBACK_CONTEXT}
- name: Workflow
attributes:
durable.execution.arn: ${EXECUTION_ARN}
Expand Down Expand Up @@ -202,9 +200,6 @@ TelemetryAssertions:
status: ERROR
service_name: ${SERVICE_NAME}
links:
- name: "${/^(?:otel-failed-callback(?: create callback id|-callback)|CALLBACK)$/}"
attributes:
durable.operation.id: ${FAILED_CALLBACK}
- name: Workflow
attributes:
durable.execution.arn: ${EXECUTION_ARN}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ TelemetryAssertions:
minimum_spans: 7
minimum_invocations: 3
require_execution_correlation: true
require_unique_root_per_trace: true
allowed_execution_arns:
- ${EXECUTION_ARN}
- ${TARGET_EXECUTION_ARN}
Expand Down Expand Up @@ -164,9 +165,6 @@ TelemetryAssertions:
status: ERROR
service_name: ${SERVICE_NAME}
links:
- name: otel-failed-invoke
attributes:
durable.operation.id: ${FAILED_INVOKE}
- name: Workflow
attributes:
durable.execution.arn: ${EXECUTION_ARN}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ ExpectedResult:
TelemetryAssertions:
minimum_spans: 2
require_execution_correlation: true
require_unique_root_per_trace: true
require_all_spans: true
span_assertion_scope:
attributes:
Expand Down
Loading
Loading