elastic/elastic-transport-go
GitHub: elastic/elastic-transport-go
Elastic 官方的 Go 传输层共享库,为 Elasticsearch 等 Elastic 服务的 Go 客户端提供连接池、节点发现、重试、指标和日志等基础设施能力。
Stars: 14 | Forks: 22
# elastic-transport-go
这个库源自 elasticsearch-net,随后经过改造,可用于所有的 Elastic 服务,而不仅仅是
Elasticsearch。
它提供了 `go-elasticsearch` 使用的 Transport 接口、连接池、集群发现以及多种 logger。
## 安装
将该包添加到你的 go.mod 文件中:
`require github.com/elastic/elastic-transport-go/v8 main`
## 用法
### Transport
Transport 提供了访问 Elasticsearch API 的基础层。使用
`NewClient` 和函数式选项创建一个 client:
```
package main
import (
"context"
"log"
"net/http"
"net/url"
"time"
"github.com/elastic/elastic-transport-go/v8/elastictransport"
)
func main() {
u, _ := url.Parse("http://127.0.0.1:9200")
transport, err := elastictransport.NewClient(
elastictransport.WithURLs(u),
)
if err != nil {
log.Fatalln(err)
}
defer func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_ = transport.Close(ctx)
}()
req, _ := http.NewRequest("GET", "/", nil)
res, err := transport.Perform(req)
if err != nil {
log.Fatalln(err)
}
defer res.Body.Close()
log.Println(res)
}
```
选项会按顺序应用;当同一个设置被多次指定时,
以最后一个值为准。请在
[包文档](https://pkg.go.dev/github.com/elastic/elastic-transport-go/v8/elastictransport)
中查看 `With*` 函数以获取所有可用选项的完整列表。
常见示例:
```
// Multiple nodes with basic auth, custom retries, and compression
transport, err := elastictransport.NewClient(
elastictransport.WithURLs(u1, u2, u3),
elastictransport.WithBasicAuth("elastic", "changeme"),
elastictransport.WithRetry(5, 429, 502, 503, 504),
elastictransport.WithRetryBackoff(func(attempt int) time.Duration {
return time.Duration(attempt) * 100 * time.Millisecond
}),
elastictransport.WithCompression(gzip.BestSpeed),
)
```
### 发现
Discovery 模块会调用集群以获取其完整的节点列表。
一旦你的 transport 设置完毕,你可以像这样轻松触发此行为:
```
err := transport.DiscoverNodes()
```
或者在创建 client 时配置自动的定期发现:
```
transport, err := elastictransport.NewClient(
elastictransport.WithURLs(u),
elastictransport.WithDiscoverNodesInterval(5 * time.Minute),
)
```
### 指标
允许你直接从 transport 获取指标。在创建 client 时启用指标:
```
transport, err := elastictransport.NewClient(
elastictransport.WithURLs(u),
elastictransport.WithMetrics(),
)
```
### 分级日志
`WithLeveledLogger` 为 transport 内部事件(连接管理、节点发现)
设置了一个结构化的分级 logger。`LeveledLogger` 接口
使用与 `log/slog` 相同的 `(msg, keysAndValues...)` 约定:
```
type LeveledLogger interface {
Debug(msg string, keysAndValues ...any)
Info(msg string, keysAndValues ...any)
Warn(msg string, keysAndValues ...any)
Error(msg string, keysAndValues ...any)
}
```
添加 `LoggingInterceptor` 以便通过同一个
logger 记录请求/响应往返。这是获得完整日志的推荐方式:
```
transport, err := elastictransport.NewClient(
elastictransport.WithURLs(u),
elastictransport.WithLeveledLogger(&elastictransport.SlogLogger{
Logger: slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelDebug,
})),
}),
elastictransport.WithInterceptors(
elastictransport.LoggingInterceptor(false, false),
),
)
```
成功的往返将以 **Info** 级别记录;错误以 **Error** 级别记录;
连接管理事件根据严重程度以 **Debug**、**Warn** 或 **Error** 级别记录。
#### Body 日志
`LoggingInterceptor` 接受两个布尔值,用于启用请求和/或响应
body 捕获:
```
elastictransport.WithInterceptors(
elastictransport.LoggingInterceptor(true, true), // request body, response body
)
```
#### 内置 slog Handler
`sloghandler` 子包为所有已弃用的 logger 提供了直接替换的
`slog.Handler`:
| 已弃用的 Logger | sloghandler 替代方案 |
| ----------------- | ---------------------------------- |
| `TextLogger` | `sloghandler.NewTextHandler(w)` |
| `ColorLogger` | `sloghandler.NewColorHandler(w)` |
| `CurlLogger` | `sloghandler.NewCurlHandler(w)` |
| `JSONLogger` | `sloghandler.NewJSONECSHandler(w)` |
```
import "github.com/elastic/elastic-transport-go/v8/elastictransport/sloghandler"
transport, err := elastictransport.NewClient(
elastictransport.WithURLs(u),
elastictransport.WithLeveledLogger(&elastictransport.SlogLogger{
Logger: slog.New(sloghandler.NewColorHandler(os.Stderr)),
}),
elastictransport.WithInterceptors(
elastictransport.LoggingInterceptor(false, false),
),
)
```
#### 自定义 Logger 实现
针对 zap、zerolog、logrus 和 logr 的现成 adapter 位于
[`_examples/logging/adapters/`](./_examples/logging/adapters/) 中。
要使用不同的日志库,只需在一个轻量级的包装器上实现这四个方法:
```
type ZapLeveledLogger struct{ Logger *zap.SugaredLogger }
func (l *ZapLeveledLogger) Debug(ctx context.Context, msg string, kv ...any) { l.Logger.Debugw(msg, kv...) }
func (l *ZapLeveledLogger) Info(ctx context.Context, msg string, kv ...any) { l.Logger.Infow(msg, kv...) }
func (l *ZapLeveledLogger) Warn(ctx context.Context, msg string, kv ...any) { l.Logger.Warnw(msg, kv...) }
func (l *ZapLeveledLogger) Error(ctx context.Context, msg string, kv ...any) { l.Logger.Errorw(msg, kv...) }
```
#### Context 集成
logger 会在 `Perform` 期间被注入到请求 context 中,从而使
自定义 interceptor 可以通过 `LoggerFromContext` 获取它。调用者可以使用
`ContextWithLogger` 为单个请求覆盖 logger:
```
interceptor := func(next elastictransport.RoundTripFunc) elastictransport.RoundTripFunc {
return func(req *http.Request) (*http.Response, error) {
if logger := elastictransport.LoggerFromContext(req.Context()); logger != nil {
logger.Debug("before request", "method", req.Method)
}
return next(req)
}
}
```
#### 从 WithDebugLogger / WithLogger 迁移
`WithDebugLogger()` 和 `WithLogger()` 仍然有效,但已弃用。在
底层,`WithDebugLogger` 现在会创建一个封装了 `slog.Default()` 的 `SlogLogger`。
| | `WithDebugLogger()` | `WithLogger()` | `WithLeveledLogger()` + `LoggingInterceptor` |
| --------------------- | ------------------- | --------------- | -------------------------------------------- |
| 往返日志 | 否 | 是 | 是(通过 interceptor) |
| 连接事件 | 是(仅 Debug) | 否 | 是 |
| 输出目标 | stdout | 用户控制 | 用户控制 |
| 日志级别 | 仅 Debug | 无 | Debug/Info/Warn/Error |
| 结构化数据 | 否 | 否 | 是(键值对) |
| 自定义 logger 支持 | 否 | 是 | 是 |
| 单个 client 隔离 | 是 | 是 | 是 |
| Context 注入 | 否 | 否 | 是 |
| 可组合的顺序 | 否 | 否 | 是(interceptor 链) |
替换原有的任意旧选项:
```
// Before (WithDebugLogger)
transport, err := elastictransport.NewClient(
elastictransport.WithURLs(u),
elastictransport.WithDebugLogger(),
)
// Before (WithLogger)
transport, err := elastictransport.NewClient(
elastictransport.WithURLs(u),
elastictransport.WithLogger(&elastictransport.TextLogger{Output: os.Stdout}),
)
// After
transport, err := elastictransport.NewClient(
elastictransport.WithURLs(u),
elastictransport.WithLeveledLogger(&elastictransport.SlogLogger{
Logger: slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelDebug,
})),
}),
elastictransport.WithInterceptors(
elastictransport.LoggingInterceptor(false, false),
),
)
```
### 请求/响应 Logger(已弃用)
可以通过 `WithLogger` 选项提供一个 logger。有几个内置的 logger
可用:
#### TextLogger
配置:
```
transport, err := elastictransport.NewClient(
elastictransport.WithURLs(u),
elastictransport.WithLogger(&elastictransport.TextLogger{Output: os.Stdout, EnableRequestBody: true, EnableResponseBody: true}),
)
```
输出:
```
< {
< "name" : "es",
< "cluster_name" : "elasticsearch",
< "cluster_uuid" : "RxB1iqTNT9q3LlIkTsmWRA",
< "version" : {
< "number" : "8.0.0-SNAPSHOT",
< "build_flavor" : "default",
< "build_type" : "docker",
< "build_hash" : "0564e027dc6c69236937b1edcc04c207b4cd8128",
< "build_date" : "2021-11-25T00:23:33.139514432Z",
< "build_snapshot" : true,
< "lucene_version" : "9.0.0",
< "minimum_wire_compatibility_version" : "7.16.0",
< "minimum_index_compatibility_version" : "7.0.0"
< },
< "tagline" : "You Know, for Search"
< }
```
#### JSONLogger
配置:
```
transport, err := elastictransport.NewClient(
elastictransport.WithURLs(u),
elastictransport.WithLogger(&elastictransport.JSONLogger{Output: os.Stdout, EnableRequestBody: true, EnableResponseBody: true}),
)
```
输出:
```
{
"@timestamp": "2021-11-25T16:33:51Z",
"event": {
"duration": 2892269
},
"url": {
"scheme": "http",
"domain": "127.0.0.1",
"port": 9200,
"path": "/",
"query": ""
},
"http": {
"request": {
"method": "GET"
},
"response": {
"status_code": 200,
"body": "{\n \"name\" : \"es1\",\n \"cluster_name\" : \"go-elasticsearch\",\n \"cluster_uuid\" : \"RxB1iqTNT9q3LlIkTsmWRA\",\n \"version\" : {\n \"number\" : \"8.0.0-SNAPSHOT\",\n \"build_flavor\" : \"default\",\n \"build_type\" : \"docker\",\n \"build_hash\" : \"0564e027dc6c69236937b1edcc04c207b4cd8128\",\n \"build_date\" : \"2021-11-25T00:23:33.139514432Z\",\n \"build_snapshot\" : true,\n \"lucene_version\" : \"9.0.0\",\n \"minimum_wire_compatibility_version\" : \"8.0.0\",\n \"minimum_index_compatibility_version\" : \"7.0.0\"\n },\n \"tagline\" : \"You Know, for Search\"\n}\n"
}
}
}
```
#### ColorLogger
配置:
```
transport, err := elastictransport.NewClient(
elastictransport.WithURLs(u),
elastictransport.WithLogger(&elastictransport.ColorLogger{Output: os.Stdout, EnableRequestBody: true, EnableResponseBody: true}),
)
```
输出:
```
GET http://127.0.0.1:9200/ 200 OK 2ms
« {
« "name" : "es1",
« "cluster_name" : "go-elasticsearch",
« "cluster_uuid" : "RxB1iqTNT9q3LlIkTsmWRA",
« "version" : {
« "number" : "8.0.0-SNAPSHOT",
« "build_flavor" : "default",
« "build_type" : "docker",
« "build_hash" : "0564e027dc6c69236937b1edcc04c207b4cd8128",
« "build_date" : "2021-11-25T00:23:33.139514432Z",
« "build_snapshot" : true,
« "lucene_version" : "9.0.0",
« "minimum_wire_compatibility_version" : "7.16.0",
« "minimum_index_compatibility_version" : "7.0.0"
« },
« "tagline" : "You Know, for Search"
« }
────────────────────────────────────────────────────────────────────────────────
```
#### CurlLogger
配置:
```
transport, err := elastictransport.NewClient(
elastictransport.WithURLs(u),
elastictransport.WithLogger(&elastictransport.CurlLogger{Output: os.Stdout, EnableRequestBody: true, EnableResponseBody: true}),
)
```
输出:
```
curl -X GET 'http://localhost:9200/?pretty'
# => 2021-11-25T16:40:11Z [200 OK] 3ms
# {
# "name": "es1",
# "cluster_name": "go-elasticsearch",
# "cluster_uuid": "RxB1iqTNT9q3LlIkTsmWRA",
# "version": {
# "number": "8.0.0-SNAPSHOT",
# "build_flavor": "default",
# "build_type": "docker",
# "build_hash": "0564e027dc6c69236937b1edcc04c207b4cd8128",
# "build_date": "2021-11-25T00:23:33.139514432Z",
# "build_snapshot": true,
# "lucene_version": "9.0.0",
# "minimum_wire_compatibility_version": "7.16.0",
# "minimum_index_compatibility_version": "7.0.0"
# },
# "tagline": "You Know, for Search"
# }
```
# 许可证
基于 Apache License, Version 2.0 授权。
标签:日志审计