Skip to content

Commit 6b32be8

Browse files
committed
ft: add fup support for dynamodb
Signed-off-by: Cagri Yonca <[email protected]>
1 parent 982639f commit 6b32be8

File tree

7 files changed

+133
-40
lines changed

7 files changed

+133
-40
lines changed

src/instana/agent/host.py

+17-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from instana.options import StandardOptions
2323
from instana.util import to_json
2424
from instana.util.runtime import get_py_source
25+
from instana.util.span_utils import get_operation_specifier
2526
from instana.version import VERSION
2627

2728

@@ -346,14 +347,16 @@ def report_spans(self, payload: Dict[str, Any]) -> Optional[Response]:
346347
return
347348

348349
def filter_spans(self, spans: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
349-
from instana.util.traceutils import is_service_or_endpoint_ignored
350-
350+
"""
351+
Filters given span list using ignore-endpoint variable and returns the list of filtered spans.
352+
"""
351353
filtered_spans = []
352354
for span in spans:
353355
if (hasattr(span, "n") or hasattr(span, "name")) and hasattr(span, "data"):
354356
service = span.n
355-
endpoint = span.data[service]["command"]
356-
if isinstance(endpoint, str) and is_service_or_endpoint_ignored(
357+
operation_specifier = get_operation_specifier(service)
358+
endpoint = span.data[service][operation_specifier]
359+
if isinstance(endpoint, str) and self.__is_service_or_endpoint_ignored(
357360
service, endpoint
358361
):
359362
continue
@@ -363,6 +366,16 @@ def filter_spans(self, spans: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
363366
filtered_spans.append(span)
364367
return filtered_spans
365368

369+
def __is_service_or_endpoint_ignored(
370+
self, service: str, endpoint: str = ""
371+
) -> bool:
372+
"""Check if the given service and endpoint combination should be ignored."""
373+
374+
return (
375+
service.lower() in self.options.ignore_endpoints
376+
or f"{service.lower()}.{endpoint.lower()}" in self.options.ignore_endpoints
377+
)
378+
366379
def handle_agent_tasks(self, task: Dict[str, Any]) -> None:
367380
"""
368381
When request(s) are received by the host agent, it is sent here

src/instana/util/span_utils.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# (c) Copyright IBM Corp. 2025
2+
3+
from typing import Optional
4+
5+
6+
def get_operation_specifier(span_name: str) -> Optional[str]:
7+
"""Get the specific operation specifier for the given span."""
8+
operation_specifier = ""
9+
if span_name == "redis":
10+
operation_specifier = "command"
11+
elif span_name == "dynamodb":
12+
operation_specifier = "op"
13+
return operation_specifier

src/instana/util/traceutils.py

+7-14
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
from instana.log import logger
1616
from instana.singletons import agent, tracer
1717
from instana.span.span import get_current_span
18-
from instana.tracer import InstanaTracer
1918

2019
if TYPE_CHECKING:
2120
from instana.span.span import InstanaSpan
21+
from instana.tracer import InstanaTracer
2222

2323

2424
def extract_custom_headers(
@@ -61,7 +61,7 @@ def extract_custom_headers(
6161
logger.debug("extract_custom_headers: ", exc_info=True)
6262

6363

64-
def get_active_tracer() -> Optional[InstanaTracer]:
64+
def get_active_tracer() -> Optional["InstanaTracer"]:
6565
"""Get the currently active tracer if one exists."""
6666
try:
6767
current_span = get_current_span()
@@ -78,7 +78,11 @@ def get_active_tracer() -> Optional[InstanaTracer]:
7878

7979

8080
def get_tracer_tuple() -> (
81-
Tuple[Optional[InstanaTracer], Optional["InstanaSpan"], Optional[str]]
81+
Tuple[
82+
Optional["InstanaTracer"],
83+
Optional["InstanaSpan"],
84+
Optional[str],
85+
]
8286
):
8387
"""Get a tuple of (tracer, span, span_name) for the current context."""
8488
active_tracer = get_active_tracer()
@@ -93,14 +97,3 @@ def get_tracer_tuple() -> (
9397
def tracing_is_off() -> bool:
9498
"""Check if tracing is currently disabled."""
9599
return not (bool(get_active_tracer()) or agent.options.allow_exit_as_root)
96-
97-
98-
def is_service_or_endpoint_ignored(
99-
service: str,
100-
endpoint: str = "",
101-
) -> bool:
102-
"""Check if the given service and endpoint combination should be ignored."""
103-
return (
104-
service.lower() in agent.options.ignore_endpoints
105-
or f"{service.lower()}.{endpoint.lower()}" in agent.options.ignore_endpoints
106-
)

tests/agent/test_host.py

+30
Original file line numberDiff line numberDiff line change
@@ -690,3 +690,33 @@ def test_diagnostics(self, caplog: pytest.LogCaptureFixture) -> None:
690690
assert "reporting_thread: None" in caplog.messages
691691
assert f"report_interval: {agent.collector.report_interval}" in caplog.messages
692692
assert "should_send_snapshot_data: True" in caplog.messages
693+
694+
def test_is_service_or_endpoint_ignored(self) -> None:
695+
self.agent.options.ignore_endpoints.append("service1")
696+
self.agent.options.ignore_endpoints.append("service2.endpoint1")
697+
698+
# ignore all endpoints of service1
699+
assert self.agent._HostAgent__is_service_or_endpoint_ignored("service1")
700+
assert self.agent._HostAgent__is_service_or_endpoint_ignored(
701+
"service1", "endpoint1"
702+
)
703+
assert self.agent._HostAgent__is_service_or_endpoint_ignored(
704+
"service1", "endpoint2"
705+
)
706+
707+
# case-insensitive
708+
assert self.agent._HostAgent__is_service_or_endpoint_ignored("SERVICE1")
709+
assert self.agent._HostAgent__is_service_or_endpoint_ignored(
710+
"service1", "ENDPOINT1"
711+
)
712+
713+
# ignore only endpoint1 of service2
714+
assert self.agent._HostAgent__is_service_or_endpoint_ignored(
715+
"service2", "endpoint1"
716+
)
717+
assert not self.agent._HostAgent__is_service_or_endpoint_ignored(
718+
"service2", "endpoint2"
719+
)
720+
721+
# don't ignore other services
722+
assert not self.agent._HostAgent__is_service_or_endpoint_ignored("service3")

tests/clients/boto3/test_boto3_dynamodb.py

+51
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
# (c) Copyright IBM Corp. 2021
22
# (c) Copyright Instana Inc. 2020
33

4+
import os
45
from typing import Generator
56

67
import boto3
78
import pytest
89
from moto import mock_aws
910

11+
from instana.options import StandardOptions
1012
from instana.singletons import agent, tracer
1113
from tests.helpers import get_first_span_by_filter
1214

@@ -67,6 +69,55 @@ def test_dynamodb_create_table(self) -> None:
6769
assert dynamodb_span.data["dynamodb"]["region"] == "us-west-2"
6870
assert dynamodb_span.data["dynamodb"]["table"] == "dynamodb-table"
6971

72+
def test_ignore_dynamodb(self) -> None:
73+
os.environ["INSTANA_IGNORE_ENDPOINTS"] = "dynamodb"
74+
agent.options = StandardOptions()
75+
76+
with tracer.start_as_current_span("test"):
77+
self.dynamodb.create_table(
78+
TableName="dynamodb-table",
79+
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
80+
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
81+
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
82+
)
83+
84+
spans = self.recorder.queued_spans()
85+
assert len(spans) == 2
86+
87+
filter = lambda span: span.n == "dynamodb" # noqa: E731
88+
dynamodb_span = get_first_span_by_filter(spans, filter)
89+
assert dynamodb_span
90+
91+
filtered_spans = agent.filter_spans(spans)
92+
assert len(filtered_spans) == 1
93+
94+
assert dynamodb_span not in filtered_spans
95+
96+
def test_ignore_create_table(self) -> None:
97+
os.environ["INSTANA_IGNORE_ENDPOINTS"] = "dynamodb.createtable"
98+
agent.options = StandardOptions()
99+
100+
with tracer.start_as_current_span("test"):
101+
self.dynamodb.create_table(
102+
TableName="dynamodb-table",
103+
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
104+
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
105+
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
106+
)
107+
self.dynamodb.list_tables()
108+
109+
spans = self.recorder.queued_spans()
110+
assert len(spans) == 3
111+
112+
filtered_spans = agent.filter_spans(spans)
113+
assert len(filtered_spans) == 2
114+
115+
filter = lambda span: span.n == "dynamodb" # noqa: E731
116+
dynamodb_span = get_first_span_by_filter(filtered_spans, filter)
117+
118+
assert dynamodb_span.n == "dynamodb"
119+
assert dynamodb_span.data["dynamodb"]["op"] == "ListTables"
120+
70121
def test_dynamodb_create_table_as_root_exit_span(self) -> None:
71122
agent.options.allow_exit_as_root = True
72123
self.dynamodb.create_table(

tests/util/test_span_utils.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from typing import Optional
2+
import pytest
3+
4+
from instana.util.span_utils import get_operation_specifier
5+
6+
7+
@pytest.mark.parametrize(
8+
"span_name, expected_result",
9+
[("something", ""), ("redis", "command"), ("dynamodb", "op")],
10+
)
11+
def test_get_operation_specifier(
12+
span_name: str, expected_result: Optional[str]
13+
) -> None:
14+
response_redis = get_operation_specifier(span_name)
15+
assert response_redis == expected_result

tests/util/test_traceutils.py

-22
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
extract_custom_headers,
99
get_active_tracer,
1010
get_tracer_tuple,
11-
is_service_or_endpoint_ignored,
1211
tracing_is_off,
1312
)
1413

@@ -96,24 +95,3 @@ def test_tracing_is_off() -> None:
9695
response = tracing_is_off()
9796
assert not response
9897
agent.options.allow_exit_as_root = False
99-
100-
101-
def test_is_service_or_endpoint_ignored() -> None:
102-
agent.options.ignore_endpoints.append("service1")
103-
agent.options.ignore_endpoints.append("service2.endpoint1")
104-
105-
# ignore all endpoints of service1
106-
assert is_service_or_endpoint_ignored("service1")
107-
assert is_service_or_endpoint_ignored("service1", "endpoint1")
108-
assert is_service_or_endpoint_ignored("service1", "endpoint2")
109-
110-
# case-insensitive
111-
assert is_service_or_endpoint_ignored("SERVICE1")
112-
assert is_service_or_endpoint_ignored("service1", "ENDPOINT1")
113-
114-
# ignore only endpoint1 of service2
115-
assert is_service_or_endpoint_ignored("service2", "endpoint1")
116-
assert not is_service_or_endpoint_ignored("service2", "endpoint2")
117-
118-
# don't ignore other services
119-
assert not is_service_or_endpoint_ignored("service3")

0 commit comments

Comments
 (0)