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  }