Skip to content

Add OTel-compliant span status to ETW DTO logging path#259

Merged
nikhilNava merged 1 commit into
mainfrom
nikhilNava/etw-span-status
Jun 25, 2026
Merged

Add OTel-compliant span status to ETW DTO logging path#259
nikhilNava merged 1 commit into
mainfrom
nikhilNava/etw-span-status

Conversation

@nikhilNava

@nikhilNava nikhilNava commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Records an OTel-spec-compliant span status (and error.type) on the ETW DTO logging path (Path B), which previously dropped status entirely. The Activity-based path (Path A) already emits status via ExportFormatter.FormatSingle; this brings Path B (A365EtwLogger -> *DataBuilder -> BaseData -> ExportFormatter.FormatLogData) to parity.

What changed

  • New (DTOs/) - SpanStatusCode enum (Unset=0/Ok=1/Error=2, mapping directly onto OTLP Status.StatusCode), SpanStatus struct, and SpanStatusBuilder (central Exception -> status + error.type mapping, mirroring OpenTelemetryScope.RecordError). Placed with the data model they describe, next to SpanKindConstants.
  • BaseData - adds StatusCode/StatusMessage; ToDictionary() now emits Status { code, message }.
  • Builders / logger - optional Exception? error = null threaded through all 5 *DataBuilder.Build methods (via a shared ApplyStatus helper on BaseDataBuilder), plus A365EtwLogger and IA365EtwLogger.
  • ExportFormatter.FormatLogData - emits the Status object (defaults to { code: 0, message: "" } when absent).

Emitted shape (errored span)

"Attributes": { "...": "...", "error.type": "System.TimeoutException" },
"Status": { "code": 2, "message": "The operation timed out." }

code uses the numeric OTLP enum; error.type follows OTel semantic conventions (HTTP status for RequestFailedException, otherwise the full type name). With no error, Status defaults to Unset (code: 0) and error.type is absent. The optional error parameter keeps all existing call sites source-compatible.

Testing

  • dotnet build src/Microsoft.Agents.A365.Sdk.sln - succeeds (0 warnings, 0 errors)
  • dotnet test src/Microsoft.Agents.A365.Sdk.sln - all projects pass (345 Observability.Runtime tests, including new SpanStatusBuilderTests, ExportFormatterStatusTests, and added BaseData/InvokeAgentDataBuilder status cases)

Note

Also includes a docs-only enhancement to .github/copilot-instructions.md (build/architecture guidance) from earlier in the same working session.

Span generated from ETW DTO

 {
   "Name": "InvokeAgent",
   "Attributes": {
     "telemetry.sdk.name": "A365ObservabilitySDK",
     "telemetry.sdk.version": "1.1.15.29568",
     "telemetry.sdk.language": "dotnet",
     "gen_ai.operation.name": "invoke_agent",
     "gen_ai.agent.id": "agent-123",
     "gen_ai.agent.name": "ContosoAssistant",
     "gen_ai.agent.description": "Helps with sales questions",
     "microsoft.agent.user.id": "auid-001",
     "microsoft.agent.user.email": "assistant@contoso.com",
     "microsoft.a365.agent.blueprint.id": "bp-777",
     "microsoft.a365.agent.platform.id": "platform-42",
     "microsoft.tenant.id": "tenant-abc",
     "gen_ai.provider.name": "openai",
     "gen_ai.agent.version": "1.0.0",
     "server.address": "agent.contoso.com",
     "server.port": "8443",
     "microsoft.channel.name": "teams",
     "microsoft.channel.link": "https://teams.microsoft.com/thread/1",
     "gen_ai.conversation.id": "conv-789",
     "user.id": "user-555",
     "user.email": "jane@contoso.com",
     "user.name": "Jane Doe",
     "client.address": "203.0.113.9",
     "microsoft.a365.caller.agent.name": "OrchestratorAgent",
     "microsoft.a365.caller.agent.id": "caller-agent-999",
     "microsoft.a365.caller.agent.blueprint.id": "bp-100",
     "microsoft.a365.caller.agent.user.id": "auid-callr",
     "microsoft.a365.caller.agent.user.email": "orch@contoso.com",
     "microsoft.a365.caller.agent.platform.id": "platform-1",
     "microsoft.a365.caller.agent.version": "2.3.1",
     "gen_ai.input.messages":  "[{\"role\":\"user\",\"parts\":[{\"content\":\"What were Q3 sales?\",\"type\":\"text\"}]}]",
     "gen_ai.output.messages": "[{\"finish_reason\":\"stop\",\"role\":\"assistant\",\"parts\":[{\"content\":\"Q3 sales were $4.2M.\",\"type\":\"text\"}]}]",
     "error.type": "System.TimeoutException"
   },
   "StartTimeUnixNano": 1782421200000000000,
   "EndTimeUnixNano": 1782421201234000000,
   "SpanId": "00f067aa0ba902b7",
   "ParentSpanId": "0102030405060708",
   "TraceId": "4bf92f3577b34da6a3ce929d0e0e4736",
   "Kind": "Client",
   "Status": { "code": 2, "message": "The operation timed out." }
 }

Copilot AI review requested due to automatic review settings June 25, 2026 22:18
@nikhilNava nikhilNava requested review from a team as code owners June 25, 2026 22:18
@github-actions

Copy link
Copy Markdown

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Copilot AI 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.

Pull request overview

This PR brings the ETW DTO logging path (“Path B”) up to parity with the Activity-based path by emitting an OpenTelemetry-compliant span Status object (numeric code + message) and recording error.type when an exception is provided.

Changes:

  • Introduces SpanStatusCode, SpanStatus, and SpanStatusBuilder to centralize exception → OTel status + error.type mapping for DTO telemetry.
  • Threads an optional Exception? error = null through IA365EtwLogger/A365EtwLogger and all *DataBuilder.Build methods, applying status onto BaseData.
  • Extends DTO serialization (BaseData.ToDictionary and ExportFormatter.FormatLogData) and adds targeted unit tests covering status emission and defaults.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/DTOs/Builders/SpanStatusBuilderTests.cs Adds unit coverage for SpanStatusBuilder status + error.type mapping behavior.
src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/DTOs/Builders/InvokeAgentDataBuilderTests.cs Validates builder behavior for unset vs error status when error is provided/omitted.
src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/DTOs/BaseDataTests.cs Verifies BaseData default status values and ToDictionary() emitted shape.
src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Common/ExportFormatterStatusTests.cs Ensures FormatLogData emits Status consistently (present, error, and missing-key default).
src/Observability/Runtime/Etw/IA365EtwLogger.cs Extends logger interface APIs with optional error parameter and XML docs.
src/Observability/Runtime/Etw/A365EtwLogger.cs Forwards optional error through ETW logger methods into DTO builders.
src/Observability/Runtime/DTOs/SpanStatusCode.cs Adds OTLP-aligned numeric status code enum (Unset/Ok/Error).
src/Observability/Runtime/DTOs/SpanStatus.cs Adds DTO representation of OTel status (Code + optional Message).
src/Observability/Runtime/DTOs/Builders/SpanStatusBuilder.cs Implements exception → status mapping and error.type population to attributes.
src/Observability/Runtime/DTOs/Builders/OutputDataBuilder.cs Threads optional error through and applies status on output DTO build.
src/Observability/Runtime/DTOs/Builders/InvokeAgentDataBuilder.cs Threads optional error through and applies status on invoke-agent DTO build.
src/Observability/Runtime/DTOs/Builders/ExecuteToolDataBuilder.cs Threads optional error through and applies status on execute-tool DTO build.
src/Observability/Runtime/DTOs/Builders/ExecuteInferenceDataBuilder.cs Threads optional error through and applies status on inference DTO build.
src/Observability/Runtime/DTOs/Builders/BaseDataBuilder.cs Adds shared ApplyStatus helper for consistent DTO status annotation.
src/Observability/Runtime/DTOs/Builders/ApplyGuardrailDataBuilder.cs Threads optional error through and applies status on guardrail DTO build.
src/Observability/Runtime/DTOs/BaseData.cs Adds StatusCode/StatusMessage and emits Status { code, message } in ToDictionary().
src/Observability/Runtime/Common/ExportFormatter.cs Emits Status in FormatLogData, defaulting to { code: 0, message: "" } when absent.
docs/superpowers/specs/2026-06-25-etw-span-status-design.md Adds design spec for ETW DTO status, but currently contains implementation-path/type mismatches.
.github/copilot-instructions.md Docs-only enhancement describing build/test commands and repo architecture conventions.

Comment thread docs/superpowers/specs/2026-06-25-etw-span-status-design.md Outdated
Comment thread docs/superpowers/specs/2026-06-25-etw-span-status-design.md Outdated
Comment thread docs/superpowers/specs/2026-06-25-etw-span-status-design.md Outdated
Comment thread docs/superpowers/specs/2026-06-25-etw-span-status-design.md Outdated
Record span status (Unset/Error) and error.type on the ETW DTO-based
telemetry path so error reporting matches the OTel semantic conventions
already used by the Activity-based scope path.

- Add SpanStatus struct, SpanStatusCode enum and SpanStatusBuilder
  (exception -> status + error.type) under DTOs, alongside the data model
  they describe (parallel to SpanKindConstants)
- Add StatusCode/StatusMessage to BaseData and emit Status in ToDictionary
- Thread optional Exception error param through all 5 data builders,
  A365EtwLogger and IA365EtwLogger
- Emit Status in ExportFormatter.FormatLogData
- Add unit tests for SpanStatusBuilder, BaseData, ExportFormatter and
  InvokeAgentDataBuilder status behavior

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@nikhilNava nikhilNava merged commit 16aae66 into main Jun 25, 2026
7 checks passed
@nikhilNava nikhilNava deleted the nikhilNava/etw-span-status branch June 25, 2026 23:29
nikhilNava added a commit to microsoft/opentelemetry-distro-dotnet that referenced this pull request Jun 26, 2026
* Add OTel-compliant span status to ETW DTO logging path

Records an OTel-spec-compliant span status (and error.type) on the ETW
DTO logging path (Path B), which previously dropped status entirely.
Brings it to parity with the Activity-based path (FormatSingle).

- New SpanStatusCode enum, SpanStatus struct, and SpanStatusBuilder
  (central Exception -> status + error.type mapping, mirroring
  OpenTelemetryScope.RecordError).
- BaseData: adds StatusCode/StatusMessage; ToDictionary emits Status.
- Optional Exception? error threaded through all 5 *DataBuilder.Build
  methods (via shared ApplyStatus on BaseDataBuilder), A365EtwLogger,
  and IA365EtwLogger.
- ExportFormatter.FormatLogData emits the Status object (defaults to
  { code: 0, message: "" } when absent).

Ports microsoft/Agent365-dotnet#259 into the distro repo.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Strip error.type on no-error path in SpanStatusBuilder

Addresses PR feedback: error.type is not part of the reserved-key filter
for extraAttributes, so a caller could pass it through and have it emitted
while status stayed Unset. FromError now removes any pre-existing error.type
attribute on the no-error path, ensuring error.type is never present without
an accompanying error status.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Nikhil Chitlur Navakiran (from Dev Box) <nikhilc@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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