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  }