github.com/erda-project/erda-infra@v1.0.9/providers/elasticsearch/writer.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 "context" 19 "encoding/json" 20 "errors" 21 "fmt" 22 "reflect" 23 "strings" 24 "time" 25 26 "github.com/erda-project/erda-infra/base/logs" 27 "github.com/olivere/elastic" 28 "github.com/recallsong/go-utils/reflectx" 29 ) 30 31 // Document . 32 type Document struct { 33 ID string `json:"id"` 34 Index string `json:"index"` 35 Data interface{} `json:"data"` 36 } 37 38 type batchWriter struct { 39 client *elastic.Client 40 typ string 41 timeout string 42 retry int 43 retryDuration time.Duration 44 log logs.Logger 45 } 46 47 func (w *batchWriter) Write(data interface{}) error { 48 _, err := w.WriteN(data) 49 return err 50 } 51 52 func (w *batchWriter) WriteN(data ...interface{}) (int, error) { 53 if len(data) <= 0 { 54 return 0, nil 55 } 56 57 requests := make([]elastic.BulkableRequest, len(data), len(data)) 58 for i, item := range data { 59 if doc, ok := item.(*Document); ok { 60 req := elastic.NewBulkIndexRequest().Index(doc.Index).Type(w.typ).Doc(doc.Data) 61 if len(doc.ID) > 0 { 62 req.Id(doc.ID) 63 } 64 requests[i] = req 65 } else { 66 return 0, fmt.Errorf("%s is not *elasticsearch.Document", reflect.TypeOf(item)) 67 } 68 } 69 for i := 0; ; i++ { 70 res, err := w.client.Bulk().Add(requests...).Timeout(w.timeout).Do(context.Background()) 71 if err != nil { 72 if i < w.retry { 73 w.log.Warnf("failed to write batch(%d) to elasticsearch and retry after %s: %s", len(requests), w.retryDuration.String(), err) 74 time.Sleep(w.retryDuration) 75 continue 76 } 77 w.log.Errorf("failed to write batch(%d) to elasticsearch: %s", len(requests), err) 78 break 79 } 80 if res.Errors { 81 for _, item := range res.Failed() { 82 if item == nil || item.Error == nil { 83 continue 84 } 85 byts, _ := json.Marshal(item.Error) 86 // TODO: notify error 87 w.log.Errorf("failed to index data, [%s][%s]: %s", item.Index, item.Type, reflectx.BytesToString(byts)) 88 } 89 } 90 break 91 } 92 return len(requests), nil 93 } 94 95 func (w *batchWriter) Close() error { return nil } 96 97 // NewWriter . 98 func NewWriter(client *elastic.Client, timeout time.Duration, enc EncodeFunc) *Writer { 99 w := &Writer{ 100 client: client, 101 enc: enc, 102 } 103 if timeout > 0 { 104 w.timeout = fmt.Sprintf("%dms", timeout.Milliseconds()) 105 } 106 return w 107 } 108 109 // Writer . 110 type Writer struct { 111 client *elastic.Client 112 enc EncodeFunc 113 timeout string 114 } 115 116 // Close . 117 func (w *Writer) Close() error { return nil } 118 119 // Write . 120 func (w *Writer) Write(data interface{}) error { 121 index, id, typ, body, err := w.enc(data) 122 if err != nil { 123 return err 124 } 125 _, err = w.client.Index(). 126 Index(index).Id(id).Type(typ). 127 BodyJson(body).Timeout(w.timeout).Do(context.Background()) 128 return err 129 } 130 131 // WriteN . 132 func (w *Writer) WriteN(list ...interface{}) (int, error) { 133 if len(list) <= 0 { 134 return 0, nil 135 } 136 bulk := w.client.Bulk() 137 for _, data := range list { 138 index, id, typ, body, err := w.enc(data) 139 if err != nil { 140 return 0, err 141 } 142 req := elastic.NewBulkIndexRequest().Index(index).Id(id).Type(typ).Doc(body) 143 bulk.Add(req) 144 } 145 res, err := bulk.Timeout(w.timeout).Do(context.Background()) 146 if err != nil { 147 berr := &BatchWriteError{ 148 List: list, 149 Errors: make([]error, len(list), len(list)), 150 } 151 for i, n := 0, len(list); i < n; i++ { 152 berr.Errors[i] = err 153 } 154 return 0, berr 155 } 156 if res.Errors { 157 if len(res.Items) != len(list) { 158 return 0, fmt.Errorf("request items(%d), but response items(%d)", len(list), len(res.Items)) 159 } 160 berr := &BatchWriteError{ 161 List: make([]interface{}, 0, len(list)), 162 Errors: make([]error, 0, len(list)), 163 } 164 for i, item := range res.Items { 165 for _, result := range item { // len(item) is 1, contains index request only 166 if !(result.Status >= 200 && result.Status <= 299) { 167 var sb strings.Builder 168 json.NewEncoder(&sb).Encode(result) 169 berr.List = append(berr.List, list[i]) 170 berr.Errors = append(berr.Errors, errors.New(sb.String())) 171 break 172 } 173 } 174 } 175 return len(list) - len(berr.Errors), berr 176 } 177 return len(list), nil 178 } 179 180 // BatchWriteError . 181 type BatchWriteError struct { 182 List []interface{} 183 Errors []error 184 } 185 186 // Error . 187 func (e *BatchWriteError) Error() string { 188 if len(e.Errors) <= 0 { 189 return "" 190 } 191 return fmt.Sprintf("bulk writes occur errors(%d): %v ...", len(e.Errors), e.Errors[0]) 192 }