kdrantTracing

fun kdrantTracing(openTelemetry: OpenTelemetry, serverAddress: String? = null, serverPort: Int? = null): (QdrantTransport) -> QdrantTransport

Tracing for Kdrant, on the seam every engine goes through.

kdrant-micrometer answers how slow Qdrant is. It cannot answer where a slow request sat, and in a RAG service that is usually the question worth asking: the call took 900 ms and the search was 40 of them, or the search was 700 and the embedding was 40. A metric cannot separate those. A span can, because it nests inside the caller's.

val qdrant = Kdrant(
host = "localhost",
decorateTransport = kdrantTracing(openTelemetry, serverAddress = "localhost", serverPort = 6333),
)

The same call works on KdrantGrpc, and produces the same spans: the decorator sits above the wire, so one implementation covers both engines and a third would inherit it.

What the spans say

Attribute names follow OpenTelemetry's database conventions rather than an invented vocabulary, so the spans group with everything else in the trace:

AttributeValue
db.system.nameqdrant
db.operation.namethe client operation, e.g. query, upsert, create_collection
db.collection.namethe collection, when the operation names one
server.address, server.portwhen you pass them
error.typethe dev.kdrant.KdrantException subclass, on a failed span

The span name is <operation> <collection>, or just the operation for the calls that are not about one collection.

What the spans never say

No payload value, no vector, no filter. A span attribute is exported to a backend many people can read, and the whole point of a filter is often that it names a tenant. That is also why a failed span carries error.type and no server message: Qdrant's errors quote the request back, and the request is the thing being kept out.

The attribute set is closed and asserted in the tests, so a future operation cannot quietly widen it.