diff --git a/Makefile b/Makefile index f2eed69..1ae4a12 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ vet: golint: golint -set_exit_status ./... gocyclo: - gocyclo -over 12 $(GO_FILES_NO_TEST) + gocyclo -over 13 $(GO_FILES_NO_TEST) errcheck: errcheck -ignoretests ./... nakedret: diff --git a/go.mod b/go.mod index 4023c1e..e406484 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,8 @@ require ( github.com/kisielk/errcheck v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/testify v1.2.2 + go.opencensus.io v0.18.0 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 // indirect - golang.org/x/tools v0.0.0-20181101071927-45ff765b4815 // indirect + golang.org/x/net v0.0.0-20181029044818-c44066c5c816 // indirect + golang.org/x/tools v0.0.0-20181102050050-92b943e6bff7 // indirect ) diff --git a/go.sum b/go.sum index b6db93c..18382a0 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,46 @@ +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/alexkohler/nakedret v0.0.0-20171106223215-c0e305a4f690 h1:+tfdYWf4oDrj9c0/77f5oDBxZT2EPjS1AJf+PApGNCk= github.com/alexkohler/nakedret v0.0.0-20171106223215-c0e305a4f690/go.mod h1:tfDQbtPt67HhBK/6P0yNktIX7peCxfOp0jO9007DrLE= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835 h1:roDmqJ4Qes7hrDOsWsMCce0vQHz3xiMPjJ9m4c2eeNs= github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835/go.mod h1:BjL/N0+C+j9uNX+1xcNuM9vdSIcXCZrQZUYbXOFbgN8= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/kisielk/errcheck v1.1.0 h1:ZqfnKyx9KGpRcW04j5nnPDgRgoXUeLh2YFBeFzphcA0= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 h1:x/bBzNauLQAlE3fLku/xy92Y8QwKX5HZymrMz2IiKFc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816 h1:mVFkLpejdFLXVUv9E42f3XJVfMdqd0IVLVIVLjZWn5o= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181101071927-45ff765b4815 h1:MFLfgxncXyUrOtGJGmWoQH0yNFWSHGuJjDWqYe1h9Ug= -golang.org/x/tools v0.0.0-20181101071927-45ff765b4815/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181102050050-92b943e6bff7 h1:BdbtPv/+DFTRA5+9560n/FamYyleF+XonScH/3H1+Tk= +golang.org/x/tools v0.0.0-20181102050050-92b943e6bff7/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/search.go b/search.go index 237a397..61ff10d 100644 --- a/search.go +++ b/search.go @@ -9,6 +9,8 @@ import ( "net/url" "strconv" "time" + + "go.opencensus.io/trace" ) var ( @@ -27,28 +29,46 @@ var ( // - context has a deadline/timeout set and // - seconds to wait is not after context's deadline/timeout, making this fail early func (cms *Contentful) GetMany(ctx context.Context, parameters SearchParameters, data interface{}) error { + ctx, span := trace.StartSpan(ctx, "github.com/janivihervas/contentful-go.GetMany") + defer span.End() + response, err := cms.search(ctx, parameters) if err != nil { + addSpanError(span, trace.StatusCodeUnknown, err) return err } if response.Total == 0 || len(response.Items) == 0 { + addSpanError(span, trace.StatusCodeNotFound, ErrNoEntries) return ErrNoEntries } + _, spanParse := trace.StartSpan(ctx, "github.com/janivihervas/contentful-go.parse") + defer spanParse.End() appendIncludes(&response) flattenedItems, err := flattenItems(response.Includes, response.Items) if err != nil { + addSpanError(spanParse, trace.StatusCodeUnknown, err) + addSpanError(span, trace.StatusCodeUnknown, err) return err } bytes, err := json.Marshal(flattenedItems) if err != nil { + addSpanError(spanParse, trace.StatusCodeInternal, err) + addSpanError(span, trace.StatusCodeInternal, err) return err } - return json.Unmarshal(bytes, data) + err = json.Unmarshal(bytes, data) + if err != nil { + addSpanError(spanParse, trace.StatusCodeInternal, err) + addSpanError(span, trace.StatusCodeInternal, err) + return err + } + + return nil } // GetOne entry from Contentful. The flattened json output will be marshaled into data parameter. @@ -58,74 +78,134 @@ func (cms *Contentful) GetMany(ctx context.Context, parameters SearchParameters, // - context has a deadline/timeout set and // - seconds to wait is not after context's deadline/timeout, making this fail early func (cms *Contentful) GetOne(ctx context.Context, parameters SearchParameters, data interface{}) error { + ctx, span := trace.StartSpan(ctx, "github.com/janivihervas/contentful-go.GetOne") + defer span.End() + response, err := cms.search(ctx, parameters) if err != nil { + addSpanError(span, trace.StatusCodeUnknown, err) return err } if response.Total == 0 || len(response.Items) == 0 { + addSpanError(span, trace.StatusCodeNotFound, ErrNoEntries) return ErrNoEntries } if response.Total != 1 || len(response.Items) != 1 { + addSpanError(span, trace.StatusCodeOutOfRange, ErrMoreThanOneEntry) return ErrMoreThanOneEntry } + _, spanParse := trace.StartSpan(ctx, "github.com/janivihervas/contentful-go.parse") + defer spanParse.End() appendIncludes(&response) flattenedItem, err := flattenItem(response.Includes, response.Items[0]) if err != nil { + addSpanError(spanParse, trace.StatusCodeUnknown, err) + addSpanError(span, trace.StatusCodeUnknown, err) return err } bytes, err := json.Marshal(flattenedItem) if err != nil { + addSpanError(spanParse, trace.StatusCodeInternal, err) + addSpanError(span, trace.StatusCodeInternal, err) return err } - return json.Unmarshal(bytes, data) + err = json.Unmarshal(bytes, data) + if err != nil { + addSpanError(spanParse, trace.StatusCodeInternal, err) + addSpanError(span, trace.StatusCodeInternal, err) + return err + } + + return nil } func (cms *Contentful) search(ctx context.Context, parameters SearchParameters) (searchResults, error) { + ctx, span := trace.StartSpan(ctx, "github.com/janivihervas/contentful-go.search") + defer span.End() + response := searchResults{} if parameters.Values == nil { parameters.Values = url.Values{} } parameters.Set("include", "10") - u := cms.url + "/spaces/" + cms.spaceID + "/entries?" + parameters.Encode() - req, err := http.NewRequest("GET", u, nil) + urlStr := cms.url + "/spaces/" + cms.spaceID + "/entries?" + parameters.Encode() + urlParsed, err := url.Parse(urlStr) + if err != nil { + addSpanError(span, trace.StatusCodeInternal, err) + return response, err + } + + span.AddAttributes(trace.StringAttribute("http.host", urlParsed.Host)) + span.AddAttributes(trace.StringAttribute("http.method", http.MethodGet)) + span.AddAttributes(trace.StringAttribute("http.path", urlParsed.Path)) + span.AddAttributes(trace.StringAttribute("http.query", urlParsed.RawQuery)) + + req, err := http.NewRequest(http.MethodGet, urlStr, nil) if err != nil { + addSpanError(span, trace.StatusCodeInternal, err) return response, err } req.Header.Add("Authorization", "Bearer "+cms.token) req = req.WithContext(ctx) resp, err := http.DefaultClient.Do(req) + if err == context.Canceled { + addSpanError(span, trace.StatusCodeCancelled, err) + return response, err + } + if err == context.DeadlineExceeded { + addSpanError(span, trace.StatusCodeDeadlineExceeded, err) + return response, err + } if err != nil { + addSpanError(span, trace.StatusCodeUnknown, err) return response, err } defer func() { _ = resp.Body.Close() }() + span.AddAttributes(trace.Int64Attribute("http.status_code", int64(resp.StatusCode))) + if resp.StatusCode == http.StatusTooManyRequests { + addSpanError(span, trace.StatusCodeResourceExhausted, ErrTooManyRequests) seconds := retryAfter(ctx, resp) if seconds == -1 { + addSpanError(span, trace.StatusCodeDeadlineExceeded, ErrTooManyRequests) return response, ErrTooManyRequests } - time.Sleep(time.Second * time.Duration(seconds)) - return cms.search(ctx, parameters) + span.AddAttributes(trace.Int64Attribute("http.ratelimit_reset", int64(seconds))) + + select { + case <-time.After(time.Second * time.Duration(seconds)): + return cms.search(ctx, parameters) + case <-ctx.Done(): + addSpanError(span, trace.StatusCodeCancelled, err) + return response, ctx.Err() + } } if resp.StatusCode != http.StatusOK { - return response, fmt.Errorf("non-ok status code: %d", resp.StatusCode) + err = fmt.Errorf("non-ok status code: %d", resp.StatusCode) + addSpanError(span, trace.StatusCodeUnknown, err) + return response, err } err = json.NewDecoder(resp.Body).Decode(&response) + if err != nil { + addSpanError(span, trace.StatusCodeInternal, err) + return response, err + } - return response, err + return response, nil } func retryAfter(ctx context.Context, resp *http.Response) int { diff --git a/span.go b/span.go new file mode 100644 index 0000000..267cc11 --- /dev/null +++ b/span.go @@ -0,0 +1,9 @@ +package contentful + +import "go.opencensus.io/trace" + +func addSpanError(span *trace.Span, statusCode int32, err error) { + span.SetStatus(trace.Status{Code: statusCode, Message: err.Error()}) + // For Jaeger + span.AddAttributes(trace.BoolAttribute("error", true)) +}