github.com/ravendb/ravendb-go-client@v0.0.0-20240229102137-4474ee7aa0fa/examples/log.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"net/http/httputil"
    11  	"os"
    12  	"path/filepath"
    13  	"runtime/pprof"
    14  	"sync"
    15  	"sync/atomic"
    16  
    17  	"github.com/ravendb/ravendb-go-client"
    18  )
    19  
    20  var (
    21  	// if true, printing of failed reqeusts is delayed until PrintFailedRequests
    22  	// is called.
    23  	// can be enabled by setting LOG_FAILED_HTTP_REQUESTS_DELAYED env variable to "true"
    24  	logFailedRequestsDelayed = false
    25  
    26  	// if true, we log RavenDB's output to stdout
    27  	// can be enabled by setting LOG_RAVEN_SERVER env variable to "true"
    28  	ravenServerVerbose = false
    29  
    30  	// if true, logs summary of all HTTP requests i.e. "GET /foo" to stdout
    31  	logRequestSummary = false
    32  
    33  	// if true, logs request and response of failed http requests (i.e. those returning
    34  	// status code >= 400) to stdout
    35  	logFailedRequests = false
    36  
    37  	// httpLoggerWriter is where we log all http requests and responses
    38  	httpLoggerWriter io.WriteCloser
    39  	// httpFailedRequestsLogger is where we log failed http requests.
    40  	// it's either os.Stdout for immediate logging or bytes.Buffer for delayed logging
    41  	httpFailedRequestsLogger io.Writer
    42  	// httpRequestCount numbers http requests which helps to match http
    43  	// traffic from java client with go client
    44  	httpRequestCount int32
    45  
    46  	errLogDisabled int32 // atomic, if > 0, don't log error requests
    47  
    48  	muLog sync.Mutex
    49  )
    50  
    51  func logsLock() {
    52  	muLog.Lock()
    53  }
    54  
    55  func logsUnlock() {
    56  	muLog.Unlock()
    57  }
    58  
    59  type loggingTransport struct {
    60  	originalTransport http.RoundTripper
    61  }
    62  
    63  func (t *loggingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
    64  	atomic.AddInt32(&httpRequestCount, 1)
    65  
    66  	maybeLogRequestSummary(req)
    67  	maybeCaptureRequestBody(req)
    68  	rsp, err := t.originalTransport.RoundTrip(req)
    69  	maybeLogFailedResponse(req, rsp, err)
    70  	maybeLogHTTPRequest(req, rsp, err)
    71  	return rsp, err
    72  }
    73  
    74  func httpClientProcessor(c *http.Client) {
    75  	t := c.Transport
    76  	c.Transport = &loggingTransport{
    77  		originalTransport: t,
    78  	}
    79  }
    80  
    81  func setupLogging() {
    82  	logsLock()
    83  	defer logsUnlock()
    84  
    85  	if verboseLogging {
    86  		logFailedRequestsDelayed = true
    87  		logRequestSummary = true
    88  		logFailedRequests = true
    89  		// logallRequests has to be set explicitly
    90  		// logAllRequests = true
    91  	}
    92  
    93  	ravendb.HTTPClientPostProcessor = httpClientProcessor
    94  
    95  	httpLoggerWriter = nil
    96  	if logAllRequests {
    97  		var err error
    98  		path := logAllRequestsPath
    99  		f, err := os.Create(path)
   100  		if err != nil {
   101  			fmt.Printf("os.Create('%s') failed with %s\n", path, err)
   102  		} else {
   103  			fmt.Printf("Logging HTTP traffic to %s\n", path)
   104  			httpLoggerWriter = f
   105  		}
   106  	}
   107  
   108  	httpFailedRequestsLogger = nil
   109  	if logFailedRequests {
   110  		if logFailedRequestsDelayed {
   111  			httpFailedRequestsLogger = bytes.NewBuffer(nil)
   112  		} else {
   113  			httpFailedRequestsLogger = os.Stdout
   114  		}
   115  	}
   116  }
   117  
   118  func finishLogging() {
   119  	logsLock()
   120  	defer logsUnlock()
   121  	w := httpLoggerWriter
   122  	if w != nil {
   123  		w.Close()
   124  		httpLoggerWriter = nil
   125  	}
   126  }
   127  
   128  func isErrLoggingDisabled() bool {
   129  	n := atomic.LoadInt32(&errLogDisabled)
   130  	return n > 0
   131  }
   132  
   133  // for temporarily disabling logging of failed requests (if a given
   134  // test is known to issue failing requests)
   135  // usage: defer disableLogFailedRequests()()
   136  // or:
   137  // restorer := disableLogFailedRequests()
   138  // ...
   139  // restorer()
   140  // this is not perfect in parallel tests because (it might over-disable)
   141  // but we're not doing parallel tests
   142  func disableLogFailedRequests() func() {
   143  	atomic.AddInt32(&errLogDisabled, 1)
   144  	return func() {
   145  		atomic.AddInt32(&errLogDisabled, -1)
   146  	}
   147  }
   148  
   149  // returns copy of resp.Body but also makes it available for subsequent reads
   150  func getCopyOfResponseBody(resp *http.Response) ([]byte, error) {
   151  	if resp == nil {
   152  		return nil, nil
   153  	}
   154  	d, err := ioutil.ReadAll(resp.Body)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  	resp.Body = ioutil.NopCloser(bytes.NewBuffer(d))
   159  	return d, nil
   160  }
   161  
   162  func logRequestAndResponseToWriter(w io.Writer, req *http.Request, rsp *http.Response, reqErr error) {
   163  	n := atomic.LoadInt32(&httpRequestCount)
   164  
   165  	fmt.Fprintf(w, "=========== %d:\n", n)
   166  	if reqErr != nil {
   167  		fmt.Fprintf(w, "%s\n", reqErr)
   168  	}
   169  
   170  	d, err := httputil.DumpRequest(req, false)
   171  	if err == nil {
   172  		w.Write(d)
   173  	}
   174  
   175  	if req.Body != nil {
   176  		if cr, ok := req.Body.(*CapturingReadCloser); ok {
   177  			body := cr.capturedData.Bytes()
   178  			if len(body) > 0 {
   179  				fmt.Fprintf(w, "Request body %d bytes:\n%s\n", len(body), maybePrettyPrintJSON(body))
   180  			}
   181  		} else {
   182  			fmt.Fprint(w, "Can't get request body\n")
   183  		}
   184  	}
   185  
   186  	if reqErr != nil {
   187  		return
   188  	}
   189  
   190  	if rsp == nil {
   191  		fmt.Fprint(w, "No response\n")
   192  		return
   193  	}
   194  	fmt.Fprint(w, "--------\n")
   195  	d, err = httputil.DumpResponse(rsp, false)
   196  	if err == nil {
   197  		w.Write(d)
   198  	}
   199  	if d, err := getCopyOfResponseBody(rsp); err != nil {
   200  		fmt.Fprintf(w, "Failed to read response body. Error: '%s'\n", err)
   201  	} else {
   202  		if len(d) > 0 {
   203  			fmt.Fprintf(w, "Response body %d bytes:\n%s\n", len(d), maybePrettyPrintJSON(d))
   204  		}
   205  	}
   206  }
   207  
   208  func maybePrintFailedRequestsLog() {
   209  	logsLock()
   210  	defer logsUnlock()
   211  	if logFailedRequests && logFailedRequestsDelayed {
   212  		buf := httpFailedRequestsLogger.(*bytes.Buffer)
   213  		os.Stdout.Write(buf.Bytes())
   214  		buf.Reset()
   215  	}
   216  }
   217  
   218  func maybeLogHTTPRequest(req *http.Request, rsp *http.Response, err error) {
   219  	logsLock()
   220  	defer logsUnlock()
   221  
   222  	if httpLoggerWriter == nil {
   223  		return
   224  	}
   225  	logRequestAndResponseToWriter(httpLoggerWriter, req, rsp, err)
   226  }
   227  
   228  func maybeLogFailedResponse(req *http.Request, rsp *http.Response, err error) {
   229  	logsLock()
   230  	defer logsUnlock()
   231  
   232  	if !logFailedRequests || isErrLoggingDisabled() {
   233  		return
   234  	}
   235  	if err == nil && rsp.StatusCode < 400 {
   236  		// not failed
   237  		return
   238  	}
   239  	logRequestAndResponseToWriter(httpFailedRequestsLogger, req, rsp, err)
   240  }
   241  
   242  // to be able to print request body for failed requests, we must replace
   243  // body with one that captures data read from original body.
   244  func maybeCaptureRequestBody(req *http.Request) {
   245  	shouldCapture := (logFailedRequests && !isErrLoggingDisabled()) || (httpLoggerWriter != nil)
   246  	if !shouldCapture {
   247  		return
   248  	}
   249  
   250  	switch req.Method {
   251  	case http.MethodGet, http.MethodHead, "RESET":
   252  		// just in case (probably redundant with req.Bddy != nil check)
   253  		return
   254  	}
   255  	if req.Body != nil {
   256  		req.Body = NewCapturingReadCloser(req.Body)
   257  	}
   258  }
   259  
   260  func maybeLogRequestSummary(req *http.Request) {
   261  	if !logRequestSummary {
   262  		return
   263  	}
   264  	method := req.Method
   265  	uri := req.URL.String()
   266  	fmt.Printf("%s %s\n", method, uri)
   267  }
   268  
   269  // This helps debugging leaking gorutines by dumping stack traces
   270  // of all goroutines to a file
   271  func logGoroutines(file string) {
   272  	if file == "" {
   273  		file = "goroutines.txt"
   274  	}
   275  	path := filepath.Join("logs", file)
   276  	dir := filepath.Dir(path)
   277  	err := os.MkdirAll(dir, 0755)
   278  	if err != nil {
   279  		return
   280  	}
   281  	profile := pprof.Lookup("goroutine")
   282  	if profile == nil {
   283  		return
   284  	}
   285  
   286  	f, err := os.Create(path)
   287  	if err != nil {
   288  		return
   289  	}
   290  	defer f.Close()
   291  	profile.WriteTo(f, 2)
   292  }
   293  
   294  // CapturingReadCloser is a reader that captures data that was read from
   295  // underlying reader
   296  type CapturingReadCloser struct {
   297  	tee          io.Reader
   298  	orig         io.ReadCloser
   299  	capturedData bytes.Buffer
   300  	wasClosed    bool
   301  }
   302  
   303  // Read reads data from reader
   304  func (rc *CapturingReadCloser) Read(p []byte) (int, error) {
   305  	if rc.wasClosed {
   306  		panic("reading after being closed")
   307  	}
   308  	return rc.tee.Read(p)
   309  }
   310  
   311  // Close closes a reader
   312  func (rc *CapturingReadCloser) Close() error {
   313  	rc.wasClosed = true
   314  	return rc.orig.Close()
   315  }
   316  
   317  // NewCapturingReadCloser returns a new capturing reader
   318  func NewCapturingReadCloser(orig io.ReadCloser) *CapturingReadCloser {
   319  	res := &CapturingReadCloser{
   320  		orig: orig,
   321  	}
   322  	res.tee = io.TeeReader(orig, &res.capturedData)
   323  	return res
   324  }
   325  
   326  func isUnprintable(c byte) bool {
   327  	if c < 32 {
   328  		// 9 - tab, 10 - LF, 13 - CR
   329  		if c == 9 || c == 10 || c == 13 {
   330  			return false
   331  		}
   332  		return true
   333  	}
   334  	return c >= 127
   335  }
   336  
   337  func isBinaryData(d []byte) bool {
   338  	for _, b := range d {
   339  		if isUnprintable(b) {
   340  			return true
   341  		}
   342  	}
   343  	return false
   344  }
   345  
   346  func asHex(d []byte) ([]byte, bool) {
   347  	if !isBinaryData(d) {
   348  		return d, false
   349  	}
   350  
   351  	// convert unprintable characters to hex
   352  	var res []byte
   353  	for i, c := range d {
   354  		if i > 2048 {
   355  			break
   356  		}
   357  		if isUnprintable(c) {
   358  			s := fmt.Sprintf("x%02x ", c)
   359  			res = append(res, s...)
   360  		} else {
   361  			res = append(res, c)
   362  		}
   363  	}
   364  	return res, true
   365  }
   366  
   367  // if d is a valid json, pretty-print it
   368  // only used for debugging
   369  func maybePrettyPrintJSON(d []byte) []byte {
   370  	if d2, ok := asHex(d); ok {
   371  		return d2
   372  	}
   373  	var m map[string]interface{}
   374  	err := json.Unmarshal(d, &m)
   375  	if err != nil {
   376  		return d
   377  	}
   378  	d2, err := json.MarshalIndent(m, "", "  ")
   379  	if err != nil {
   380  		return d
   381  	}
   382  	return d2
   383  }