1// +build go1.13 2 3// Copyright (c) Microsoft Corporation. All rights reserved. 4// Licensed under the MIT License. 5 6package azcore 7 8import ( 9 "bytes" 10 "fmt" 11 "strings" 12 "time" 13 14 "github.com/Azure/azure-sdk-for-go/sdk/internal/runtime" 15) 16 17// LogOptions configures the logging policy's behavior. 18type LogOptions struct { 19 // IncludeBody indicates if request and response bodies should be included in logging. 20 // The default value is false. 21 // NOTE: enabling this can lead to disclosure of sensitive information, use with care. 22 IncludeBody bool 23} 24 25type logPolicy struct { 26 options LogOptions 27} 28 29// NewLogPolicy creates a RequestLogPolicy object configured using the specified options. 30// Pass nil to accept the default values; this is the same as passing a zero-value options. 31func NewLogPolicy(o *LogOptions) Policy { 32 if o == nil { 33 o = &LogOptions{} 34 } 35 return &logPolicy{options: *o} 36} 37 38// logPolicyOpValues is the struct containing the per-operation values 39type logPolicyOpValues struct { 40 try int32 41 start time.Time 42} 43 44func (p *logPolicy) Do(req *Request) (*Response, error) { 45 // Get the per-operation values. These are saved in the Message's map so that they persist across each retry calling into this policy object. 46 var opValues logPolicyOpValues 47 if req.OperationValue(&opValues); opValues.start.IsZero() { 48 opValues.start = time.Now() // If this is the 1st try, record this operation's start time 49 } 50 opValues.try++ // The first try is #1 (not #0) 51 req.SetOperationValue(opValues) 52 53 // Log the outgoing request as informational 54 if Log().Should(LogRequest) { 55 b := &bytes.Buffer{} 56 fmt.Fprintf(b, "==> OUTGOING REQUEST (Try=%d)\n", opValues.try) 57 writeRequestWithResponse(b, req, nil, nil) 58 var err error 59 if p.options.IncludeBody { 60 err = req.writeBody(b) 61 } 62 Log().Write(LogRequest, b.String()) 63 if err != nil { 64 return nil, err 65 } 66 } 67 68 // Set the time for this particular retry operation and then Do the operation. 69 tryStart := time.Now() 70 response, err := req.Next() // Make the request 71 tryEnd := time.Now() 72 tryDuration := tryEnd.Sub(tryStart) 73 opDuration := tryEnd.Sub(opValues.start) 74 75 if Log().Should(LogResponse) { 76 // We're going to log this; build the string to log 77 b := &bytes.Buffer{} 78 fmt.Fprintf(b, "==> REQUEST/RESPONSE (Try=%d/%v, OpTime=%v) -- ", opValues.try, tryDuration, opDuration) 79 if err != nil { // This HTTP request did not get a response from the service 80 fmt.Fprint(b, "REQUEST ERROR\n") 81 } else { 82 fmt.Fprint(b, "RESPONSE RECEIVED\n") 83 } 84 85 writeRequestWithResponse(b, req, response, err) 86 if err != nil { 87 // skip frames runtime.Callers() and runtime.StackTrace() 88 b.WriteString(runtime.StackTrace(2, StackFrameCount)) 89 } else if p.options.IncludeBody { 90 err = response.writeBody(b) 91 } 92 Log().Write(LogResponse, b.String()) 93 } 94 return response, err 95} 96 97// returns true if the request/response body should be logged. 98// this is determined by looking at the content-type header value. 99func shouldLogBody(b *bytes.Buffer, contentType string) bool { 100 if strings.HasPrefix(contentType, "text") || 101 strings.HasSuffix(contentType, "json") || 102 strings.HasSuffix(contentType, "xml") { 103 return true 104 } 105 fmt.Fprintf(b, " Skip logging body for %s\n", contentType) 106 return false 107} 108