forked from rollbar/rollbar-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
telemetry.go
172 lines (149 loc) · 4.46 KB
/
telemetry.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
163
164
165
166
167
168
169
170
171
172
package rollbar
import (
"fmt"
"io"
"log"
"net/http"
"os"
"regexp"
"time"
)
const TelemetryQueueSize = 50
// Telemetry struct contains writer (for logs) and round tripper (for http client) and enables to queue the events
type Telemetry struct {
Logger struct {
Writer io.Writer
}
Network struct {
Proxied http.RoundTripper
ScrubHeaders *regexp.Regexp
enableReqHeaders bool
enableResHeaders bool
}
Queue *Queue
}
// Write is the writer for telemetry logs
func (t *Telemetry) Write(p []byte) (int, error) {
telemetryData := t.populateLoggerBody(p)
t.Queue.Push(telemetryData)
return t.Logger.Writer.Write(p)
}
// RoundTrip implements RoundTrip in http.RoundTripper
func (t *Telemetry) RoundTrip(req *http.Request) (res *http.Response, e error) {
// Send the request, get the response (or the error)
res, e = t.Network.Proxied.RoundTrip(req)
if e != nil {
fmt.Printf("Error: %v", e)
}
telemetryData := t.populateTransporterBody(req, res)
t.Queue.Push(telemetryData)
return
}
func (t *Telemetry) populateLoggerBody(p []byte) map[string]interface{} {
var data = map[string]interface{}{}
message := map[string]interface{}{"message": string(p)}
data["body"] = message
data["source"] = "client"
data["timestamp_ms"] = time.Now().UnixNano() / int64(time.Millisecond)
data["type"] = "log"
data["level"] = "log"
return data
}
func (t *Telemetry) populateTransporterBody(req *http.Request, res *http.Response) map[string]interface{} {
var data = map[string]interface{}{}
var dataBody = map[string]interface{}{}
dataBody["status_code"] = nil
data["level"] = "info"
if res != nil {
dataBody["status_code"] = res.StatusCode
if res.StatusCode >= http.StatusInternalServerError {
data["level"] = "critical"
} else if res.StatusCode >= http.StatusBadRequest {
data["level"] = "error"
}
if t.Network.enableResHeaders {
var dataHeaders = map[string][]string{}
for k, v := range res.Header {
dataHeaders[k] = v
}
filteredDataHeaders := filterFlatten(t.Network.ScrubHeaders, dataHeaders, nil)
response := map[string]interface{}{"headers": filteredDataHeaders}
dataBody["response"] = response
}
}
dataBody["url"] = req.URL.Scheme + "://" + req.Host + req.URL.Path
dataBody["method"] = req.Method
dataBody["subtype"] = "http"
if t.Network.enableReqHeaders {
var dataHeaders = map[string][]string{}
for k, v := range req.Header {
dataHeaders[k] = v
}
filteredDataHeaders := filterFlatten(t.Network.ScrubHeaders, dataHeaders, nil)
dataBody["request_headers"] = filteredDataHeaders
}
data["body"] = dataBody
data["source"] = "client"
data["timestamp_ms"] = time.Now().UnixNano() / int64(time.Millisecond)
data["type"] = "network"
return data
}
// GetQueueItems gets all the items from the queue
func (t *Telemetry) GetQueueItems() []interface{} {
return t.Queue.Items()
}
// OptionFunc is the pointer to the optional parameter function
type OptionFunc func(*Telemetry)
// EnableNetworkTelemetry enables the network telemetry
// it wraps up the client for telemetry
// http.DefaultClient can also be passed by the reference
func EnableNetworkTelemetry(httpClient *http.Client) OptionFunc {
return func(f *Telemetry) {
if httpClient.Transport == nil {
f.Network.Proxied = http.DefaultTransport
} else {
f.Network.Proxied = httpClient.Transport
}
httpClient.Transport = f
}
}
// EnableNetworkTelemetryRequestHeaders enables telemetry request headers
func EnableNetworkTelemetryRequestHeaders() OptionFunc {
return func(f *Telemetry) {
f.Network.enableReqHeaders = true
}
}
// EnableNetworkTelemetryResponseHeaders enables telemetry response headers
func EnableNetworkTelemetryResponseHeaders() OptionFunc {
return func(f *Telemetry) {
f.Network.enableResHeaders = true
}
}
// SetCustomQueueSize initializes the queue with a custom size
func SetCustomQueueSize(size int) OptionFunc {
return func(f *Telemetry) {
f.Queue = NewQueue(size)
}
}
// EnableLoggerTelemetry enables logger telemetry
func EnableLoggerTelemetry() OptionFunc {
return func(f *Telemetry) {
f.Logger.Writer = os.Stdout
log.SetOutput(f)
}
}
// NewTelemetry initializes telemetry object with scrubheader
func NewTelemetry(scrubHeaders *regexp.Regexp, options ...OptionFunc) *Telemetry {
res := &Telemetry{
Queue: NewQueue(TelemetryQueueSize),
}
for _, opt := range options {
opt(res)
}
if scrubHeaders == nil {
res.Network.ScrubHeaders = regexp.MustCompile("Authorization")
} else {
res.Network.ScrubHeaders = scrubHeaders
}
return res
}