github.com/erda-project/erda-infra@v1.0.9/providers/elasticsearch/elasticsearch.go (about) 1 // Copyright (c) 2021 Terminus, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package elasticsearch 16 17 import ( 18 "fmt" 19 "reflect" 20 "strings" 21 "time" 22 23 "github.com/erda-project/erda-infra/base/logs" 24 "github.com/erda-project/erda-infra/base/servicehub" 25 writer "github.com/erda-project/erda-infra/pkg/parallel-writer" 26 "github.com/olivere/elastic" 27 ) 28 29 // Interface . 30 type Interface interface { 31 URL() string 32 Client() *elastic.Client 33 NewBatchWriter(*BatchWriterConfig) writer.Writer 34 NewBatchWriterWithOptions(c *WriterConfig, opts ...BatchWriteOption) writer.Writer 35 NewWriter(opts *WriteOptions) *Writer 36 } 37 38 type ( 39 // BatchWriterConfig . 40 BatchWriterConfig struct { 41 Type string `file:"type" desc:"index type"` 42 Parallelism uint64 `file:"parallelism" default:"4" desc:"parallelism"` 43 Batch struct { 44 Size uint64 `file:"size" default:"100" desc:"batch size"` 45 Timeout time.Duration `file:"timeout" default:"30s" desc:"timeout to flush buffer for batch write"` 46 } `file:"batch"` 47 Retry int `file:"retry" desc:"retry if fail to write"` 48 } 49 // WriterConfig deprecated, use BatchWriterConfig instead of 50 WriterConfig = BatchWriterConfig 51 52 // BatchWriteOptions . 53 BatchWriteOptions struct { 54 ErrorHandler func(error) error 55 } 56 // BatchWriteOption . 57 BatchWriteOption func(opts *BatchWriteOptions) 58 59 // EncodeFunc . 60 EncodeFunc func(data interface{}) (index, id, typ string, body interface{}, err error) 61 62 // WriteOptions . 63 WriteOptions struct { 64 Timeout time.Duration 65 Enc EncodeFunc 66 } 67 ) 68 69 var clientType = reflect.TypeOf((*elastic.Client)(nil)) 70 71 type config struct { 72 URLs string `file:"urls" default:"http://localhost:9200" desc:"servers urls"` 73 Security bool `file:"security" default:"false" desc:"enable http basic auth"` 74 Username string `file:"username" default:"" desc:"username"` 75 Password string `file:"password" default:"" desc:"password"` 76 } 77 78 // provider . 79 type provider struct { 80 Cfg *config 81 Log logs.Logger 82 client *elastic.Client 83 } 84 85 // Init . 86 func (p *provider) Init(ctx servicehub.Context) error { 87 options := []elastic.ClientOptionFunc{ 88 elastic.SetURL(strings.Split(p.Cfg.URLs, ",")...), 89 elastic.SetSniff(false), 90 } 91 if p.Cfg.Security && (p.Cfg.Username != "" || p.Cfg.Password != "") { 92 options = append(options, elastic.SetBasicAuth(p.Cfg.Username, p.Cfg.Password)) 93 } 94 client, err := elastic.NewClient(options...) 95 if err != nil { 96 return fmt.Errorf("failed to create elasticsearch client: %s", err) 97 } 98 p.client = client 99 return nil 100 } 101 102 // Provide . 103 func (p *provider) Provide(ctx servicehub.DependencyContext, args ...interface{}) interface{} { 104 if ctx.Type() == clientType || ctx.Service() == "elasticsearch-client" || ctx.Service() == "elastic-client" { 105 return p.client 106 } 107 return &service{ 108 p: p, 109 log: p.Log.Sub(ctx.Caller()), 110 } 111 } 112 113 type service struct { 114 p *provider 115 log logs.Logger 116 } 117 118 func (s *service) Client() *elastic.Client { return s.p.client } 119 func (s *service) URL() string { 120 // TODO parse user 121 return strings.Split(s.p.Cfg.URLs, ",")[0] 122 } 123 124 func (s *service) NewBatchWriterWithOptions(c *WriterConfig, opts ...BatchWriteOption) writer.Writer { 125 options := s.newDefaultBatchWriteOptions() 126 for _, opt := range opts { 127 opt(options) 128 } 129 return writer.ParallelBatch(func(uint64) writer.Writer { 130 return &batchWriter{ 131 client: s.p.client, 132 log: s.log, 133 typ: c.Type, 134 retry: c.Retry, 135 retryDuration: 3 * time.Second, 136 timeout: fmt.Sprintf("%dms", c.Batch.Timeout.Milliseconds()), 137 } 138 }, c.Parallelism, c.Batch.Size, c.Batch.Timeout, options.ErrorHandler) 139 } 140 141 func (s *service) NewBatchWriter(c *WriterConfig) writer.Writer { 142 return s.NewBatchWriterWithOptions(c) 143 } 144 145 func (s *service) newDefaultBatchWriteOptions() *BatchWriteOptions { 146 return &BatchWriteOptions{ 147 ErrorHandler: s.batchWriteError, 148 } 149 } 150 151 func (s *service) batchWriteError(err error) error { 152 s.log.Errorf("failed to write elasticsearch: %s", err) 153 return nil // skip error 154 } 155 156 // WithBatchErrorHandler . 157 func WithBatchErrorHandler(eh func(error) error) BatchWriteOption { 158 return func(opts *BatchWriteOptions) { 159 opts.ErrorHandler = eh 160 } 161 } 162 163 func (s *service) NewWriter(opts *WriteOptions) *Writer { 164 timeout := opts.Timeout 165 if timeout <= 0 { 166 timeout = 30 * time.Second 167 } 168 return NewWriter(s.p.client, timeout, opts.Enc) 169 } 170 171 func init() { 172 servicehub.Register("elasticsearch", &servicehub.Spec{ 173 Services: []string{"elasticsearch", "elasticsearch-client", "elastic-client"}, 174 Types: []reflect.Type{ 175 reflect.TypeOf((*Interface)(nil)).Elem(), 176 clientType, 177 }, 178 Description: "elasticsearch", 179 ConfigFunc: func() interface{} { return &config{} }, 180 Creator: func() servicehub.Provider { 181 return &provider{} 182 }, 183 }) 184 }