| OLD | NEW |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | 1 // Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 // Package logservice provides a client which can be used to to collect and send
batches of logs to the eventlog service. | 5 // Package logservice provides a client which can be used to to collect and send
batches of logs to the eventlog service. |
| 6 package logservice | 6 package logservice |
| 7 | 7 |
| 8 import ( | 8 import ( |
| 9 "bytes" | 9 "bytes" |
| 10 "context" |
| 11 "fmt" |
| 10 "net/http" | 12 "net/http" |
| 11 "time" | 13 "time" |
| 12 | 14 |
| 13 "github.com/golang/protobuf/proto" | 15 "github.com/golang/protobuf/proto" |
| 14 | 16 |
| 15 logpb "github.com/luci/luci-go/common/eventlog/proto" | 17 logpb "github.com/luci/luci-go/common/eventlog/proto" |
| 16 "golang.org/x/net/context" | |
| 17 ) | 18 ) |
| 18 | 19 |
| 19 // Client sends event logs to the eventlog service. | 20 // Logger sends event logs to the eventlog service. |
| 20 type Client struct { | 21 type Logger struct { |
| 21 HTTPClient *http.Client | 22 HTTPClient *http.Client |
| 23 |
| 22 serverAddr string | 24 serverAddr string |
| 23 logSource string | 25 logSource string |
| 24 } | 26 } |
| 25 | 27 |
| 26 // NewClient constructs a new Client. | 28 // NewLogger constructs a new Client. |
| 27 // Users must call Close when the Client is no longer needed. | 29 // Users must call Close when the Client is no longer needed. |
| 28 func NewClient(serverAddr, logSourceName string) *Client { | 30 func NewLogger(serverAddr, logSourceName string) *Logger { |
| 29 » return &Client{ | 31 » return &Logger{ |
| 30 serverAddr: serverAddr, | 32 serverAddr: serverAddr, |
| 31 logSource: logSourceName, | 33 logSource: logSourceName, |
| 32 } | 34 } |
| 33 } | 35 } |
| 34 | 36 |
| 35 // TODO(mcgreevy): support bundling log requests. | 37 // retryError is an error for an operation that should be retried. |
| 38 type retryError struct { |
| 39 » error |
| 40 } |
| 36 | 41 |
| 37 // LogSync synchronously logs events to the eventlog service. | 42 // LogSync synchronously logs events to the eventlog service. |
| 38 // The EventTime in each event must have been obtained from time.Now. | 43 // The EventTime in each event must have been obtained from time.Now. |
| 39 func (c *Client) LogSync(ctx context.Context, events []*logpb.LogRequestLite_Log
EventLite) error { | 44 // The returned error can be supplied to ShouldRetry to determine whether the op
eration should be retried. |
| 45 func (l *Logger) LogSync(ctx context.Context, events ...*logpb.LogRequestLite_Lo
gEventLite) error { |
| 40 // TODO(mcgreevy): consider supporting custom clocks. | 46 // TODO(mcgreevy): consider supporting custom clocks. |
| 41 log := &logpb.LogRequestLite{ | 47 log := &logpb.LogRequestLite{ |
| 42 RequestTimeMs: proto.Int64(time.Now().UnixNano() / 1e6), | 48 RequestTimeMs: proto.Int64(time.Now().UnixNano() / 1e6), |
| 43 » » LogSourceName: &c.logSource, | 49 » » LogSourceName: &l.logSource, |
| 44 LogEvent: events, | 50 LogEvent: events, |
| 45 } | 51 } |
| 46 | 52 |
| 47 buf, err := proto.Marshal(log) | 53 buf, err := proto.Marshal(log) |
| 48 if err != nil { | 54 if err != nil { |
| 49 return err | 55 return err |
| 50 } | 56 } |
| 51 | 57 |
| 52 » req, err := http.NewRequest("POST", c.serverAddr, bytes.NewReader(buf)) | 58 » req, err := http.NewRequest("POST", l.serverAddr, bytes.NewReader(buf)) |
| 53 if err != nil { | 59 if err != nil { |
| 54 return err | 60 return err |
| 55 } | 61 } |
| 56 | 62 |
| 57 req.Header.Set("Content-Type", "application/octet-stream") | 63 req.Header.Set("Content-Type", "application/octet-stream") |
| 58 req = req.WithContext(ctx) | 64 req = req.WithContext(ctx) |
| 59 » resp, err := c.HTTPClient.Do(req) | 65 » resp, err := l.HTTPClient.Do(req) |
| 60 if err != nil { | 66 if err != nil { |
| 61 » » return err | 67 » » return retryError{err} |
| 62 } | 68 } |
| 63 » resp.Body.Close() | 69 » defer resp.Body.Close() |
| 64 | 70 |
| 65 » return nil | 71 » sc := resp.StatusCode |
| 72 » switch { |
| 73 » case sc == 200: |
| 74 » » return nil |
| 75 » case sc == 400: |
| 76 » » // malformed; this should not happen. |
| 77 » » return fmt.Errorf("eventlog: malformed request: %v", req) |
| 78 » case sc == 401: |
| 79 » » return retryError{fmt.Errorf("eventlog: auth failed: %v", req)} |
| 80 » case sc >= 500 && sc < 600: |
| 81 » » return retryError{fmt.Errorf("eventlog: req failed: %v", req)} |
| 82 » default: |
| 83 » » return fmt.Errorf("eventlog: unexpected status code: %v for requ
est: %v", sc, req) |
| 84 » } |
| 66 } | 85 } |
| 67 | 86 |
| 68 // Close flushes any pending logs and releases any resources held by the client. | 87 // ShouldRetry reports whether a LogSync call (which returned err) should be ret
ried. |
| 69 // Close should be called when the client is no longer needed. | 88 func ShouldRetry(err error) bool { |
| 70 func (c *Client) Close() error { | 89 » _, ok := err.(retryError) |
| 71 » // We will need this later, but for now it's a no-op. | 90 » return ok |
| 72 » return nil | |
| 73 } | 91 } |
| OLD | NEW |