github.com/alwitt/goutils@v0.6.4/core.go (about) 1 package goutils 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/gob" 7 "fmt" 8 "net/http" 9 "path" 10 "runtime" 11 "sync" 12 "time" 13 14 "github.com/apex/log" 15 ) 16 17 // LogMetadataModifier is the function signature of a callback to update log.Fields with additional 18 // key-value pairs. 19 type LogMetadataModifier func(context.Context, log.Fields) 20 21 // Component is the base structure for all components 22 type Component struct { 23 // LogTags the Apex logging message metadata tags 24 LogTags log.Fields 25 // LogTagModifiers is the list of log metadata modifier callbacks 26 LogTagModifiers []LogMetadataModifier 27 } 28 29 /* 30 NewLogTagsForContext generates a new deep-copied LogTags for an execution context 31 32 @return a new log.Fields 33 */ 34 func (c Component) NewLogTagsForContext() log.Fields { 35 result := log.Fields{} 36 var buf bytes.Buffer 37 if err := gob.NewEncoder(&buf).Encode(&c.LogTags); err != nil { 38 return c.LogTags 39 } 40 if err := gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(&result); err != nil { 41 return c.LogTags 42 } 43 return result 44 } 45 46 /* 47 GetLogTagsForContext creates a new Apex log.Fields metadata structure for a specific context 48 49 @param ctxt context.Context - a request context 50 @return the new Apec log.Fields metadata 51 */ 52 func (c Component) GetLogTagsForContext(ctxt context.Context) log.Fields { 53 theTags := c.NewLogTagsForContext() 54 for _, modifer := range c.LogTagModifiers { 55 modifer(ctxt, theTags) 56 } 57 // Add file location 58 if pc, file, lineNo, ok := runtime.Caller(1); ok { 59 funcName := runtime.FuncForPC(pc).Name() 60 fileName := path.Base(file) 61 theTags["file"] = fileName 62 theTags["line"] = lineNo 63 theTags["func"] = funcName 64 } 65 return theTags 66 } 67 68 // ====================================================================================== 69 70 // RestRequestParamKey associated key for RESTRequestParam when storing in request context 71 type RestRequestParamKey struct{} 72 73 // RestRequestParam is a helper object for logging a request's parameters into its context 74 type RestRequestParam struct { 75 // ID is the request ID 76 ID string `json:"id"` 77 // Host is the request host 78 Host string `json:"host" validate:"required,fqdn"` 79 // URI is the request URI 80 URI string `json:"uri" validate:"required,uri"` 81 // Method is the request method 82 Method string `json:"method" validate:"required,oneof=GET HEAD PUT POST PATCH DELETE OPTIONS"` 83 // Referer is the request referer string 84 Referer string `json:"referer"` 85 // RemoteAddr is the request 86 RemoteAddr string `json:"remote_address"` 87 // Proto is the request HTTP proto string 88 Proto string `json:"http_proto"` 89 // ProtoMajor is the request HTTP proto major version 90 ProtoMajor int `json:"http_version_major"` 91 // ProtoMinor is the request HTTP proto minor version 92 ProtoMinor int `json:"http_version_minor"` 93 // RequestHeaders additional request headers 94 RequestHeaders http.Header 95 // Timestamp is when the request is first received 96 Timestamp time.Time 97 } 98 99 // updateLogTags updates Apex log.Fields map with values the requests's parameters 100 func (i *RestRequestParam) updateLogTags(tags log.Fields) { 101 tags["request_id"] = i.ID 102 tags["request_host"] = i.Host 103 tags["request_uri"] = fmt.Sprintf("'%s'", i.URI) 104 tags["request_method"] = i.Method 105 tags["request_referer"] = i.Referer 106 tags["request_remote_address"] = i.RemoteAddr 107 tags["request_proto"] = i.Proto 108 tags["request_http_version_major"] = i.ProtoMajor 109 tags["request_http_version_minor"] = i.ProtoMinor 110 tags["request_timestamp"] = i.Timestamp.UTC().Format(time.RFC3339Nano) 111 for header, headerValues := range i.RequestHeaders { 112 tags[header] = headerValues 113 } 114 } 115 116 /* 117 ModifyLogMetadataByRestRequestParam update log metadata with info from RestRequestParam 118 119 @param ctxt context.Context - a request context 120 @param theTags log.Fields - a log metadata to update 121 */ 122 func ModifyLogMetadataByRestRequestParam(ctxt context.Context, theTags log.Fields) { 123 if ctxt.Value(RestRequestParamKey{}) != nil { 124 v, ok := ctxt.Value(RestRequestParamKey{}).(RestRequestParam) 125 if ok { 126 v.updateLogTags(theTags) 127 } 128 } 129 } 130 131 // ====================================================================================== 132 133 /* 134 TimeBoundedWaitGroupWait is a wrapper around wait group wait with a time limit 135 136 @param wgCtxt context.Context - context associated with the wait group 137 @param wg *sync.WaitGroup - the wait group to 138 @param timeout time.Duration - wait timeout duration 139 */ 140 func TimeBoundedWaitGroupWait( 141 wgCtxt context.Context, wg *sync.WaitGroup, timeout time.Duration, 142 ) error { 143 c := make(chan bool) 144 go func() { 145 defer close(c) 146 wg.Wait() 147 }() 148 select { 149 case <-c: 150 return nil 151 case <-wgCtxt.Done(): 152 return fmt.Errorf("associated context expired") 153 case <-time.After(timeout): 154 return fmt.Errorf("wait-group wait timed out") 155 } 156 } 157 158 // ====================================================================================== 159 160 // HTTPRequestRetryParam HTTP client request retry parameters 161 type HTTPRequestRetryParam struct { 162 // MaxRetires maximum number of retries 163 MaxRetires int 164 // InitialWaitTime the initial retry wait time 165 InitialWaitTime time.Duration 166 // MaxWaitTime the max retry wait time 167 MaxWaitTime time.Duration 168 }