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 }