Blame vendor/github.com/Azure/azure-storage-blob-go/azblob/zc_policy_request_log.go

Packit 63bb0d
package azblob
Packit 63bb0d
Packit 63bb0d
import (
Packit 63bb0d
	"bytes"
Packit 63bb0d
	"context"
Packit 63bb0d
	"fmt"
Packit 63bb0d
	"net/http"
Packit 63bb0d
	"net/url"
Packit 63bb0d
	"runtime"
Packit 63bb0d
	"strings"
Packit 63bb0d
	"time"
Packit 63bb0d
Packit 63bb0d
	"github.com/Azure/azure-pipeline-go/pipeline"
Packit 63bb0d
)
Packit 63bb0d
Packit 63bb0d
// RequestLogOptions configures the retry policy's behavior.
Packit 63bb0d
type RequestLogOptions struct {
Packit 63bb0d
	// LogWarningIfTryOverThreshold logs a warning if a tried operation takes longer than the specified
Packit 63bb0d
	// duration (-1=no logging; 0=default threshold).
Packit 63bb0d
	LogWarningIfTryOverThreshold time.Duration
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (o RequestLogOptions) defaults() RequestLogOptions {
Packit 63bb0d
	if o.LogWarningIfTryOverThreshold == 0 {
Packit 63bb0d
		// It would be good to relate this to https://azure.microsoft.com/en-us/support/legal/sla/storage/v1_2/
Packit 63bb0d
		// But this monitors the time to get the HTTP response; NOT the time to download the response body.
Packit 63bb0d
		o.LogWarningIfTryOverThreshold = 3 * time.Second // Default to 3 seconds
Packit 63bb0d
	}
Packit 63bb0d
	return o
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// NewRequestLogPolicyFactory creates a RequestLogPolicyFactory object configured using the specified options.
Packit 63bb0d
func NewRequestLogPolicyFactory(o RequestLogOptions) pipeline.Factory {
Packit 63bb0d
	o = o.defaults() // Force defaults to be calculated
Packit 63bb0d
	return pipeline.FactoryFunc(func(next pipeline.Policy, po *pipeline.PolicyOptions) pipeline.PolicyFunc {
Packit 63bb0d
		// These variables are per-policy; shared by multiple calls to Do
Packit 63bb0d
		var try int32
Packit 63bb0d
		operationStart := time.Now() // If this is the 1st try, record the operation state time
Packit 63bb0d
		return func(ctx context.Context, request pipeline.Request) (response pipeline.Response, err error) {
Packit 63bb0d
			try++ // The first try is #1 (not #0)
Packit 63bb0d
Packit 63bb0d
			// Log the outgoing request as informational
Packit 63bb0d
			if po.ShouldLog(pipeline.LogInfo) {
Packit 63bb0d
				b := &bytes.Buffer{}
Packit 63bb0d
				fmt.Fprintf(b, "==> OUTGOING REQUEST (Try=%d)\n", try)
Packit 63bb0d
				pipeline.WriteRequestWithResponse(b, prepareRequestForLogging(request), nil, nil)
Packit 63bb0d
				po.Log(pipeline.LogInfo, b.String())
Packit 63bb0d
			}
Packit 63bb0d
Packit 63bb0d
			// Set the time for this particular retry operation and then Do the operation.
Packit 63bb0d
			tryStart := time.Now()
Packit 63bb0d
			response, err = next.Do(ctx, request) // Make the request
Packit 63bb0d
			tryEnd := time.Now()
Packit 63bb0d
			tryDuration := tryEnd.Sub(tryStart)
Packit 63bb0d
			opDuration := tryEnd.Sub(operationStart)
Packit 63bb0d
Packit 63bb0d
			logLevel, forceLog := pipeline.LogInfo, false // Default logging information
Packit 63bb0d
Packit 63bb0d
			// If the response took too long, we'll upgrade to warning.
Packit 63bb0d
			if o.LogWarningIfTryOverThreshold > 0 && tryDuration > o.LogWarningIfTryOverThreshold {
Packit 63bb0d
				// Log a warning if the try duration exceeded the specified threshold
Packit 63bb0d
				logLevel, forceLog = pipeline.LogWarning, true
Packit 63bb0d
			}
Packit 63bb0d
Packit Service 3a6627
			var sc int
Packit Service 3a6627
			if err == nil { // We got a valid response from the service
Packit Service 3a6627
				sc = response.Response().StatusCode
Packit Service 3a6627
			} else { // We got an error, so we should inspect if we got a response
Packit Service 3a6627
				if se, ok := err.(StorageError); ok {
Packit Service 3a6627
					if r := se.Response(); r != nil {
Packit Service 3a6627
						sc = r.StatusCode
Packit Service 3a6627
					}
Packit 63bb0d
				}
Packit Service 3a6627
			}
Packit Service 3a6627
Packit Service 3a6627
			if sc == 0 || ((sc >= 400 && sc <= 499) && sc != http.StatusNotFound && sc != http.StatusConflict && sc != http.StatusPreconditionFailed && sc != http.StatusRequestedRangeNotSatisfiable) || (sc >= 500 && sc <= 599) {
Packit Service 3a6627
				logLevel, forceLog = pipeline.LogError, true // Promote to Error any 4xx (except those listed is an error) or any 5xx
Packit Service 3a6627
			} else {
Packit Service 3a6627
				// For other status codes, we leave the level as is.
Packit 63bb0d
			}
Packit 63bb0d
Packit 63bb0d
			if shouldLog := po.ShouldLog(logLevel); forceLog || shouldLog {
Packit 63bb0d
				// We're going to log this; build the string to log
Packit 63bb0d
				b := &bytes.Buffer{}
Packit 63bb0d
				slow := ""
Packit 63bb0d
				if o.LogWarningIfTryOverThreshold > 0 && tryDuration > o.LogWarningIfTryOverThreshold {
Packit 63bb0d
					slow = fmt.Sprintf("[SLOW >%v]", o.LogWarningIfTryOverThreshold)
Packit 63bb0d
				}
Packit 63bb0d
				fmt.Fprintf(b, "==> REQUEST/RESPONSE (Try=%d/%v%s, OpTime=%v) -- ", try, tryDuration, slow, opDuration)
Packit 63bb0d
				if err != nil { // This HTTP request did not get a response from the service
Packit 63bb0d
					fmt.Fprint(b, "REQUEST ERROR\n")
Packit 63bb0d
				} else {
Packit 63bb0d
					if logLevel == pipeline.LogError {
Packit 63bb0d
						fmt.Fprint(b, "RESPONSE STATUS CODE ERROR\n")
Packit 63bb0d
					} else {
Packit 63bb0d
						fmt.Fprint(b, "RESPONSE SUCCESSFULLY RECEIVED\n")
Packit 63bb0d
					}
Packit 63bb0d
				}
Packit 63bb0d
Packit 63bb0d
				pipeline.WriteRequestWithResponse(b, prepareRequestForLogging(request), response.Response(), err)
Packit 63bb0d
				if logLevel <= pipeline.LogError {
Packit 63bb0d
					b.Write(stack()) // For errors (or lower levels), we append the stack trace (an expensive operation)
Packit 63bb0d
				}
Packit 63bb0d
				msg := b.String()
Packit 63bb0d
Packit 63bb0d
				if forceLog {
Packit 63bb0d
					pipeline.ForceLog(logLevel, msg)
Packit 63bb0d
				}
Packit 63bb0d
				if shouldLog {
Packit 63bb0d
					po.Log(logLevel, msg)
Packit 63bb0d
				}
Packit 63bb0d
			}
Packit 63bb0d
			return response, err
Packit 63bb0d
		}
Packit 63bb0d
	})
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// RedactSigQueryParam redacts the 'sig' query parameter in URL's raw query to protect secret.
Packit 63bb0d
func RedactSigQueryParam(rawQuery string) (bool, string) {
Packit 63bb0d
	rawQuery = strings.ToLower(rawQuery) // lowercase the string so we can look for ?sig= and &sig=
Packit 63bb0d
	sigFound := strings.Contains(rawQuery, "?sig=")
Packit 63bb0d
	if !sigFound {
Packit 63bb0d
		sigFound = strings.Contains(rawQuery, "&sig=")
Packit 63bb0d
		if !sigFound {
Packit 63bb0d
			return sigFound, rawQuery // [?|&]sig= not found; return same rawQuery passed in (no memory allocation)
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	// [?|&]sig= found, redact its value
Packit 63bb0d
	values, _ := url.ParseQuery(rawQuery)
Packit 63bb0d
	for name := range values {
Packit 63bb0d
		if strings.EqualFold(name, "sig") {
Packit 63bb0d
			values[name] = []string{"REDACTED"}
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	return sigFound, values.Encode()
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func prepareRequestForLogging(request pipeline.Request) *http.Request {
Packit 63bb0d
	req := request
Packit 63bb0d
	if sigFound, rawQuery := RedactSigQueryParam(req.URL.RawQuery); sigFound {
Packit 63bb0d
		// Make copy so we don't destroy the query parameters we actually need to send in the request
Packit 63bb0d
		req = request.Copy()
Packit 63bb0d
		req.Request.URL.RawQuery = rawQuery
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	return prepareRequestForServiceLogging(req)
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func stack() []byte {
Packit 63bb0d
	buf := make([]byte, 1024)
Packit 63bb0d
	for {
Packit 63bb0d
		n := runtime.Stack(buf, false)
Packit 63bb0d
		if n < len(buf) {
Packit 63bb0d
			return buf[:n]
Packit 63bb0d
		}
Packit 63bb0d
		buf = make([]byte, 2*len(buf))
Packit 63bb0d
	}
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
///////////////////////////////////////////////////////////////////////////////////////
Packit 63bb0d
// Redact phase useful for blob and file service only. For other services,
Packit 63bb0d
// this method can directly return request.Request.
Packit 63bb0d
///////////////////////////////////////////////////////////////////////////////////////
Packit 63bb0d
func prepareRequestForServiceLogging(request pipeline.Request) *http.Request {
Packit 63bb0d
	req := request
Packit 63bb0d
	if exist, key := doesHeaderExistCaseInsensitive(req.Header, xMsCopySourceHeader); exist {
Packit 63bb0d
		req = request.Copy()
Packit 63bb0d
		url, err := url.Parse(req.Header.Get(key))
Packit 63bb0d
		if err == nil {
Packit 63bb0d
			if sigFound, rawQuery := RedactSigQueryParam(url.RawQuery); sigFound {
Packit 63bb0d
				url.RawQuery = rawQuery
Packit 63bb0d
				req.Header.Set(xMsCopySourceHeader, url.String())
Packit 63bb0d
			}
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	return req.Request
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
const xMsCopySourceHeader = "x-ms-copy-source"
Packit 63bb0d
Packit 63bb0d
func doesHeaderExistCaseInsensitive(header http.Header, key string) (bool, string) {
Packit 63bb0d
	for keyInHeader := range header {
Packit 63bb0d
		if strings.EqualFold(keyInHeader, key) {
Packit 63bb0d
			return true, keyInHeader
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	return false, ""
Packit 63bb0d
}