-
Notifications
You must be signed in to change notification settings - Fork 351
/
sender.go
162 lines (142 loc) · 4.57 KB
/
sender.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
150
151
152
153
154
155
156
157
158
159
160
161
162
package stats
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
"github.com/hashicorp/go-retryablehttp"
"github.com/treeverse/lakefs/pkg/logging"
)
const defaultRetryMax = 2
var (
ErrSendError = errors.New("stats: send error")
ErrNoInstallationID = fmt.Errorf("installation ID is missing: %w", ErrSendError)
)
type Sender interface {
SendEvent(ctx context.Context, event *InputEvent) error
UpdateMetadata(ctx context.Context, m Metadata) error
UpdateCommPrefs(ctx context.Context, commPrefs *CommPrefsData) error
}
type TimeFn func() time.Time
type HTTPSender struct {
addr string
client *http.Client
}
type LoggerAdapter struct {
logging.Logger
}
func (l *LoggerAdapter) Printf(msg string, args ...interface{}) {
l.Tracef(msg, args...)
}
func NewHTTPSender(addr string, log logging.Logger) *HTTPSender {
retryClient := retryablehttp.NewClient()
retryClient.Logger = &LoggerAdapter{Logger: log}
retryClient.RetryMax = defaultRetryMax
return &HTTPSender{
addr: addr,
client: retryClient.StandardClient(),
}
}
// IsSuccessStatusCode returns true for status code 2xx
func IsSuccessStatusCode(statusCode int) bool {
return statusCode >= http.StatusOK && statusCode < http.StatusMultipleChoices
}
func (s *HTTPSender) UpdateMetadata(ctx context.Context, m Metadata) error {
if len(m.InstallationID) == 0 {
return ErrNoInstallationID
}
serialized, err := json.Marshal(m)
if err != nil {
return fmt.Errorf("failed to serialize account metadata: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, s.addr+"/installation", bytes.NewBuffer(serialized))
if err != nil {
return fmt.Errorf("could not create HTTP request: %s: %w", err, ErrSendError)
}
res, err := s.client.Do(req)
if err != nil {
return fmt.Errorf("could not make HTTP request: %s: %w", err, ErrSendError)
}
defer func() { _ = res.Body.Close() }()
if !IsSuccessStatusCode(res.StatusCode) {
return fmt.Errorf("request failed - status=%d: %w", res.StatusCode, ErrSendError)
}
return nil
}
func (s *HTTPSender) SendEvent(ctx context.Context, event *InputEvent) error {
serialized, err := json.Marshal(event)
if err != nil {
return fmt.Errorf("could not serialize event: %s: %w", err, ErrSendError)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, s.addr+"/events", bytes.NewBuffer(serialized))
if err != nil {
return fmt.Errorf("could not create HTTP request: %s: %w", err, ErrSendError)
}
res, err := s.client.Do(req)
if err != nil {
return fmt.Errorf("could not make HTTP request: %s: %w", err, ErrSendError)
}
defer func() { _ = res.Body.Close() }()
if !IsSuccessStatusCode(res.StatusCode) {
return fmt.Errorf("request failed - status=%d: %w", res.StatusCode, ErrSendError)
}
return nil
}
func (s *HTTPSender) UpdateCommPrefs(ctx context.Context, commPrefs *CommPrefsData) error {
serialized, err := json.Marshal(commPrefs)
if err != nil {
return fmt.Errorf("could not serialize comm prefs: %s: %w", err, ErrSendError)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, s.addr+"/comm_prefs", bytes.NewBuffer(serialized))
if err != nil {
return fmt.Errorf("could not create HTTP request: %s: %w", err, ErrSendError)
}
res, err := s.client.Do(req)
if err != nil {
return fmt.Errorf("could not make HTTP request: %s: %w", err, ErrSendError)
}
defer func() { _ = res.Body.Close() }()
if !IsSuccessStatusCode(res.StatusCode) {
return fmt.Errorf("request failed - status=%d: %w", res.StatusCode, ErrSendError)
}
return nil
}
type dummySender struct {
logging.Logger
}
func (s *dummySender) SendEvent(_ context.Context, event *InputEvent) error {
if s.Logger == nil || !s.IsTracing() {
return nil
}
s.WithFields(logging.Fields{
"installation_id": event.InstallationID,
"process_id": event.ProcessID,
"event_time": event.Time,
"metrics": fmt.Sprintf("%+v", event.Metrics),
}).Trace("dummy sender received metrics")
return nil
}
func (s *dummySender) UpdateMetadata(_ context.Context, m Metadata) error {
if s.Logger == nil || !s.IsTracing() {
return nil
}
s.WithFields(logging.Fields{
"metadata": fmt.Sprintf("%+v", m),
}).Trace("dummy sender received metadata")
return nil
}
func (s *dummySender) UpdateCommPrefs(_ context.Context, commPrefs *CommPrefsData) error {
if s.Logger == nil || !s.IsTracing() {
return nil
}
s.WithFields(logging.Fields{
"email": commPrefs.Email,
"featureUpdates": commPrefs.FeatureUpdates,
"securityUpdates": commPrefs.SecurityUpdates,
"installationID": commPrefs.InstallationID,
}).Trace("dummy sender received comm prefs")
return nil
}