github.com/waldiirawan/apm-agent-go/v2@v2.2.2/capturebody.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package apm // import "github.com/waldiirawan/apm-agent-go/v2" 19 20 import ( 21 "bytes" 22 "io" 23 "net/http" 24 "net/url" 25 "sync" 26 "unicode/utf8" 27 28 "github.com/waldiirawan/apm-agent-go/v2/internal/apmstrings" 29 "github.com/waldiirawan/apm-agent-go/v2/model" 30 ) 31 32 // CaptureBodyMode holds a value indicating how a tracer should capture 33 // HTTP request bodies: for transactions, for errors, for both, or neither. 34 type CaptureBodyMode int 35 36 const ( 37 // CaptureBodyOff disables capturing of HTTP request bodies. This is 38 // the default mode. 39 CaptureBodyOff CaptureBodyMode = 0 40 41 // CaptureBodyErrors captures HTTP request bodies for only errors. 42 CaptureBodyErrors CaptureBodyMode = 1 43 44 // CaptureBodyTransactions captures HTTP request bodies for only 45 // transactions. 46 CaptureBodyTransactions CaptureBodyMode = 1 << 1 47 48 // CaptureBodyAll captures HTTP request bodies for both transactions 49 // and errors. 50 CaptureBodyAll CaptureBodyMode = CaptureBodyErrors | CaptureBodyTransactions 51 ) 52 53 var bodyCapturerPool = sync.Pool{ 54 New: func() interface{} { 55 return &BodyCapturer{} 56 }, 57 } 58 59 // CaptureHTTPRequestBody replaces req.Body and returns a possibly nil 60 // BodyCapturer which can later be passed to Context.SetHTTPRequestBody 61 // for setting the request body in a transaction or error context. If the 62 // tracer is not configured to capture HTTP request bodies, then req.Body 63 // is left alone and nil is returned. 64 // 65 // This must be called before the request body is read. The BodyCapturer's 66 // Discard method should be called after it is no longer needed, in order 67 // to recycle its memory. 68 func (t *Tracer) CaptureHTTPRequestBody(req *http.Request) *BodyCapturer { 69 if req.Body == nil { 70 return nil 71 } 72 captureBody := t.instrumentationConfig().captureBody 73 if captureBody == CaptureBodyOff { 74 return nil 75 } 76 77 bc := bodyCapturerPool.Get().(*BodyCapturer) 78 bc.captureBody = captureBody 79 bc.request = req 80 bc.originalBody = req.Body 81 bc.buffer.Reset() 82 req.Body = bodyCapturerReadCloser{BodyCapturer: bc} 83 return bc 84 } 85 86 // bodyCapturerReadCloser implements io.ReadCloser using the embedded BodyCapturer. 87 type bodyCapturerReadCloser struct { 88 *BodyCapturer 89 } 90 91 // Close closes the original body. 92 func (bc bodyCapturerReadCloser) Close() error { 93 bc.mu.Lock() 94 defer bc.mu.Unlock() 95 return bc.originalBody.Close() 96 } 97 98 // Read reads from the original body, copying into bc.buffer. 99 func (bc bodyCapturerReadCloser) Read(p []byte) (int, error) { 100 bc.mu.Lock() 101 defer bc.mu.Unlock() 102 n, err := bc.originalBody.Read(p) 103 if n > 0 { 104 bc.buffer.Write(p[:n]) 105 } 106 return n, err 107 } 108 109 // BodyCapturer is returned by Tracer.CaptureHTTPRequestBody to later be 110 // passed to Context.SetHTTPRequestBody. 111 // 112 // Calling Context.SetHTTPRequestBody will reset req.Body to its original 113 // value, and invalidates the BodyCapturer. 114 type BodyCapturer struct { 115 captureBody CaptureBodyMode 116 117 mu sync.RWMutex 118 readbuf [bytes.MinRead]byte 119 buffer limitedBuffer 120 request *http.Request 121 originalBody io.ReadCloser 122 } 123 124 // Discard discards the body capturer: the original request body is 125 // replaced, and the body capturer is returned to a pool for reuse. 126 // The BodyCapturer must not be used after calling this. 127 // 128 // Discard has no effect if bc is nil. 129 func (bc *BodyCapturer) Discard() { 130 if bc == nil { 131 return 132 } 133 bc.mu.Lock() 134 defer bc.mu.Unlock() 135 bc.request.Body = bc.originalBody 136 bodyCapturerPool.Put(bc) 137 } 138 139 func (bc *BodyCapturer) setContext(out *model.RequestBody, req *http.Request) bool { 140 if req != nil && req.PostForm != nil { 141 // We must copy the map in case we need to 142 // sanitize the values. Ideally we should only 143 // copy if sanitization is necessary, but body 144 // capture shouldn't typically be enabled so 145 // we don't currently optimize this. 146 postForm := make(url.Values, len(req.PostForm)) 147 for k, v := range req.PostForm { 148 vcopy := make([]string, len(v)) 149 for i := range vcopy { 150 vcopy[i] = truncateString(v[i]) 151 } 152 postForm[k] = vcopy 153 } 154 out.Form = postForm 155 return true 156 } 157 158 body, n := bc.getBufferTruncated() 159 if n == stringLengthLimit { 160 // There is at least enough data in the buffer 161 // to hit the string length limit, so we don't 162 // need to read from bc.originalBody as well. 163 out.Raw = body 164 return true 165 } 166 167 bc.mu.Lock() 168 defer bc.mu.Unlock() 169 170 // Read the remaining body, limiting to the maximum number of bytes 171 // that could make up the truncation limit. We ignore any errors here, 172 // and just return whatever we can. 173 rem := utf8.UTFMax * (stringLengthLimit - n) 174 for { 175 buf := bc.readbuf[:] 176 if rem < bytes.MinRead { 177 buf = buf[:rem] 178 } 179 n, err := bc.originalBody.Read(buf) 180 if n > 0 { 181 bc.buffer.Write(buf[:n]) 182 rem -= n 183 } 184 if rem == 0 || err != nil { 185 break 186 } 187 } 188 body, _ = bc.getBufferTruncatedLocked() 189 out.Raw = body 190 return body != "" 191 } 192 193 func (bc *BodyCapturer) getBufferTruncated() (string, int) { 194 bc.mu.RLock() 195 defer bc.mu.RUnlock() 196 return bc.getBufferTruncatedLocked() 197 } 198 199 func (bc *BodyCapturer) getBufferTruncatedLocked() (string, int) { 200 return apmstrings.Truncate(bc.buffer.String(), stringLengthLimit) 201 } 202 203 type limitedBuffer struct { 204 bytes.Buffer 205 } 206 207 func (b *limitedBuffer) Write(p []byte) (n int, err error) { 208 rem := (stringLengthLimit * utf8.UTFMax) - b.Len() 209 n = len(p) 210 if n > rem { 211 p = p[:rem] 212 } 213 written, err := b.Buffer.Write(p) 214 if err != nil { 215 n = written 216 } 217 return n, err 218 }