forked from rollbar/rollbar-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
base_transport.go
149 lines (129 loc) · 4.62 KB
/
base_transport.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package rollbar
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"sync"
"time"
)
type baseTransport struct {
// Rollbar access token used by this transport for communication with the Rollbar API.
Token string
// Endpoint to post items to.
Endpoint string
// Logger used to report errors when sending data to Rollbar, e.g.
// when the Rollbar API returns 409 Too Many Requests response.
// If not set, the client will use the standard log.Printf by default.
Logger ClientLogger
// RetryAttempts is how often to attempt to resend an item when a temporary network error occurs
// This defaults to DefaultRetryAttempts
// Set this value to 0 if you do not want retries to happen
RetryAttempts int
// PrintPayloadOnError is whether or not to output the payload to the set logger or to stderr if
// an error occurs during transport to the Rollbar API.
PrintPayloadOnError bool
// ItemsPerMinute has the max number of items to send in a given minute
ItemsPerMinute int
// custom http client (http.DefaultClient used by default)
httpClient *http.Client
perMinCounter int
startTime time.Time
lock sync.RWMutex
}
// SetToken updates the token to use for future API requests.
func (t *baseTransport) SetToken(token string) {
t.Token = token
}
// SetEndpoint updates the API endpoint to send items to.
func (t *baseTransport) SetEndpoint(endpoint string) {
t.Endpoint = endpoint
}
// SetItemsPerMinute sets the max number of items to send in a given minute
func (t *baseTransport) SetItemsPerMinute(itemsPerMinute int) {
t.lock.Lock()
t.ItemsPerMinute = itemsPerMinute
t.lock.Unlock()
}
// SetLogger updates the logger that this transport uses for reporting errors that occur while
// processing items.
func (t *baseTransport) SetLogger(logger ClientLogger) {
t.lock.Lock()
defer t.lock.Unlock()
t.Logger = logger
}
// SetRetryAttempts is how often to attempt to resend an item when a temporary network error occurs
// This defaults to DefaultRetryAttempts
// Set this value to 0 if you do not want retries to happen
func (t *baseTransport) SetRetryAttempts(retryAttempts int) {
t.RetryAttempts = retryAttempts
}
// SetPrintPayloadOnError is whether or not to output the payload to stderr if an error occurs during
// transport to the Rollbar API.
func (t *baseTransport) SetPrintPayloadOnError(printPayloadOnError bool) {
t.PrintPayloadOnError = printPayloadOnError
}
// SetHTTPClient sets custom http client. http.DefaultClient is used by default
func (t *baseTransport) SetHTTPClient(c *http.Client) {
t.httpClient = c
}
// getHTTPClient returns either custom client (if set) or http.DefaultClient
func (t *baseTransport) getHTTPClient() *http.Client {
if t.httpClient != nil {
return t.httpClient
}
return http.DefaultClient
}
func (t *baseTransport) clientPost(body io.Reader) (resp *http.Response, err error) {
req, err := http.NewRequest("POST", t.Endpoint, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Rollbar-Access-Token", t.Token)
return t.getHTTPClient().Do(req)
}
// post returns an error which indicates the type of error that occurred while attempting to
// send the body input to the endpoint given, or nil if no error occurred. If error is not nil, the
// boolean return parameter indicates whether the error is temporary or not. If this boolean return
// value is true then the caller could call this function again with the same input and possibly
// see a non-error response.
func (t *baseTransport) post(body map[string]interface{}) (bool, error) {
t.lock.RLock()
defer t.lock.RUnlock()
if len(t.Token) == 0 {
rollbarError(t.Logger, "empty token")
return false, nil
}
jsonBody, err := json.Marshal(body)
if err != nil {
rollbarError(t.Logger, "failed to encode payload: %s", err.Error())
return false, err
}
resp, err := t.clientPost(bytes.NewReader(jsonBody))
if err != nil {
rollbarError(t.Logger, "POST failed: %s", err.Error())
return isTemporary(err), err
}
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
if resp.StatusCode != 200 {
rollbarError(t.Logger, "received response: %s", resp.Status)
// http.StatusTooManyRequests is only defined in Go 1.6+ so we use 429 directly
isRateLimit := resp.StatusCode == 429
return isRateLimit, ErrHTTPError(resp.StatusCode)
}
return false, nil
}
func (t *baseTransport) shouldSend() bool {
t.lock.RLock()
defer t.lock.RUnlock()
if t.ItemsPerMinute > 0 && t.perMinCounter >= t.ItemsPerMinute {
rollbarError(t.Logger, fmt.Sprintf("item per minute limit reached: %d occurences, "+
"ignoring errors until timeout", t.perMinCounter))
return false
}
return true
}