feat(azure-search): add Azure AI Search instrumentation and semantic conventions - #4392
feat(azure-search): add Azure AI Search instrumentation and semantic conventions#4392sanyamk23 wants to merge 3 commits into
Conversation
|
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughAdded a new Azure AI Search OpenTelemetry instrumentation package. It wraps three Azure Search client types, records request and response attributes, handles span errors, registers semantic conventions, and includes package configuration, documentation, and tests. ChangesAzure AI Search instrumentation
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant SearchClient
participant AzureSearchInstrumentor
participant Tracer
participant SpanExporter
SearchClient->>AzureSearchInstrumentor: wrapped search operation
AzureSearchInstrumentor->>Tracer: create client span
AzureSearchInstrumentor->>SearchClient: execute original operation
SearchClient-->>AzureSearchInstrumentor: response or exception
AzureSearchInstrumentor->>Tracer: record attributes and status
Tracer->>SpanExporter: export completed span
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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
`@packages/opentelemetry-instrumentation-azure-search/opentelemetry/instrumentation/azure_search/__init__.py`:
- Around line 176-179: Update the skillset handling branch in the
instrumentation method to stop recording skillset.name as
SpanAttributes.AZURE_SEARCH_INDEXER_NAME; add and use a dedicated
AZURE_SEARCH_SKILLSET_NAME attribute, and update the README entries for
create_skillset, get_skillset, and delete_skillset to document the resulting
behavior.
- Around line 190-205: Update the response-attribute handling for the document
methods in _set_response_attributes to count successful results using each
IndexingResult’s succeeded field instead of accessing r.error. Preserve the
existing total count from results and ensure both AZURE_SEARCH_SUCCEEDED_COUNT
and AZURE_SEARCH_DOCUMENTS_COUNT are set for all listed methods.
In `@packages/opentelemetry-instrumentation-azure-search/README.md`:
- Line 40: The README attribute table is inconsistent with the Azure Search
instrumentation behavior. After updating the input-attribute extraction logic to
capture the index name for SearchIndexClient.analyze_text and the indexer name
for create_skillset, get_skillset, and delete_skillset, update the corresponding
analyze_text and skillset rows in the README table to document the attributes
actually emitted.
In
`@packages/opentelemetry-instrumentation-azure-search/tests/test_azure_search_instrumentation.py`:
- Around line 13-38: Update _make_search_client, _make_index_client, and
_make_indexer_client to construct real patched Azure SDK client instances, or
test doubles that invoke the instrumented SDK methods, instead of
MagicMock(spec=...). Preserve the existing endpoint and index configuration so
calls such as search, get_index, and indexer operations exercise
AzureSearchInstrumentor’s _instrument_method wrapping.
🪄 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 Plus
Run ID: 6e1558ea-d47a-424d-86d5-3053b459f8ac
📒 Files selected for processing (15)
packages/opentelemetry-instrumentation-azure-search/.python-versionpackages/opentelemetry-instrumentation-azure-search/README.mdpackages/opentelemetry-instrumentation-azure-search/opentelemetry/__init__.pypackages/opentelemetry-instrumentation-azure-search/opentelemetry/instrumentation/__init__.pypackages/opentelemetry-instrumentation-azure-search/opentelemetry/instrumentation/azure_search/__init__.pypackages/opentelemetry-instrumentation-azure-search/opentelemetry/instrumentation/azure_search/config.pypackages/opentelemetry-instrumentation-azure-search/opentelemetry/instrumentation/azure_search/utils.pypackages/opentelemetry-instrumentation-azure-search/opentelemetry/instrumentation/azure_search/version.pypackages/opentelemetry-instrumentation-azure-search/poetry.tomlpackages/opentelemetry-instrumentation-azure-search/project.jsonpackages/opentelemetry-instrumentation-azure-search/pyproject.tomlpackages/opentelemetry-instrumentation-azure-search/tests/__init__.pypackages/opentelemetry-instrumentation-azure-search/tests/conftest.pypackages/opentelemetry-instrumentation-azure-search/tests/test_azure_search_instrumentation.pypackages/opentelemetry-semantic-conventions-ai/opentelemetry/semconv_ai/__init__.py
| def _make_search_client(index_name="test-index"): | ||
| """Create a mock SearchClient with patched endpoint and index.""" | ||
| from azure.search.documents import SearchClient | ||
|
|
||
| client = MagicMock(spec=SearchClient) | ||
| client._endpoint = "https://test.search.windows.net" | ||
| client._index_name = index_name | ||
| return client | ||
|
|
||
|
|
||
| def _make_index_client(): | ||
| """Create a mock SearchIndexClient.""" | ||
| from azure.search.documents.indexes import SearchIndexClient | ||
|
|
||
| client = MagicMock(spec=SearchIndexClient) | ||
| client._endpoint = "https://test.search.windows.net" | ||
| return client | ||
|
|
||
|
|
||
| def _make_indexer_client(): | ||
| """Create a mock SearchIndexerClient.""" | ||
| from azure.search.documents.indexes import SearchIndexerClient | ||
|
|
||
| client = MagicMock(spec=SearchIndexerClient) | ||
| client._endpoint = "https://test.search.windows.net" | ||
| return client |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🌐 Web query:
Does unittest.mock MagicMock(spec=SomeClass) delegate method calls to the real class implementation or to a class attribute patched by wrapt.wrap_function_wrapper?
💡 Result:
No, unittest.mock.MagicMock(spec=SomeClass) does not delegate method calls to the implementation of a class attribute patched by wrapt.wrap_function_wrapper [1][2]. When you create a MagicMock with a spec (e.g., MagicMock(spec=SomeClass)), the mock object creates a mock interface based on the attributes and methods of the original class at the time the mock is initialized [3][2]. Because MagicMock is designed to be a "test double" that replaces the original object, it is functionally independent of the real class's actual code execution [1][2]. Here is why delegation does not occur: 1. Isolation by Design: The purpose of a MagicMock (especially with a spec) is to intercept calls to methods defined in the spec, not to proxy them to the underlying implementation [1][2]. If you call a method on a MagicMock, the mock simply records the call and returns its own pre-configured return value (or a new child mock) [1][4]. 2. Mocking Interface vs. Behavior: The spec=SomeClass argument only limits the mock's interface—it ensures that if you attempt to access a method that does not exist on SomeClass, the mock raises an AttributeError [3][2]. It does not link the mock's behavior to the logic of the real methods, regardless of whether those methods have been monkey-patched by wrapt [2]. 3. Interaction with wrapt: wrapt.wrap_function_wrapper works by replacing an attribute in the original class (or module) with a wrapper function [5][6]. When you create a MagicMock with spec=SomeClass, the mock inspects the state of SomeClass at that moment. Even if you have successfully patched SomeClass with wrapt, the MagicMock does not execute the patched method; it treats the method name as a target to be mocked [1][2]. If you need the behavior of the real class to persist while only modifying specific methods, you should generally not replace the entire class with a MagicMock. Instead, consider using patch.object to replace only the specific method you wish to change, or use a side_effect function on the mock if you want to conditionally delegate to the original implementation [1][7][2].
Citations:
- 1: https://docs.python.org/3/library/unittest.mock.html
- 2: https://python-testing-debugging.com/advanced-mocking-test-doubles-in-python/deep-dive-into-unittestmock/when-to-use-magicmock-vs-mock-in-python/
- 3: https://docs.python.org/3/library/unittest.mock-examples.html
- 4: https://docs.python.org/release/3.10.19/library/unittest.mock.html
- 5: https://grahamdumpleton.me/posts/2026/05/lazy-monkey-patching-with-wrapt/
- 6: https://grahamdumpleton.me/posts/2015/03/safely-applying-monkey-patches-in-python/
- 7: https://stackoverflow.com/questions/50138219/mock-class-with-original-spec-python
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Locate relevant files"
fd -a 'test_azure_search_instrumentation.py|__init__.py|conftest.py' . | sed 's#^\./##' | sort
echo
echo "Inspect test helpers and instrumentor setup"
for f in $(fd 'test_azure_search_instrumentation.py|conftest.py|__init__.py' packages/opentelemetry-instrumentation-azure-search); do
echo "===== $f ====="
wc -l "$f"
sed -n '1,220p' "$f"
done
echo
echo "Find AzureSearchInstrumentor and instrumentation helper patterns"
rg -n "AzureSearchInstrumentor|wrap_function_wrapper|instrument\\(|_wrap|search_spans|get_index|create_data_source|indexer|IndexingResult|r\\.error" packages/opentelemetry-instrumentation-azure -S
echo
python3 - <<'PY'
import inspect
from unittest.mock import MagicMock
class Fake:
def method(self):
return "real"
Fake.method = lambda self: "patched class-level"
m = MagicMock(spec=Fake)
print("MagicMock(spec=Fake) call result:", m.method())
print("MagicMock object identity different:", m.method is not Fake.method)
PYRepository: traceloop/openllmetry
Length of output: 32076
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Read remaining azure_search __init__.py"
sed -n '220,327p' packages/opentelemetry-instrumentation-azure-search/opentelemetry/instrumentation/azure_search/__init__.py
echo
echo "Find AzureSearchInstrumentor instrumentation code"
rg -n "class AzureSearchInstrumentor|def instrument|def _uninstrument|wrap_function_wrapper|_WrappedOperation" packages/opentelemetry-instrumentation-azure-search/opentelemetry/instrumentation/azure_search -S
echo
echo "Search for AzureSearch helpers outside package root"
rg -n "AzureSearchInstrumentor|wrap_function_wrapper|_WrappedOperation" packages/test_*.py packages/opentelemetry-instrumentation-azure-search -S
echo
echo "Static probe: MagicMock(spec=fake_class_with_patch) call bypass"
python3 - <<'PY'
from unittest.mock import MagicMock
import sys
class FakeSdkMethod:
def __init__(self, name):
self.calls = []
self.output = f"real {name}"
def __get__(self, obj, objtype=None):
def call(*args, **kwargs):
self.calls.append((args, kwargs))
return self.output
return call
FakeSdkMethod.search = FakeSdkMethod("search")
FakeSdkMethod.instance_attribute = 1
spec_obj = MagicMock(spec=type("Sdk", (), {"search": lambda self: "spec searched"}))
print("spec_obj.search is spec class attribute?", spec_obj.search is type("Sdk", (), {"search": lambda self: "spec searched"}).search)
print("spec_obj.search callable?", callable(spec_obj.search))
print("spec_obj.search result:", spec_obj.search())
# Mirror the conftest patching model: an existing client-like object gets a patched runtime method later.
class Client:
def search(self):
return "before patch"
Client.search = lambda self: "patched"
spec_client = MagicMock(spec=Client)
spec_client.search().return_value = "config return"
print("spec_client.search is patched runtime method?", spec_client.search is Client.search)
PYRepository: traceloop/openllmetry
Length of output: 6474
Use real patched SDK clients in these tests.
_make_search_client, _make_index_client, and _make_indexer_client build MagicMock(spec=SearchClient) and the index/indexer equivalents, then the tests call client.search(...), client.get_index(...), etc. These mocked methods are independent child mocks, so they do not invoke the AzureSearchInstrumentor wrappage in _instrument_method.
Move to real SearchClient/SearchIndexClient/SearchIndexerClient instances, or another test double whose method call path is exercised by the patched SDK method.
🤖 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
`@packages/opentelemetry-instrumentation-azure-search/tests/test_azure_search_instrumentation.py`
around lines 13 - 38, Update _make_search_client, _make_index_client, and
_make_indexer_client to construct real patched Azure SDK client instances, or
test doubles that invoke the instrumented SDK methods, instead of
MagicMock(spec=...). Preserve the existing endpoint and index configuration so
calls such as search, get_index, and indexer operations exercise
AzureSearchInstrumentor’s _instrument_method wrapping.
- Use AZURE_SEARCH_SKILLSET_NAME for skillset operations instead of AZURE_SEARCH_INDEXER_NAME (distinct Azure Search entities) - Fix IndexingResult succeeded check: use r.succeeded instead of not r.error - Fix README: analyze_text does not capture index_name, skillset operations capture skillset_name - Remove spec= from MagicMock in tests to avoid wrapt wrapper issues Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Description
Adds a new
opentelemetry-instrumentation-azure-searchpackage and the corresponding semantic conventions to support tracing for Azure AI Search.Changes
opentelemetry-instrumentation-azure-searchwith instrumentation, config, utils, testssemconv_ai(db.azure_search.*namespace): index name, search text, top, filter, result/document/succeeded counts, indexer name/status, index dimensions/doc count/size, service limit/usage, autocomplete/suggest textSummary by CodeRabbit
New Features
Documentation
Tests