Observability
The Go client for Elasticsearch provides built-in support for distributed tracing via OpenTelemetry and transport-level metrics. These features help you monitor client behavior, diagnose performance issues, and correlate Elasticsearch requests with the rest of your application.
The client includes a built-in OpenTelemetry integration that creates spans for every Elasticsearch API call. This is the recommended approach for distributed tracing.
Use NewOpenTelemetryInstrumentation to create an instrumentation provider and pass it to the client configuration:
import (
"github.com/elastic/go-elasticsearch/v9"
"go.opentelemetry.io/otel"
)
// Use the global TracerProvider (set up elsewhere in your application)
es, err := elasticsearch.NewClient(elasticsearch.Config{
Instrumentation: elasticsearch.NewOpenTelemetryInstrumentation(nil, false),
})
niluses the globalTracerProviderfromotel.GetTracerProvider(). Pass an explicit provider to use a specific one.
NewOpenTelemetryInstrumentation accepts two parameters:
| Parameter | Type | Description |
|---|---|---|
provider |
trace.TracerProvider |
The OpenTelemetry tracer provider. Pass nil to use the globally registered provider. |
captureSearchBody |
bool |
When true, the search query body is captured as the db.statement span attribute for search endpoints. |
The search endpoints that support body capture are:
searchasync_search.submitmsearcheql.searchterms_enumsearch_templatemsearch_templaterender_search_template
Enabling captureSearchBody may expose sensitive data in your traces. Only enable it in development or when your trace backend is secured appropriately.
The built-in instrumentation follows the OpenTelemetry Semantic Conventions for Elasticsearch. For details on how Elasticsearch integrates with OpenTelemetry, see OpenTelemetry integration in the Elasticsearch documentation. Each API call creates a client span with the following attributes:
| Attribute | Description | Example |
|---|---|---|
db.system |
Always "elasticsearch" |
elasticsearch |
db.operation |
The API endpoint name | search |
db.statement |
The request body (search endpoints only, when captureSearchBody is true) |
{"query":{"match_all":{}}} |
db.elasticsearch.path_parts.* |
Path parameters for the endpoint | db.elasticsearch.path_parts.index = "my-index" |
http.request.method |
The HTTP method | GET |
url.full |
The full request URL | https://localhost:9200/my-index/_search |
server.address |
The Elasticsearch node hostname | localhost |
server.port |
The Elasticsearch node port | 9200 |
db.elasticsearch.cluster.name |
The cluster name (from response header X-Found-Handling-Cluster) |
my-cluster |
db.elasticsearch.node.name |
The node name (from response header X-Found-Handling-Instance) |
instance-001 |
import (
"context"
"log"
"github.com/elastic/go-elasticsearch/v9"
"go.opentelemetry.io/otel"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
)
func main() {
// Set up an OTLP exporter
exporter, err := otlptracegrpc.New(context.Background())
if err != nil {
log.Fatal(err)
}
// Create and register a TracerProvider
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
)
defer tp.Shutdown(context.Background())
otel.SetTracerProvider(tp)
// Create the Elasticsearch client with instrumentation
es, err := elasticsearch.NewClient(elasticsearch.Config{
Addresses: []string{"https://localhost:9200"},
Instrumentation: elasticsearch.NewOpenTelemetryInstrumentation(tp, true),
})
if err != nil {
log.Fatal(err)
}
defer es.Close(context.Background())
// Every API call now creates an OpenTelemetry span
res, err := es.Info()
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
}
The client can collect transport-level metrics including request counts, failures, and response status code distributions. Enable metrics via the EnableMetrics configuration option.
es, err := elasticsearch.NewClient(elasticsearch.Config{
EnableMetrics: true,
})
Once enabled, metrics are available through the transport's Metrics() method. You can publish them using Go's expvar package for built-in HTTP monitoring:
import "expvar"
es, err := elasticsearch.NewClient(elasticsearch.Config{
EnableMetrics: true,
})
// Publish metrics at /debug/vars
expvar.Publish("go-elasticsearch", expvar.Func(func() any {
m, _ := es.Metrics()
return m
}))
The metrics include:
- Requests — Total number of requests sent
- Failures — Total number of failed requests
- Responses — Count of responses grouped by HTTP status code
- Connections — Information about live and dead connection pool members
Enable debug logging to see detailed request and response information:
es, err := elasticsearch.NewClient(elasticsearch.Config{
EnableDebugLogger: true,
})
For more control over log output, use the Logger field with one of the built-in loggers from the elastictransport package:
| Logger | Description |
|---|---|
TextLogger |
Plain text output |
ColorLogger |
Color-coded output for terminals |
CurlLogger |
Outputs requests as curl commands (useful for debugging) |
JSONLogger |
Structured JSON output |
import "github.com/elastic/elastic-transport-go/v8/elastictransport"
es, err := elasticsearch.NewClient(elasticsearch.Config{
Logger: &elastictransport.ColorLogger{
Output: os.Stdout,
EnableRequestBody: true,
EnableResponseBody: true,
},
})
Alternative integrations
You can integrate with Elastic APM by wrapping the HTTP transport with the apmelasticsearch package:
import (
"net/http"
"github.com/elastic/go-elasticsearch/v9"
"go.elastic.co/apm/module/apmelasticsearch/v2"
)
es, err := elasticsearch.NewClient(elasticsearch.Config{
Transport: apmelasticsearch.WrapRoundTripper(http.DefaultTransport),
})
This automatically creates APM transactions and spans for each Elasticsearch request.
For OpenCensus-based tracing, use the ochttp transport:
import (
"github.com/elastic/go-elasticsearch/v9"
"go.opencensus.io/plugin/ochttp"
)
es, err := elasticsearch.NewClient(elasticsearch.Config{
Transport: &ochttp.Transport{},
})
You can also implement fully custom observability using interceptors. This gives you complete control over logging, metrics, and tracing at the HTTP level. See the interceptors guide for examples.