Skip to content

Feat dynatrace backend - #46

Open
samhithnadig wants to merge 3 commits into
traceloop:mainfrom
samhithnadig:feat-dynatrace-backend
Open

Feat dynatrace backend#46
samhithnadig wants to merge 3 commits into
traceloop:mainfrom
samhithnadig:feat-dynatrace-backend

Conversation

@samhithnadig

@samhithnadig samhithnadig commented Jun 16, 2026

Copy link
Copy Markdown

Description

Closes #5

This PR adds support for Dynatrace as an observability backend, allowing the MCP server to query OpenTelemetry traces from Dynatrace environments.

Changes Made

  • Created DynatraceBackend class in src/opentelemetry_mcp/backends/dynatrace.py implementing the BaseBackend interface.
  • Added API integration for trace querying, service listing, and specific trace retrieval using Dynatrace v2 Trace APIs.
  • Configured Bearer token authorization headers matching the implementation requirements.
  • Set up initial unit test file at tests/backends/test_dynatrace.py to cover core backend functionality and validation.

Testing Done

  • Added unit tests for health check connectivity, header formatting, and supported operator mappings.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added Dynatrace v2 trace backend integration with comprehensive trace and span search capabilities
    • Enabled querying and retrieving traces and spans from Dynatrace with advanced filtering
    • Added service discovery to list available services in your instance
    • Enabled operation discovery to list service operations for better exploration
    • Included health check functionality to verify connectivity and configuration

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new file src/opentelemetry_mcp/backends/dynatrace.py implementing DynatraceBackend extending BaseBackend. The class authenticates via Bearer token, implements search_traces, search_spans, get_trace, list_services, get_service_operations, and health_check against the Dynatrace v2 Traces API, and includes a _parse_dynatrace_trace method converting Dynatrace JSON into TraceData/SpanData models.

Changes

Dynatrace Backend

Layer / File(s) Summary
Class declaration, auth headers, and filter operators
src/opentelemetry_mcp/backends/dynatrace.py
Defines DynatraceBackend extending BaseBackend, constructs Authorization: Bearer header from self.api_key, and reports equality-only FilterOperator support.
Trace and span query methods
src/opentelemetry_mcp/backends/dynatrace.py
search_traces calls /api/v2/traces with formatted params and parses results; search_spans derives a TraceQuery, calls search_traces, flattens spans, and truncates to limit; get_trace fetches by trace ID and raises on empty response or parse failure.
Service discovery and health check
src/opentelemetry_mcp/backends/dynatrace.py
list_services fetches /api/v2/traces/services; get_service_operations fetches per-service operations; health_check delegates to list_services and returns a HealthCheckResponse with healthy or unhealthy status plus error details.
Dynatrace JSON-to-model parser
src/opentelemetry_mcp/backends/dynatrace.py
_parse_dynatrace_trace iterates raw spans, builds SpanData with timing/attributes, infers ERROR status from Dynatrace status code or error-attribute presence, computes trace-level start time, total duration, and aggregated status, logs structural failures, and returns None on missing required fields or no spans.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 Hop hop, a new backend appears,
Dynatrace traces now crystal clear!
With Bearer tokens and spans parsed right,
Each ERROR flagged, each service in sight.
The rabbit cheers — more backends, more cheer! 🎉

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements most core requirements from issue #5: backend class, search_traces, get_trace, list_services, and authentication. However, get_aggregated_usage is not implemented, and documentation updates (README, .env.example) are not included in this PR. Implement the missing get_aggregated_usage method and ensure documentation updates to .env.example and README.md are included in this PR or a follow-up.
Title check ❓ Inconclusive The title is vague and uses generic phrasing. 'Feat dynatrace backend' lacks specificity about what was implemented and doesn't clearly convey the key change compared to a more descriptive title. Consider using a more descriptive title like 'Add Dynatrace backend implementation with trace search and service listing' to better summarize the main change.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Out of Scope Changes check ✅ Passed The PR focuses exclusively on implementing the DynatraceBackend class with trace search, retrieval, and service listing operations, all of which are directly aligned with the linked issue requirements.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@CLAassistant

CLAassistant commented Jun 16, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/opentelemetry_mcp/backends/dynatrace.py (1)

180-181: ⚡ Quick win

Add explicit return None after logging the parse error.

The implicit None return is correct but explicit is clearer and ensures MyPy strict mode compliance.

Proposed fix
         except Exception as e:
             logger.error(f"Error parsing Dynatrace trace structural metrics: {e}")
+            return None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/opentelemetry_mcp/backends/dynatrace.py` around lines 180 - 181, In the
exception handler for parsing Dynatrace trace structural metrics (the except
Exception as e block), add an explicit `return None` statement immediately after
the logger.error call. This makes the function's return behavior explicit and
ensures compliance with MyPy strict mode, even though Python's implicit None
return would work the same way.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/opentelemetry_mcp/backends/dynatrace.py`:
- Around line 171-179: The code assumes spans[0] is the root span, but the root
span is actually the one where parent_span_id is None. To fix this, locate the
root span by iterating through the spans list and finding the span where
parent_span_id equals None, then use that span's service_name and operation_name
for the TraceData construction instead of directly accessing
spans[0].service_name and spans[0].operation_name. This ensures correct values
regardless of the order Dynatrace returns spans in.
- Around line 166-169: The trace_duration calculation is incorrect because it
sums all span durations, which overcounts when spans execute concurrently and
overlap. Replace the sum-based calculation with the correct approach: calculate
the maximum end time across all spans (by finding max of start_time plus
duration_ms for each span), then subtract the minimum start time (trace_start)
to get the actual trace duration that accounts for parallel execution.
- Around line 96-104: The get_service_operations method constructs a URL by
directly interpolating the service_name parameter without URL encoding. When
service names contain special characters like /, %, or spaces, this produces
malformed requests. Import quote from urllib.parse at the top of the file, then
in the get_service_operations method, wrap the service_name parameter with
quote() when constructing the API endpoint URL in the self.client.get() call to
properly encode any special characters in the path segment.
- Line 155: The datetime.fromtimestamp() call in the trace data initialization
is creating a naive datetime in local time, which can cause timezone
inconsistencies when trace data is compared across systems. Modify the
datetime.fromtimestamp() call to explicitly specify UTC timezone by adding the
timezone parameter, ensuring the datetime object is timezone-aware and
consistently represents UTC time regardless of the system's local timezone.
- Around line 31-49: The search_traces method in the Dynatrace backend is
calling a non-existent API endpoint and using incompatible query parameters.
Replace the `/api/v2/traces` endpoint call with the correct Logs and Events API
endpoint that Dynatrace provides for querying span data. Instead of using
query.to_backend_params() which produces Jaeger-style parameters (service,
operation, minDuration), convert the TraceQuery object into a proper Dynatrace
Query Language (DQL) query string that reflects the actual filter criteria from
the TraceQuery object. Update the response parsing logic to handle the actual
response format from the Logs and Events API for span data, which will differ
from the current trace structure expectation.

---

Nitpick comments:
In `@src/opentelemetry_mcp/backends/dynatrace.py`:
- Around line 180-181: In the exception handler for parsing Dynatrace trace
structural metrics (the except Exception as e block), add an explicit `return
None` statement immediately after the logger.error call. This makes the
function's return behavior explicit and ensures compliance with MyPy strict
mode, even though Python's implicit None return would work the same way.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e118c821-c83f-4db5-92bb-d3943bdf436b

📥 Commits

Reviewing files that changed from the base of the PR and between 997c9e0 and 378d9aa.

📒 Files selected for processing (1)
  • src/opentelemetry_mcp/backends/dynatrace.py

Comment thread src/opentelemetry_mcp/backends/dynatrace.py
Comment thread src/opentelemetry_mcp/backends/dynatrace.py
Comment thread src/opentelemetry_mcp/backends/dynatrace.py
Comment thread src/opentelemetry_mcp/backends/dynatrace.py
Comment thread src/opentelemetry_mcp/backends/dynatrace.py
@samhithnadig

Copy link
Copy Markdown
Author

Hi, just following up on this PR whenever you get a chance to review it. Let me know if anything else is needed from my side. Thanks!

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.

Add support for Dynatrace backend

2 participants