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  }