github.com/blend/go-sdk@v1.20220411.3/r2/event.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package r2 9 10 import ( 11 "context" 12 "fmt" 13 "io" 14 "net/http" 15 "strings" 16 "time" 17 18 "github.com/blend/go-sdk/logger" 19 "github.com/blend/go-sdk/timeutil" 20 "github.com/blend/go-sdk/webutil" 21 ) 22 23 const ( 24 // Flag is a logger event flag. 25 Flag = "http.client.request" 26 // FlagResponse is a logger event flag. 27 FlagResponse = "http.client.response" 28 ) 29 30 // NewEvent returns a new event. 31 func NewEvent(flag string, options ...EventOption) Event { 32 e := Event{ 33 Flag: flag, 34 } 35 for _, option := range options { 36 option(&e) 37 } 38 return e 39 } 40 41 // NewEventListener returns a new r2 event listener. 42 func NewEventListener(listener func(context.Context, Event)) logger.Listener { 43 return func(ctx context.Context, e logger.Event) { 44 if typed, isTyped := e.(Event); isTyped { 45 listener(ctx, typed) 46 } 47 } 48 } 49 50 // NewEventFilter returns a new r2 event filter. 51 func NewEventFilter(filter func(context.Context, Event) (Event, bool)) logger.Filter { 52 return func(ctx context.Context, e logger.Event) (logger.Event, bool) { 53 if typed, isTyped := e.(Event); isTyped { 54 return filter(ctx, typed) 55 } 56 return e, false 57 } 58 } 59 60 var ( 61 _ logger.Event = (*Event)(nil) 62 _ logger.TextWritable = (*Event)(nil) 63 _ logger.JSONWritable = (*Event)(nil) 64 ) 65 66 // Event is a response to outgoing requests. 67 type Event struct { 68 Flag string 69 // The request metadata. 70 Request *http.Request 71 // The response metadata (excluding the body). 72 Response *http.Response 73 // The response body. 74 Body []byte 75 // Elapsed is the time elapsed. 76 Elapsed time.Duration 77 } 78 79 // GetFlag implements logger.Event. 80 func (e Event) GetFlag() string { return e.Flag } 81 82 // WriteText writes the event to a text writer. 83 func (e Event) WriteText(tf logger.TextFormatter, wr io.Writer) { 84 if e.Request != nil && e.Response != nil { 85 fmt.Fprintf(wr, "%s %s %s (%v)", e.Request.Method, e.Request.URL.String(), webutil.ColorizeStatusCodeWithFormatter(tf, e.Response.StatusCode), e.Elapsed) 86 } else if e.Request != nil { 87 fmt.Fprintf(wr, "%s %s", e.Request.Method, e.Request.URL.String()) 88 } 89 if e.Body != nil { 90 fmt.Fprint(wr, logger.Newline) 91 fmt.Fprint(wr, string(e.Body)) 92 } 93 } 94 95 // Decompose implements logger.JSONWritable. 96 func (e Event) Decompose() map[string]interface{} { 97 output := make(map[string]interface{}) 98 if e.Request != nil { 99 var url string 100 if e.Request.URL != nil { 101 url = e.Request.URL.String() 102 } 103 output["req"] = map[string]interface{}{ 104 "method": e.Request.Method, 105 "url": url, 106 "headers": e.Request.Header, 107 } 108 } 109 if e.Response != nil { 110 output["res"] = map[string]interface{}{ 111 "statusCode": e.Response.StatusCode, 112 "contentLength": e.Response.ContentLength, 113 "contentType": tryHeader(e.Response.Header, "Content-Type", "content-type"), 114 "contentEncoding": tryHeader(e.Response.Header, "Content-Encoding", "content-encoding"), 115 "headers": e.Response.Header, 116 "cert": webutil.ParseCertInfo(e.Response), 117 "elapsed": timeutil.Milliseconds(e.Elapsed), 118 } 119 } 120 if e.Body != nil { 121 output["body"] = string(e.Body) 122 } 123 124 return output 125 } 126 127 // EventJSONSchema is the json schema of the logger event. 128 type EventJSONSchema struct { 129 Req struct { 130 StartTime time.Time `json:"startTime"` 131 Method string `json:"method"` 132 URL string `json:"url"` 133 Headers map[string][]string `json:"headers"` 134 } `json:"req"` 135 Res struct { 136 CompleteTime time.Time `json:"completeTime"` 137 StatusCode int `json:"statusCode"` 138 ContentLength int `json:"contentLength"` 139 Headers map[string][]string `json:"headers"` 140 } `json:"res"` 141 Body string `json:"body"` 142 } 143 144 func tryHeader(headers http.Header, keys ...string) string { 145 for _, key := range keys { 146 if values, hasValues := headers[key]; hasValues { 147 return strings.Join(values, ";") 148 } 149 } 150 return "" 151 }