@@ -12,7 +12,11 @@ import (
1212 "sync"
1313 "time"
1414
15+ "bytes"
16+ "encoding/base64"
17+ "encoding/binary"
1518 "github.com/tinylib/msgp/msgp"
19+ "math/rand"
1620)
1721
1822const (
@@ -52,12 +56,22 @@ type Config struct {
5256 // Sub-second precision timestamps are only possible for those using fluentd
5357 // v0.14+ and serializing their messages with msgpack.
5458 SubSecondPrecision bool `json:"sub_second_precision"`
59+
60+ // RequestAck sends the chunk option with a unique ID. The server will
61+ // respond with an acknowledgement. This option improves the reliability
62+ // of the message transmission.
63+ RequestAck bool `json:"request_ack"`
64+ }
65+
66+ type msgToSend struct {
67+ data []byte
68+ ack string
5569}
5670
5771type Fluent struct {
5872 Config
5973
60- pending chan [] byte
74+ pending chan * msgToSend
6175 wg sync.WaitGroup
6276
6377 muconn sync.Mutex
@@ -103,7 +117,7 @@ func New(config Config) (f *Fluent, err error) {
103117 if config .Async {
104118 f = & Fluent {
105119 Config : config ,
106- pending : make (chan [] byte , config .BufferLimit ),
120+ pending : make (chan * msgToSend , config .BufferLimit ),
107121 }
108122 f .wg .Add (1 )
109123 go f .run ()
@@ -188,7 +202,7 @@ func (f *Fluent) PostWithTime(tag string, tm time.Time, message interface{}) err
188202}
189203
190204func (f * Fluent ) EncodeAndPostData (tag string , tm time.Time , message interface {}) error {
191- var data [] byte
205+ var data * msgToSend
192206 var err error
193207 if data , err = f .EncodeData (tag , tm , message ); err != nil {
194208 return fmt .Errorf ("fluent#EncodeAndPostData: can't convert '%#v' to msgpack:%v" , message , err )
@@ -197,11 +211,11 @@ func (f *Fluent) EncodeAndPostData(tag string, tm time.Time, message interface{}
197211}
198212
199213// Deprecated: Use EncodeAndPostData instead
200- func (f * Fluent ) PostRawData (data [] byte ) {
214+ func (f * Fluent ) PostRawData (data * msgToSend ) {
201215 f .postRawData (data )
202216}
203217
204- func (f * Fluent ) postRawData (data [] byte ) error {
218+ func (f * Fluent ) postRawData (data * msgToSend ) error {
205219 if f .Config .Async {
206220 return f .appendBuffer (data )
207221 }
@@ -219,22 +233,51 @@ type MessageChunk struct {
219233// So, it should write JSON marshaler by hand.
220234func (chunk * MessageChunk ) MarshalJSON () ([]byte , error ) {
221235 data , err := json .Marshal (chunk .message .Record )
222- return []byte (fmt .Sprintf ("[\" %s\" ,%d,%s,null]" , chunk .message .Tag ,
223- chunk .message .Time , data )), err
236+ if err != nil {
237+ return nil , err
238+ }
239+ option , err := json .Marshal (chunk .message .Option )
240+ if err != nil {
241+ return nil , err
242+ }
243+ return []byte (fmt .Sprintf ("[\" %s\" ,%d,%s,%s]" , chunk .message .Tag ,
244+ chunk .message .Time , data , option )), err
224245}
225246
226- func (f * Fluent ) EncodeData (tag string , tm time.Time , message interface {}) (data []byte , err error ) {
247+ // getUniqueID returns a base64 encoded unique ID that can be used for chunk/ack
248+ // mechanism, see
249+ // https://github.com/fluent/fluentd/wiki/Forward-Protocol-Specification-v1#option
250+ func getUniqueID (timeUnix int64 ) string {
251+ buf := bytes .NewBuffer (nil )
252+ enc := base64 .NewEncoder (base64 .StdEncoding , buf )
253+ if err := binary .Write (enc , binary .LittleEndian , timeUnix ); err != nil {
254+ panic (err )
255+ }
256+ if err := binary .Write (enc , binary .LittleEndian , rand .Uint64 ()); err != nil {
257+ panic (err )
258+ }
259+ enc .Close ()
260+ return buf .String ()
261+ }
262+
263+ func (f * Fluent ) EncodeData (tag string , tm time.Time , message interface {}) (data * msgToSend , err error ) {
264+ option := make (map [string ]string )
265+ data = & msgToSend {}
227266 timeUnix := tm .Unix ()
267+ if f .Config .RequestAck {
268+ data .ack = getUniqueID (timeUnix )
269+ option ["chunk" ] = data .ack
270+ }
228271 if f .Config .MarshalAsJSON {
229- msg := Message {Tag : tag , Time : timeUnix , Record : message }
272+ msg := Message {Tag : tag , Time : timeUnix , Record : message , Option : option }
230273 chunk := & MessageChunk {message : msg }
231- data , err = json .Marshal (chunk )
274+ data . data , err = json .Marshal (chunk )
232275 } else if f .Config .SubSecondPrecision {
233- msg := & MessageExt {Tag : tag , Time : EventTime (tm ), Record : message }
234- data , err = msg .MarshalMsg (nil )
276+ msg := & MessageExt {Tag : tag , Time : EventTime (tm ), Record : message , Option : option }
277+ data . data , err = msg .MarshalMsg (nil )
235278 } else {
236- msg := & Message {Tag : tag , Time : timeUnix , Record : message }
237- data , err = msg .MarshalMsg (nil )
279+ msg := & Message {Tag : tag , Time : timeUnix , Record : message , Option : option }
280+ data . data , err = msg .MarshalMsg (nil )
238281 }
239282 return
240283}
@@ -250,7 +293,7 @@ func (f *Fluent) Close() (err error) {
250293}
251294
252295// appendBuffer appends data to buffer with lock.
253- func (f * Fluent ) appendBuffer (data [] byte ) error {
296+ func (f * Fluent ) appendBuffer (data * msgToSend ) error {
254297 select {
255298 case f .pending <- data :
256299 default :
@@ -303,7 +346,7 @@ func e(x, y float64) int {
303346 return int (math .Pow (x , y ))
304347}
305348
306- func (f * Fluent ) write (data [] byte ) error {
349+ func (f * Fluent ) write (data * msgToSend ) error {
307350
308351 for i := 0 ; i < f .Config .MaxRetry ; i ++ {
309352
@@ -330,10 +373,25 @@ func (f *Fluent) write(data []byte) error {
330373 } else {
331374 f .conn .SetWriteDeadline (time.Time {})
332375 }
333- _ , err := f .conn .Write (data )
376+ _ , err := f .conn .Write (data . data )
334377 if err != nil {
335378 f .close ()
336379 } else {
380+ // Acknowledgment check
381+ if data .ack != "" {
382+ ack := & AckResp {}
383+ if f .Config .MarshalAsJSON {
384+ dec := json .NewDecoder (f .conn )
385+ err = dec .Decode (ack )
386+ } else {
387+ r := msgp .NewReader (f .conn )
388+ err = ack .DecodeMsg (r )
389+ }
390+ if err != nil || ack .Ack != data .ack {
391+ f .close ()
392+ continue
393+ }
394+ }
337395 return err
338396 }
339397 }
0 commit comments