forked from elastic/apm-agent-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
context.go
181 lines (164 loc) · 5.2 KB
/
context.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
173
174
175
176
177
178
179
180
181
package elasticapm
import (
"fmt"
"net/http"
"strings"
"github.com/elastic/apm-agent-go/internal/apmhttputil"
"github.com/elastic/apm-agent-go/model"
)
// Context provides methods for setting transaction and error context.
type Context struct {
model model.Context
request model.Request
requestBody model.RequestBody
requestHeaders model.RequestHeaders
requestSocket model.RequestSocket
response model.Response
responseHeaders model.ResponseHeaders
user model.User
captureBodyMask CaptureBodyMode
}
func (c *Context) build() *model.Context {
switch {
case c.model.Request != nil:
case c.model.Response != nil:
case c.model.User != nil:
case len(c.model.Custom) != 0:
case len(c.model.Tags) != 0:
default:
return nil
}
return &c.model
}
func (c *Context) reset() {
modelContext := model.Context{
// TODO(axw) reuse space for tags
Custom: c.model.Custom[:0],
}
*c = Context{
model: modelContext,
captureBodyMask: c.captureBodyMask,
}
}
// SetCustom sets a custom context key/value pair. If the key is invalid
// (contains '.', '*', or '"'), the call is a no-op. The value must be
// JSON-encodable.
//
// If value implements interface{AppendJSON([]byte) []byte}, that will be
// used to encode the value. Otherwise, value will be encoded using
// json.Marshal. As a special case, values of type map[string]interface{}
// will be traversed and values encoded according to the same rules.
func (c *Context) SetCustom(key string, value interface{}) {
if !validTagKey(key) {
return
}
c.model.Custom.Set(key, value)
}
// SetTag sets a tag in the context. If the key is invalid
// (contains '.', '*', or '"'), the call is a no-op.
func (c *Context) SetTag(key, value string) {
if !validTagKey(key) {
return
}
value = truncateString(value)
if c.model.Tags == nil {
c.model.Tags = map[string]string{key: value}
} else {
c.model.Tags[key] = value
}
}
// SetHTTPRequest sets details of the HTTP request in the context.
//
// This function may be used for either clients or servers. For
// server-side requests, various proxy forwarding headers are taken
// into account to reconstruct the URL, and determining the client
// address.
//
// If the request URL contains user info, it will be removed and
// excluded from the URL's "full" field.
//
// If the request contains HTTP Basic Authentication, the username
// from that will be recorded in the context. Otherwise, if the
// request contains user info in the URL (i.e. a client-side URL),
// that will be used.
func (c *Context) SetHTTPRequest(req *http.Request) {
// Special cases to avoid calling into fmt.Sprintf in most cases.
var httpVersion string
switch {
case req.ProtoMajor == 1 && req.ProtoMinor == 1:
httpVersion = "1.1"
case req.ProtoMajor == 2 && req.ProtoMinor == 0:
httpVersion = "2.0"
default:
httpVersion = fmt.Sprintf("%d.%d", req.ProtoMajor, req.ProtoMinor)
}
var forwarded *apmhttputil.ForwardedHeader
if fwd := req.Header.Get("Forwarded"); fwd != "" {
parsed := apmhttputil.ParseForwarded(fwd)
forwarded = &parsed
}
c.request = model.Request{
Body: c.request.Body,
URL: apmhttputil.RequestURL(req, forwarded),
Method: truncateString(req.Method),
HTTPVersion: httpVersion,
Cookies: req.Cookies(),
}
c.model.Request = &c.request
c.requestHeaders = model.RequestHeaders{
ContentType: req.Header.Get("Content-Type"),
Cookie: strings.Join(req.Header["Cookie"], ";"),
UserAgent: req.UserAgent(),
}
if c.requestHeaders != (model.RequestHeaders{}) {
c.request.Headers = &c.requestHeaders
}
c.requestSocket = model.RequestSocket{
Encrypted: req.TLS != nil,
RemoteAddress: apmhttputil.RemoteAddr(req, forwarded),
}
if c.requestSocket != (model.RequestSocket{}) {
c.request.Socket = &c.requestSocket
}
username, _, ok := req.BasicAuth()
if !ok && req.URL.User != nil {
username = req.URL.User.Username()
}
c.user.Username = truncateString(username)
if c.user.Username != "" {
c.model.User = &c.user
}
}
// SetHTTPRequestBody sets the request body in context given a (possibly nil)
// BodyCapturer returned by Tracer.CaptureHTTPRequestBody.
func (c *Context) SetHTTPRequestBody(bc *BodyCapturer) {
if bc == nil || bc.captureBody&c.captureBodyMask == 0 {
return
}
if bc.setContext(&c.requestBody) {
c.request.Body = &c.requestBody
}
}
// SetHTTPResponseHeaders sets the HTTP response headers in the context.
func (c *Context) SetHTTPResponseHeaders(h http.Header) {
c.responseHeaders.ContentType = h.Get("Content-Type")
if c.responseHeaders.ContentType != "" {
c.response.Headers = &c.responseHeaders
c.model.Response = &c.response
}
}
// SetHTTPResponseHeadersSent records whether or not response were sent.
func (c *Context) SetHTTPResponseHeadersSent(headersSent bool) {
c.response.HeadersSent = &headersSent
c.model.Response = &c.response
}
// SetHTTPResponseFinished records whether or not the response was finished.
func (c *Context) SetHTTPResponseFinished(finished bool) {
c.response.Finished = &finished
c.model.Response = &c.response
}
// SetHTTPStatusCode records the HTTP response status code.
func (c *Context) SetHTTPStatusCode(statusCode int) {
c.response.StatusCode = statusCode
c.model.Response = &c.response
}