github.com/zchee/zap-cloudlogging@v0.0.0-20220819025602-19b026d3900e/http.go (about)

     1  // Copyright 2022 The zap-cloudlogging Authors
     2  // SPDX-License-Identifier: BSD-3-Clause
     3  
     4  package zapcloudlogging
     5  
     6  import (
     7  	"bytes"
     8  	"io"
     9  	"net/http"
    10  	"unicode/utf8"
    11  
    12  	"go.uber.org/zap"
    13  	"go.uber.org/zap/zapcore"
    14  	logtypepb "google.golang.org/genproto/googleapis/logging/type"
    15  )
    16  
    17  const (
    18  	// HTTPRequestKey a structured record in the format of the LogEntry HttpRequest field.
    19  	//
    20  	// httpRequest field:
    21  	// - https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#FIELDS.http_request
    22  	// - https://cloud.google.com/logging/docs/structured-logging#special-payload-fields
    23  	HTTPRequestKey = "httpRequest"
    24  )
    25  
    26  // HTTPPayload represents a Cloud Logging httpRequest fields.
    27  type HTTPPayload struct {
    28  	*logtypepb.HttpRequest
    29  }
    30  
    31  var _ zapcore.ObjectMarshaler = (*HTTPPayload)(nil)
    32  
    33  // MarshalLogObject implements zapcore.ObjectMarshaler.
    34  func (p *HTTPPayload) MarshalLogObject(enc zapcore.ObjectEncoder) error {
    35  	enc.AddString("requestMethod", p.GetRequestMethod())
    36  	enc.AddString("requestUrl", p.GetRequestUrl())
    37  	enc.AddInt64("requestSize", p.GetRequestSize())
    38  	enc.AddInt64("responseSize", p.GetResponseSize())
    39  	enc.AddString("userAgent", p.GetUserAgent())
    40  	enc.AddString("remoteIp", p.GetRemoteIp())
    41  	enc.AddString("serverIp", p.GetServerIp())
    42  	enc.AddString("referer", p.GetReferer())
    43  	enc.AddDuration("latency", p.GetLatency().AsDuration())
    44  	enc.AddInt64("cacheFillBytes", p.GetCacheFillBytes())
    45  	enc.AddString("protocol", p.GetProtocol())
    46  	enc.AddInt32("status", p.GetStatus())
    47  	enc.AddBool("cacheLookup", p.GetCacheLookup())
    48  	enc.AddBool("cacheHit", p.GetCacheHit())
    49  	enc.AddBool("cacheValidatedWithOriginServer", p.GetCacheValidatedWithOriginServer())
    50  
    51  	return nil
    52  }
    53  
    54  // NewHTTPRequest returns a new HTTPPayload struct, based on the passed
    55  // in http.Request and http.Response objects.
    56  func NewHTTPRequest(r *http.Request, res *http.Response) *HTTPPayload {
    57  	if r == nil {
    58  		r = &http.Request{}
    59  	}
    60  	if res == nil {
    61  		res = &http.Response{}
    62  	}
    63  
    64  	req := &HTTPPayload{
    65  		HttpRequest: &logtypepb.HttpRequest{
    66  			RequestMethod: r.Method,
    67  			Status:        int32(res.StatusCode),
    68  			UserAgent:     r.UserAgent(),
    69  			RemoteIp:      r.RemoteAddr,
    70  			Referer:       r.Referer(),
    71  			Protocol:      r.Proto,
    72  		},
    73  	}
    74  
    75  	if url := r.URL; url != nil {
    76  		u := *r.URL
    77  		u.Fragment = ""
    78  		req.RequestUrl = fixUTF8(u.String())
    79  	}
    80  
    81  	buf := new(bytes.Buffer)
    82  	if body := r.Body; body != nil {
    83  		n, _ := io.Copy(buf, body)
    84  		req.RequestSize = n
    85  	}
    86  
    87  	if body := res.Body; body != nil {
    88  		buf.Reset()
    89  		n, _ := io.Copy(buf, body)
    90  		req.ResponseSize = n
    91  	}
    92  
    93  	return req
    94  }
    95  
    96  // fixUTF8 is a helper that fixes an invalid UTF-8 string by replacing
    97  // invalid UTF-8 runes with the Unicode replacement character (U+FFFD).
    98  // See Issue https://github.com/googleapis/google-cloud-go/issues/1383.
    99  func fixUTF8(s string) string {
   100  	if utf8.ValidString(s) {
   101  		return s
   102  	}
   103  
   104  	// Otherwise time to build the sequence.
   105  	buf := new(bytes.Buffer)
   106  	buf.Grow(len(s))
   107  	for _, r := range s {
   108  		if utf8.ValidRune(r) {
   109  			buf.WriteRune(r)
   110  		} else {
   111  			buf.WriteRune('\uFFFD')
   112  		}
   113  	}
   114  	return buf.String()
   115  }
   116  
   117  // HTTP adds the Cloud Logging "httpRequest" field.
   118  //
   119  // httpRequest: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#HttpRequest
   120  func HTTP(req *HTTPPayload) zap.Field {
   121  	return zap.Object(HTTPRequestKey, req)
   122  }