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 }