Elasticsearch (tracing)

Elasticsearch is a distributed search and analytics engine. Respan’s instrumentation wraps the official Python client’s shared transport boundary, so the synchronous Elasticsearch client and asynchronous AsyncElasticsearch client receive the same tracing coverage.

Each request emits a canonical Respan task span with its operation, sanitized target, status, timing, and optional request and response content.

Create an account at platform.respan.ai and grab an API key.

Run npx @respan/cli setup to set up with your coding agent.

Setup

1

Install packages

$pip install respan-ai respan-instrumentation-elasticsearch "elasticsearch[async]>=8.13,<10"
2

Set environment variables

$export RESPAN_API_KEY="YOUR_RESPAN_API_KEY"

Your Elasticsearch connection can point to a local cluster, Elastic Cloud, or another compatible deployment.

3

Initialize and run

1import os
2
3from elasticsearch import Elasticsearch
4from respan import Respan, workflow
5from respan_instrumentation_elasticsearch import ElasticsearchInstrumentor
6
7respan = Respan(
8 api_key=os.environ["RESPAN_API_KEY"],
9 app_name="elasticsearch-tracing",
10 instrumentations=[ElasticsearchInstrumentor()],
11)
12client = Elasticsearch("http://localhost:9200")
13
14
15@workflow(name="elasticsearch_search_workflow")
16def search_articles():
17 client.index(
18 index="articles",
19 id="doc-1",
20 document={"title": "Tracing Elasticsearch with Respan"},
21 )
22 return client.search(
23 index="articles",
24 query={"match": {"title": "tracing"}},
25 )
26
27
28try:
29 print(search_articles().body)
30finally:
31 client.close()
32 respan.shutdown()

Passing ElasticsearchInstrumentor() to Respan activates and later deactivates the transport patches for you.

4

View your trace

Open the Traces page and search for workflow name elasticsearch_search_workflow. The workflow contains nested elasticsearch.index and elasticsearch.search task spans.

Asynchronous client

AsyncElasticsearch is covered by the same instrumentor; no additional setup is required.

1import asyncio
2import os
3
4from elasticsearch import AsyncElasticsearch
5from respan import Respan, workflow
6from respan_instrumentation_elasticsearch import ElasticsearchInstrumentor
7
8respan = Respan(
9 api_key=os.environ["RESPAN_API_KEY"],
10 instrumentations=[ElasticsearchInstrumentor()],
11)
12
13
14@workflow(name="elasticsearch_async_bulk_workflow")
15async def index_and_search(client: AsyncElasticsearch):
16 await client.bulk(
17 operations=[
18 {"index": {"_index": "articles", "_id": "async-1"}},
19 {"title": "Async Elasticsearch tracing"},
20 ]
21 )
22 return await client.search(index="articles", query={"match_all": {}})
23
24
25async def main():
26 client = AsyncElasticsearch("http://localhost:9200")
27 try:
28 result = await index_and_search(client)
29 print(result.body)
30 finally:
31 await client.close()
32 respan.shutdown()
33
34
35asyncio.run(main())

Covered operations

The transport-level integration covers every request made by the official clients. Common operation names include:

OperationExamples
Documentsindex, get, exists, update, and delete
Searchsearch, count, and multi-search requests
BulkSynchronous and asynchronous bulk indexing and deletion
IndicesRefresh and other index-management requests
ClusterCluster health and other cluster APIs
Other APIsRequests not matched above are still traced as Elasticsearch request spans

Content capture

Request and response bodies are captured by default. Disable content capture when documents, queries, or results may contain sensitive data:

1from respan import Respan
2from respan_instrumentation_elasticsearch import ElasticsearchInstrumentor
3
4respan = Respan(
5 instrumentations=[ElasticsearchInstrumentor(capture_content=False)],
6)

With capture disabled, spans retain the operation, sanitized target, HTTP status, timing, and exception type while omitting request and response bodies. Document and task identifiers in targets are sanitized, and request headers are never captured.

Errors

Transport exceptions are re-raised after the span records OpenTelemetry error status, status_code, and error.message. HTTP error responses are marked the same way. When content capture is disabled, backend response details and exception messages are replaced with a generic status or exception type.

1from elasticsearch import NotFoundError
2
3try:
4 client.get(index="articles", id="missing-document")
5except NotFoundError as exc:
6 print(f"Expected Elasticsearch status: {exc.meta.status}")

Configuration

ParameterTypeDefaultDescription
capture_contentboolTrueCapture request and response bodies in canonical entity input and output.
max_attribute_charsint16000Maximum serialized size for each structured input or output attribute; values below 512 are raised to 512.
request_hookcallable | NoneNoneCalled with (span, method, target, kwargs) before the transport request.
response_hookcallable | NoneNoneCalled with (span, response_body) after a successful transport response.
api_keystr | NoneNoneRespan option that falls back to RESPAN_API_KEY.
base_urlstr | NoneNoneRespan option that falls back to RESPAN_BASE_URL.

Add request metadata

Use propagate_attributes to group Elasticsearch activity by user, thread, or application metadata.

1from respan import propagate_attributes
2
3
4def search_for_user(user_id: str, question: str):
5 with propagate_attributes(
6 customer_identifier=user_id,
7 thread_identifier="search-session-123",
8 metadata={"index": "articles", "feature": "knowledge-search"},
9 ):
10 return client.search(
11 index="articles",
12 query={"match": {"body": question}},
13 )

Lifecycle

Activation and deactivation are idempotent and shared safely across multiple instrumentor instances. When you pass the instrumentor to Respan, call respan.shutdown() when the process exits so spans are flushed and the final shared transport patches are removed.