pax_global_header00006660000000000000000000000064144352137250014520gustar00rootroot0000000000000052 comment=dfe43fa6a8d72c23e2205d0b80e762346e203f78 golang-github-go-kit-kit-0.13.0/000077500000000000000000000000001443521372500163055ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/.github/000077500000000000000000000000001443521372500176455ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/.github/FUNDING.yml000066400000000000000000000001061443521372500214570ustar00rootroot00000000000000# These are supported funding model platforms github: [peterbourgon] golang-github-go-kit-kit-0.13.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001443521372500220305ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/.github/ISSUE_TEMPLATE/bug_report.yaml000066400000000000000000000005401443521372500250630ustar00rootroot00000000000000name: Bug report description: Report a bug labels: [bug] body: - type: textarea attributes: label: What did you do? validations: required: true - type: textarea attributes: label: What did you expect? validations: required: true - type: textarea attributes: label: What happened instead? validations: required: true golang-github-go-kit-kit-0.13.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000010411443521372500240140ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: Ask a question url: https://github.com/go-kit/kit/discussions/new?category=q-a about: Questions and discussions with the Go kit community - name: Website url: https://gokit.io/ about: Project overview, examples, frequently asked questions, etc. - name: Reference url: https://pkg.go.dev/github.com/go-kit/kit about: Go kit package documentation - name: Slack channel url: https://gophers.slack.com/messages/go-kit about: Real-time discussions and Q&A golang-github-go-kit-kit-0.13.0/.github/ISSUE_TEMPLATE/feature_request.yaml000066400000000000000000000002661443521372500261230ustar00rootroot00000000000000name: Feature request description: Suggest new functionality or an enhancement body: - type: textarea attributes: label: What would you like? validations: required: true golang-github-go-kit-kit-0.13.0/.github/workflows/000077500000000000000000000000001443521372500217025ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/.github/workflows/.editorconfig000066400000000000000000000000301443521372500243500ustar00rootroot00000000000000[*.yml] indent_size = 2 golang-github-go-kit-kit-0.13.0/.github/workflows/ci.yml000066400000000000000000000034021443521372500230170ustar00rootroot00000000000000name: CI on: push: branches: - master pull_request: jobs: build: name: Build runs-on: ubuntu-latest strategy: matrix: # Support latest and one minor back go: ["1.17", "1.18", "1.19"] env: GOFLAGS: -mod=readonly services: etcd: image: gcr.io/etcd-development/etcd:v3.5.0 ports: - 2379 env: ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379 ETCD_ADVERTISE_CLIENT_URLS: http://0.0.0.0:2379 options: --health-cmd "ETCDCTL_API=3 etcdctl --endpoints http://localhost:2379 endpoint health" --health-interval 10s --health-timeout 5s --health-retries 5 consul: image: consul:1.10 ports: - 8500 zk: image: zookeeper:3.5 ports: - 2181 eureka: image: springcloud/eureka ports: - 8761 env: eureka.server.responseCacheUpdateIntervalMs: 1000 steps: - name: Set up Go uses: actions/setup-go@v2.1.3 with: stable: "false" go-version: ${{ matrix.go }} - name: Checkout code uses: actions/checkout@v2 - name: Run tests env: ETCD_ADDR: http://localhost:${{ job.services.etcd.ports[2379] }} CONSUL_ADDR: localhost:${{ job.services.consul.ports[8500] }} ZK_ADDR: localhost:${{ job.services.zk.ports[2181] }} EUREKA_ADDR: http://localhost:${{ job.services.eureka.ports[8761] }}/eureka run: go test -v -race -coverprofile=coverage.coverprofile -covermode=atomic -tags integration ./... - name: Upload coverage uses: codecov/codecov-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} file: coverage.coverprofile golang-github-go-kit-kit-0.13.0/.gitignore000066400000000000000000000007241443521372500203000ustar00rootroot00000000000000*.coverprofile # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test _old* # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe # https://github.com/github/gitignore/blob/master/Global/Vim.gitignore # swap [._]*.s[a-w][a-z] [._]s[a-w][a-z] # session Session.vim # temporary .netrwhist *~ # auto-generated tag files tags golang-github-go-kit-kit-0.13.0/CONTRIBUTING.md000066400000000000000000000015071443521372500205410ustar00rootroot00000000000000# Contributing First, thank you for contributing! We love and encourage pull requests from everyone. Before submitting major changes, here are a few guidelines to follow: 1. Check the [open issues][issues] and [pull requests][prs] for existing discussions. 1. Open an [issue][issues] first, to discuss a new feature or enhancement. 1. Write tests, and make sure the test suite passes locally and on CI. 1. Open a pull request, and reference the relevant issue(s). 1. After receiving feedback, [squash your commits][squash] and add a [great commit message][message]. 1. Have fun! [issues]: https://github.com/go-kit/kit/issues [prs]: https://github.com/go-kit/kit/pulls [squash]: http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html [message]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html golang-github-go-kit-kit-0.13.0/LICENSE000066400000000000000000000020711443521372500173120ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 Peter Bourgon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-go-kit-kit-0.13.0/README.md000066400000000000000000000153011443521372500175640ustar00rootroot00000000000000# Go kit ![GitHub Workflow Status](https://github.com/go-kit/kit/workflows/CI/badge.svg) [![GoDev](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/go-kit/kit?tab=doc) [![codecov](https://codecov.io/gh/go-kit/kit/branch/master/graph/badge.svg)](https://codecov.io/gh/go-kit/kit) [![Go Report Card](https://goreportcard.com/badge/go-kit/kit)](https://goreportcard.com/report/go-kit/kit) [![Sourcegraph](https://sourcegraph.com/github.com/go-kit/kit/-/badge.svg)](https://sourcegraph.com/github.com/go-kit/kit?badge) **Go kit** is a **programming toolkit** for building microservices (or elegant monoliths) in Go. We solve common problems in distributed systems and application architecture so you can focus on delivering business value. - Website: [gokit.io](https://gokit.io) - Mailing list: [go-kit](https://groups.google.com/forum/#!forum/go-kit) - Slack: [gophers.slack.com](https://gophers.slack.com) **#go-kit** ([invite](https://gophersinvite.herokuapp.com/)) ## Sponsors Click [here](https://github.com/sponsors/peterbourgon) or Sponsor, above, for more information on sponsorship. ## Motivation Go has emerged as the language of the server, but it remains underrepresented in so-called "modern enterprise" companies like Facebook, Twitter, Netflix, and SoundCloud. Many of these organizations have turned to JVM-based stacks for their business logic, owing in large part to libraries and ecosystems that directly support their microservice architectures. To reach its next level of success, Go needs more than simple primitives and idioms. It needs a comprehensive toolkit, for coherent distributed programming in the large. Go kit is a set of packages and best practices, which provide a comprehensive, robust, and trustable way of building microservices for organizations of any size. For more details, see [the website](https://gokit.io), [the motivating blog post](http://peter.bourgon.org/go-kit/) and [the video of the talk](https://www.youtube.com/watch?v=iFR_7AKkJFU). See also the [Go kit talk at GopherCon 2015](https://www.youtube.com/watch?v=1AjaZi4QuGo). ## Goals - Operate in a heterogeneous SOA — expect to interact with mostly non-Go-kit services - RPC as the primary messaging pattern - Pluggable serialization and transport — not just JSON over HTTP - Operate within existing infrastructures — no mandates for specific tools or technologies ## Non-goals - Supporting messaging patterns other than RPC (for now) — e.g. MPI, pub/sub, CQRS, etc. - Re-implementing functionality that can be provided by adapting existing software - Having opinions on operational concerns: deployment, configuration, process supervision, orchestration, etc. ## Contributing Please see [CONTRIBUTING.md](/CONTRIBUTING.md). Thank you, [contributors](https://github.com/go-kit/kit/graphs/contributors)! ## Dependency management Go kit is [modules](https://github.com/golang/go/wiki/Modules) aware, and we encourage users to use the standard modules tooling. But Go kit is at major version 0, so it should be compatible with non-modules environments. ## Code generators There are several third-party tools that can generate Go kit code based on different starting assumptions. - [RecoLabs/microgen](https://github.com/RecoLabs/microgen) - [GrantZheng/kit](https://github.com/GrantZheng/kit) - [kujtimiihoxha/kit](https://github.com/kujtimiihoxha/kit) (unmaintained) - [nytimes/marvin](https://github.com/nytimes/marvin) - [sagikazarmark/mga](https://github.com/sagikazarmark/mga) - [sagikazarmark/protoc-gen-go-kit](https://github.com/sagikazarmark/protoc-gen-go-kit) - [metaverse/truss](https://github.com/metaverse/truss) ## Related projects Projects with a ★ have had particular influence on Go kit's design (or vice-versa). ### Service frameworks - [gizmo](https://github.com/nytimes/gizmo), a microservice toolkit from The New York Times ★ - [go-micro](https://github.com/micro/go-micro), a distributed systems development framework ★ - [gotalk](https://github.com/rsms/gotalk), async peer communication protocol & library - [Kite](https://github.com/koding/kite), a micro-service framework - [gocircuit](https://github.com/gocircuit/circuit), dynamic cloud orchestration ### Individual components - [afex/hystrix-go](https://github.com/afex/hystrix-go), client-side latency and fault tolerance library - [armon/go-metrics](https://github.com/armon/go-metrics), library for exporting performance and runtime metrics to external metrics systems - [codahale/lunk](https://github.com/codahale/lunk), structured logging in the style of Google's Dapper or Twitter's Zipkin - [eapache/go-resiliency](https://github.com/eapache/go-resiliency), resiliency patterns - [sasbury/logging](https://github.com/sasbury/logging), a tagged style of logging - [grpc/grpc-go](https://github.com/grpc/grpc-go), HTTP/2 based RPC - [inconshreveable/log15](https://github.com/inconshreveable/log15), simple, powerful logging for Go ★ - [mailgun/vulcand](https://github.com/vulcand/vulcand), programmatic load balancer backed by etcd - [mattheath/phosphor](https://github.com/mondough/phosphor), distributed system tracing - [pivotal-golang/lager](https://github.com/pivotal-golang/lager), an opinionated logging library - [rubyist/circuitbreaker](https://github.com/rubyist/circuitbreaker), circuit breaker library - [sirupsen/logrus](https://github.com/sirupsen/logrus), structured, pluggable logging for Go ★ - [sourcegraph/appdash](https://github.com/sourcegraph/appdash), application tracing system based on Google's Dapper - [spacemonkeygo/monitor](https://github.com/spacemonkeygo/monitor), data collection, monitoring, instrumentation, and Zipkin client library - [streadway/handy](https://github.com/streadway/handy), net/http handler filters - [vitess/rpcplus](https://godoc.org/github.com/youtube/vitess/go/rpcplus), package rpc + context.Context - [gdamore/mangos](https://github.com/gdamore/mangos), nanomsg implementation in pure Go ### Web frameworks - [Gorilla](http://www.gorillatoolkit.org) - [Gin](https://gin-gonic.com/) - [Negroni](https://github.com/codegangsta/negroni) - [Goji](https://github.com/zenazn/goji) - [Martini](https://github.com/go-martini/martini) - [Beego](https://beego.vip/) - [Revel](https://revel.github.io/) (considered [harmful](https://github.com/go-kit/kit/issues/350)) - [GoBuffalo](https://gobuffalo.io/) ## Additional reading - [Architecting for the Cloud](https://slideshare.net/stonse/architecting-for-the-cloud-using-netflixoss-codemash-workshop-29852233) — Netflix - [Dapper, a Large-Scale Distributed Systems Tracing Infrastructure](http://research.google.com/pubs/pub36356.html) — Google - [Your Server as a Function](http://monkey.org/~marius/funsrv.pdf) (PDF) — Twitter golang-github-go-kit-kit-0.13.0/auth/000077500000000000000000000000001443521372500172465ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/auth/basic/000077500000000000000000000000001443521372500203275ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/auth/basic/README.md000066400000000000000000000015621443521372500216120ustar00rootroot00000000000000This package provides a Basic Authentication middleware. It'll try to compare credentials from Authentication request header to a username/password pair in middleware constructor. More details about this type of authentication can be found in [Mozilla article](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication). ## Usage ```go import httptransport "github.com/go-kit/kit/transport/http" httptransport.NewServer( AuthMiddleware(cfg.auth.user, cfg.auth.password, "Example Realm")(makeUppercaseEndpoint()), decodeMappingsRequest, httptransport.EncodeJSONResponse, httptransport.ServerBefore(httptransport.PopulateRequestContext), ) ``` For AuthMiddleware to be able to pick up the Authentication header from an HTTP request we need to pass it through the context with something like ```httptransport.ServerBefore(httptransport.PopulateRequestContext)```.golang-github-go-kit-kit-0.13.0/auth/basic/middleware.go000066400000000000000000000047671443521372500230110ustar00rootroot00000000000000package basic import ( "bytes" "context" "crypto/sha256" "crypto/subtle" "encoding/base64" "fmt" "net/http" "strings" "github.com/go-kit/kit/endpoint" httptransport "github.com/go-kit/kit/transport/http" ) // AuthError represents an authorization error. type AuthError struct { Realm string } // StatusCode is an implementation of the StatusCoder interface in go-kit/http. func (AuthError) StatusCode() int { return http.StatusUnauthorized } // Error is an implementation of the Error interface. func (AuthError) Error() string { return http.StatusText(http.StatusUnauthorized) } // Headers is an implementation of the Headerer interface in go-kit/http. func (e AuthError) Headers() http.Header { return http.Header{ "Content-Type": []string{"text/plain; charset=utf-8"}, "X-Content-Type-Options": []string{"nosniff"}, "WWW-Authenticate": []string{fmt.Sprintf(`Basic realm=%q`, e.Realm)}, } } // parseBasicAuth parses an HTTP Basic Authentication string. // "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ([]byte("Aladdin"), []byte("open sesame"), true). func parseBasicAuth(auth string) (username, password []byte, ok bool) { const prefix = "Basic " if !strings.HasPrefix(auth, prefix) { return } c, err := base64.StdEncoding.DecodeString(auth[len(prefix):]) if err != nil { return } s := bytes.IndexByte(c, ':') if s < 0 { return } return c[:s], c[s+1:], true } // Returns a hash of a given slice. func toHashSlice(s []byte) []byte { hash := sha256.Sum256(s) return hash[:] } // AuthMiddleware returns a Basic Authentication middleware for a particular user and password. func AuthMiddleware(requiredUser, requiredPassword, realm string) endpoint.Middleware { requiredUserBytes := toHashSlice([]byte(requiredUser)) requiredPasswordBytes := toHashSlice([]byte(requiredPassword)) return func(next endpoint.Endpoint) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { auth, ok := ctx.Value(httptransport.ContextKeyRequestAuthorization).(string) if !ok { return nil, AuthError{realm} } givenUser, givenPassword, ok := parseBasicAuth(auth) if !ok { return nil, AuthError{realm} } givenUserBytes := toHashSlice(givenUser) givenPasswordBytes := toHashSlice(givenPassword) if subtle.ConstantTimeCompare(givenUserBytes, requiredUserBytes) == 0 || subtle.ConstantTimeCompare(givenPasswordBytes, requiredPasswordBytes) == 0 { return nil, AuthError{realm} } return next(ctx, request) } } } golang-github-go-kit-kit-0.13.0/auth/basic/middleware_test.go000066400000000000000000000032761443521372500240420ustar00rootroot00000000000000package basic import ( "context" "encoding/base64" "fmt" "testing" httptransport "github.com/go-kit/kit/transport/http" ) func TestWithBasicAuth(t *testing.T) { requiredUser := "test-user" requiredPassword := "test-pass" realm := "test realm" type want struct { result interface{} err error } tests := []struct { name string authHeader interface{} want want }{ {"Isn't valid with nil header", nil, want{nil, AuthError{realm}}}, {"Isn't valid with non-string header", 42, want{nil, AuthError{realm}}}, {"Isn't valid without authHeader", "", want{nil, AuthError{realm}}}, {"Isn't valid for wrong user", makeAuthString("wrong-user", requiredPassword), want{nil, AuthError{realm}}}, {"Isn't valid for wrong password", makeAuthString(requiredUser, "wrong-password"), want{nil, AuthError{realm}}}, {"Is valid for correct creds", makeAuthString(requiredUser, requiredPassword), want{true, nil}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := context.WithValue(context.TODO(), httptransport.ContextKeyRequestAuthorization, tt.authHeader) result, err := AuthMiddleware(requiredUser, requiredPassword, realm)(passedValidation)(ctx, nil) if result != tt.want.result || err != tt.want.err { t.Errorf("WithBasicAuth() = result: %v, err: %v, want result: %v, want error: %v", result, err, tt.want.result, tt.want.err) } }) } } func makeAuthString(user string, password string) string { data := []byte(fmt.Sprintf("%s:%s", user, password)) return fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString(data)) } func passedValidation(ctx context.Context, request interface{}) (response interface{}, err error) { return true, nil } golang-github-go-kit-kit-0.13.0/auth/casbin/000077500000000000000000000000001443521372500205055ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/auth/casbin/middleware.go000066400000000000000000000044121443521372500231520ustar00rootroot00000000000000package casbin import ( "context" "errors" stdcasbin "github.com/casbin/casbin/v2" "github.com/go-kit/kit/endpoint" ) type contextKey string const ( // CasbinModelContextKey holds the key to store the access control model // in context, it can be a path to configuration file or a casbin/model // Model. CasbinModelContextKey contextKey = "CasbinModel" // CasbinPolicyContextKey holds the key to store the access control policy // in context, it can be a path to policy file or an implementation of // casbin/persist Adapter interface. CasbinPolicyContextKey contextKey = "CasbinPolicy" // CasbinEnforcerContextKey holds the key to retrieve the active casbin // Enforcer. CasbinEnforcerContextKey contextKey = "CasbinEnforcer" ) var ( // ErrModelContextMissing denotes a casbin model was not passed into // the parsing of middleware's context. ErrModelContextMissing = errors.New("CasbinModel is required in context") // ErrPolicyContextMissing denotes a casbin policy was not passed into // the parsing of middleware's context. ErrPolicyContextMissing = errors.New("CasbinPolicy is required in context") // ErrUnauthorized denotes the subject is not authorized to do the action // intended on the given object, based on the context model and policy. ErrUnauthorized = errors.New("Unauthorized Access") ) // NewEnforcer checks whether the subject is authorized to do the specified // action on the given object. If a valid access control model and policy // is given, then the generated casbin Enforcer is stored in the context // with CasbinEnforcer as the key. func NewEnforcer( subject string, object interface{}, action string, ) endpoint.Middleware { return func(next endpoint.Endpoint) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { casbinModel := ctx.Value(CasbinModelContextKey) casbinPolicy := ctx.Value(CasbinPolicyContextKey) enforcer, err := stdcasbin.NewEnforcer(casbinModel, casbinPolicy) if err != nil { return nil, err } ctx = context.WithValue(ctx, CasbinEnforcerContextKey, enforcer) ok, err := enforcer.Enforce(subject, object, action) if err != nil { return nil, err } if !ok { return nil, ErrUnauthorized } return next(ctx, request) } } } golang-github-go-kit-kit-0.13.0/auth/casbin/middleware_test.go000066400000000000000000000034161443521372500242140ustar00rootroot00000000000000package casbin import ( "context" "testing" stdcasbin "github.com/casbin/casbin/v2" "github.com/casbin/casbin/v2/model" fileadapter "github.com/casbin/casbin/v2/persist/file-adapter" ) func TestStructBaseContext(t *testing.T) { e := func(ctx context.Context, i interface{}) (interface{}, error) { return ctx, nil } m := model.NewModel() m.AddDef("r", "r", "sub, obj, act") m.AddDef("p", "p", "sub, obj, act") m.AddDef("e", "e", "some(where (p.eft == allow))") m.AddDef("m", "m", "r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)") a := fileadapter.NewAdapter("testdata/keymatch_policy.csv") ctx := context.WithValue(context.Background(), CasbinModelContextKey, m) ctx = context.WithValue(ctx, CasbinPolicyContextKey, a) // positive case middleware := NewEnforcer("alice", "/alice_data/resource1", "GET")(e) ctx1, err := middleware(ctx, struct{}{}) if err != nil { t.Fatalf("Enforcer returned error: %s", err) } _, ok := ctx1.(context.Context).Value(CasbinEnforcerContextKey).(*stdcasbin.Enforcer) if !ok { t.Fatalf("context should contains the active enforcer") } // negative case middleware = NewEnforcer("alice", "/alice_data/resource2", "POST")(e) _, err = middleware(ctx, struct{}{}) if err == nil { t.Fatalf("Enforcer should return error") } } func TestFileBaseContext(t *testing.T) { e := func(ctx context.Context, i interface{}) (interface{}, error) { return ctx, nil } ctx := context.WithValue(context.Background(), CasbinModelContextKey, "testdata/basic_model.conf") ctx = context.WithValue(ctx, CasbinPolicyContextKey, "testdata/basic_policy.csv") // positive case middleware := NewEnforcer("alice", "data1", "read")(e) _, err := middleware(ctx, struct{}{}) if err != nil { t.Fatalf("Enforcer returned error: %s", err) } } golang-github-go-kit-kit-0.13.0/auth/casbin/testdata/000077500000000000000000000000001443521372500223165ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/auth/casbin/testdata/basic_model.conf000066400000000000000000000003021443521372500254210ustar00rootroot00000000000000[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [policy_effect] e = some(where (p.eft == allow)) [matchers] m = r.sub == p.sub && r.obj == p.obj && r.act == p.actgolang-github-go-kit-kit-0.13.0/auth/casbin/testdata/basic_policy.csv000066400000000000000000000000521443521372500254700ustar00rootroot00000000000000p, alice, data1, read p, bob, data2, writegolang-github-go-kit-kit-0.13.0/auth/casbin/testdata/keymatch_policy.csv000066400000000000000000000002451443521372500262200ustar00rootroot00000000000000p, alice, /alice_data/*, GET p, alice, /alice_data/resource1, POST p, bob, /alice_data/resource2, GET p, bob, /bob_data/*, POST p, cathy, /cathy_data, (GET)|(POST)golang-github-go-kit-kit-0.13.0/auth/jwt/000077500000000000000000000000001443521372500200525ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/auth/jwt/README.md000066400000000000000000000062241443521372500213350ustar00rootroot00000000000000# package auth/jwt `package auth/jwt` provides a set of interfaces for service authorization through [JSON Web Tokens](https://jwt.io/). ## Usage NewParser takes a key function and an expected signing method and returns an `endpoint.Middleware`. The middleware will parse a token passed into the context via the `jwt.JWTContextKey`. If the token is valid, any claims will be added to the context via the `jwt.JWTClaimsContextKey`. ```go import ( stdjwt "github.com/golang-jwt/jwt/v4" "github.com/go-kit/kit/auth/jwt" "github.com/go-kit/kit/endpoint" ) func main() { var exampleEndpoint endpoint.Endpoint { kf := func(token *stdjwt.Token) (interface{}, error) { return []byte("SigningString"), nil } exampleEndpoint = MakeExampleEndpoint(service) exampleEndpoint = jwt.NewParser(kf, stdjwt.SigningMethodHS256, jwt.StandardClaimsFactory)(exampleEndpoint) } } ``` NewSigner takes a JWT key ID header, the signing key, signing method, and a claims object. It returns an `endpoint.Middleware`. The middleware will build the token string and add it to the context via the `jwt.JWTContextKey`. ```go import ( stdjwt "github.com/golang-jwt/jwt/v4" "github.com/go-kit/kit/auth/jwt" "github.com/go-kit/kit/endpoint" ) func main() { var exampleEndpoint endpoint.Endpoint { exampleEndpoint = grpctransport.NewClient(...).Endpoint() exampleEndpoint = jwt.NewSigner( "kid-header", []byte("SigningString"), stdjwt.SigningMethodHS256, jwt.Claims{}, )(exampleEndpoint) } } ``` In order for the parser and the signer to work, the authorization headers need to be passed between the request and the context. `HTTPToContext()`, `ContextToHTTP()`, `GRPCToContext()`, and `ContextToGRPC()` are given as helpers to do this. These functions implement the correlating transport's RequestFunc interface and can be passed as ClientBefore or ServerBefore options. Example of use in a client: ```go import ( stdjwt "github.com/golang-jwt/jwt/v4" grpctransport "github.com/go-kit/kit/transport/grpc" "github.com/go-kit/kit/auth/jwt" "github.com/go-kit/kit/endpoint" ) func main() { options := []httptransport.ClientOption{} var exampleEndpoint endpoint.Endpoint { exampleEndpoint = grpctransport.NewClient(..., grpctransport.ClientBefore(jwt.ContextToGRPC())).Endpoint() exampleEndpoint = jwt.NewSigner( "kid-header", []byte("SigningString"), stdjwt.SigningMethodHS256, jwt.Claims{}, )(exampleEndpoint) } } ``` Example of use in a server: ```go import ( "context" "github.com/go-kit/kit/auth/jwt" "github.com/go-kit/log" grpctransport "github.com/go-kit/kit/transport/grpc" ) func MakeGRPCServer(ctx context.Context, endpoints Endpoints, logger log.Logger) pb.ExampleServer { options := []grpctransport.ServerOption{grpctransport.ServerErrorLogger(logger)} return &grpcServer{ createUser: grpctransport.NewServer( ctx, endpoints.CreateUserEndpoint, DecodeGRPCCreateUserRequest, EncodeGRPCCreateUserResponse, append(options, grpctransport.ServerBefore(jwt.GRPCToContext()))..., ), getUser: grpctransport.NewServer( ctx, endpoints.GetUserEndpoint, DecodeGRPCGetUserRequest, EncodeGRPCGetUserResponse, options..., ), } } ``` golang-github-go-kit-kit-0.13.0/auth/jwt/middleware.go000066400000000000000000000114651443521372500225250ustar00rootroot00000000000000package jwt import ( "context" "errors" "github.com/go-kit/kit/endpoint" "github.com/golang-jwt/jwt/v4" ) type contextKey string const ( // JWTContextKey holds the key used to store a JWT in the context. JWTContextKey contextKey = "JWTToken" // JWTTokenContextKey is an alias for JWTContextKey. // // Deprecated: prefer JWTContextKey. JWTTokenContextKey = JWTContextKey // JWTClaimsContextKey holds the key used to store the JWT Claims in the // context. JWTClaimsContextKey contextKey = "JWTClaims" ) var ( // ErrTokenContextMissing denotes a token was not passed into the parsing // middleware's context. ErrTokenContextMissing = errors.New("token up for parsing was not passed through the context") // ErrTokenInvalid denotes a token was not able to be validated. ErrTokenInvalid = errors.New("JWT was invalid") // ErrTokenExpired denotes a token's expire header (exp) has since passed. ErrTokenExpired = errors.New("JWT is expired") // ErrTokenMalformed denotes a token was not formatted as a JWT. ErrTokenMalformed = errors.New("JWT is malformed") // ErrTokenNotActive denotes a token's not before header (nbf) is in the // future. ErrTokenNotActive = errors.New("token is not valid yet") // ErrUnexpectedSigningMethod denotes a token was signed with an unexpected // signing method. ErrUnexpectedSigningMethod = errors.New("unexpected signing method") ) // NewSigner creates a new JWT generating middleware, specifying key ID, // signing string, signing method and the claims you would like it to contain. // Tokens are signed with a Key ID header (kid) which is useful for determining // the key to use for parsing. Particularly useful for clients. func NewSigner(kid string, key []byte, method jwt.SigningMethod, claims jwt.Claims) endpoint.Middleware { return func(next endpoint.Endpoint) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { token := jwt.NewWithClaims(method, claims) token.Header["kid"] = kid // Sign and get the complete encoded token as a string using the secret tokenString, err := token.SignedString(key) if err != nil { return nil, err } ctx = context.WithValue(ctx, JWTContextKey, tokenString) return next(ctx, request) } } } // ClaimsFactory is a factory for jwt.Claims. // Useful in NewParser middleware. type ClaimsFactory func() jwt.Claims // MapClaimsFactory is a ClaimsFactory that returns // an empty jwt.MapClaims. func MapClaimsFactory() jwt.Claims { return jwt.MapClaims{} } // StandardClaimsFactory is a ClaimsFactory that returns // an empty jwt.StandardClaims. func StandardClaimsFactory() jwt.Claims { return &jwt.StandardClaims{} } // NewParser creates a new JWT parsing middleware, specifying a // jwt.Keyfunc interface, the signing method and the claims type to be used. NewParser // adds the resulting claims to endpoint context or returns error on invalid token. // Particularly useful for servers. func NewParser(keyFunc jwt.Keyfunc, method jwt.SigningMethod, newClaims ClaimsFactory) endpoint.Middleware { return func(next endpoint.Endpoint) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { // tokenString is stored in the context from the transport handlers. tokenString, ok := ctx.Value(JWTContextKey).(string) if !ok { return nil, ErrTokenContextMissing } // Parse takes the token string and a function for looking up the // key. The latter is especially useful if you use multiple keys // for your application. The standard is to use 'kid' in the head // of the token to identify which key to use, but the parsed token // (head and claims) is provided to the callback, providing // flexibility. token, err := jwt.ParseWithClaims(tokenString, newClaims(), func(token *jwt.Token) (interface{}, error) { // Don't forget to validate the alg is what you expect: if token.Method != method { return nil, ErrUnexpectedSigningMethod } return keyFunc(token) }) if err != nil { if e, ok := err.(*jwt.ValidationError); ok { switch { case e.Errors&jwt.ValidationErrorMalformed != 0: // Token is malformed return nil, ErrTokenMalformed case e.Errors&jwt.ValidationErrorExpired != 0: // Token is expired return nil, ErrTokenExpired case e.Errors&jwt.ValidationErrorNotValidYet != 0: // Token is not active yet return nil, ErrTokenNotActive case e.Inner != nil: // report e.Inner return nil, e.Inner } // We have a ValidationError but have no specific Go kit error for it. // Fall through to return original error. } return nil, err } if !token.Valid { return nil, ErrTokenInvalid } ctx = context.WithValue(ctx, JWTClaimsContextKey, token.Claims) return next(ctx, request) } } } golang-github-go-kit-kit-0.13.0/auth/jwt/middleware_test.go000066400000000000000000000170671443521372500235700ustar00rootroot00000000000000package jwt import ( "context" "sync" "testing" "time" "crypto/subtle" "github.com/go-kit/kit/endpoint" "github.com/golang-jwt/jwt/v4" ) type customClaims struct { MyProperty string `json:"my_property"` jwt.StandardClaims } func (c customClaims) VerifyMyProperty(p string) bool { return subtle.ConstantTimeCompare([]byte(c.MyProperty), []byte(p)) != 0 } var ( kid = "kid" key = []byte("test_signing_key") myProperty = "some value" method = jwt.SigningMethodHS256 invalidMethod = jwt.SigningMethodRS256 mapClaims = jwt.MapClaims{"user": "go-kit"} standardClaims = jwt.StandardClaims{Audience: "go-kit"} myCustomClaims = customClaims{MyProperty: myProperty, StandardClaims: standardClaims} // Signed tokens generated at https://jwt.io/ signedKey = "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtpZCIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiZ28ta2l0In0.14M2VmYyApdSlV_LZ88ajjwuaLeIFplB8JpyNy0A19E" standardSignedKey = "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtpZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJnby1raXQifQ.L5ypIJjCOOv3jJ8G5SelaHvR04UJuxmcBN5QW3m_aoY" customSignedKey = "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtpZCIsInR5cCI6IkpXVCJ9.eyJteV9wcm9wZXJ0eSI6InNvbWUgdmFsdWUiLCJhdWQiOiJnby1raXQifQ.s8F-IDrV4WPJUsqr7qfDi-3GRlcKR0SRnkTeUT_U-i0" invalidKey = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.e30.vKVCKto-Wn6rgz3vBdaZaCBGfCBDTXOENSo_X2Gq7qA" malformedKey = "malformed.jwt.token" ) func signingValidator(t *testing.T, signer endpoint.Endpoint, expectedKey string) { ctx, err := signer(context.Background(), struct{}{}) if err != nil { t.Fatalf("Signer returned error: %s", err) } token, ok := ctx.(context.Context).Value(JWTContextKey).(string) if !ok { t.Fatal("Token did not exist in context") } if token != expectedKey { t.Fatalf("JWTs did not match: expecting %s got %s", expectedKey, token) } } func TestNewSigner(t *testing.T) { e := func(ctx context.Context, i interface{}) (interface{}, error) { return ctx, nil } signer := NewSigner(kid, key, method, mapClaims)(e) signingValidator(t, signer, signedKey) signer = NewSigner(kid, key, method, standardClaims)(e) signingValidator(t, signer, standardSignedKey) signer = NewSigner(kid, key, method, myCustomClaims)(e) signingValidator(t, signer, customSignedKey) } func TestJWTParser(t *testing.T) { e := func(ctx context.Context, i interface{}) (interface{}, error) { return ctx, nil } keys := func(token *jwt.Token) (interface{}, error) { return key, nil } parser := NewParser(keys, method, MapClaimsFactory)(e) // No Token is passed into the parser _, err := parser(context.Background(), struct{}{}) if err == nil { t.Error("Parser should have returned an error") } if err != ErrTokenContextMissing { t.Errorf("unexpected error returned, expected: %s got: %s", ErrTokenContextMissing, err) } // Invalid Token is passed into the parser ctx := context.WithValue(context.Background(), JWTContextKey, invalidKey) _, err = parser(ctx, struct{}{}) if err == nil { t.Error("Parser should have returned an error") } // Invalid Method is used in the parser badParser := NewParser(keys, invalidMethod, MapClaimsFactory)(e) ctx = context.WithValue(context.Background(), JWTContextKey, signedKey) _, err = badParser(ctx, struct{}{}) if err == nil { t.Error("Parser should have returned an error") } if err != ErrUnexpectedSigningMethod { t.Errorf("unexpected error returned, expected: %s got: %s", ErrUnexpectedSigningMethod, err) } // Invalid key is used in the parser invalidKeys := func(token *jwt.Token) (interface{}, error) { return []byte("bad"), nil } badParser = NewParser(invalidKeys, method, MapClaimsFactory)(e) ctx = context.WithValue(context.Background(), JWTContextKey, signedKey) _, err = badParser(ctx, struct{}{}) if err == nil { t.Error("Parser should have returned an error") } // Correct token is passed into the parser ctx = context.WithValue(context.Background(), JWTContextKey, signedKey) ctx1, err := parser(ctx, struct{}{}) if err != nil { t.Fatalf("Parser returned error: %s", err) } cl, ok := ctx1.(context.Context).Value(JWTClaimsContextKey).(jwt.MapClaims) if !ok { t.Fatal("Claims were not passed into context correctly") } if cl["user"] != mapClaims["user"] { t.Fatalf("JWT Claims.user did not match: expecting %s got %s", mapClaims["user"], cl["user"]) } // Test for malformed token error response parser = NewParser(keys, method, StandardClaimsFactory)(e) ctx = context.WithValue(context.Background(), JWTContextKey, malformedKey) ctx1, err = parser(ctx, struct{}{}) if want, have := ErrTokenMalformed, err; want != have { t.Fatalf("Expected %+v, got %+v", want, have) } // Test for expired token error response parser = NewParser(keys, method, StandardClaimsFactory)(e) expired := jwt.NewWithClaims(method, jwt.StandardClaims{ExpiresAt: time.Now().Unix() - 100}) token, err := expired.SignedString(key) if err != nil { t.Fatalf("Unable to Sign Token: %+v", err) } ctx = context.WithValue(context.Background(), JWTContextKey, token) ctx1, err = parser(ctx, struct{}{}) if want, have := ErrTokenExpired, err; want != have { t.Fatalf("Expected %+v, got %+v", want, have) } // Test for not activated token error response parser = NewParser(keys, method, StandardClaimsFactory)(e) notactive := jwt.NewWithClaims(method, jwt.StandardClaims{NotBefore: time.Now().Unix() + 100}) token, err = notactive.SignedString(key) if err != nil { t.Fatalf("Unable to Sign Token: %+v", err) } ctx = context.WithValue(context.Background(), JWTContextKey, token) ctx1, err = parser(ctx, struct{}{}) if want, have := ErrTokenNotActive, err; want != have { t.Fatalf("Expected %+v, got %+v", want, have) } // test valid standard claims token parser = NewParser(keys, method, StandardClaimsFactory)(e) ctx = context.WithValue(context.Background(), JWTContextKey, standardSignedKey) ctx1, err = parser(ctx, struct{}{}) if err != nil { t.Fatalf("Parser returned error: %s", err) } stdCl, ok := ctx1.(context.Context).Value(JWTClaimsContextKey).(*jwt.StandardClaims) if !ok { t.Fatal("Claims were not passed into context correctly") } if !stdCl.VerifyAudience("go-kit", true) { t.Fatalf("JWT jwt.StandardClaims.Audience did not match: expecting %s got %s", standardClaims.Audience, stdCl.Audience) } // test valid customized claims token parser = NewParser(keys, method, func() jwt.Claims { return &customClaims{} })(e) ctx = context.WithValue(context.Background(), JWTContextKey, customSignedKey) ctx1, err = parser(ctx, struct{}{}) if err != nil { t.Fatalf("Parser returned error: %s", err) } custCl, ok := ctx1.(context.Context).Value(JWTClaimsContextKey).(*customClaims) if !ok { t.Fatal("Claims were not passed into context correctly") } if !custCl.VerifyAudience("go-kit", true) { t.Fatalf("JWT customClaims.Audience did not match: expecting %s got %s", standardClaims.Audience, custCl.Audience) } if !custCl.VerifyMyProperty(myProperty) { t.Fatalf("JWT customClaims.MyProperty did not match: expecting %s got %s", myProperty, custCl.MyProperty) } } func TestIssue562(t *testing.T) { var ( kf = func(token *jwt.Token) (interface{}, error) { return []byte("secret"), nil } e = NewParser(kf, jwt.SigningMethodHS256, MapClaimsFactory)(endpoint.Nop) key = JWTContextKey val = "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtpZCIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiZ28ta2l0In0.14M2VmYyApdSlV_LZ88ajjwuaLeIFplB8JpyNy0A19E" ctx = context.WithValue(context.Background(), key, val) ) wg := sync.WaitGroup{} for i := 0; i < 100; i++ { wg.Add(1) go func() { defer wg.Done() e(ctx, struct{}{}) // fatal error: concurrent map read and map write }() } wg.Wait() } golang-github-go-kit-kit-0.13.0/auth/jwt/transport.go000066400000000000000000000042671443521372500224460ustar00rootroot00000000000000package jwt import ( "context" "fmt" stdhttp "net/http" "strings" "google.golang.org/grpc/metadata" "github.com/go-kit/kit/transport/grpc" "github.com/go-kit/kit/transport/http" ) const ( bearer string = "bearer" bearerFormat string = "Bearer %s" ) // HTTPToContext moves a JWT from request header to context. Particularly // useful for servers. func HTTPToContext() http.RequestFunc { return func(ctx context.Context, r *stdhttp.Request) context.Context { token, ok := extractTokenFromAuthHeader(r.Header.Get("Authorization")) if !ok { return ctx } return context.WithValue(ctx, JWTContextKey, token) } } // ContextToHTTP moves a JWT from context to request header. Particularly // useful for clients. func ContextToHTTP() http.RequestFunc { return func(ctx context.Context, r *stdhttp.Request) context.Context { token, ok := ctx.Value(JWTContextKey).(string) if ok { r.Header.Add("Authorization", generateAuthHeaderFromToken(token)) } return ctx } } // GRPCToContext moves a JWT from grpc metadata to context. Particularly // userful for servers. func GRPCToContext() grpc.ServerRequestFunc { return func(ctx context.Context, md metadata.MD) context.Context { // capital "Key" is illegal in HTTP/2. authHeader, ok := md["authorization"] if !ok { return ctx } token, ok := extractTokenFromAuthHeader(authHeader[0]) if ok { ctx = context.WithValue(ctx, JWTContextKey, token) } return ctx } } // ContextToGRPC moves a JWT from context to grpc metadata. Particularly // useful for clients. func ContextToGRPC() grpc.ClientRequestFunc { return func(ctx context.Context, md *metadata.MD) context.Context { token, ok := ctx.Value(JWTContextKey).(string) if ok { // capital "Key" is illegal in HTTP/2. (*md)["authorization"] = []string{generateAuthHeaderFromToken(token)} } return ctx } } func extractTokenFromAuthHeader(val string) (token string, ok bool) { authHeaderParts := strings.Split(val, " ") if len(authHeaderParts) != 2 || !strings.EqualFold(authHeaderParts[0], bearer) { return "", false } return authHeaderParts[1], true } func generateAuthHeaderFromToken(token string) string { return fmt.Sprintf(bearerFormat, token) } golang-github-go-kit-kit-0.13.0/auth/jwt/transport_test.go000066400000000000000000000062671443521372500235070ustar00rootroot00000000000000package jwt import ( "context" "fmt" "net/http" "testing" "google.golang.org/grpc/metadata" ) func TestHTTPToContext(t *testing.T) { reqFunc := HTTPToContext() // When the header doesn't exist ctx := reqFunc(context.Background(), &http.Request{}) if ctx.Value(JWTContextKey) != nil { t.Error("Context shouldn't contain the encoded JWT") } // Authorization header value has invalid format header := http.Header{} header.Set("Authorization", "no expected auth header format value") ctx = reqFunc(context.Background(), &http.Request{Header: header}) if ctx.Value(JWTContextKey) != nil { t.Error("Context shouldn't contain the encoded JWT") } // Authorization header is correct header.Set("Authorization", generateAuthHeaderFromToken(signedKey)) ctx = reqFunc(context.Background(), &http.Request{Header: header}) token := ctx.Value(JWTContextKey).(string) if token != signedKey { t.Errorf("Context doesn't contain the expected encoded token value; expected: %s, got: %s", signedKey, token) } } func TestContextToHTTP(t *testing.T) { reqFunc := ContextToHTTP() // No JWT is passed in the context ctx := context.Background() r := http.Request{} reqFunc(ctx, &r) token := r.Header.Get("Authorization") if token != "" { t.Error("authorization key should not exist in metadata") } // Correct JWT is passed in the context ctx = context.WithValue(context.Background(), JWTContextKey, signedKey) r = http.Request{Header: http.Header{}} reqFunc(ctx, &r) token = r.Header.Get("Authorization") expected := generateAuthHeaderFromToken(signedKey) if token != expected { t.Errorf("Authorization header does not contain the expected JWT; expected %s, got %s", expected, token) } } func TestGRPCToContext(t *testing.T) { md := metadata.MD{} reqFunc := GRPCToContext() // No Authorization header is passed ctx := reqFunc(context.Background(), md) token := ctx.Value(JWTContextKey) if token != nil { t.Error("Context should not contain a JWT") } // Invalid Authorization header is passed md["authorization"] = []string{signedKey} ctx = reqFunc(context.Background(), md) token = ctx.Value(JWTContextKey) if token != nil { t.Error("Context should not contain a JWT") } // Authorization header is correct md["authorization"] = []string{fmt.Sprintf("Bearer %s", signedKey)} ctx = reqFunc(context.Background(), md) token, ok := ctx.Value(JWTContextKey).(string) if !ok { t.Fatal("JWT not passed to context correctly") } if token != signedKey { t.Errorf("JWTs did not match: expecting %s got %s", signedKey, token) } } func TestContextToGRPC(t *testing.T) { reqFunc := ContextToGRPC() // No JWT is passed in the context ctx := context.Background() md := metadata.MD{} reqFunc(ctx, &md) _, ok := md["authorization"] if ok { t.Error("authorization key should not exist in metadata") } // Correct JWT is passed in the context ctx = context.WithValue(context.Background(), JWTContextKey, signedKey) md = metadata.MD{} reqFunc(ctx, &md) token, ok := md["authorization"] if !ok { t.Fatal("JWT not passed to metadata correctly") } if token[0] != generateAuthHeaderFromToken(signedKey) { t.Errorf("JWTs did not match: expecting %s got %s", signedKey, token[0]) } } golang-github-go-kit-kit-0.13.0/circuitbreaker/000077500000000000000000000000001443521372500213035ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/circuitbreaker/doc.go000066400000000000000000000007221443521372500224000ustar00rootroot00000000000000// Package circuitbreaker implements the circuit breaker pattern. // // Circuit breakers prevent thundering herds, and improve resiliency against // intermittent errors. Every client-side endpoint should be wrapped in a // circuit breaker. // // We provide several implementations in this package, but if you're looking // for guidance, Gobreaker is probably the best place to start. It has a // simple and intuitive API, and is well-tested. package circuitbreaker golang-github-go-kit-kit-0.13.0/circuitbreaker/gobreaker.go000066400000000000000000000012751443521372500236000ustar00rootroot00000000000000package circuitbreaker import ( "context" "github.com/sony/gobreaker" "github.com/go-kit/kit/endpoint" ) // Gobreaker returns an endpoint.Middleware that implements the circuit // breaker pattern using the sony/gobreaker package. Only errors returned by // the wrapped endpoint count against the circuit breaker's error count. // // See http://godoc.org/github.com/sony/gobreaker for more information. func Gobreaker(cb *gobreaker.CircuitBreaker) endpoint.Middleware { return func(next endpoint.Endpoint) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { return cb.Execute(func() (interface{}, error) { return next(ctx, request) }) } } } golang-github-go-kit-kit-0.13.0/circuitbreaker/gobreaker_test.go000066400000000000000000000010331443521372500246270ustar00rootroot00000000000000package circuitbreaker_test import ( "testing" "github.com/sony/gobreaker" "github.com/go-kit/kit/circuitbreaker" ) func TestGobreaker(t *testing.T) { var ( breaker = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{})) primeWith = 100 shouldPass = func(n int) bool { return n <= 5 } // https://github.com/sony/gobreaker/blob/bfa846d/gobreaker.go#L76 circuitOpenError = "circuit breaker is open" ) testFailingEndpoint(t, breaker, primeWith, shouldPass, 0, circuitOpenError) } golang-github-go-kit-kit-0.13.0/circuitbreaker/handy_breaker.go000066400000000000000000000016601443521372500244330ustar00rootroot00000000000000package circuitbreaker import ( "context" "time" "github.com/streadway/handy/breaker" "github.com/go-kit/kit/endpoint" ) // HandyBreaker returns an endpoint.Middleware that implements the circuit // breaker pattern using the streadway/handy/breaker package. Only errors // returned by the wrapped endpoint count against the circuit breaker's error // count. // // See http://godoc.org/github.com/streadway/handy/breaker for more // information. func HandyBreaker(cb breaker.Breaker) endpoint.Middleware { return func(next endpoint.Endpoint) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { if !cb.Allow() { return nil, breaker.ErrCircuitOpen } defer func(begin time.Time) { if err == nil { cb.Success(time.Since(begin)) } else { cb.Failure(time.Since(begin)) } }(time.Now()) response, err = next(ctx, request) return } } } golang-github-go-kit-kit-0.13.0/circuitbreaker/handy_breaker_test.go000066400000000000000000000011361443521372500254700ustar00rootroot00000000000000package circuitbreaker_test import ( "testing" handybreaker "github.com/streadway/handy/breaker" "github.com/go-kit/kit/circuitbreaker" ) func TestHandyBreaker(t *testing.T) { var ( failureRatio = 0.05 breaker = circuitbreaker.HandyBreaker(handybreaker.NewBreaker(failureRatio)) primeWith = handybreaker.DefaultMinObservations * 10 shouldPass = func(n int) bool { return (float64(n) / float64(primeWith+n)) <= failureRatio } openCircuitError = handybreaker.ErrCircuitOpen.Error() ) testFailingEndpoint(t, breaker, primeWith, shouldPass, 0, openCircuitError) } golang-github-go-kit-kit-0.13.0/circuitbreaker/hystrix.go000066400000000000000000000014761443521372500233540ustar00rootroot00000000000000package circuitbreaker import ( "context" "github.com/afex/hystrix-go/hystrix" "github.com/go-kit/kit/endpoint" ) // Hystrix returns an endpoint.Middleware that implements the circuit // breaker pattern using the afex/hystrix-go package. // // When using this circuit breaker, please configure your commands separately. // // See https://godoc.org/github.com/afex/hystrix-go/hystrix for more // information. func Hystrix(commandName string) endpoint.Middleware { return func(next endpoint.Endpoint) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { var resp interface{} if err := hystrix.Do(commandName, func() (err error) { resp, err = next(ctx, request) return err }, nil); err != nil { return nil, err } return resp, nil } } } golang-github-go-kit-kit-0.13.0/circuitbreaker/hystrix_test.go000066400000000000000000000021671443521372500244110ustar00rootroot00000000000000package circuitbreaker_test import ( "io/ioutil" stdlog "log" "testing" "time" "github.com/afex/hystrix-go/hystrix" "github.com/go-kit/kit/circuitbreaker" ) func TestHystrix(t *testing.T) { stdlog.SetOutput(ioutil.Discard) const ( commandName = "my-endpoint" errorPercent = 5 maxConcurrent = 1000 ) hystrix.ConfigureCommand(commandName, hystrix.CommandConfig{ ErrorPercentThreshold: errorPercent, MaxConcurrentRequests: maxConcurrent, }) var ( breaker = circuitbreaker.Hystrix(commandName) primeWith = hystrix.DefaultVolumeThreshold * 2 shouldPass = func(n int) bool { return (float64(n) / float64(primeWith+n)) <= (float64(errorPercent-1) / 100.0) } openCircuitError = hystrix.ErrCircuitOpen.Error() ) // hystrix-go uses buffered channels to receive reports on request success/failure, // and so is basically impossible to test deterministically. We have to make sure // the report buffer is emptied, by injecting a sleep between each invocation. requestDelay := 5 * time.Millisecond testFailingEndpoint(t, breaker, primeWith, shouldPass, requestDelay, openCircuitError) } golang-github-go-kit-kit-0.13.0/circuitbreaker/util_test.go000066400000000000000000000033731443521372500236540ustar00rootroot00000000000000package circuitbreaker_test import ( "context" "errors" "fmt" "path/filepath" "runtime" "testing" "time" "github.com/go-kit/kit/endpoint" ) func testFailingEndpoint( t *testing.T, breaker endpoint.Middleware, primeWith int, shouldPass func(int) bool, requestDelay time.Duration, openCircuitError string, ) { _, file, line, _ := runtime.Caller(1) caller := fmt.Sprintf("%s:%d", filepath.Base(file), line) // Create a mock endpoint and wrap it with the breaker. m := mock{} var e endpoint.Endpoint e = m.endpoint e = breaker(e) // Prime the endpoint with successful requests. for i := 0; i < primeWith; i++ { if _, err := e(context.Background(), struct{}{}); err != nil { t.Fatalf("%s: during priming, got error: %v", caller, err) } time.Sleep(requestDelay) } // Switch the endpoint to start throwing errors. m.err = errors.New("tragedy+disaster") m.through = 0 // The first several should be allowed through and yield our error. for i := 0; shouldPass(i); i++ { if _, err := e(context.Background(), struct{}{}); err != m.err { t.Fatalf("%s: want %v, have %v", caller, m.err, err) } time.Sleep(requestDelay) } through := m.through // But the rest should be blocked by an open circuit. for i := 0; i < 10; i++ { if _, err := e(context.Background(), struct{}{}); err.Error() != openCircuitError { t.Fatalf("%s: want %q, have %q", caller, openCircuitError, err.Error()) } time.Sleep(requestDelay) } // Make sure none of those got through. if want, have := through, m.through; want != have { t.Errorf("%s: want %d, have %d", caller, want, have) } } type mock struct { through int err error } func (m *mock) endpoint(context.Context, interface{}) (interface{}, error) { m.through++ return struct{}{}, m.err } golang-github-go-kit-kit-0.13.0/codecov.yml000066400000000000000000000000171443521372500204500ustar00rootroot00000000000000comment: false golang-github-go-kit-kit-0.13.0/docker-compose-integration.yml000066400000000000000000000007671443521372500242750ustar00rootroot00000000000000version: '2' services: etcd: image: gcr.io/etcd-development/etcd:v3.5.0 ports: - "2379:2379" environment: ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379 ETCD_ADVERTISE_CLIENT_URLS: http://0.0.0.0:2379 consul: image: consul:1.7 ports: - "8500:8500" zk: image: zookeeper:3.5 ports: - "2181:2181" eureka: image: springcloud/eureka environment: eureka.server.responseCacheUpdateIntervalMs: 1000 ports: - "8761:8761" golang-github-go-kit-kit-0.13.0/endpoint/000077500000000000000000000000001443521372500201255ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/endpoint/doc.go000066400000000000000000000003231443521372500212170ustar00rootroot00000000000000// Package endpoint defines an abstraction for RPCs. // // Endpoints are a fundamental building block for many Go kit components. // Endpoints are implemented by servers, and called by clients. package endpoint golang-github-go-kit-kit-0.13.0/endpoint/endpoint.go000066400000000000000000000026561443521372500223050ustar00rootroot00000000000000package endpoint import ( "context" ) // Endpoint is the fundamental building block of servers and clients. // It represents a single RPC method. type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error) // Nop is an endpoint that does nothing and returns a nil error. // Useful for tests. func Nop(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil } // Middleware is a chainable behavior modifier for endpoints. type Middleware func(Endpoint) Endpoint // Chain is a helper function for composing middlewares. Requests will // traverse them in the order they're declared. That is, the first middleware // is treated as the outermost middleware. func Chain(outer Middleware, others ...Middleware) Middleware { return func(next Endpoint) Endpoint { for i := len(others) - 1; i >= 0; i-- { // reverse next = others[i](next) } return outer(next) } } // Failer may be implemented by Go kit response types that contain business // logic error details. If Failed returns a non-nil error, the Go kit transport // layer may interpret this as a business logic error, and may encode it // differently than a regular, successful response. // // It's not necessary for your response types to implement Failer, but it may // help for more sophisticated use cases. The addsvc example shows how Failer // should be used by a complete application. type Failer interface { Failed() error } golang-github-go-kit-kit-0.13.0/endpoint/endpoint_example_test.go000066400000000000000000000015171443521372500250520ustar00rootroot00000000000000package endpoint_test import ( "context" "fmt" "github.com/go-kit/kit/endpoint" ) func ExampleChain() { e := endpoint.Chain( annotate("first"), annotate("second"), annotate("third"), )(myEndpoint) if _, err := e(ctx, req); err != nil { panic(err) } // Output: // first pre // second pre // third pre // my endpoint! // third post // second post // first post } var ( ctx = context.Background() req = struct{}{} ) func annotate(s string) endpoint.Middleware { return func(next endpoint.Endpoint) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { fmt.Println(s, "pre") defer fmt.Println(s, "post") return next(ctx, request) } } } func myEndpoint(context.Context, interface{}) (interface{}, error) { fmt.Println("my endpoint!") return struct{}{}, nil } golang-github-go-kit-kit-0.13.0/examples/000077500000000000000000000000001443521372500201235ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/examples/README.md000066400000000000000000000001461443521372500214030ustar00rootroot00000000000000# Examples Examples have been relocated to a separate repository: https://github.com/go-kit/examples golang-github-go-kit-kit-0.13.0/go.mod000066400000000000000000000101131443521372500174070ustar00rootroot00000000000000module github.com/go-kit/kit go 1.17 require ( github.com/VividCortex/gohistogram v1.0.0 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 github.com/aws/aws-sdk-go v1.40.45 github.com/aws/aws-sdk-go-v2 v1.9.1 github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1 github.com/casbin/casbin/v2 v2.37.0 github.com/go-kit/log v0.2.0 github.com/go-zookeeper/zk v1.0.2 github.com/golang-jwt/jwt/v4 v4.0.0 github.com/hashicorp/consul/api v1.14.0 github.com/hudl/fargo v1.4.0 github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab github.com/nats-io/nats-server/v2 v2.8.4 github.com/nats-io/nats.go v1.15.0 github.com/opentracing/opentracing-go v1.2.0 github.com/openzipkin/zipkin-go v0.2.5 github.com/performancecopilot/speed/v4 v4.0.0 github.com/prometheus/client_golang v1.11.1 github.com/rabbitmq/amqp091-go v1.2.0 github.com/sirupsen/logrus v1.8.1 github.com/sony/gobreaker v0.4.1 github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e go.etcd.io/etcd/client/pkg/v3 v3.5.0 go.etcd.io/etcd/client/v2 v2.305.0 go.etcd.io/etcd/client/v3 v3.5.0 go.opencensus.io v0.23.0 go.uber.org/zap v1.19.1 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 google.golang.org/grpc v1.40.0 google.golang.org/protobuf v1.27.1 ) require ( github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect github.com/armon/go-metrics v0.4.0 // indirect github.com/aws/smithy-go v1.8.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.1.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/clbanning/mxj v1.8.4 // indirect github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd/v22 v22.3.2 // indirect github.com/edsrzf/mmap-go v1.0.0 // indirect github.com/fatih/color v1.13.0 // indirect github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.2.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/serf v0.10.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.14.4 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/miekg/dns v1.1.43 // indirect github.com/minio/highwayhash v1.0.2 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a // indirect github.com/nats-io/nkeys v0.3.0 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.30.0 // indirect github.com/prometheus/procfs v0.7.3 // indirect go.etcd.io/etcd/api/v3 v3.5.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.7.0 // indirect golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24 // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4 // indirect gopkg.in/gcfg.v1 v1.2.3 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) golang-github-go-kit-kit-0.13.0/go.sum000066400000000000000000002437531443521372500174560ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q= github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.40.45 h1:QN1nsY27ssD/JmW4s83qmSb+uL6DG4GmCDzjmJB4xUI= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v1.9.1 h1:ZbovGV/qo40nrOJ4q8G33AGICzaPI45FHQWJ9650pF4= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1 h1:w/fPGB0t5rWwA43mux4e9ozFSH5zF1moQemlA131PWc= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= github.com/aws/smithy-go v1.8.0 h1:AEwwwXQZtUwP5Mz506FeXXrKBe0jA8gVM+1gEcSRooc= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/casbin/casbin/v2 v2.37.0 h1:/poEwPSovi4bTOcP752/CsTQiRz2xycyVKFG7GUhbDw= github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2 h1:cZqz+yOJ/R64LcKjNQOdARott/jP7BnUQ9Ah7KaZCvw= github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8 h1:a9ENSRDFBUPkJ5lCgVZh26+ZbGyoVJG7yb5SSzF5H54= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.0 h1:7i2K3eKTos3Vc0enKCfnVcgHh2olr/MyfboYq7cAcFw= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-zookeeper/zk v1.0.2 h1:4mx0EYENAdX/B/rbunjlt5+4RTA/a9SMHBRuSKdGxPM= github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.14.0 h1:Y64GIJ8hYTu+tuGekwO4G4ardXoiCivX9wv1iP/kihk= github.com/hashicorp/consul/api v1.14.0/go.mod h1:bcaw5CSZ7NE9qfOfKCI1xb7ZKjzu/MyvQkCLTfqLqxQ= github.com/hashicorp/consul/sdk v0.10.0 h1:rGLEh2AWK4K0KCMvqWAz2EYxQqgciIfMagWZ0nVe5MI= github.com/hashicorp/consul/sdk v0.10.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M= github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/memberlist v0.4.0 h1:k3uda5gZcltmafuFF+UFqNEl5PrH+yPZ4zkjp1f/H/8= github.com/hashicorp/memberlist v0.4.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hashicorp/serf v0.10.0 h1:89qvvpfMQnz6c2y4pv7j2vUUmeT1+5TSZMexuTbtsPs= github.com/hashicorp/serf v0.10.0/go.mod h1:bXN03oZc5xlH46k/K1qTrpXb9ERKyY1/i/N5mxvgrZw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.4.0 h1:ZDDILMbB37UlAVLlWcJ2Iz1XuahZZTDZfdCKeclfq2s= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab h1:HqW4xhhynfjrtEiiSGcQUd6vrK23iMam1FO8rI7mwig= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.14.4 h1:eijASRJcobkVtSt81Olfh7JX43osYLwy5krOJo6YEu4= github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a h1:lem6QCvxR0Y28gth9P+wV2K/zYUUAkJ+55U8cpS0p5I= github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= github.com/nats-io/nats-server/v2 v2.8.4 h1:0jQzze1T9mECg8YZEl8+WYUXb9JKluJfCBriPUtluB4= github.com/nats-io/nats-server/v2 v2.8.4/go.mod h1:8zZa+Al3WsESfmgSs98Fi06dRWLH5Bnq90m5bKD/eT4= github.com/nats-io/nats.go v1.15.0 h1:3IXNBolWrwIUf2soxh6Rla8gPzYWEZQBUBK6RV21s+o= github.com/nats-io/nats.go v1.15.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin/zipkin-go v0.2.5 h1:UwtQQx2pyPIgWYHRg+epgdx1/HnBQTgN3/oIYEJTQzU= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/performancecopilot/speed/v4 v4.0.0 h1:VxEDCmdkfbQYDlcr/GC9YoN9PQ6p8ulk9xVsepYy9ZY= github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug= github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rabbitmq/amqp091-go v1.2.0 h1:1pHBxAsQh54R9eX/xo679fUEAfv3loMqi0pvRFOj2nk= github.com/rabbitmq/amqp091-go v1.2.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e h1:mOtuXaRAbVZsxAHVdPR3IjfmN8T1h2iczJLynhLybf8= github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/etcd/api/v3 v3.5.0 h1:GsV3S+OfZEOCNXdtNkBSR7kgLobAa/SO6tCxRa0GAYw= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0 h1:2aQv6F436YnN7I4VbI8PPYrBhu+SmrTaADcf8Mi/6PU= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0 h1:ftQ0nOOHMcbMS3KIaDQ0g5Qcd6bhaBrQT6b89DfwLTs= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38= golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24 h1:TyKJRhyo17yWxOMCTHKWrc5rddHORMlnZ/j57umaUd8= golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M= golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4 h1:ysnBoUyeL/H6RCvNRhWHjKoDEmguI+mPU+qHgK8qv/w= google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= golang-github-go-kit-kit-0.13.0/lint000077500000000000000000000011141443521372500171760ustar00rootroot00000000000000#!/usr/bin/env bash set -o errexit set -o nounset set -o pipefail if [ ! $(command -v gometalinter) ] then go get github.com/alecthomas/gometalinter gometalinter --update --install fi time gometalinter \ --exclude='error return value not checked.*(Close|Log|Print).*\(errcheck\)$' \ --exclude='.*_test\.go:.*error return value not checked.*\(errcheck\)$' \ --exclude='/thrift/' \ --exclude='/pb/' \ --exclude='no args in Log call \(vet\)' \ --disable=dupl \ --disable=aligncheck \ --disable=gotype \ --cyclo-over=20 \ --tests \ --concurrency=2 \ --deadline=300s \ ./... golang-github-go-kit-kit-0.13.0/log/000077500000000000000000000000001443521372500170665ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/log/README.md000066400000000000000000000122251443521372500203470ustar00rootroot00000000000000# package log **Deprecation notice:** The core Go kit log packages (log, log/level, log/term, and log/syslog) have been moved to their own repository at github.com/go-kit/log. The corresponding packages in this directory remain for backwards compatibility. Their types alias the types and their functions call the functions provided by the new repository. Using either import path should be equivalent. Prefer the new import path when practical. ______ `package log` provides a minimal interface for structured logging in services. It may be wrapped to encode conventions, enforce type-safety, provide leveled logging, and so on. It can be used for both typical application log events, and log-structured data streams. ## Structured logging Structured logging is, basically, conceding to the reality that logs are _data_, and warrant some level of schematic rigor. Using a stricter, key/value-oriented message format for our logs, containing contextual and semantic information, makes it much easier to get insight into the operational activity of the systems we build. Consequently, `package log` is of the strong belief that "[the benefits of structured logging outweigh the minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)". Migrating from unstructured to structured logging is probably a lot easier than you'd expect. ```go // Unstructured log.Printf("HTTP server listening on %s", addr) // Structured logger.Log("transport", "HTTP", "addr", addr, "msg", "listening") ``` ## Usage ### Typical application logging ```go w := log.NewSyncWriter(os.Stderr) logger := log.NewLogfmtLogger(w) logger.Log("question", "what is the meaning of life?", "answer", 42) // Output: // question="what is the meaning of life?" answer=42 ``` ### Contextual Loggers ```go func main() { var logger log.Logger logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) logger = log.With(logger, "instance_id", 123) logger.Log("msg", "starting") NewWorker(log.With(logger, "component", "worker")).Run() NewSlacker(log.With(logger, "component", "slacker")).Run() } // Output: // instance_id=123 msg=starting // instance_id=123 component=worker msg=running // instance_id=123 component=slacker msg=running ``` ### Interact with stdlib logger Redirect stdlib logger to Go kit logger. ```go import ( "os" stdlog "log" kitlog "github.com/go-kit/kit/log" ) func main() { logger := kitlog.NewJSONLogger(kitlog.NewSyncWriter(os.Stdout)) stdlog.SetOutput(kitlog.NewStdlibAdapter(logger)) stdlog.Print("I sure like pie") } // Output: // {"msg":"I sure like pie","ts":"2016/01/01 12:34:56"} ``` Or, if, for legacy reasons, you need to pipe all of your logging through the stdlib log package, you can redirect Go kit logger to the stdlib logger. ```go logger := kitlog.NewLogfmtLogger(kitlog.StdlibWriter{}) logger.Log("legacy", true, "msg", "at least it's something") // Output: // 2016/01/01 12:34:56 legacy=true msg="at least it's something" ``` ### Timestamps and callers ```go var logger log.Logger logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller) logger.Log("msg", "hello") // Output: // ts=2016-01-01T12:34:56Z caller=main.go:15 msg=hello ``` ## Levels Log levels are supported via the [level package](https://godoc.org/github.com/go-kit/kit/log/level). ## Supported output formats - [Logfmt](https://brandur.org/logfmt) ([see also](https://blog.codeship.com/logfmt-a-log-format-thats-easy-to-read-and-write)) - JSON ## Enhancements `package log` is centered on the one-method Logger interface. ```go type Logger interface { Log(keyvals ...interface{}) error } ``` This interface, and its supporting code like is the product of much iteration and evaluation. For more details on the evolution of the Logger interface, see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1), a talk by [Chris Hines](https://github.com/ChrisHines). Also, please see [#63](https://github.com/go-kit/kit/issues/63), [#76](https://github.com/go-kit/kit/pull/76), [#131](https://github.com/go-kit/kit/issues/131), [#157](https://github.com/go-kit/kit/pull/157), [#164](https://github.com/go-kit/kit/issues/164), and [#252](https://github.com/go-kit/kit/pull/252) to review historical conversations about package log and the Logger interface. Value-add packages and suggestions, like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/kit/log/level), are of course welcome. Good proposals should - Be composable with [contextual loggers](https://godoc.org/github.com/go-kit/kit/log#With), - Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/kit/log#Caller) in any wrapped contextual loggers, and - Be friendly to packages that accept only an unadorned log.Logger. ## Benchmarks & comparisons There are a few Go logging benchmarks and comparisons that include Go kit's package log. - [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) includes kit/log - [uber-common/zap](https://github.com/uber-common/zap), a zero-alloc logging library, includes a comparison with kit/log golang-github-go-kit-kit-0.13.0/log/deprecated_levels/000077500000000000000000000000001443521372500225405ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/log/deprecated_levels/levels.go000066400000000000000000000077731443521372500243770ustar00rootroot00000000000000// Package levels implements leveled logging on top of Go kit's log package. // // Deprecated: Use github.com/go-kit/log/level instead. package levels import "github.com/go-kit/log" // Levels provides a leveled logging wrapper around a logger. It has five // levels: debug, info, warning (warn), error, and critical (crit). If you // want a different set of levels, you can create your own levels type very // easily, and you can elide the configuration. type Levels struct { logger log.Logger levelKey string // We have a choice between storing level values in string fields or // making a separate context for each level. When using string fields the // Log method must combine the base context, the level data, and the // logged keyvals; but the With method only requires updating one context. // If we instead keep a separate context for each level the Log method // must only append the new keyvals; but the With method would have to // update all five contexts. // Roughly speaking, storing multiple contexts breaks even if the ratio of // Log/With calls is more than the number of levels. We have chosen to // make the With method cheap and the Log method a bit more costly because // we do not expect most applications to Log more than five times for each // call to With. debugValue string infoValue string warnValue string errorValue string critValue string } // New creates a new leveled logger, wrapping the passed logger. func New(logger log.Logger, options ...Option) Levels { l := Levels{ logger: logger, levelKey: "level", debugValue: "debug", infoValue: "info", warnValue: "warn", errorValue: "error", critValue: "crit", } for _, option := range options { option(&l) } return l } // With returns a new leveled logger that includes keyvals in all log events. func (l Levels) With(keyvals ...interface{}) Levels { return Levels{ logger: log.With(l.logger, keyvals...), levelKey: l.levelKey, debugValue: l.debugValue, infoValue: l.infoValue, warnValue: l.warnValue, errorValue: l.errorValue, critValue: l.critValue, } } // Debug returns a debug level logger. func (l Levels) Debug() log.Logger { return log.WithPrefix(l.logger, l.levelKey, l.debugValue) } // Info returns an info level logger. func (l Levels) Info() log.Logger { return log.WithPrefix(l.logger, l.levelKey, l.infoValue) } // Warn returns a warning level logger. func (l Levels) Warn() log.Logger { return log.WithPrefix(l.logger, l.levelKey, l.warnValue) } // Error returns an error level logger. func (l Levels) Error() log.Logger { return log.WithPrefix(l.logger, l.levelKey, l.errorValue) } // Crit returns a critical level logger. func (l Levels) Crit() log.Logger { return log.WithPrefix(l.logger, l.levelKey, l.critValue) } // Option sets a parameter for leveled loggers. type Option func(*Levels) // Key sets the key for the field used to indicate log level. By default, // the key is "level". func Key(key string) Option { return func(l *Levels) { l.levelKey = key } } // DebugValue sets the value for the field used to indicate the debug log // level. By default, the value is "debug". func DebugValue(value string) Option { return func(l *Levels) { l.debugValue = value } } // InfoValue sets the value for the field used to indicate the info log level. // By default, the value is "info". func InfoValue(value string) Option { return func(l *Levels) { l.infoValue = value } } // WarnValue sets the value for the field used to indicate the warning log // level. By default, the value is "warn". func WarnValue(value string) Option { return func(l *Levels) { l.warnValue = value } } // ErrorValue sets the value for the field used to indicate the error log // level. By default, the value is "error". func ErrorValue(value string) Option { return func(l *Levels) { l.errorValue = value } } // CritValue sets the value for the field used to indicate the critical log // level. By default, the value is "crit". func CritValue(value string) Option { return func(l *Levels) { l.critValue = value } } golang-github-go-kit-kit-0.13.0/log/deprecated_levels/levels_test.go000066400000000000000000000032731443521372500254250ustar00rootroot00000000000000package levels_test import ( "bytes" "os" "testing" levels "github.com/go-kit/kit/log/deprecated_levels" "github.com/go-kit/log" ) func TestDefaultLevels(t *testing.T) { buf := bytes.Buffer{} logger := levels.New(log.NewLogfmtLogger(&buf)) logger.Debug().Log("msg", "résumé") // of course you'd want to do this if want, have := "level=debug msg=résumé\n", buf.String(); want != have { t.Errorf("want %#v, have %#v", want, have) } buf.Reset() logger.Info().Log("msg", "Åhus") if want, have := "level=info msg=Åhus\n", buf.String(); want != have { t.Errorf("want %#v, have %#v", want, have) } buf.Reset() logger.Error().Log("msg", "© violation") if want, have := "level=error msg=\"© violation\"\n", buf.String(); want != have { t.Errorf("want %#v, have %#v", want, have) } buf.Reset() logger.Crit().Log("msg", " ") if want, have := "level=crit msg=\"\\t\"\n", buf.String(); want != have { t.Errorf("want %#v, have %#v", want, have) } } func TestModifiedLevels(t *testing.T) { buf := bytes.Buffer{} logger := levels.New( log.NewJSONLogger(&buf), levels.Key("l"), levels.DebugValue("dbg"), levels.InfoValue("nfo"), levels.WarnValue("wrn"), levels.ErrorValue("err"), levels.CritValue("crt"), ) logger.With("easter_island", "176°").Debug().Log("msg", "moai") if want, have := `{"easter_island":"176°","l":"dbg","msg":"moai"}`+"\n", buf.String(); want != have { t.Errorf("want %#v, have %#v", want, have) } } func ExampleLevels() { logger := levels.New(log.NewLogfmtLogger(os.Stdout)) logger.Debug().Log("msg", "hello") logger.With("context", "foo").Warn().Log("err", "error") // Output: // level=debug msg=hello // level=warn context=foo err=error } golang-github-go-kit-kit-0.13.0/log/doc.go000066400000000000000000000124371443521372500201710ustar00rootroot00000000000000// Package log provides a structured logger. // // Deprecated: Use github.com/go-kit/log instead. // // Structured logging produces logs easily consumed later by humans or // machines. Humans might be interested in debugging errors, or tracing // specific requests. Machines might be interested in counting interesting // events, or aggregating information for off-line processing. In both cases, // it is important that the log messages are structured and actionable. // Package log is designed to encourage both of these best practices. // // Basic Usage // // The fundamental interface is Logger. Loggers create log events from // key/value data. The Logger interface has a single method, Log, which // accepts a sequence of alternating key/value pairs, which this package names // keyvals. // // type Logger interface { // Log(keyvals ...interface{}) error // } // // Here is an example of a function using a Logger to create log events. // // func RunTask(task Task, logger log.Logger) string { // logger.Log("taskID", task.ID, "event", "starting task") // ... // logger.Log("taskID", task.ID, "event", "task complete") // } // // The keys in the above example are "taskID" and "event". The values are // task.ID, "starting task", and "task complete". Every key is followed // immediately by its value. // // Keys are usually plain strings. Values may be any type that has a sensible // encoding in the chosen log format. With structured logging it is a good // idea to log simple values without formatting them. This practice allows // the chosen logger to encode values in the most appropriate way. // // Contextual Loggers // // A contextual logger stores keyvals that it includes in all log events. // Building appropriate contextual loggers reduces repetition and aids // consistency in the resulting log output. With, WithPrefix, and WithSuffix // add context to a logger. We can use With to improve the RunTask example. // // func RunTask(task Task, logger log.Logger) string { // logger = log.With(logger, "taskID", task.ID) // logger.Log("event", "starting task") // ... // taskHelper(task.Cmd, logger) // ... // logger.Log("event", "task complete") // } // // The improved version emits the same log events as the original for the // first and last calls to Log. Passing the contextual logger to taskHelper // enables each log event created by taskHelper to include the task.ID even // though taskHelper does not have access to that value. Using contextual // loggers this way simplifies producing log output that enables tracing the // life cycle of individual tasks. (See the Contextual example for the full // code of the above snippet.) // // Dynamic Contextual Values // // A Valuer function stored in a contextual logger generates a new value each // time an event is logged. The Valuer example demonstrates how this feature // works. // // Valuers provide the basis for consistently logging timestamps and source // code location. The log package defines several valuers for that purpose. // See Timestamp, DefaultTimestamp, DefaultTimestampUTC, Caller, and // DefaultCaller. A common logger initialization sequence that ensures all log // entries contain a timestamp and source location looks like this: // // logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)) // logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller) // // Concurrent Safety // // Applications with multiple goroutines want each log event written to the // same logger to remain separate from other log events. Package log provides // two simple solutions for concurrent safe logging. // // NewSyncWriter wraps an io.Writer and serializes each call to its Write // method. Using a SyncWriter has the benefit that the smallest practical // portion of the logging logic is performed within a mutex, but it requires // the formatting Logger to make only one call to Write per log event. // // NewSyncLogger wraps any Logger and serializes each call to its Log method. // Using a SyncLogger has the benefit that it guarantees each log event is // handled atomically within the wrapped logger, but it typically serializes // both the formatting and output logic. Use a SyncLogger if the formatting // logger may perform multiple writes per log event. // // Error Handling // // This package relies on the practice of wrapping or decorating loggers with // other loggers to provide composable pieces of functionality. It also means // that Logger.Log must return an error because some // implementations—especially those that output log data to an io.Writer—may // encounter errors that cannot be handled locally. This in turn means that // Loggers that wrap other loggers should return errors from the wrapped // logger up the stack. // // Fortunately, the decorator pattern also provides a way to avoid the // necessity to check for errors every time an application calls Logger.Log. // An application required to panic whenever its Logger encounters // an error could initialize its logger as follows. // // fmtlogger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)) // logger := log.LoggerFunc(func(keyvals ...interface{}) error { // if err := fmtlogger.Log(keyvals...); err != nil { // panic(err) // } // return nil // }) package log golang-github-go-kit-kit-0.13.0/log/example_test.go000066400000000000000000000053371443521372500221170ustar00rootroot00000000000000package log_test import ( "math/rand" "os" "sync" "time" "github.com/go-kit/kit/log" ) func Example_basic() { logger := log.NewLogfmtLogger(os.Stdout) type Task struct { ID int } RunTask := func(task Task, logger log.Logger) { logger.Log("taskID", task.ID, "event", "starting task") logger.Log("taskID", task.ID, "event", "task complete") } RunTask(Task{ID: 1}, logger) // Output: // taskID=1 event="starting task" // taskID=1 event="task complete" } func Example_contextual() { logger := log.NewLogfmtLogger(os.Stdout) type Task struct { ID int Cmd string } taskHelper := func(cmd string, logger log.Logger) { // execute(cmd) logger.Log("cmd", cmd, "dur", 42*time.Millisecond) } RunTask := func(task Task, logger log.Logger) { logger = log.With(logger, "taskID", task.ID) logger.Log("event", "starting task") taskHelper(task.Cmd, logger) logger.Log("event", "task complete") } RunTask(Task{ID: 1, Cmd: "echo Hello, world!"}, logger) // Output: // taskID=1 event="starting task" // taskID=1 cmd="echo Hello, world!" dur=42ms // taskID=1 event="task complete" } func Example_valuer() { logger := log.NewLogfmtLogger(os.Stdout) count := 0 counter := func() interface{} { count++ return count } logger = log.With(logger, "count", log.Valuer(counter)) logger.Log("call", "first") logger.Log("call", "second") // Output: // count=1 call=first // count=2 call=second } func Example_debugInfo() { logger := log.NewLogfmtLogger(os.Stdout) // make time predictable for this test baseTime := time.Date(2015, time.February, 3, 10, 0, 0, 0, time.UTC) mockTime := func() time.Time { baseTime = baseTime.Add(time.Second) return baseTime } logger = log.With(logger, "time", log.Timestamp(mockTime), "caller", log.DefaultCaller) logger.Log("call", "first") logger.Log("call", "second") // ... logger.Log("call", "third") // Output: // time=2015-02-03T10:00:01Z caller=example_test.go:93 call=first // time=2015-02-03T10:00:02Z caller=example_test.go:94 call=second // time=2015-02-03T10:00:03Z caller=example_test.go:98 call=third } func Example_syncWriter() { w := log.NewSyncWriter(os.Stdout) logger := log.NewLogfmtLogger(w) type Task struct { ID int } var wg sync.WaitGroup RunTask := func(task Task, logger log.Logger) { logger.Log("taskID", task.ID, "event", "starting task") time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond) logger.Log("taskID", task.ID, "event", "task complete") wg.Done() } wg.Add(2) go RunTask(Task{ID: 1}, logger) go RunTask(Task{ID: 2}, logger) wg.Wait() // Unordered output: // taskID=1 event="starting task" // taskID=2 event="starting task" // taskID=1 event="task complete" // taskID=2 event="task complete" } golang-github-go-kit-kit-0.13.0/log/json_logger.go000066400000000000000000000006351443521372500217310ustar00rootroot00000000000000package log import ( "io" "github.com/go-kit/log" ) // NewJSONLogger returns a Logger that encodes keyvals to the Writer as a // single JSON object. Each log event produces no more than one call to // w.Write. The passed Writer must be safe for concurrent use by multiple // goroutines if the returned Logger will be used concurrently. func NewJSONLogger(w io.Writer) Logger { return log.NewJSONLogger(w) } golang-github-go-kit-kit-0.13.0/log/level/000077500000000000000000000000001443521372500201755ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/log/level/doc.go000066400000000000000000000017461443521372500213010ustar00rootroot00000000000000// Package level implements leveled logging on top of Go kit's log package. // // Deprecated: Use github.com/go-kit/log/level instead. // // To use the level package, create a logger as per normal in your func main, // and wrap it with level.NewFilter. // // var logger log.Logger // logger = log.NewLogfmtLogger(os.Stderr) // logger = level.NewFilter(logger, level.AllowInfo()) // <-- // logger = log.With(logger, "ts", log.DefaultTimestampUTC) // // Then, at the callsites, use one of the level.Debug, Info, Warn, or Error // helper methods to emit leveled log events. // // logger.Log("foo", "bar") // as normal, no level // level.Debug(logger).Log("request_id", reqID, "trace_data", trace.Get()) // if value > 100 { // level.Error(logger).Log("value", value) // } // // NewFilter allows precise control over what happens when a log event is // emitted without a level key, or if a squelched level is used. Check the // Option functions for details. package level golang-github-go-kit-kit-0.13.0/log/level/example_test.go000066400000000000000000000023401443521372500232150ustar00rootroot00000000000000package level_test import ( "errors" "os" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" ) func Example_basic() { logger := log.NewLogfmtLogger(os.Stdout) level.Debug(logger).Log("msg", "this message is at the debug level") level.Info(logger).Log("msg", "this message is at the info level") level.Warn(logger).Log("msg", "this message is at the warn level") level.Error(logger).Log("msg", "this message is at the error level") // Output: // level=debug msg="this message is at the debug level" // level=info msg="this message is at the info level" // level=warn msg="this message is at the warn level" // level=error msg="this message is at the error level" } func Example_filtered() { // Set up logger with level filter. logger := log.NewLogfmtLogger(os.Stdout) logger = level.NewFilter(logger, level.AllowInfo()) logger = log.With(logger, "caller", log.DefaultCaller) // Use level helpers to log at different levels. level.Error(logger).Log("err", errors.New("bad data")) level.Info(logger).Log("event", "data saved") level.Debug(logger).Log("next item", 17) // filtered // Output: // level=error caller=example_test.go:32 err="bad data" // level=info caller=example_test.go:33 event="data saved" } golang-github-go-kit-kit-0.13.0/log/level/level.go000066400000000000000000000076361443521372500216470ustar00rootroot00000000000000package level import ( "github.com/go-kit/log" "github.com/go-kit/log/level" ) // Error returns a logger that includes a Key/ErrorValue pair. func Error(logger log.Logger) log.Logger { return level.Error(logger) } // Warn returns a logger that includes a Key/WarnValue pair. func Warn(logger log.Logger) log.Logger { return level.Warn(logger) } // Info returns a logger that includes a Key/InfoValue pair. func Info(logger log.Logger) log.Logger { return level.Info(logger) } // Debug returns a logger that includes a Key/DebugValue pair. func Debug(logger log.Logger) log.Logger { return level.Debug(logger) } // NewFilter wraps next and implements level filtering. See the commentary on // the Option functions for a detailed description of how to configure levels. // If no options are provided, all leveled log events created with Debug, // Info, Warn or Error helper methods are squelched and non-leveled log // events are passed to next unmodified. func NewFilter(next log.Logger, options ...Option) log.Logger { return level.NewFilter(next, options...) } // Option sets a parameter for the leveled logger. type Option = level.Option // AllowAll is an alias for AllowDebug. func AllowAll() Option { return level.AllowAll() } // AllowDebug allows error, warn, info and debug level log events to pass. func AllowDebug() Option { return level.AllowDebug() } // AllowInfo allows error, warn and info level log events to pass. func AllowInfo() Option { return level.AllowInfo() } // AllowWarn allows error and warn level log events to pass. func AllowWarn() Option { return level.AllowWarn() } // AllowError allows only error level log events to pass. func AllowError() Option { return level.AllowError() } // AllowNone allows no leveled log events to pass. func AllowNone() Option { return level.AllowNone() } // ErrNotAllowed sets the error to return from Log when it squelches a log // event disallowed by the configured Allow[Level] option. By default, // ErrNotAllowed is nil; in this case the log event is squelched with no // error. func ErrNotAllowed(err error) Option { return level.ErrNotAllowed(err) } // SquelchNoLevel instructs Log to squelch log events with no level, so that // they don't proceed through to the wrapped logger. If SquelchNoLevel is set // to true and a log event is squelched in this way, the error value // configured with ErrNoLevel is returned to the caller. func SquelchNoLevel(squelch bool) Option { return level.SquelchNoLevel(squelch) } // ErrNoLevel sets the error to return from Log when it squelches a log event // with no level. By default, ErrNoLevel is nil; in this case the log event is // squelched with no error. func ErrNoLevel(err error) Option { return level.ErrNoLevel(err) } // NewInjector wraps next and returns a logger that adds a Key/level pair to // the beginning of log events that don't already contain a level. In effect, // this gives a default level to logs without a level. func NewInjector(next log.Logger, lvl Value) log.Logger { return level.NewInjector(next, lvl) } // Value is the interface that each of the canonical level values implement. // It contains unexported methods that prevent types from other packages from // implementing it and guaranteeing that NewFilter can distinguish the levels // defined in this package from all other values. type Value = level.Value // Key returns the unique key added to log events by the loggers in this // package. func Key() interface{} { return level.Key() } // ErrorValue returns the unique value added to log events by Error. func ErrorValue() Value { return level.ErrorValue() } // WarnValue returns the unique value added to log events by Warn. func WarnValue() Value { return level.WarnValue() } // InfoValue returns the unique value added to log events by Info. func InfoValue() Value { return level.InfoValue() } // DebugValue returns the unique value added to log events by Debug. func DebugValue() Value { return level.DebugValue() } golang-github-go-kit-kit-0.13.0/log/log.go000066400000000000000000000043421443521372500202010ustar00rootroot00000000000000package log import ( "github.com/go-kit/log" ) // Logger is the fundamental interface for all log operations. Log creates a // log event from keyvals, a variadic sequence of alternating keys and values. // Implementations must be safe for concurrent use by multiple goroutines. In // particular, any implementation of Logger that appends to keyvals or // modifies or retains any of its elements must make a copy first. type Logger = log.Logger // ErrMissingValue is appended to keyvals slices with odd length to substitute // the missing value. var ErrMissingValue = log.ErrMissingValue // With returns a new contextual logger with keyvals prepended to those passed // to calls to Log. If logger is also a contextual logger created by With, // WithPrefix, or WithSuffix, keyvals is appended to the existing context. // // The returned Logger replaces all value elements (odd indexes) containing a // Valuer with their generated value for each call to its Log method. func With(logger Logger, keyvals ...interface{}) Logger { return log.With(logger, keyvals...) } // WithPrefix returns a new contextual logger with keyvals prepended to those // passed to calls to Log. If logger is also a contextual logger created by // With, WithPrefix, or WithSuffix, keyvals is prepended to the existing context. // // The returned Logger replaces all value elements (odd indexes) containing a // Valuer with their generated value for each call to its Log method. func WithPrefix(logger Logger, keyvals ...interface{}) Logger { return log.WithPrefix(logger, keyvals...) } // WithSuffix returns a new contextual logger with keyvals appended to those // passed to calls to Log. If logger is also a contextual logger created by // With, WithPrefix, or WithSuffix, keyvals is appended to the existing context. // // The returned Logger replaces all value elements (odd indexes) containing a // Valuer with their generated value for each call to its Log method. func WithSuffix(logger Logger, keyvals ...interface{}) Logger { return log.WithSuffix(logger, keyvals...) } // LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If // f is a function with the appropriate signature, LoggerFunc(f) is a Logger // object that calls f. type LoggerFunc = log.LoggerFunc golang-github-go-kit-kit-0.13.0/log/logfmt_logger.go000066400000000000000000000006341443521372500222470ustar00rootroot00000000000000package log import ( "io" "github.com/go-kit/log" ) // NewLogfmtLogger returns a logger that encodes keyvals to the Writer in // logfmt format. Each log event produces no more than one call to w.Write. // The passed Writer must be safe for concurrent use by multiple goroutines if // the returned Logger will be used concurrently. func NewLogfmtLogger(w io.Writer) Logger { return log.NewLogfmtLogger(w) } golang-github-go-kit-kit-0.13.0/log/logrus/000077500000000000000000000000001443521372500204015ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/log/logrus/logrus_logger.go000066400000000000000000000026231443521372500236050ustar00rootroot00000000000000// Package logrus provides an adapter to the // go-kit log.Logger interface. package logrus import ( "errors" "fmt" "github.com/go-kit/log" "github.com/sirupsen/logrus" ) type Logger struct { field logrus.FieldLogger level logrus.Level } type Option func(*Logger) var errMissingValue = errors.New("(MISSING)") // NewLogger returns a Go kit log.Logger that sends log events to a logrus.Logger. func NewLogger(logger logrus.FieldLogger, options ...Option) log.Logger { l := &Logger{ field: logger, level: logrus.InfoLevel, } for _, optFunc := range options { optFunc(l) } return l } // WithLevel configures a logrus logger to log at level for all events. func WithLevel(level logrus.Level) Option { return func(c *Logger) { c.level = level } } func (l Logger) Log(keyvals ...interface{}) error { fields := logrus.Fields{} for i := 0; i < len(keyvals); i += 2 { if i+1 < len(keyvals) { fields[fmt.Sprint(keyvals[i])] = keyvals[i+1] } else { fields[fmt.Sprint(keyvals[i])] = errMissingValue } } switch l.level { case logrus.InfoLevel: l.field.WithFields(fields).Info() case logrus.ErrorLevel: l.field.WithFields(fields).Error() case logrus.DebugLevel: l.field.WithFields(fields).Debug() case logrus.WarnLevel: l.field.WithFields(fields).Warn() case logrus.TraceLevel: l.field.WithFields(fields).Trace() default: l.field.WithFields(fields).Print() } return nil } golang-github-go-kit-kit-0.13.0/log/logrus/logrus_logger_test.go000066400000000000000000000057041443521372500246470ustar00rootroot00000000000000package logrus_test import ( "bytes" "encoding/json" "errors" "strings" "testing" log "github.com/go-kit/kit/log/logrus" "github.com/sirupsen/logrus" ) func TestLogrusLogger(t *testing.T) { t.Parallel() buf := &bytes.Buffer{} logrusLogger := logrus.New() logrusLogger.Out = buf logrusLogger.Formatter = &logrus.TextFormatter{TimestampFormat: "02-01-2006 15:04:05", FullTimestamp: true} logger := log.NewLogger(logrusLogger) if err := logger.Log("hello", "world"); err != nil { t.Fatal(err) } if want, have := "hello=world\n", strings.Split(buf.String(), " ")[3]; want != have { t.Errorf("want %#v, have %#v", want, have) } buf.Reset() if err := logger.Log("a", 1, "err", errors.New("error")); err != nil { t.Fatal(err) } if want, have := "a=1 err=error", strings.TrimSpace(strings.SplitAfterN(buf.String(), " ", 4)[3]); want != have { t.Errorf("want %#v, have %#v", want, have) } buf.Reset() if err := logger.Log("a", 1, "b"); err != nil { t.Fatal(err) } if want, have := "a=1 b=\"(MISSING)\"", strings.TrimSpace(strings.SplitAfterN(buf.String(), " ", 4)[3]); want != have { t.Errorf("want %#v, have %#v", want, have) } buf.Reset() if err := logger.Log("my_map", mymap{0: 0}); err != nil { t.Fatal(err) } if want, have := "my_map=special_behavior", strings.TrimSpace(strings.Split(buf.String(), " ")[3]); want != have { t.Errorf("want %#v, have %#v", want, have) } } type mymap map[int]int func (m mymap) String() string { return "special_behavior" } func TestWithLevel(t *testing.T) { tests := []struct { name string level logrus.Level expectedLevel logrus.Level }{ { name: "Test Debug level", level: logrus.DebugLevel, expectedLevel: logrus.DebugLevel, }, { name: "Test Error level", level: logrus.ErrorLevel, expectedLevel: logrus.ErrorLevel, }, { name: "Test Warn level", level: logrus.WarnLevel, expectedLevel: logrus.WarnLevel, }, { name: "Test Info level", level: logrus.InfoLevel, expectedLevel: logrus.InfoLevel, }, { name: "Test Trace level", level: logrus.TraceLevel, expectedLevel: logrus.TraceLevel, }, { name: "Test not existing level", level: 999, expectedLevel: logrus.InfoLevel, }, } for _, tt := range tests { buf := &bytes.Buffer{} logrusLogger := logrus.New() logrusLogger.Out = buf logrusLogger.Level = tt.level logrusLogger.Formatter = &logrus.JSONFormatter{} logger := log.NewLogger(logrusLogger, log.WithLevel(tt.level)) t.Run(tt.name, func(t *testing.T) { if err := logger.Log(); err != nil { t.Fatal(err) } l := map[string]interface{}{} if err := json.Unmarshal(buf.Bytes(), &l); err != nil { t.Fatal(err) } if v, ok := l["level"].(string); !ok || v != tt.expectedLevel.String() { t.Fatalf("Logging levels doesn't match. Expected: %s, got: %s", tt.level, v) } }) } } golang-github-go-kit-kit-0.13.0/log/nop_logger.go000066400000000000000000000002421443521372500215460ustar00rootroot00000000000000package log import "github.com/go-kit/log" // NewNopLogger returns a logger that doesn't do anything. func NewNopLogger() Logger { return log.NewNopLogger() } golang-github-go-kit-kit-0.13.0/log/stdlib.go000066400000000000000000000037541443521372500207070ustar00rootroot00000000000000package log import ( "io" "github.com/go-kit/log" ) // StdlibWriter implements io.Writer by invoking the stdlib log.Print. It's // designed to be passed to a Go kit logger as the writer, for cases where // it's necessary to redirect all Go kit log output to the stdlib logger. // // If you have any choice in the matter, you shouldn't use this. Prefer to // redirect the stdlib log to the Go kit logger via NewStdlibAdapter. type StdlibWriter = log.StdlibWriter // StdlibAdapter wraps a Logger and allows it to be passed to the stdlib // logger's SetOutput. It will extract date/timestamps, filenames, and // messages, and place them under relevant keys. type StdlibAdapter = log.StdlibAdapter // StdlibAdapterOption sets a parameter for the StdlibAdapter. type StdlibAdapterOption = log.StdlibAdapterOption // TimestampKey sets the key for the timestamp field. By default, it's "ts". func TimestampKey(key string) StdlibAdapterOption { return log.TimestampKey(key) } // FileKey sets the key for the file and line field. By default, it's "caller". func FileKey(key string) StdlibAdapterOption { return log.FileKey(key) } // MessageKey sets the key for the actual log message. By default, it's "msg". func MessageKey(key string) StdlibAdapterOption { return log.MessageKey(key) } // Prefix configures the adapter to parse a prefix from stdlib log events. If // you provide a non-empty prefix to the stdlib logger, then your should provide // that same prefix to the adapter via this option. // // By default, the prefix isn't included in the msg key. Set joinPrefixToMsg to // true if you want to include the parsed prefix in the msg. func Prefix(prefix string, joinPrefixToMsg bool) StdlibAdapterOption { return log.Prefix(prefix, joinPrefixToMsg) } // NewStdlibAdapter returns a new StdlibAdapter wrapper around the passed // logger. It's designed to be passed to log.SetOutput. func NewStdlibAdapter(logger Logger, options ...StdlibAdapterOption) io.Writer { return log.NewStdlibAdapter(logger, options...) } golang-github-go-kit-kit-0.13.0/log/sync.go000066400000000000000000000022771443521372500204010ustar00rootroot00000000000000package log import ( "io" "github.com/go-kit/log" ) // SwapLogger wraps another logger that may be safely replaced while other // goroutines use the SwapLogger concurrently. The zero value for a SwapLogger // will discard all log events without error. // // SwapLogger serves well as a package global logger that can be changed by // importers. type SwapLogger = log.SwapLogger // NewSyncWriter returns a new writer that is safe for concurrent use by // multiple goroutines. Writes to the returned writer are passed on to w. If // another write is already in progress, the calling goroutine blocks until // the writer is available. // // If w implements the following interface, so does the returned writer. // // interface { // Fd() uintptr // } func NewSyncWriter(w io.Writer) io.Writer { return log.NewSyncWriter(w) } // NewSyncLogger returns a logger that synchronizes concurrent use of the // wrapped logger. When multiple goroutines use the SyncLogger concurrently // only one goroutine will be allowed to log to the wrapped logger at a time. // The other goroutines will block until the logger is available. func NewSyncLogger(logger Logger) Logger { return log.NewSyncLogger(logger) } golang-github-go-kit-kit-0.13.0/log/syslog/000077500000000000000000000000001443521372500204065ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/log/syslog/example_test.go000066400000000000000000000011771443521372500234350ustar00rootroot00000000000000// +build !windows // +build !plan9 // +build !nacl package syslog_test import ( "fmt" gosyslog "log/syslog" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/go-kit/kit/log/syslog" ) func ExampleNewSyslogLogger_defaultPrioritySelector() { // Normal syslog writer w, err := gosyslog.New(gosyslog.LOG_INFO, "experiment") if err != nil { fmt.Println(err) return } // syslog logger with logfmt formatting logger := syslog.NewSyslogLogger(w, log.NewLogfmtLogger) logger.Log("msg", "info because of default") logger.Log(level.Key(), level.DebugValue(), "msg", "debug because of explicit level") } golang-github-go-kit-kit-0.13.0/log/syslog/syslog.go000066400000000000000000000020651443521372500222600ustar00rootroot00000000000000//go:build !windows && !plan9 && !nacl // +build !windows,!plan9,!nacl // Deprecated: Use github.com/go-kit/log/syslog instead. package syslog import ( "io" "github.com/go-kit/log" "github.com/go-kit/log/syslog" ) // SyslogWriter is an interface wrapping stdlib syslog Writer. type SyslogWriter = syslog.SyslogWriter // NewSyslogLogger returns a new Logger which writes to syslog in syslog format. // The body of the log message is the formatted output from the Logger returned // by newLogger. func NewSyslogLogger(w SyslogWriter, newLogger func(io.Writer) log.Logger, options ...Option) log.Logger { return syslog.NewSyslogLogger(w, newLogger, options...) } // Option sets a parameter for syslog loggers. type Option = syslog.Option // PrioritySelector inspects the list of keyvals and selects a syslog priority. type PrioritySelector = syslog.PrioritySelector // PrioritySelectorOption sets priority selector function to choose syslog // priority. func PrioritySelectorOption(selector PrioritySelector) Option { return syslog.PrioritySelectorOption(selector) } golang-github-go-kit-kit-0.13.0/log/term/000077500000000000000000000000001443521372500200355ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/log/term/colorlogger.go000066400000000000000000000021711443521372500227030ustar00rootroot00000000000000package term import ( "io" "github.com/go-kit/log" "github.com/go-kit/log/term" ) // Color represents an ANSI color. The zero value is Default. type Color = term.Color // ANSI colors. const ( Default = term.Default Black = term.Black DarkRed = term.DarkRed DarkGreen = term.DarkGreen Brown = term.Brown DarkBlue = term.DarkBlue DarkMagenta = term.DarkMagenta DarkCyan = term.DarkCyan Gray = term.Gray DarkGray = term.DarkGray Red = term.Red Green = term.Green Yellow = term.Yellow Blue = term.Blue Magenta = term.Magenta Cyan = term.Cyan White = term.White ) // FgBgColor represents a foreground and background color. type FgBgColor = term.FgBgColor // NewColorLogger returns a Logger which writes colored logs to w. ANSI color // codes for the colors returned by color are added to the formatted output // from the Logger returned by newLogger and the combined result written to w. func NewColorLogger(w io.Writer, newLogger func(io.Writer) log.Logger, color func(keyvals ...interface{}) FgBgColor) log.Logger { return term.NewColorLogger(w, newLogger, color) } golang-github-go-kit-kit-0.13.0/log/term/colorwriter.go000066400000000000000000000004671443521372500227460ustar00rootroot00000000000000package term import ( "io" "github.com/go-kit/log/term" ) // NewColorWriter returns an io.Writer that writes to w and provides cross // platform support for ANSI color codes. If w is not a terminal it is // returned unmodified. func NewColorWriter(w io.Writer) io.Writer { return term.NewColorWriter(w) } golang-github-go-kit-kit-0.13.0/log/term/example_test.go000066400000000000000000000025631443521372500230640ustar00rootroot00000000000000package term_test import ( "errors" "os" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/term" ) func ExampleNewLogger_redErrors() { // Color errors red colorFn := func(keyvals ...interface{}) term.FgBgColor { for i := 1; i < len(keyvals); i += 2 { if _, ok := keyvals[i].(error); ok { return term.FgBgColor{Fg: term.White, Bg: term.Red} } } return term.FgBgColor{} } logger := term.NewLogger(os.Stdout, log.NewLogfmtLogger, colorFn) logger.Log("msg", "default color", "err", nil) logger.Log("msg", "colored because of error", "err", errors.New("coloring error")) } func ExampleNewLogger_levelColors() { // Color by level value colorFn := func(keyvals ...interface{}) term.FgBgColor { for i := 0; i < len(keyvals)-1; i += 2 { if keyvals[i] != "level" { continue } switch keyvals[i+1] { case "debug": return term.FgBgColor{Fg: term.DarkGray} case "info": return term.FgBgColor{Fg: term.Gray} case "warn": return term.FgBgColor{Fg: term.Yellow} case "error": return term.FgBgColor{Fg: term.Red} case "crit": return term.FgBgColor{Fg: term.Gray, Bg: term.DarkRed} default: return term.FgBgColor{} } } return term.FgBgColor{} } logger := term.NewLogger(os.Stdout, log.NewJSONLogger, colorFn) logger.Log("level", "warn", "msg", "yellow") logger.Log("level", "debug", "msg", "dark gray") } golang-github-go-kit-kit-0.13.0/log/term/term.go000066400000000000000000000013261443521372500213350ustar00rootroot00000000000000// Package term provides tools for logging to a terminal. // // Deprecated: Use github.com/go-kit/log/term instead. package term import ( "io" "github.com/go-kit/log" "github.com/go-kit/log/term" ) // NewLogger returns a Logger that takes advantage of terminal features if // possible. Log events are formatted by the Logger returned by newLogger. If // w is a terminal each log event is colored according to the color function. func NewLogger(w io.Writer, newLogger func(io.Writer) log.Logger, color func(keyvals ...interface{}) FgBgColor) log.Logger { return term.NewLogger(w, newLogger, color) } // IsTerminal returns true if w writes to a terminal. func IsTerminal(w io.Writer) bool { return term.IsTerminal(w) } golang-github-go-kit-kit-0.13.0/log/value.go000066400000000000000000000033721443521372500205360ustar00rootroot00000000000000package log import ( "time" "github.com/go-kit/log" ) // A Valuer generates a log value. When passed to With, WithPrefix, or // WithSuffix in a value element (odd indexes), it represents a dynamic // value which is re-evaluated with each log event. type Valuer = log.Valuer // Timestamp returns a timestamp Valuer. It invokes the t function to get the // time; unless you are doing something tricky, pass time.Now. // // Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which // are TimestampFormats that use the RFC3339Nano format. func Timestamp(t func() time.Time) Valuer { return log.Timestamp(t) } // TimestampFormat returns a timestamp Valuer with a custom time format. It // invokes the t function to get the time to format; unless you are doing // something tricky, pass time.Now. The layout string is passed to // Time.Format. // // Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which // are TimestampFormats that use the RFC3339Nano format. func TimestampFormat(t func() time.Time, layout string) Valuer { return log.TimestampFormat(t, layout) } // Caller returns a Valuer that returns a file and line from a specified depth // in the callstack. Users will probably want to use DefaultCaller. func Caller(depth int) Valuer { return log.Caller(depth) } var ( // DefaultTimestamp is a Valuer that returns the current wallclock time, // respecting time zones, when bound. DefaultTimestamp = log.DefaultTimestamp // DefaultTimestampUTC is a Valuer that returns the current time in UTC // when bound. DefaultTimestampUTC = log.DefaultTimestampUTC // DefaultCaller is a Valuer that returns the file and line where the Log // method was invoked. It can only be used with log.With. DefaultCaller = log.DefaultCaller ) golang-github-go-kit-kit-0.13.0/log/zap/000077500000000000000000000000001443521372500176605ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/log/zap/zap_sugar_logger.go000066400000000000000000000017031443521372500235420ustar00rootroot00000000000000package zap import ( "github.com/go-kit/log" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) type zapSugarLogger func(msg string, keysAndValues ...interface{}) func (l zapSugarLogger) Log(kv ...interface{}) error { l("", kv...) return nil } // NewZapSugarLogger returns a Go kit log.Logger that sends // log events to a zap.Logger. func NewZapSugarLogger(logger *zap.Logger, level zapcore.Level) log.Logger { sugarLogger := logger.WithOptions(zap.AddCallerSkip(2)).Sugar() var sugar zapSugarLogger switch level { case zapcore.DebugLevel: sugar = sugarLogger.Debugw case zapcore.InfoLevel: sugar = sugarLogger.Infow case zapcore.WarnLevel: sugar = sugarLogger.Warnw case zapcore.ErrorLevel: sugar = sugarLogger.Errorw case zapcore.DPanicLevel: sugar = sugarLogger.DPanicw case zapcore.PanicLevel: sugar = sugarLogger.Panicw case zapcore.FatalLevel: sugar = sugarLogger.Fatalw default: sugar = sugarLogger.Infow } return sugar } golang-github-go-kit-kit-0.13.0/log/zap/zap_sugar_logger_test.go000066400000000000000000000046301443521372500246030ustar00rootroot00000000000000package zap_test import ( "encoding/json" kitzap "github.com/go-kit/kit/log/zap" "go.uber.org/zap" "go.uber.org/zap/zapcore" "strings" "testing" ) func TestZapSugarLogger(t *testing.T) { // logger config encoderConfig := zap.NewDevelopmentEncoderConfig() encoder := zapcore.NewJSONEncoder(encoderConfig) levelKey := encoderConfig.LevelKey // basic test cases type testCase struct { level zapcore.Level kvs []interface{} want map[string]string } testCases := []testCase{ {level: zapcore.DebugLevel, kvs: []interface{}{"key1", "value1"}, want: map[string]string{levelKey: "DEBUG", "key1": "value1"}}, {level: zapcore.InfoLevel, kvs: []interface{}{"key2", "value2"}, want: map[string]string{levelKey: "INFO", "key2": "value2"}}, {level: zapcore.WarnLevel, kvs: []interface{}{"key3", "value3"}, want: map[string]string{levelKey: "WARN", "key3": "value3"}}, {level: zapcore.ErrorLevel, kvs: []interface{}{"key4", "value4"}, want: map[string]string{levelKey: "ERROR", "key4": "value4"}}, {level: zapcore.DPanicLevel, kvs: []interface{}{"key5", "value5"}, want: map[string]string{levelKey: "DPANIC", "key5": "value5"}}, {level: zapcore.PanicLevel, kvs: []interface{}{"key6", "value6"}, want: map[string]string{levelKey: "PANIC", "key6": "value6"}}, } // test for _, testCase := range testCases { t.Run(testCase.level.String(), func(t *testing.T) { // make logger writer := &tbWriter{tb: t} logger := zap.New( zapcore.NewCore(encoder, zapcore.AddSync(writer), zap.DebugLevel), zap.Development()) // check panic shouldPanic := testCase.level >= zapcore.DPanicLevel kitLogger := kitzap.NewZapSugarLogger(logger, testCase.level) defer func() { isPanic := recover() != nil if shouldPanic != isPanic { t.Errorf("test level %v should panic(%v), but %v", testCase.level, shouldPanic, isPanic) } // check log kvs logMap := make(map[string]string) err := json.Unmarshal([]byte(writer.sb.String()), &logMap) if err != nil { t.Errorf("unmarshal error: %v", err) } else { for k, v := range testCase.want { vv, ok := logMap[k] if !ok || v != vv { t.Error("error log") } } } }() kitLogger.Log(testCase.kvs...) }) } } type tbWriter struct { tb testing.TB sb strings.Builder } func (w *tbWriter) Write(b []byte) (n int, err error) { w.tb.Logf(string(b)) w.sb.Write(b) return len(b), nil } golang-github-go-kit-kit-0.13.0/metrics/000077500000000000000000000000001443521372500177535ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/README.md000066400000000000000000000050141443521372500212320ustar00rootroot00000000000000# package metrics `package metrics` provides a set of uniform interfaces for service instrumentation. It has [counters](http://prometheus.io/docs/concepts/metric_types/#counter), [gauges](http://prometheus.io/docs/concepts/metric_types/#gauge), and [histograms](http://prometheus.io/docs/concepts/metric_types/#histogram), and provides adapters to popular metrics packages, like [expvar](https://golang.org/pkg/expvar), [StatsD](https://github.com/etsy/statsd), and [Prometheus](https://prometheus.io). ## Rationale Code instrumentation is absolutely essential to achieve [observability](https://speakerdeck.com/mattheath/observability-in-micro-service-architectures) into a distributed system. Metrics and instrumentation tools have coalesced around a few well-defined idioms. `package metrics` provides a common, minimal interface those idioms for service authors. ## Usage A simple counter, exported via expvar. ```go import ( "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/expvar" ) func main() { var myCount metrics.Counter myCount = expvar.NewCounter("my_count") myCount.Add(1) } ``` A histogram for request duration, exported via a Prometheus summary with dynamically-computed quantiles. ```go import ( "time" stdprometheus "github.com/prometheus/client_golang/prometheus" "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/prometheus" ) func main() { var dur metrics.Histogram = prometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ Namespace: "myservice", Subsystem: "api", Name: "request_duration_seconds", Help: "Total time spent serving requests.", }, []string{}) // ... } func handleRequest(dur metrics.Histogram) { defer func(begin time.Time) { dur.Observe(time.Since(begin).Seconds()) }(time.Now()) // handle request } ``` A gauge for the number of goroutines currently running, exported via StatsD. ```go import ( "context" "net" "os" "runtime" "time" "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/statsd" ) func main() { statsd := statsd.New("foo_svc.", log.NewNopLogger()) report := time.NewTicker(5 * time.Second) defer report.Stop() go statsd.SendLoop(context.Background(), report.C, "tcp", "statsd.internal:8125") goroutines := statsd.NewGauge("goroutine_count") go exportGoroutines(goroutines) // ... } func exportGoroutines(g metrics.Gauge) { for range time.Tick(time.Second) { g.Set(float64(runtime.NumGoroutine())) } } ``` For more information, see [the package documentation](https://godoc.org/github.com/go-kit/kit/metrics). golang-github-go-kit-kit-0.13.0/metrics/cloudwatch/000077500000000000000000000000001443521372500221105ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/cloudwatch/cloudwatch.go000066400000000000000000000225641443521372500246050ustar00rootroot00000000000000package cloudwatch import ( "context" "fmt" "os" "strconv" "sync" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface" "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/generic" "github.com/go-kit/kit/metrics/internal/lv" "github.com/go-kit/log" ) const ( maxConcurrentRequests = 20 maxValuesInABatch = 150 ) // CloudWatch receives metrics observations and forwards them to CloudWatch. // Create a CloudWatch object, use it to create metrics, and pass those metrics as // dependencies to the components that will use them. // // To regularly report metrics to CloudWatch, use the WriteLoop helper method. type CloudWatch struct { mtx sync.RWMutex sem chan struct{} namespace string svc cloudwatchiface.CloudWatchAPI counters *lv.Space gauges *lv.Space histograms *lv.Space percentiles []float64 // percentiles to track logger log.Logger numConcurrentRequests int } // Option is a function adapter to change config of the CloudWatch struct type Option func(*CloudWatch) // WithLogger sets the Logger that will receive error messages generated // during the WriteLoop. By default, fmt logger is used. func WithLogger(logger log.Logger) Option { return func(c *CloudWatch) { c.logger = logger } } // WithPercentiles registers the percentiles to track, overriding the // existing/default values. // Reason is that Cloudwatch makes you pay per metric, so you can save half the money // by only using 2 metrics instead of the default 4. func WithPercentiles(percentiles ...float64) Option { return func(c *CloudWatch) { c.percentiles = make([]float64, 0, len(percentiles)) for _, p := range percentiles { if p < 0 || p > 1 { continue // illegal entry; ignore } c.percentiles = append(c.percentiles, p) } } } // WithConcurrentRequests sets the upper limit on how many // cloudwatch.PutMetricDataRequest may be under way at any // given time. If n is greater than 20, 20 is used. By default, // the max is set at 10 concurrent requests. func WithConcurrentRequests(n int) Option { return func(c *CloudWatch) { if n > maxConcurrentRequests { n = maxConcurrentRequests } c.numConcurrentRequests = n } } // New returns a CloudWatch object that may be used to create metrics. // Namespace is applied to all created metrics and maps to the CloudWatch namespace. // Callers must ensure that regular calls to Send are performed, either // manually or with one of the helper methods. func New(namespace string, svc cloudwatchiface.CloudWatchAPI, options ...Option) *CloudWatch { cw := &CloudWatch{ sem: nil, // set below namespace: namespace, svc: svc, counters: lv.NewSpace(), gauges: lv.NewSpace(), histograms: lv.NewSpace(), numConcurrentRequests: 10, logger: log.NewLogfmtLogger(os.Stderr), percentiles: []float64{0.50, 0.90, 0.95, 0.99}, } for _, opt := range options { opt(cw) } cw.sem = make(chan struct{}, cw.numConcurrentRequests) return cw } // NewCounter returns a counter. Observations are aggregated and emitted once // per write invocation. func (cw *CloudWatch) NewCounter(name string) metrics.Counter { return &Counter{ name: name, obs: cw.counters.Observe, } } // NewGauge returns an gauge. func (cw *CloudWatch) NewGauge(name string) metrics.Gauge { return &Gauge{ name: name, obs: cw.gauges.Observe, add: cw.gauges.Add, } } // NewHistogram returns a histogram. func (cw *CloudWatch) NewHistogram(name string) metrics.Histogram { return &Histogram{ name: name, obs: cw.histograms.Observe, } } // WriteLoop is a helper method that invokes Send every time the passed // channel fires. This method blocks until ctx is canceled, so clients // probably want to run it in its own goroutine. For typical usage, create a // time.Ticker and pass its C channel to this method. func (cw *CloudWatch) WriteLoop(ctx context.Context, c <-chan time.Time) { for { select { case <-c: if err := cw.Send(); err != nil { cw.logger.Log("during", "Send", "err", err) } case <-ctx.Done(): return } } } // Send will fire an API request to CloudWatch with the latest stats for // all metrics. It is preferred that the WriteLoop method is used. func (cw *CloudWatch) Send() error { cw.mtx.RLock() defer cw.mtx.RUnlock() now := time.Now() var datums []*cloudwatch.MetricDatum cw.counters.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool { value := sum(values) datums = append(datums, &cloudwatch.MetricDatum{ MetricName: aws.String(name), Dimensions: makeDimensions(lvs...), Value: aws.Float64(value), Timestamp: aws.Time(now), }) return true }) cw.gauges.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool { if len(values) == 0 { return true } datum := &cloudwatch.MetricDatum{ MetricName: aws.String(name), Dimensions: makeDimensions(lvs...), Timestamp: aws.Time(now), } // CloudWatch Put Metrics API (https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html) // expects batch of unique values including the array of corresponding counts valuesCounter := make(map[float64]int) for _, v := range values { valuesCounter[v]++ } for value, count := range valuesCounter { if len(datum.Values) == maxValuesInABatch { break } datum.Values = append(datum.Values, aws.Float64(value)) datum.Counts = append(datum.Counts, aws.Float64(float64(count))) } datums = append(datums, datum) return true }) // format a [0,1]-float value to a percentile value, with minimum nr of decimals // 0.90 -> "90" // 0.95 -> "95" // 0.999 -> "99.9" formatPerc := func(p float64) string { return strconv.FormatFloat(p*100, 'f', -1, 64) } cw.histograms.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool { histogram := generic.NewHistogram(name, 50) for _, v := range values { histogram.Observe(v) } for _, perc := range cw.percentiles { value := histogram.Quantile(perc) datums = append(datums, &cloudwatch.MetricDatum{ MetricName: aws.String(fmt.Sprintf("%s_%s", name, formatPerc(perc))), Dimensions: makeDimensions(lvs...), Value: aws.Float64(value), Timestamp: aws.Time(now), }) } return true }) var batches [][]*cloudwatch.MetricDatum for len(datums) > 0 { var batch []*cloudwatch.MetricDatum lim := min(len(datums), maxConcurrentRequests) batch, datums = datums[:lim], datums[lim:] batches = append(batches, batch) } var errors = make(chan error, len(batches)) for _, batch := range batches { go func(batch []*cloudwatch.MetricDatum) { cw.sem <- struct{}{} defer func() { <-cw.sem }() _, err := cw.svc.PutMetricData(&cloudwatch.PutMetricDataInput{ Namespace: aws.String(cw.namespace), MetricData: batch, }) errors <- err }(batch) } var firstErr error for i := 0; i < cap(errors); i++ { if err := <-errors; err != nil && firstErr == nil { firstErr = err } } return firstErr } func sum(a []float64) float64 { var v float64 for _, f := range a { v += f } return v } func min(a, b int) int { if a < b { return a } return b } type observeFunc func(name string, lvs lv.LabelValues, value float64) // Counter is a counter. Observations are forwarded to a node // object, and aggregated (summed) per timeseries. type Counter struct { name string lvs lv.LabelValues obs observeFunc } // With implements metrics.Counter. func (c *Counter) With(labelValues ...string) metrics.Counter { return &Counter{ name: c.name, lvs: c.lvs.With(labelValues...), obs: c.obs, } } // Add implements metrics.Counter. func (c *Counter) Add(delta float64) { c.obs(c.name, c.lvs, delta) } // Gauge is a gauge. Observations are forwarded to a node // object, and aggregated (the last observation selected) per timeseries. type Gauge struct { name string lvs lv.LabelValues obs observeFunc add observeFunc } // With implements metrics.Gauge. func (g *Gauge) With(labelValues ...string) metrics.Gauge { return &Gauge{ name: g.name, lvs: g.lvs.With(labelValues...), obs: g.obs, add: g.add, } } // Set implements metrics.Gauge. func (g *Gauge) Set(value float64) { g.obs(g.name, g.lvs, value) } // Add implements metrics.Gauge. func (g *Gauge) Add(delta float64) { g.add(g.name, g.lvs, delta) } // Histogram is an Influx histrogram. Observations are aggregated into a // generic.Histogram and emitted as per-quantile gauges to the Influx server. type Histogram struct { name string lvs lv.LabelValues obs observeFunc } // With implements metrics.Histogram. func (h *Histogram) With(labelValues ...string) metrics.Histogram { return &Histogram{ name: h.name, lvs: h.lvs.With(labelValues...), obs: h.obs, } } // Observe implements metrics.Histogram. func (h *Histogram) Observe(value float64) { h.obs(h.name, h.lvs, value) } func makeDimensions(labelValues ...string) []*cloudwatch.Dimension { dimensions := make([]*cloudwatch.Dimension, len(labelValues)/2) for i, j := 0, 0; i < len(labelValues); i, j = i+2, j+1 { dimensions[j] = &cloudwatch.Dimension{ Name: aws.String(labelValues[i]), Value: aws.String(labelValues[i+1]), } } return dimensions } golang-github-go-kit-kit-0.13.0/metrics/cloudwatch/cloudwatch_test.go000066400000000000000000000174251443521372500256440ustar00rootroot00000000000000package cloudwatch import ( "errors" "fmt" "strconv" "sync" "testing" "github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface" "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/teststat" "github.com/go-kit/log" ) const metricNameToGenerateError = "metric_name_used_to_throw_an_error" var errTest = errors.New("test error") type mockCloudWatch struct { cloudwatchiface.CloudWatchAPI mtx sync.RWMutex valuesReceived map[string][]float64 dimensionsReceived map[string][]*cloudwatch.Dimension } func newMockCloudWatch() *mockCloudWatch { return &mockCloudWatch{ valuesReceived: map[string][]float64{}, dimensionsReceived: map[string][]*cloudwatch.Dimension{}, } } func (mcw *mockCloudWatch) PutMetricData(input *cloudwatch.PutMetricDataInput) (*cloudwatch.PutMetricDataOutput, error) { mcw.mtx.Lock() defer mcw.mtx.Unlock() for _, datum := range input.MetricData { if *datum.MetricName == metricNameToGenerateError { return nil, errTest } if len(datum.Values) > 0 { for _, v := range datum.Values { mcw.valuesReceived[*datum.MetricName] = append(mcw.valuesReceived[*datum.MetricName], *v) } } else { mcw.valuesReceived[*datum.MetricName] = append(mcw.valuesReceived[*datum.MetricName], *datum.Value) } mcw.dimensionsReceived[*datum.MetricName] = datum.Dimensions } return nil, nil } func (mcw *mockCloudWatch) testDimensions(name string, labelValues ...string) error { mcw.mtx.RLock() _, hasValue := mcw.valuesReceived[name] if !hasValue { return nil // nothing to check; 0 samples were received } dimensions, ok := mcw.dimensionsReceived[name] mcw.mtx.RUnlock() if !ok { if len(labelValues) > 0 { return errors.New("Expected dimensions to be available, but none were") } } LabelValues: for i, j := 0, 0; i < len(labelValues); i, j = i+2, j+1 { name, value := labelValues[i], labelValues[i+1] for _, dimension := range dimensions { if *dimension.Name == name { if *dimension.Value == value { break LabelValues } } } return fmt.Errorf("could not find dimension with name %s and value %s", name, value) } return nil } func TestCounter(t *testing.T) { namespace, name := "abc", "def" label, value := "label", "value" svc := newMockCloudWatch() cw := New(namespace, svc, WithLogger(log.NewNopLogger())) counter := cw.NewCounter(name).With(label, value) valuef := func() float64 { if err := cw.Send(); err != nil { t.Fatal(err) } svc.mtx.RLock() defer svc.mtx.RUnlock() value := svc.valuesReceived[name][len(svc.valuesReceived[name])-1] delete(svc.valuesReceived, name) return value } if err := teststat.TestCounter(counter, valuef); err != nil { t.Fatal(err) } if err := teststat.TestCounter(counter, valuef); err != nil { t.Fatal("Fill and flush counter 2nd time: ", err) } if err := svc.testDimensions(name, label, value); err != nil { t.Fatal(err) } } func TestCounterLowSendConcurrency(t *testing.T) { namespace := "abc" var names, labels, values []string for i := 1; i <= 45; i++ { num := strconv.Itoa(i) names = append(names, "name"+num) labels = append(labels, "label"+num) values = append(values, "value"+num) } svc := newMockCloudWatch() cw := New(namespace, svc, WithLogger(log.NewNopLogger()), WithConcurrentRequests(2), ) counters := make(map[string]metrics.Counter) var wants []float64 for i, name := range names { counters[name] = cw.NewCounter(name).With(labels[i], values[i]) wants = append(wants, teststat.FillCounter(counters[name])) } if err := cw.Send(); err != nil { t.Fatal(err) } for i, name := range names { if l := len(svc.valuesReceived[name]); l == 0 && wants[i] == 0 { continue } else if l != 1 { t.Fatalf("one value expected, got %d", l) } if svc.valuesReceived[name][0] != wants[i] { t.Fatalf("want %f, have %f", wants[i], svc.valuesReceived[name]) } if err := svc.testDimensions(name, labels[i], values[i]); err != nil { t.Fatal(err) } } } func TestGauge(t *testing.T) { namespace, name := "abc", "def" label, value := "label", "value" svc := newMockCloudWatch() cw := New(namespace, svc, WithLogger(log.NewNopLogger())) gauge := cw.NewGauge(name).With(label, value) valuef := func() []float64 { if err := cw.Send(); err != nil { t.Fatal(err) } svc.mtx.RLock() defer svc.mtx.RUnlock() res := svc.valuesReceived[name] delete(svc.valuesReceived, name) return res } if err := teststat.TestGauge(gauge, valuef); err != nil { t.Fatal(err) } if err := svc.testDimensions(name, label, value); err != nil { t.Fatal(err) } } func TestHistogram(t *testing.T) { namespace, name := "abc", "def" label, value := "label", "value" svc := newMockCloudWatch() cw := New(namespace, svc, WithLogger(log.NewNopLogger())) histogram := cw.NewHistogram(name).With(label, value) n50 := fmt.Sprintf("%s_50", name) n90 := fmt.Sprintf("%s_90", name) n95 := fmt.Sprintf("%s_95", name) n99 := fmt.Sprintf("%s_99", name) quantiles := func() (p50, p90, p95, p99 float64) { err := cw.Send() if err != nil { t.Fatal(err) } svc.mtx.RLock() defer svc.mtx.RUnlock() if len(svc.valuesReceived[n50]) > 0 { p50 = svc.valuesReceived[n50][0] delete(svc.valuesReceived, n50) } if len(svc.valuesReceived[n90]) > 0 { p90 = svc.valuesReceived[n90][0] delete(svc.valuesReceived, n90) } if len(svc.valuesReceived[n95]) > 0 { p95 = svc.valuesReceived[n95][0] delete(svc.valuesReceived, n95) } if len(svc.valuesReceived[n99]) > 0 { p99 = svc.valuesReceived[n99][0] delete(svc.valuesReceived, n99) } return } if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil { t.Fatal(err) } if err := svc.testDimensions(n50, label, value); err != nil { t.Fatal(err) } if err := svc.testDimensions(n90, label, value); err != nil { t.Fatal(err) } if err := svc.testDimensions(n95, label, value); err != nil { t.Fatal(err) } if err := svc.testDimensions(n99, label, value); err != nil { t.Fatal(err) } // now test with only 2 custom percentiles // svc = newMockCloudWatch() cw = New(namespace, svc, WithLogger(log.NewNopLogger()), WithPercentiles(0.50, 0.90)) histogram = cw.NewHistogram(name).With(label, value) customQuantiles := func() (p50, p90, p95, p99 float64) { err := cw.Send() if err != nil { t.Fatal(err) } svc.mtx.RLock() defer svc.mtx.RUnlock() if len(svc.valuesReceived[n50]) > 0 { p50 = svc.valuesReceived[n50][0] delete(svc.valuesReceived, n50) } if len(svc.valuesReceived[n90]) > 0 { p90 = svc.valuesReceived[n90][0] delete(svc.valuesReceived, n90) } // our teststat.TestHistogram wants us to give p95 and p99, // but with custom percentiles we don't have those. // So fake them. Maybe we should make teststat.nvq() public and use that? p95 = 541.121341 p99 = 558.158697 // but fail if they are actually set (because that would mean the // WithPercentiles() is not respected) if _, isSet := svc.valuesReceived[n95]; isSet { t.Fatal("p95 should not be set") } if _, isSet := svc.valuesReceived[n99]; isSet { t.Fatal("p99 should not be set") } return } if err := teststat.TestHistogram(histogram, customQuantiles, 0.01); err != nil { t.Fatal(err) } if err := svc.testDimensions(n50, label, value); err != nil { t.Fatal(err) } if err := svc.testDimensions(n90, label, value); err != nil { t.Fatal(err) } if err := svc.testDimensions(n95, label, value); err != nil { t.Fatal(err) } if err := svc.testDimensions(n99, label, value); err != nil { t.Fatal(err) } } func TestErrorLog(t *testing.T) { namespace := "abc" svc := newMockCloudWatch() cw := New(namespace, svc, WithLogger(log.NewNopLogger())) cw.NewGauge(metricNameToGenerateError).Set(123) if err := cw.Send(); err != errTest { t.Fatal("Expected error, but didn't get one") } } golang-github-go-kit-kit-0.13.0/metrics/cloudwatch2/000077500000000000000000000000001443521372500221725ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/cloudwatch2/cloudwatch2.go000066400000000000000000000154401443521372500247440ustar00rootroot00000000000000// Package cloudwatch2 emits all data as a StatisticsSet (rather than // a singular Value) to CloudWatch via the aws-sdk-go-v2 SDK. package cloudwatch2 import ( "context" "math" "sync" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatch" "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" "golang.org/x/sync/errgroup" "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/internal/convert" "github.com/go-kit/kit/metrics/internal/lv" "github.com/go-kit/log" ) const ( maxConcurrentRequests = 20 ) // CloudWatchAPI is an interface that defines the set of Amazon CloudWatch API operations required by CloudWatch. type CloudWatchAPI interface { PutMetricData(ctx context.Context, params *cloudwatch.PutMetricDataInput, optFns ...func(*cloudwatch.Options)) (*cloudwatch.PutMetricDataOutput, error) } // CloudWatch receives metrics observations and forwards them to CloudWatch. // Create a CloudWatch object, use it to create metrics, and pass those metrics as // dependencies to the components that will use them. // // To regularly report metrics to CloudWatch, use the WriteLoop helper method. type CloudWatch struct { mtx sync.RWMutex sem chan struct{} namespace string svc CloudWatchAPI counters *lv.Space logger log.Logger numConcurrentRequests int } // Option is a function adapter to change config of the CloudWatch struct type Option func(*CloudWatch) // WithLogger sets the Logger that will receive error messages generated // during the WriteLoop. By default, no logger is used. func WithLogger(logger log.Logger) Option { return func(cw *CloudWatch) { cw.logger = logger } } // WithConcurrentRequests sets the upper limit on how many // cloudwatch.PutMetricDataRequest may be under way at any // given time. If n is greater than 20, 20 is used. By default, // the max is set at 10 concurrent requests. func WithConcurrentRequests(n int) Option { return func(cw *CloudWatch) { if n > maxConcurrentRequests { n = maxConcurrentRequests } cw.numConcurrentRequests = n } } // New returns a CloudWatch object that may be used to create metrics. // Namespace is applied to all created metrics and maps to the CloudWatch namespace. // Callers must ensure that regular calls to Send are performed, either // manually or with one of the helper methods. func New(namespace string, svc CloudWatchAPI, options ...Option) *CloudWatch { cw := &CloudWatch{ namespace: namespace, svc: svc, counters: lv.NewSpace(), numConcurrentRequests: 10, logger: log.NewNopLogger(), } for _, optFunc := range options { optFunc(cw) } cw.sem = make(chan struct{}, cw.numConcurrentRequests) return cw } // NewCounter returns a counter. Observations are aggregated and emitted once // per write invocation. func (cw *CloudWatch) NewCounter(name string) metrics.Counter { return &Counter{ name: name, obs: cw.counters.Observe, } } // NewGauge returns an gauge. Under the covers, there is no distinctions // in CloudWatch for how Counters/Histograms/Gauges are reported, so this // just wraps a cloudwatch2.Counter. func (cw *CloudWatch) NewGauge(name string) metrics.Gauge { return convert.NewCounterAsGauge(cw.NewCounter(name)) } // NewHistogram returns a histogram. Under the covers, there is no distinctions // in CloudWatch for how Counters/Histograms/Gauges are reported, so this // just wraps a cloudwatch2.Counter. func (cw *CloudWatch) NewHistogram(name string) metrics.Histogram { return convert.NewCounterAsHistogram(cw.NewCounter(name)) } // WriteLoop is a helper method that invokes Send every time the passed // channel fires. This method blocks until ctx is canceled, so clients // probably want to run it in its own goroutine. For typical usage, create a // time.Ticker and pass its C channel to this method. func (cw *CloudWatch) WriteLoop(ctx context.Context, c <-chan time.Time) { for { select { case <-c: if err := cw.Send(); err != nil { cw.logger.Log("during", "Send", "err", err) } case <-ctx.Done(): return } } } // Send will fire an API request to CloudWatch with the latest stats for // all metrics. It is preferred that the WriteLoop method is used. func (cw *CloudWatch) Send() error { cw.mtx.RLock() defer cw.mtx.RUnlock() now := time.Now() var datums []types.MetricDatum cw.counters.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool { datums = append(datums, types.MetricDatum{ MetricName: aws.String(name), Dimensions: makeDimensions(lvs...), StatisticValues: stats(values), Timestamp: aws.Time(now), }) return true }) var batches [][]types.MetricDatum for len(datums) > 0 { var batch []types.MetricDatum lim := len(datums) if lim > maxConcurrentRequests { lim = maxConcurrentRequests } batch, datums = datums[:lim], datums[lim:] batches = append(batches, batch) } var g errgroup.Group for _, batch := range batches { batch := batch g.Go(func() error { cw.sem <- struct{}{} defer func() { <-cw.sem }() _, err := cw.svc.PutMetricData(context.TODO(), &cloudwatch.PutMetricDataInput{ Namespace: aws.String(cw.namespace), MetricData: batch, }) return err }) } return g.Wait() } var zero = float64(0.0) // Just build this once to reduce construction costs whenever // someone does a Send with no aggregated values. var zeros = types.StatisticSet{ Maximum: &zero, Minimum: &zero, Sum: &zero, SampleCount: &zero, } func stats(a []float64) *types.StatisticSet { count := float64(len(a)) if count == 0 { return &zeros } var sum float64 var min = math.MaxFloat64 var max = math.MaxFloat64 * -1 for _, f := range a { sum += f if f < min { min = f } if f > max { max = f } } return &types.StatisticSet{ Maximum: &max, Minimum: &min, Sum: &sum, SampleCount: &count, } } func makeDimensions(labelValues ...string) []types.Dimension { dimensions := make([]types.Dimension, len(labelValues)/2) for i, j := 0, 0; i < len(labelValues); i, j = i+2, j+1 { dimensions[j] = types.Dimension{ Name: aws.String(labelValues[i]), Value: aws.String(labelValues[i+1]), } } return dimensions } type observeFunc func(name string, lvs lv.LabelValues, value float64) // Counter is a counter. Observations are forwarded to a node // object, and aggregated per timeseries. type Counter struct { name string lvs lv.LabelValues obs observeFunc } // With implements metrics.Counter. func (c *Counter) With(labelValues ...string) metrics.Counter { return &Counter{ name: c.name, lvs: c.lvs.With(labelValues...), obs: c.obs, } } // Add implements metrics.Counter. func (c *Counter) Add(delta float64) { c.obs(c.name, c.lvs, delta) } golang-github-go-kit-kit-0.13.0/metrics/cloudwatch2/cloudwatch2_test.go000066400000000000000000000054651443521372500260110ustar00rootroot00000000000000package cloudwatch2 import ( "context" "strings" "testing" "github.com/aws/aws-sdk-go-v2/service/cloudwatch" "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" ) func TestStats(t *testing.T) { testCases := []struct { name string vals []float64 xMin float64 xMax float64 xSum float64 xCt float64 }{ { "empty", []float64{}, 0.0, 0.0, 0.0, 0.0, }, { "single", []float64{3.1416}, 3.1416, 3.1416, 3.1416, 1.0, }, { "double", []float64{1.0, 9.0}, 1.0, 9.0, 10.0, 2.0, }, { "multiple", []float64{5.0, 1.0, 9.0, 5.0}, 1.0, 9.0, 20.0, 4.0, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { s := stats(tc.vals) if tc.xMin != *s.Minimum { t.Errorf("expected [%f]: %f\n", tc.xMin, *s.Minimum) } if tc.xMax != *s.Maximum { t.Errorf("expected [%f]: %f\n", tc.xMax, *s.Maximum) } if tc.xSum != *s.Sum { t.Errorf("expected [%f]: %f\n", tc.xSum, *s.Sum) } if tc.xCt != *s.SampleCount { t.Errorf("expected [%f]: %f\n", tc.xCt, *s.SampleCount) } }) } } type mockCloudWatch struct { CloudWatchAPI latestName string latestData []types.MetricDatum } func (mcw *mockCloudWatch) PutMetricData(ctx context.Context, params *cloudwatch.PutMetricDataInput, optFns ...func(*cloudwatch.Options)) (*cloudwatch.PutMetricDataOutput, error) { mcw.latestName = *params.Namespace mcw.latestData = params.MetricData return nil, nil } func TestSend(t *testing.T) { ns := "example-namespace" svc := &mockCloudWatch{} cw := New(ns, svc) c := cw.NewCounter("c").With("charlie", "cat") h := cw.NewHistogram("h").With("hotel", "horse") g := cw.NewGauge("g").With("golf", "giraffe") c.Add(4.0) c.Add(5.0) c.Add(6.0) h.Observe(3.0) h.Observe(5.0) h.Observe(7.0) g.Set(2.0) g.Set(5.0) g.Set(8.0) err := cw.Send() if err != nil { t.Fatalf("unexpected: %v\n", err) } if ns != svc.latestName { t.Errorf("expected namespace %q; not %q\n", ns, svc.latestName) } if len(svc.latestData) != 3 { t.Errorf("expected 3 datums: %v\n", svc.latestData) } for _, datum := range svc.latestData { initial := *datum.MetricName if len(datum.Dimensions) != 1 { t.Errorf("expected 1 dimension: %v\n", datum) } if !strings.HasPrefix(*datum.Dimensions[0].Name, initial) { t.Errorf("expected %q in Name of %v\n", initial, datum.Dimensions) } if !strings.HasPrefix(*datum.Dimensions[0].Value, initial) { t.Errorf("expected %q in Value of %v\n", initial, datum.Dimensions) } if datum.StatisticValues == nil { t.Errorf("expected StatisticValues in %v\n", datum) } if *datum.StatisticValues.Sum != 15.0 { t.Errorf("expected 15.0 for Sum in %v\n", datum) } if *datum.StatisticValues.SampleCount != 3.0 { t.Errorf("expected 3.0 for SampleCount in %v\n", datum) } } } golang-github-go-kit-kit-0.13.0/metrics/discard/000077500000000000000000000000001443521372500213645ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/discard/discard.go000066400000000000000000000020501443521372500233210ustar00rootroot00000000000000// Package discard provides a no-op metrics backend. package discard import "github.com/go-kit/kit/metrics" type counter struct{} // NewCounter returns a new no-op counter. func NewCounter() metrics.Counter { return counter{} } // With implements Counter. func (c counter) With(labelValues ...string) metrics.Counter { return c } // Add implements Counter. func (c counter) Add(delta float64) {} type gauge struct{} // NewGauge returns a new no-op gauge. func NewGauge() metrics.Gauge { return gauge{} } // With implements Gauge. func (g gauge) With(labelValues ...string) metrics.Gauge { return g } // Set implements Gauge. func (g gauge) Set(value float64) {} // Add implements metrics.Gauge. func (g gauge) Add(delta float64) {} type histogram struct{} // NewHistogram returns a new no-op histogram. func NewHistogram() metrics.Histogram { return histogram{} } // With implements Histogram. func (h histogram) With(labelValues ...string) metrics.Histogram { return h } // Observe implements histogram. func (h histogram) Observe(value float64) {} golang-github-go-kit-kit-0.13.0/metrics/doc.go000066400000000000000000000111041443521372500210440ustar00rootroot00000000000000// Package metrics provides a framework for application instrumentation. It's // primarily designed to help you get started with good and robust // instrumentation, and to help you migrate from a less-capable system like // Graphite to a more-capable system like Prometheus. If your organization has // already standardized on an instrumentation system like Prometheus, and has no // plans to change, it may make sense to use that system's instrumentation // library directly. // // This package provides three core metric abstractions (Counter, Gauge, and // Histogram) and implementations for almost all common instrumentation // backends. Each metric has an observation method (Add, Set, or Observe, // respectively) used to record values, and a With method to "scope" the // observation by various parameters. For example, you might have a Histogram to // record request durations, parameterized by the method that's being called. // // var requestDuration metrics.Histogram // // ... // requestDuration.With("method", "MyMethod").Observe(time.Since(begin)) // // This allows a single high-level metrics object (requestDuration) to work with // many code paths somewhat dynamically. The concept of With is fully supported // in some backends like Prometheus, and not supported in other backends like // Graphite. So, With may be a no-op, depending on the concrete implementation // you choose. Please check the implementation to know for sure. For // implementations that don't provide With, it's necessary to fully parameterize // each metric in the metric name, e.g. // // // Statsd // c := statsd.NewCounter("request_duration_MyMethod_200") // c.Add(1) // // // Prometheus // c := prometheus.NewCounter(stdprometheus.CounterOpts{ // Name: "request_duration", // ... // }, []string{"method", "status_code"}) // c.With("method", "MyMethod", "status_code", strconv.Itoa(code)).Add(1) // // Usage // // Metrics are dependencies, and should be passed to the components that need // them in the same way you'd construct and pass a database handle, or reference // to another component. Metrics should *not* be created in the global scope. // Instead, instantiate metrics in your func main, using whichever concrete // implementation is appropriate for your organization. // // latency := prometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ // Namespace: "myteam", // Subsystem: "foosvc", // Name: "request_latency_seconds", // Help: "Incoming request latency in seconds.", // }, []string{"method", "status_code"}) // // Write your components to take the metrics they will use as parameters to // their constructors. Use the interface types, not the concrete types. That is, // // // NewAPI takes metrics.Histogram, not *prometheus.Summary // func NewAPI(s Store, logger log.Logger, latency metrics.Histogram) *API { // // ... // } // // func (a *API) ServeFoo(w http.ResponseWriter, r *http.Request) { // begin := time.Now() // // ... // a.latency.Observe(time.Since(begin).Seconds()) // } // // Finally, pass the metrics as dependencies when building your object graph. // This should happen in func main, not in the global scope. // // api := NewAPI(store, logger, latency) // http.ListenAndServe("/", api) // // Note that metrics are "write-only" interfaces. // // Implementation details // // All metrics are safe for concurrent use. Considerable design influence has // been taken from https://github.com/codahale/metrics and // https://prometheus.io. // // Each telemetry system has different semantics for label values, push vs. // pull, support for histograms, etc. These properties influence the design of // their respective packages. This table attempts to summarize the key points of // distinction. // // SYSTEM DIM COUNTERS GAUGES HISTOGRAMS // dogstatsd n batch, push-aggregate batch, push-aggregate native, batch, push-each // statsd 1 batch, push-aggregate batch, push-aggregate native, batch, push-each // graphite 1 batch, push-aggregate batch, push-aggregate synthetic, batch, push-aggregate // expvar 1 atomic atomic synthetic, batch, in-place expose // influx n custom custom custom // prometheus n native native native // pcp 1 native native native // cloudwatch n batch push-aggregate batch push-aggregate synthetic, batch, push-aggregate // package metrics golang-github-go-kit-kit-0.13.0/metrics/dogstatsd/000077500000000000000000000000001443521372500217475ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/dogstatsd/dogstatsd.go000066400000000000000000000256701443521372500243040ustar00rootroot00000000000000// Package dogstatsd provides a DogStatsD backend for package metrics. It's very // similar to StatsD, but supports arbitrary tags per-metric, which map to Go // kit's label values. So, while label values are no-ops in StatsD, they are // supported here. For more details, see the documentation at // http://docs.datadoghq.com/guides/dogstatsd/. // // This package batches observations and emits them on some schedule to the // remote server. This is useful even if you connect to your DogStatsD server // over UDP. Emitting one network packet per observation can quickly overwhelm // even the fastest internal network. package dogstatsd import ( "context" "fmt" "io" "math/rand" "strings" "sync" "sync/atomic" "time" "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/generic" "github.com/go-kit/kit/metrics/internal/lv" "github.com/go-kit/kit/metrics/internal/ratemap" "github.com/go-kit/kit/util/conn" "github.com/go-kit/log" ) // Dogstatsd receives metrics observations and forwards them to a DogStatsD // server. Create a Dogstatsd object, use it to create metrics, and pass those // metrics as dependencies to the components that will use them. // // All metrics are buffered until WriteTo is called. Counters and gauges are // aggregated into a single observation per timeseries per write. Timings and // histograms are buffered but not aggregated. // // To regularly report metrics to an io.Writer, use the WriteLoop helper method. // To send to a DogStatsD server, use the SendLoop helper method. type Dogstatsd struct { mtx sync.RWMutex prefix string rates *ratemap.RateMap counters *lv.Space gauges map[string]*gaugeNode timings *lv.Space histograms *lv.Space logger log.Logger lvs lv.LabelValues } // New returns a Dogstatsd object that may be used to create metrics. Prefix is // applied to all created metrics. Callers must ensure that regular calls to // WriteTo are performed, either manually or with one of the helper methods. func New(prefix string, logger log.Logger, lvs ...string) *Dogstatsd { if len(lvs)%2 != 0 { panic("odd number of LabelValues; programmer error!") } return &Dogstatsd{ prefix: prefix, rates: ratemap.New(), counters: lv.NewSpace(), gauges: map[string]*gaugeNode{}, timings: lv.NewSpace(), histograms: lv.NewSpace(), logger: logger, lvs: lvs, } } // NewCounter returns a counter, sending observations to this Dogstatsd object. func (d *Dogstatsd) NewCounter(name string, sampleRate float64) *Counter { d.rates.Set(name, sampleRate) return &Counter{ name: name, obs: sampleObservations(d.counters.Observe, sampleRate), } } // NewGauge returns a gauge, sending observations to this Dogstatsd object. func (d *Dogstatsd) NewGauge(name string) *Gauge { d.mtx.Lock() n, ok := d.gauges[name] if !ok { n = &gaugeNode{gauge: &Gauge{g: generic.NewGauge(name), ddog: d}} d.gauges[name] = n } d.mtx.Unlock() return n.gauge } // NewTiming returns a histogram whose observations are interpreted as // millisecond durations, and are forwarded to this Dogstatsd object. func (d *Dogstatsd) NewTiming(name string, sampleRate float64) *Timing { d.rates.Set(name, sampleRate) return &Timing{ name: name, obs: sampleObservations(d.timings.Observe, sampleRate), } } // NewHistogram returns a histogram whose observations are of an unspecified // unit, and are forwarded to this Dogstatsd object. func (d *Dogstatsd) NewHistogram(name string, sampleRate float64) *Histogram { d.rates.Set(name, sampleRate) return &Histogram{ name: name, obs: sampleObservations(d.histograms.Observe, sampleRate), } } // WriteLoop is a helper method that invokes WriteTo to the passed writer every // time the passed channel fires. This method blocks until ctx is canceled, // so clients probably want to run it in its own goroutine. For typical // usage, create a time.Ticker and pass its C channel to this method. func (d *Dogstatsd) WriteLoop(ctx context.Context, c <-chan time.Time, w io.Writer) { for { select { case <-c: if _, err := d.WriteTo(w); err != nil { d.logger.Log("during", "WriteTo", "err", err) } case <-ctx.Done(): return } } } // SendLoop is a helper method that wraps WriteLoop, passing a managed // connection to the network and address. Like WriteLoop, this method blocks // until ctx is canceled, so clients probably want to start it in its own // goroutine. For typical usage, create a time.Ticker and pass its C channel to // this method. func (d *Dogstatsd) SendLoop(ctx context.Context, c <-chan time.Time, network, address string) { d.WriteLoop(ctx, c, conn.NewDefaultManager(network, address, d.logger)) } // WriteTo flushes the buffered content of the metrics to the writer, in // DogStatsD format. WriteTo abides best-effort semantics, so observations are // lost if there is a problem with the write. Clients should be sure to call // WriteTo regularly, ideally through the WriteLoop or SendLoop helper methods. func (d *Dogstatsd) WriteTo(w io.Writer) (count int64, err error) { var n int d.counters.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool { n, err = fmt.Fprintf(w, "%s%s:%f|c%s%s\n", d.prefix, name, sum(values), sampling(d.rates.Get(name)), d.tagValues(lvs)) if err != nil { return false } count += int64(n) return true }) if err != nil { return count, err } d.mtx.RLock() for _, root := range d.gauges { root.walk(func(name string, lvs lv.LabelValues, value float64) bool { n, err = fmt.Fprintf(w, "%s%s:%f|g%s\n", d.prefix, name, value, d.tagValues(lvs)) if err != nil { return false } count += int64(n) return true }) } d.mtx.RUnlock() d.timings.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool { sampleRate := d.rates.Get(name) for _, value := range values { n, err = fmt.Fprintf(w, "%s%s:%f|ms%s%s\n", d.prefix, name, value, sampling(sampleRate), d.tagValues(lvs)) if err != nil { return false } count += int64(n) } return true }) if err != nil { return count, err } d.histograms.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool { sampleRate := d.rates.Get(name) for _, value := range values { n, err = fmt.Fprintf(w, "%s%s:%f|h%s%s\n", d.prefix, name, value, sampling(sampleRate), d.tagValues(lvs)) if err != nil { return false } count += int64(n) } return true }) if err != nil { return count, err } return count, err } func sum(a []float64) float64 { var v float64 for _, f := range a { v += f } return v } func sampling(r float64) string { var sv string if r < 1.0 { sv = fmt.Sprintf("|@%f", r) } return sv } func (d *Dogstatsd) tagValues(labelValues []string) string { if len(labelValues) == 0 && len(d.lvs) == 0 { return "" } if len(labelValues)%2 != 0 { panic("tagValues received a labelValues with an odd number of strings") } pairs := make([]string, 0, (len(d.lvs)+len(labelValues))/2) for i := 0; i < len(d.lvs); i += 2 { pairs = append(pairs, d.lvs[i]+":"+d.lvs[i+1]) } for i := 0; i < len(labelValues); i += 2 { pairs = append(pairs, labelValues[i]+":"+labelValues[i+1]) } return "|#" + strings.Join(pairs, ",") } type observeFunc func(name string, lvs lv.LabelValues, value float64) // sampleObservations returns a modified observeFunc that samples observations. func sampleObservations(obs observeFunc, sampleRate float64) observeFunc { if sampleRate >= 1 { return obs } return func(name string, lvs lv.LabelValues, value float64) { if rand.Float64() > sampleRate { return } obs(name, lvs, value) } } // Counter is a DogStatsD counter. Observations are forwarded to a Dogstatsd // object, and aggregated (summed) per timeseries. type Counter struct { name string lvs lv.LabelValues obs observeFunc } // With implements metrics.Counter. func (c *Counter) With(labelValues ...string) metrics.Counter { return &Counter{ name: c.name, lvs: c.lvs.With(labelValues...), obs: c.obs, } } // Add implements metrics.Counter. func (c *Counter) Add(delta float64) { c.obs(c.name, c.lvs, delta) } // Gauge is a DogStatsD gauge. Observations are forwarded to a Dogstatsd // object, and aggregated (the last observation selected) per timeseries. type Gauge struct { g *generic.Gauge ddog *Dogstatsd set int32 } // With implements metrics.Gauge. func (g *Gauge) With(labelValues ...string) metrics.Gauge { g.ddog.mtx.RLock() node := g.ddog.gauges[g.g.Name] g.ddog.mtx.RUnlock() ga := &Gauge{g: g.g.With(labelValues...).(*generic.Gauge), ddog: g.ddog} return node.addGauge(ga, ga.g.LabelValues()) } // Set implements metrics.Gauge. func (g *Gauge) Set(value float64) { g.g.Set(value) g.touch() } // Add implements metrics.Gauge. func (g *Gauge) Add(delta float64) { g.g.Add(delta) g.touch() } // Timing is a DogStatsD timing, or metrics.Histogram. Observations are // forwarded to a Dogstatsd object, and collected (but not aggregated) per // timeseries. type Timing struct { name string lvs lv.LabelValues obs observeFunc } // With implements metrics.Timing. func (t *Timing) With(labelValues ...string) metrics.Histogram { return &Timing{ name: t.name, lvs: t.lvs.With(labelValues...), obs: t.obs, } } // Observe implements metrics.Histogram. Value is interpreted as milliseconds. func (t *Timing) Observe(value float64) { t.obs(t.name, t.lvs, value) } // Histogram is a DogStatsD histrogram. Observations are forwarded to a // Dogstatsd object, and collected (but not aggregated) per timeseries. type Histogram struct { name string lvs lv.LabelValues obs observeFunc } // With implements metrics.Histogram. func (h *Histogram) With(labelValues ...string) metrics.Histogram { return &Histogram{ name: h.name, lvs: h.lvs.With(labelValues...), obs: h.obs, } } // Observe implements metrics.Histogram. func (h *Histogram) Observe(value float64) { h.obs(h.name, h.lvs, value) } type pair struct{ label, value string } type gaugeNode struct { mtx sync.RWMutex gauge *Gauge children map[pair]*gaugeNode } func (n *gaugeNode) addGauge(g *Gauge, lvs lv.LabelValues) *Gauge { n.mtx.Lock() defer n.mtx.Unlock() if len(lvs) == 0 { if n.gauge == nil { n.gauge = g } return n.gauge } if len(lvs) < 2 { panic("too few LabelValues; programmer error!") } head, tail := pair{lvs[0], lvs[1]}, lvs[2:] if n.children == nil { n.children = map[pair]*gaugeNode{} } child, ok := n.children[head] if !ok { child = &gaugeNode{} n.children[head] = child } return child.addGauge(g, tail) } func (n *gaugeNode) walk(fn func(string, lv.LabelValues, float64) bool) bool { n.mtx.RLock() defer n.mtx.RUnlock() if n.gauge != nil { value, ok := n.gauge.read() if ok && !fn(n.gauge.g.Name, n.gauge.g.LabelValues(), value) { return false } } for _, child := range n.children { if !child.walk(fn) { return false } } return true } func (g *Gauge) touch() { atomic.StoreInt32(&(g.set), 1) } func (g *Gauge) read() (float64, bool) { set := atomic.SwapInt32(&(g.set), 0) return g.g.Value(), set != 0 } golang-github-go-kit-kit-0.13.0/metrics/dogstatsd/dogstatsd_test.go000066400000000000000000000060171443521372500253350ustar00rootroot00000000000000package dogstatsd import ( "testing" "github.com/go-kit/kit/metrics/teststat" "github.com/go-kit/log" ) func TestCounter(t *testing.T) { prefix, name := "abc.", "def" label, value := "label", "value" regex := `^` + prefix + name + `:([0-9\.]+)\|c\|#` + label + `:` + value + `$` d := New(prefix, log.NewNopLogger()) counter := d.NewCounter(name, 1.0).With(label, value) valuef := teststat.SumLines(d, regex) if err := teststat.TestCounter(counter, valuef); err != nil { t.Fatal(err) } } func TestCounterSampled(t *testing.T) { // This will involve multiplying the observed sum by the inverse of the // sample rate and checking against the expected value within some // tolerance. t.Skip("TODO") } func TestGauge(t *testing.T) { prefix, name := "ghi.", "jkl" label, value := "xyz", "abc" regex := `^` + prefix + name + `:([0-9\.]+)\|g\|#hostname:foohost,` + label + `:` + value + `$` d := New(prefix, log.NewNopLogger(), "hostname", "foohost") gauge := d.NewGauge(name).With(label, value) valuef := teststat.LastLine(d, regex) if err := teststat.TestGauge(gauge, valuef); err != nil { t.Fatal(err) } } // DogStatsD histograms just emit all observations. So, we collect them into // a generic histogram, and run the statistics test on that. func TestHistogram(t *testing.T) { prefix, name := "dogstatsd.", "histogram_test" label, value := "abc", "def" regex := `^` + prefix + name + `:([0-9\.]+)\|h\|#` + label + `:` + value + `$` d := New(prefix, log.NewNopLogger()) histogram := d.NewHistogram(name, 1.0).With(label, value) quantiles := teststat.Quantiles(d, regex, 50) // no |@0.X if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil { t.Fatal(err) } } func TestHistogramSampled(t *testing.T) { prefix, name := "dogstatsd.", "sampled_histogram_test" label, value := "foo", "bar" regex := `^` + prefix + name + `:([0-9\.]+)\|h\|@0\.01[0]*\|#` + label + `:` + value + `$` d := New(prefix, log.NewNopLogger()) histogram := d.NewHistogram(name, 0.01).With(label, value) quantiles := teststat.Quantiles(d, regex, 50) if err := teststat.TestHistogram(histogram, quantiles, 0.02); err != nil { t.Fatal(err) } } func TestTiming(t *testing.T) { prefix, name := "dogstatsd.", "timing_test" label, value := "wiggle", "bottom" regex := `^` + prefix + name + `:([0-9\.]+)\|ms\|#` + label + `:` + value + `$` d := New(prefix, log.NewNopLogger()) histogram := d.NewTiming(name, 1.0).With(label, value) quantiles := teststat.Quantiles(d, regex, 50) // no |@0.X if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil { t.Fatal(err) } } func TestTimingSampled(t *testing.T) { prefix, name := "dogstatsd.", "sampled_timing_test" label, value := "internal", "external" regex := `^` + prefix + name + `:([0-9\.]+)\|ms\|@0.03[0]*\|#` + label + `:` + value + `$` d := New(prefix, log.NewNopLogger()) histogram := d.NewTiming(name, 0.03).With(label, value) quantiles := teststat.Quantiles(d, regex, 50) if err := teststat.TestHistogram(histogram, quantiles, 0.02); err != nil { t.Fatal(err) } } golang-github-go-kit-kit-0.13.0/metrics/expvar/000077500000000000000000000000001443521372500212605ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/expvar/expvar.go000066400000000000000000000050401443521372500231130ustar00rootroot00000000000000// Package expvar provides expvar backends for metrics. // Label values are not supported. package expvar import ( "expvar" "sync" "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/generic" ) // Counter implements the counter metric with an expvar float. // Label values are not supported. type Counter struct { f *expvar.Float } // NewCounter creates an expvar Float with the given name, and returns an object // that implements the Counter interface. func NewCounter(name string) *Counter { return &Counter{ f: expvar.NewFloat(name), } } // With is a no-op. func (c *Counter) With(labelValues ...string) metrics.Counter { return c } // Add implements Counter. func (c *Counter) Add(delta float64) { c.f.Add(delta) } // Gauge implements the gauge metric with an expvar float. // Label values are not supported. type Gauge struct { f *expvar.Float } // NewGauge creates an expvar Float with the given name, and returns an object // that implements the Gauge interface. func NewGauge(name string) *Gauge { return &Gauge{ f: expvar.NewFloat(name), } } // With is a no-op. func (g *Gauge) With(labelValues ...string) metrics.Gauge { return g } // Set implements Gauge. func (g *Gauge) Set(value float64) { g.f.Set(value) } // Add implements metrics.Gauge. func (g *Gauge) Add(delta float64) { g.f.Add(delta) } // Histogram implements the histogram metric with a combination of the generic // Histogram object and several expvar Floats, one for each of the 50th, 90th, // 95th, and 99th quantiles of observed values, with the quantile attached to // the name as a suffix. Label values are not supported. type Histogram struct { mtx sync.Mutex h *generic.Histogram p50 *expvar.Float p90 *expvar.Float p95 *expvar.Float p99 *expvar.Float } // NewHistogram returns a Histogram object with the given name and number of // buckets in the underlying histogram object. 50 is a good default number of // buckets. func NewHistogram(name string, buckets int) *Histogram { return &Histogram{ h: generic.NewHistogram(name, buckets), p50: expvar.NewFloat(name + ".p50"), p90: expvar.NewFloat(name + ".p90"), p95: expvar.NewFloat(name + ".p95"), p99: expvar.NewFloat(name + ".p99"), } } // With is a no-op. func (h *Histogram) With(labelValues ...string) metrics.Histogram { return h } // Observe implements Histogram. func (h *Histogram) Observe(value float64) { h.mtx.Lock() defer h.mtx.Unlock() h.h.Observe(value) h.p50.Set(h.h.Quantile(0.50)) h.p90.Set(h.h.Quantile(0.90)) h.p95.Set(h.h.Quantile(0.95)) h.p99.Set(h.h.Quantile(0.99)) } golang-github-go-kit-kit-0.13.0/metrics/expvar/expvar_test.go000066400000000000000000000023321443521372500241530ustar00rootroot00000000000000package expvar import ( "strconv" "testing" "github.com/go-kit/kit/metrics/teststat" ) func TestCounter(t *testing.T) { counter := NewCounter("expvar_counter").With("label values", "not supported").(*Counter) value := func() float64 { f, _ := strconv.ParseFloat(counter.f.String(), 64); return f } if err := teststat.TestCounter(counter, value); err != nil { t.Fatal(err) } } func TestGauge(t *testing.T) { gauge := NewGauge("expvar_gauge").With("label values", "not supported").(*Gauge) value := func() []float64 { f, _ := strconv.ParseFloat(gauge.f.String(), 64); return []float64{f} } if err := teststat.TestGauge(gauge, value); err != nil { t.Fatal(err) } } func TestHistogram(t *testing.T) { histogram := NewHistogram("expvar_histogram", 50).With("label values", "not supported").(*Histogram) quantiles := func() (float64, float64, float64, float64) { p50, _ := strconv.ParseFloat(histogram.p50.String(), 64) p90, _ := strconv.ParseFloat(histogram.p90.String(), 64) p95, _ := strconv.ParseFloat(histogram.p95.String(), 64) p99, _ := strconv.ParseFloat(histogram.p99.String(), 64) return p50, p90, p95, p99 } if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil { t.Fatal(err) } } golang-github-go-kit-kit-0.13.0/metrics/generic/000077500000000000000000000000001443521372500213675ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/generic/generic.go000066400000000000000000000135211443521372500233340ustar00rootroot00000000000000// Package generic implements generic versions of each of the metric types. They // can be embedded by other implementations, and converted to specific formats // as necessary. package generic import ( "fmt" "io" "math" "sync" "sync/atomic" "github.com/VividCortex/gohistogram" "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/internal/lv" ) // Counter is an in-memory implementation of a Counter. type Counter struct { bits uint64 // bits has to be the first word in order to be 64-aligned on 32-bit Name string lvs lv.LabelValues } // NewCounter returns a new, usable Counter. func NewCounter(name string) *Counter { return &Counter{ Name: name, } } // With implements Counter. func (c *Counter) With(labelValues ...string) metrics.Counter { return &Counter{ Name: c.Name, bits: atomic.LoadUint64(&c.bits), lvs: c.lvs.With(labelValues...), } } // Add implements Counter. func (c *Counter) Add(delta float64) { for { var ( old = atomic.LoadUint64(&c.bits) newf = math.Float64frombits(old) + delta new = math.Float64bits(newf) ) if atomic.CompareAndSwapUint64(&c.bits, old, new) { break } } } // Value returns the current value of the counter. func (c *Counter) Value() float64 { return math.Float64frombits(atomic.LoadUint64(&c.bits)) } // ValueReset returns the current value of the counter, and resets it to zero. // This is useful for metrics backends whose counter aggregations expect deltas, // like Graphite. func (c *Counter) ValueReset() float64 { for { var ( old = atomic.LoadUint64(&c.bits) newf = 0.0 new = math.Float64bits(newf) ) if atomic.CompareAndSwapUint64(&c.bits, old, new) { return math.Float64frombits(old) } } } // LabelValues returns the set of label values attached to the counter. func (c *Counter) LabelValues() []string { return c.lvs } // Gauge is an in-memory implementation of a Gauge. type Gauge struct { bits uint64 // bits has to be the first word in order to be 64-aligned on 32-bit Name string lvs lv.LabelValues } // NewGauge returns a new, usable Gauge. func NewGauge(name string) *Gauge { return &Gauge{ Name: name, } } // With implements Gauge. func (g *Gauge) With(labelValues ...string) metrics.Gauge { return &Gauge{ Name: g.Name, bits: atomic.LoadUint64(&g.bits), lvs: g.lvs.With(labelValues...), } } // Set implements Gauge. func (g *Gauge) Set(value float64) { atomic.StoreUint64(&g.bits, math.Float64bits(value)) } // Add implements metrics.Gauge. func (g *Gauge) Add(delta float64) { for { var ( old = atomic.LoadUint64(&g.bits) newf = math.Float64frombits(old) + delta new = math.Float64bits(newf) ) if atomic.CompareAndSwapUint64(&g.bits, old, new) { break } } } // Value returns the current value of the gauge. func (g *Gauge) Value() float64 { return math.Float64frombits(atomic.LoadUint64(&g.bits)) } // LabelValues returns the set of label values attached to the gauge. func (g *Gauge) LabelValues() []string { return g.lvs } // Histogram is an in-memory implementation of a streaming histogram, based on // VividCortex/gohistogram. It dynamically computes quantiles, so it's not // suitable for aggregation. type Histogram struct { Name string lvs lv.LabelValues h *safeHistogram } // NewHistogram returns a numeric histogram based on VividCortex/gohistogram. A // good default value for buckets is 50. func NewHistogram(name string, buckets int) *Histogram { return &Histogram{ Name: name, h: &safeHistogram{Histogram: gohistogram.NewHistogram(buckets)}, } } // With implements Histogram. func (h *Histogram) With(labelValues ...string) metrics.Histogram { return &Histogram{ Name: h.Name, lvs: h.lvs.With(labelValues...), h: h.h, } } // Observe implements Histogram. func (h *Histogram) Observe(value float64) { h.h.Lock() defer h.h.Unlock() h.h.Add(value) } // Quantile returns the value of the quantile q, 0.0 < q < 1.0. func (h *Histogram) Quantile(q float64) float64 { h.h.RLock() defer h.h.RUnlock() return h.h.Quantile(q) } // LabelValues returns the set of label values attached to the histogram. func (h *Histogram) LabelValues() []string { return h.lvs } // Print writes a string representation of the histogram to the passed writer. // Useful for printing to a terminal. func (h *Histogram) Print(w io.Writer) { h.h.RLock() defer h.h.RUnlock() fmt.Fprint(w, h.h.String()) } // safeHistogram exists as gohistogram.Histogram is not goroutine-safe. type safeHistogram struct { sync.RWMutex gohistogram.Histogram } // Bucket is a range in a histogram which aggregates observations. type Bucket struct { From, To, Count int64 } // Quantile is a pair of a quantile (0..100) and its observed maximum value. type Quantile struct { Quantile int // 0..100 Value int64 } // SimpleHistogram is an in-memory implementation of a Histogram. It only tracks // an approximate moving average, so is likely too naïve for many use cases. type SimpleHistogram struct { mtx sync.RWMutex lvs lv.LabelValues avg float64 n uint64 } // NewSimpleHistogram returns a SimpleHistogram, ready for observations. func NewSimpleHistogram() *SimpleHistogram { return &SimpleHistogram{} } // With implements Histogram. func (h *SimpleHistogram) With(labelValues ...string) metrics.Histogram { return &SimpleHistogram{ lvs: h.lvs.With(labelValues...), avg: h.avg, n: h.n, } } // Observe implements Histogram. func (h *SimpleHistogram) Observe(value float64) { h.mtx.Lock() defer h.mtx.Unlock() h.n++ h.avg -= h.avg / float64(h.n) h.avg += value / float64(h.n) } // ApproximateMovingAverage returns the approximate moving average of observations. func (h *SimpleHistogram) ApproximateMovingAverage() float64 { h.mtx.RLock() defer h.mtx.RUnlock() return h.avg } // LabelValues returns the set of label values attached to the histogram. func (h *SimpleHistogram) LabelValues() []string { return h.lvs } golang-github-go-kit-kit-0.13.0/metrics/generic/generic_test.go000066400000000000000000000112571443521372500243770ustar00rootroot00000000000000package generic_test // This is package generic_test in order to get around an import cycle: this // package imports teststat to do its testing, but package teststat imports // generic to use its Histogram in the Quantiles helper function. import ( "go/ast" "go/importer" "go/parser" "go/token" "go/types" "io/ioutil" "math" "math/rand" "sync" "testing" "github.com/go-kit/kit/metrics/generic" "github.com/go-kit/kit/metrics/teststat" ) func TestCounter(t *testing.T) { name := "my_counter" counter := generic.NewCounter(name).With("label", "counter").(*generic.Counter) if want, have := name, counter.Name; want != have { t.Errorf("Name: want %q, have %q", want, have) } value := counter.Value if err := teststat.TestCounter(counter, value); err != nil { t.Fatal(err) } } func TestValueReset(t *testing.T) { counter := generic.NewCounter("test_value_reset") counter.Add(123) counter.Add(456) counter.Add(789) if want, have := float64(123+456+789), counter.ValueReset(); want != have { t.Errorf("want %f, have %f", want, have) } if want, have := float64(0), counter.Value(); want != have { t.Errorf("want %f, have %f", want, have) } } func TestGauge(t *testing.T) { name := "my_gauge" gauge := generic.NewGauge(name).With("label", "gauge").(*generic.Gauge) if want, have := name, gauge.Name; want != have { t.Errorf("Name: want %q, have %q", want, have) } value := func() []float64 { return []float64{gauge.Value()} } if err := teststat.TestGauge(gauge, value); err != nil { t.Fatal(err) } } func TestHistogram(t *testing.T) { name := "my_histogram" histogram := generic.NewHistogram(name, 50).With("label", "histogram").(*generic.Histogram) if want, have := name, histogram.Name; want != have { t.Errorf("Name: want %q, have %q", want, have) } quantiles := func() (float64, float64, float64, float64) { return histogram.Quantile(0.50), histogram.Quantile(0.90), histogram.Quantile(0.95), histogram.Quantile(0.99) } if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil { t.Fatal(err) } } func TestIssue424(t *testing.T) { var ( histogram = generic.NewHistogram("dont_panic", 50) concurrency = 100 operations = 1000 wg sync.WaitGroup ) wg.Add(concurrency) for i := 0; i < concurrency; i++ { go func() { defer wg.Done() for j := 0; j < operations; j++ { histogram.Observe(float64(j)) histogram.Observe(histogram.Quantile(0.5)) } }() } wg.Wait() } func TestSimpleHistogram(t *testing.T) { histogram := generic.NewSimpleHistogram().With("label", "simple_histogram").(*generic.SimpleHistogram) var ( sum int count = 1234 // not too big ) for i := 0; i < count; i++ { value := rand.Intn(1000) sum += value histogram.Observe(float64(value)) } var ( want = float64(sum) / float64(count) have = histogram.ApproximateMovingAverage() tolerance = 0.001 // real real slim ) if math.Abs(want-have)/want > tolerance { t.Errorf("want %f, have %f", want, have) } } // Naive atomic alignment test. // The problem is related to the use of `atomic.*` and not directly to a structure. // But currently works for Counter and Gauge. // To have a more solid test, this test should be removed and the other tests should be run on a 32-bit arch. func TestAtomicAlignment(t *testing.T) { content, err := ioutil.ReadFile("./generic.go") if err != nil { t.Fatal(err) } fset := token.NewFileSet() file, err := parser.ParseFile(fset, "generic.go", content, parser.ParseComments) if err != nil { t.Fatal(err) } conf := types.Config{Importer: importer.ForCompiler(fset, "source", nil)} pkg, err := conf.Check(".", fset, []*ast.File{file}, nil) if err != nil { t.Fatal(err) } // uses ARM as reference for 32-bit arch sizes := types.SizesFor("gc", "arm") names := []string{"Counter", "Gauge"} for _, name := range names { t.Run(name, func(t *testing.T) { checkAtomicAlignment(t, sizes, pkg.Scope().Lookup(name), pkg) }) } } func checkAtomicAlignment(t *testing.T, sizes types.Sizes, obj types.Object, pkg *types.Package) { t.Helper() st := obj.Type().Underlying().(*types.Struct) posToCheck := make(map[int]types.Type) var vars []*types.Var for i := 0; i < st.NumFields(); i++ { field := st.Field(i) if v, ok := field.Type().(*types.Basic); ok { switch v.Kind() { case types.Uint64, types.Float64, types.Int64: posToCheck[i] = v } } vars = append(vars, types.NewVar(field.Pos(), pkg, field.Name(), field.Type())) } offsets := sizes.Offsetsof(vars) for i, offset := range offsets { if _, ok := posToCheck[i]; !ok { continue } if offset%8 != 0 { t.Errorf("misalignment detected in %s for the type %s, offset %d", obj.Name(), posToCheck[i], offset) } } } golang-github-go-kit-kit-0.13.0/metrics/graphite/000077500000000000000000000000001443521372500215565ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/graphite/graphite.go000066400000000000000000000142241443521372500237130ustar00rootroot00000000000000// Package graphite provides a Graphite backend for metrics. Metrics are batched // and emitted in the plaintext protocol. For more information, see // http://graphite.readthedocs.io/en/latest/feeding-carbon.html#the-plaintext-protocol // // Graphite does not have a native understanding of metric parameterization, so // label values not supported. Use distinct metrics for each unique combination // of label values. package graphite import ( "context" "fmt" "io" "sync" "time" "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/generic" "github.com/go-kit/kit/util/conn" "github.com/go-kit/log" ) // Graphite receives metrics observations and forwards them to a Graphite server. // Create a Graphite object, use it to create metrics, and pass those metrics as // dependencies to the components that will use them. // // All metrics are buffered until WriteTo is called. Counters and gauges are // aggregated into a single observation per timeseries per write. Histograms are // exploded into per-quantile gauges and reported once per write. // // To regularly report metrics to an io.Writer, use the WriteLoop helper method. // To send to a Graphite server, use the SendLoop helper method. type Graphite struct { mtx sync.RWMutex prefix string counters map[string]*Counter gauges map[string]*Gauge histograms map[string]*Histogram logger log.Logger } // New returns a Graphite object that may be used to create metrics. Prefix is // applied to all created metrics. Callers must ensure that regular calls to // WriteTo are performed, either manually or with one of the helper methods. func New(prefix string, logger log.Logger) *Graphite { return &Graphite{ prefix: prefix, counters: map[string]*Counter{}, gauges: map[string]*Gauge{}, histograms: map[string]*Histogram{}, logger: logger, } } // NewCounter returns a counter. Observations are aggregated and emitted once // per write invocation. func (g *Graphite) NewCounter(name string) *Counter { c := NewCounter(g.prefix + name) g.mtx.Lock() g.counters[g.prefix+name] = c g.mtx.Unlock() return c } // NewGauge returns a gauge. Observations are aggregated and emitted once per // write invocation. func (g *Graphite) NewGauge(name string) *Gauge { ga := NewGauge(g.prefix + name) g.mtx.Lock() g.gauges[g.prefix+name] = ga g.mtx.Unlock() return ga } // NewHistogram returns a histogram. Observations are aggregated and emitted as // per-quantile gauges, once per write invocation. 50 is a good default value // for buckets. func (g *Graphite) NewHistogram(name string, buckets int) *Histogram { h := NewHistogram(g.prefix+name, buckets) g.mtx.Lock() g.histograms[g.prefix+name] = h g.mtx.Unlock() return h } // WriteLoop is a helper method that invokes WriteTo to the passed writer every // time the passed channel fires. This method blocks until ctx is canceled, // so clients probably want to run it in its own goroutine. For typical // usage, create a time.Ticker and pass its C channel to this method. func (g *Graphite) WriteLoop(ctx context.Context, c <-chan time.Time, w io.Writer) { for { select { case <-c: if _, err := g.WriteTo(w); err != nil { g.logger.Log("during", "WriteTo", "err", err) } case <-ctx.Done(): return } } } // SendLoop is a helper method that wraps WriteLoop, passing a managed // connection to the network and address. Like WriteLoop, this method blocks // until ctx is canceled, so clients probably want to start it in its own // goroutine. For typical usage, create a time.Ticker and pass its C channel to // this method. func (g *Graphite) SendLoop(ctx context.Context, c <-chan time.Time, network, address string) { g.WriteLoop(ctx, c, conn.NewDefaultManager(network, address, g.logger)) } // WriteTo flushes the buffered content of the metrics to the writer, in // Graphite plaintext format. WriteTo abides best-effort semantics, so // observations are lost if there is a problem with the write. Clients should be // sure to call WriteTo regularly, ideally through the WriteLoop or SendLoop // helper methods. func (g *Graphite) WriteTo(w io.Writer) (count int64, err error) { g.mtx.RLock() defer g.mtx.RUnlock() now := time.Now().Unix() for name, c := range g.counters { n, err := fmt.Fprintf(w, "%s %f %d\n", name, c.c.ValueReset(), now) if err != nil { return count, err } count += int64(n) } for name, ga := range g.gauges { n, err := fmt.Fprintf(w, "%s %f %d\n", name, ga.g.Value(), now) if err != nil { return count, err } count += int64(n) } for name, h := range g.histograms { for _, p := range []struct { s string f float64 }{ {"50", 0.50}, {"90", 0.90}, {"95", 0.95}, {"99", 0.99}, } { n, err := fmt.Fprintf(w, "%s.p%s %f %d\n", name, p.s, h.h.Quantile(p.f), now) if err != nil { return count, err } count += int64(n) } } return count, err } // Counter is a Graphite counter metric. type Counter struct { c *generic.Counter } // NewCounter returns a new usable counter metric. func NewCounter(name string) *Counter { return &Counter{generic.NewCounter(name)} } // With is a no-op. func (c *Counter) With(...string) metrics.Counter { return c } // Add implements counter. func (c *Counter) Add(delta float64) { c.c.Add(delta) } // Gauge is a Graphite gauge metric. type Gauge struct { g *generic.Gauge } // NewGauge returns a new usable Gauge metric. func NewGauge(name string) *Gauge { return &Gauge{generic.NewGauge(name)} } // With is a no-op. func (g *Gauge) With(...string) metrics.Gauge { return g } // Set implements gauge. func (g *Gauge) Set(value float64) { g.g.Set(value) } // Add implements metrics.Gauge. func (g *Gauge) Add(delta float64) { g.g.Add(delta) } // Histogram is a Graphite histogram metric. Observations are bucketed into // per-quantile gauges. type Histogram struct { h *generic.Histogram } // NewHistogram returns a new usable Histogram metric. func NewHistogram(name string, buckets int) *Histogram { return &Histogram{generic.NewHistogram(name, buckets)} } // With is a no-op. func (h *Histogram) With(...string) metrics.Histogram { return h } // Observe implements histogram. func (h *Histogram) Observe(value float64) { h.h.Observe(value) } golang-github-go-kit-kit-0.13.0/metrics/graphite/graphite_test.go000066400000000000000000000040461443521372500247530ustar00rootroot00000000000000package graphite import ( "bytes" "regexp" "strconv" "testing" "github.com/go-kit/kit/metrics/teststat" "github.com/go-kit/log" ) func TestCounter(t *testing.T) { prefix, name := "abc.", "def" label, value := "label", "value" // ignored for Graphite regex := `^` + prefix + name + ` ([0-9\.]+) [0-9]+$` g := New(prefix, log.NewNopLogger()) counter := g.NewCounter(name).With(label, value) valuef := teststat.SumLines(g, regex) if err := teststat.TestCounter(counter, valuef); err != nil { t.Fatal(err) } } func TestGauge(t *testing.T) { prefix, name := "ghi.", "jkl" label, value := "xyz", "abc" // ignored for Graphite regex := `^` + prefix + name + ` ([0-9\.]+) [0-9]+$` g := New(prefix, log.NewNopLogger()) gauge := g.NewGauge(name).With(label, value) valuef := teststat.LastLine(g, regex) if err := teststat.TestGauge(gauge, valuef); err != nil { t.Fatal(err) } } func TestHistogram(t *testing.T) { // The histogram test is actually like 4 gauge tests. prefix, name := "graphite.", "histogram_test" label, value := "abc", "def" // ignored for Graphite re50 := regexp.MustCompile(prefix + name + `.p50 ([0-9\.]+) [0-9]+`) re90 := regexp.MustCompile(prefix + name + `.p90 ([0-9\.]+) [0-9]+`) re95 := regexp.MustCompile(prefix + name + `.p95 ([0-9\.]+) [0-9]+`) re99 := regexp.MustCompile(prefix + name + `.p99 ([0-9\.]+) [0-9]+`) g := New(prefix, log.NewNopLogger()) histogram := g.NewHistogram(name, 50).With(label, value) quantiles := func() (float64, float64, float64, float64) { var buf bytes.Buffer g.WriteTo(&buf) match50 := re50.FindStringSubmatch(buf.String()) p50, _ := strconv.ParseFloat(match50[1], 64) match90 := re90.FindStringSubmatch(buf.String()) p90, _ := strconv.ParseFloat(match90[1], 64) match95 := re95.FindStringSubmatch(buf.String()) p95, _ := strconv.ParseFloat(match95[1], 64) match99 := re99.FindStringSubmatch(buf.String()) p99, _ := strconv.ParseFloat(match99[1], 64) return p50, p90, p95, p99 } if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil { t.Fatal(err) } } golang-github-go-kit-kit-0.13.0/metrics/influx/000077500000000000000000000000001443521372500212605ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/influx/example_test.go000066400000000000000000000060261443521372500243050ustar00rootroot00000000000000package influx import ( "fmt" "regexp" influxdb "github.com/influxdata/influxdb1-client/v2" "github.com/go-kit/log" ) func ExampleCounter() { in := New(map[string]string{"a": "b"}, influxdb.BatchPointsConfig{}, log.NewNopLogger()) counter := in.NewCounter("influx_counter") counter.Add(10) counter.With("error", "true").Add(1) counter.With("error", "false").Add(2) counter.Add(50) client := &bufWriter{} in.WriteTo(client) expectedLines := []string{ `(influx_counter,a=b count=60) [0-9]{19}`, `(influx_counter,a=b,error=true count=1) [0-9]{19}`, `(influx_counter,a=b,error=false count=2) [0-9]{19}`, } if err := extractAndPrintMessage(expectedLines, client.buf.String()); err != nil { fmt.Println(err.Error()) } // Output: // influx_counter,a=b count=60 // influx_counter,a=b,error=true count=1 // influx_counter,a=b,error=false count=2 } func ExampleGauge() { in := New(map[string]string{"a": "b"}, influxdb.BatchPointsConfig{}, log.NewNopLogger()) gauge := in.NewGauge("influx_gauge") gauge.Set(10) gauge.With("error", "true").Set(2) gauge.With("error", "true").Set(1) gauge.With("error", "false").Set(2) gauge.Set(50) gauge.With("test", "true").Set(1) gauge.With("test", "true").Add(1) client := &bufWriter{} in.WriteTo(client) expectedLines := []string{ `(influx_gauge,a=b,test=true value=2) [0-9]{19}`, `(influx_gauge,a=b value=50) [0-9]{19}`, `(influx_gauge,a=b,error=true value=1) [0-9]{19}`, `(influx_gauge,a=b,error=false value=2) [0-9]{19}`, } if err := extractAndPrintMessage(expectedLines, client.buf.String()); err != nil { fmt.Println(err.Error()) } // Output: // influx_gauge,a=b,test=true value=2 // influx_gauge,a=b value=50 // influx_gauge,a=b,error=true value=1 // influx_gauge,a=b,error=false value=2 } func ExampleHistogram() { in := New(map[string]string{"foo": "alpha"}, influxdb.BatchPointsConfig{}, log.NewNopLogger()) histogram := in.NewHistogram("influx_histogram") histogram.Observe(float64(10)) histogram.With("error", "true").Observe(float64(1)) histogram.With("error", "false").Observe(float64(2)) histogram.Observe(float64(50)) client := &bufWriter{} in.WriteTo(client) expectedLines := []string{ `(influx_histogram,foo=alpha p50=10,p90=50,p95=50,p99=50) [0-9]{19}`, `(influx_histogram,error=true,foo=alpha p50=1,p90=1,p95=1,p99=1) [0-9]{19}`, `(influx_histogram,error=false,foo=alpha p50=2,p90=2,p95=2,p99=2) [0-9]{19}`, } if err := extractAndPrintMessage(expectedLines, client.buf.String()); err != nil { fmt.Println(err.Error()) } // Output: // influx_histogram,foo=alpha p50=10,p90=50,p95=50,p99=50 // influx_histogram,error=true,foo=alpha p50=1,p90=1,p95=1,p99=1 // influx_histogram,error=false,foo=alpha p50=2,p90=2,p95=2,p99=2 } func extractAndPrintMessage(expected []string, msg string) error { for _, pattern := range expected { re := regexp.MustCompile(pattern) match := re.FindStringSubmatch(msg) if len(match) != 2 { return fmt.Errorf("pattern not found! {%s} [%s]: %v\n", pattern, msg, match) } fmt.Println(match[1]) } return nil } golang-github-go-kit-kit-0.13.0/metrics/influx/influx.go000066400000000000000000000165421443521372500231240ustar00rootroot00000000000000// Package influx provides an InfluxDB implementation for metrics. The model is // similar to other push-based instrumentation systems. Observations are // aggregated locally and emitted to the Influx server on regular intervals. package influx import ( "context" "time" influxdb "github.com/influxdata/influxdb1-client/v2" "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/generic" "github.com/go-kit/kit/metrics/internal/lv" "github.com/go-kit/log" ) // Influx is a store for metrics that will be emitted to an Influx database. // // Influx is a general purpose time-series database, and has no native concepts // of counters, gauges, or histograms. Counters are modeled as a timeseries with // one data point per flush, with a "count" field that reflects all adds since // the last flush. Gauges are modeled as a timeseries with one data point per // flush, with a "value" field that reflects the current state of the gauge. // Histograms are modeled as a timeseries with one data point per combination of tags, // with a set of quantile fields that reflects the p50, p90, p95 & p99. // // Influx tags are attached to the Influx object, can be given to each // metric at construction and can be updated anytime via With function. Influx fields // are mapped to Go kit label values directly by this collector. Actual metric // values are provided as fields with specific names depending on the metric. // // All observations are collected in memory locally, and flushed on demand. type Influx struct { counters *lv.Space gauges *lv.Space histograms *lv.Space tags map[string]string conf influxdb.BatchPointsConfig logger log.Logger } // New returns an Influx, ready to create metrics and collect observations. Tags // are applied to all metrics created from this object. The BatchPointsConfig is // used during flushing. func New(tags map[string]string, conf influxdb.BatchPointsConfig, logger log.Logger) *Influx { return &Influx{ counters: lv.NewSpace(), gauges: lv.NewSpace(), histograms: lv.NewSpace(), tags: tags, conf: conf, logger: logger, } } // NewCounter returns an Influx counter. func (in *Influx) NewCounter(name string) *Counter { return &Counter{ name: name, obs: in.counters.Observe, } } // NewGauge returns an Influx gauge. func (in *Influx) NewGauge(name string) *Gauge { return &Gauge{ name: name, obs: in.gauges.Observe, add: in.gauges.Add, } } // NewHistogram returns an Influx histogram. func (in *Influx) NewHistogram(name string) *Histogram { return &Histogram{ name: name, obs: in.histograms.Observe, } } // BatchPointsWriter captures a subset of the influxdb.Client methods necessary // for emitting metrics observations. type BatchPointsWriter interface { Write(influxdb.BatchPoints) error } // WriteLoop is a helper method that invokes WriteTo to the passed writer every // time the passed channel fires. This method blocks until the channel is // closed, so clients probably want to run it in its own goroutine. For typical // usage, create a time.Ticker and pass its C channel to this method. func (in *Influx) WriteLoop(ctx context.Context, c <-chan time.Time, w BatchPointsWriter) { for { select { case <-c: if err := in.WriteTo(w); err != nil { in.logger.Log("during", "WriteTo", "err", err) } case <-ctx.Done(): return } } } // WriteTo flushes the buffered content of the metrics to the writer, in an // Influx BatchPoints format. WriteTo abides best-effort semantics, so // observations are lost if there is a problem with the write. Clients should be // sure to call WriteTo regularly, ideally through the WriteLoop helper method. func (in *Influx) WriteTo(w BatchPointsWriter) (err error) { bp, err := influxdb.NewBatchPoints(in.conf) if err != nil { return err } now := time.Now() in.counters.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool { tags := mergeTags(in.tags, lvs) var p *influxdb.Point fields := map[string]interface{}{"count": sum(values)} p, err = influxdb.NewPoint(name, tags, fields, now) if err != nil { return false } bp.AddPoint(p) return true }) if err != nil { return err } in.gauges.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool { tags := mergeTags(in.tags, lvs) var p *influxdb.Point fields := map[string]interface{}{"value": last(values)} p, err = influxdb.NewPoint(name, tags, fields, now) if err != nil { return false } bp.AddPoint(p) return true }) if err != nil { return err } in.histograms.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool { histogram := generic.NewHistogram(name, 50) tags := mergeTags(in.tags, lvs) var p *influxdb.Point for _, v := range values { histogram.Observe(v) } fields := map[string]interface{}{ "p50": histogram.Quantile(0.50), "p90": histogram.Quantile(0.90), "p95": histogram.Quantile(0.95), "p99": histogram.Quantile(0.99), } p, err = influxdb.NewPoint(name, tags, fields, now) if err != nil { return false } bp.AddPoint(p) return true }) if err != nil { return err } return w.Write(bp) } func mergeTags(tags map[string]string, labelValues []string) map[string]string { if len(labelValues)%2 != 0 { panic("mergeTags received a labelValues with an odd number of strings") } ret := make(map[string]string, len(tags)+len(labelValues)/2) for k, v := range tags { ret[k] = v } for i := 0; i < len(labelValues); i += 2 { ret[labelValues[i]] = labelValues[i+1] } return ret } func sum(a []float64) float64 { var v float64 for _, f := range a { v += f } return v } func last(a []float64) float64 { return a[len(a)-1] } type observeFunc func(name string, lvs lv.LabelValues, value float64) // Counter is an Influx counter. Observations are forwarded to an Influx // object, and aggregated (summed) per timeseries. type Counter struct { name string lvs lv.LabelValues obs observeFunc } // With implements metrics.Counter. func (c *Counter) With(labelValues ...string) metrics.Counter { return &Counter{ name: c.name, lvs: c.lvs.With(labelValues...), obs: c.obs, } } // Add implements metrics.Counter. func (c *Counter) Add(delta float64) { c.obs(c.name, c.lvs, delta) } // Gauge is an Influx gauge. Observations are forwarded to a Dogstatsd // object, and aggregated (the last observation selected) per timeseries. type Gauge struct { name string lvs lv.LabelValues obs observeFunc add observeFunc } // With implements metrics.Gauge. func (g *Gauge) With(labelValues ...string) metrics.Gauge { return &Gauge{ name: g.name, lvs: g.lvs.With(labelValues...), obs: g.obs, add: g.add, } } // Set implements metrics.Gauge. func (g *Gauge) Set(value float64) { g.obs(g.name, g.lvs, value) } // Add implements metrics.Gauge. func (g *Gauge) Add(delta float64) { g.add(g.name, g.lvs, delta) } // Histogram is an Influx histrogram. Observations are aggregated into a // generic.Histogram and emitted as per-quantile gauges to the Influx server. type Histogram struct { name string lvs lv.LabelValues obs observeFunc } // With implements metrics.Histogram. func (h *Histogram) With(labelValues ...string) metrics.Histogram { return &Histogram{ name: h.name, lvs: h.lvs.With(labelValues...), obs: h.obs, } } // Observe implements metrics.Histogram. func (h *Histogram) Observe(value float64) { h.obs(h.name, h.lvs, value) } golang-github-go-kit-kit-0.13.0/metrics/influx/influx_test.go000066400000000000000000000071141443521372500241560ustar00rootroot00000000000000package influx import ( "bytes" "fmt" "regexp" "strconv" "strings" "testing" influxdb "github.com/influxdata/influxdb1-client/v2" "github.com/go-kit/kit/metrics/teststat" "github.com/go-kit/log" ) func TestCounter(t *testing.T) { in := New(map[string]string{"a": "b"}, influxdb.BatchPointsConfig{}, log.NewNopLogger()) re := regexp.MustCompile(`influx_counter,a=b count=([0-9\.]+) [0-9]+`) // reverse-engineered :\ counter := in.NewCounter("influx_counter") value := func() float64 { client := &bufWriter{} in.WriteTo(client) match := re.FindStringSubmatch(client.buf.String()) f, _ := strconv.ParseFloat(match[1], 64) return f } if err := teststat.TestCounter(counter, value); err != nil { t.Fatal(err) } } func TestGauge(t *testing.T) { in := New(map[string]string{"foo": "alpha"}, influxdb.BatchPointsConfig{}, log.NewNopLogger()) re := regexp.MustCompile(`influx_gauge,foo=alpha value=([0-9\.]+) [0-9]+`) gauge := in.NewGauge("influx_gauge") value := func() []float64 { client := &bufWriter{} in.WriteTo(client) match := re.FindStringSubmatch(client.buf.String()) f, _ := strconv.ParseFloat(match[1], 64) return []float64{f} } if err := teststat.TestGauge(gauge, value); err != nil { t.Fatal(err) } } func TestHistogram(t *testing.T) { in := New(map[string]string{"foo": "alpha"}, influxdb.BatchPointsConfig{}, log.NewNopLogger()) re := regexp.MustCompile(`influx_histogram,bar=beta,foo=alpha p50=([0-9\.]+),p90=([0-9\.]+),p95=([0-9\.]+),p99=([0-9\.]+) [0-9]+`) histogram := in.NewHistogram("influx_histogram").With("bar", "beta") quantiles := func() (float64, float64, float64, float64) { w := &bufWriter{} in.WriteTo(w) match := re.FindStringSubmatch(w.buf.String()) if len(match) != 5 { t.Errorf("These are not the quantiles you're looking for: %v\n", match) } var result [4]float64 for i, q := range match[1:] { result[i], _ = strconv.ParseFloat(q, 64) } return result[0], result[1], result[2], result[3] } if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil { t.Fatal(err) } } func TestHistogramLabels(t *testing.T) { in := New(map[string]string{}, influxdb.BatchPointsConfig{}, log.NewNopLogger()) h := in.NewHistogram("foo") h.Observe(123) h.With("abc", "xyz").Observe(456) w := &bufWriter{} if err := in.WriteTo(w); err != nil { t.Fatal(err) } if want, have := 2, len(strings.Split(strings.TrimSpace(w.buf.String()), "\n")); want != have { t.Errorf("want %d, have %d", want, have) } } func TestIssue404(t *testing.T) { in := New(map[string]string{}, influxdb.BatchPointsConfig{}, log.NewNopLogger()) counterOne := in.NewCounter("influx_counter_one").With("a", "b") counterOne.Add(123) counterTwo := in.NewCounter("influx_counter_two").With("c", "d") counterTwo.Add(456) w := &bufWriter{} in.WriteTo(w) lines := strings.Split(strings.TrimSpace(w.buf.String()), "\n") if want, have := 2, len(lines); want != have { t.Fatalf("want %d, have %d", want, have) } for _, line := range lines { if strings.HasPrefix(line, "influx_counter_one") { if !strings.HasPrefix(line, "influx_counter_one,a=b count=123 ") { t.Errorf("invalid influx_counter_one: %s", line) } } else if strings.HasPrefix(line, "influx_counter_two") { if !strings.HasPrefix(line, "influx_counter_two,c=d count=456 ") { t.Errorf("invalid influx_counter_two: %s", line) } } else { t.Errorf("unexpected line: %s", line) } } } type bufWriter struct { buf bytes.Buffer } func (w *bufWriter) Write(bp influxdb.BatchPoints) error { for _, p := range bp.Points() { fmt.Fprintf(&w.buf, p.String()+"\n") } return nil } golang-github-go-kit-kit-0.13.0/metrics/influxstatsd/000077500000000000000000000000001443521372500225035ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/influxstatsd/influxstatsd.go000066400000000000000000000252701443521372500255700ustar00rootroot00000000000000// Package influxstatsd provides support for InfluxData's StatsD Telegraf plugin. It's very // similar to StatsD, but supports arbitrary tags per-metric, which map to Go // kit's label values. So, while label values are no-ops in StatsD, they are // supported here. For more details, see the article at // https://www.influxdata.com/blog/getting-started-with-sending-statsd-metrics-to-telegraf-influxdb/ // // This package batches observations and emits them on some schedule to the // remote server. This is useful even if you connect to your service // over UDP. Emitting one network packet per observation can quickly overwhelm // even the fastest internal network. package influxstatsd import ( "context" "fmt" "io" "strings" "sync" "sync/atomic" "time" "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/generic" "github.com/go-kit/kit/metrics/internal/lv" "github.com/go-kit/kit/metrics/internal/ratemap" "github.com/go-kit/kit/util/conn" "github.com/go-kit/log" ) // Influxstatsd receives metrics observations and forwards them to a server. // Create a Influxstatsd object, use it to create metrics, and pass those // metrics as dependencies to the components that will use them. // // All metrics are buffered until WriteTo is called. Counters and gauges are // aggregated into a single observation per timeseries per write. Timings and // histograms are buffered but not aggregated. // // To regularly report metrics to an io.Writer, use the WriteLoop helper method. // To send to a InfluxStatsD server, use the SendLoop helper method. type Influxstatsd struct { mtx sync.RWMutex prefix string rates *ratemap.RateMap counters *lv.Space gauges map[string]*gaugeNode timings *lv.Space histograms *lv.Space logger log.Logger lvs lv.LabelValues } // New returns a Influxstatsd object that may be used to create metrics. Prefix is // applied to all created metrics. Callers must ensure that regular calls to // WriteTo are performed, either manually or with one of the helper methods. func New(prefix string, logger log.Logger, lvs ...string) *Influxstatsd { if len(lvs)%2 != 0 { panic("odd number of LabelValues; programmer error!") } return &Influxstatsd{ prefix: prefix, rates: ratemap.New(), counters: lv.NewSpace(), gauges: map[string]*gaugeNode{}, // https://github.com/go-kit/kit/pull/588 timings: lv.NewSpace(), histograms: lv.NewSpace(), logger: logger, lvs: lvs, } } // NewCounter returns a counter, sending observations to this Influxstatsd object. func (d *Influxstatsd) NewCounter(name string, sampleRate float64) *Counter { d.rates.Set(name, sampleRate) return &Counter{ name: name, obs: d.counters.Observe, } } // NewGauge returns a gauge, sending observations to this Influxstatsd object. func (d *Influxstatsd) NewGauge(name string) *Gauge { d.mtx.Lock() n, ok := d.gauges[name] if !ok { n = &gaugeNode{gauge: &Gauge{g: generic.NewGauge(name), influx: d}} d.gauges[name] = n } d.mtx.Unlock() return n.gauge } // NewTiming returns a histogram whose observations are interpreted as // millisecond durations, and are forwarded to this Influxstatsd object. func (d *Influxstatsd) NewTiming(name string, sampleRate float64) *Timing { d.rates.Set(name, sampleRate) return &Timing{ name: name, obs: d.timings.Observe, } } // NewHistogram returns a histogram whose observations are of an unspecified // unit, and are forwarded to this Influxstatsd object. func (d *Influxstatsd) NewHistogram(name string, sampleRate float64) *Histogram { d.rates.Set(name, sampleRate) return &Histogram{ name: name, obs: d.histograms.Observe, } } // WriteLoop is a helper method that invokes WriteTo to the passed writer every // time the passed channel fires. This method blocks until ctx is canceled, // so clients probably want to run it in its own goroutine. For typical // usage, create a time.Ticker and pass its C channel to this method. func (d *Influxstatsd) WriteLoop(ctx context.Context, c <-chan time.Time, w io.Writer) { for { select { case <-c: if _, err := d.WriteTo(w); err != nil { d.logger.Log("during", "WriteTo", "err", err) } case <-ctx.Done(): return } } } // SendLoop is a helper method that wraps WriteLoop, passing a managed // connection to the network and address. Like WriteLoop, this method blocks // until ctx is canceled, so clients probably want to start it in its own // goroutine. For typical usage, create a time.Ticker and pass its C channel to // this method. func (d *Influxstatsd) SendLoop(ctx context.Context, c <-chan time.Time, network, address string) { d.WriteLoop(ctx, c, conn.NewDefaultManager(network, address, d.logger)) } // WriteTo flushes the buffered content of the metrics to the writer, in // InfluxStatsD format. WriteTo abides best-effort semantics, so observations are // lost if there is a problem with the write. Clients should be sure to call // WriteTo regularly, ideally through the WriteLoop or SendLoop helper methods. func (d *Influxstatsd) WriteTo(w io.Writer) (count int64, err error) { var n int d.counters.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool { n, err = fmt.Fprintf(w, "%s%s%s:%f|c%s\n", d.prefix, name, d.tagValues(lvs), sum(values), sampling(d.rates.Get(name))) if err != nil { return false } count += int64(n) return true }) if err != nil { return count, err } d.mtx.RLock() for _, root := range d.gauges { root.walk(func(name string, lvs lv.LabelValues, value float64) bool { n, err = fmt.Fprintf(w, "%s%s%s:%f|g\n", d.prefix, name, d.tagValues(lvs), value) if err != nil { return false } count += int64(n) return true }) } d.mtx.RUnlock() d.timings.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool { sampleRate := d.rates.Get(name) for _, value := range values { n, err = fmt.Fprintf(w, "%s%s%s:%f|ms%s\n", d.prefix, name, d.tagValues(lvs), value, sampling(sampleRate)) if err != nil { return false } count += int64(n) } return true }) if err != nil { return count, err } d.histograms.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool { sampleRate := d.rates.Get(name) for _, value := range values { n, err = fmt.Fprintf(w, "%s%s%s:%f|h%s\n", d.prefix, name, d.tagValues(lvs), value, sampling(sampleRate)) if err != nil { return false } count += int64(n) } return true }) if err != nil { return count, err } return count, err } func sum(a []float64) float64 { var v float64 for _, f := range a { v += f } return v } func sampling(r float64) string { var sv string if r < 1.0 { sv = fmt.Sprintf("|@%f", r) } return sv } func (d *Influxstatsd) tagValues(labelValues []string) string { if len(labelValues) == 0 && len(d.lvs) == 0 { return "" } if len(labelValues)%2 != 0 { panic("tagValues received a labelValues with an odd number of strings") } pairs := make([]string, 0, (len(d.lvs)+len(labelValues))/2) for i := 0; i < len(d.lvs); i += 2 { pairs = append(pairs, d.lvs[i]+"="+d.lvs[i+1]) } for i := 0; i < len(labelValues); i += 2 { pairs = append(pairs, labelValues[i]+"="+labelValues[i+1]) } return "," + strings.Join(pairs, ",") } type observeFunc func(name string, lvs lv.LabelValues, value float64) // Counter is a InfluxStatsD counter. Observations are forwarded to a Influxstatsd // object, and aggregated (summed) per timeseries. type Counter struct { name string lvs lv.LabelValues obs observeFunc } // With implements metrics.Counter. func (c *Counter) With(labelValues ...string) metrics.Counter { return &Counter{ name: c.name, lvs: c.lvs.With(labelValues...), obs: c.obs, } } // Add implements metrics.Counter. func (c *Counter) Add(delta float64) { c.obs(c.name, c.lvs, delta) } // Gauge is a InfluxStatsD gauge. Observations are forwarded to a Influxstatsd // object, and aggregated (the last observation selected) per timeseries. type Gauge struct { g *generic.Gauge influx *Influxstatsd set int32 } // With implements metrics.Gauge. func (g *Gauge) With(labelValues ...string) metrics.Gauge { g.influx.mtx.RLock() node := g.influx.gauges[g.g.Name] g.influx.mtx.RUnlock() ga := &Gauge{g: g.g.With(labelValues...).(*generic.Gauge), influx: g.influx} return node.addGauge(ga, ga.g.LabelValues()) } // Set implements metrics.Gauge. func (g *Gauge) Set(value float64) { g.g.Set(value) g.touch() } // Add implements metrics.Gauge. func (g *Gauge) Add(delta float64) { g.g.Add(delta) g.touch() } // Timing is a InfluxStatsD timing, or metrics.Histogram. Observations are // forwarded to a Influxstatsd object, and collected (but not aggregated) per // timeseries. type Timing struct { name string lvs lv.LabelValues obs observeFunc } // With implements metrics.Timing. func (t *Timing) With(labelValues ...string) metrics.Histogram { return &Timing{ name: t.name, lvs: t.lvs.With(labelValues...), obs: t.obs, } } // Observe implements metrics.Histogram. Value is interpreted as milliseconds. func (t *Timing) Observe(value float64) { t.obs(t.name, t.lvs, value) } // Histogram is a InfluxStatsD histrogram. Observations are forwarded to a // Influxstatsd object, and collected (but not aggregated) per timeseries. type Histogram struct { name string lvs lv.LabelValues obs observeFunc } // With implements metrics.Histogram. func (h *Histogram) With(labelValues ...string) metrics.Histogram { return &Histogram{ name: h.name, lvs: h.lvs.With(labelValues...), obs: h.obs, } } // Observe implements metrics.Histogram. func (h *Histogram) Observe(value float64) { h.obs(h.name, h.lvs, value) } type pair struct{ label, value string } type gaugeNode struct { mtx sync.RWMutex gauge *Gauge children map[pair]*gaugeNode } func (n *gaugeNode) addGauge(g *Gauge, lvs lv.LabelValues) *Gauge { n.mtx.Lock() defer n.mtx.Unlock() if len(lvs) == 0 { if n.gauge == nil { n.gauge = g } return n.gauge } if len(lvs) < 2 { panic("too few LabelValues; programmer error!") } head, tail := pair{lvs[0], lvs[1]}, lvs[2:] if n.children == nil { n.children = map[pair]*gaugeNode{} } child, ok := n.children[head] if !ok { child = &gaugeNode{} n.children[head] = child } return child.addGauge(g, tail) } func (n *gaugeNode) walk(fn func(string, lv.LabelValues, float64) bool) bool { n.mtx.RLock() defer n.mtx.RUnlock() if n.gauge != nil { value, ok := n.gauge.read() if ok && !fn(n.gauge.g.Name, n.gauge.g.LabelValues(), value) { return false } } for _, child := range n.children { if !child.walk(fn) { return false } } return true } func (g *Gauge) touch() { atomic.StoreInt32(&(g.set), 1) } func (g *Gauge) read() (float64, bool) { set := atomic.SwapInt32(&(g.set), 0) return g.g.Value(), set != 0 } golang-github-go-kit-kit-0.13.0/metrics/influxstatsd/influxstatsd_test.go000066400000000000000000000060251443521372500266240ustar00rootroot00000000000000package influxstatsd import ( "testing" "github.com/go-kit/kit/metrics/teststat" "github.com/go-kit/log" ) func TestCounter(t *testing.T) { prefix, name := "abc.", "def" label, value := "label", "value" regex := `^` + prefix + name + "," + label + `=` + value + `:([0-9\.]+)\|c$` d := New(prefix, log.NewNopLogger()) counter := d.NewCounter(name, 1.0).With(label, value) valuef := teststat.SumLines(d, regex) if err := teststat.TestCounter(counter, valuef); err != nil { t.Fatal(err) } } func TestCounterSampled(t *testing.T) { // This will involve multiplying the observed sum by the inverse of the // sample rate and checking against the expected value within some // tolerance. t.Skip("TODO") } func TestGauge(t *testing.T) { prefix, name := "ghi.", "jkl" label, value := "xyz", "abc" regex := `^` + prefix + name + `,hostname=foohost,` + label + `=` + value + `:([0-9\.]+)\|g$` d := New(prefix, log.NewNopLogger(), "hostname", "foohost") gauge := d.NewGauge(name).With(label, value) valuef := teststat.LastLine(d, regex) if err := teststat.TestGauge(gauge, valuef); err != nil { t.Fatal(err) } } // InfluxStatsD histograms just emit all observations. So, we collect them into // a generic histogram, and run the statistics test on that. func TestHistogram(t *testing.T) { prefix, name := "influxstatsd.", "histogram_test" label, value := "abc", "def" regex := `^` + prefix + name + "," + label + `=` + value + `:([0-9\.]+)\|h$` d := New(prefix, log.NewNopLogger()) histogram := d.NewHistogram(name, 1.0).With(label, value) quantiles := teststat.Quantiles(d, regex, 50) // no |@0.X if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil { t.Fatal(err) } } func TestHistogramSampled(t *testing.T) { prefix, name := "influxstatsd.", "sampled_histogram_test" label, value := "foo", "bar" regex := `^` + prefix + name + "," + label + `=` + value + `:([0-9\.]+)\|h\|@0\.01[0]*$` d := New(prefix, log.NewNopLogger()) histogram := d.NewHistogram(name, 0.01).With(label, value) quantiles := teststat.Quantiles(d, regex, 50) if err := teststat.TestHistogram(histogram, quantiles, 0.02); err != nil { t.Fatal(err) } } func TestTiming(t *testing.T) { prefix, name := "influxstatsd.", "timing_test" label, value := "wiggle", "bottom" regex := `^` + prefix + name + "," + label + `=` + value + `:([0-9\.]+)\|ms$` d := New(prefix, log.NewNopLogger()) histogram := d.NewTiming(name, 1.0).With(label, value) quantiles := teststat.Quantiles(d, regex, 50) // no |@0.X if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil { t.Fatal(err) } } func TestTimingSampled(t *testing.T) { prefix, name := "influxstatsd.", "sampled_timing_test" label, value := "internal", "external" regex := `^` + prefix + name + "," + label + `=` + value + `:([0-9\.]+)\|ms\|@0.03[0]*$` d := New(prefix, log.NewNopLogger()) histogram := d.NewTiming(name, 0.03).With(label, value) quantiles := teststat.Quantiles(d, regex, 50) if err := teststat.TestHistogram(histogram, quantiles, 0.02); err != nil { t.Fatal(err) } } golang-github-go-kit-kit-0.13.0/metrics/internal/000077500000000000000000000000001443521372500215675ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/internal/convert/000077500000000000000000000000001443521372500232475ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/internal/convert/convert.go000066400000000000000000000063501443521372500252620ustar00rootroot00000000000000// Package convert provides a way to use Counters, Histograms, or Gauges // as one of the other types package convert import "github.com/go-kit/kit/metrics" type counterHistogram struct { c metrics.Counter } // NewCounterAsHistogram returns a Histogram that actually writes the // value on an underlying Counter func NewCounterAsHistogram(c metrics.Counter) metrics.Histogram { return counterHistogram{c} } // With implements Histogram. func (ch counterHistogram) With(labelValues ...string) metrics.Histogram { return counterHistogram{ch.c.With(labelValues...)} } // Observe implements histogram. func (ch counterHistogram) Observe(value float64) { ch.c.Add(value) } type histogramCounter struct { h metrics.Histogram } // NewHistogramAsCounter returns a Counter that actually writes the // value on an underlying Histogram func NewHistogramAsCounter(h metrics.Histogram) metrics.Counter { return histogramCounter{h} } // With implements Counter. func (hc histogramCounter) With(labelValues ...string) metrics.Counter { return histogramCounter{hc.h.With(labelValues...)} } // Add implements Counter. func (hc histogramCounter) Add(delta float64) { hc.h.Observe(delta) } type counterGauge struct { c metrics.Counter } // NewCounterAsGauge returns a Gauge that actually writes the // value on an underlying Counter func NewCounterAsGauge(c metrics.Counter) metrics.Gauge { return counterGauge{c} } // With implements Gauge. func (cg counterGauge) With(labelValues ...string) metrics.Gauge { return counterGauge{cg.c.With(labelValues...)} } // Set implements Gauge. func (cg counterGauge) Set(value float64) { cg.c.Add(value) } // Add implements metrics.Gauge. func (cg counterGauge) Add(delta float64) { cg.c.Add(delta) } type gaugeCounter struct { g metrics.Gauge } // NewGaugeAsCounter returns a Counter that actually writes the // value on an underlying Gauge func NewGaugeAsCounter(g metrics.Gauge) metrics.Counter { return gaugeCounter{g} } // With implements Counter. func (gc gaugeCounter) With(labelValues ...string) metrics.Counter { return gaugeCounter{gc.g.With(labelValues...)} } // Add implements Counter. func (gc gaugeCounter) Add(delta float64) { gc.g.Set(delta) } type histogramGauge struct { h metrics.Histogram } // NewHistogramAsGauge returns a Gauge that actually writes the // value on an underlying Histogram func NewHistogramAsGauge(h metrics.Histogram) metrics.Gauge { return histogramGauge{h} } // With implements Gauge. func (hg histogramGauge) With(labelValues ...string) metrics.Gauge { return histogramGauge{hg.h.With(labelValues...)} } // Set implements Gauge. func (hg histogramGauge) Set(value float64) { hg.h.Observe(value) } // Add implements metrics.Gauge. func (hg histogramGauge) Add(delta float64) { hg.h.Observe(delta) } type gaugeHistogram struct { g metrics.Gauge } // NewGaugeAsHistogram returns a Histogram that actually writes the // value on an underlying Gauge func NewGaugeAsHistogram(g metrics.Gauge) metrics.Histogram { return gaugeHistogram{g} } // With implements Histogram. func (gh gaugeHistogram) With(labelValues ...string) metrics.Histogram { return gaugeHistogram{gh.g.With(labelValues...)} } // Observe implements histogram. func (gh gaugeHistogram) Observe(value float64) { gh.g.Set(value) } golang-github-go-kit-kit-0.13.0/metrics/internal/convert/convert_test.go000066400000000000000000000031651443521372500263220ustar00rootroot00000000000000package convert import ( "testing" "github.com/go-kit/kit/metrics/generic" "github.com/go-kit/kit/metrics/teststat" ) func TestCounterHistogramConversion(t *testing.T) { name := "my_counter" c := generic.NewCounter(name) h := NewCounterAsHistogram(c) top := NewHistogramAsCounter(h).With("label", "counter").(histogramCounter) mid := top.h.(counterHistogram) low := mid.c.(*generic.Counter) if want, have := name, low.Name; want != have { t.Errorf("Name: want %q, have %q", want, have) } if err := teststat.TestCounter(top, low.Value); err != nil { t.Fatal(err) } } func TestCounterGaugeConversion(t *testing.T) { name := "my_counter" c := generic.NewCounter(name) g := NewCounterAsGauge(c) top := NewGaugeAsCounter(g).With("label", "counter").(gaugeCounter) mid := top.g.(counterGauge) low := mid.c.(*generic.Counter) if want, have := name, low.Name; want != have { t.Errorf("Name: want %q, have %q", want, have) } if err := teststat.TestCounter(top, low.Value); err != nil { t.Fatal(err) } } func TestHistogramGaugeConversion(t *testing.T) { name := "my_histogram" h := generic.NewHistogram(name, 50) g := NewHistogramAsGauge(h) top := NewGaugeAsHistogram(g).With("label", "histogram").(gaugeHistogram) mid := top.g.(histogramGauge) low := mid.h.(*generic.Histogram) if want, have := name, low.Name; want != have { t.Errorf("Name: want %q, have %q", want, have) } quantiles := func() (float64, float64, float64, float64) { return low.Quantile(0.50), low.Quantile(0.90), low.Quantile(0.95), low.Quantile(0.99) } if err := teststat.TestHistogram(top, quantiles, 0.01); err != nil { t.Fatal(err) } } golang-github-go-kit-kit-0.13.0/metrics/internal/lv/000077500000000000000000000000001443521372500222105ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/internal/lv/labelvalues.go000066400000000000000000000007351443521372500250430ustar00rootroot00000000000000package lv // LabelValues is a type alias that provides validation on its With method. // Metrics may include it as a member to help them satisfy With semantics and // save some code duplication. type LabelValues []string // With validates the input, and returns a new aggregate labelValues. func (lvs LabelValues) With(labelValues ...string) LabelValues { if len(labelValues)%2 != 0 { labelValues = append(labelValues, "unknown") } return append(lvs, labelValues...) } golang-github-go-kit-kit-0.13.0/metrics/internal/lv/labelvalues_test.go000066400000000000000000000011511443521372500260730ustar00rootroot00000000000000package lv import ( "strings" "testing" ) func TestWith(t *testing.T) { var a LabelValues b := a.With("a", "1") c := a.With("b", "2", "c", "3") if want, have := "", strings.Join(a, ""); want != have { t.Errorf("With appears to mutate the original LabelValues: want %q, have %q", want, have) } if want, have := "a1", strings.Join(b, ""); want != have { t.Errorf("With does not appear to return the right thing: want %q, have %q", want, have) } if want, have := "b2c3", strings.Join(c, ""); want != have { t.Errorf("With does not appear to return the right thing: want %q, have %q", want, have) } } golang-github-go-kit-kit-0.13.0/metrics/internal/lv/space.go000066400000000000000000000071371443521372500236420ustar00rootroot00000000000000package lv import "sync" // NewSpace returns an N-dimensional vector space. func NewSpace() *Space { return &Space{} } // Space represents an N-dimensional vector space. Each name and unique label // value pair establishes a new dimension and point within that dimension. Order // matters, i.e. [a=1 b=2] identifies a different timeseries than [b=2 a=1]. type Space struct { mtx sync.RWMutex nodes map[string]*node } // Observe locates the time series identified by the name and label values in // the vector space, and appends the value to the list of observations. func (s *Space) Observe(name string, lvs LabelValues, value float64) { s.nodeFor(name).observe(lvs, value) } // Add locates the time series identified by the name and label values in // the vector space, and appends the delta to the last value in the list of // observations. func (s *Space) Add(name string, lvs LabelValues, delta float64) { s.nodeFor(name).add(lvs, delta) } // Walk traverses the vector space and invokes fn for each non-empty time series // which is encountered. Return false to abort the traversal. func (s *Space) Walk(fn func(name string, lvs LabelValues, observations []float64) bool) { s.mtx.RLock() defer s.mtx.RUnlock() for name, node := range s.nodes { f := func(lvs LabelValues, observations []float64) bool { return fn(name, lvs, observations) } if !node.walk(LabelValues{}, f) { return } } } // Reset empties the current space and returns a new Space with the old // contents. Reset a Space to get an immutable copy suitable for walking. func (s *Space) Reset() *Space { s.mtx.Lock() defer s.mtx.Unlock() n := NewSpace() n.nodes, s.nodes = s.nodes, n.nodes return n } func (s *Space) nodeFor(name string) *node { s.mtx.Lock() defer s.mtx.Unlock() if s.nodes == nil { s.nodes = map[string]*node{} } n, ok := s.nodes[name] if !ok { n = &node{} s.nodes[name] = n } return n } // node exists at a specific point in the N-dimensional vector space of all // possible label values. The node collects observations and has child nodes // with greater specificity. type node struct { mtx sync.RWMutex observations []float64 children map[pair]*node } type pair struct{ label, value string } func (n *node) observe(lvs LabelValues, value float64) { n.mtx.Lock() defer n.mtx.Unlock() if len(lvs) <= 0 { n.observations = append(n.observations, value) return } if len(lvs) < 2 { panic("too few LabelValues; programmer error!") } head, tail := pair{lvs[0], lvs[1]}, lvs[2:] if n.children == nil { n.children = map[pair]*node{} } child, ok := n.children[head] if !ok { child = &node{} n.children[head] = child } child.observe(tail, value) } func (n *node) add(lvs LabelValues, delta float64) { n.mtx.Lock() defer n.mtx.Unlock() if len(lvs) <= 0 { var value float64 if len(n.observations) > 0 { value = last(n.observations) + delta } else { value = delta } n.observations = append(n.observations, value) return } if len(lvs) < 2 { panic("too few LabelValues; programmer error!") } head, tail := pair{lvs[0], lvs[1]}, lvs[2:] if n.children == nil { n.children = map[pair]*node{} } child, ok := n.children[head] if !ok { child = &node{} n.children[head] = child } child.add(tail, delta) } func (n *node) walk(lvs LabelValues, fn func(LabelValues, []float64) bool) bool { n.mtx.RLock() defer n.mtx.RUnlock() if len(n.observations) > 0 && !fn(lvs, n.observations) { return false } for p, child := range n.children { if !child.walk(append(lvs, p.label, p.value), fn) { return false } } return true } func last(a []float64) float64 { return a[len(a)-1] } golang-github-go-kit-kit-0.13.0/metrics/internal/lv/space_test.go000066400000000000000000000043261443521372500246760ustar00rootroot00000000000000package lv import ( "strings" "testing" ) func TestSpaceWalkAbort(t *testing.T) { s := NewSpace() s.Observe("a", LabelValues{"a", "b"}, 1) s.Observe("a", LabelValues{"c", "d"}, 2) s.Observe("a", LabelValues{"e", "f"}, 4) s.Observe("a", LabelValues{"g", "h"}, 8) s.Observe("b", LabelValues{"a", "b"}, 16) s.Observe("b", LabelValues{"c", "d"}, 32) s.Observe("b", LabelValues{"e", "f"}, 64) s.Observe("b", LabelValues{"g", "h"}, 128) var count int s.Walk(func(name string, lvs LabelValues, obs []float64) bool { count++ return false }) if want, have := 1, count; want != have { t.Errorf("want %d, have %d", want, have) } } func TestSpaceWalkSums(t *testing.T) { s := NewSpace() s.Observe("metric_one", LabelValues{}, 1) s.Observe("metric_one", LabelValues{}, 2) s.Observe("metric_one", LabelValues{"a", "1", "b", "2"}, 4) s.Observe("metric_one", LabelValues{"a", "1", "b", "2"}, 8) s.Observe("metric_one", LabelValues{}, 16) s.Observe("metric_one", LabelValues{"a", "1", "b", "3"}, 32) s.Observe("metric_two", LabelValues{}, 64) s.Observe("metric_two", LabelValues{}, 128) s.Observe("metric_two", LabelValues{"a", "1", "b", "2"}, 256) have := map[string]float64{} s.Walk(func(name string, lvs LabelValues, obs []float64) bool { have[name+" ["+strings.Join(lvs, "")+"]"] += sum(obs) return true }) want := map[string]float64{ "metric_one []": 1 + 2 + 16, "metric_one [a1b2]": 4 + 8, "metric_one [a1b3]": 32, "metric_two []": 64 + 128, "metric_two [a1b2]": 256, } for keystr, wantsum := range want { if havesum := have[keystr]; wantsum != havesum { t.Errorf("%q: want %.1f, have %.1f", keystr, wantsum, havesum) } delete(want, keystr) delete(have, keystr) } for keystr, havesum := range have { t.Errorf("%q: unexpected observations recorded: %.1f", keystr, havesum) } } func TestSpaceWalkSkipsEmptyDimensions(t *testing.T) { s := NewSpace() s.Observe("foo", LabelValues{"bar", "1", "baz", "2"}, 123) var count int s.Walk(func(name string, lvs LabelValues, obs []float64) bool { count++ return true }) if want, have := 1, count; want != have { t.Errorf("want %d, have %d", want, have) } } func sum(a []float64) (v float64) { for _, f := range a { v += f } return } golang-github-go-kit-kit-0.13.0/metrics/internal/ratemap/000077500000000000000000000000001443521372500232205ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/internal/ratemap/ratemap.go000066400000000000000000000017611443521372500252050ustar00rootroot00000000000000// Package ratemap implements a goroutine-safe map of string to float64. It can // be embedded in implementations whose metrics support fixed sample rates, so // that an additional parameter doesn't have to be tracked through the e.g. // lv.Space object. package ratemap import "sync" // RateMap is a simple goroutine-safe map of string to float64. type RateMap struct { mtx sync.RWMutex m map[string]float64 } // New returns a new RateMap. func New() *RateMap { return &RateMap{ m: map[string]float64{}, } } // Set writes the given name/rate pair to the map. // Set is safe for concurrent access by multiple goroutines. func (m *RateMap) Set(name string, rate float64) { m.mtx.Lock() defer m.mtx.Unlock() m.m[name] = rate } // Get retrieves the rate for the given name, or 1.0 if none is set. // Get is safe for concurrent access by multiple goroutines. func (m *RateMap) Get(name string) float64 { m.mtx.RLock() defer m.mtx.RUnlock() f, ok := m.m[name] if !ok { f = 1.0 } return f } golang-github-go-kit-kit-0.13.0/metrics/metrics.go000066400000000000000000000014611443521372500217520ustar00rootroot00000000000000package metrics // Counter describes a metric that accumulates values monotonically. // An example of a counter is the number of received HTTP requests. type Counter interface { With(labelValues ...string) Counter Add(delta float64) } // Gauge describes a metric that takes specific values over time. // An example of a gauge is the current depth of a job queue. type Gauge interface { With(labelValues ...string) Gauge Set(value float64) Add(delta float64) } // Histogram describes a metric that takes repeated observations of the same // kind of thing, and produces a statistical summary of those observations, // typically expressed as quantiles or buckets. An example of a histogram is // HTTP request latencies. type Histogram interface { With(labelValues ...string) Histogram Observe(value float64) } golang-github-go-kit-kit-0.13.0/metrics/multi/000077500000000000000000000000001443521372500211055ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/multi/multi.go000066400000000000000000000041701443521372500225700ustar00rootroot00000000000000// Package multi provides adapters that send observations to multiple metrics // simultaneously. This is useful if your service needs to emit to multiple // instrumentation systems at the same time, for example if your organization is // transitioning from one system to another. package multi import "github.com/go-kit/kit/metrics" // Counter collects multiple individual counters and treats them as a unit. type Counter []metrics.Counter // NewCounter returns a multi-counter, wrapping the passed counters. func NewCounter(c ...metrics.Counter) Counter { return Counter(c) } // Add implements counter. func (c Counter) Add(delta float64) { for _, counter := range c { counter.Add(delta) } } // With implements counter. func (c Counter) With(labelValues ...string) metrics.Counter { next := make(Counter, len(c)) for i := range c { next[i] = c[i].With(labelValues...) } return next } // Gauge collects multiple individual gauges and treats them as a unit. type Gauge []metrics.Gauge // NewGauge returns a multi-gauge, wrapping the passed gauges. func NewGauge(g ...metrics.Gauge) Gauge { return Gauge(g) } // Set implements Gauge. func (g Gauge) Set(value float64) { for _, gauge := range g { gauge.Set(value) } } // With implements gauge. func (g Gauge) With(labelValues ...string) metrics.Gauge { next := make(Gauge, len(g)) for i := range g { next[i] = g[i].With(labelValues...) } return next } // Add implements metrics.Gauge. func (g Gauge) Add(delta float64) { for _, gauge := range g { gauge.Add(delta) } } // Histogram collects multiple individual histograms and treats them as a unit. type Histogram []metrics.Histogram // NewHistogram returns a multi-histogram, wrapping the passed histograms. func NewHistogram(h ...metrics.Histogram) Histogram { return Histogram(h) } // Observe implements Histogram. func (h Histogram) Observe(value float64) { for _, histogram := range h { histogram.Observe(value) } } // With implements histogram. func (h Histogram) With(labelValues ...string) metrics.Histogram { next := make(Histogram, len(h)) for i := range h { next[i] = h[i].With(labelValues...) } return next } golang-github-go-kit-kit-0.13.0/metrics/multi/multi_test.go000066400000000000000000000042071443521372500236300ustar00rootroot00000000000000package multi import ( "fmt" "testing" "github.com/go-kit/kit/metrics" ) func TestMultiCounter(t *testing.T) { c1 := &mockCounter{} c2 := &mockCounter{} c3 := &mockCounter{} mc := NewCounter(c1, c2, c3) mc.Add(123) mc.Add(456) want := "[123 456]" for i, m := range []fmt.Stringer{c1, c2, c3} { if have := m.String(); want != have { t.Errorf("c%d: want %q, have %q", i+1, want, have) } } } func TestMultiGauge(t *testing.T) { g1 := &mockGauge{} g2 := &mockGauge{} g3 := &mockGauge{} mg := NewGauge(g1, g2, g3) mg.Set(9) mg.Set(8) mg.Set(7) mg.Add(3) want := "[9 8 7 10]" for i, m := range []fmt.Stringer{g1, g2, g3} { if have := m.String(); want != have { t.Errorf("g%d: want %q, have %q", i+1, want, have) } } } func TestMultiHistogram(t *testing.T) { h1 := &mockHistogram{} h2 := &mockHistogram{} h3 := &mockHistogram{} mh := NewHistogram(h1, h2, h3) mh.Observe(1) mh.Observe(2) mh.Observe(4) mh.Observe(8) want := "[1 2 4 8]" for i, m := range []fmt.Stringer{h1, h2, h3} { if have := m.String(); want != have { t.Errorf("g%d: want %q, have %q", i+1, want, have) } } } type mockCounter struct { obs []float64 } func (c *mockCounter) Add(delta float64) { c.obs = append(c.obs, delta) } func (c *mockCounter) With(...string) metrics.Counter { return c } func (c *mockCounter) String() string { return fmt.Sprintf("%v", c.obs) } type mockGauge struct { obs []float64 } func (g *mockGauge) Set(value float64) { g.obs = append(g.obs, value) } func (g *mockGauge) With(...string) metrics.Gauge { return g } func (g *mockGauge) String() string { return fmt.Sprintf("%v", g.obs) } func (g *mockGauge) Add(delta float64) { var value float64 if len(g.obs) > 0 { value = g.obs[len(g.obs)-1] + delta } else { value = delta } g.obs = append(g.obs, value) } type mockHistogram struct { obs []float64 } func (h *mockHistogram) Observe(value float64) { h.obs = append(h.obs, value) } func (h *mockHistogram) With(...string) metrics.Histogram { return h } func (h *mockHistogram) String() string { return fmt.Sprintf("%v", h.obs) } golang-github-go-kit-kit-0.13.0/metrics/pcp/000077500000000000000000000000001443521372500205355ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/pcp/pcp.go000066400000000000000000000077531443521372500216620ustar00rootroot00000000000000package pcp import ( "github.com/performancecopilot/speed/v4" "github.com/go-kit/kit/metrics" ) // Reporter encapsulates a speed client. type Reporter struct { c *speed.PCPClient } // NewReporter creates a new Reporter instance. The first parameter is the // application name and is used to create the speed client. Hence it should be a // valid speed parameter name and should not contain spaces or the path // separator for your operating system. func NewReporter(appname string) (*Reporter, error) { c, err := speed.NewPCPClient(appname) if err != nil { return nil, err } return &Reporter{c}, nil } // Start starts the underlying speed client so it can start reporting registered // metrics to your PCP installation. func (r *Reporter) Start() { r.c.MustStart() } // Stop stops the underlying speed client so it can stop reporting registered // metrics to your PCP installation. func (r *Reporter) Stop() { r.c.MustStop() } // Counter implements metrics.Counter via a single dimensional speed.Counter. type Counter struct { c speed.Counter } // NewCounter creates a new Counter. This requires a name parameter and can // optionally take a couple of description strings, that are used to create the // underlying speed.Counter and are reported by PCP. func (r *Reporter) NewCounter(name string, desc ...string) (*Counter, error) { c, err := speed.NewPCPCounter(0, name, desc...) if err != nil { return nil, err } r.c.MustRegister(c) return &Counter{c}, nil } // With is a no-op. func (c *Counter) With(labelValues ...string) metrics.Counter { return c } // Add increments Counter. speed.Counters only take int64, so delta is converted // to int64 before observation. func (c *Counter) Add(delta float64) { c.c.Inc(int64(delta)) } // Gauge implements metrics.Gauge via a single dimensional speed.Gauge. type Gauge struct { g speed.Gauge } // NewGauge creates a new Gauge. This requires a name parameter and can // optionally take a couple of description strings, that are used to create the // underlying speed.Gauge and are reported by PCP. func (r *Reporter) NewGauge(name string, desc ...string) (*Gauge, error) { g, err := speed.NewPCPGauge(0, name, desc...) if err != nil { return nil, err } r.c.MustRegister(g) return &Gauge{g}, nil } // With is a no-op. func (g *Gauge) With(labelValues ...string) metrics.Gauge { return g } // Set sets the value of the gauge. func (g *Gauge) Set(value float64) { g.g.Set(value) } // Add adds a value to the gauge. func (g *Gauge) Add(delta float64) { g.g.Inc(delta) } // Histogram wraps a speed Histogram. type Histogram struct { h speed.Histogram } // NewHistogram creates a new Histogram. The minimum observeable value is 0. The // maximum observeable value is 3600000000 (3.6e9). // // The required parameters are a metric name, the minimum and maximum observable // values, and a metric unit for the units of the observed values. // // Optionally, it can also take a couple of description strings. func (r *Reporter) NewHistogram(name string, min, max int64, unit speed.MetricUnit, desc ...string) (*Histogram, error) { h, err := speed.NewPCPHistogram(name, min, max, 5, unit, desc...) if err != nil { return nil, err } r.c.MustRegister(h) return &Histogram{h}, nil } // With is a no-op. func (h *Histogram) With(labelValues ...string) metrics.Histogram { return h } // Observe observes a value. // // This converts float64 value to int64 before observation, as the Histogram in // speed is backed using codahale/hdrhistogram, which only observes int64 // values. Additionally, the value is interpreted in the metric unit used to // construct the histogram. func (h *Histogram) Observe(value float64) { h.h.MustRecord(int64(value)) } // Mean returns the mean of the values observed so far by the Histogram. func (h *Histogram) Mean() float64 { return h.h.Mean() } // Percentile returns a percentile value for the given percentile // between 0 and 100 for all values observed by the histogram. func (h *Histogram) Percentile(p float64) int64 { return h.h.Percentile(p) } golang-github-go-kit-kit-0.13.0/metrics/pcp/pcp_test.go000066400000000000000000000030711443521372500227060ustar00rootroot00000000000000package pcp import ( "testing" "github.com/performancecopilot/speed/v4" "github.com/go-kit/kit/metrics/teststat" ) func TestCounter(t *testing.T) { r, err := NewReporter("test_counter") if err != nil { t.Fatal(err) } counter, err := r.NewCounter("speed_counter") if err != nil { t.Fatal(err) } counter = counter.With("label values", "not supported").(*Counter) value := func() float64 { f := counter.c.Val(); return float64(f) } if err := teststat.TestCounter(counter, value); err != nil { t.Fatal(err) } } func TestGauge(t *testing.T) { r, err := NewReporter("test_gauge") if err != nil { t.Fatal(err) } gauge, err := r.NewGauge("speed_gauge") if err != nil { t.Fatal(err) } gauge = gauge.With("label values", "not supported").(*Gauge) value := func() []float64 { f := gauge.g.Val(); return []float64{f} } if err := teststat.TestGauge(gauge, value); err != nil { t.Fatal(err) } } func TestHistogram(t *testing.T) { r, err := NewReporter("test_histogram") if err != nil { t.Fatal(err) } histogram, err := r.NewHistogram("speed_histogram", 0, 3600000000, speed.OneUnit) if err != nil { t.Fatal(err) } histogram = histogram.With("label values", "not supported").(*Histogram) quantiles := func() (float64, float64, float64, float64) { p50 := float64(histogram.Percentile(50)) p90 := float64(histogram.Percentile(90)) p95 := float64(histogram.Percentile(95)) p99 := float64(histogram.Percentile(99)) return p50, p90, p95, p99 } if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil { t.Fatal(err) } } golang-github-go-kit-kit-0.13.0/metrics/prometheus/000077500000000000000000000000001443521372500221465ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/prometheus/prometheus.go000066400000000000000000000106721443521372500246760ustar00rootroot00000000000000// Package prometheus provides Prometheus implementations for metrics. // Individual metrics are mapped to their Prometheus counterparts, and // (depending on the constructor used) may be automatically registered in the // global Prometheus metrics registry. package prometheus import ( "github.com/prometheus/client_golang/prometheus" "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/internal/lv" ) // Counter implements Counter, via a Prometheus CounterVec. type Counter struct { cv *prometheus.CounterVec lvs lv.LabelValues } // NewCounterFrom constructs and registers a Prometheus CounterVec, // and returns a usable Counter object. func NewCounterFrom(opts prometheus.CounterOpts, labelNames []string) *Counter { cv := prometheus.NewCounterVec(opts, labelNames) prometheus.MustRegister(cv) return NewCounter(cv) } // NewCounter wraps the CounterVec and returns a usable Counter object. func NewCounter(cv *prometheus.CounterVec) *Counter { return &Counter{ cv: cv, } } // With implements Counter. func (c *Counter) With(labelValues ...string) metrics.Counter { return &Counter{ cv: c.cv, lvs: c.lvs.With(labelValues...), } } // Add implements Counter. func (c *Counter) Add(delta float64) { c.cv.With(makeLabels(c.lvs...)).Add(delta) } // Gauge implements Gauge, via a Prometheus GaugeVec. type Gauge struct { gv *prometheus.GaugeVec lvs lv.LabelValues } // NewGaugeFrom constructs and registers a Prometheus GaugeVec, // and returns a usable Gauge object. func NewGaugeFrom(opts prometheus.GaugeOpts, labelNames []string) *Gauge { gv := prometheus.NewGaugeVec(opts, labelNames) prometheus.MustRegister(gv) return NewGauge(gv) } // NewGauge wraps the GaugeVec and returns a usable Gauge object. func NewGauge(gv *prometheus.GaugeVec) *Gauge { return &Gauge{ gv: gv, } } // With implements Gauge. func (g *Gauge) With(labelValues ...string) metrics.Gauge { return &Gauge{ gv: g.gv, lvs: g.lvs.With(labelValues...), } } // Set implements Gauge. func (g *Gauge) Set(value float64) { g.gv.With(makeLabels(g.lvs...)).Set(value) } // Add is supported by Prometheus GaugeVecs. func (g *Gauge) Add(delta float64) { g.gv.With(makeLabels(g.lvs...)).Add(delta) } // Summary implements Histogram, via a Prometheus SummaryVec. The difference // between a Summary and a Histogram is that Summaries don't require predefined // quantile buckets, but cannot be statistically aggregated. type Summary struct { sv *prometheus.SummaryVec lvs lv.LabelValues } // NewSummaryFrom constructs and registers a Prometheus SummaryVec, // and returns a usable Summary object. func NewSummaryFrom(opts prometheus.SummaryOpts, labelNames []string) *Summary { sv := prometheus.NewSummaryVec(opts, labelNames) prometheus.MustRegister(sv) return NewSummary(sv) } // NewSummary wraps the SummaryVec and returns a usable Summary object. func NewSummary(sv *prometheus.SummaryVec) *Summary { return &Summary{ sv: sv, } } // With implements Histogram. func (s *Summary) With(labelValues ...string) metrics.Histogram { return &Summary{ sv: s.sv, lvs: s.lvs.With(labelValues...), } } // Observe implements Histogram. func (s *Summary) Observe(value float64) { s.sv.With(makeLabels(s.lvs...)).Observe(value) } // Histogram implements Histogram via a Prometheus HistogramVec. The difference // between a Histogram and a Summary is that Histograms require predefined // quantile buckets, and can be statistically aggregated. type Histogram struct { hv *prometheus.HistogramVec lvs lv.LabelValues } // NewHistogramFrom constructs and registers a Prometheus HistogramVec, // and returns a usable Histogram object. func NewHistogramFrom(opts prometheus.HistogramOpts, labelNames []string) *Histogram { hv := prometheus.NewHistogramVec(opts, labelNames) prometheus.MustRegister(hv) return NewHistogram(hv) } // NewHistogram wraps the HistogramVec and returns a usable Histogram object. func NewHistogram(hv *prometheus.HistogramVec) *Histogram { return &Histogram{ hv: hv, } } // With implements Histogram. func (h *Histogram) With(labelValues ...string) metrics.Histogram { return &Histogram{ hv: h.hv, lvs: h.lvs.With(labelValues...), } } // Observe implements Histogram. func (h *Histogram) Observe(value float64) { h.hv.With(makeLabels(h.lvs...)).Observe(value) } func makeLabels(labelValues ...string) prometheus.Labels { labels := prometheus.Labels{} for i := 0; i < len(labelValues); i += 2 { labels[labelValues[i]] = labelValues[i+1] } return labels } golang-github-go-kit-kit-0.13.0/metrics/prometheus/prometheus_test.go000066400000000000000000000152201443521372500257270ustar00rootroot00000000000000package prometheus import ( "io/ioutil" "math" "math/rand" "net/http" "net/http/httptest" "reflect" "regexp" "strconv" "strings" "testing" "github.com/go-kit/kit/metrics/teststat" stdprometheus "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) func TestCounter(t *testing.T) { s := httptest.NewServer(promhttp.HandlerFor(stdprometheus.DefaultGatherer, promhttp.HandlerOpts{})) defer s.Close() scrape := func() string { resp, _ := http.Get(s.URL) buf, _ := ioutil.ReadAll(resp.Body) return string(buf) } namespace, subsystem, name := "ns", "ss", "foo" re := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{alpha="alpha-value",beta="beta-value"} ([0-9\.]+)`) counter := NewCounterFrom(stdprometheus.CounterOpts{ Namespace: namespace, Subsystem: subsystem, Name: name, Help: "This is the help string.", }, []string{"alpha", "beta"}).With("beta", "beta-value", "alpha", "alpha-value") // order shouldn't matter value := func() float64 { matches := re.FindStringSubmatch(scrape()) f, _ := strconv.ParseFloat(matches[1], 64) return f } if err := teststat.TestCounter(counter, value); err != nil { t.Fatal(err) } } func TestGauge(t *testing.T) { s := httptest.NewServer(promhttp.HandlerFor(stdprometheus.DefaultGatherer, promhttp.HandlerOpts{})) defer s.Close() scrape := func() string { resp, _ := http.Get(s.URL) buf, _ := ioutil.ReadAll(resp.Body) return string(buf) } namespace, subsystem, name := "aaa", "bbb", "ccc" re := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{foo="bar"} ([0-9\.]+)`) gauge := NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: subsystem, Name: name, Help: "This is a different help string.", }, []string{"foo"}).With("foo", "bar") value := func() []float64 { matches := re.FindStringSubmatch(scrape()) f, _ := strconv.ParseFloat(matches[1], 64) return []float64{f} } if err := teststat.TestGauge(gauge, value); err != nil { t.Fatal(err) } } func TestSummary(t *testing.T) { s := httptest.NewServer(promhttp.HandlerFor(stdprometheus.DefaultGatherer, promhttp.HandlerOpts{})) defer s.Close() scrape := func() string { resp, _ := http.Get(s.URL) buf, _ := ioutil.ReadAll(resp.Body) return string(buf) } namespace, subsystem, name := "test", "prometheus", "summary" re50 := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{a="a",b="b",quantile="0.5"} ([0-9\.]+)`) re90 := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{a="a",b="b",quantile="0.9"} ([0-9\.]+)`) re99 := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{a="a",b="b",quantile="0.99"} ([0-9\.]+)`) summary := NewSummaryFrom(stdprometheus.SummaryOpts{ Namespace: namespace, Subsystem: subsystem, Name: name, Help: "This is the help string for the summary.", Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, }, []string{"a", "b"}).With("b", "b").With("a", "a") quantiles := func() (float64, float64, float64, float64) { buf := scrape() match50 := re50.FindStringSubmatch(buf) p50, _ := strconv.ParseFloat(match50[1], 64) match90 := re90.FindStringSubmatch(buf) p90, _ := strconv.ParseFloat(match90[1], 64) match99 := re99.FindStringSubmatch(buf) p99, _ := strconv.ParseFloat(match99[1], 64) p95 := p90 + ((p99 - p90) / 2) // Prometheus, y u no p95??? :< #yolo return p50, p90, p95, p99 } if err := teststat.TestHistogram(summary, quantiles, 0.01); err != nil { t.Fatal(err) } } func TestHistogram(t *testing.T) { // Prometheus reports histograms as a count of observations that fell into // each predefined bucket, with the bucket value representing a global upper // limit. That is, the count monotonically increases over the buckets. This // requires a different strategy to test. s := httptest.NewServer(promhttp.HandlerFor(stdprometheus.DefaultGatherer, promhttp.HandlerOpts{})) defer s.Close() scrape := func() string { resp, _ := http.Get(s.URL) buf, _ := ioutil.ReadAll(resp.Body) return string(buf) } namespace, subsystem, name := "test", "prometheus", "histogram" re := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `_bucket{x="1",le="([0-9]+|\+Inf)"} ([0-9\.]+)`) numStdev := 3 bucketMin := (teststat.Mean - (numStdev * teststat.Stdev)) bucketMax := (teststat.Mean + (numStdev * teststat.Stdev)) if bucketMin < 0 { bucketMin = 0 } bucketCount := 10 bucketDelta := (bucketMax - bucketMin) / bucketCount buckets := []float64{} for i := bucketMin; i <= bucketMax; i += bucketDelta { buckets = append(buckets, float64(i)) } histogram := NewHistogramFrom(stdprometheus.HistogramOpts{ Namespace: namespace, Subsystem: subsystem, Name: name, Help: "This is the help string for the histogram.", Buckets: buckets, }, []string{"x"}).With("x", "1") // Can't TestHistogram, because Prometheus Histograms don't dynamically // compute quantiles. Instead, they fill up buckets. So, let's populate the // histogram kind of manually. teststat.PopulateNormalHistogram(histogram, rand.Int()) // Then, we use ExpectedObservationsLessThan to validate. for _, line := range strings.Split(scrape(), "\n") { match := re.FindStringSubmatch(line) if match == nil { continue } bucket, _ := strconv.ParseInt(match[1], 10, 64) have, _ := strconv.ParseFloat(match[2], 64) want := teststat.ExpectedObservationsLessThan(bucket) if match[1] == "+Inf" { want = int64(teststat.Count) // special case } // Unfortunately, we observe experimentally that Prometheus is quite // imprecise at the extremes. I'm setting a very high tolerance for now. // It would be great to dig in and figure out whether that's a problem // with my Expected calculation, or in Prometheus. tolerance := 0.25 if delta := math.Abs(float64(want) - float64(have)); (delta / float64(want)) > tolerance { t.Errorf("Bucket %d: want %d, have %d (%.1f%%)", bucket, want, int(have), (100.0 * delta / float64(want))) } } } func TestInconsistentLabelCardinality(t *testing.T) { defer func() { x := recover() if x == nil { t.Fatal("expected panic, got none") } err, ok := x.(error) if !ok { t.Fatalf("expected error, got %s", reflect.TypeOf(x)) } if want, have := "inconsistent label cardinality", err.Error(); !strings.HasPrefix(have, want) { t.Fatalf("want %q, have %q", want, have) } }() NewCounterFrom(stdprometheus.CounterOpts{ Namespace: "test", Subsystem: "inconsistent_label_cardinality", Name: "foobar", Help: "This is the help string for the metric.", }, []string{"a", "b"}).With( "a", "1", "b", "2", "c", "KABOOM!", ).Add(123) } golang-github-go-kit-kit-0.13.0/metrics/provider/000077500000000000000000000000001443521372500216055ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/provider/discard.go000066400000000000000000000013511443521372500235450ustar00rootroot00000000000000package provider import ( "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/discard" ) type discardProvider struct{} // NewDiscardProvider returns a provider that produces no-op metrics via the // discarding backend. func NewDiscardProvider() Provider { return discardProvider{} } // NewCounter implements Provider. func (discardProvider) NewCounter(string) metrics.Counter { return discard.NewCounter() } // NewGauge implements Provider. func (discardProvider) NewGauge(string) metrics.Gauge { return discard.NewGauge() } // NewHistogram implements Provider. func (discardProvider) NewHistogram(string, int) metrics.Histogram { return discard.NewHistogram() } // Stop implements Provider. func (discardProvider) Stop() {} golang-github-go-kit-kit-0.13.0/metrics/provider/dogstatsd.go000066400000000000000000000024071443521372500241330ustar00rootroot00000000000000package provider import ( "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/dogstatsd" ) type dogstatsdProvider struct { d *dogstatsd.Dogstatsd stop func() } // NewDogstatsdProvider wraps the given Dogstatsd object and stop func and // returns a Provider that produces Dogstatsd metrics. A typical stop function // would be ticker.Stop from the ticker passed to the SendLoop helper method. func NewDogstatsdProvider(d *dogstatsd.Dogstatsd, stop func()) Provider { return &dogstatsdProvider{ d: d, stop: stop, } } // NewCounter implements Provider, returning a new Dogstatsd Counter with a // sample rate of 1.0. func (p *dogstatsdProvider) NewCounter(name string) metrics.Counter { return p.d.NewCounter(name, 1.0) } // NewGauge implements Provider. func (p *dogstatsdProvider) NewGauge(name string) metrics.Gauge { return p.d.NewGauge(name) } // NewHistogram implements Provider, returning a new Dogstatsd Histogram (note: // not a Timing) with a sample rate of 1.0. The buckets argument is ignored. func (p *dogstatsdProvider) NewHistogram(name string, _ int) metrics.Histogram { return p.d.NewHistogram(name, 1.0) } // Stop implements Provider, invoking the stop function passed at construction. func (p *dogstatsdProvider) Stop() { p.stop() } golang-github-go-kit-kit-0.13.0/metrics/provider/expvar.go000066400000000000000000000014101443521372500234350ustar00rootroot00000000000000package provider import ( "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/expvar" ) type expvarProvider struct{} // NewExpvarProvider returns a Provider that produces expvar metrics. func NewExpvarProvider() Provider { return expvarProvider{} } // NewCounter implements Provider. func (p expvarProvider) NewCounter(name string) metrics.Counter { return expvar.NewCounter(name) } // NewGauge implements Provider. func (p expvarProvider) NewGauge(name string) metrics.Gauge { return expvar.NewGauge(name) } // NewHistogram implements Provider. func (p expvarProvider) NewHistogram(name string, buckets int) metrics.Histogram { return expvar.NewHistogram(name, buckets) } // Stop implements Provider, but is a no-op. func (p expvarProvider) Stop() {} golang-github-go-kit-kit-0.13.0/metrics/provider/graphite.go000066400000000000000000000021051443521372500237350ustar00rootroot00000000000000package provider import ( "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/graphite" ) type graphiteProvider struct { g *graphite.Graphite stop func() } // NewGraphiteProvider wraps the given Graphite object and stop func and returns // a Provider that produces Graphite metrics. A typical stop function would be // ticker.Stop from the ticker passed to the SendLoop helper method. func NewGraphiteProvider(g *graphite.Graphite, stop func()) Provider { return &graphiteProvider{ g: g, stop: stop, } } // NewCounter implements Provider. func (p *graphiteProvider) NewCounter(name string) metrics.Counter { return p.g.NewCounter(name) } // NewGauge implements Provider. func (p *graphiteProvider) NewGauge(name string) metrics.Gauge { return p.g.NewGauge(name) } // NewHistogram implements Provider. func (p *graphiteProvider) NewHistogram(name string, buckets int) metrics.Histogram { return p.g.NewHistogram(name, buckets) } // Stop implements Provider, invoking the stop function passed at construction. func (p *graphiteProvider) Stop() { p.stop() } golang-github-go-kit-kit-0.13.0/metrics/provider/influx.go000066400000000000000000000020471443521372500234440ustar00rootroot00000000000000package provider import ( "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/influx" ) type influxProvider struct { in *influx.Influx stop func() } // NewInfluxProvider takes the given Influx object and stop func, and returns // a Provider that produces Influx metrics. func NewInfluxProvider(in *influx.Influx, stop func()) Provider { return &influxProvider{ in: in, stop: stop, } } // NewCounter implements Provider. Per-metric tags are not supported. func (p *influxProvider) NewCounter(name string) metrics.Counter { return p.in.NewCounter(name) } // NewGauge implements Provider. Per-metric tags are not supported. func (p *influxProvider) NewGauge(name string) metrics.Gauge { return p.in.NewGauge(name) } // NewHistogram implements Provider. Per-metric tags are not supported. func (p *influxProvider) NewHistogram(name string, buckets int) metrics.Histogram { return p.in.NewHistogram(name) } // Stop implements Provider, invoking the stop function passed at construction. func (p *influxProvider) Stop() { p.stop() } golang-github-go-kit-kit-0.13.0/metrics/provider/prometheus.go000066400000000000000000000040571443521372500243350ustar00rootroot00000000000000package provider import ( stdprometheus "github.com/prometheus/client_golang/prometheus" "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/prometheus" ) type prometheusProvider struct { namespace string subsystem string } // NewPrometheusProvider returns a Provider that produces Prometheus metrics. // Namespace and subsystem are applied to all produced metrics. func NewPrometheusProvider(namespace, subsystem string) Provider { return &prometheusProvider{ namespace: namespace, subsystem: subsystem, } } // NewCounter implements Provider via prometheus.NewCounterFrom, i.e. the // counter is registered. The metric's namespace and subsystem are taken from // the Provider. Help is set to the name of the metric, and no const label names // are set. func (p *prometheusProvider) NewCounter(name string) metrics.Counter { return prometheus.NewCounterFrom(stdprometheus.CounterOpts{ Namespace: p.namespace, Subsystem: p.subsystem, Name: name, Help: name, }, []string{}) } // NewGauge implements Provider via prometheus.NewGaugeFrom, i.e. the gauge is // registered. The metric's namespace and subsystem are taken from the Provider. // Help is set to the name of the metric, and no const label names are set. func (p *prometheusProvider) NewGauge(name string) metrics.Gauge { return prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: p.namespace, Subsystem: p.subsystem, Name: name, Help: name, }, []string{}) } // NewHistogram implements Provider via prometheus.NewSummaryFrom, i.e. the summary // is registered. The metric's namespace and subsystem are taken from the // Provider. Help is set to the name of the metric, and no const label names are // set. Buckets are ignored. func (p *prometheusProvider) NewHistogram(name string, _ int) metrics.Histogram { return prometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ Namespace: p.namespace, Subsystem: p.subsystem, Name: name, Help: name, }, []string{}) } // Stop implements Provider, but is a no-op. func (p *prometheusProvider) Stop() {} golang-github-go-kit-kit-0.13.0/metrics/provider/provider.go000066400000000000000000000030371443521372500237710ustar00rootroot00000000000000// Package provider provides a factory-like abstraction for metrics backends. // This package is provided specifically for the needs of the NY Times framework // Gizmo. Most normal Go kit users shouldn't need to use it. // // Normally, if your microservice needs to support different metrics backends, // you can simply do different construction based on a flag. For example, // // var latency metrics.Histogram // var requests metrics.Counter // switch *metricsBackend { // case "prometheus": // latency = prometheus.NewSummaryVec(...) // requests = prometheus.NewCounterVec(...) // case "statsd": // s := statsd.New(...) // t := time.NewTicker(5*time.Second) // go s.SendLoop(ctx, t.C, "tcp", "statsd.local:8125") // latency = s.NewHistogram(...) // requests = s.NewCounter(...) // default: // log.Fatal("unsupported metrics backend %q", *metricsBackend) // } // package provider import ( "github.com/go-kit/kit/metrics" ) // Provider abstracts over constructors and lifecycle management functions for // each supported metrics backend. It should only be used by those who need to // swap out implementations dynamically. // // This is primarily useful for intermediating frameworks, and is likely // unnecessary for most Go kit services. See the package-level doc comment for // more typical usage instructions. type Provider interface { NewCounter(name string) metrics.Counter NewGauge(name string) metrics.Gauge NewHistogram(name string, buckets int) metrics.Histogram Stop() } golang-github-go-kit-kit-0.13.0/metrics/provider/statsd.go000066400000000000000000000022541443521372500234410ustar00rootroot00000000000000package provider import ( "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/statsd" ) type statsdProvider struct { s *statsd.Statsd stop func() } // NewStatsdProvider wraps the given Statsd object and stop func and returns a // Provider that produces Statsd metrics. A typical stop function would be // ticker.Stop from the ticker passed to the SendLoop helper method. func NewStatsdProvider(s *statsd.Statsd, stop func()) Provider { return &statsdProvider{ s: s, stop: stop, } } // NewCounter implements Provider. func (p *statsdProvider) NewCounter(name string) metrics.Counter { return p.s.NewCounter(name, 1.0) } // NewGauge implements Provider. func (p *statsdProvider) NewGauge(name string) metrics.Gauge { return p.s.NewGauge(name) } // NewHistogram implements Provider, returning a StatsD Timing that accepts // observations in milliseconds. The sample rate is fixed at 1.0. The bucket // parameter is ignored. func (p *statsdProvider) NewHistogram(name string, _ int) metrics.Histogram { return p.s.NewTiming(name, 1.0) } // Stop implements Provider, invoking the stop function passed at construction. func (p *statsdProvider) Stop() { p.stop() } golang-github-go-kit-kit-0.13.0/metrics/statsd/000077500000000000000000000000001443521372500212555ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/statsd/statsd.go000066400000000000000000000156241443521372500231160ustar00rootroot00000000000000// Package statsd provides a StatsD backend for package metrics. StatsD has no // concept of arbitrary key-value tagging, so label values are not supported, // and With is a no-op on all metrics. // // This package batches observations and emits them on some schedule to the // remote server. This is useful even if you connect to your StatsD server over // UDP. Emitting one network packet per observation can quickly overwhelm even // the fastest internal network. package statsd import ( "context" "fmt" "io" "time" "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/internal/lv" "github.com/go-kit/kit/metrics/internal/ratemap" "github.com/go-kit/kit/util/conn" "github.com/go-kit/log" ) // Statsd receives metrics observations and forwards them to a StatsD server. // Create a Statsd object, use it to create metrics, and pass those metrics as // dependencies to the components that will use them. // // All metrics are buffered until WriteTo is called. Counters and gauges are // aggregated into a single observation per timeseries per write. Timings are // buffered but not aggregated. // // To regularly report metrics to an io.Writer, use the WriteLoop helper method. // To send to a StatsD server, use the SendLoop helper method. type Statsd struct { prefix string rates *ratemap.RateMap // The observations are collected in an N-dimensional vector space, even // though they only take advantage of a single dimension (name). This is an // implementation detail born purely from convenience. It would be more // accurate to collect them in a map[string][]float64, but we already have // this nice data structure and helper methods. counters *lv.Space gauges *lv.Space timings *lv.Space logger log.Logger } // New returns a Statsd object that may be used to create metrics. Prefix is // applied to all created metrics. Callers must ensure that regular calls to // WriteTo are performed, either manually or with one of the helper methods. func New(prefix string, logger log.Logger) *Statsd { return &Statsd{ prefix: prefix, rates: ratemap.New(), counters: lv.NewSpace(), gauges: lv.NewSpace(), timings: lv.NewSpace(), logger: logger, } } // NewCounter returns a counter, sending observations to this Statsd object. func (s *Statsd) NewCounter(name string, sampleRate float64) *Counter { s.rates.Set(s.prefix+name, sampleRate) return &Counter{ name: s.prefix + name, obs: s.counters.Observe, } } // NewGauge returns a gauge, sending observations to this Statsd object. func (s *Statsd) NewGauge(name string) *Gauge { return &Gauge{ name: s.prefix + name, obs: s.gauges.Observe, add: s.gauges.Add, } } // NewTiming returns a histogram whose observations are interpreted as // millisecond durations, and are forwarded to this Statsd object. func (s *Statsd) NewTiming(name string, sampleRate float64) *Timing { s.rates.Set(s.prefix+name, sampleRate) return &Timing{ name: s.prefix + name, obs: s.timings.Observe, } } // WriteLoop is a helper method that invokes WriteTo to the passed writer every // time the passed channel fires. This method blocks until ctx is canceled, // so clients probably want to run it in its own goroutine. For typical // usage, create a time.Ticker and pass its C channel to this method. func (s *Statsd) WriteLoop(ctx context.Context, c <-chan time.Time, w io.Writer) { for { select { case <-c: if _, err := s.WriteTo(w); err != nil { s.logger.Log("during", "WriteTo", "err", err) } case <-ctx.Done(): return } } } // SendLoop is a helper method that wraps WriteLoop, passing a managed // connection to the network and address. Like WriteLoop, this method blocks // until ctx is canceled, so clients probably want to start it in its own // goroutine. For typical usage, create a time.Ticker and pass its C channel to // this method. func (s *Statsd) SendLoop(ctx context.Context, c <-chan time.Time, network, address string) { s.WriteLoop(ctx, c, conn.NewDefaultManager(network, address, s.logger)) } // WriteTo flushes the buffered content of the metrics to the writer, in // StatsD format. WriteTo abides best-effort semantics, so observations are // lost if there is a problem with the write. Clients should be sure to call // WriteTo regularly, ideally through the WriteLoop or SendLoop helper methods. func (s *Statsd) WriteTo(w io.Writer) (count int64, err error) { var n int s.counters.Reset().Walk(func(name string, _ lv.LabelValues, values []float64) bool { n, err = fmt.Fprintf(w, "%s:%f|c%s\n", name, sum(values), sampling(s.rates.Get(name))) if err != nil { return false } count += int64(n) return true }) if err != nil { return count, err } s.gauges.Reset().Walk(func(name string, _ lv.LabelValues, values []float64) bool { n, err = fmt.Fprintf(w, "%s:%f|g\n", name, last(values)) if err != nil { return false } count += int64(n) return true }) if err != nil { return count, err } s.timings.Reset().Walk(func(name string, _ lv.LabelValues, values []float64) bool { sampleRate := s.rates.Get(name) for _, value := range values { n, err = fmt.Fprintf(w, "%s:%f|ms%s\n", name, value, sampling(sampleRate)) if err != nil { return false } count += int64(n) } return true }) if err != nil { return count, err } return count, err } func sum(a []float64) float64 { var v float64 for _, f := range a { v += f } return v } func last(a []float64) float64 { return a[len(a)-1] } func sampling(r float64) string { var sv string if r < 1.0 { sv = fmt.Sprintf("|@%f", r) } return sv } type observeFunc func(name string, lvs lv.LabelValues, value float64) // Counter is a StatsD counter. Observations are forwarded to a Statsd object, // and aggregated (summed) per timeseries. type Counter struct { name string obs observeFunc } // With is a no-op. func (c *Counter) With(...string) metrics.Counter { return c } // Add implements metrics.Counter. func (c *Counter) Add(delta float64) { c.obs(c.name, lv.LabelValues{}, delta) } // Gauge is a StatsD gauge. Observations are forwarded to a Statsd object, and // aggregated (the last observation selected) per timeseries. type Gauge struct { name string obs observeFunc add observeFunc } // With is a no-op. func (g *Gauge) With(...string) metrics.Gauge { return g } // Set implements metrics.Gauge. func (g *Gauge) Set(value float64) { g.obs(g.name, lv.LabelValues{}, value) } // Add implements metrics.Gauge. func (g *Gauge) Add(delta float64) { g.add(g.name, lv.LabelValues{}, delta) } // Timing is a StatsD timing, or metrics.Histogram. Observations are // forwarded to a Statsd object, and collected (but not aggregated) per // timeseries. type Timing struct { name string obs observeFunc } // With is a no-op. func (t *Timing) With(...string) metrics.Histogram { return t } // Observe implements metrics.Histogram. Value is interpreted as milliseconds. func (t *Timing) Observe(value float64) { t.obs(t.name, lv.LabelValues{}, value) } golang-github-go-kit-kit-0.13.0/metrics/statsd/statsd_test.go000066400000000000000000000037451443521372500241560ustar00rootroot00000000000000package statsd import ( "testing" "github.com/go-kit/kit/metrics/teststat" "github.com/go-kit/log" ) func TestCounter(t *testing.T) { prefix, name := "abc.", "def" label, value := "label", "value" // ignored regex := `^` + prefix + name + `:([0-9\.]+)\|c$` s := New(prefix, log.NewNopLogger()) counter := s.NewCounter(name, 1.0).With(label, value) valuef := teststat.SumLines(s, regex) if err := teststat.TestCounter(counter, valuef); err != nil { t.Fatal(err) } } func TestCounterSampled(t *testing.T) { // This will involve multiplying the observed sum by the inverse of the // sample rate and checking against the expected value within some // tolerance. t.Skip("TODO") } func TestGauge(t *testing.T) { prefix, name := "ghi.", "jkl" label, value := "xyz", "abc" // ignored regex := `^` + prefix + name + `:([0-9\.]+)\|g$` s := New(prefix, log.NewNopLogger()) gauge := s.NewGauge(name).With(label, value) valuef := teststat.LastLine(s, regex) if err := teststat.TestGauge(gauge, valuef); err != nil { t.Fatal(err) } } // StatsD timings just emit all observations. So, we collect them into a generic // histogram, and run the statistics test on that. func TestTiming(t *testing.T) { prefix, name := "statsd.", "timing_test" label, value := "abc", "def" // ignored regex := `^` + prefix + name + `:([0-9\.]+)\|ms$` s := New(prefix, log.NewNopLogger()) timing := s.NewTiming(name, 1.0).With(label, value) quantiles := teststat.Quantiles(s, regex, 50) // no |@0.X if err := teststat.TestHistogram(timing, quantiles, 0.01); err != nil { t.Fatal(err) } } func TestTimingSampled(t *testing.T) { prefix, name := "statsd.", "sampled_timing_test" label, value := "foo", "bar" // ignored regex := `^` + prefix + name + `:([0-9\.]+)\|ms\|@0\.01[0]*$` s := New(prefix, log.NewNopLogger()) timing := s.NewTiming(name, 0.01).With(label, value) quantiles := teststat.Quantiles(s, regex, 50) if err := teststat.TestHistogram(timing, quantiles, 0.02); err != nil { t.Fatal(err) } } golang-github-go-kit-kit-0.13.0/metrics/teststat/000077500000000000000000000000001443521372500216265ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/metrics/teststat/buffers.go000066400000000000000000000035231443521372500236140ustar00rootroot00000000000000package teststat import ( "bufio" "bytes" "io" "regexp" "strconv" "github.com/go-kit/kit/metrics/generic" ) // SumLines expects a regex whose first capture group can be parsed as a // float64. It will dump the WriterTo and parse each line, expecting to find a // match. It returns the sum of all captured floats. func SumLines(w io.WriterTo, regex string) func() float64 { return func() float64 { sum, _ := stats(w, regex, nil) return sum } } // LastLine expects a regex whose first capture group can be parsed as a // float64. It will dump the WriterTo and parse each line, expecting to find a // match. It returns the final captured float. func LastLine(w io.WriterTo, regex string) func() []float64 { return func() []float64 { _, final := stats(w, regex, nil) return []float64{final} } } // Quantiles expects a regex whose first capture group can be parsed as a // float64. It will dump the WriterTo and parse each line, expecting to find a // match. It observes all captured floats into a generic.Histogram with the // given number of buckets, and returns the 50th, 90th, 95th, and 99th quantiles // from that histogram. func Quantiles(w io.WriterTo, regex string, buckets int) func() (float64, float64, float64, float64) { return func() (float64, float64, float64, float64) { h := generic.NewHistogram("quantile-test", buckets) stats(w, regex, h) return h.Quantile(0.50), h.Quantile(0.90), h.Quantile(0.95), h.Quantile(0.99) } } func stats(w io.WriterTo, regex string, h *generic.Histogram) (sum, final float64) { re := regexp.MustCompile(regex) buf := &bytes.Buffer{} w.WriteTo(buf) s := bufio.NewScanner(buf) for s.Scan() { match := re.FindStringSubmatch(s.Text()) f, err := strconv.ParseFloat(match[1], 64) if err != nil { panic(err) } sum += f final = f if h != nil { h.Observe(f) } } return sum, final } golang-github-go-kit-kit-0.13.0/metrics/teststat/populate.go000066400000000000000000000041571443521372500240150ustar00rootroot00000000000000package teststat import ( "math" "math/rand" "github.com/go-kit/kit/metrics" ) // PopulateNormalHistogram makes a series of normal random observations into the // histogram. The number of observations is determined by Count. The randomness // is determined by Mean, Stdev, and the seed parameter. // // This is a low-level function, exported only for metrics that don't perform // dynamic quantile computation, like a Prometheus Histogram (c.f. Summary). In // most cases, you don't need to use this function, and can use TestHistogram // instead. func PopulateNormalHistogram(h metrics.Histogram, seed int) { r := rand.New(rand.NewSource(int64(seed))) for i := 0; i < Count; i++ { sample := r.NormFloat64()*float64(Stdev) + float64(Mean) if sample < 0 { sample = 0 } h.Observe(sample) } } func normalQuantiles() (p50, p90, p95, p99 float64) { return nvq(50), nvq(90), nvq(95), nvq(99) } func nvq(quantile int) float64 { // https://en.wikipedia.org/wiki/Normal_distribution#Quantile_function return float64(Mean) + float64(Stdev)*math.Sqrt2*erfinv(2*(float64(quantile)/100)-1) } func erfinv(y float64) float64 { // https://stackoverflow.com/questions/5971830/need-code-for-inverse-error-function if y < -1.0 || y > 1.0 { panic("invalid input") } var ( a = [4]float64{0.886226899, -1.645349621, 0.914624893, -0.140543331} b = [4]float64{-2.118377725, 1.442710462, -0.329097515, 0.012229801} c = [4]float64{-1.970840454, -1.624906493, 3.429567803, 1.641345311} d = [2]float64{3.543889200, 1.637067800} ) const y0 = 0.7 var x, z float64 if math.Abs(y) == 1.0 { x = -y * math.Log(0.0) } else if y < -y0 { z = math.Sqrt(-math.Log((1.0 + y) / 2.0)) x = -(((c[3]*z+c[2])*z+c[1])*z + c[0]) / ((d[1]*z+d[0])*z + 1.0) } else { if y < y0 { z = y * y x = y * (((a[3]*z+a[2])*z+a[1])*z + a[0]) / ((((b[3]*z+b[3])*z+b[1])*z+b[0])*z + 1.0) } else { z = math.Sqrt(-math.Log((1.0 - y) / 2.0)) x = (((c[3]*z+c[2])*z+c[1])*z + c[0]) / ((d[1]*z+d[0])*z + 1.0) } x -= (math.Erf(x) - y) / (2.0 / math.SqrtPi * math.Exp(-x*x)) x -= (math.Erf(x) - y) / (2.0 / math.SqrtPi * math.Exp(-x*x)) } return x } golang-github-go-kit-kit-0.13.0/metrics/teststat/teststat.go000066400000000000000000000071251443521372500240350ustar00rootroot00000000000000// Package teststat provides helpers for testing metrics backends. package teststat import ( "errors" "fmt" "math" "math/rand" "reflect" "sort" "strings" "github.com/go-kit/kit/metrics" ) // TestCounter puts some deltas through the counter, and then calls the value // func to check that the counter has the correct final value. func TestCounter(counter metrics.Counter, value func() float64) error { want := FillCounter(counter) if have := value(); want != have { return fmt.Errorf("want %f, have %f", want, have) } return nil } // FillCounter puts some deltas through the counter and returns the total value. func FillCounter(counter metrics.Counter) float64 { a := rand.Perm(100) n := rand.Intn(len(a)) var want float64 for i := 0; i < n; i++ { f := float64(a[i]) counter.Add(f) want += f } return want } // TestGauge puts some values through the gauge, and then calls the value func // to check that the gauge has the correct final value. func TestGauge(gauge metrics.Gauge, value func() []float64) error { a := rand.Perm(100) n := rand.Intn(len(a)) var want []float64 for i := 0; i < n; i++ { f := float64(a[i]) gauge.Set(f) want = append(want, f) } for i := 0; i < n; i++ { f := float64(a[i]) gauge.Add(f) want = append(want, want[len(want)-1]+f) } have := value() switch len(have) { case 0: return fmt.Errorf("got 0 values") case 1: // provider doesn't support multi value if have[0] != want[len(want)-1] { return fmt.Errorf("want %f, have %f", want, have) } default: // provider support multi value gauges sort.Float64s(want) sort.Float64s(have) if !reflect.DeepEqual(want, have) { return fmt.Errorf("want %f, have %f", want, have) } } return nil } // TestHistogram puts some observations through the histogram, and then calls // the quantiles func to checks that the histogram has computed the correct // quantiles within some tolerance func TestHistogram(histogram metrics.Histogram, quantiles func() (p50, p90, p95, p99 float64), tolerance float64) error { PopulateNormalHistogram(histogram, rand.Int()) want50, want90, want95, want99 := normalQuantiles() have50, have90, have95, have99 := quantiles() var errs []string if want, have := want50, have50; !cmp(want, have, tolerance) { errs = append(errs, fmt.Sprintf("p50: want %f, have %f", want, have)) } if want, have := want90, have90; !cmp(want, have, tolerance) { errs = append(errs, fmt.Sprintf("p90: want %f, have %f", want, have)) } if want, have := want95, have95; !cmp(want, have, tolerance) { errs = append(errs, fmt.Sprintf("p95: want %f, have %f", want, have)) } if want, have := want99, have99; !cmp(want, have, tolerance) { errs = append(errs, fmt.Sprintf("p99: want %f, have %f", want, have)) } if len(errs) > 0 { return errors.New(strings.Join(errs, "; ")) } return nil } var ( // Count is the number of observations. Count = 12345 // Mean is the center of the normal distribution of observations. Mean = 500 // Stdev of the normal distribution of observations. Stdev = 25 ) // ExpectedObservationsLessThan returns the number of observations that should // have a value less than or equal to the given value, given a normal // distribution of observations described by Count, Mean, and Stdev. func ExpectedObservationsLessThan(bucket int64) int64 { // https://code.google.com/p/gostat/source/browse/stat/normal.go cdf := ((1.0 / 2.0) * (1 + math.Erf((float64(bucket)-float64(Mean))/(float64(Stdev)*math.Sqrt2)))) return int64(cdf * float64(Count)) } func cmp(want, have, tol float64) bool { if (math.Abs(want-have) / want) > tol { return false } return true } golang-github-go-kit-kit-0.13.0/metrics/timer.go000066400000000000000000000015001443521372500214160ustar00rootroot00000000000000package metrics import "time" // Timer acts as a stopwatch, sending observations to a wrapped histogram. // It's a bit of helpful syntax sugar for h.Observe(time.Since(x)). type Timer struct { h Histogram t time.Time u time.Duration } // NewTimer wraps the given histogram and records the current time. func NewTimer(h Histogram) *Timer { return &Timer{ h: h, t: time.Now(), u: time.Second, } } // ObserveDuration captures the number of seconds since the timer was // constructed, and forwards that observation to the histogram. func (t *Timer) ObserveDuration() { d := float64(time.Since(t.t).Nanoseconds()) / float64(t.u) if d < 0 { d = 0 } t.h.Observe(d) } // Unit sets the unit of the float64 emitted by the timer. // By default, the timer emits seconds. func (t *Timer) Unit(u time.Duration) { t.u = u } golang-github-go-kit-kit-0.13.0/metrics/timer_test.go000066400000000000000000000025631443521372500224670ustar00rootroot00000000000000package metrics_test import ( "math" "testing" "time" "github.com/go-kit/kit/metrics" "github.com/go-kit/kit/metrics/generic" ) func TestTimerFast(t *testing.T) { h := generic.NewSimpleHistogram() metrics.NewTimer(h).ObserveDuration() tolerance := 0.050 if want, have := 0.000, h.ApproximateMovingAverage(); math.Abs(want-have) > tolerance { t.Errorf("want %.3f, have %.3f", want, have) } } func TestTimerSlow(t *testing.T) { h := generic.NewSimpleHistogram() timer := metrics.NewTimer(h) time.Sleep(250 * time.Millisecond) timer.ObserveDuration() tolerance := 0.050 if want, have := 0.250, h.ApproximateMovingAverage(); math.Abs(want-have) > tolerance { t.Errorf("want %.3f, have %.3f", want, have) } } func TestTimerUnit(t *testing.T) { for _, tc := range []struct { name string unit time.Duration tolerance float64 want float64 }{ {"Seconds", time.Second, 0.010, 0.100}, {"Milliseconds", time.Millisecond, 10, 100}, {"Nanoseconds", time.Nanosecond, 10000000, 100000000}, } { t.Run(tc.name, func(t *testing.T) { h := generic.NewSimpleHistogram() timer := metrics.NewTimer(h) time.Sleep(100 * time.Millisecond) timer.Unit(tc.unit) timer.ObserveDuration() if want, have := tc.want, h.ApproximateMovingAverage(); math.Abs(want-have) > tc.tolerance { t.Errorf("want %.3f, have %.3f", want, have) } }) } } golang-github-go-kit-kit-0.13.0/ratelimit/000077500000000000000000000000001443521372500202775ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/ratelimit/token_bucket.go000066400000000000000000000042331443521372500233050ustar00rootroot00000000000000package ratelimit import ( "context" "errors" "github.com/go-kit/kit/endpoint" ) // ErrLimited is returned in the request path when the rate limiter is // triggered and the request is rejected. var ErrLimited = errors.New("rate limit exceeded") // Allower dictates whether or not a request is acceptable to run. // The Limiter from "golang.org/x/time/rate" already implements this interface, // one is able to use that in NewErroringLimiter without any modifications. type Allower interface { Allow() bool } // NewErroringLimiter returns an endpoint.Middleware that acts as a rate // limiter. Requests that would exceed the // maximum request rate are simply rejected with an error. func NewErroringLimiter(limit Allower) endpoint.Middleware { return func(next endpoint.Endpoint) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { if !limit.Allow() { return nil, ErrLimited } return next(ctx, request) } } } // Waiter dictates how long a request must be delayed. // The Limiter from "golang.org/x/time/rate" already implements this interface, // one is able to use that in NewDelayingLimiter without any modifications. type Waiter interface { Wait(ctx context.Context) error } // NewDelayingLimiter returns an endpoint.Middleware that acts as a // request throttler. Requests that would // exceed the maximum request rate are delayed via the Waiter function func NewDelayingLimiter(limit Waiter) endpoint.Middleware { return func(next endpoint.Endpoint) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { if err := limit.Wait(ctx); err != nil { return nil, err } return next(ctx, request) } } } // AllowerFunc is an adapter that lets a function operate as if // it implements Allower type AllowerFunc func() bool // Allow makes the adapter implement Allower func (f AllowerFunc) Allow() bool { return f() } // WaiterFunc is an adapter that lets a function operate as if // it implements Waiter type WaiterFunc func(ctx context.Context) error // Wait makes the adapter implement Waiter func (f WaiterFunc) Wait(ctx context.Context) error { return f(ctx) } golang-github-go-kit-kit-0.13.0/ratelimit/token_bucket_test.go000066400000000000000000000022101443521372500243350ustar00rootroot00000000000000package ratelimit_test import ( "context" "strings" "testing" "time" "golang.org/x/time/rate" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/ratelimit" ) var nopEndpoint = func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil } func TestXRateErroring(t *testing.T) { limit := rate.NewLimiter(rate.Every(time.Minute), 1) testSuccessThenFailure( t, ratelimit.NewErroringLimiter(limit)(nopEndpoint), ratelimit.ErrLimited.Error()) } func TestXRateDelaying(t *testing.T) { limit := rate.NewLimiter(rate.Every(time.Minute), 1) testSuccessThenFailure( t, ratelimit.NewDelayingLimiter(limit)(nopEndpoint), "exceed context deadline") } func testSuccessThenFailure(t *testing.T, e endpoint.Endpoint, failContains string) { ctx, cxl := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cxl() // First request should succeed. if _, err := e(ctx, struct{}{}); err != nil { t.Errorf("unexpected: %v\n", err) } // Next request should fail. if _, err := e(ctx, struct{}{}); !strings.Contains(err.Error(), failContains) { t.Errorf("expected `%s`: %v\n", failContains, err) } } golang-github-go-kit-kit-0.13.0/sd/000077500000000000000000000000001443521372500167135ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/sd/benchmark_test.go000066400000000000000000000011301443521372500222260ustar00rootroot00000000000000package sd import ( "io" "testing" "github.com/go-kit/kit/endpoint" "github.com/go-kit/log" ) func BenchmarkEndpoints(b *testing.B) { var ( ca = make(closer) cb = make(closer) cmap = map[string]io.Closer{"a": ca, "b": cb} factory = func(instance string) (endpoint.Endpoint, io.Closer, error) { return endpoint.Nop, cmap[instance], nil } c = newEndpointCache(factory, log.NewNopLogger(), endpointerOptions{}) ) b.ReportAllocs() c.Update(Event{Instances: []string{"a", "b"}}) b.RunParallel(func(pb *testing.PB) { for pb.Next() { c.Endpoints() } }) } golang-github-go-kit-kit-0.13.0/sd/consul/000077500000000000000000000000001443521372500202165ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/sd/consul/client.go000066400000000000000000000022231443521372500220220ustar00rootroot00000000000000package consul import ( consul "github.com/hashicorp/consul/api" ) // Client is a wrapper around the Consul API. type Client interface { // Register a service with the local agent. Register(r *consul.AgentServiceRegistration) error // Deregister a service with the local agent. Deregister(r *consul.AgentServiceRegistration) error // Service Service(service, tag string, passingOnly bool, queryOpts *consul.QueryOptions) ([]*consul.ServiceEntry, *consul.QueryMeta, error) } type client struct { consul *consul.Client } // NewClient returns an implementation of the Client interface, wrapping a // concrete Consul client. func NewClient(c *consul.Client) Client { return &client{consul: c} } func (c *client) Register(r *consul.AgentServiceRegistration) error { return c.consul.Agent().ServiceRegister(r) } func (c *client) Deregister(r *consul.AgentServiceRegistration) error { return c.consul.Agent().ServiceDeregister(r.ID) } func (c *client) Service(service, tag string, passingOnly bool, queryOpts *consul.QueryOptions) ([]*consul.ServiceEntry, *consul.QueryMeta, error) { return c.consul.Health().Service(service, tag, passingOnly, queryOpts) } golang-github-go-kit-kit-0.13.0/sd/consul/client_test.go000066400000000000000000000065761443521372500231000ustar00rootroot00000000000000package consul import ( "context" "errors" "io" "reflect" "testing" stdconsul "github.com/hashicorp/consul/api" "github.com/go-kit/kit/endpoint" ) func TestClientRegistration(t *testing.T) { c := newTestClient(nil) services, _, err := c.Service(testRegistration.Name, "", true, &stdconsul.QueryOptions{}) if err != nil { t.Error(err) } if want, have := 0, len(services); want != have { t.Errorf("want %d, have %d", want, have) } if err := c.Register(testRegistration); err != nil { t.Error(err) } if err := c.Register(testRegistration); err == nil { t.Errorf("want error, have %v", err) } services, _, err = c.Service(testRegistration.Name, "", true, &stdconsul.QueryOptions{}) if err != nil { t.Error(err) } if want, have := 1, len(services); want != have { t.Errorf("want %d, have %d", want, have) } if err := c.Deregister(testRegistration); err != nil { t.Error(err) } if err := c.Deregister(testRegistration); err == nil { t.Errorf("want error, have %v", err) } services, _, err = c.Service(testRegistration.Name, "", true, &stdconsul.QueryOptions{}) if err != nil { t.Error(err) } if want, have := 0, len(services); want != have { t.Errorf("want %d, have %d", want, have) } } type testClient struct { entries []*stdconsul.ServiceEntry } func newTestClient(entries []*stdconsul.ServiceEntry) *testClient { return &testClient{ entries: entries, } } var _ Client = &testClient{} func (c *testClient) Service(service, tag string, _ bool, opts *stdconsul.QueryOptions) ([]*stdconsul.ServiceEntry, *stdconsul.QueryMeta, error) { var results []*stdconsul.ServiceEntry for _, entry := range c.entries { if entry.Service.Service != service { continue } if tag != "" { tagMap := map[string]struct{}{} for _, t := range entry.Service.Tags { tagMap[t] = struct{}{} } if _, ok := tagMap[tag]; !ok { continue } } results = append(results, entry) } return results, &stdconsul.QueryMeta{LastIndex: opts.WaitIndex}, nil } func (c *testClient) Register(r *stdconsul.AgentServiceRegistration) error { toAdd := registration2entry(r) for _, entry := range c.entries { if reflect.DeepEqual(*entry, *toAdd) { return errors.New("duplicate") } } c.entries = append(c.entries, toAdd) return nil } func (c *testClient) Deregister(r *stdconsul.AgentServiceRegistration) error { toDelete := registration2entry(r) var newEntries []*stdconsul.ServiceEntry for _, entry := range c.entries { if reflect.DeepEqual(*entry, *toDelete) { continue } newEntries = append(newEntries, entry) } if len(newEntries) == len(c.entries) { return errors.New("not found") } c.entries = newEntries return nil } func registration2entry(r *stdconsul.AgentServiceRegistration) *stdconsul.ServiceEntry { return &stdconsul.ServiceEntry{ Node: &stdconsul.Node{ Node: "some-node", Address: r.Address, }, Service: &stdconsul.AgentService{ ID: r.ID, Service: r.Name, Tags: r.Tags, Port: r.Port, Address: r.Address, }, // Checks ignored } } func testFactory(instance string) (endpoint.Endpoint, io.Closer, error) { return func(context.Context, interface{}) (interface{}, error) { return instance, nil }, nil, nil } var testRegistration = &stdconsul.AgentServiceRegistration{ ID: "my-id", Name: "my-name", Tags: []string{"my-tag-1", "my-tag-2"}, Port: 12345, Address: "my-address", } golang-github-go-kit-kit-0.13.0/sd/consul/doc.go000066400000000000000000000001361443521372500213120ustar00rootroot00000000000000// Package consul provides Instancer and Registrar implementations for Consul. package consul golang-github-go-kit-kit-0.13.0/sd/consul/instancer.go000066400000000000000000000106201443521372500225320ustar00rootroot00000000000000package consul import ( "errors" "fmt" "time" consul "github.com/hashicorp/consul/api" "github.com/go-kit/kit/sd" "github.com/go-kit/kit/sd/internal/instance" "github.com/go-kit/kit/util/conn" "github.com/go-kit/log" ) const defaultIndex = 0 // errStopped notifies the loop to quit. aka stopped via quitc var errStopped = errors.New("quit and closed consul instancer") // Instancer yields instances for a service in Consul. type Instancer struct { cache *instance.Cache client Client logger log.Logger service string tags []string passingOnly bool quitc chan struct{} } // NewInstancer returns a Consul instancer that publishes instances for the // requested service. It only returns instances for which all of the passed tags // are present. func NewInstancer(client Client, logger log.Logger, service string, tags []string, passingOnly bool) *Instancer { s := &Instancer{ cache: instance.NewCache(), client: client, logger: log.With(logger, "service", service, "tags", fmt.Sprint(tags)), service: service, tags: tags, passingOnly: passingOnly, quitc: make(chan struct{}), } instances, index, err := s.getInstances(defaultIndex, nil) if err == nil { s.logger.Log("instances", len(instances)) } else { s.logger.Log("err", err) } s.cache.Update(sd.Event{Instances: instances, Err: err}) go s.loop(index) return s } // Stop terminates the instancer. func (s *Instancer) Stop() { close(s.quitc) } func (s *Instancer) loop(lastIndex uint64) { var ( instances []string err error d time.Duration = 10 * time.Millisecond index uint64 ) for { instances, index, err = s.getInstances(lastIndex, s.quitc) switch { case errors.Is(err, errStopped): return // stopped via quitc case err != nil: s.logger.Log("err", err) time.Sleep(d) d = conn.Exponential(d) s.cache.Update(sd.Event{Err: err}) case index == defaultIndex: s.logger.Log("err", "index is not sane") time.Sleep(d) d = conn.Exponential(d) case index < lastIndex: s.logger.Log("err", "index is less than previous; resetting to default") lastIndex = defaultIndex time.Sleep(d) d = conn.Exponential(d) default: lastIndex = index s.cache.Update(sd.Event{Instances: instances}) d = 10 * time.Millisecond } } } func (s *Instancer) getInstances(lastIndex uint64, interruptc chan struct{}) ([]string, uint64, error) { tag := "" if len(s.tags) > 0 { tag = s.tags[0] } // Consul doesn't support more than one tag in its service query method. // https://github.com/hashicorp/consul/issues/294 // Hashi suggest prepared queries, but they don't support blocking. // https://www.consul.io/docs/agent/http/query.html#execute // If we want blocking for efficiency, we must filter tags manually. type response struct { instances []string index uint64 } var ( errc = make(chan error, 1) resc = make(chan response, 1) ) go func() { entries, meta, err := s.client.Service(s.service, tag, s.passingOnly, &consul.QueryOptions{ WaitIndex: lastIndex, }) if err != nil { errc <- err return } if len(s.tags) > 1 { entries = filterEntries(entries, s.tags[1:]...) } resc <- response{ instances: makeInstances(entries), index: meta.LastIndex, } }() select { case err := <-errc: return nil, 0, err case res := <-resc: return res.instances, res.index, nil case <-interruptc: return nil, 0, errStopped } } // Register implements Instancer. func (s *Instancer) Register(ch chan<- sd.Event) { s.cache.Register(ch) } // Deregister implements Instancer. func (s *Instancer) Deregister(ch chan<- sd.Event) { s.cache.Deregister(ch) } func filterEntries(entries []*consul.ServiceEntry, tags ...string) []*consul.ServiceEntry { var es []*consul.ServiceEntry ENTRIES: for _, entry := range entries { ts := make(map[string]struct{}, len(entry.Service.Tags)) for _, tag := range entry.Service.Tags { ts[tag] = struct{}{} } for _, tag := range tags { if _, ok := ts[tag]; !ok { continue ENTRIES } } es = append(es, entry) } return es } func makeInstances(entries []*consul.ServiceEntry) []string { instances := make([]string, len(entries)) for i, entry := range entries { addr := entry.Node.Address if entry.Service.Address != "" { addr = entry.Service.Address } instances[i] = fmt.Sprintf("%s:%d", addr, entry.Service.Port) } return instances } golang-github-go-kit-kit-0.13.0/sd/consul/instancer_test.go000066400000000000000000000164461443521372500236050ustar00rootroot00000000000000package consul import ( "context" "fmt" "io" "testing" "time" consul "github.com/hashicorp/consul/api" "github.com/go-kit/kit/sd" "github.com/go-kit/log" ) var _ sd.Instancer = (*Instancer)(nil) // API check var consulState = []*consul.ServiceEntry{ { Node: &consul.Node{ Address: "10.0.0.0", Node: "app00.local", }, Service: &consul.AgentService{ ID: "search-api-0", Port: 8000, Service: "search", Tags: []string{ "api", "v1", }, }, }, { Node: &consul.Node{ Address: "10.0.0.1", Node: "app01.local", }, Service: &consul.AgentService{ ID: "search-api-1", Port: 8001, Service: "search", Tags: []string{ "api", "v2", }, }, }, { Node: &consul.Node{ Address: "10.0.0.1", Node: "app01.local", }, Service: &consul.AgentService{ Address: "10.0.0.10", ID: "search-db-0", Port: 9000, Service: "search", Tags: []string{ "db", }, }, }, } func TestInstancer(t *testing.T) { var ( logger = log.NewNopLogger() client = newTestClient(consulState) ) s := NewInstancer(client, logger, "search", []string{"api"}, true) defer s.Stop() state := s.cache.State() if want, have := 2, len(state.Instances); want != have { t.Errorf("want %d, have %d", want, have) } } func TestInstancerNoService(t *testing.T) { var ( logger = log.NewNopLogger() client = newTestClient(consulState) ) s := NewInstancer(client, logger, "feed", []string{}, true) defer s.Stop() state := s.cache.State() if want, have := 0, len(state.Instances); want != have { t.Fatalf("want %d, have %d", want, have) } } func TestInstancerWithTags(t *testing.T) { var ( logger = log.NewNopLogger() client = newTestClient(consulState) ) s := NewInstancer(client, logger, "search", []string{"api", "v2"}, true) defer s.Stop() state := s.cache.State() if want, have := 1, len(state.Instances); want != have { t.Fatalf("want %d, have %d", want, have) } } func TestInstancerAddressOverride(t *testing.T) { s := NewInstancer(newTestClient(consulState), log.NewNopLogger(), "search", []string{"db"}, true) defer s.Stop() state := s.cache.State() if want, have := 1, len(state.Instances); want != have { t.Fatalf("want %d, have %d", want, have) } endpoint, closer, err := testFactory(state.Instances[0]) if err != nil { t.Fatal(err) } if closer != nil { defer closer.Close() } response, err := endpoint(context.Background(), struct{}{}) if err != nil { t.Fatal(err) } if want, have := "10.0.0.10:9000", response.(string); want != have { t.Errorf("want %q, have %q", want, have) } } type eofTestClient struct { client *testClient eofSig chan bool called chan struct{} } func neweofTestClient(client *testClient, sig chan bool, called chan struct{}) Client { return &eofTestClient{client: client, eofSig: sig, called: called} } func (c *eofTestClient) Register(r *consul.AgentServiceRegistration) error { return c.client.Register(r) } func (c *eofTestClient) Deregister(r *consul.AgentServiceRegistration) error { return c.client.Deregister(r) } func (c *eofTestClient) Service(service, tag string, passingOnly bool, queryOpts *consul.QueryOptions) ([]*consul.ServiceEntry, *consul.QueryMeta, error) { c.called <- struct{}{} shouldEOF := <-c.eofSig if shouldEOF { return nil, &consul.QueryMeta{}, io.EOF } return c.client.Service(service, tag, passingOnly, queryOpts) } func TestInstancerWithEOF(t *testing.T) { var ( sig = make(chan bool, 1) called = make(chan struct{}, 1) logger = log.NewNopLogger() client = neweofTestClient(newTestClient(consulState), sig, called) ) sig <- false s := NewInstancer(client, logger, "search", []string{"api"}, true) defer s.Stop() select { case <-called: case <-time.Tick(time.Millisecond * 500): t.Error("failed, to receive call") } state := s.cache.State() if want, have := 2, len(state.Instances); want != have { t.Errorf("want %d, have %d", want, have) } // some error occurred resulting in io.EOF sig <- true // Service Called Once select { case <-called: case <-time.Tick(time.Millisecond * 500): t.Error("failed, to receive call in time") } sig <- false // loop should continue select { case <-called: case <-time.Tick(time.Millisecond * 500): t.Error("failed, to receive call in time") } } type badIndexTestClient struct { client *testClient called chan struct{} } func newBadIndexTestClient(client *testClient, called chan struct{}) Client { return &badIndexTestClient{client: client, called: called} } func (c *badIndexTestClient) Register(r *consul.AgentServiceRegistration) error { return c.client.Register(r) } func (c *badIndexTestClient) Deregister(r *consul.AgentServiceRegistration) error { return c.client.Deregister(r) } func (c *badIndexTestClient) Service(service, tag string, passingOnly bool, queryOpts *consul.QueryOptions) ([]*consul.ServiceEntry, *consul.QueryMeta, error) { switch { case queryOpts.WaitIndex == 0: queryOpts.WaitIndex = 100 case queryOpts.WaitIndex == 100: queryOpts.WaitIndex = 99 default: } c.called <- struct{}{} return c.client.Service(service, tag, passingOnly, queryOpts) } func TestInstancerWithInvalidIndex(t *testing.T) { var ( called = make(chan struct{}, 1) logger = log.NewNopLogger() client = newBadIndexTestClient(newTestClient(consulState), called) ) s := NewInstancer(client, logger, "search", []string{"api"}, true) defer s.Stop() select { case <-called: case <-time.Tick(time.Millisecond * 500): t.Error("failed, to receive call") } state := s.cache.State() if want, have := 2, len(state.Instances); want != have { t.Errorf("want %d, have %d", want, have) } // loop should continue select { case <-called: case <-time.Tick(time.Millisecond * 500): t.Error("failed, to receive call in time") } } type indexTestClient struct { client *testClient index uint64 errs chan error } func newIndexTestClient(c *testClient, errs chan error) *indexTestClient { return &indexTestClient{ client: c, index: 0, errs: errs, } } func (i *indexTestClient) Register(r *consul.AgentServiceRegistration) error { return i.client.Register(r) } func (i *indexTestClient) Deregister(r *consul.AgentServiceRegistration) error { return i.client.Deregister(r) } func (i *indexTestClient) Service(service, tag string, passingOnly bool, queryOpts *consul.QueryOptions) ([]*consul.ServiceEntry, *consul.QueryMeta, error) { // Assumes this is the first call Service, loop hasn't begun running yet if i.index == 0 && queryOpts.WaitIndex == 0 { i.index = 100 entries, meta, err := i.client.Service(service, tag, passingOnly, queryOpts) meta.LastIndex = i.index return entries, meta, err } if queryOpts.WaitIndex < i.index { i.errs <- fmt.Errorf("wait index %d is less than or equal to previous value", queryOpts.WaitIndex) } entries, meta, err := i.client.Service(service, tag, passingOnly, queryOpts) i.index++ meta.LastIndex = i.index return entries, meta, err } func TestInstancerLoopIndex(t *testing.T) { var ( errs = make(chan error, 1) logger = log.NewNopLogger() client = newIndexTestClient(newTestClient(consulState), errs) ) go func() { for err := range errs { t.Error(err) t.FailNow() } }() instancer := NewInstancer(client, logger, "search", []string{"api"}, true) defer instancer.Stop() time.Sleep(2 * time.Second) } golang-github-go-kit-kit-0.13.0/sd/consul/integration_test.go000066400000000000000000000037301443521372500241320ustar00rootroot00000000000000//go:build integration // +build integration package consul import ( "io" "os" "testing" "time" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/sd" "github.com/go-kit/log" stdconsul "github.com/hashicorp/consul/api" ) func TestIntegration(t *testing.T) { consulAddr := os.Getenv("CONSUL_ADDR") if consulAddr == "" { t.Skip("CONSUL_ADDR not set; skipping integration test") } stdClient, err := stdconsul.NewClient(&stdconsul.Config{ Address: consulAddr, }) if err != nil { t.Fatal(err) } client := NewClient(stdClient) logger := log.NewLogfmtLogger(os.Stderr) // Produce a fake service registration. r := &stdconsul.AgentServiceRegistration{ ID: "my-service-ID", Name: "my-service-name", Tags: []string{"alpha", "beta"}, Port: 12345, Address: "my-address", EnableTagOverride: false, // skipping check(s) } // Build an Instancer on r.Name + r.Tags. factory := func(instance string) (endpoint.Endpoint, io.Closer, error) { t.Logf("factory invoked for %q", instance) return endpoint.Nop, nil, nil } instancer := NewInstancer( client, log.With(logger, "component", "instancer"), r.Name, r.Tags, true, ) endpointer := sd.NewEndpointer( instancer, factory, log.With(logger, "component", "endpointer"), ) time.Sleep(time.Second) // Before we publish, we should have no endpoints. endpoints, err := endpointer.Endpoints() if err != nil { t.Error(err) } if want, have := 0, len(endpoints); want != have { t.Errorf("want %d, have %d", want, have) } // Build a registrar for r. registrar := NewRegistrar(client, r, log.With(logger, "component", "registrar")) registrar.Register() defer registrar.Deregister() time.Sleep(time.Second) // Now we should have one active endpoints. endpoints, err = endpointer.Endpoints() if err != nil { t.Error(err) } if want, have := 1, len(endpoints); want != have { t.Errorf("want %d, have %d", want, have) } } golang-github-go-kit-kit-0.13.0/sd/consul/registrar.go000066400000000000000000000021321443521372500225450ustar00rootroot00000000000000package consul import ( "fmt" stdconsul "github.com/hashicorp/consul/api" "github.com/go-kit/log" ) // Registrar registers service instance liveness information to Consul. type Registrar struct { client Client registration *stdconsul.AgentServiceRegistration logger log.Logger } // NewRegistrar returns a Consul Registrar acting on the provided catalog // registration. func NewRegistrar(client Client, r *stdconsul.AgentServiceRegistration, logger log.Logger) *Registrar { return &Registrar{ client: client, registration: r, logger: log.With(logger, "service", r.Name, "tags", fmt.Sprint(r.Tags), "address", r.Address), } } // Register implements sd.Registrar interface. func (p *Registrar) Register() { if err := p.client.Register(p.registration); err != nil { p.logger.Log("err", err) } else { p.logger.Log("action", "register") } } // Deregister implements sd.Registrar interface. func (p *Registrar) Deregister() { if err := p.client.Deregister(p.registration); err != nil { p.logger.Log("err", err) } else { p.logger.Log("action", "deregister") } } golang-github-go-kit-kit-0.13.0/sd/consul/registrar_test.go000066400000000000000000000011401443521372500236020ustar00rootroot00000000000000package consul import ( "testing" stdconsul "github.com/hashicorp/consul/api" "github.com/go-kit/log" ) func TestRegistrar(t *testing.T) { client := newTestClient([]*stdconsul.ServiceEntry{}) p := NewRegistrar(client, testRegistration, log.NewNopLogger()) if want, have := 0, len(client.entries); want != have { t.Errorf("want %d, have %d", want, have) } p.Register() if want, have := 1, len(client.entries); want != have { t.Errorf("want %d, have %d", want, have) } p.Deregister() if want, have := 0, len(client.entries); want != have { t.Errorf("want %d, have %d", want, have) } } golang-github-go-kit-kit-0.13.0/sd/dnssrv/000077500000000000000000000000001443521372500202325ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/sd/dnssrv/doc.go000066400000000000000000000001331443521372500213230ustar00rootroot00000000000000// Package dnssrv provides an Instancer implementation for DNS SRV records. package dnssrv golang-github-go-kit-kit-0.13.0/sd/dnssrv/instancer.go000066400000000000000000000050561443521372500225550ustar00rootroot00000000000000package dnssrv import ( "errors" "fmt" "net" "time" "github.com/go-kit/kit/sd" "github.com/go-kit/kit/sd/internal/instance" "github.com/go-kit/log" ) // ErrPortZero is returned by the resolve machinery // when a DNS resolver returns an SRV record with its // port set to zero. var ErrPortZero = errors.New("resolver returned SRV record with port 0") // Instancer yields instances from the named DNS SRV record. The name is // resolved on a fixed schedule. Priorities and weights are ignored. type Instancer struct { cache *instance.Cache name string logger log.Logger quit chan struct{} } // NewInstancer returns a DNS SRV instancer. func NewInstancer( name string, ttl time.Duration, logger log.Logger, ) *Instancer { return NewInstancerDetailed(name, time.NewTicker(ttl), net.LookupSRV, logger) } // NewInstancerDetailed is the same as NewInstancer, but allows users to // provide an explicit lookup refresh ticker instead of a TTL, and specify the // lookup function instead of using net.LookupSRV. func NewInstancerDetailed( name string, refresh *time.Ticker, lookup Lookup, logger log.Logger, ) *Instancer { p := &Instancer{ cache: instance.NewCache(), name: name, logger: logger, quit: make(chan struct{}), } instances, err := p.resolve(lookup) if err == nil { logger.Log("name", name, "instances", len(instances)) } else { logger.Log("name", name, "err", err) } p.cache.Update(sd.Event{Instances: instances, Err: err}) go p.loop(refresh, lookup) return p } // Stop terminates the Instancer. func (in *Instancer) Stop() { close(in.quit) } func (in *Instancer) loop(t *time.Ticker, lookup Lookup) { defer t.Stop() for { select { case <-t.C: instances, err := in.resolve(lookup) if err != nil { in.logger.Log("name", in.name, "err", err) in.cache.Update(sd.Event{Err: err}) continue // don't replace potentially-good with bad } in.cache.Update(sd.Event{Instances: instances}) case <-in.quit: return } } } func (in *Instancer) resolve(lookup Lookup) ([]string, error) { _, addrs, err := lookup("", "", in.name) if err != nil { return nil, err } instances := make([]string, len(addrs)) for i, addr := range addrs { if addr.Port == 0 { return nil, ErrPortZero } instances[i] = net.JoinHostPort(addr.Target, fmt.Sprint(addr.Port)) } return instances, nil } // Register implements Instancer. func (in *Instancer) Register(ch chan<- sd.Event) { in.cache.Register(ch) } // Deregister implements Instancer. func (in *Instancer) Deregister(ch chan<- sd.Event) { in.cache.Deregister(ch) } golang-github-go-kit-kit-0.13.0/sd/dnssrv/instancer_test.go000066400000000000000000000046611443521372500236150ustar00rootroot00000000000000package dnssrv import ( "net" "sync/atomic" "testing" "time" "github.com/go-kit/kit/sd" "github.com/go-kit/log" ) var _ sd.Instancer = (*Instancer)(nil) // API check func TestRefresh(t *testing.T) { name := "some.service.internal" ticker := time.NewTicker(time.Second) ticker.Stop() tickc := make(chan time.Time) ticker.C = tickc var lookups uint64 records := []*net.SRV{} lookup := func(service, proto, name string) (string, []*net.SRV, error) { t.Logf("lookup(%q, %q, %q)", service, proto, name) atomic.AddUint64(&lookups, 1) return "cname", records, nil } instancer := NewInstancerDetailed(name, ticker, lookup, log.NewNopLogger()) defer instancer.Stop() // First lookup, empty state := instancer.cache.State() if state.Err != nil { t.Error(state.Err) } if want, have := 0, len(state.Instances); want != have { t.Errorf("want %d, have %d", want, have) } if want, have := uint64(1), atomic.LoadUint64(&lookups); want != have { t.Errorf("want %d, have %d", want, have) } // Load some records and lookup again records = []*net.SRV{ {Target: "1.0.0.1", Port: 1001}, {Target: "1.0.0.2", Port: 1002}, {Target: "1.0.0.3", Port: 1003}, } tickc <- time.Now() // There is a race condition where the instancer.State call below // invokes the cache before it is updated by the tick above. // TODO(pb): solve by running the read through the loop goroutine. time.Sleep(100 * time.Millisecond) state = instancer.cache.State() if state.Err != nil { t.Error(state.Err) } if want, have := 3, len(state.Instances); want != have { t.Errorf("want %d, have %d", want, have) } if want, have := uint64(2), atomic.LoadUint64(&lookups); want != have { t.Errorf("want %d, have %d", want, have) } } func TestIssue892(t *testing.T) { ticker := time.NewTicker(time.Second) ticker.Stop() tickc := make(chan time.Time) ticker.C = tickc records := []*net.SRV{ {Target: "1.0.0.1", Port: 80}, {Target: "1.0.0.2", Port: 0}, {Target: "1.0.0.3", Port: 80}, } lookup := func(service, proto, name string) (string, []*net.SRV, error) { return "cname", records, nil } instancer := NewInstancerDetailed("name", ticker, lookup, log.NewNopLogger()) defer instancer.Stop() tickc <- time.Now() time.Sleep(100 * time.Millisecond) if want, have := ErrPortZero, instancer.cache.State().Err; want != have { t.Fatalf("want %v, have %v", want, have) } } type nopCloser struct{} func (nopCloser) Close() error { return nil } golang-github-go-kit-kit-0.13.0/sd/dnssrv/lookup.go000066400000000000000000000003651443521372500220760ustar00rootroot00000000000000package dnssrv import "net" // Lookup is a function that resolves a DNS SRV record to multiple addresses. // It has the same signature as net.LookupSRV. type Lookup func(service, proto, name string) (cname string, addrs []*net.SRV, err error) golang-github-go-kit-kit-0.13.0/sd/doc.go000066400000000000000000000006131443521372500200070ustar00rootroot00000000000000// Package sd provides utilities related to service discovery. That includes the // client-side loadbalancer pattern, where a microservice subscribes to a // service discovery system in order to reach remote instances; as well as the // registrator pattern, where a microservice registers itself in a service // discovery system. Implementations are provided for most common systems. package sd golang-github-go-kit-kit-0.13.0/sd/endpoint_cache.go000066400000000000000000000075041443521372500222130ustar00rootroot00000000000000package sd import ( "io" "sort" "sync" "time" "github.com/go-kit/kit/endpoint" "github.com/go-kit/log" ) // endpointCache collects the most recent set of instances from a service discovery // system, creates endpoints for them using a factory function, and makes // them available to consumers. type endpointCache struct { options endpointerOptions mtx sync.RWMutex factory Factory cache map[string]endpointCloser err error endpoints []endpoint.Endpoint logger log.Logger invalidateDeadline time.Time timeNow func() time.Time } type endpointCloser struct { endpoint.Endpoint io.Closer } // newEndpointCache returns a new, empty endpointCache. func newEndpointCache(factory Factory, logger log.Logger, options endpointerOptions) *endpointCache { return &endpointCache{ options: options, factory: factory, cache: map[string]endpointCloser{}, logger: logger, timeNow: time.Now, } } // Update should be invoked by clients with a complete set of current instance // strings whenever that set changes. The cache manufactures new endpoints via // the factory, closes old endpoints when they disappear, and persists existing // endpoints if they survive through an update. func (c *endpointCache) Update(event Event) { c.mtx.Lock() defer c.mtx.Unlock() // Happy path. if event.Err == nil { c.updateCache(event.Instances) c.err = nil return } // Sad path. Something's gone wrong in sd. c.logger.Log("err", event.Err) if !c.options.invalidateOnError { return // keep returning the last known endpoints on error } if c.err != nil { return // already in the error state, do nothing & keep original error } c.err = event.Err // set new deadline to invalidate Endpoints unless non-error Event is received c.invalidateDeadline = c.timeNow().Add(c.options.invalidateTimeout) return } func (c *endpointCache) updateCache(instances []string) { // Deterministic order (for later). sort.Strings(instances) // Produce the current set of services. cache := make(map[string]endpointCloser, len(instances)) for _, instance := range instances { // If it already exists, just copy it over. if sc, ok := c.cache[instance]; ok { cache[instance] = sc delete(c.cache, instance) continue } // If it doesn't exist, create it. service, closer, err := c.factory(instance) if err != nil { c.logger.Log("instance", instance, "err", err) continue } cache[instance] = endpointCloser{service, closer} } // Close any leftover endpoints. for _, sc := range c.cache { if sc.Closer != nil { sc.Closer.Close() } } // Populate the slice of endpoints. endpoints := make([]endpoint.Endpoint, 0, len(cache)) for _, instance := range instances { // A bad factory may mean an instance is not present. if _, ok := cache[instance]; !ok { continue } endpoints = append(endpoints, cache[instance].Endpoint) } // Swap and trigger GC for old copies. c.endpoints = endpoints c.cache = cache } // Endpoints yields the current set of (presumably identical) endpoints, ordered // lexicographically by the corresponding instance string. func (c *endpointCache) Endpoints() ([]endpoint.Endpoint, error) { // in the steady state we're going to have many goroutines calling Endpoints() // concurrently, so to minimize contention we use a shared R-lock. c.mtx.RLock() if c.err == nil || c.timeNow().Before(c.invalidateDeadline) { defer c.mtx.RUnlock() return c.endpoints, nil } c.mtx.RUnlock() // in case of an error, switch to an exclusive lock. c.mtx.Lock() defer c.mtx.Unlock() // re-check condition due to a race between RUnlock() and Lock(). if c.err == nil || c.timeNow().Before(c.invalidateDeadline) { return c.endpoints, nil } c.updateCache(nil) // close any remaining active endpoints return nil, c.err } golang-github-go-kit-kit-0.13.0/sd/endpoint_cache_test.go000066400000000000000000000111611443521372500232440ustar00rootroot00000000000000package sd import ( "errors" "io" "testing" "time" "github.com/go-kit/kit/endpoint" "github.com/go-kit/log" ) func TestEndpointCache(t *testing.T) { var ( ca = make(closer) cb = make(closer) c = map[string]io.Closer{"a": ca, "b": cb} f = func(instance string) (endpoint.Endpoint, io.Closer, error) { return endpoint.Nop, c[instance], nil } cache = newEndpointCache(f, log.NewNopLogger(), endpointerOptions{}) ) // Populate cache.Update(Event{Instances: []string{"a", "b"}}) select { case <-ca: t.Errorf("endpoint a closed, not good") case <-cb: t.Errorf("endpoint b closed, not good") case <-time.After(time.Millisecond): t.Logf("no closures yet, good") } assertEndpointsLen(t, cache, 2) // Duplicate, should be no-op cache.Update(Event{Instances: []string{"a", "b"}}) select { case <-ca: t.Errorf("endpoint a closed, not good") case <-cb: t.Errorf("endpoint b closed, not good") case <-time.After(time.Millisecond): t.Logf("no closures yet, good") } assertEndpointsLen(t, cache, 2) // Error, should continue returning old endpoints cache.Update(Event{Err: errors.New("sd error")}) select { case <-ca: t.Errorf("endpoint a closed, not good") case <-cb: t.Errorf("endpoint b closed, not good") case <-time.After(time.Millisecond): t.Logf("no closures yet, good") } assertEndpointsLen(t, cache, 2) // Delete b go cache.Update(Event{Instances: []string{"a"}}) select { case <-ca: t.Errorf("endpoint a closed, not good") case <-cb: t.Logf("endpoint b closed, good") case <-time.After(time.Second): t.Errorf("didn't close the deleted instance in time") } assertEndpointsLen(t, cache, 1) // Delete a go cache.Update(Event{Instances: []string{}}) select { // case <-cb: will succeed, as it's closed case <-ca: t.Logf("endpoint a closed, good") case <-time.After(time.Second): t.Errorf("didn't close the deleted instance in time") } assertEndpointsLen(t, cache, 0) } func TestEndpointCacheErrorAndTimeout(t *testing.T) { var ( ca = make(closer) cb = make(closer) c = map[string]io.Closer{"a": ca, "b": cb} f = func(instance string) (endpoint.Endpoint, io.Closer, error) { return endpoint.Nop, c[instance], nil } timeOut = 100 * time.Millisecond cache = newEndpointCache(f, log.NewNopLogger(), endpointerOptions{ invalidateOnError: true, invalidateTimeout: timeOut, }) ) timeNow := time.Now() cache.timeNow = func() time.Time { return timeNow } // Populate cache.Update(Event{Instances: []string{"a"}}) select { case <-ca: t.Errorf("endpoint a closed, not good") case <-time.After(time.Millisecond): t.Logf("no closures yet, good") } assertEndpointsLen(t, cache, 1) // Send error, keep time still. cache.Update(Event{Err: errors.New("sd error")}) select { case <-ca: t.Errorf("endpoint a closed, not good") case <-time.After(time.Millisecond): t.Logf("no closures yet, good") } assertEndpointsLen(t, cache, 1) // Move the time, but less than the timeout timeNow = timeNow.Add(timeOut / 2) assertEndpointsLen(t, cache, 1) select { case <-ca: t.Errorf("endpoint a closed, not good") case <-time.After(time.Millisecond): t.Logf("no closures yet, good") } // Move the time past the timeout timeNow = timeNow.Add(timeOut) assertEndpointsError(t, cache, "sd error") select { case <-ca: t.Logf("endpoint a closed, good") case <-time.After(time.Millisecond): t.Errorf("didn't close the deleted instance in time") } // Send another error cache.Update(Event{Err: errors.New("another sd error")}) assertEndpointsError(t, cache, "sd error") // expect original error } func TestBadFactory(t *testing.T) { cache := newEndpointCache(func(string) (endpoint.Endpoint, io.Closer, error) { return nil, nil, errors.New("bad factory") }, log.NewNopLogger(), endpointerOptions{}) cache.Update(Event{Instances: []string{"foo:1234", "bar:5678"}}) assertEndpointsLen(t, cache, 0) } func assertEndpointsLen(t *testing.T, cache *endpointCache, l int) { endpoints, err := cache.Endpoints() if err != nil { t.Errorf("unexpected error %v", err) return } if want, have := l, len(endpoints); want != have { t.Errorf("want %d, have %d", want, have) } } func assertEndpointsError(t *testing.T, cache *endpointCache, wantErr string) { endpoints, err := cache.Endpoints() if err == nil { t.Errorf("expecting error, not good") return } if want, have := wantErr, err.Error(); want != have { t.Errorf("want %s, have %s", want, have) return } if want, have := 0, len(endpoints); want != have { t.Errorf("want %d, have %d", want, have) } } type closer chan struct{} func (c closer) Close() error { close(c); return nil } golang-github-go-kit-kit-0.13.0/sd/endpointer.go000066400000000000000000000056451443521372500214230ustar00rootroot00000000000000package sd import ( "time" "github.com/go-kit/kit/endpoint" "github.com/go-kit/log" ) // Endpointer listens to a service discovery system and yields a set of // identical endpoints on demand. An error indicates a problem with connectivity // to the service discovery system, or within the system itself; an Endpointer // may yield no endpoints without error. type Endpointer interface { Endpoints() ([]endpoint.Endpoint, error) } // FixedEndpointer yields a fixed set of endpoints. type FixedEndpointer []endpoint.Endpoint // Endpoints implements Endpointer. func (s FixedEndpointer) Endpoints() ([]endpoint.Endpoint, error) { return s, nil } // NewEndpointer creates an Endpointer that subscribes to updates from Instancer src // and uses factory f to create Endpoints. If src notifies of an error, the Endpointer // keeps returning previously created Endpoints assuming they are still good, unless // this behavior is disabled via InvalidateOnError option. func NewEndpointer(src Instancer, f Factory, logger log.Logger, options ...EndpointerOption) *DefaultEndpointer { opts := endpointerOptions{} for _, opt := range options { opt(&opts) } se := &DefaultEndpointer{ cache: newEndpointCache(f, logger, opts), instancer: src, ch: make(chan Event), } go se.receive() src.Register(se.ch) return se } // EndpointerOption allows control of endpointCache behavior. type EndpointerOption func(*endpointerOptions) // InvalidateOnError returns EndpointerOption that controls how the Endpointer // behaves when then Instancer publishes an Event containing an error. // Without this option the Endpointer continues returning the last known // endpoints. With this option, the Endpointer continues returning the last // known endpoints until the timeout elapses, then closes all active endpoints // and starts returning an error. Once the Instancer sends a new update with // valid resource instances, the normal operation is resumed. func InvalidateOnError(timeout time.Duration) EndpointerOption { return func(opts *endpointerOptions) { opts.invalidateOnError = true opts.invalidateTimeout = timeout } } type endpointerOptions struct { invalidateOnError bool invalidateTimeout time.Duration } // DefaultEndpointer implements an Endpointer interface. // When created with NewEndpointer function, it automatically registers // as a subscriber to events from the Instances and maintains a list // of active Endpoints. type DefaultEndpointer struct { cache *endpointCache instancer Instancer ch chan Event } func (de *DefaultEndpointer) receive() { for event := range de.ch { de.cache.Update(event) } } // Close deregisters DefaultEndpointer from the Instancer and stops the internal go-routine. func (de *DefaultEndpointer) Close() { de.instancer.Deregister(de.ch) close(de.ch) } // Endpoints implements Endpointer. func (de *DefaultEndpointer) Endpoints() ([]endpoint.Endpoint, error) { return de.cache.Endpoints() } golang-github-go-kit-kit-0.13.0/sd/endpointer_test.go000066400000000000000000000041411443521372500224500ustar00rootroot00000000000000package sd_test import ( "io" "testing" "time" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/sd" "github.com/go-kit/kit/sd/internal/instance" "github.com/go-kit/log" ) func TestDefaultEndpointer(t *testing.T) { var ( ca = make(closer) cb = make(closer) c = map[string]io.Closer{"a": ca, "b": cb} f = func(instance string) (endpoint.Endpoint, io.Closer, error) { return endpoint.Nop, c[instance], nil } instancer = &mockInstancer{instance.NewCache()} ) // set initial state instancer.Update(sd.Event{Instances: []string{"a", "b"}}) endpointer := sd.NewEndpointer(instancer, f, log.NewNopLogger(), sd.InvalidateOnError(time.Minute)) var ( endpoints []endpoint.Endpoint err error ) if !within(time.Second, func() bool { endpoints, err = endpointer.Endpoints() return err == nil && len(endpoints) == 2 }) { t.Errorf("wanted 2 endpoints, got %d (%v)", len(endpoints), err) } instancer.Update(sd.Event{Instances: []string{}}) select { case <-ca: t.Logf("endpoint a closed, good") case <-time.After(time.Millisecond): t.Errorf("didn't close the deleted instance in time") } select { case <-cb: t.Logf("endpoint b closed, good") case <-time.After(time.Millisecond): t.Errorf("didn't close the deleted instance in time") } if endpoints, err := endpointer.Endpoints(); err != nil { t.Errorf("unepected error %v", err) } else if want, have := 0, len(endpoints); want != have { t.Errorf("want %d, have %d", want, have) } endpointer.Close() instancer.Update(sd.Event{Instances: []string{"a"}}) // TODO verify that on Close the endpointer fully disconnects from the instancer. // Unfortunately, because we use instance.Cache, this test cannot be in the sd package, // and therefore does not have access to the endpointer's private members. } type mockInstancer struct{ *instance.Cache } type closer chan struct{} func (c closer) Close() error { close(c); return nil } func within(d time.Duration, f func() bool) bool { deadline := time.Now().Add(d) for time.Now().Before(deadline) { if f() { return true } time.Sleep(d / 10) } return false } golang-github-go-kit-kit-0.13.0/sd/etcd/000077500000000000000000000000001443521372500176325ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/sd/etcd/client.go000066400000000000000000000115601443521372500214420ustar00rootroot00000000000000package etcd import ( "context" "crypto/tls" "crypto/x509" "errors" "io/ioutil" "net" "net/http" "time" etcd "go.etcd.io/etcd/client/v2" ) var ( // ErrNoKey indicates a client method needs a key but receives none. ErrNoKey = errors.New("no key provided") // ErrNoValue indicates a client method needs a value but receives none. ErrNoValue = errors.New("no value provided") ) // Client is a wrapper around the etcd client. type Client interface { // GetEntries queries the given prefix in etcd and returns a slice // containing the values of all keys found, recursively, underneath that // prefix. GetEntries(prefix string) ([]string, error) // WatchPrefix watches the given prefix in etcd for changes. When a change // is detected, it will signal on the passed channel. Clients are expected // to call GetEntries to update themselves with the latest set of complete // values. WatchPrefix will always send an initial sentinel value on the // channel after establishing the watch, to ensure that clients always // receive the latest set of values. WatchPrefix will block until the // context passed to the NewClient constructor is terminated. WatchPrefix(prefix string, ch chan struct{}) // Register a service with etcd. Register(s Service) error // Deregister a service with etcd. Deregister(s Service) error } type client struct { keysAPI etcd.KeysAPI ctx context.Context } // ClientOptions defines options for the etcd client. All values are optional. // If any duration is not specified, a default of 3 seconds will be used. type ClientOptions struct { Cert string Key string CACert string DialTimeout time.Duration DialKeepAlive time.Duration HeaderTimeoutPerRequest time.Duration } // NewClient returns Client with a connection to the named machines. It will // return an error if a connection to the cluster cannot be made. The parameter // machines needs to be a full URL with schemas. e.g. "http://localhost:2379" // will work, but "localhost:2379" will not. func NewClient(ctx context.Context, machines []string, options ClientOptions) (Client, error) { if options.DialTimeout == 0 { options.DialTimeout = 3 * time.Second } if options.DialKeepAlive == 0 { options.DialKeepAlive = 3 * time.Second } if options.HeaderTimeoutPerRequest == 0 { options.HeaderTimeoutPerRequest = 3 * time.Second } transport := etcd.DefaultTransport if options.Cert != "" && options.Key != "" { tlsCert, err := tls.LoadX509KeyPair(options.Cert, options.Key) if err != nil { return nil, err } caCertCt, err := ioutil.ReadFile(options.CACert) if err != nil { return nil, err } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCertCt) transport = &http.Transport{ TLSClientConfig: &tls.Config{ Certificates: []tls.Certificate{tlsCert}, RootCAs: caCertPool, }, Dial: func(network, address string) (net.Conn, error) { return (&net.Dialer{ Timeout: options.DialTimeout, KeepAlive: options.DialKeepAlive, }).Dial(network, address) }, } } ce, err := etcd.New(etcd.Config{ Endpoints: machines, Transport: transport, HeaderTimeoutPerRequest: options.HeaderTimeoutPerRequest, }) if err != nil { return nil, err } return &client{ keysAPI: etcd.NewKeysAPI(ce), ctx: ctx, }, nil } // GetEntries implements the etcd Client interface. func (c *client) GetEntries(key string) ([]string, error) { resp, err := c.keysAPI.Get(c.ctx, key, &etcd.GetOptions{Recursive: true}) if err != nil { return nil, err } // Special case. Note that it's possible that len(resp.Node.Nodes) == 0 and // resp.Node.Value is also empty, in which case the key is empty and we // should not return any entries. if len(resp.Node.Nodes) == 0 && resp.Node.Value != "" { return []string{resp.Node.Value}, nil } entries := make([]string, len(resp.Node.Nodes)) for i, node := range resp.Node.Nodes { entries[i] = node.Value } return entries, nil } // WatchPrefix implements the etcd Client interface. func (c *client) WatchPrefix(prefix string, ch chan struct{}) { watch := c.keysAPI.Watcher(prefix, &etcd.WatcherOptions{AfterIndex: 0, Recursive: true}) ch <- struct{}{} // make sure caller invokes GetEntries for { if _, err := watch.Next(c.ctx); err != nil { return } ch <- struct{}{} } } func (c *client) Register(s Service) error { if s.Key == "" { return ErrNoKey } if s.Value == "" { return ErrNoValue } var err error if s.TTL != nil { _, err = c.keysAPI.Set(c.ctx, s.Key, s.Value, &etcd.SetOptions{ PrevExist: etcd.PrevIgnore, TTL: s.TTL.ttl, }) } else { _, err = c.keysAPI.Create(c.ctx, s.Key, s.Value) } return err } func (c *client) Deregister(s Service) error { if s.Key == "" { return ErrNoKey } _, err := c.keysAPI.Delete(c.ctx, s.Key, s.DeleteOptions) return err } golang-github-go-kit-kit-0.13.0/sd/etcd/client_test.go000066400000000000000000000203021443521372500224730ustar00rootroot00000000000000package etcd import ( "context" "errors" "reflect" "testing" "time" etcd "go.etcd.io/etcd/client/v2" ) func TestNewClient(t *testing.T) { client, err := NewClient( context.Background(), []string{"http://irrelevant:12345"}, ClientOptions{ DialTimeout: 2 * time.Second, DialKeepAlive: 2 * time.Second, HeaderTimeoutPerRequest: 2 * time.Second, }, ) if err != nil { t.Fatalf("unexpected error creating client: %v", err) } if client == nil { t.Fatal("expected new Client, got nil") } } // NewClient should fail when providing invalid or missing endpoints. func TestOptions(t *testing.T) { a, err := NewClient( context.Background(), []string{}, ClientOptions{ Cert: "", Key: "", CACert: "", DialTimeout: 2 * time.Second, DialKeepAlive: 2 * time.Second, HeaderTimeoutPerRequest: 2 * time.Second, }, ) if err == nil { t.Errorf("expected error: %v", err) } if a != nil { t.Fatalf("expected client to be nil on failure") } _, err = NewClient( context.Background(), []string{"http://irrelevant:12345"}, ClientOptions{ Cert: "blank.crt", Key: "blank.key", CACert: "blank.CACert", DialTimeout: 2 * time.Second, DialKeepAlive: 2 * time.Second, HeaderTimeoutPerRequest: 2 * time.Second, }, ) if err == nil { t.Errorf("expected error: %v", err) } } // Mocks of the underlying etcd.KeysAPI interface that is called by the methods we want to test // fakeKeysAPI implements etcd.KeysAPI, event and err are channels used to emulate // an etcd event or error, getres will be returned when etcd.KeysAPI.Get is called. type fakeKeysAPI struct { event chan bool err chan bool getres *getResult } type getResult struct { resp *etcd.Response err error } // Get return the content of getres or nil, nil func (fka *fakeKeysAPI) Get(ctx context.Context, key string, opts *etcd.GetOptions) (*etcd.Response, error) { if fka.getres == nil { return nil, nil } return fka.getres.resp, fka.getres.err } // Set is not used in the tests func (fka *fakeKeysAPI) Set(ctx context.Context, key, value string, opts *etcd.SetOptions) (*etcd.Response, error) { return nil, nil } // Delete is not used in the tests func (fka *fakeKeysAPI) Delete(ctx context.Context, key string, opts *etcd.DeleteOptions) (*etcd.Response, error) { return nil, nil } // Create is not used in the tests func (fka *fakeKeysAPI) Create(ctx context.Context, key, value string) (*etcd.Response, error) { return nil, nil } // CreateInOrder is not used in the tests func (fka *fakeKeysAPI) CreateInOrder(ctx context.Context, dir, value string, opts *etcd.CreateInOrderOptions) (*etcd.Response, error) { return nil, nil } // Update is not used in the tests func (fka *fakeKeysAPI) Update(ctx context.Context, key, value string) (*etcd.Response, error) { return nil, nil } // Watcher return a fakeWatcher that will forward event and error received on the channels func (fka *fakeKeysAPI) Watcher(key string, opts *etcd.WatcherOptions) etcd.Watcher { return &fakeWatcher{fka.event, fka.err} } // fakeWatcher implements etcd.Watcher type fakeWatcher struct { event chan bool err chan bool } // Next blocks until an etcd event or error is emulated. // When an event occurs it just return nil response and error. // When an error occur it return a non nil error. func (fw *fakeWatcher) Next(context.Context) (*etcd.Response, error) { select { case <-fw.event: return nil, nil case <-fw.err: return nil, errors.New("error from underlying etcd watcher") } } // newFakeClient return a new etcd.Client built on top of the mocked interfaces func newFakeClient(event, err chan bool, getres *getResult) Client { return &client{ keysAPI: &fakeKeysAPI{event, err, getres}, ctx: context.Background(), } } // Register should fail when the provided service has an empty key or value func TestRegisterClient(t *testing.T) { client := newFakeClient(nil, nil, nil) err := client.Register(Service{Key: "", Value: "value", DeleteOptions: nil}) if want, have := ErrNoKey, err; want != have { t.Fatalf("want %v, have %v", want, have) } err = client.Register(Service{Key: "key", Value: "", DeleteOptions: nil}) if want, have := ErrNoValue, err; want != have { t.Fatalf("want %v, have %v", want, have) } err = client.Register(Service{Key: "key", Value: "value", DeleteOptions: nil}) if err != nil { t.Fatal(err) } } // Deregister should fail if the input service has an empty key func TestDeregisterClient(t *testing.T) { client := newFakeClient(nil, nil, nil) err := client.Deregister(Service{Key: "", Value: "value", DeleteOptions: nil}) if want, have := ErrNoKey, err; want != have { t.Fatalf("want %v, have %v", want, have) } err = client.Deregister(Service{Key: "key", Value: "", DeleteOptions: nil}) if err != nil { t.Fatal(err) } } // WatchPrefix notify the caller by writing on the channel if an etcd event occurs // or return in case of an underlying error func TestWatchPrefix(t *testing.T) { err := make(chan bool) event := make(chan bool) watchPrefixReturned := make(chan bool, 1) client := newFakeClient(event, err, nil) ch := make(chan struct{}) go func() { client.WatchPrefix("prefix", ch) // block until an etcd event or error occurs watchPrefixReturned <- true }() // WatchPrefix force the caller to read once from the channel before actually // sending notification, emulate that first read. <-ch // Emulate an etcd event event <- true if want, have := struct{}{}, <-ch; want != have { t.Fatalf("want %v, have %v", want, have) } // Emulate an error, WatchPrefix should return err <- true select { case <-watchPrefixReturned: break case <-time.After(1 * time.Second): t.Fatal("WatchPrefix not returning on errors") } } var errKeyAPI = errors.New("emulate error returned by KeysAPI.Get") // table of test cases for method GetEntries var getEntriesTestTable = []struct { input getResult // value returned by the underlying etcd.KeysAPI.Get resp []string // response expected in output of GetEntries err error //error expected in output of GetEntries }{ // test case: an error is returned by etcd.KeysAPI.Get {getResult{nil, errKeyAPI}, nil, errKeyAPI}, // test case: return a single leaf node, with an empty value {getResult{&etcd.Response{ Action: "get", Node: &etcd.Node{ Key: "nodekey", Dir: false, Value: "", Nodes: nil, CreatedIndex: 0, ModifiedIndex: 0, Expiration: nil, TTL: 0, }, PrevNode: nil, Index: 0, }, nil}, []string{}, nil}, // test case: return a single leaf node, with a value {getResult{&etcd.Response{ Action: "get", Node: &etcd.Node{ Key: "nodekey", Dir: false, Value: "nodevalue", Nodes: nil, CreatedIndex: 0, ModifiedIndex: 0, Expiration: nil, TTL: 0, }, PrevNode: nil, Index: 0, }, nil}, []string{"nodevalue"}, nil}, // test case: return a node with two childs {getResult{&etcd.Response{ Action: "get", Node: &etcd.Node{ Key: "nodekey", Dir: true, Value: "nodevalue", Nodes: []*etcd.Node{ { Key: "childnode1", Dir: false, Value: "childvalue1", Nodes: nil, CreatedIndex: 0, ModifiedIndex: 0, Expiration: nil, TTL: 0, }, { Key: "childnode2", Dir: false, Value: "childvalue2", Nodes: nil, CreatedIndex: 0, ModifiedIndex: 0, Expiration: nil, TTL: 0, }, }, CreatedIndex: 0, ModifiedIndex: 0, Expiration: nil, TTL: 0, }, PrevNode: nil, Index: 0, }, nil}, []string{"childvalue1", "childvalue2"}, nil}, } func TestGetEntries(t *testing.T) { for _, et := range getEntriesTestTable { client := newFakeClient(nil, nil, &et.input) resp, err := client.GetEntries("prefix") if want, have := et.resp, resp; !reflect.DeepEqual(want, have) { t.Fatalf("want %v, have %v", want, have) } if want, have := et.err, err; want != have { t.Fatalf("want %v, have %v", want, have) } } } golang-github-go-kit-kit-0.13.0/sd/etcd/doc.go000066400000000000000000000003611443521372500207260ustar00rootroot00000000000000// Package etcd provides an Instancer and Registrar implementation for etcd. If // you use etcd as your service discovery system, this package will help you // implement the registration and client-side load balancing patterns. package etcd golang-github-go-kit-kit-0.13.0/sd/etcd/example_test.go000066400000000000000000000041661443521372500226620ustar00rootroot00000000000000package etcd import ( "context" "io" "time" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/sd" "github.com/go-kit/kit/sd/lb" "github.com/go-kit/log" ) func Example() { // Let's say this is a service that means to register itself. // First, we will set up some context. var ( etcdServer = "http://10.0.0.1:2379" // don't forget schema and port! prefix = "/services/foosvc/" // known at compile time instance = "1.2.3.4:8080" // taken from runtime or platform, somehow key = prefix + instance // should be globally unique value = "http://" + instance // based on our transport ctx = context.Background() ) // Build the client. client, err := NewClient(ctx, []string{etcdServer}, ClientOptions{}) if err != nil { panic(err) } // Build the registrar. registrar := NewRegistrar(client, Service{ Key: key, Value: value, }, log.NewNopLogger()) // Register our instance. registrar.Register() // At the end of our service lifecycle, for example at the end of func main, // we should make sure to deregister ourselves. This is important! Don't // accidentally skip this step by invoking a log.Fatal or os.Exit in the // interim, which bypasses the defer stack. defer registrar.Deregister() // It's likely that we'll also want to connect to other services and call // their methods. We can build an Instancer to listen for changes from etcd, // create Endpointer, wrap it with a load-balancer to pick a single // endpoint, and finally wrap it with a retry strategy to get something that // can be used as an endpoint directly. barPrefix := "/services/barsvc" logger := log.NewNopLogger() instancer, err := NewInstancer(client, barPrefix, logger) if err != nil { panic(err) } endpointer := sd.NewEndpointer(instancer, barFactory, logger) balancer := lb.NewRoundRobin(endpointer) retry := lb.Retry(3, 3*time.Second, balancer) // And now retry can be used like any other endpoint. req := struct{}{} if _, err = retry(ctx, req); err != nil { panic(err) } } func barFactory(string) (endpoint.Endpoint, io.Closer, error) { return endpoint.Nop, nil, nil } golang-github-go-kit-kit-0.13.0/sd/etcd/instancer.go000066400000000000000000000033151443521372500221510ustar00rootroot00000000000000package etcd import ( "github.com/go-kit/kit/sd" "github.com/go-kit/kit/sd/internal/instance" "github.com/go-kit/log" ) // Instancer yields instances stored in a certain etcd keyspace. Any kind of // change in that keyspace is watched and will update the Instancer's Instancers. type Instancer struct { cache *instance.Cache client Client prefix string logger log.Logger quitc chan struct{} } // NewInstancer returns an etcd instancer. It will start watching the given // prefix for changes, and update the subscribers. func NewInstancer(c Client, prefix string, logger log.Logger) (*Instancer, error) { s := &Instancer{ client: c, prefix: prefix, cache: instance.NewCache(), logger: logger, quitc: make(chan struct{}), } instances, err := s.client.GetEntries(s.prefix) if err == nil { logger.Log("prefix", s.prefix, "instances", len(instances)) } else { logger.Log("prefix", s.prefix, "err", err) } s.cache.Update(sd.Event{Instances: instances, Err: err}) go s.loop() return s, nil } func (s *Instancer) loop() { ch := make(chan struct{}) go s.client.WatchPrefix(s.prefix, ch) for { select { case <-ch: instances, err := s.client.GetEntries(s.prefix) if err != nil { s.logger.Log("msg", "failed to retrieve entries", "err", err) s.cache.Update(sd.Event{Err: err}) continue } s.cache.Update(sd.Event{Instances: instances}) case <-s.quitc: return } } } // Stop terminates the Instancer. func (s *Instancer) Stop() { close(s.quitc) } // Register implements Instancer. func (s *Instancer) Register(ch chan<- sd.Event) { s.cache.Register(ch) } // Deregister implements Instancer. func (s *Instancer) Deregister(ch chan<- sd.Event) { s.cache.Deregister(ch) } golang-github-go-kit-kit-0.13.0/sd/etcd/instancer_test.go000066400000000000000000000024611443521372500232110ustar00rootroot00000000000000package etcd import ( "errors" "testing" stdetcd "go.etcd.io/etcd/client/v2" "github.com/go-kit/kit/sd" "github.com/go-kit/log" ) var _ sd.Instancer = (*Instancer)(nil) // API check var ( node = &stdetcd.Node{ Key: "/foo", Nodes: []*stdetcd.Node{ {Key: "/foo/1", Value: "1:1"}, {Key: "/foo/2", Value: "1:2"}, }, } fakeResponse = &stdetcd.Response{ Node: node, } ) var _ sd.Instancer = &Instancer{} // API check func TestInstancer(t *testing.T) { client := &fakeClient{ responses: map[string]*stdetcd.Response{"/foo": fakeResponse}, } s, err := NewInstancer(client, "/foo", log.NewNopLogger()) if err != nil { t.Fatal(err) } defer s.Stop() if state := s.cache.State(); state.Err != nil { t.Fatal(state.Err) } } type fakeClient struct { responses map[string]*stdetcd.Response } func (c *fakeClient) GetEntries(prefix string) ([]string, error) { response, ok := c.responses[prefix] if !ok { return nil, errors.New("key not exist") } entries := make([]string, len(response.Node.Nodes)) for i, node := range response.Node.Nodes { entries[i] = node.Value } return entries, nil } func (c *fakeClient) WatchPrefix(prefix string, ch chan struct{}) {} func (c *fakeClient) Register(Service) error { return nil } func (c *fakeClient) Deregister(Service) error { return nil } golang-github-go-kit-kit-0.13.0/sd/etcd/integration_test.go000066400000000000000000000064271443521372500235540ustar00rootroot00000000000000//go:build flaky_integration // +build flaky_integration package etcd import ( "context" "io" "os" "testing" "time" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/sd" "github.com/go-kit/log" ) // Package sd/etcd provides a wrapper around the etcd key/value store. This // example assumes the user has an instance of etcd installed and running // locally on port 2379. func TestIntegration(t *testing.T) { addr := os.Getenv("ETCD_ADDR") if addr == "" { t.Skip("ETCD_ADDR not set; skipping integration test") } var ( prefix = "/services/foosvc/" // known at compile time instance = "1.2.3.4:8080" // taken from runtime or platform, somehow key = prefix + instance value = "http://" + instance // based on our transport ) client, err := NewClient(context.Background(), []string{addr}, ClientOptions{ DialTimeout: 2 * time.Second, DialKeepAlive: 2 * time.Second, HeaderTimeoutPerRequest: 2 * time.Second, }) if err != nil { t.Fatalf("NewClient(%q): %v", addr, err) } // Verify test data is initially empty. entries, err := client.GetEntries(key) if err == nil { t.Fatalf("GetEntries(%q): expected error, got none", key) } t.Logf("GetEntries(%q): %v (OK)", key, err) // Instantiate a new Registrar, passing in test data. registrar := NewRegistrar(client, Service{ Key: key, Value: value, }, log.With(log.NewLogfmtLogger(os.Stderr), "component", "registrar")) // Register our instance. registrar.Register() t.Logf("Registered") // Retrieve entries from etcd manually. entries, err = client.GetEntries(key) if err != nil { t.Fatalf("client.GetEntries(%q): %v", key, err) } if want, have := 1, len(entries); want != have { t.Fatalf("client.GetEntries(%q): want %d, have %d", key, want, have) } if want, have := value, entries[0]; want != have { t.Fatalf("want %q, have %q", want, have) } instancer, err := NewInstancer( client, prefix, log.With(log.NewLogfmtLogger(os.Stderr), "component", "instancer"), ) if err != nil { t.Fatalf("NewInstancer: %v", err) } endpointer := sd.NewEndpointer( instancer, func(string) (endpoint.Endpoint, io.Closer, error) { return endpoint.Nop, nil, nil }, log.With(log.NewLogfmtLogger(os.Stderr), "component", "instancer"), ) t.Logf("Constructed Endpointer OK") if !within(time.Second, func() bool { endpoints, err := endpointer.Endpoints() return err == nil && len(endpoints) == 1 }) { t.Fatalf("Endpointer didn't see Register in time") } t.Logf("Endpointer saw Register OK") // Deregister first instance of test data. registrar.Deregister() t.Logf("Deregistered") // Check it was deregistered. if !within(time.Second, func() bool { endpoints, err := endpointer.Endpoints() t.Logf("Checking Deregister: len(endpoints) = %d, err = %v", len(endpoints), err) return err == nil && len(endpoints) == 0 }) { t.Fatalf("Endpointer didn't see Deregister in time") } // Verify test data no longer exists in etcd. _, err = client.GetEntries(key) if err == nil { t.Fatalf("GetEntries(%q): expected error, got none", key) } t.Logf("GetEntries(%q): %v (OK)", key, err) } func within(d time.Duration, f func() bool) bool { deadline := time.Now().Add(d) for time.Now().Before(deadline) { if f() { return true } time.Sleep(d / 10) } return false } golang-github-go-kit-kit-0.13.0/sd/etcd/registrar.go000066400000000000000000000060561443521372500221720ustar00rootroot00000000000000package etcd import ( "sync" "time" etcd "go.etcd.io/etcd/client/v2" "github.com/go-kit/log" ) const minHeartBeatTime = 500 * time.Millisecond // Registrar registers service instance liveness information to etcd. type Registrar struct { client Client service Service logger log.Logger quitmtx sync.Mutex quit chan struct{} } // Service holds the instance identifying data you want to publish to etcd. Key // must be unique, and value is the string returned to subscribers, typically // called the "instance" string in other parts of package sd. type Service struct { Key string // unique key, e.g. "/service/foobar/1.2.3.4:8080" Value string // returned to subscribers, e.g. "http://1.2.3.4:8080" TTL *TTLOption DeleteOptions *etcd.DeleteOptions } // TTLOption allow setting a key with a TTL. This option will be used by a loop // goroutine which regularly refreshes the lease of the key. type TTLOption struct { heartbeat time.Duration // e.g. time.Second * 3 ttl time.Duration // e.g. time.Second * 10 } // NewTTLOption returns a TTLOption that contains proper TTL settings. Heartbeat // is used to refresh the lease of the key periodically; its value should be at // least 500ms. TTL defines the lease of the key; its value should be // significantly greater than heartbeat. // // Good default values might be 3s heartbeat, 10s TTL. func NewTTLOption(heartbeat, ttl time.Duration) *TTLOption { if heartbeat <= minHeartBeatTime { heartbeat = minHeartBeatTime } if ttl <= heartbeat { ttl = 3 * heartbeat } return &TTLOption{ heartbeat: heartbeat, ttl: ttl, } } // NewRegistrar returns a etcd Registrar acting on the provided catalog // registration (service). func NewRegistrar(client Client, service Service, logger log.Logger) *Registrar { return &Registrar{ client: client, service: service, logger: log.With(logger, "key", service.Key, "value", service.Value), } } // Register implements the sd.Registrar interface. Call it when you want your // service to be registered in etcd, typically at startup. func (r *Registrar) Register() { if err := r.client.Register(r.service); err != nil { r.logger.Log("err", err) } else { r.logger.Log("action", "register") } if r.service.TTL != nil { go r.loop() } } func (r *Registrar) loop() { r.quitmtx.Lock() if r.quit != nil { return // already running } r.quit = make(chan struct{}) r.quitmtx.Unlock() tick := time.NewTicker(r.service.TTL.heartbeat) defer tick.Stop() for { select { case <-tick.C: if err := r.client.Register(r.service); err != nil { r.logger.Log("err", err) } case <-r.quit: return } } } // Deregister implements the sd.Registrar interface. Call it when you want your // service to be deregistered from etcd, typically just prior to shutdown. func (r *Registrar) Deregister() { if err := r.client.Deregister(r.service); err != nil { r.logger.Log("err", err) } else { r.logger.Log("action", "deregister") } r.quitmtx.Lock() defer r.quitmtx.Unlock() if r.quit != nil { close(r.quit) r.quit = nil } } golang-github-go-kit-kit-0.13.0/sd/etcd/registrar_test.go000066400000000000000000000056111443521372500232250ustar00rootroot00000000000000package etcd import ( "bytes" "errors" "testing" "github.com/go-kit/log" ) // testClient is a basic implementation of Client type testClient struct { registerRes error // value returned when Register or Deregister is called } func (tc *testClient) GetEntries(prefix string) ([]string, error) { return nil, nil } func (tc *testClient) WatchPrefix(prefix string, ch chan struct{}) { return } func (tc *testClient) Register(s Service) error { return tc.registerRes } func (tc *testClient) Deregister(s Service) error { return tc.registerRes } // default service used to build registrar in our tests var testService = Service{"testKey", "testValue", nil, nil} // NewRegistar should return a registar with a logger using the service key and value func TestNewRegistar(t *testing.T) { c := Client(&testClient{nil}) buf := &bytes.Buffer{} logger := log.NewLogfmtLogger(buf) r := NewRegistrar( c, testService, logger, ) if err := r.logger.Log("msg", "message"); err != nil { t.Fatal(err) } if want, have := "key=testKey value=testValue msg=message\n", buf.String(); want != have { t.Errorf("\nwant: %shave: %s", want, have) } } // Register log the error returned by the client or log the successful registration action // table of test cases for method Register var registerTestTable = []struct { registerRes error // value returned by the client on calls to Register log string // expected log by the registrar }{ // test case: an error is returned by the client {errors.New("regError"), "key=testKey value=testValue err=regError\n"}, // test case: registration successful {nil, "key=testKey value=testValue action=register\n"}, } func TestRegister(t *testing.T) { for _, tc := range registerTestTable { c := Client(&testClient{tc.registerRes}) buf := &bytes.Buffer{} logger := log.NewLogfmtLogger(buf) r := NewRegistrar( c, testService, logger, ) r.Register() if want, have := tc.log, buf.String(); want != have { t.Fatalf("want %v, have %v", want, have) } } } // Deregister log the error returned by the client or log the successful deregistration action // table of test cases for method Deregister var deregisterTestTable = []struct { deregisterRes error // value returned by the client on calls to Deregister log string // expected log by the registrar }{ // test case: an error is returned by the client {errors.New("deregError"), "key=testKey value=testValue err=deregError\n"}, // test case: deregistration successful {nil, "key=testKey value=testValue action=deregister\n"}, } func TestDeregister(t *testing.T) { for _, tc := range deregisterTestTable { c := Client(&testClient{tc.deregisterRes}) buf := &bytes.Buffer{} logger := log.NewLogfmtLogger(buf) r := NewRegistrar( c, testService, logger, ) r.Deregister() if want, have := tc.log, buf.String(); want != have { t.Fatalf("want %v, have %v", want, have) } } } golang-github-go-kit-kit-0.13.0/sd/etcdv3/000077500000000000000000000000001443521372500201035ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/sd/etcdv3/client.go000066400000000000000000000137771443521372500217270ustar00rootroot00000000000000package etcdv3 import ( "context" "crypto/tls" "errors" "time" "go.etcd.io/etcd/client/pkg/v3/transport" clientv3 "go.etcd.io/etcd/client/v3" "google.golang.org/grpc" ) var ( // ErrNoKey indicates a client method needs a key but receives none. ErrNoKey = errors.New("no key provided") // ErrNoValue indicates a client method needs a value but receives none. ErrNoValue = errors.New("no value provided") ) // Client is a wrapper around the etcd client. type Client interface { // GetEntries queries the given prefix in etcd and returns a slice // containing the values of all keys found, recursively, underneath that // prefix. GetEntries(prefix string) ([]string, error) // WatchPrefix watches the given prefix in etcd for changes. When a change // is detected, it will signal on the passed channel. Clients are expected // to call GetEntries to update themselves with the latest set of complete // values. WatchPrefix will always send an initial sentinel value on the // channel after establishing the watch, to ensure that clients always // receive the latest set of values. WatchPrefix will block until the // context passed to the NewClient constructor is terminated. WatchPrefix(prefix string, ch chan struct{}) // Register a service with etcd. Register(s Service) error // Deregister a service with etcd. Deregister(s Service) error // LeaseID returns the lease id created for this service instance LeaseID() int64 } type client struct { cli *clientv3.Client ctx context.Context kv clientv3.KV // Watcher interface instance, used to leverage Watcher.Close() watcher clientv3.Watcher // watcher context wctx context.Context // watcher cancel func wcf context.CancelFunc // leaseID will be 0 (clientv3.NoLease) if a lease was not created leaseID clientv3.LeaseID hbch <-chan *clientv3.LeaseKeepAliveResponse // Lease interface instance, used to leverage Lease.Close() leaser clientv3.Lease } // ClientOptions defines options for the etcd client. All values are optional. // If any duration is not specified, a default of 3 seconds will be used. type ClientOptions struct { Cert string Key string CACert string DialTimeout time.Duration DialKeepAlive time.Duration // DialOptions is a list of dial options for the gRPC client (e.g., for interceptors). // For example, pass grpc.WithBlock() to block until the underlying connection is up. // Without this, Dial returns immediately and connecting the server happens in background. DialOptions []grpc.DialOption Username string Password string } // NewClient returns Client with a connection to the named machines. It will // return an error if a connection to the cluster cannot be made. func NewClient(ctx context.Context, machines []string, options ClientOptions) (Client, error) { if options.DialTimeout == 0 { options.DialTimeout = 3 * time.Second } if options.DialKeepAlive == 0 { options.DialKeepAlive = 3 * time.Second } var err error var tlscfg *tls.Config if options.Cert != "" && options.Key != "" { tlsInfo := transport.TLSInfo{ CertFile: options.Cert, KeyFile: options.Key, TrustedCAFile: options.CACert, } tlscfg, err = tlsInfo.ClientConfig() if err != nil { return nil, err } } cli, err := clientv3.New(clientv3.Config{ Context: ctx, Endpoints: machines, DialTimeout: options.DialTimeout, DialKeepAliveTime: options.DialKeepAlive, DialOptions: options.DialOptions, TLS: tlscfg, Username: options.Username, Password: options.Password, }) if err != nil { return nil, err } return &client{ cli: cli, ctx: ctx, kv: clientv3.NewKV(cli), }, nil } func (c *client) LeaseID() int64 { return int64(c.leaseID) } // GetEntries implements the etcd Client interface. func (c *client) GetEntries(key string) ([]string, error) { resp, err := c.kv.Get(c.ctx, key, clientv3.WithPrefix()) if err != nil { return nil, err } entries := make([]string, len(resp.Kvs)) for i, kv := range resp.Kvs { entries[i] = string(kv.Value) } return entries, nil } // WatchPrefix implements the etcd Client interface. func (c *client) WatchPrefix(prefix string, ch chan struct{}) { c.wctx, c.wcf = context.WithCancel(c.ctx) c.watcher = clientv3.NewWatcher(c.cli) wch := c.watcher.Watch(c.wctx, prefix, clientv3.WithPrefix(), clientv3.WithRev(0)) ch <- struct{}{} for wr := range wch { if wr.Canceled { return } ch <- struct{}{} } } func (c *client) Register(s Service) error { var err error if s.Key == "" { return ErrNoKey } if s.Value == "" { return ErrNoValue } if c.leaser != nil { c.leaser.Close() } c.leaser = clientv3.NewLease(c.cli) if c.watcher != nil { c.watcher.Close() } c.watcher = clientv3.NewWatcher(c.cli) if c.kv == nil { c.kv = clientv3.NewKV(c.cli) } if s.TTL == nil { s.TTL = NewTTLOption(time.Second*3, time.Second*10) } grantResp, err := c.leaser.Grant(c.ctx, int64(s.TTL.ttl.Seconds())) if err != nil { return err } c.leaseID = grantResp.ID _, err = c.kv.Put( c.ctx, s.Key, s.Value, clientv3.WithLease(c.leaseID), ) if err != nil { return err } // this will keep the key alive 'forever' or until we revoke it or // the context is canceled c.hbch, err = c.leaser.KeepAlive(c.ctx, c.leaseID) if err != nil { return err } // discard the keepalive response, make etcd library not to complain // fix bug #799 go func() { for { select { case r := <-c.hbch: // avoid dead loop when channel was closed if r == nil { return } case <-c.ctx.Done(): return } } }() return nil } func (c *client) Deregister(s Service) error { defer c.close() if s.Key == "" { return ErrNoKey } if _, err := c.cli.Delete(c.ctx, s.Key, clientv3.WithIgnoreLease()); err != nil { return err } return nil } // close will close any open clients and call // the watcher cancel func func (c *client) close() { if c.leaser != nil { c.leaser.Close() } if c.watcher != nil { c.watcher.Close() } if c.wcf != nil { c.wcf() } } golang-github-go-kit-kit-0.13.0/sd/etcdv3/client_test.go000066400000000000000000000031171443521372500227510ustar00rootroot00000000000000package etcdv3 import ( "context" "testing" "time" "google.golang.org/grpc" ) const ( // irrelevantEndpoint is an address which does not exists. irrelevantEndpoint = "http://irrelevant:12345" ) func TestNewClient(t *testing.T) { client, err := NewClient( context.Background(), []string{irrelevantEndpoint}, ClientOptions{ DialTimeout: 3 * time.Second, DialKeepAlive: 3 * time.Second, }, ) if err != nil { t.Fatalf("unexpected error creating client: %v", err) } if client == nil { t.Fatal("expected new Client, got nil") } } func TestClientOptions(t *testing.T) { client, err := NewClient( context.Background(), []string{}, ClientOptions{ Cert: "", Key: "", CACert: "", DialTimeout: 3 * time.Second, DialKeepAlive: 3 * time.Second, }, ) if err == nil { t.Errorf("expected error: %v", err) } if client != nil { t.Fatalf("expected client to be nil on failure") } _, err = NewClient( context.Background(), []string{irrelevantEndpoint}, ClientOptions{ Cert: "does-not-exist.crt", Key: "does-not-exist.key", CACert: "does-not-exist.CACert", DialTimeout: 3 * time.Second, DialKeepAlive: 3 * time.Second, }, ) if err == nil { t.Errorf("expected error: %v", err) } client, err = NewClient( context.Background(), []string{irrelevantEndpoint}, ClientOptions{ DialOptions: []grpc.DialOption{grpc.WithBlock()}, }, ) if err == nil { t.Errorf("expected connection should fail") } if client != nil { t.Errorf("expected client to be nil on failure") } } golang-github-go-kit-kit-0.13.0/sd/etcdv3/doc.go000066400000000000000000000003731443521372500212020ustar00rootroot00000000000000// Package etcdv3 provides an Instancer and Registrar implementation for etcd v3. If // you use etcd v3 as your service discovery system, this package will help you // implement the registration and client-side load balancing patterns. package etcdv3 golang-github-go-kit-kit-0.13.0/sd/etcdv3/example_test.go000066400000000000000000000053311443521372500231260ustar00rootroot00000000000000package etcdv3 import ( "context" "io" "time" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/sd" "github.com/go-kit/kit/sd/lb" "github.com/go-kit/log" "google.golang.org/grpc" ) func Example() { // Let's say this is a service that means to register itself. // First, we will set up some context. var ( etcdServer = "10.0.0.1:2379" // in the change from v2 to v3, the schema is no longer necessary if connecting directly to an etcd v3 instance prefix = "/services/foosvc/" // known at compile time instance = "1.2.3.4:8080" // taken from runtime or platform, somehow key = prefix + instance // should be globally unique value = "http://" + instance // based on our transport ctx = context.Background() ) options := ClientOptions{ // Path to trusted ca file CACert: "", // Path to certificate Cert: "", // Path to private key Key: "", // Username if required Username: "", // Password if required Password: "", // If DialTimeout is 0, it defaults to 3s DialTimeout: time.Second * 3, // If DialKeepAlive is 0, it defaults to 3s DialKeepAlive: time.Second * 3, // If passing `grpc.WithBlock`, dial connection will block until success. DialOptions: []grpc.DialOption{grpc.WithBlock()}, } // Build the client. client, err := NewClient(ctx, []string{etcdServer}, options) if err != nil { panic(err) } // Build the registrar. registrar := NewRegistrar(client, Service{ Key: key, Value: value, }, log.NewNopLogger()) // Register our instance. registrar.Register() // At the end of our service lifecycle, for example at the end of func main, // we should make sure to deregister ourselves. This is important! Don't // accidentally skip this step by invoking a log.Fatal or os.Exit in the // interim, which bypasses the defer stack. defer registrar.Deregister() // It's likely that we'll also want to connect to other services and call // their methods. We can build an Instancer to listen for changes from etcd, // create Endpointer, wrap it with a load-balancer to pick a single // endpoint, and finally wrap it with a retry strategy to get something that // can be used as an endpoint directly. barPrefix := "/services/barsvc" logger := log.NewNopLogger() instancer, err := NewInstancer(client, barPrefix, logger) if err != nil { panic(err) } endpointer := sd.NewEndpointer(instancer, barFactory, logger) balancer := lb.NewRoundRobin(endpointer) retry := lb.Retry(3, 3*time.Second, balancer) // And now retry can be used like any other endpoint. req := struct{}{} if _, err = retry(ctx, req); err != nil { panic(err) } } func barFactory(string) (endpoint.Endpoint, io.Closer, error) { return endpoint.Nop, nil, nil } golang-github-go-kit-kit-0.13.0/sd/etcdv3/instancer.go000066400000000000000000000033201443521372500224160ustar00rootroot00000000000000package etcdv3 import ( "github.com/go-kit/kit/sd" "github.com/go-kit/kit/sd/internal/instance" "github.com/go-kit/log" ) // Instancer yields instances stored in a certain etcd keyspace. Any kind of // change in that keyspace is watched and will update the Instancer's Instancers. type Instancer struct { cache *instance.Cache client Client prefix string logger log.Logger quitc chan struct{} } // NewInstancer returns an etcd instancer. It will start watching the given // prefix for changes, and update the subscribers. func NewInstancer(c Client, prefix string, logger log.Logger) (*Instancer, error) { s := &Instancer{ client: c, prefix: prefix, cache: instance.NewCache(), logger: logger, quitc: make(chan struct{}), } instances, err := s.client.GetEntries(s.prefix) if err == nil { logger.Log("prefix", s.prefix, "instances", len(instances)) } else { logger.Log("prefix", s.prefix, "err", err) } s.cache.Update(sd.Event{Instances: instances, Err: err}) go s.loop() return s, nil } func (s *Instancer) loop() { ch := make(chan struct{}) go s.client.WatchPrefix(s.prefix, ch) for { select { case <-ch: instances, err := s.client.GetEntries(s.prefix) if err != nil { s.logger.Log("msg", "failed to retrieve entries", "err", err) s.cache.Update(sd.Event{Err: err}) continue } s.cache.Update(sd.Event{Instances: instances}) case <-s.quitc: return } } } // Stop terminates the Instancer. func (s *Instancer) Stop() { close(s.quitc) } // Register implements Instancer. func (s *Instancer) Register(ch chan<- sd.Event) { s.cache.Register(ch) } // Deregister implements Instancer. func (s *Instancer) Deregister(ch chan<- sd.Event) { s.cache.Deregister(ch) } golang-github-go-kit-kit-0.13.0/sd/etcdv3/instancer_test.go000066400000000000000000000026151443521372500234630ustar00rootroot00000000000000package etcdv3 import ( "errors" "testing" "github.com/go-kit/kit/sd" "github.com/go-kit/log" ) var _ sd.Instancer = (*Instancer)(nil) // API check type testKV struct { Key []byte Value []byte } type testResponse struct { Kvs []testKV } var ( fakeResponse = testResponse{ Kvs: []testKV{ { Key: []byte("/foo/1"), Value: []byte("1:1"), }, { Key: []byte("/foo/2"), Value: []byte("2:2"), }, }, } ) var _ sd.Instancer = &Instancer{} // API check func TestInstancer(t *testing.T) { client := &fakeClient{ responses: map[string]testResponse{"/foo": fakeResponse}, } s, err := NewInstancer(client, "/foo", log.NewNopLogger()) if err != nil { t.Fatal(err) } defer s.Stop() if state := s.cache.State(); state.Err != nil { t.Fatal(state.Err) } } type fakeClient struct { responses map[string]testResponse } func (c *fakeClient) GetEntries(prefix string) ([]string, error) { response, ok := c.responses[prefix] if !ok { return nil, errors.New("key not exist") } entries := make([]string, len(response.Kvs)) for i, node := range response.Kvs { entries[i] = string(node.Value) } return entries, nil } func (c *fakeClient) WatchPrefix(prefix string, ch chan struct{}) { } func (c *fakeClient) LeaseID() int64 { return 0 } func (c *fakeClient) Register(Service) error { return nil } func (c *fakeClient) Deregister(Service) error { return nil } golang-github-go-kit-kit-0.13.0/sd/etcdv3/integration_test.go000066400000000000000000000136121443521372500240170ustar00rootroot00000000000000//go:build flaky_integration // +build flaky_integration package etcdv3 import ( "context" "io" "os" "testing" "time" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/sd" "github.com/go-kit/log" ) func runIntegration(settings integrationSettings, client Client, service Service, t *testing.T) { // Verify test data is initially empty. entries, err := client.GetEntries(settings.key) if err != nil { t.Fatalf("GetEntries(%q): expected no error, got one: %v", settings.key, err) } if len(entries) > 0 { t.Fatalf("GetEntries(%q): expected no instance entries, got %d", settings.key, len(entries)) } t.Logf("GetEntries(%q): %v (OK)", settings.key, entries) // Instantiate a new Registrar, passing in test data. registrar := NewRegistrar( client, service, log.With(log.NewLogfmtLogger(os.Stderr), "component", "registrar"), ) // Register our instance. registrar.Register() t.Log("Registered") // Retrieve entries from etcd manually. entries, err = client.GetEntries(settings.key) if err != nil { t.Fatalf("client.GetEntries(%q): %v", settings.key, err) } if want, have := 1, len(entries); want != have { t.Fatalf("client.GetEntries(%q): want %d, have %d", settings.key, want, have) } if want, have := settings.value, entries[0]; want != have { t.Fatalf("want %q, have %q", want, have) } instancer, err := NewInstancer( client, settings.prefix, log.With(log.NewLogfmtLogger(os.Stderr), "component", "instancer"), ) if err != nil { t.Fatalf("NewInstancer: %v", err) } t.Log("Constructed Instancer OK") defer instancer.Stop() endpointer := sd.NewEndpointer( instancer, func(string) (endpoint.Endpoint, io.Closer, error) { return endpoint.Nop, nil, nil }, log.With(log.NewLogfmtLogger(os.Stderr), "component", "instancer"), ) t.Log("Constructed Endpointer OK") defer endpointer.Close() if !within(time.Second, func() bool { endpoints, err := endpointer.Endpoints() return err == nil && len(endpoints) == 1 }) { t.Fatal("Endpointer didn't see Register in time") } t.Log("Endpointer saw Register OK") // Deregister first instance of test data. registrar.Deregister() t.Log("Deregistered") // Check it was deregistered. if !within(time.Second, func() bool { endpoints, err := endpointer.Endpoints() t.Logf("Checking Deregister: len(endpoints) = %d, err = %v", len(endpoints), err) return err == nil && len(endpoints) == 0 }) { t.Fatalf("Endpointer didn't see Deregister in time") } // Verify test data no longer exists in etcd. entries, err = client.GetEntries(settings.key) if err != nil { t.Fatalf("GetEntries(%q): expected no error, got one: %v", settings.key, err) } if len(entries) > 0 { t.Fatalf("GetEntries(%q): expected no entries, got %v", settings.key, entries) } t.Logf("GetEntries(%q): %v (OK)", settings.key, entries) } type integrationSettings struct { addr string prefix string instance string key string value string } func testIntegrationSettings(t *testing.T) integrationSettings { var settings integrationSettings settings.addr = os.Getenv("ETCD_ADDR") if settings.addr == "" { t.Skip("ETCD_ADDR not set; skipping integration test") } settings.prefix = "/services/foosvc/" // known at compile time settings.instance = "1.2.3.4:8080" // taken from runtime or platform, somehow settings.key = settings.prefix + settings.instance settings.value = "http://" + settings.instance // based on our transport return settings } // Package sd/etcd provides a wrapper around the etcd key/value store. This // example assumes the user has an instance of etcd installed and running // locally on port 2379. func TestIntegration(t *testing.T) { settings := testIntegrationSettings(t) client, err := NewClient(context.Background(), []string{settings.addr}, ClientOptions{ DialTimeout: 2 * time.Second, DialKeepAlive: 2 * time.Second, }) if err != nil { t.Fatalf("NewClient(%q): %v", settings.addr, err) } service := Service{ Key: settings.key, Value: settings.value, } runIntegration(settings, client, service, t) } func TestIntegrationTTL(t *testing.T) { settings := testIntegrationSettings(t) client, err := NewClient(context.Background(), []string{settings.addr}, ClientOptions{ DialTimeout: 2 * time.Second, DialKeepAlive: 2 * time.Second, }) if err != nil { t.Fatalf("NewClient(%q): %v", settings.addr, err) } service := Service{ Key: settings.key, Value: settings.value, TTL: NewTTLOption(time.Second*3, time.Second*10), } defer client.Deregister(service) runIntegration(settings, client, service, t) } func TestIntegrationRegistrarOnly(t *testing.T) { settings := testIntegrationSettings(t) client, err := NewClient(context.Background(), []string{settings.addr}, ClientOptions{ DialTimeout: 2 * time.Second, DialKeepAlive: 2 * time.Second, }) if err != nil { t.Fatalf("NewClient(%q): %v", settings.addr, err) } service := Service{ Key: settings.key, Value: settings.value, TTL: NewTTLOption(time.Second*3, time.Second*10), } defer client.Deregister(service) // Verify test data is initially empty. entries, err := client.GetEntries(settings.key) if err != nil { t.Fatalf("GetEntries(%q): expected no error, got one: %v", settings.key, err) } if len(entries) > 0 { t.Fatalf("GetEntries(%q): expected no instance entries, got %d", settings.key, len(entries)) } t.Logf("GetEntries(%q): %v (OK)", settings.key, entries) // Instantiate a new Registrar, passing in test data. registrar := NewRegistrar( client, service, log.With(log.NewLogfmtLogger(os.Stderr), "component", "registrar"), ) // Register our instance. registrar.Register() t.Log("Registered") // Deregister our instance. (so we test registrar only scenario) registrar.Deregister() t.Log("Deregistered") } func within(d time.Duration, f func() bool) bool { deadline := time.Now().Add(d) for time.Now().Before(deadline) { if f() { return true } time.Sleep(d / 10) } return false } golang-github-go-kit-kit-0.13.0/sd/etcdv3/registrar.go000066400000000000000000000052241443521372500224370ustar00rootroot00000000000000package etcdv3 import ( "sync" "time" "github.com/go-kit/log" ) const minHeartBeatTime = 500 * time.Millisecond // Registrar registers service instance liveness information to etcd. type Registrar struct { client Client service Service logger log.Logger quitmtx sync.Mutex quit chan struct{} } // Service holds the instance identifying data you want to publish to etcd. Key // must be unique, and value is the string returned to subscribers, typically // called the "instance" string in other parts of package sd. type Service struct { Key string // unique key, e.g. "/service/foobar/1.2.3.4:8080" Value string // returned to subscribers, e.g. "http://1.2.3.4:8080" TTL *TTLOption } // TTLOption allow setting a key with a TTL. This option will be used by a loop // goroutine which regularly refreshes the lease of the key. type TTLOption struct { heartbeat time.Duration // e.g. time.Second * 3 ttl time.Duration // e.g. time.Second * 10 } // NewTTLOption returns a TTLOption that contains proper TTL settings. Heartbeat // is used to refresh the lease of the key periodically; its value should be at // least 500ms. TTL defines the lease of the key; its value should be // significantly greater than heartbeat. // // Good default values might be 3s heartbeat, 10s TTL. func NewTTLOption(heartbeat, ttl time.Duration) *TTLOption { if heartbeat <= minHeartBeatTime { heartbeat = minHeartBeatTime } if ttl <= heartbeat { ttl = 3 * heartbeat } return &TTLOption{ heartbeat: heartbeat, ttl: ttl, } } // NewRegistrar returns a etcd Registrar acting on the provided catalog // registration (service). func NewRegistrar(client Client, service Service, logger log.Logger) *Registrar { return &Registrar{ client: client, service: service, logger: log.With(logger, "key", service.Key, "value", service.Value), } } // Register implements the sd.Registrar interface. Call it when you want your // service to be registered in etcd, typically at startup. func (r *Registrar) Register() { if err := r.client.Register(r.service); err != nil { r.logger.Log("err", err) return } if r.service.TTL != nil { r.logger.Log("action", "register", "lease", r.client.LeaseID()) } else { r.logger.Log("action", "register") } } // Deregister implements the sd.Registrar interface. Call it when you want your // service to be deregistered from etcd, typically just prior to shutdown. func (r *Registrar) Deregister() { if err := r.client.Deregister(r.service); err != nil { r.logger.Log("err", err) } else { r.logger.Log("action", "deregister") } r.quitmtx.Lock() defer r.quitmtx.Unlock() if r.quit != nil { close(r.quit) r.quit = nil } } golang-github-go-kit-kit-0.13.0/sd/etcdv3/registrar_test.go000066400000000000000000000057441443521372500235050ustar00rootroot00000000000000package etcdv3 import ( "bytes" "errors" "testing" "github.com/go-kit/log" ) // testClient is a basic implementation of Client type testClient struct { registerRes error // value returned when Register or Deregister is called } func (tc *testClient) GetEntries(prefix string) ([]string, error) { return nil, nil } func (tc *testClient) WatchPrefix(prefix string, ch chan struct{}) { } func (tc *testClient) Register(s Service) error { return tc.registerRes } func (tc *testClient) Deregister(s Service) error { return tc.registerRes } func (tc *testClient) LeaseID() int64 { return 0 } // default service used to build registrar in our tests var testService = Service{ Key: "testKey", Value: "testValue", TTL: nil, } // NewRegistar should return a registar with a logger using the service key and value func TestNewRegistar(t *testing.T) { c := Client(&testClient{nil}) buf := &bytes.Buffer{} logger := log.NewLogfmtLogger(buf) r := NewRegistrar( c, testService, logger, ) if err := r.logger.Log("msg", "message"); err != nil { t.Fatal(err) } if want, have := "key=testKey value=testValue msg=message\n", buf.String(); want != have { t.Errorf("\nwant: %shave: %s", want, have) } } func TestRegister(t *testing.T) { // Register log the error returned by the client or log the successful registration action // table of test cases for method Register var registerTestTable = []struct { registerRes error // value returned by the client on calls to Register log string // expected log by the registrar }{ // test case: an error is returned by the client {errors.New("regError"), "key=testKey value=testValue err=regError\n"}, // test case: registration successful {nil, "key=testKey value=testValue action=register\n"}, } for _, tc := range registerTestTable { c := Client(&testClient{tc.registerRes}) buf := &bytes.Buffer{} logger := log.NewLogfmtLogger(buf) r := NewRegistrar( c, testService, logger, ) r.Register() if want, have := tc.log, buf.String(); want != have { t.Fatalf("want %v, have %v", want, have) } } } func TestDeregister(t *testing.T) { // Deregister log the error returned by the client or log the successful deregistration action // table of test cases for method Deregister var deregisterTestTable = []struct { deregisterRes error // value returned by the client on calls to Deregister log string // expected log by the registrar }{ // test case: an error is returned by the client {errors.New("deregError"), "key=testKey value=testValue err=deregError\n"}, // test case: deregistration successful {nil, "key=testKey value=testValue action=deregister\n"}, } for _, tc := range deregisterTestTable { c := Client(&testClient{tc.deregisterRes}) buf := &bytes.Buffer{} logger := log.NewLogfmtLogger(buf) r := NewRegistrar( c, testService, logger, ) r.Deregister() if want, have := tc.log, buf.String(); want != have { t.Fatalf("want %v, have %v", want, have) } } } golang-github-go-kit-kit-0.13.0/sd/eureka/000077500000000000000000000000001443521372500201675ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/sd/eureka/doc.go000066400000000000000000000001531443521372500212620ustar00rootroot00000000000000// Package eureka provides Instancer and Registrar implementations for Netflix OSS's Eureka package eureka golang-github-go-kit-kit-0.13.0/sd/eureka/instancer.go000066400000000000000000000046261443521372500225140ustar00rootroot00000000000000package eureka import ( "fmt" "github.com/hudl/fargo" "github.com/go-kit/kit/sd" "github.com/go-kit/kit/sd/internal/instance" "github.com/go-kit/log" ) // Instancer yields instances stored in the Eureka registry for the given app. // Changes in that app are watched and will update the subscribers. type Instancer struct { cache *instance.Cache conn fargoConnection app string logger log.Logger quitc chan chan struct{} } // NewInstancer returns a Eureka Instancer. It will start watching the given // app string for changes, and update the subscribers accordingly. func NewInstancer(conn fargoConnection, app string, logger log.Logger) *Instancer { logger = log.With(logger, "app", app) s := &Instancer{ cache: instance.NewCache(), conn: conn, app: app, logger: logger, quitc: make(chan chan struct{}), } done := make(chan struct{}) updates := conn.ScheduleAppUpdates(app, true, done) s.consume(<-updates) go s.loop(updates, done) return s } // Stop terminates the Instancer. func (s *Instancer) Stop() { q := make(chan struct{}) s.quitc <- q <-q s.quitc = nil } func (s *Instancer) consume(update fargo.AppUpdate) { if update.Err != nil { s.logger.Log("during", "Update", "err", update.Err) s.cache.Update(sd.Event{Err: update.Err}) return } instances := convertFargoAppToInstances(update.App) s.logger.Log("instances", len(instances)) s.cache.Update(sd.Event{Instances: instances}) } func (s *Instancer) loop(updates <-chan fargo.AppUpdate, done chan<- struct{}) { defer close(done) for { select { case update := <-updates: s.consume(update) case q := <-s.quitc: close(q) return } } } func (s *Instancer) getInstances() ([]string, error) { app, err := s.conn.GetApp(s.app) if err != nil { return nil, err } return convertFargoAppToInstances(app), nil } func convertFargoAppToInstances(app *fargo.Application) []string { instances := make([]string, len(app.Instances)) for i, inst := range app.Instances { instances[i] = fmt.Sprintf("%s:%d", inst.IPAddr, inst.Port) } return instances } // Register implements Instancer. func (s *Instancer) Register(ch chan<- sd.Event) { s.cache.Register(ch) } // Deregister implements Instancer. func (s *Instancer) Deregister(ch chan<- sd.Event) { s.cache.Deregister(ch) } // state returns the current state of instance.Cache, only for testing func (s *Instancer) state() sd.Event { return s.cache.State() } golang-github-go-kit-kit-0.13.0/sd/eureka/instancer_test.go000066400000000000000000000043051443521372500235450ustar00rootroot00000000000000package eureka import ( "testing" "time" "github.com/hudl/fargo" "github.com/go-kit/kit/sd" ) var _ sd.Instancer = (*Instancer)(nil) // API check func TestInstancer(t *testing.T) { connection := &testConnection{ instances: []*fargo.Instance{instanceTest1, instanceTest2}, errApplication: nil, } instancer := NewInstancer(connection, appNameTest, loggerTest) defer instancer.Stop() state := instancer.state() if state.Err != nil { t.Fatal(state.Err) } if want, have := 2, len(state.Instances); want != have { t.Errorf("want %d, have %d", want, have) } } func TestInstancerReceivesUpdates(t *testing.T) { connection := &testConnection{ instances: []*fargo.Instance{instanceTest1}, errApplication: nil, } instancer := NewInstancer(connection, appNameTest, loggerTest) defer instancer.Stop() verifyCount := func(want int) (have int, converged bool) { const maxPollAttempts = 5 const delayPerAttempt = 200 * time.Millisecond for i := 1; ; i++ { state := instancer.state() if have := len(state.Instances); want == have { return have, true } else if i == maxPollAttempts { return have, false } time.Sleep(delayPerAttempt) } } if have, converged := verifyCount(1); !converged { t.Fatalf("initial: want %d, have %d", 1, have) } if err := connection.RegisterInstance(instanceTest2); err != nil { t.Fatalf("failed to register an instance: %v", err) } if have, converged := verifyCount(2); !converged { t.Fatalf("after registration: want %d, have %d", 2, have) } if err := connection.DeregisterInstance(instanceTest1); err != nil { t.Fatalf("failed to unregister an instance: %v", err) } if have, converged := verifyCount(1); !converged { t.Fatalf("after deregistration: want %d, have %d", 1, have) } } func TestBadInstancerScheduleUpdates(t *testing.T) { connection := &testConnection{ instances: []*fargo.Instance{instanceTest1}, errApplication: errTest, } instancer := NewInstancer(connection, appNameTest, loggerTest) defer instancer.Stop() state := instancer.state() if state.Err == nil { t.Fatal("expecting error") } if want, have := 0, len(state.Instances); want != have { t.Errorf("want %d, have %d", want, have) } } golang-github-go-kit-kit-0.13.0/sd/eureka/integration_test.go000066400000000000000000000052461443521372500241070ustar00rootroot00000000000000//go:build integration // +build integration package eureka import ( "os" "testing" "time" "github.com/hudl/fargo" "github.com/go-kit/log" ) // Package sd/eureka provides a wrapper around the Netflix Eureka service // registry by way of the Fargo library. This test assumes the user has an // instance of Eureka available at the address in the environment variable. // Example `${EUREKA_ADDR}` format: http://localhost:8761/eureka // // NOTE: when starting a Eureka server for integration testing, ensure // the response cache interval is reduced to one second. This can be // achieved with the following Java argument: // `-Deureka.server.responseCacheUpdateIntervalMs=1000` func TestIntegration(t *testing.T) { eurekaAddr := os.Getenv("EUREKA_ADDR") if eurekaAddr == "" { t.Skip("EUREKA_ADDR is not set") } logger := log.NewLogfmtLogger(os.Stderr) logger = log.With(logger, "ts", log.DefaultTimestamp) var fargoConfig fargo.Config // Target Eureka server(s). fargoConfig.Eureka.ServiceUrls = []string{eurekaAddr} // How often the subscriber should poll for updates. fargoConfig.Eureka.PollIntervalSeconds = 1 // Create a Fargo connection and a Eureka registrar. fargoConnection := fargo.NewConnFromConfig(fargoConfig) registrar1 := NewRegistrar(&fargoConnection, instanceTest1, log.With(logger, "component", "registrar1")) // Register one instance. registrar1.Register() defer registrar1.Deregister() // Build a Eureka instancer. instancer := NewInstancer( &fargoConnection, appNameTest, log.With(logger, "component", "instancer"), ) defer instancer.Stop() // checks every 100ms (fr up to 10s) for the expected number of instances to be reported waitForInstances := func(count int) { for t := 0; t < 100; t++ { state := instancer.state() if len(state.Instances) == count { return } time.Sleep(100 * time.Millisecond) } state := instancer.state() if state.Err != nil { t.Error(state.Err) } if want, have := 1, len(state.Instances); want != have { t.Errorf("want %d, have %d", want, have) } } // We should have one instance immediately after subscriber instantiation. waitForInstances(1) // Register a second instance registrar2 := NewRegistrar(&fargoConnection, instanceTest2, log.With(logger, "component", "registrar2")) registrar2.Register() defer registrar2.Deregister() // In case of exceptional circumstances. // This should be enough time for a scheduled update assuming Eureka is // configured with the properties mentioned in the function comments. waitForInstances(2) // Deregister the second instance. registrar2.Deregister() // Wait for another scheduled update. // And then there was one. waitForInstances(1) } golang-github-go-kit-kit-0.13.0/sd/eureka/registrar.go000066400000000000000000000066211443521372500225250ustar00rootroot00000000000000package eureka import ( "fmt" "net/http" "sync" "time" "github.com/hudl/fargo" "github.com/go-kit/kit/sd" "github.com/go-kit/log" ) // Matches official Netflix Java client default. const defaultRenewalInterval = 30 * time.Second // The methods of fargo.Connection used in this package. type fargoConnection interface { RegisterInstance(instance *fargo.Instance) error DeregisterInstance(instance *fargo.Instance) error ReregisterInstance(instance *fargo.Instance) error HeartBeatInstance(instance *fargo.Instance) error ScheduleAppUpdates(name string, await bool, done <-chan struct{}) <-chan fargo.AppUpdate GetApp(name string) (*fargo.Application, error) } type fargoUnsuccessfulHTTPResponse struct { statusCode int messagePrefix string } func (u *fargoUnsuccessfulHTTPResponse) Error() string { return fmt.Sprintf("err=%s code=%d", u.messagePrefix, u.statusCode) } // Registrar maintains service instance liveness information in Eureka. type Registrar struct { conn fargoConnection instance *fargo.Instance logger log.Logger quitc chan chan struct{} sync.Mutex } var _ sd.Registrar = (*Registrar)(nil) // NewRegistrar returns an Eureka Registrar acting on behalf of the provided // Fargo connection and instance. See the integration test for usage examples. func NewRegistrar(conn fargoConnection, instance *fargo.Instance, logger log.Logger) *Registrar { return &Registrar{ conn: conn, instance: instance, logger: log.With(logger, "service", instance.App, "address", fmt.Sprintf("%s:%d", instance.IPAddr, instance.Port)), } } // Register implements sd.Registrar. func (r *Registrar) Register() { r.Lock() defer r.Unlock() if r.quitc != nil { return // Already in the registration loop. } if err := r.conn.RegisterInstance(r.instance); err != nil { r.logger.Log("during", "Register", "err", err) } r.quitc = make(chan chan struct{}) go r.loop() } // Deregister implements sd.Registrar. func (r *Registrar) Deregister() { r.Lock() defer r.Unlock() if r.quitc == nil { return // Already deregistered. } q := make(chan struct{}) r.quitc <- q <-q r.quitc = nil } func (r *Registrar) loop() { var renewalInterval time.Duration if r.instance.LeaseInfo.RenewalIntervalInSecs > 0 { renewalInterval = time.Duration(r.instance.LeaseInfo.RenewalIntervalInSecs) * time.Second } else { renewalInterval = defaultRenewalInterval } ticker := time.NewTicker(renewalInterval) defer ticker.Stop() for { select { case <-ticker.C: if err := r.heartbeat(); err != nil { r.logger.Log("during", "heartbeat", "err", err) } case q := <-r.quitc: if err := r.conn.DeregisterInstance(r.instance); err != nil { r.logger.Log("during", "Deregister", "err", err) } close(q) return } } } func httpResponseStatusCode(err error) (code int, present bool) { if code, ok := fargo.HTTPResponseStatusCode(err); ok { return code, true } // Allow injection of errors for testing. if u, ok := err.(*fargoUnsuccessfulHTTPResponse); ok { return u.statusCode, true } return 0, false } func isNotFound(err error) bool { code, ok := httpResponseStatusCode(err) return ok && code == http.StatusNotFound } func (r *Registrar) heartbeat() error { err := r.conn.HeartBeatInstance(r.instance) if err == nil { return nil } if isNotFound(err) { // Instance expired (e.g. network partition). Re-register. return r.conn.ReregisterInstance(r.instance) } return err } golang-github-go-kit-kit-0.13.0/sd/eureka/registrar_test.go000066400000000000000000000050231443521372500235570ustar00rootroot00000000000000package eureka import ( "testing" "time" ) func TestRegistrar(t *testing.T) { connection := &testConnection{ errHeartbeat: errTest, } registrar1 := NewRegistrar(connection, instanceTest1, loggerTest) registrar2 := NewRegistrar(connection, instanceTest2, loggerTest) // Not registered. registrar1.Deregister() if want, have := 0, len(connection.instances); want != have { t.Errorf("want %d, have %d", want, have) } // Register. registrar1.Register() if want, have := 1, len(connection.instances); want != have { t.Errorf("want %d, have %d", want, have) } registrar2.Register() if want, have := 2, len(connection.instances); want != have { t.Errorf("want %d, have %d", want, have) } // Deregister. registrar1.Deregister() if want, have := 1, len(connection.instances); want != have { t.Errorf("want %d, have %d", want, have) } // Already registered. registrar1.Register() if want, have := 2, len(connection.instances); want != have { t.Errorf("want %d, have %d", want, have) } registrar1.Register() if want, have := 2, len(connection.instances); want != have { t.Errorf("want %d, have %d", want, have) } // Wait for a heartbeat failure. time.Sleep(1010 * time.Millisecond) if want, have := 2, len(connection.instances); want != have { t.Errorf("want %d, have %d", want, have) } registrar1.Deregister() if want, have := 1, len(connection.instances); want != have { t.Errorf("want %d, have %d", want, have) } } func TestBadRegister(t *testing.T) { connection := &testConnection{ errRegister: errTest, } registrar := NewRegistrar(connection, instanceTest1, loggerTest) registrar.Register() if want, have := 0, len(connection.instances); want != have { t.Errorf("want %d, have %d", want, have) } } func TestBadDeregister(t *testing.T) { connection := &testConnection{ errDeregister: errTest, } registrar := NewRegistrar(connection, instanceTest1, loggerTest) registrar.Register() if want, have := 1, len(connection.instances); want != have { t.Errorf("want %d, have %d", want, have) } registrar.Deregister() if want, have := 1, len(connection.instances); want != have { t.Errorf("want %d, have %d", want, have) } } func TestExpiredInstance(t *testing.T) { connection := &testConnection{ errHeartbeat: errNotFound, } registrar := NewRegistrar(connection, instanceTest1, loggerTest) registrar.Register() // Wait for a heartbeat failure. time.Sleep(1010 * time.Millisecond) if want, have := 1, len(connection.instances); want != have { t.Errorf("want %d, have %d", want, have) } } golang-github-go-kit-kit-0.13.0/sd/eureka/util_test.go000066400000000000000000000074001443521372500225330ustar00rootroot00000000000000package eureka import ( "errors" "fmt" "reflect" "sync" "time" "github.com/go-kit/log" "github.com/hudl/fargo" ) type testConnection struct { mu sync.RWMutex instances []*fargo.Instance errApplication error errHeartbeat error errRegister error errDeregister error } var ( errTest = errors.New("kaboom") errNotFound = &fargoUnsuccessfulHTTPResponse{statusCode: 404, messagePrefix: "not found"} loggerTest = log.NewNopLogger() appNameTest = "go-kit" instanceTest1 = &fargo.Instance{ HostName: "serveregistrar1.acme.org", Port: 8080, App: appNameTest, IPAddr: "192.168.0.1", VipAddress: "192.168.0.1", SecureVipAddress: "192.168.0.1", HealthCheckUrl: "http://serveregistrar1.acme.org:8080/healthz", StatusPageUrl: "http://serveregistrar1.acme.org:8080/status", HomePageUrl: "http://serveregistrar1.acme.org:8080/", Status: fargo.UP, DataCenterInfo: fargo.DataCenterInfo{Name: fargo.MyOwn}, LeaseInfo: fargo.LeaseInfo{RenewalIntervalInSecs: 1}, } instanceTest2 = &fargo.Instance{ HostName: "serveregistrar2.acme.org", Port: 8080, App: appNameTest, IPAddr: "192.168.0.2", VipAddress: "192.168.0.2", SecureVipAddress: "192.168.0.2", HealthCheckUrl: "http://serveregistrar2.acme.org:8080/healthz", StatusPageUrl: "http://serveregistrar2.acme.org:8080/status", HomePageUrl: "http://serveregistrar2.acme.org:8080/", Status: fargo.UP, DataCenterInfo: fargo.DataCenterInfo{Name: fargo.MyOwn}, } ) var _ fargoConnection = (*testConnection)(nil) func (c *testConnection) RegisterInstance(i *fargo.Instance) error { if c.errRegister != nil { return c.errRegister } c.mu.Lock() defer c.mu.Unlock() for _, instance := range c.instances { if reflect.DeepEqual(*instance, *i) { return errors.New("already registered") } } c.instances = append(c.instances, i) return nil } func (c *testConnection) HeartBeatInstance(i *fargo.Instance) error { return c.errHeartbeat } func (c *testConnection) DeregisterInstance(i *fargo.Instance) error { if c.errDeregister != nil { return c.errDeregister } c.mu.Lock() defer c.mu.Unlock() remaining := make([]*fargo.Instance, 0, len(c.instances)) for _, instance := range c.instances { if reflect.DeepEqual(*instance, *i) { continue } remaining = append(remaining, instance) } if len(remaining) == len(c.instances) { return errors.New("not registered") } c.instances = remaining return nil } func (c *testConnection) ReregisterInstance(ins *fargo.Instance) error { return nil } func (c *testConnection) instancesForApplication(name string) []*fargo.Instance { c.mu.RLock() defer c.mu.RUnlock() instances := make([]*fargo.Instance, 0, len(c.instances)) for _, i := range c.instances { if i.App == name { instances = append(instances, i) } } return instances } func (c *testConnection) GetApp(name string) (*fargo.Application, error) { if err := c.errApplication; err != nil { return nil, err } instances := c.instancesForApplication(name) if len(instances) == 0 { return nil, fmt.Errorf("application not found for name=%s", name) } return &fargo.Application{Name: name, Instances: instances}, nil } func (c *testConnection) ScheduleAppUpdates(name string, await bool, done <-chan struct{}) <-chan fargo.AppUpdate { updatec := make(chan fargo.AppUpdate, 1) send := func() { app, err := c.GetApp(name) select { case updatec <- fargo.AppUpdate{App: app, Err: err}: default: } } if await { send() } go func() { ticker := time.NewTicker(100 * time.Millisecond) for { select { case <-ticker.C: send() case <-done: ticker.Stop() return } } }() return updatec } golang-github-go-kit-kit-0.13.0/sd/factory.go000066400000000000000000000011511443521372500207070ustar00rootroot00000000000000package sd import ( "io" "github.com/go-kit/kit/endpoint" ) // Factory is a function that converts an instance string (e.g. host:port) to a // specific endpoint. Instances that provide multiple endpoints require multiple // factories. A factory also returns an io.Closer that's invoked when the // instance goes away and needs to be cleaned up. Factories may return nil // closers. // // Users are expected to provide their own factory functions that assume // specific transports, or can deduce transports by parsing the instance string. type Factory func(instance string) (endpoint.Endpoint, io.Closer, error) golang-github-go-kit-kit-0.13.0/sd/instancer.go000066400000000000000000000030721443521372500212320ustar00rootroot00000000000000package sd // Event represents a push notification generated from the underlying service discovery // implementation. It contains either a full set of available resource instances, or // an error indicating some issue with obtaining information from discovery backend. // Examples of errors may include loosing connection to the discovery backend, or // trying to look up resource instances using an incorrectly formatted key. // After receiving an Event with an error the listenter should treat previously discovered // resource instances as stale (although it may choose to continue using them). // If the Instancer is able to restore connection to the discovery backend it must push // another Event with the current set of resource instances. type Event struct { Instances []string Err error } // Instancer listens to a service discovery system and notifies registered // observers of changes in the resource instances. Every event sent to the channels // contains a complete set of instances known to the Instancer. That complete set is // sent immediately upon registering the channel, and on any future updates from // discovery system. type Instancer interface { Register(chan<- Event) Deregister(chan<- Event) Stop() } // FixedInstancer yields a fixed set of instances. type FixedInstancer []string // Register implements Instancer. func (d FixedInstancer) Register(ch chan<- Event) { ch <- Event{Instances: d} } // Deregister implements Instancer. func (d FixedInstancer) Deregister(ch chan<- Event) {} // Stop implements Instancer. func (d FixedInstancer) Stop() {} golang-github-go-kit-kit-0.13.0/sd/internal/000077500000000000000000000000001443521372500205275ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/sd/internal/instance/000077500000000000000000000000001443521372500223335ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/sd/internal/instance/cache.go000066400000000000000000000042411443521372500237260ustar00rootroot00000000000000package instance import ( "reflect" "sort" "sync" "github.com/go-kit/kit/sd" ) // Cache keeps track of resource instances provided to it via Update method // and implements the Instancer interface type Cache struct { mtx sync.RWMutex state sd.Event reg registry } // NewCache creates a new Cache. func NewCache() *Cache { return &Cache{ reg: registry{}, } } // Update receives new instances from service discovery, stores them internally, // and notifies all registered listeners. func (c *Cache) Update(event sd.Event) { c.mtx.Lock() defer c.mtx.Unlock() sort.Strings(event.Instances) if reflect.DeepEqual(c.state, event) { return // no need to broadcast the same instances } c.state = event c.reg.broadcast(event) } // State returns the current state of discovery (instances or error) as sd.Event func (c *Cache) State() sd.Event { c.mtx.RLock() event := c.state c.mtx.RUnlock() eventCopy := copyEvent(event) return eventCopy } // Stop implements Instancer. Since the cache is just a plain-old store of data, // Stop is a no-op. func (c *Cache) Stop() {} // Register implements Instancer. func (c *Cache) Register(ch chan<- sd.Event) { c.mtx.Lock() defer c.mtx.Unlock() c.reg.register(ch) event := c.state eventCopy := copyEvent(event) // always push the current state to new channels ch <- eventCopy } // Deregister implements Instancer. func (c *Cache) Deregister(ch chan<- sd.Event) { c.mtx.Lock() defer c.mtx.Unlock() c.reg.deregister(ch) } // registry is not goroutine-safe. type registry map[chan<- sd.Event]struct{} func (r registry) broadcast(event sd.Event) { for c := range r { eventCopy := copyEvent(event) c <- eventCopy } } func (r registry) register(c chan<- sd.Event) { r[c] = struct{}{} } func (r registry) deregister(c chan<- sd.Event) { delete(r, c) } // copyEvent does a deep copy on sd.Event func copyEvent(e sd.Event) sd.Event { // observers all need their own copy of event // because they can directly modify event.Instances // for example, by calling sort.Strings if e.Instances == nil { return e } instances := make([]string, len(e.Instances)) copy(instances, e.Instances) e.Instances = instances return e } golang-github-go-kit-kit-0.13.0/sd/internal/instance/cache_test.go000066400000000000000000000062441443521372500247720ustar00rootroot00000000000000package instance import ( "context" "fmt" "io" "reflect" "testing" "time" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/sd" "github.com/go-kit/log" ) var _ sd.Instancer = (*Cache)(nil) // API check // The test verifies the following: // registering causes initial notification of the current state // instances are sorted // different update causes new notification // identical notifications cause no updates // no updates after de-registering func TestCache(t *testing.T) { e1 := sd.Event{Instances: []string{"y", "x"}} // not sorted e2 := sd.Event{Instances: []string{"c", "a", "b"}} cache := NewCache() if want, have := 0, len(cache.State().Instances); want != have { t.Fatalf("want %v instances, have %v", want, have) } cache.Update(e1) // sets initial state if want, have := 2, len(cache.State().Instances); want != have { t.Fatalf("want %v instances, have %v", want, have) } r1 := make(chan sd.Event) go cache.Register(r1) expectUpdate(t, r1, []string{"x", "y"}) go cache.Update(e2) // different set expectUpdate(t, r1, []string{"a", "b", "c"}) cache.Deregister(r1) close(r1) } func expectUpdate(t *testing.T, r chan sd.Event, expect []string) { select { case e := <-r: if want, have := expect, e.Instances; !reflect.DeepEqual(want, have) { t.Fatalf("want: %v, have: %v", want, have) } case <-time.After(time.Second): t.Fatalf("did not receive expected update %v", expect) } } func TestRegistry(t *testing.T) { reg := make(registry) c1 := make(chan sd.Event, 1) c2 := make(chan sd.Event, 1) reg.register(c1) reg.register(c2) // validate that both channels receive the update reg.broadcast(sd.Event{Instances: []string{"x", "y"}}) if want, have := []string{"x", "y"}, (<-c1).Instances; !reflect.DeepEqual(want, have) { t.Fatalf("want: %v, have: %v", want, have) } if want, have := []string{"x", "y"}, (<-c2).Instances; !reflect.DeepEqual(want, have) { t.Fatalf("want: %v, have: %v", want, have) } reg.deregister(c1) reg.deregister(c2) close(c1) close(c2) // if deregister didn't work, broadcast would panic on closed channels reg.broadcast(sd.Event{Instances: []string{"x", "y"}}) } // This test is meant to be run with the race detector enabled: -race. // It ensures that every registered observer receives a copy // of sd.Event.Instances because observers can directly modify the field. // For example, endpointCache calls sort.Strings() on sd.Event.Instances. func TestDataRace(t *testing.T) { instances := make([]string, 0) // the number of iterations here maters because we need sort.Strings to // perform a Swap in doPivot -> medianOfThree to cause a data race. for i := 1; i < 1000; i++ { instances = append(instances, fmt.Sprintf("%v", i)) } e1 := sd.Event{Instances: instances} cache := NewCache() cache.Update(e1) nullEndpoint := func(_ context.Context, _ interface{}) (interface{}, error) { return nil, nil } nullFactory := func(instance string) (endpoint.Endpoint, io.Closer, error) { return nullEndpoint, nil, nil } logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error { return nil })) sd.NewEndpointer(cache, nullFactory, logger) sd.NewEndpointer(cache, nullFactory, logger) } golang-github-go-kit-kit-0.13.0/sd/lb/000077500000000000000000000000001443521372500173105ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/sd/lb/balancer.go000066400000000000000000000005071443521372500214100ustar00rootroot00000000000000package lb import ( "errors" "github.com/go-kit/kit/endpoint" ) // Balancer yields endpoints according to some heuristic. type Balancer interface { Endpoint() (endpoint.Endpoint, error) } // ErrNoEndpoints is returned when no qualifying endpoints are available. var ErrNoEndpoints = errors.New("no endpoints available") golang-github-go-kit-kit-0.13.0/sd/lb/doc.go000066400000000000000000000003641443521372500204070ustar00rootroot00000000000000// Package lb implements the client-side load balancer pattern. When combined // with a service discovery system of record, it enables a more decentralized // architecture, removing the need for separate load balancers like HAProxy. package lb golang-github-go-kit-kit-0.13.0/sd/lb/random.go000066400000000000000000000011121443521372500211120ustar00rootroot00000000000000package lb import ( "math/rand" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/sd" ) // NewRandom returns a load balancer that selects services randomly. func NewRandom(s sd.Endpointer, seed int64) Balancer { return &random{ s: s, r: rand.New(rand.NewSource(seed)), } } type random struct { s sd.Endpointer r *rand.Rand } func (r *random) Endpoint() (endpoint.Endpoint, error) { endpoints, err := r.s.Endpoints() if err != nil { return nil, err } if len(endpoints) <= 0 { return nil, ErrNoEndpoints } return endpoints[r.r.Intn(len(endpoints))], nil } golang-github-go-kit-kit-0.13.0/sd/lb/random_test.go000066400000000000000000000022461443521372500221620ustar00rootroot00000000000000package lb import ( "context" "math" "testing" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/sd" ) func TestRandom(t *testing.T) { var ( n = 7 endpoints = make([]endpoint.Endpoint, n) counts = make([]int, n) seed = int64(12345) iterations = 1000000 want = iterations / n tolerance = want / 100 // 1% ) for i := 0; i < n; i++ { i0 := i endpoints[i] = func(context.Context, interface{}) (interface{}, error) { counts[i0]++; return struct{}{}, nil } } endpointer := sd.FixedEndpointer(endpoints) balancer := NewRandom(endpointer, seed) for i := 0; i < iterations; i++ { endpoint, _ := balancer.Endpoint() endpoint(context.Background(), struct{}{}) } for i, have := range counts { delta := int(math.Abs(float64(want - have))) if delta > tolerance { t.Errorf("%d: want %d, have %d, delta %d > %d tolerance", i, want, have, delta, tolerance) } } } func TestRandomNoEndpoints(t *testing.T) { endpointer := sd.FixedEndpointer{} balancer := NewRandom(endpointer, 1415926) _, err := balancer.Endpoint() if want, have := ErrNoEndpoints, err; want != have { t.Errorf("want %v, have %v", want, have) } } golang-github-go-kit-kit-0.13.0/sd/lb/retry.go000066400000000000000000000066321443521372500210130ustar00rootroot00000000000000package lb import ( "context" "fmt" "strings" "time" "github.com/go-kit/kit/endpoint" ) // RetryError is an error wrapper that is used by the retry mechanism. All // errors returned by the retry mechanism via its endpoint will be RetryErrors. type RetryError struct { RawErrors []error // all errors encountered from endpoints directly Final error // the final, terminating error } func (e RetryError) Error() string { var suffix string if len(e.RawErrors) > 1 { a := make([]string, len(e.RawErrors)-1) for i := 0; i < len(e.RawErrors)-1; i++ { // last one is Final a[i] = e.RawErrors[i].Error() } suffix = fmt.Sprintf(" (previously: %s)", strings.Join(a, "; ")) } return fmt.Sprintf("%v%s", e.Final, suffix) } // Callback is a function that is given the current attempt count and the error // received from the underlying endpoint. It should return whether the Retry // function should continue trying to get a working endpoint, and a custom error // if desired. The error message may be nil, but a true/false is always // expected. In all cases, if the replacement error is supplied, the received // error will be replaced in the calling context. type Callback func(n int, received error) (keepTrying bool, replacement error) // Retry wraps a service load balancer and returns an endpoint oriented load // balancer for the specified service method. Requests to the endpoint will be // automatically load balanced via the load balancer. Requests that return // errors will be retried until they succeed, up to max times, or until the // timeout is elapsed, whichever comes first. func Retry(max int, timeout time.Duration, b Balancer) endpoint.Endpoint { return RetryWithCallback(timeout, b, maxRetries(max)) } func maxRetries(max int) Callback { return func(n int, err error) (keepTrying bool, replacement error) { return n < max, nil } } func alwaysRetry(int, error) (keepTrying bool, replacement error) { return true, nil } // RetryWithCallback wraps a service load balancer and returns an endpoint // oriented load balancer for the specified service method. Requests to the // endpoint will be automatically load balanced via the load balancer. Requests // that return errors will be retried until they succeed, up to max times, until // the callback returns false, or until the timeout is elapsed, whichever comes // first. func RetryWithCallback(timeout time.Duration, b Balancer, cb Callback) endpoint.Endpoint { if cb == nil { cb = alwaysRetry } if b == nil { panic("nil Balancer") } return func(ctx context.Context, request interface{}) (response interface{}, err error) { var ( newctx, cancel = context.WithTimeout(ctx, timeout) responses = make(chan interface{}, 1) errs = make(chan error, 1) final RetryError ) defer cancel() for i := 1; ; i++ { go func() { e, err := b.Endpoint() if err != nil { errs <- err return } response, err := e(newctx, request) if err != nil { errs <- err return } responses <- response }() select { case <-newctx.Done(): return nil, newctx.Err() case response := <-responses: return response, nil case err := <-errs: final.RawErrors = append(final.RawErrors, err) keepTrying, replacement := cb(i, err) if replacement != nil { err = replacement } if !keepTrying { final.Final = err return nil, final } continue } } } } golang-github-go-kit-kit-0.13.0/sd/lb/retry_test.go000066400000000000000000000107611443521372500220500ustar00rootroot00000000000000package lb_test import ( "context" "errors" "testing" "time" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/sd" "github.com/go-kit/kit/sd/lb" ) func TestRetryMaxTotalFail(t *testing.T) { var ( endpoints = sd.FixedEndpointer{} // no endpoints rr = lb.NewRoundRobin(endpoints) retry = lb.Retry(999, time.Second, rr) // lots of retries ctx = context.Background() ) if _, err := retry(ctx, struct{}{}); err == nil { t.Errorf("expected error, got none") // should fail } } func TestRetryMaxPartialFail(t *testing.T) { var ( endpoints = []endpoint.Endpoint{ func(context.Context, interface{}) (interface{}, error) { return nil, errors.New("error one") }, func(context.Context, interface{}) (interface{}, error) { return nil, errors.New("error two") }, func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil /* OK */ }, } endpointer = sd.FixedEndpointer{ 0: endpoints[0], 1: endpoints[1], 2: endpoints[2], } retries = len(endpoints) - 1 // not quite enough retries rr = lb.NewRoundRobin(endpointer) ctx = context.Background() ) if _, err := lb.Retry(retries, time.Second, rr)(ctx, struct{}{}); err == nil { t.Errorf("expected error two, got none") } } func TestRetryMaxSuccess(t *testing.T) { var ( endpoints = []endpoint.Endpoint{ func(context.Context, interface{}) (interface{}, error) { return nil, errors.New("error one") }, func(context.Context, interface{}) (interface{}, error) { return nil, errors.New("error two") }, func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil /* OK */ }, } endpointer = sd.FixedEndpointer{ 0: endpoints[0], 1: endpoints[1], 2: endpoints[2], } retries = len(endpoints) // exactly enough retries rr = lb.NewRoundRobin(endpointer) ctx = context.Background() ) if _, err := lb.Retry(retries, time.Second, rr)(ctx, struct{}{}); err != nil { t.Error(err) } } func TestRetryTimeout(t *testing.T) { var ( step = make(chan struct{}) e = func(context.Context, interface{}) (interface{}, error) { <-step; return struct{}{}, nil } timeout = time.Millisecond retry = lb.Retry(999, timeout, lb.NewRoundRobin(sd.FixedEndpointer{0: e})) errs = make(chan error, 1) invoke = func() { _, err := retry(context.Background(), struct{}{}); errs <- err } ) go func() { step <- struct{}{} }() // queue up a flush of the endpoint invoke() // invoke the endpoint and trigger the flush if err := <-errs; err != nil { // that should succeed t.Error(err) } go func() { time.Sleep(10 * timeout); step <- struct{}{} }() // a delayed flush invoke() // invoke the endpoint if err := <-errs; err != context.DeadlineExceeded { // that should not succeed t.Errorf("wanted %v, got none", context.DeadlineExceeded) } } func TestAbortEarlyCustomMessage(t *testing.T) { var ( myErr = errors.New("aborting early") cb = func(int, error) (bool, error) { return false, myErr } endpoints = sd.FixedEndpointer{} // no endpoints rr = lb.NewRoundRobin(endpoints) retry = lb.RetryWithCallback(time.Second, rr, cb) // lots of retries ctx = context.Background() ) _, err := retry(ctx, struct{}{}) if want, have := myErr, err.(lb.RetryError).Final; want != have { t.Errorf("want %v, have %v", want, have) } } func TestErrorPassedUnchangedToCallback(t *testing.T) { var ( myErr = errors.New("my custom error") cb = func(_ int, err error) (bool, error) { if want, have := myErr, err; want != have { t.Errorf("want %v, have %v", want, have) } return false, nil } endpoint = func(ctx context.Context, request interface{}) (interface{}, error) { return nil, myErr } endpoints = sd.FixedEndpointer{endpoint} // no endpoints rr = lb.NewRoundRobin(endpoints) retry = lb.RetryWithCallback(time.Second, rr, cb) // lots of retries ctx = context.Background() ) _, err := retry(ctx, struct{}{}) if want, have := myErr, err.(lb.RetryError).Final; want != have { t.Errorf("want %v, have %v", want, have) } } func TestHandleNilCallback(t *testing.T) { var ( endpointer = sd.FixedEndpointer{ func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil /* OK */ }, } rr = lb.NewRoundRobin(endpointer) ctx = context.Background() ) retry := lb.RetryWithCallback(time.Second, rr, nil) if _, err := retry(ctx, struct{}{}); err != nil { t.Error(err) } } golang-github-go-kit-kit-0.13.0/sd/lb/round_robin.go000066400000000000000000000011571443521372500221630ustar00rootroot00000000000000package lb import ( "sync/atomic" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/sd" ) // NewRoundRobin returns a load balancer that returns services in sequence. func NewRoundRobin(s sd.Endpointer) Balancer { return &roundRobin{ s: s, c: 0, } } type roundRobin struct { s sd.Endpointer c uint64 } func (rr *roundRobin) Endpoint() (endpoint.Endpoint, error) { endpoints, err := rr.s.Endpoints() if err != nil { return nil, err } if len(endpoints) <= 0 { return nil, ErrNoEndpoints } old := atomic.AddUint64(&rr.c, 1) - 1 idx := old % uint64(len(endpoints)) return endpoints[idx], nil } golang-github-go-kit-kit-0.13.0/sd/lb/round_robin_test.go000066400000000000000000000035341443521372500232230ustar00rootroot00000000000000package lb import ( "context" "reflect" "sync" "sync/atomic" "testing" "time" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/sd" ) func TestRoundRobin(t *testing.T) { var ( counts = []int{0, 0, 0} endpoints = []endpoint.Endpoint{ func(context.Context, interface{}) (interface{}, error) { counts[0]++; return struct{}{}, nil }, func(context.Context, interface{}) (interface{}, error) { counts[1]++; return struct{}{}, nil }, func(context.Context, interface{}) (interface{}, error) { counts[2]++; return struct{}{}, nil }, } ) endpointer := sd.FixedEndpointer(endpoints) balancer := NewRoundRobin(endpointer) for i, want := range [][]int{ {1, 0, 0}, {1, 1, 0}, {1, 1, 1}, {2, 1, 1}, {2, 2, 1}, {2, 2, 2}, {3, 2, 2}, } { endpoint, err := balancer.Endpoint() if err != nil { t.Fatal(err) } endpoint(context.Background(), struct{}{}) if have := counts; !reflect.DeepEqual(want, have) { t.Fatalf("%d: want %v, have %v", i, want, have) } } } func TestRoundRobinNoEndpoints(t *testing.T) { endpointer := sd.FixedEndpointer{} balancer := NewRoundRobin(endpointer) _, err := balancer.Endpoint() if want, have := ErrNoEndpoints, err; want != have { t.Errorf("want %v, have %v", want, have) } } func TestRoundRobinNoRace(t *testing.T) { balancer := NewRoundRobin(sd.FixedEndpointer([]endpoint.Endpoint{ endpoint.Nop, endpoint.Nop, endpoint.Nop, endpoint.Nop, endpoint.Nop, })) var ( n = 100 done = make(chan struct{}) wg sync.WaitGroup count uint64 ) wg.Add(n) for i := 0; i < n; i++ { go func() { defer wg.Done() for { select { case <-done: return default: _, _ = balancer.Endpoint() atomic.AddUint64(&count, 1) } } }() } time.Sleep(time.Second) close(done) wg.Wait() t.Logf("made %d calls", atomic.LoadUint64(&count)) } golang-github-go-kit-kit-0.13.0/sd/registrar.go000066400000000000000000000007751443521372500212550ustar00rootroot00000000000000package sd // Registrar registers instance information to a service discovery system when // an instance becomes alive and healthy, and deregisters that information when // the service becomes unhealthy or goes away. // // Registrar implementations exist for various service discovery systems. Note // that identifying instance information (e.g. host:port) must be given via the // concrete constructor; this interface merely signals lifecycle changes. type Registrar interface { Register() Deregister() } golang-github-go-kit-kit-0.13.0/sd/zk/000077500000000000000000000000001443521372500173375ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/sd/zk/client.go000066400000000000000000000165701443521372500211550ustar00rootroot00000000000000package zk import ( "errors" "net" "strings" "time" "github.com/go-zookeeper/zk" "github.com/go-kit/log" ) // DefaultACL is the default ACL to use for creating znodes. var ( DefaultACL = zk.WorldACL(zk.PermAll) ErrInvalidCredentials = errors.New("invalid credentials provided") ErrClientClosed = errors.New("client service closed") ErrNotRegistered = errors.New("not registered") ErrNodeNotFound = errors.New("node not found") ) const ( // DefaultConnectTimeout is the default timeout to establish a connection to // a ZooKeeper node. DefaultConnectTimeout = 2 * time.Second // DefaultSessionTimeout is the default timeout to keep the current // ZooKeeper session alive during a temporary disconnect. DefaultSessionTimeout = 5 * time.Second ) // Client is a wrapper around a lower level ZooKeeper client implementation. type Client interface { // GetEntries should query the provided path in ZooKeeper, place a watch on // it and retrieve data from its current child nodes. GetEntries(path string) ([]string, <-chan zk.Event, error) // CreateParentNodes should try to create the path in case it does not exist // yet on ZooKeeper. CreateParentNodes(path string) error // Register a service with ZooKeeper. Register(s *Service) error // Deregister a service with ZooKeeper. Deregister(s *Service) error // Stop should properly shutdown the client implementation Stop() } type clientConfig struct { logger log.Logger acl []zk.ACL credentials []byte connectTimeout time.Duration sessionTimeout time.Duration rootNodePayload [][]byte eventHandler func(zk.Event) } // Option functions enable friendly APIs. type Option func(*clientConfig) error type client struct { *zk.Conn clientConfig active bool quit chan struct{} } // ACL returns an Option specifying a non-default ACL for creating parent nodes. func ACL(acl []zk.ACL) Option { return func(c *clientConfig) error { c.acl = acl return nil } } // Credentials returns an Option specifying a user/password combination which // the client will use to authenticate itself with. func Credentials(user, pass string) Option { return func(c *clientConfig) error { if user == "" || pass == "" { return ErrInvalidCredentials } c.credentials = []byte(user + ":" + pass) return nil } } // ConnectTimeout returns an Option specifying a non-default connection timeout // when we try to establish a connection to a ZooKeeper server. func ConnectTimeout(t time.Duration) Option { return func(c *clientConfig) error { if t.Seconds() < 1 { return errors.New("invalid connect timeout (minimum value is 1 second)") } c.connectTimeout = t return nil } } // SessionTimeout returns an Option specifying a non-default session timeout. func SessionTimeout(t time.Duration) Option { return func(c *clientConfig) error { if t.Seconds() < 1 { return errors.New("invalid session timeout (minimum value is 1 second)") } c.sessionTimeout = t return nil } } // Payload returns an Option specifying non-default data values for each znode // created by CreateParentNodes. func Payload(payload [][]byte) Option { return func(c *clientConfig) error { c.rootNodePayload = payload return nil } } // EventHandler returns an Option specifying a callback function to handle // incoming zk.Event payloads (ZooKeeper connection events). func EventHandler(handler func(zk.Event)) Option { return func(c *clientConfig) error { c.eventHandler = handler return nil } } // NewClient returns a ZooKeeper client with a connection to the server cluster. // It will return an error if the server cluster cannot be resolved. func NewClient(servers []string, logger log.Logger, options ...Option) (Client, error) { defaultEventHandler := func(event zk.Event) { logger.Log("eventtype", event.Type.String(), "server", event.Server, "state", event.State.String(), "err", event.Err) } config := clientConfig{ acl: DefaultACL, connectTimeout: DefaultConnectTimeout, sessionTimeout: DefaultSessionTimeout, eventHandler: defaultEventHandler, logger: logger, } for _, option := range options { if err := option(&config); err != nil { return nil, err } } // dialer overrides the default ZooKeeper library Dialer so we can configure // the connectTimeout. The current library has a hardcoded value of 1 second // and there are reports of race conditions, due to slow DNS resolvers and // other network latency issues. dialer := func(network, address string, _ time.Duration) (net.Conn, error) { return net.DialTimeout(network, address, config.connectTimeout) } conn, eventc, err := zk.Connect(servers, config.sessionTimeout, withLogger(logger), zk.WithDialer(dialer)) if err != nil { return nil, err } if len(config.credentials) > 0 { err = conn.AddAuth("digest", config.credentials) if err != nil { return nil, err } } c := &client{conn, config, true, make(chan struct{})} // Start listening for incoming Event payloads and callback the set // eventHandler. go func() { for { select { case event := <-eventc: config.eventHandler(event) case <-c.quit: return } } }() return c, nil } // CreateParentNodes implements the ZooKeeper Client interface. func (c *client) CreateParentNodes(path string) error { if !c.active { return ErrClientClosed } if path[0] != '/' { return zk.ErrInvalidPath } payload := []byte("") pathString := "" pathNodes := strings.Split(path, "/") for i := 1; i < len(pathNodes); i++ { if i <= len(c.rootNodePayload) { payload = c.rootNodePayload[i-1] } else { payload = []byte("") } pathString += "/" + pathNodes[i] _, err := c.Create(pathString, payload, 0, c.acl) // not being able to create the node because it exists or not having // sufficient rights is not an issue. It is ok for the node to already // exist and/or us to only have read rights if err != nil && err != zk.ErrNodeExists && err != zk.ErrNoAuth { return err } } return nil } // GetEntries implements the ZooKeeper Client interface. func (c *client) GetEntries(path string) ([]string, <-chan zk.Event, error) { // retrieve list of child nodes for given path and add watch to path znodes, _, eventc, err := c.ChildrenW(path) if err != nil { return nil, eventc, err } var resp []string for _, znode := range znodes { // retrieve payload for child znode and add to response array if data, _, err := c.Get(path + "/" + znode); err == nil { resp = append(resp, string(data)) } } return resp, eventc, nil } // Register implements the ZooKeeper Client interface. func (c *client) Register(s *Service) error { if s.Path[len(s.Path)-1] != '/' { s.Path += "/" } path := s.Path + s.Name if err := c.CreateParentNodes(path); err != nil { return err } if path[len(path)-1] != '/' { path += "/" } node, err := c.CreateProtectedEphemeralSequential(path, s.Data, c.acl) if err != nil { return err } s.node = node return nil } // Deregister implements the ZooKeeper Client interface. func (c *client) Deregister(s *Service) error { if s.node == "" { return ErrNotRegistered } path := s.Path + s.Name found, stat, err := c.Exists(path) if err != nil { return err } if !found { return ErrNodeNotFound } if err := c.Delete(path, stat.Version); err != nil { return err } return nil } // Stop implements the ZooKeeper Client interface. func (c *client) Stop() { c.active = false close(c.quit) c.Close() } golang-github-go-kit-kit-0.13.0/sd/zk/client_test.go000066400000000000000000000075171443521372500222150ustar00rootroot00000000000000package zk import ( "bytes" "testing" "time" stdzk "github.com/go-zookeeper/zk" "github.com/go-kit/log" ) func TestNewClient(t *testing.T) { var ( acl = stdzk.WorldACL(stdzk.PermRead) connectTimeout = 3 * time.Second sessionTimeout = 20 * time.Second payload = [][]byte{[]byte("Payload"), []byte("Test")} ) c, err := NewClient( []string{"FailThisInvalidHost!!!"}, log.NewNopLogger(), ) if err == nil { t.Errorf("expected error, got nil") } hasFired := false calledEventHandler := make(chan struct{}) eventHandler := func(event stdzk.Event) { if !hasFired { // test is successful if this function has fired at least once hasFired = true close(calledEventHandler) } } c, err = NewClient( []string{"localhost"}, log.NewNopLogger(), ACL(acl), ConnectTimeout(connectTimeout), SessionTimeout(sessionTimeout), Payload(payload), EventHandler(eventHandler), ) if err != nil { t.Fatal(err) } defer c.Stop() clientImpl, ok := c.(*client) if !ok { t.Fatal("retrieved incorrect Client implementation") } if want, have := acl, clientImpl.acl; want[0] != have[0] { t.Errorf("want %+v, have %+v", want, have) } if want, have := connectTimeout, clientImpl.connectTimeout; want != have { t.Errorf("want %d, have %d", want, have) } if want, have := sessionTimeout, clientImpl.sessionTimeout; want != have { t.Errorf("want %d, have %d", want, have) } if want, have := payload, clientImpl.rootNodePayload; !bytes.Equal(want[0], have[0]) || !bytes.Equal(want[1], have[1]) { t.Errorf("want %s, have %s", want, have) } select { case <-calledEventHandler: case <-time.After(100 * time.Millisecond): t.Errorf("event handler never called") } } func TestOptions(t *testing.T) { _, err := NewClient([]string{"localhost"}, log.NewNopLogger(), Credentials("valid", "credentials")) if err != nil && err != stdzk.ErrNoServer { t.Errorf("unexpected error: %v", err) } _, err = NewClient([]string{"localhost"}, log.NewNopLogger(), Credentials("nopass", "")) if want, have := err, ErrInvalidCredentials; want != have { t.Errorf("want %v, have %v", want, have) } _, err = NewClient([]string{"localhost"}, log.NewNopLogger(), ConnectTimeout(0)) if err == nil { t.Errorf("expected connect timeout error") } _, err = NewClient([]string{"localhost"}, log.NewNopLogger(), SessionTimeout(0)) if err == nil { t.Errorf("expected connect timeout error") } } func TestCreateParentNodes(t *testing.T) { payload := [][]byte{[]byte("Payload"), []byte("Test")} c, err := NewClient([]string{"localhost:65500"}, log.NewNopLogger()) if err != nil { t.Errorf("unexpected error: %v", err) } if c == nil { t.Fatal("expected new Client, got nil") } s, err := NewInstancer(c, "/validpath", log.NewNopLogger()) if err != stdzk.ErrNoServer { t.Errorf("unexpected error: %v", err) } if s != nil { t.Error("expected failed new Instancer") } s, err = NewInstancer(c, "invalidpath", log.NewNopLogger()) if err != stdzk.ErrInvalidPath { t.Errorf("unexpected error: %v", err) } _, _, err = c.GetEntries("/validpath") if err != stdzk.ErrNoServer { t.Errorf("unexpected error: %v", err) } c.Stop() err = c.CreateParentNodes("/validpath") if err != ErrClientClosed { t.Errorf("unexpected error: %v", err) } s, err = NewInstancer(c, "/validpath", log.NewNopLogger()) if err != ErrClientClosed { t.Errorf("unexpected error: %v", err) } if s != nil { t.Error("expected failed new Instancer") } c, err = NewClient([]string{"localhost:65500"}, log.NewNopLogger(), Payload(payload)) if err != nil { t.Errorf("unexpected error: %v", err) } if c == nil { t.Fatal("expected new Client, got nil") } s, err = NewInstancer(c, "/validpath", log.NewNopLogger()) if err != stdzk.ErrNoServer { t.Errorf("unexpected error: %v", err) } if s != nil { t.Error("expected failed new Instancer") } } golang-github-go-kit-kit-0.13.0/sd/zk/doc.go000066400000000000000000000001311443521372500204260ustar00rootroot00000000000000// Package zk provides Instancer and Registrar implementations for ZooKeeper. package zk golang-github-go-kit-kit-0.13.0/sd/zk/instancer.go000066400000000000000000000045041443521372500216570ustar00rootroot00000000000000package zk import ( "github.com/go-zookeeper/zk" "github.com/go-kit/kit/sd" "github.com/go-kit/kit/sd/internal/instance" "github.com/go-kit/log" ) // Instancer yield instances stored in a certain ZooKeeper path. Any kind of // change in that path is watched and will update the subscribers. type Instancer struct { cache *instance.Cache client Client path string logger log.Logger quitc chan struct{} } // NewInstancer returns a ZooKeeper Instancer. ZooKeeper will start watching // the given path for changes and update the Instancer endpoints. func NewInstancer(c Client, path string, logger log.Logger) (*Instancer, error) { s := &Instancer{ cache: instance.NewCache(), client: c, path: path, logger: logger, quitc: make(chan struct{}), } err := s.client.CreateParentNodes(s.path) if err != nil { return nil, err } instances, eventc, err := s.client.GetEntries(s.path) if err != nil { logger.Log("path", s.path, "msg", "failed to retrieve entries", "err", err) // other implementations continue here, but we exit because we don't know if eventc is valid return nil, err } logger.Log("path", s.path, "instances", len(instances)) s.cache.Update(sd.Event{Instances: instances}) go s.loop(eventc) return s, nil } func (s *Instancer) loop(eventc <-chan zk.Event) { var ( instances []string err error ) for { select { case <-eventc: // We received a path update notification. Call GetEntries to // retrieve child node data, and set a new watch, as ZK watches are // one-time triggers. instances, eventc, err = s.client.GetEntries(s.path) if err != nil { s.logger.Log("path", s.path, "msg", "failed to retrieve entries", "err", err) s.cache.Update(sd.Event{Err: err}) continue } s.logger.Log("path", s.path, "instances", len(instances)) s.cache.Update(sd.Event{Instances: instances}) case <-s.quitc: return } } } // Stop terminates the Instancer. func (s *Instancer) Stop() { close(s.quitc) } // Register implements Instancer. func (s *Instancer) Register(ch chan<- sd.Event) { s.cache.Register(ch) } // Deregister implements Instancer. func (s *Instancer) Deregister(ch chan<- sd.Event) { s.cache.Deregister(ch) } // state returns the current state of instance.Cache, only for testing func (s *Instancer) state() sd.Event { return s.cache.State() } golang-github-go-kit-kit-0.13.0/sd/zk/instancer_test.go000066400000000000000000000056611443521372500227230ustar00rootroot00000000000000package zk import ( "testing" "time" "github.com/go-kit/kit/sd" ) var _ sd.Instancer = (*Instancer)(nil) // API check func TestInstancer(t *testing.T) { client := newFakeClient() instancer, err := NewInstancer(client, path, logger) if err != nil { t.Fatalf("failed to create new Instancer: %v", err) } defer instancer.Stop() endpointer := sd.NewEndpointer(instancer, newFactory(""), logger) if _, err := endpointer.Endpoints(); err != nil { t.Fatal(err) } } func TestBadFactory(t *testing.T) { client := newFakeClient() instancer, err := NewInstancer(client, path, logger) if err != nil { t.Fatalf("failed to create new Instancer: %v", err) } defer instancer.Stop() endpointer := sd.NewEndpointer(instancer, newFactory("kaboom"), logger) // instance1 came online client.AddService(path+"/instance1", "kaboom") // instance2 came online client.AddService(path+"/instance2", "zookeeper_node_data") if err = asyncTest(100*time.Millisecond, 1, endpointer); err != nil { t.Error(err) } } func TestServiceUpdate(t *testing.T) { client := newFakeClient() instancer, err := NewInstancer(client, path, logger) if err != nil { t.Fatalf("failed to create new Instancer: %v", err) } defer instancer.Stop() endpointer := sd.NewEndpointer(instancer, newFactory(""), logger) endpoints, err := endpointer.Endpoints() if err != nil { t.Fatal(err) } if want, have := 0, len(endpoints); want != have { t.Errorf("want %d, have %d", want, have) } // instance1 came online client.AddService(path+"/instance1", "zookeeper_node_data1") // instance2 came online client.AddService(path+"/instance2", "zookeeper_node_data2") // we should have 2 instances if err = asyncTest(100*time.Millisecond, 2, endpointer); err != nil { t.Error(err) } // TODO(pb): this bit is flaky // //// watch triggers an error... //client.SendErrorOnWatch() // //// test if error was consumed //if err = client.ErrorIsConsumedWithin(100 * time.Millisecond); err != nil { // t.Error(err) //} // instance3 came online client.AddService(path+"/instance3", "zookeeper_node_data3") // we should have 3 instances if err = asyncTest(100*time.Millisecond, 3, endpointer); err != nil { t.Error(err) } // instance1 goes offline client.RemoveService(path + "/instance1") // instance2 goes offline client.RemoveService(path + "/instance2") // we should have 1 instance if err = asyncTest(100*time.Millisecond, 1, endpointer); err != nil { t.Error(err) } } func TestBadInstancerCreate(t *testing.T) { client := newFakeClient() client.SendErrorOnWatch() instancer, err := NewInstancer(client, path, logger) if err == nil { t.Error("expected error on new Instancer") } if instancer != nil { t.Error("expected Instancer not to be created") } instancer, err = NewInstancer(client, "BadPath", logger) if err == nil { t.Error("expected error on new Instancer") } if instancer != nil { t.Error("expected Instancer not to be created") } } golang-github-go-kit-kit-0.13.0/sd/zk/integration_test.go000066400000000000000000000115331443521372500232530ustar00rootroot00000000000000// +build integration package zk import ( "bytes" "os" "testing" "time" stdzk "github.com/go-zookeeper/zk" ) var ( host []string ) func TestMain(m *testing.M) { zkAddr := os.Getenv("ZK_ADDR") if zkAddr != "" { host = []string{zkAddr} } m.Run() } func TestCreateParentNodesOnServer(t *testing.T) { if len(host) == 0 { t.Skip("ZK_ADDR not set; skipping integration test") } payload := [][]byte{[]byte("Payload"), []byte("Test")} c1, err := NewClient(host, logger, Payload(payload)) if err != nil { t.Fatalf("Connect returned error: %v", err) } if c1 == nil { t.Fatal("Expected pointer to client, got nil") } defer c1.Stop() instancer, err := NewInstancer(c1, path, logger) if err != nil { t.Fatalf("Unable to create Subscriber: %v", err) } defer instancer.Stop() state := instancer.state() if state.Err != nil { t.Fatal(err) } if want, have := 0, len(state.Instances); want != have { t.Errorf("want %d, have %d", want, have) } c2, err := NewClient(host, logger) if err != nil { t.Fatalf("Connect returned error: %v", err) } defer c2.Stop() data, _, err := c2.(*client).Get(path) if err != nil { t.Fatal(err) } // test Client implementation of CreateParentNodes. It should have created // our payload if bytes.Compare(data, payload[1]) != 0 { t.Errorf("want %s, have %s", payload[1], data) } } func TestCreateBadParentNodesOnServer(t *testing.T) { if len(host) == 0 { t.Skip("ZK_ADDR not set; skipping integration test") } c, _ := NewClient(host, logger) defer c.Stop() _, err := NewInstancer(c, "invalid/path", logger) if want, have := stdzk.ErrInvalidPath, err; want != have { t.Errorf("want %v, have %v", want, have) } } func TestCredentials1(t *testing.T) { if len(host) == 0 { t.Skip("ZK_ADDR not set; skipping integration test") } acl := stdzk.DigestACL(stdzk.PermAll, "user", "secret") c, _ := NewClient(host, logger, ACL(acl), Credentials("user", "secret")) defer c.Stop() _, err := NewInstancer(c, "/acl-issue-test", logger) if err != nil { t.Fatal(err) } } func TestCredentials2(t *testing.T) { if len(host) == 0 { t.Skip("ZK_ADDR not set; skipping integration test") } acl := stdzk.DigestACL(stdzk.PermAll, "user", "secret") c, _ := NewClient(host, logger, ACL(acl)) defer c.Stop() _, err := NewInstancer(c, "/acl-issue-test", logger) if err != stdzk.ErrNoAuth { t.Errorf("want %v, have %v", stdzk.ErrNoAuth, err) } } func TestConnection(t *testing.T) { if len(host) == 0 { t.Skip("ZK_ADDR not set; skipping integration test") } c, _ := NewClient(host, logger) c.Stop() _, err := NewInstancer(c, "/acl-issue-test", logger) if err != ErrClientClosed { t.Errorf("want %v, have %v", ErrClientClosed, err) } } func TestGetEntriesOnServer(t *testing.T) { if len(host) == 0 { t.Skip("ZK_ADDR not set; skipping integration test") } var instancePayload = "10.0.3.204:8002" c1, err := NewClient(host, logger) if err != nil { t.Fatalf("Connect returned error: %v", err) } defer c1.Stop() c2, err := NewClient(host, logger) s, err := NewInstancer(c2, path, logger) if err != nil { t.Fatal(err) } defer c2.Stop() instance1 := &Service{ Path: path, Name: "instance1", Data: []byte(instancePayload), } if err = c2.Register(instance1); err != nil { t.Fatalf("Unable to create test ephemeral znode 1: %+v", err) } instance2 := &Service{ Path: path, Name: "instance2", Data: []byte(instancePayload), } if err = c2.Register(instance2); err != nil { t.Fatalf("Unable to create test ephemeral znode 2: %+v", err) } time.Sleep(50 * time.Millisecond) state := s.state() if state.Err != nil { t.Fatal(state.Err) } if want, have := 2, len(state.Instances); want != have { t.Errorf("want %d, have %d", want, have) } } func TestGetEntriesPayloadOnServer(t *testing.T) { t.Skip("FLAKY") if len(host) == 0 { t.Skip("ZK_ADDR not set; skipping integration test") } c, err := NewClient(host, logger) if err != nil { t.Fatalf("Connect returned error: %v", err) } _, eventc, err := c.GetEntries(path) if err != nil { t.Fatal(err) } instance3 := Service{ Path: path, Name: "instance3", Data: []byte("just some payload"), } registrar := NewRegistrar(c, instance3, logger) registrar.Register() select { case event := <-eventc: if want, have := stdzk.EventNodeChildrenChanged.String(), event.Type.String(); want != have { t.Errorf("want %s, have %s", want, have) } case <-time.After(10 * time.Second): t.Errorf("expected incoming watch event, timeout occurred") } _, eventc, err = c.GetEntries(path) if err != nil { t.Fatal(err) } registrar.Deregister() select { case event := <-eventc: if want, have := stdzk.EventNodeChildrenChanged.String(), event.Type.String(); want != have { t.Errorf("want %s, have %s", want, have) } case <-time.After(100 * time.Millisecond): t.Errorf("expected incoming watch event, timeout occurred") } } golang-github-go-kit-kit-0.13.0/sd/zk/logwrapper.go000066400000000000000000000011611443521372500220470ustar00rootroot00000000000000package zk import ( "fmt" "github.com/go-zookeeper/zk" "github.com/go-kit/log" ) // wrapLogger wraps a Go kit logger so we can use it as the logging service for // the ZooKeeper library, which expects a Printf method to be available. type wrapLogger struct { log.Logger } func (logger wrapLogger) Printf(format string, args ...interface{}) { logger.Log("msg", fmt.Sprintf(format, args...)) } // withLogger replaces the ZooKeeper library's default logging service with our // own Go kit logger. func withLogger(logger log.Logger) func(c *zk.Conn) { return func(c *zk.Conn) { c.SetLogger(wrapLogger{logger}) } } golang-github-go-kit-kit-0.13.0/sd/zk/registrar.go000066400000000000000000000026001443521372500216660ustar00rootroot00000000000000package zk import "github.com/go-kit/log" // Registrar registers service instance liveness information to ZooKeeper. type Registrar struct { client Client service Service logger log.Logger } // Service holds the root path, service name and instance identifying data you // want to publish to ZooKeeper. type Service struct { Path string // discovery namespace, example: /myorganization/myplatform/ Name string // service name, example: addscv Data []byte // instance data to store for discovery, example: 10.0.2.10:80 node string // Client will record the ephemeral node name so we can deregister } // NewRegistrar returns a ZooKeeper Registrar acting on the provided catalog // registration. func NewRegistrar(client Client, service Service, logger log.Logger) *Registrar { return &Registrar{ client: client, service: service, logger: log.With(logger, "service", service.Name, "path", service.Path, "data", string(service.Data), ), } } // Register implements sd.Registrar interface. func (r *Registrar) Register() { if err := r.client.Register(&r.service); err != nil { r.logger.Log("err", err) } else { r.logger.Log("action", "register") } } // Deregister implements sd.Registrar interface. func (r *Registrar) Deregister() { if err := r.client.Deregister(&r.service); err != nil { r.logger.Log("err", err) } else { r.logger.Log("action", "deregister") } } golang-github-go-kit-kit-0.13.0/sd/zk/util_test.go000066400000000000000000000050271443521372500217060ustar00rootroot00000000000000package zk import ( "errors" "fmt" "io" "sync" "time" "github.com/go-zookeeper/zk" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/sd" "github.com/go-kit/log" ) var ( path = "/gokit.test/service.name" logger = log.NewNopLogger() ) type fakeClient struct { mtx sync.Mutex ch chan zk.Event responses map[string]string result bool } func newFakeClient() *fakeClient { return &fakeClient{ ch: make(chan zk.Event, 1), responses: make(map[string]string), result: true, } } func (c *fakeClient) CreateParentNodes(path string) error { if path == "BadPath" { return errors.New("dummy error") } return nil } func (c *fakeClient) GetEntries(path string) ([]string, <-chan zk.Event, error) { c.mtx.Lock() defer c.mtx.Unlock() if c.result == false { c.result = true return []string{}, c.ch, errors.New("dummy error") } responses := []string{} for _, data := range c.responses { responses = append(responses, data) } return responses, c.ch, nil } func (c *fakeClient) AddService(node, data string) { c.mtx.Lock() defer c.mtx.Unlock() c.responses[node] = data c.ch <- zk.Event{} } func (c *fakeClient) RemoveService(node string) { c.mtx.Lock() defer c.mtx.Unlock() delete(c.responses, node) c.ch <- zk.Event{} } func (c *fakeClient) Register(s *Service) error { return nil } func (c *fakeClient) Deregister(s *Service) error { return nil } func (c *fakeClient) SendErrorOnWatch() { c.mtx.Lock() defer c.mtx.Unlock() c.result = false c.ch <- zk.Event{} } func (c *fakeClient) ErrorIsConsumedWithin(timeout time.Duration) error { t := time.After(timeout) for { select { case <-t: return fmt.Errorf("expected error not consumed after timeout %s", timeout) default: c.mtx.Lock() if c.result == false { c.mtx.Unlock() return nil } c.mtx.Unlock() } } } func (c *fakeClient) Stop() {} func newFactory(fakeError string) sd.Factory { return func(instance string) (endpoint.Endpoint, io.Closer, error) { if fakeError == instance { return nil, nil, errors.New(fakeError) } return endpoint.Nop, nil, nil } } func asyncTest(timeout time.Duration, want int, s sd.Endpointer) (err error) { var endpoints []endpoint.Endpoint have := -1 // want can never be <0 t := time.After(timeout) for { select { case <-t: return fmt.Errorf("want %d, have %d (timeout %s)", want, have, timeout.String()) default: endpoints, err = s.Endpoints() have = len(endpoints) if err != nil || want == have { return } time.Sleep(timeout / 10) } } } golang-github-go-kit-kit-0.13.0/tracing/000077500000000000000000000000001443521372500177345ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/tracing/README.md000066400000000000000000000076201443521372500212200ustar00rootroot00000000000000# package tracing `package tracing` provides [Dapper]-style request tracing to services. ## Rationale Request tracing is a fundamental building block for large distributed applications. It's instrumental in understanding request flows, identifying hot spots, and diagnosing errors. All microservice infrastructures will benefit from request tracing; sufficiently large infrastructures will require it. ## Zipkin [Zipkin] is one of the most used OSS distributed tracing platforms available with support for many different languages and frameworks. Go kit provides bindings to the native Go tracing implementation [zipkin-go]. If using Zipkin with Go kit in a polyglot microservices environment, this is the preferred binding to use. Instrumentation exists for `kit/transport/http` and `kit/transport/grpc`. The bindings are highlighted in the [addsvc] example. For more information regarding Zipkin feel free to visit [Zipkin's Gitter]. ## OpenCensus Go kit supports transport and endpoint middlewares for the [OpenCensus] instrumentation library. OpenCensus provides a cross language consistent data model and instrumentation libraries for tracing and metrics. From this data model it allows exports to various tracing and metrics backends including but not limited to Zipkin, Prometheus, Stackdriver Trace & Monitoring, Jaeger, AWS X-Ray and Datadog. Go kit uses the [opencensus-go] implementation to power its middlewares. ## OpenTracing Go kit supports the [OpenTracing] API and uses the [opentracing-go] package to provide tracing middlewares for its servers and clients. Currently OpenTracing instrumentation exists for `kit/transport/http` and `kit/transport/grpc`. Since [OpenTracing] is an effort to provide a generic API, Go kit should support a multitude of tracing backends. If a Tracer implementation or OpenTracing bridge in Go for your back-end exists, it should work out of the box. Please note that the "world view" of existing tracing systems do differ. OpenTracing can not guarantee you that tracing alignment is perfect in a microservice environment especially one which is not exclusively OpenTracing enabled or switching from one tracing backend to another truly entails just a change in configuration. The following tracing back-ends are known to work with Go kit through the OpenTracing interface and are highlighted in the [addsvc] example. ### AppDash [Appdash] support is available straight from their system repository in the [appdash/opentracing] directory. ### LightStep [LightStep] support is available through their standard Go package [lightstep-tracer-go]. ### Zipkin [Zipkin] support is available through the [zipkin-go-opentracing] package. ## OpenTelemetry [OpenTelemetry] came to life as a result of merging [OpenCensus] and [OpenTracing]. Go kit instrumentation can be found in [opentelemetry-go-contrib] which is a central repository of instrumentation libraries. [Dapper]: http://research.google.com/pubs/pub36356.html [addsvc]: https://github.com/go-kit/examples/tree/master/addsvc [README]: https://github.com/go-kit/kit/blob/master/tracing/zipkin/README.md [OpenTracing]: http://opentracing.io [opentracing-go]: https://github.com/opentracing/opentracing-go [Zipkin]: http://zipkin.io/ [Open Zipkin GitHub]: https://github.com/openzipkin [zipkin-go-opentracing]: https://github.com/openzipkin-contrib/zipkin-go-opentracing [zipkin-go]: https://github.com/openzipkin/zipkin-go [Zipkin's Gitter]: https://gitter.im/openzipkin/zipkin [Appdash]: https://github.com/sourcegraph/appdash [appdash/opentracing]: https://github.com/sourcegraph/appdash/tree/master/opentracing [LightStep]: http://lightstep.com/ [lightstep-tracer-go]: https://github.com/lightstep/lightstep-tracer-go [OpenCensus]: https://opencensus.io/ [opencensus-go]: https://github.com/census-instrumentation/opencensus-go [OpenTelemetry]: https://opentelemetry.io/ [opentelemetry-go-contrib]: https://github.com/open-telemetry/opentelemetry-go-contrib golang-github-go-kit-kit-0.13.0/tracing/doc.go000066400000000000000000000005621443521372500210330ustar00rootroot00000000000000// Package tracing provides helpers and bindings for distributed tracing. // // As your infrastructure grows, it becomes important to be able to trace a // request, as it travels through multiple services and back to the user. // Package tracing provides endpoints and transport helpers and middlewares to // capture and emit request-scoped information. package tracing golang-github-go-kit-kit-0.13.0/tracing/opencensus/000077500000000000000000000000001443521372500221165ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/tracing/opencensus/doc.go000066400000000000000000000005351443521372500232150ustar00rootroot00000000000000// Package opencensus provides Go kit integration to the OpenCensus project. // OpenCensus is a single distribution of libraries for metrics and distributed // tracing with minimal overhead that allows you to export data to multiple // backends. The Go kit OpenCencus package as provided here contains middlewares // for tracing. package opencensus golang-github-go-kit-kit-0.13.0/tracing/opencensus/endpoint.go000066400000000000000000000052711443521372500242720ustar00rootroot00000000000000package opencensus import ( "context" "strconv" "go.opencensus.io/trace" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/sd/lb" ) // TraceEndpointDefaultName is the default endpoint span name to use. const TraceEndpointDefaultName = "gokit/endpoint" // TraceEndpoint returns an Endpoint middleware, tracing a Go kit endpoint. // This endpoint tracer should be used in combination with a Go kit Transport // tracing middleware, generic OpenCensus transport middleware or custom before // and after transport functions as service propagation of SpanContext is not // provided in this middleware. func TraceEndpoint(name string, options ...EndpointOption) endpoint.Middleware { if name == "" { name = TraceEndpointDefaultName } cfg := &EndpointOptions{} for _, o := range options { o(cfg) } return func(next endpoint.Endpoint) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { if cfg.GetName != nil { if newName := cfg.GetName(ctx, name); newName != "" { name = newName } } ctx, span := trace.StartSpan(ctx, name) if len(cfg.Attributes) > 0 { span.AddAttributes(cfg.Attributes...) } defer span.End() if cfg.GetAttributes != nil { if attrs := cfg.GetAttributes(ctx); len(attrs) > 0 { span.AddAttributes(attrs...) } } defer func() { if err != nil { if lberr, ok := err.(lb.RetryError); ok { // handle errors originating from lb.Retry attrs := make([]trace.Attribute, 0, len(lberr.RawErrors)) for idx, rawErr := range lberr.RawErrors { attrs = append(attrs, trace.StringAttribute( "gokit.retry.error."+strconv.Itoa(idx+1), rawErr.Error(), )) } span.AddAttributes(attrs...) span.SetStatus(trace.Status{ Code: trace.StatusCodeUnknown, Message: lberr.Final.Error(), }) return } // generic error span.SetStatus(trace.Status{ Code: trace.StatusCodeUnknown, Message: err.Error(), }) return } // test for business error if res, ok := response.(endpoint.Failer); ok && res.Failed() != nil { span.AddAttributes( trace.StringAttribute("gokit.business.error", res.Failed().Error()), ) if cfg.IgnoreBusinessError { span.SetStatus(trace.Status{Code: trace.StatusCodeOK}) return } // treating business error as real error in span. span.SetStatus(trace.Status{ Code: trace.StatusCodeUnknown, Message: res.Failed().Error(), }) return } // no errors identified span.SetStatus(trace.Status{Code: trace.StatusCodeOK}) }() response, err = next(ctx, request) return } } } golang-github-go-kit-kit-0.13.0/tracing/opencensus/endpoint_options.go000066400000000000000000000043541443521372500260460ustar00rootroot00000000000000package opencensus import ( "context" "go.opencensus.io/trace" ) // EndpointOptions holds the options for tracing an endpoint type EndpointOptions struct { // IgnoreBusinessError if set to true will not treat a business error // identified through the endpoint.Failer interface as a span error. IgnoreBusinessError bool // Attributes holds the default attributes which will be set on span // creation by our Endpoint middleware. Attributes []trace.Attribute // GetName is an optional function that can set the span name based on the existing name // for the endpoint and information in the context. // // If the function is nil, or the returned name is empty, the existing name for the endpoint is used. GetName func(ctx context.Context, name string) string // GetAttributes is an optional function that can extract trace attributes // from the context and add them to the span. GetAttributes func(ctx context.Context) []trace.Attribute } // EndpointOption allows for functional options to our OpenCensus endpoint // tracing middleware. type EndpointOption func(*EndpointOptions) // WithEndpointConfig sets all configuration options at once by use of the // EndpointOptions struct. func WithEndpointConfig(options EndpointOptions) EndpointOption { return func(o *EndpointOptions) { *o = options } } // WithEndpointAttributes sets the default attributes for the spans created by // the Endpoint tracer. func WithEndpointAttributes(attrs ...trace.Attribute) EndpointOption { return func(o *EndpointOptions) { o.Attributes = attrs } } // WithIgnoreBusinessError if set to true will not treat a business error // identified through the endpoint.Failer interface as a span error. func WithIgnoreBusinessError(val bool) EndpointOption { return func(o *EndpointOptions) { o.IgnoreBusinessError = val } } // WithSpanName extracts additional attributes from the request context. func WithSpanName(fn func(ctx context.Context, name string) string) EndpointOption { return func(o *EndpointOptions) { o.GetName = fn } } // WithSpanAttributes extracts additional attributes from the request context. func WithSpanAttributes(fn func(ctx context.Context) []trace.Attribute) EndpointOption { return func(o *EndpointOptions) { o.GetAttributes = fn } } golang-github-go-kit-kit-0.13.0/tracing/opencensus/endpoint_test.go000066400000000000000000000114141443521372500253250ustar00rootroot00000000000000package opencensus_test import ( "context" "errors" "testing" "time" "go.opencensus.io/trace" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/sd" "github.com/go-kit/kit/sd/lb" "github.com/go-kit/kit/tracing/opencensus" ) const ( span1 = "" span2 = "SPAN-2" span3 = "SPAN-3" span4 = "SPAN-4" span5 = "SPAN-5" span6 = "SPAN-6" ) var ( err1 = errors.New("some error") err2 = errors.New("other error") err3 = errors.New("some business error") err4 = errors.New("other business error") ) // compile time assertion var _ endpoint.Failer = failedResponse{} type failedResponse struct { err error } func (r failedResponse) Failed() error { return r.err } func passEndpoint(_ context.Context, req interface{}) (interface{}, error) { if err, _ := req.(error); err != nil { return nil, err } return req, nil } func TestTraceEndpoint(t *testing.T) { ctx := context.Background() e := &recordingExporter{} trace.RegisterExporter(e) trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()}) // span 1 span1Attrs := []trace.Attribute{ trace.StringAttribute("string", "value"), trace.Int64Attribute("int64", 42), } mw := opencensus.TraceEndpoint( span1, opencensus.WithEndpointAttributes(span1Attrs...), ) mw(endpoint.Nop)(ctx, nil) // span 2 opts := opencensus.EndpointOptions{} mw = opencensus.TraceEndpoint(span2, opencensus.WithEndpointConfig(opts)) mw(passEndpoint)(ctx, err1) // span3 mw = opencensus.TraceEndpoint(span3) ep := lb.Retry(5, 1*time.Second, lb.NewRoundRobin(sd.FixedEndpointer{passEndpoint})) mw(ep)(ctx, err2) // span4 mw = opencensus.TraceEndpoint(span4) mw(passEndpoint)(ctx, failedResponse{err: err3}) // span5 mw = opencensus.TraceEndpoint(span5, opencensus.WithIgnoreBusinessError(true)) mw(passEndpoint)(ctx, failedResponse{err: err4}) // span6 span6Attrs := []trace.Attribute{ trace.StringAttribute("string", "value"), trace.Int64Attribute("int64", 42), } mw = opencensus.TraceEndpoint( "", opencensus.WithSpanName(func(ctx context.Context, name string) string { return span6 }), opencensus.WithSpanAttributes(func(ctx context.Context) []trace.Attribute { return span6Attrs }), ) mw(endpoint.Nop)(ctx, nil) // check span count spans := e.Flush() if want, have := 6, len(spans); want != have { t.Fatalf("incorrected number of spans, wanted %d, got %d", want, have) } // test span 1 span := spans[0] if want, have := int32(trace.StatusCodeOK), span.Code; want != have { t.Errorf("incorrect status code, wanted %d, got %d", want, have) } if want, have := opencensus.TraceEndpointDefaultName, span.Name; want != have { t.Errorf("incorrect span name, wanted %q, got %q", want, have) } if want, have := 2, len(span.Attributes); want != have { t.Fatalf("incorrect attribute count, wanted %d, got %d", want, have) } // test span 2 span = spans[1] if want, have := int32(trace.StatusCodeUnknown), span.Code; want != have { t.Errorf("incorrect status code, wanted %d, got %d", want, have) } if want, have := span2, span.Name; want != have { t.Errorf("incorrect span name, wanted %q, got %q", want, have) } if want, have := 0, len(span.Attributes); want != have { t.Fatalf("incorrect attribute count, wanted %d, got %d", want, have) } // test span 3 span = spans[2] if want, have := int32(trace.StatusCodeUnknown), span.Code; want != have { t.Errorf("incorrect status code, wanted %d, got %d", want, have) } if want, have := span3, span.Name; want != have { t.Errorf("incorrect span name, wanted %q, got %q", want, have) } if want, have := 5, len(span.Attributes); want != have { t.Fatalf("incorrect attribute count, wanted %d, got %d", want, have) } // test span 4 span = spans[3] if want, have := int32(trace.StatusCodeUnknown), span.Code; want != have { t.Errorf("incorrect status code, wanted %d, got %d", want, have) } if want, have := span4, span.Name; want != have { t.Errorf("incorrect span name, wanted %q, got %q", want, have) } if want, have := 1, len(span.Attributes); want != have { t.Fatalf("incorrect attribute count, wanted %d, got %d", want, have) } // test span 5 span = spans[4] if want, have := int32(trace.StatusCodeOK), span.Code; want != have { t.Errorf("incorrect status code, wanted %d, got %d", want, have) } if want, have := span5, span.Name; want != have { t.Errorf("incorrect span name, wanted %q, got %q", want, have) } if want, have := 1, len(span.Attributes); want != have { t.Fatalf("incorrect attribute count, wanted %d, got %d", want, have) } // test span 6 span = spans[5] if want, have := span6, span.Name; want != have { t.Errorf("incorrect span name, wanted %q, got %q", want, have) } if want, have := 2, len(span.Attributes); want != have { t.Fatalf("incorrect attribute count, wanted %d, got %d", want, have) } } golang-github-go-kit-kit-0.13.0/tracing/opencensus/grpc.go000066400000000000000000000067031443521372500234060ustar00rootroot00000000000000package opencensus import ( "context" "go.opencensus.io/trace" "go.opencensus.io/trace/propagation" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" kitgrpc "github.com/go-kit/kit/transport/grpc" ) const propagationKey = "grpc-trace-bin" // GRPCClientTrace enables OpenCensus tracing of a Go kit gRPC transport client. func GRPCClientTrace(options ...TracerOption) kitgrpc.ClientOption { cfg := TracerOptions{} for _, option := range options { option(&cfg) } clientBefore := kitgrpc.ClientBefore( func(ctx context.Context, md *metadata.MD) context.Context { var name string if cfg.Name != "" { name = cfg.Name } else { name = ctx.Value(kitgrpc.ContextKeyRequestMethod).(string) } ctx, span := trace.StartSpan( ctx, name, trace.WithSampler(cfg.Sampler), trace.WithSpanKind(trace.SpanKindClient), ) if !cfg.Public { traceContextBinary := string(propagation.Binary(span.SpanContext())) (*md)[propagationKey] = append((*md)[propagationKey], traceContextBinary) } return ctx }, ) clientFinalizer := kitgrpc.ClientFinalizer( func(ctx context.Context, err error) { if span := trace.FromContext(ctx); span != nil { if s, ok := status.FromError(err); ok { span.SetStatus(trace.Status{Code: int32(s.Code()), Message: s.Message()}) } else { span.SetStatus(trace.Status{Code: int32(codes.Unknown), Message: err.Error()}) } span.End() } }, ) return func(c *kitgrpc.Client) { clientBefore(c) clientFinalizer(c) } } // GRPCServerTrace enables OpenCensus tracing of a Go kit gRPC transport server. func GRPCServerTrace(options ...TracerOption) kitgrpc.ServerOption { cfg := TracerOptions{} for _, option := range options { option(&cfg) } serverBefore := kitgrpc.ServerBefore( func(ctx context.Context, md metadata.MD) context.Context { var name string if cfg.Name != "" { name = cfg.Name } else { name, _ = ctx.Value(kitgrpc.ContextKeyRequestMethod).(string) if name == "" { // we can't find the gRPC method. probably the // unaryInterceptor was not wired up. name = "unknown grpc method" } } var ( parentContext trace.SpanContext traceContext = md[propagationKey] ok bool ) if len(traceContext) > 0 { traceContextBinary := []byte(traceContext[0]) parentContext, ok = propagation.FromBinary(traceContextBinary) if ok && !cfg.Public { ctx, _ = trace.StartSpanWithRemoteParent( ctx, name, parentContext, trace.WithSpanKind(trace.SpanKindServer), trace.WithSampler(cfg.Sampler), ) return ctx } } ctx, span := trace.StartSpan( ctx, name, trace.WithSpanKind(trace.SpanKindServer), trace.WithSampler(cfg.Sampler), ) if ok { span.AddLink( trace.Link{ TraceID: parentContext.TraceID, SpanID: parentContext.SpanID, Type: trace.LinkTypeChild, }, ) } return ctx }, ) serverFinalizer := kitgrpc.ServerFinalizer( func(ctx context.Context, err error) { if span := trace.FromContext(ctx); span != nil { if s, ok := status.FromError(err); ok { span.SetStatus(trace.Status{Code: int32(s.Code()), Message: s.Message()}) } else { span.SetStatus(trace.Status{Code: int32(codes.Internal), Message: err.Error()}) } span.End() } }, ) return func(s *kitgrpc.Server) { serverBefore(s) serverFinalizer(s) } } golang-github-go-kit-kit-0.13.0/tracing/opencensus/grpc_test.go000066400000000000000000000111751443521372500244440ustar00rootroot00000000000000package opencensus_test import ( "context" "errors" "testing" "go.opencensus.io/trace" "go.opencensus.io/trace/propagation" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "github.com/go-kit/kit/endpoint" ockit "github.com/go-kit/kit/tracing/opencensus" grpctransport "github.com/go-kit/kit/transport/grpc" ) type dummy struct{} const traceContextKey = "grpc-trace-bin" func unaryInterceptor( ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption, ) error { return nil } func TestGRPCClientTrace(t *testing.T) { rec := &recordingExporter{} trace.RegisterExporter(rec) cc, err := grpc.Dial( "", grpc.WithUnaryInterceptor(unaryInterceptor), grpc.WithInsecure(), ) if err != nil { t.Fatalf("unable to create gRPC dialer: %s", err.Error()) } traces := []struct { name string err error }{ {"", nil}, {"CustomName", nil}, {"", errors.New("dummy-error")}, } for _, tr := range traces { clientTracer := ockit.GRPCClientTrace( ockit.WithName(tr.name), ockit.WithSampler(trace.AlwaysSample()), ) ep := grpctransport.NewClient( cc, "dummyService", "dummyMethod", func(context.Context, interface{}) (interface{}, error) { return nil, nil }, func(context.Context, interface{}) (interface{}, error) { return nil, tr.err }, dummy{}, clientTracer, ).Endpoint() ctx, parentSpan := trace.StartSpan(context.Background(), "test") _, err = ep(ctx, nil) if want, have := tr.err, err; want != have { t.Fatalf("unexpected error, want %s, have %s", tr.err.Error(), err.Error()) } spans := rec.Flush() if want, have := 1, len(spans); want != have { t.Fatalf("incorrect number of spans, want %d, have %d", want, have) } span := spans[0] if want, have := parentSpan.SpanContext().SpanID, span.ParentSpanID; want != have { t.Errorf("incorrect parent ID, want %s, have %s", want, have) } if want, have := tr.name, span.Name; want != have && want != "" { t.Errorf("incorrect span name, want %s, have %s", want, have) } if want, have := "/dummyService/dummyMethod", span.Name; want != have && tr.name == "" { t.Errorf("incorrect span name, want %s, have %s", want, have) } code := trace.StatusCodeOK if tr.err != nil { code = trace.StatusCodeUnknown if want, have := err.Error(), span.Status.Message; want != have { t.Errorf("incorrect span status msg, want %s, have %s", want, have) } } if want, have := int32(code), span.Status.Code; want != have { t.Errorf("incorrect span status code, want %d, have %d", want, have) } } } func TestGRPCServerTrace(t *testing.T) { rec := &recordingExporter{} trace.RegisterExporter(rec) traces := []struct { useParent bool name string err error }{ {false, "", nil}, {true, "", nil}, {true, "CustomName", nil}, {true, "", errors.New("dummy-error")}, } for _, tr := range traces { var ( ctx = context.Background() parentSpan *trace.Span ) server := grpctransport.NewServer( endpoint.Nop, func(context.Context, interface{}) (interface{}, error) { return nil, nil }, func(context.Context, interface{}) (interface{}, error) { return nil, tr.err }, ockit.GRPCServerTrace( ockit.WithName(tr.name), ockit.WithSampler(trace.AlwaysSample()), ), ) if tr.useParent { _, parentSpan = trace.StartSpan(context.Background(), "test") traceContextBinary := propagation.Binary(parentSpan.SpanContext()) md := metadata.MD{} md.Set(traceContextKey, string(traceContextBinary)) ctx = metadata.NewIncomingContext(ctx, md) } server.ServeGRPC(ctx, nil) spans := rec.Flush() if want, have := 1, len(spans); want != have { t.Fatalf("incorrect number of spans, want %d, have %d", want, have) } if tr.useParent { if want, have := parentSpan.SpanContext().TraceID, spans[0].TraceID; want != have { t.Errorf("incorrect trace ID, want %s, have %s", want, have) } if want, have := parentSpan.SpanContext().SpanID, spans[0].ParentSpanID; want != have { t.Errorf("incorrect span ID, want %s, have %s", want, have) } } if want, have := tr.name, spans[0].Name; want != have && want != "" { t.Errorf("incorrect span name, want %s, have %s", want, have) } if tr.err != nil { if want, have := int32(codes.Internal), spans[0].Status.Code; want != have { t.Errorf("incorrect span status code, want %d, have %d", want, have) } if want, have := tr.err.Error(), spans[0].Status.Message; want != have { t.Errorf("incorrect span status message, want %s, have %s", want, have) } } } } golang-github-go-kit-kit-0.13.0/tracing/opencensus/http.go000066400000000000000000000100041443521372500234170ustar00rootroot00000000000000package opencensus import ( "context" "net/http" "go.opencensus.io/plugin/ochttp" "go.opencensus.io/plugin/ochttp/propagation/b3" "go.opencensus.io/trace" kithttp "github.com/go-kit/kit/transport/http" ) // HTTPClientTrace enables OpenCensus tracing of a Go kit HTTP transport client. func HTTPClientTrace(options ...TracerOption) kithttp.ClientOption { cfg := TracerOptions{} for _, option := range options { option(&cfg) } if !cfg.Public && cfg.HTTPPropagate == nil { cfg.HTTPPropagate = &b3.HTTPFormat{} } clientBefore := kithttp.ClientBefore( func(ctx context.Context, req *http.Request) context.Context { var name string if cfg.Name != "" { name = cfg.Name } else { // OpenCensus states Path being default naming for a client span name = req.Method + " " + req.URL.Path } ctx, span := trace.StartSpan( ctx, name, trace.WithSampler(cfg.Sampler), trace.WithSpanKind(trace.SpanKindClient), ) span.AddAttributes( trace.StringAttribute(ochttp.HostAttribute, req.URL.Host), trace.StringAttribute(ochttp.MethodAttribute, req.Method), trace.StringAttribute(ochttp.PathAttribute, req.URL.Path), trace.StringAttribute(ochttp.UserAgentAttribute, req.UserAgent()), ) if !cfg.Public { cfg.HTTPPropagate.SpanContextToRequest(span.SpanContext(), req) } return ctx }, ) clientAfter := kithttp.ClientAfter( func(ctx context.Context, res *http.Response) context.Context { if span := trace.FromContext(ctx); span != nil { span.SetStatus(ochttp.TraceStatus(res.StatusCode, http.StatusText(res.StatusCode))) span.AddAttributes( trace.Int64Attribute(ochttp.StatusCodeAttribute, int64(res.StatusCode)), ) } return ctx }, ) clientFinalizer := kithttp.ClientFinalizer( func(ctx context.Context, err error) { if span := trace.FromContext(ctx); span != nil { if err != nil { span.SetStatus(trace.Status{ Code: trace.StatusCodeUnknown, Message: err.Error(), }) } span.End() } }, ) return func(c *kithttp.Client) { clientBefore(c) clientAfter(c) clientFinalizer(c) } } // HTTPServerTrace enables OpenCensus tracing of a Go kit HTTP transport server. func HTTPServerTrace(options ...TracerOption) kithttp.ServerOption { cfg := TracerOptions{} for _, option := range options { option(&cfg) } if !cfg.Public && cfg.HTTPPropagate == nil { cfg.HTTPPropagate = &b3.HTTPFormat{} } serverBefore := kithttp.ServerBefore( func(ctx context.Context, req *http.Request) context.Context { var ( spanContext trace.SpanContext span *trace.Span name string ok bool ) if cfg.Name != "" { name = cfg.Name } else { name = req.Method + " " + req.URL.Path } spanContext, ok = cfg.HTTPPropagate.SpanContextFromRequest(req) if ok && !cfg.Public { ctx, span = trace.StartSpanWithRemoteParent( ctx, name, spanContext, trace.WithSpanKind(trace.SpanKindServer), trace.WithSampler(cfg.Sampler), ) } else { ctx, span = trace.StartSpan( ctx, name, trace.WithSpanKind(trace.SpanKindServer), trace.WithSampler(cfg.Sampler), ) if ok { span.AddLink(trace.Link{ TraceID: spanContext.TraceID, SpanID: spanContext.SpanID, Type: trace.LinkTypeChild, Attributes: nil, }) } } span.AddAttributes( trace.StringAttribute(ochttp.MethodAttribute, req.Method), trace.StringAttribute(ochttp.PathAttribute, req.URL.Path), ) return ctx }, ) serverFinalizer := kithttp.ServerFinalizer( func(ctx context.Context, code int, r *http.Request) { if span := trace.FromContext(ctx); span != nil { span.SetStatus(ochttp.TraceStatus(code, http.StatusText(code))) if rs, ok := ctx.Value(kithttp.ContextKeyResponseSize).(int64); ok { span.AddAttributes( trace.Int64Attribute("http.response_size", rs), ) } span.End() } }, ) return func(s *kithttp.Server) { serverBefore(s) serverFinalizer(s) } } golang-github-go-kit-kit-0.13.0/tracing/opencensus/http_test.go000066400000000000000000000111061443521372500244620ustar00rootroot00000000000000package opencensus_test import ( "context" "errors" "net/http" "net/http/httptest" "net/url" "testing" "go.opencensus.io/plugin/ochttp" "go.opencensus.io/plugin/ochttp/propagation/b3" "go.opencensus.io/plugin/ochttp/propagation/tracecontext" "go.opencensus.io/trace" "go.opencensus.io/trace/propagation" "github.com/go-kit/kit/endpoint" ockit "github.com/go-kit/kit/tracing/opencensus" kithttp "github.com/go-kit/kit/transport/http" ) func TestHTTPClientTrace(t *testing.T) { var ( err error rec = &recordingExporter{} rURL, _ = url.Parse("https://httpbin.org/get") ) trace.RegisterExporter(rec) traces := []struct { name string err error }{ {"", nil}, {"CustomName", nil}, {"", errors.New("dummy-error")}, } for _, tr := range traces { clientTracer := ockit.HTTPClientTrace( ockit.WithName(tr.name), ockit.WithSampler(trace.AlwaysSample()), ) ep := kithttp.NewClient( "GET", rURL, func(ctx context.Context, r *http.Request, i interface{}) error { return nil }, func(ctx context.Context, r *http.Response) (response interface{}, err error) { return nil, tr.err }, clientTracer, ).Endpoint() ctx, parentSpan := trace.StartSpan(context.Background(), "test") _, err = ep(ctx, nil) if want, have := tr.err, err; want != have { t.Fatalf("unexpected error, want %s, have %s", tr.err.Error(), err.Error()) } spans := rec.Flush() if want, have := 1, len(spans); want != have { t.Fatalf("incorrect number of spans, want %d, have %d", want, have) } span := spans[0] if want, have := parentSpan.SpanContext().SpanID, span.ParentSpanID; want != have { t.Errorf("incorrect parent ID, want %s, have %s", want, have) } if want, have := tr.name, span.Name; want != have && want != "" { t.Errorf("incorrect span name, want %s, have %s", want, have) } if want, have := "GET /get", span.Name; want != have && tr.name == "" { t.Errorf("incorrect span name, want %s, have %s", want, have) } code := trace.StatusCodeOK if tr.err != nil { code = trace.StatusCodeUnknown if want, have := err.Error(), span.Status.Message; want != have { t.Errorf("incorrect span status msg, want %s, have %s", want, have) } } if want, have := int32(code), span.Status.Code; want != have { t.Errorf("incorrect span status code, want %d, have %d", want, have) } } } func TestHTTPServerTrace(t *testing.T) { rec := &recordingExporter{} trace.RegisterExporter(rec) traces := []struct { useParent bool name string err error propagation propagation.HTTPFormat }{ {false, "", nil, nil}, {true, "", nil, nil}, {true, "CustomName", nil, &b3.HTTPFormat{}}, {true, "", errors.New("dummy-error"), &tracecontext.HTTPFormat{}}, } for _, tr := range traces { var client http.Client handler := kithttp.NewServer( endpoint.Nop, func(context.Context, *http.Request) (interface{}, error) { return nil, nil }, func(context.Context, http.ResponseWriter, interface{}) error { return errors.New("dummy") }, ockit.HTTPServerTrace( ockit.WithName(tr.name), ockit.WithSampler(trace.AlwaysSample()), ockit.WithHTTPPropagation(tr.propagation), ), ) server := httptest.NewServer(handler) defer server.Close() const httpMethod = "GET" req, err := http.NewRequest(httpMethod, server.URL, nil) if err != nil { t.Fatalf("unable to create HTTP request: %s", err.Error()) } if tr.useParent { client = http.Client{ Transport: &ochttp.Transport{ StartOptions: trace.StartOptions{ Sampler: trace.AlwaysSample(), }, Propagation: tr.propagation, }, } } resp, err := client.Do(req.WithContext(context.Background())) if err != nil { t.Fatalf("unable to send HTTP request: %s", err.Error()) } resp.Body.Close() spans := rec.Flush() expectedSpans := 1 if tr.useParent { expectedSpans++ } if want, have := expectedSpans, len(spans); want != have { t.Fatalf("incorrect number of spans, want %d, have %d", want, have) } if tr.useParent { if want, have := spans[1].TraceID, spans[0].TraceID; want != have { t.Errorf("incorrect trace ID, want %s, have %s", want, have) } if want, have := spans[1].SpanID, spans[0].ParentSpanID; want != have { t.Errorf("incorrect span ID, want %s, have %s", want, have) } } if want, have := tr.name, spans[0].Name; want != have && want != "" { t.Errorf("incorrect span name, want %s, have %s", want, have) } if want, have := "GET /", spans[0].Name; want != have && tr.name == "" { t.Errorf("incorrect span name, want %s, have %s", want, have) } } } golang-github-go-kit-kit-0.13.0/tracing/opencensus/jsonrpc.go000066400000000000000000000105071443521372500241260ustar00rootroot00000000000000package opencensus import ( "context" "net/http" "go.opencensus.io/plugin/ochttp" "go.opencensus.io/plugin/ochttp/propagation/b3" "go.opencensus.io/trace" kithttp "github.com/go-kit/kit/transport/http" jsonrpc "github.com/go-kit/kit/transport/http/jsonrpc" ) // JSONRPCClientTrace enables OpenCensus tracing of a Go kit JSONRPC transport client. func JSONRPCClientTrace(options ...TracerOption) jsonrpc.ClientOption { cfg := TracerOptions{} for _, option := range options { option(&cfg) } if !cfg.Public && cfg.HTTPPropagate == nil { cfg.HTTPPropagate = &b3.HTTPFormat{} } clientBefore := jsonrpc.ClientBefore( func(ctx context.Context, req *http.Request) context.Context { var name string if cfg.Name != "" { name = cfg.Name } else { // OpenCensus states Path being default naming for a client span name = ctx.Value(jsonrpc.ContextKeyRequestMethod).(string) } ctx, span := trace.StartSpan( ctx, name, trace.WithSampler(cfg.Sampler), trace.WithSpanKind(trace.SpanKindClient), ) span.AddAttributes( trace.StringAttribute(ochttp.HostAttribute, req.URL.Host), trace.StringAttribute(ochttp.MethodAttribute, req.Method), trace.StringAttribute(ochttp.PathAttribute, req.URL.Path), trace.StringAttribute(ochttp.UserAgentAttribute, req.UserAgent()), ) if !cfg.Public { cfg.HTTPPropagate.SpanContextToRequest(span.SpanContext(), req) } return ctx }, ) clientAfter := jsonrpc.ClientAfter( func(ctx context.Context, res *http.Response) context.Context { if span := trace.FromContext(ctx); span != nil { span.SetStatus(ochttp.TraceStatus(res.StatusCode, http.StatusText(res.StatusCode))) span.AddAttributes( trace.Int64Attribute(ochttp.StatusCodeAttribute, int64(res.StatusCode)), ) } return ctx }, ) clientFinalizer := jsonrpc.ClientFinalizer( func(ctx context.Context, err error) { if span := trace.FromContext(ctx); span != nil { if err != nil { span.SetStatus(trace.Status{ Code: trace.StatusCodeUnknown, Message: err.Error(), }) } span.End() } }, ) return func(c *jsonrpc.Client) { clientBefore(c) clientAfter(c) clientFinalizer(c) } } // JSONRPCServerTrace enables OpenCensus tracing of a Go kit JSONRPC transport server. func JSONRPCServerTrace(options ...TracerOption) jsonrpc.ServerOption { cfg := TracerOptions{} for _, option := range options { option(&cfg) } if !cfg.Public && cfg.HTTPPropagate == nil { cfg.HTTPPropagate = &b3.HTTPFormat{} } serverBeforeCodec := jsonrpc.ServerBeforeCodec( func(ctx context.Context, httpReq *http.Request, req jsonrpc.Request) context.Context { var ( spanContext trace.SpanContext span *trace.Span name string ok bool ) if cfg.Name != "" { name = cfg.Name } else { name = ctx.Value(jsonrpc.ContextKeyRequestMethod).(string) if name == "" { // we can't find the rpc method. probably the // unaryInterceptor was not wired up. name = "unknown jsonrpc method" } } spanContext, ok = cfg.HTTPPropagate.SpanContextFromRequest(httpReq) if ok && !cfg.Public { ctx, span = trace.StartSpanWithRemoteParent( ctx, name, spanContext, trace.WithSpanKind(trace.SpanKindServer), trace.WithSampler(cfg.Sampler), ) } else { ctx, span = trace.StartSpan( ctx, name, trace.WithSpanKind(trace.SpanKindServer), trace.WithSampler(cfg.Sampler), ) if ok { span.AddLink(trace.Link{ TraceID: spanContext.TraceID, SpanID: spanContext.SpanID, Type: trace.LinkTypeChild, Attributes: nil, }) } } span.AddAttributes( trace.StringAttribute(ochttp.MethodAttribute, httpReq.Method), trace.StringAttribute(ochttp.PathAttribute, httpReq.URL.Path), ) return ctx }, ) serverFinalizer := jsonrpc.ServerFinalizer( func(ctx context.Context, code int, r *http.Request) { if span := trace.FromContext(ctx); span != nil { span.SetStatus(ochttp.TraceStatus(code, http.StatusText(code))) if rs, ok := ctx.Value(kithttp.ContextKeyResponseSize).(int64); ok { span.AddAttributes( trace.Int64Attribute("http.response_size", rs), ) } span.End() } }, ) return func(s *jsonrpc.Server) { serverBeforeCodec(s) serverFinalizer(s) } } golang-github-go-kit-kit-0.13.0/tracing/opencensus/jsonrpc_test.go000066400000000000000000000117501443521372500251660ustar00rootroot00000000000000package opencensus_test import ( "bytes" "context" "encoding/json" "errors" "fmt" "net/http" "net/http/httptest" "net/url" "testing" "go.opencensus.io/plugin/ochttp" "go.opencensus.io/plugin/ochttp/propagation/b3" "go.opencensus.io/plugin/ochttp/propagation/tracecontext" "go.opencensus.io/trace" "go.opencensus.io/trace/propagation" "github.com/go-kit/kit/endpoint" ockit "github.com/go-kit/kit/tracing/opencensus" jsonrpc "github.com/go-kit/kit/transport/http/jsonrpc" ) func TestJSONRPCClientTrace(t *testing.T) { t.Skip("FLAKY") var ( err error rec = &recordingExporter{} rURL, _ = url.Parse("https://httpbin.org/anything") endpointName = "DummyEndpoint" ) trace.RegisterExporter(rec) traces := []struct { name string err error }{ {"", nil}, {"CustomName", nil}, {"", errors.New("dummy-error")}, } for _, tr := range traces { clientTracer := ockit.JSONRPCClientTrace( ockit.WithName(tr.name), ockit.WithSampler(trace.AlwaysSample()), ) ep := jsonrpc.NewClient( rURL, endpointName, jsonrpc.ClientRequestEncoder(func(ctx context.Context, i interface{}) (json.RawMessage, error) { return json.RawMessage(`{}`), nil }), jsonrpc.ClientResponseDecoder(func(ctx context.Context, r jsonrpc.Response) (response interface{}, err error) { return nil, tr.err }), clientTracer, ).Endpoint() ctx, parentSpan := trace.StartSpan(context.Background(), "test") _, err = ep(ctx, nil) if want, have := tr.err, err; want != have { t.Fatalf("unexpected error, want %v, have %v", tr.err, err) } spans := rec.Flush() if want, have := 1, len(spans); want != have { t.Fatalf("incorrect number of spans, want %d, have %d", want, have) } span := spans[0] if want, have := parentSpan.SpanContext().SpanID, span.ParentSpanID; want != have { t.Errorf("incorrect parent ID, want %s, have %s", want, have) } if want, have := tr.name, span.Name; want != have && want != "" { t.Errorf("incorrect span name, want %s, have %s", want, have) } if want, have := endpointName, span.Name; want != have && tr.name == "" { t.Errorf("incorrect span name, want %s, have %s", want, have) } code := trace.StatusCodeOK if tr.err != nil { code = trace.StatusCodeUnknown if want, have := err.Error(), span.Status.Message; want != have { t.Errorf("incorrect span status msg, want %s, have %s", want, have) } } if want, have := int32(code), span.Status.Code; want != have { t.Errorf("incorrect span status code, want %d, have %d", want, have) } } } func TestJSONRPCServerTrace(t *testing.T) { var ( endpointName = "DummyEndpoint" rec = &recordingExporter{} ) trace.RegisterExporter(rec) traces := []struct { useParent bool name string err error propagation propagation.HTTPFormat }{ {false, "", nil, nil}, {true, "", nil, nil}, {true, "CustomName", nil, &b3.HTTPFormat{}}, {true, "", errors.New("dummy-error"), &tracecontext.HTTPFormat{}}, } for _, tr := range traces { var client http.Client handler := jsonrpc.NewServer( jsonrpc.EndpointCodecMap{ endpointName: jsonrpc.EndpointCodec{ Endpoint: endpoint.Nop, Decode: func(context.Context, json.RawMessage) (interface{}, error) { return nil, nil }, Encode: func(context.Context, interface{}) (json.RawMessage, error) { return nil, tr.err }, }, }, ockit.JSONRPCServerTrace( ockit.WithName(tr.name), ockit.WithSampler(trace.AlwaysSample()), ockit.WithHTTPPropagation(tr.propagation), ), ) server := httptest.NewServer(handler) defer server.Close() jsonStr := []byte(fmt.Sprintf(`{"method":"%s"}`, endpointName)) req, err := http.NewRequest("POST", server.URL, bytes.NewBuffer(jsonStr)) if err != nil { t.Fatalf("unable to create JSONRPC request: %v", err) } if tr.useParent { client = http.Client{ Transport: &ochttp.Transport{ StartOptions: trace.StartOptions{ Sampler: trace.AlwaysSample(), }, Propagation: tr.propagation, }, } } resp, err := client.Do(req.WithContext(context.Background())) if err != nil { t.Fatalf("unable to send JSONRPC request: %v", err) } resp.Body.Close() spans := rec.Flush() expectedSpans := 1 if tr.useParent { expectedSpans++ } if want, have := expectedSpans, len(spans); want != have { t.Fatalf("incorrect number of spans, want %d, have %d", want, have) } if tr.useParent { if want, have := spans[1].TraceID, spans[0].TraceID; want != have { t.Errorf("incorrect trace ID, want %s, have %s", want, have) } if want, have := spans[1].SpanID, spans[0].ParentSpanID; want != have { t.Errorf("incorrect span ID, want %s, have %s", want, have) } } if want, have := tr.name, spans[0].Name; want != have && want != "" { t.Errorf("incorrect span name, want %s, have %s", want, have) } if want, have := endpointName, spans[0].Name; want != have && tr.name == "" { t.Errorf("incorrect span name, want %s, have %s", want, have) } } } golang-github-go-kit-kit-0.13.0/tracing/opencensus/opencensus_test.go000066400000000000000000000006331443521372500256700ustar00rootroot00000000000000package opencensus_test import ( "sync" "go.opencensus.io/trace" ) type recordingExporter struct { mu sync.Mutex data []*trace.SpanData } func (e *recordingExporter) ExportSpan(d *trace.SpanData) { e.mu.Lock() defer e.mu.Unlock() e.data = append(e.data, d) } func (e *recordingExporter) Flush() (data []*trace.SpanData) { e.mu.Lock() defer e.mu.Unlock() data = e.data e.data = nil return } golang-github-go-kit-kit-0.13.0/tracing/opencensus/tracer_options.go000066400000000000000000000041111443521372500254750ustar00rootroot00000000000000package opencensus import ( "go.opencensus.io/plugin/ochttp/propagation/b3" "go.opencensus.io/trace" "go.opencensus.io/trace/propagation" ) // defaultHTTPPropagate holds OpenCensus' default HTTP propagation format which // currently is Zipkin's B3. var defaultHTTPPropagate propagation.HTTPFormat = &b3.HTTPFormat{} // TracerOption allows for functional options to our OpenCensus tracing // middleware. type TracerOption func(o *TracerOptions) // WithTracerConfig sets all configuration options at once. func WithTracerConfig(options TracerOptions) TracerOption { return func(o *TracerOptions) { *o = options } } // WithSampler sets the sampler to use by our OpenCensus Tracer. func WithSampler(sampler trace.Sampler) TracerOption { return func(o *TracerOptions) { o.Sampler = sampler } } // WithName sets the name for an instrumented transport endpoint. If name is omitted // at tracing middleware creation, the method of the transport or transport rpc // name is used. func WithName(name string) TracerOption { return func(o *TracerOptions) { o.Name = name } } // IsPublic should be set to true for publicly accessible servers and for // clients that should not propagate their current trace metadata. // On the server side a new trace will always be started regardless of any // trace metadata being found in the incoming request. If any trace metadata // is found, it will be added as a linked trace instead. func IsPublic(isPublic bool) TracerOption { return func(o *TracerOptions) { o.Public = isPublic } } // WithHTTPPropagation sets the propagation handlers for the HTTP transport // middlewares. If used on a non HTTP transport this is a noop. func WithHTTPPropagation(p propagation.HTTPFormat) TracerOption { return func(o *TracerOptions) { if p == nil { // reset to default OC HTTP format o.HTTPPropagate = defaultHTTPPropagate return } o.HTTPPropagate = p } } // TracerOptions holds configuration for our tracing middlewares type TracerOptions struct { Sampler trace.Sampler Name string Public bool HTTPPropagate propagation.HTTPFormat } golang-github-go-kit-kit-0.13.0/tracing/opentracing/000077500000000000000000000000001443521372500222455ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/tracing/opentracing/doc.go000066400000000000000000000003771443521372500233500ustar00rootroot00000000000000// Package opentracing provides Go kit integration to the OpenTracing project. // OpenTracing implements a general purpose interface that microservices can // program against, and which adapts to all major distributed tracing systems. package opentracing golang-github-go-kit-kit-0.13.0/tracing/opentracing/endpoint.go000066400000000000000000000065721443521372500244260ustar00rootroot00000000000000package opentracing import ( "context" "strconv" "github.com/opentracing/opentracing-go" otext "github.com/opentracing/opentracing-go/ext" otlog "github.com/opentracing/opentracing-go/log" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/sd/lb" ) // TraceEndpoint returns a Middleware that wraps the `next` Endpoint in an // OpenTracing Span called `operationName`. // // If `ctx` already has a Span, child span is created from it. // If `ctx` doesn't yet have a Span, the new one is created. func TraceEndpoint(tracer opentracing.Tracer, operationName string, opts ...EndpointOption) endpoint.Middleware { cfg := &EndpointOptions{ Tags: make(opentracing.Tags), } for _, opt := range opts { opt(cfg) } return func(next endpoint.Endpoint) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { if cfg.GetOperationName != nil { if newOperationName := cfg.GetOperationName(ctx, operationName); newOperationName != "" { operationName = newOperationName } } var span opentracing.Span if parentSpan := opentracing.SpanFromContext(ctx); parentSpan != nil { span = tracer.StartSpan( operationName, opentracing.ChildOf(parentSpan.Context()), ) } else { span = tracer.StartSpan(operationName) } defer span.Finish() applyTags(span, cfg.Tags) if cfg.GetTags != nil { extraTags := cfg.GetTags(ctx) applyTags(span, extraTags) } ctx = opentracing.ContextWithSpan(ctx, span) defer func() { if err != nil { if lbErr, ok := err.(lb.RetryError); ok { // handle errors originating from lb.Retry fields := make([]otlog.Field, 0, len(lbErr.RawErrors)) for idx, rawErr := range lbErr.RawErrors { fields = append(fields, otlog.String( "gokit.retry.error."+strconv.Itoa(idx+1), rawErr.Error(), )) } otext.LogError(span, lbErr, fields...) return } // generic error otext.LogError(span, err) return } // test for business error if res, ok := response.(endpoint.Failer); ok && res.Failed() != nil { span.LogFields( otlog.String("gokit.business.error", res.Failed().Error()), ) if cfg.IgnoreBusinessError { return } // treating business error as real error in span. otext.LogError(span, res.Failed()) return } }() return next(ctx, request) } } } // TraceServer returns a Middleware that wraps the `next` Endpoint in an // OpenTracing Span called `operationName` with server span.kind tag.. func TraceServer(tracer opentracing.Tracer, operationName string, opts ...EndpointOption) endpoint.Middleware { opts = append(opts, WithTags(map[string]interface{}{ otext.SpanKindRPCServer.Key: otext.SpanKindRPCServer.Value, })) return TraceEndpoint(tracer, operationName, opts...) } // TraceClient returns a Middleware that wraps the `next` Endpoint in an // OpenTracing Span called `operationName` with client span.kind tag. func TraceClient(tracer opentracing.Tracer, operationName string, opts ...EndpointOption) endpoint.Middleware { opts = append(opts, WithTags(map[string]interface{}{ otext.SpanKindRPCClient.Key: otext.SpanKindRPCClient.Value, })) return TraceEndpoint(tracer, operationName, opts...) } func applyTags(span opentracing.Span, tags opentracing.Tags) { for key, value := range tags { span.SetTag(key, value) } } golang-github-go-kit-kit-0.13.0/tracing/opentracing/endpoint_options.go000066400000000000000000000046361443521372500262000ustar00rootroot00000000000000package opentracing import ( "context" "github.com/opentracing/opentracing-go" ) // EndpointOptions holds the options for tracing an endpoint type EndpointOptions struct { // IgnoreBusinessError if set to true will not treat a business error // identified through the endpoint.Failer interface as a span error. IgnoreBusinessError bool // GetOperationName is an optional function that can set the span operation name based on the existing one // for the endpoint and information in the context. // // If the function is nil, or the returned name is empty, the existing name for the endpoint is used. GetOperationName func(ctx context.Context, name string) string // Tags holds the default tags which will be set on span // creation by our Endpoint middleware. Tags opentracing.Tags // GetTags is an optional function that can extract tags // from the context and add them to the span. GetTags func(ctx context.Context) opentracing.Tags } // EndpointOption allows for functional options to endpoint tracing middleware. type EndpointOption func(*EndpointOptions) // WithOptions sets all configuration options at once by use of the EndpointOptions struct. func WithOptions(options EndpointOptions) EndpointOption { return func(o *EndpointOptions) { *o = options } } // WithIgnoreBusinessError if set to true will not treat a business error // identified through the endpoint.Failer interface as a span error. func WithIgnoreBusinessError(ignoreBusinessError bool) EndpointOption { return func(o *EndpointOptions) { o.IgnoreBusinessError = ignoreBusinessError } } // WithOperationNameFunc allows to set function that can set the span operation name based on the existing one // for the endpoint and information in the context. func WithOperationNameFunc(getOperationName func(ctx context.Context, name string) string) EndpointOption { return func(o *EndpointOptions) { o.GetOperationName = getOperationName } } // WithTags adds default tags for the spans created by the Endpoint tracer. func WithTags(tags opentracing.Tags) EndpointOption { return func(o *EndpointOptions) { if o.Tags == nil { o.Tags = make(opentracing.Tags) } for key, value := range tags { o.Tags[key] = value } } } // WithTagsFunc set the func to extracts additional tags from the context. func WithTagsFunc(getTags func(ctx context.Context) opentracing.Tags) EndpointOption { return func(o *EndpointOptions) { o.GetTags = getTags } } golang-github-go-kit-kit-0.13.0/tracing/opentracing/endpoint_test.go000066400000000000000000000246161443521372500254640ustar00rootroot00000000000000package opentracing_test import ( "context" "errors" "fmt" "reflect" "testing" "time" "github.com/opentracing/opentracing-go" otext "github.com/opentracing/opentracing-go/ext" otlog "github.com/opentracing/opentracing-go/log" "github.com/opentracing/opentracing-go/mocktracer" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/sd" "github.com/go-kit/kit/sd/lb" kitot "github.com/go-kit/kit/tracing/opentracing" ) const ( span1 = "SPAN-1" span2 = "SPAN-2" span3 = "SPAN-3" span4 = "SPAN-4" span5 = "SPAN-5" span6 = "SPAN-6" span7 = "SPAN-7" span8 = "SPAN-8" ) var ( err1 = errors.New("some error") err2 = errors.New("some business error") err3 = errors.New("other business error") ) // compile time assertion var _ endpoint.Failer = failedResponse{} type failedResponse struct { err error } func (r failedResponse) Failed() error { return r.err } func TestTraceEndpoint(t *testing.T) { tracer := mocktracer.New() // Initialize the ctx with a parent Span. parentSpan := tracer.StartSpan("parent").(*mocktracer.MockSpan) defer parentSpan.Finish() ctx := opentracing.ContextWithSpan(context.Background(), parentSpan) tracedEndpoint := kitot.TraceEndpoint(tracer, "testOp")(endpoint.Nop) if _, err := tracedEndpoint(ctx, struct{}{}); err != nil { t.Fatal(err) } // tracedEndpoint created a new Span. finishedSpans := tracer.FinishedSpans() if want, have := 1, len(finishedSpans); want != have { t.Fatalf("Want %v span(s), found %v", want, have) } endpointSpan := finishedSpans[0] if want, have := "testOp", endpointSpan.OperationName; want != have { t.Fatalf("Want %q, have %q", want, have) } parentContext := parentSpan.Context().(mocktracer.MockSpanContext) endpointContext := parentSpan.Context().(mocktracer.MockSpanContext) // ... and that the parent ID is set appropriately. if want, have := parentContext.SpanID, endpointContext.SpanID; want != have { t.Errorf("Want ParentID %q, have %q", want, have) } } func TestTraceEndpointNoContextSpan(t *testing.T) { tracer := mocktracer.New() // Empty/background context. tracedEndpoint := kitot.TraceEndpoint(tracer, "testOp")(endpoint.Nop) if _, err := tracedEndpoint(context.Background(), struct{}{}); err != nil { t.Fatal(err) } // tracedEndpoint created a new Span. finishedSpans := tracer.FinishedSpans() if want, have := 1, len(finishedSpans); want != have { t.Fatalf("Want %v span(s), found %v", want, have) } endpointSpan := finishedSpans[0] if want, have := "testOp", endpointSpan.OperationName; want != have { t.Fatalf("Want %q, have %q", want, have) } } func TestTraceEndpointWithOptions(t *testing.T) { tracer := mocktracer.New() // span 1 without options mw := kitot.TraceEndpoint(tracer, span1) tracedEndpoint := mw(endpoint.Nop) _, _ = tracedEndpoint(context.Background(), struct{}{}) // span 2 with options mw = kitot.TraceEndpoint( tracer, span2, kitot.WithOptions(kitot.EndpointOptions{}), ) tracedEndpoint = mw(func(context.Context, interface{}) (interface{}, error) { return nil, err1 }) _, _ = tracedEndpoint(context.Background(), struct{}{}) // span 3 with lb error mw = kitot.TraceEndpoint( tracer, span3, kitot.WithOptions(kitot.EndpointOptions{}), ) tracedEndpoint = mw( lb.Retry( 5, 1*time.Second, lb.NewRoundRobin( sd.FixedEndpointer{ func(context.Context, interface{}) (interface{}, error) { return nil, err1 }, }, ), ), ) _, _ = tracedEndpoint(context.Background(), struct{}{}) // span 4 with disabled IgnoreBusinessError option mw = kitot.TraceEndpoint( tracer, span4, kitot.WithIgnoreBusinessError(false), ) tracedEndpoint = mw(func(context.Context, interface{}) (interface{}, error) { return failedResponse{ err: err2, }, nil }) _, _ = tracedEndpoint(context.Background(), struct{}{}) // span 5 with enabled IgnoreBusinessError option mw = kitot.TraceEndpoint(tracer, span5, kitot.WithIgnoreBusinessError(true)) tracedEndpoint = mw(func(context.Context, interface{}) (interface{}, error) { return failedResponse{ err: err3, }, nil }) _, _ = tracedEndpoint(context.Background(), struct{}{}) // span 6 with OperationNameFunc option mw = kitot.TraceEndpoint( tracer, span6, kitot.WithOperationNameFunc(func(ctx context.Context, name string) string { return fmt.Sprintf("%s-%s", "new", name) }), ) tracedEndpoint = mw(endpoint.Nop) _, _ = tracedEndpoint(context.Background(), struct{}{}) // span 7 with Tags options mw = kitot.TraceEndpoint( tracer, span7, kitot.WithTags(map[string]interface{}{ "tag1": "tag1", "tag2": "tag2", }), kitot.WithTags(map[string]interface{}{ "tag3": "tag3", }), ) tracedEndpoint = mw(endpoint.Nop) _, _ = tracedEndpoint(context.Background(), struct{}{}) // span 8 with TagsFunc options mw = kitot.TraceEndpoint( tracer, span8, kitot.WithTags(map[string]interface{}{ "tag1": "tag1", "tag2": "tag2", }), kitot.WithTags(map[string]interface{}{ "tag3": "tag3", }), kitot.WithTagsFunc(func(ctx context.Context) opentracing.Tags { return map[string]interface{}{ "tag4": "tag4", } }), ) tracedEndpoint = mw(endpoint.Nop) _, _ = tracedEndpoint(context.Background(), struct{}{}) finishedSpans := tracer.FinishedSpans() if want, have := 8, len(finishedSpans); want != have { t.Fatalf("Want %v span(s), found %v", want, have) } // test span 1 span := finishedSpans[0] if want, have := span1, span.OperationName; want != have { t.Fatalf("Want %q, have %q", want, have) } // test span 2 span = finishedSpans[1] if want, have := span2, span.OperationName; want != have { t.Fatalf("Want %q, have %q", want, have) } if want, have := true, span.Tag("error"); want != have { t.Fatalf("Want %v, have %v", want, have) } // test span 3 span = finishedSpans[2] if want, have := span3, span.OperationName; want != have { t.Fatalf("Want %q, have %q", want, have) } if want, have := true, span.Tag("error"); want != have { t.Fatalf("Want %v, have %v", want, have) } if want, have := 1, len(span.Logs()); want != have { t.Fatalf("incorrect logs count, wanted %d, got %d", want, have) } if want, have := []otlog.Field{ otlog.String("event", "error"), otlog.String("error.object", "some error (previously: some error; some error; some error; some error)"), otlog.String("gokit.retry.error.1", "some error"), otlog.String("gokit.retry.error.2", "some error"), otlog.String("gokit.retry.error.3", "some error"), otlog.String("gokit.retry.error.4", "some error"), otlog.String("gokit.retry.error.5", "some error"), }, span.Logs()[0].Fields; reflect.DeepEqual(want, have) { t.Fatalf("Want %q, have %q", want, have) } // test span 4 span = finishedSpans[3] if want, have := span4, span.OperationName; want != have { t.Fatalf("Want %q, have %q", want, have) } if want, have := true, span.Tag("error"); want != have { t.Fatalf("Want %v, have %v", want, have) } if want, have := 2, len(span.Logs()); want != have { t.Fatalf("incorrect logs count, wanted %d, got %d", want, have) } if want, have := []otlog.Field{ otlog.String("gokit.business.error", "some business error"), }, span.Logs()[0].Fields; reflect.DeepEqual(want, have) { t.Fatalf("Want %q, have %q", want, have) } if want, have := []otlog.Field{ otlog.String("event", "error"), otlog.String("error.object", "some business error"), }, span.Logs()[1].Fields; reflect.DeepEqual(want, have) { t.Fatalf("Want %q, have %q", want, have) } // test span 5 span = finishedSpans[4] if want, have := span5, span.OperationName; want != have { t.Fatalf("Want %q, have %q", want, have) } if want, have := (interface{})(nil), span.Tag("error"); want != have { t.Fatalf("Want %q, have %q", want, have) } if want, have := 1, len(span.Logs()); want != have { t.Fatalf("incorrect logs count, wanted %d, got %d", want, have) } if want, have := []otlog.Field{ otlog.String("gokit.business.error", "some business error"), }, span.Logs()[0].Fields; reflect.DeepEqual(want, have) { t.Fatalf("Want %q, have %q", want, have) } // test span 6 span = finishedSpans[5] if want, have := fmt.Sprintf("%s-%s", "new", span6), span.OperationName; want != have { t.Fatalf("Want %q, have %q", want, have) } // test span 7 span = finishedSpans[6] if want, have := span7, span.OperationName; want != have { t.Fatalf("Want %q, have %q", want, have) } if want, have := map[string]interface{}{ "tag1": "tag1", "tag2": "tag2", "tag3": "tag3", }, span.Tags(); fmt.Sprint(want) != fmt.Sprint(have) { t.Fatalf("Want %q, have %q", want, have) } // test span 8 span = finishedSpans[7] if want, have := span8, span.OperationName; want != have { t.Fatalf("Want %q, have %q", want, have) } if want, have := map[string]interface{}{ "tag1": "tag1", "tag2": "tag2", "tag3": "tag3", "tag4": "tag4", }, span.Tags(); fmt.Sprint(want) != fmt.Sprint(have) { t.Fatalf("Want %q, have %q", want, have) } } func TestTraceServer(t *testing.T) { tracer := mocktracer.New() // Empty/background context. tracedEndpoint := kitot.TraceServer(tracer, "testOp")(endpoint.Nop) if _, err := tracedEndpoint(context.Background(), struct{}{}); err != nil { t.Fatal(err) } // tracedEndpoint created a new Span. finishedSpans := tracer.FinishedSpans() if want, have := 1, len(finishedSpans); want != have { t.Fatalf("Want %v span(s), found %v", want, have) } span := finishedSpans[0] if want, have := "testOp", span.OperationName; want != have { t.Fatalf("Want %q, have %q", want, have) } if want, have := map[string]interface{}{ otext.SpanKindRPCServer.Key: otext.SpanKindRPCServer.Value, }, span.Tags(); fmt.Sprint(want) != fmt.Sprint(have) { t.Fatalf("Want %q, have %q", want, have) } } func TestTraceClient(t *testing.T) { tracer := mocktracer.New() // Empty/background context. tracedEndpoint := kitot.TraceClient(tracer, "testOp")(endpoint.Nop) if _, err := tracedEndpoint(context.Background(), struct{}{}); err != nil { t.Fatal(err) } // tracedEndpoint created a new Span. finishedSpans := tracer.FinishedSpans() if want, have := 1, len(finishedSpans); want != have { t.Fatalf("Want %v span(s), found %v", want, have) } span := finishedSpans[0] if want, have := "testOp", span.OperationName; want != have { t.Fatalf("Want %q, have %q", want, have) } if want, have := map[string]interface{}{ otext.SpanKindRPCClient.Key: otext.SpanKindRPCClient.Value, }, span.Tags(); fmt.Sprint(want) != fmt.Sprint(have) { t.Fatalf("Want %q, have %q", want, have) } } golang-github-go-kit-kit-0.13.0/tracing/opentracing/grpc.go000066400000000000000000000044621443521372500235350ustar00rootroot00000000000000package opentracing import ( "context" "encoding/base64" "strings" "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" "google.golang.org/grpc/metadata" "github.com/go-kit/log" ) // ContextToGRPC returns a grpc RequestFunc that injects an OpenTracing Span // found in `ctx` into the grpc Metadata. If no such Span can be found, the // RequestFunc is a noop. func ContextToGRPC(tracer opentracing.Tracer, logger log.Logger) func(ctx context.Context, md *metadata.MD) context.Context { return func(ctx context.Context, md *metadata.MD) context.Context { if span := opentracing.SpanFromContext(ctx); span != nil { // There's nothing we can do with an error here. if err := tracer.Inject(span.Context(), opentracing.TextMap, metadataReaderWriter{md}); err != nil { logger.Log("err", err) } } return ctx } } // GRPCToContext returns a grpc RequestFunc that tries to join with an // OpenTracing trace found in `req` and starts a new Span called // `operationName` accordingly. If no trace could be found in `req`, the Span // will be a trace root. The Span is incorporated in the returned Context and // can be retrieved with opentracing.SpanFromContext(ctx). func GRPCToContext(tracer opentracing.Tracer, operationName string, logger log.Logger) func(ctx context.Context, md metadata.MD) context.Context { return func(ctx context.Context, md metadata.MD) context.Context { var span opentracing.Span wireContext, err := tracer.Extract(opentracing.TextMap, metadataReaderWriter{&md}) if err != nil && err != opentracing.ErrSpanContextNotFound { logger.Log("err", err) } span = tracer.StartSpan(operationName, ext.RPCServerOption(wireContext)) return opentracing.ContextWithSpan(ctx, span) } } // A type that conforms to opentracing.TextMapReader and // opentracing.TextMapWriter. type metadataReaderWriter struct { *metadata.MD } func (w metadataReaderWriter) Set(key, val string) { key = strings.ToLower(key) if strings.HasSuffix(key, "-bin") { val = base64.StdEncoding.EncodeToString([]byte(val)) } (*w.MD)[key] = append((*w.MD)[key], val) } func (w metadataReaderWriter) ForeachKey(handler func(key, val string) error) error { for k, vals := range *w.MD { for _, v := range vals { if err := handler(k, v); err != nil { return err } } } return nil } golang-github-go-kit-kit-0.13.0/tracing/opentracing/grpc_test.go000066400000000000000000000041121443521372500245640ustar00rootroot00000000000000package opentracing_test import ( "context" "testing" "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/mocktracer" "google.golang.org/grpc/metadata" kitot "github.com/go-kit/kit/tracing/opentracing" "github.com/go-kit/log" ) func TestTraceGRPCRequestRoundtrip(t *testing.T) { logger := log.NewNopLogger() tracer := mocktracer.New() // Initialize the ctx with a Span to inject. beforeSpan := tracer.StartSpan("to_inject").(*mocktracer.MockSpan) defer beforeSpan.Finish() beforeSpan.SetBaggageItem("baggage", "check") beforeCtx := opentracing.ContextWithSpan(context.Background(), beforeSpan) toGRPCFunc := kitot.ContextToGRPC(tracer, logger) md := metadata.Pairs() // Call the RequestFunc. afterCtx := toGRPCFunc(beforeCtx, &md) // The Span should not have changed. afterSpan := opentracing.SpanFromContext(afterCtx) if beforeSpan != afterSpan { t.Error("Should not swap in a new span") } // No spans should have finished yet. finishedSpans := tracer.FinishedSpans() if want, have := 0, len(finishedSpans); want != have { t.Errorf("Want %v span(s), found %v", want, have) } // Use GRPCToContext to verify that we can join with the trace given MD. fromGRPCFunc := kitot.GRPCToContext(tracer, "joined", logger) joinCtx := fromGRPCFunc(afterCtx, md) joinedSpan := opentracing.SpanFromContext(joinCtx).(*mocktracer.MockSpan) joinedContext := joinedSpan.Context().(mocktracer.MockSpanContext) beforeContext := beforeSpan.Context().(mocktracer.MockSpanContext) if joinedContext.SpanID == beforeContext.SpanID { t.Error("SpanID should have changed", joinedContext.SpanID, beforeContext.SpanID) } // Check that the parent/child relationship is as expected for the joined span. if want, have := beforeContext.SpanID, joinedSpan.ParentID; want != have { t.Errorf("Want ParentID %q, have %q", want, have) } if want, have := "joined", joinedSpan.OperationName; want != have { t.Errorf("Want %q, have %q", want, have) } if want, have := "check", joinedSpan.BaggageItem("baggage"); want != have { t.Errorf("Want %q, have %q", want, have) } } golang-github-go-kit-kit-0.13.0/tracing/opentracing/http.go000066400000000000000000000045041443521372500235560ustar00rootroot00000000000000package opentracing import ( "context" "net" "net/http" "strconv" opentracing "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" kithttp "github.com/go-kit/kit/transport/http" "github.com/go-kit/log" ) // ContextToHTTP returns an http RequestFunc that injects an OpenTracing Span // found in `ctx` into the http headers. If no such Span can be found, the // RequestFunc is a noop. func ContextToHTTP(tracer opentracing.Tracer, logger log.Logger) kithttp.RequestFunc { return func(ctx context.Context, req *http.Request) context.Context { // Try to find a Span in the Context. if span := opentracing.SpanFromContext(ctx); span != nil { // Add standard OpenTracing tags. ext.HTTPMethod.Set(span, req.Method) ext.HTTPUrl.Set(span, req.URL.String()) host, portString, err := net.SplitHostPort(req.URL.Host) if err == nil { ext.PeerHostname.Set(span, host) if port, err := strconv.Atoi(portString); err == nil { ext.PeerPort.Set(span, uint16(port)) } } else { ext.PeerHostname.Set(span, req.URL.Host) } // There's nothing we can do with any errors here. if err = tracer.Inject( span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header), ); err != nil { logger.Log("err", err) } } return ctx } } // HTTPToContext returns an http RequestFunc that tries to join with an // OpenTracing trace found in `req` and starts a new Span called // `operationName` accordingly. If no trace could be found in `req`, the Span // will be a trace root. The Span is incorporated in the returned Context and // can be retrieved with opentracing.SpanFromContext(ctx). func HTTPToContext(tracer opentracing.Tracer, operationName string, logger log.Logger) kithttp.RequestFunc { return func(ctx context.Context, req *http.Request) context.Context { // Try to join to a trace propagated in `req`. var span opentracing.Span wireContext, err := tracer.Extract( opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header), ) if err != nil && err != opentracing.ErrSpanContextNotFound { logger.Log("err", err) } span = tracer.StartSpan(operationName, ext.RPCServerOption(wireContext)) ext.HTTPMethod.Set(span, req.Method) ext.HTTPUrl.Set(span, req.URL.String()) return opentracing.ContextWithSpan(ctx, span) } } golang-github-go-kit-kit-0.13.0/tracing/opentracing/http_test.go000066400000000000000000000072651443521372500246240ustar00rootroot00000000000000package opentracing_test import ( "context" "net/http" "reflect" "testing" opentracing "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" "github.com/opentracing/opentracing-go/mocktracer" kitot "github.com/go-kit/kit/tracing/opentracing" "github.com/go-kit/log" ) func TestTraceHTTPRequestRoundtrip(t *testing.T) { logger := log.NewNopLogger() tracer := mocktracer.New() // Initialize the ctx with a Span to inject. beforeSpan := tracer.StartSpan("to_inject").(*mocktracer.MockSpan) defer beforeSpan.Finish() beforeSpan.SetBaggageItem("baggage", "check") beforeCtx := opentracing.ContextWithSpan(context.Background(), beforeSpan) toHTTPFunc := kitot.ContextToHTTP(tracer, logger) req, _ := http.NewRequest("GET", "http://test.biz/path", nil) // Call the RequestFunc. afterCtx := toHTTPFunc(beforeCtx, req) // The Span should not have changed. afterSpan := opentracing.SpanFromContext(afterCtx) if beforeSpan != afterSpan { t.Error("Should not swap in a new span") } // No spans should have finished yet. finishedSpans := tracer.FinishedSpans() if want, have := 0, len(finishedSpans); want != have { t.Errorf("Want %v span(s), found %v", want, have) } // Use HTTPToContext to verify that we can join with the trace given a req. fromHTTPFunc := kitot.HTTPToContext(tracer, "joined", logger) joinCtx := fromHTTPFunc(afterCtx, req) joinedSpan := opentracing.SpanFromContext(joinCtx).(*mocktracer.MockSpan) joinedContext := joinedSpan.Context().(mocktracer.MockSpanContext) beforeContext := beforeSpan.Context().(mocktracer.MockSpanContext) if joinedContext.SpanID == beforeContext.SpanID { t.Error("SpanID should have changed", joinedContext.SpanID, beforeContext.SpanID) } // Check that the parent/child relationship is as expected for the joined span. if want, have := beforeContext.SpanID, joinedSpan.ParentID; want != have { t.Errorf("Want ParentID %q, have %q", want, have) } if want, have := "joined", joinedSpan.OperationName; want != have { t.Errorf("Want %q, have %q", want, have) } if want, have := "check", joinedSpan.BaggageItem("baggage"); want != have { t.Errorf("Want %q, have %q", want, have) } } func TestContextToHTTPTags(t *testing.T) { tracer := mocktracer.New() span := tracer.StartSpan("to_inject").(*mocktracer.MockSpan) defer span.Finish() ctx := opentracing.ContextWithSpan(context.Background(), span) req, _ := http.NewRequest("GET", "http://test.biz/path", nil) kitot.ContextToHTTP(tracer, log.NewNopLogger())(ctx, req) expectedTags := map[string]interface{}{ string(ext.HTTPMethod): "GET", string(ext.HTTPUrl): "http://test.biz/path", string(ext.PeerHostname): "test.biz", } if !reflect.DeepEqual(expectedTags, span.Tags()) { t.Errorf("Want %q, have %q", expectedTags, span.Tags()) } } func TestHTTPToContextTags(t *testing.T) { tracer := mocktracer.New() parentSpan := tracer.StartSpan("to_extract").(*mocktracer.MockSpan) defer parentSpan.Finish() req, _ := http.NewRequest("GET", "http://test.biz/path", nil) tracer.Inject(parentSpan.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header)) ctx := kitot.HTTPToContext(tracer, "op", log.NewNopLogger())(context.Background(), req) opentracing.SpanFromContext(ctx).Finish() childSpan := tracer.FinishedSpans()[0] expectedTags := map[string]interface{}{ string(ext.HTTPMethod): "GET", string(ext.HTTPUrl): "http://test.biz/path", string(ext.SpanKind): ext.SpanKindRPCServerEnum, } if !reflect.DeepEqual(expectedTags, childSpan.Tags()) { t.Errorf("Want %q, have %q", expectedTags, childSpan.Tags()) } if want, have := "op", childSpan.OperationName; want != have { t.Errorf("Want %q, have %q", want, have) } } golang-github-go-kit-kit-0.13.0/tracing/zipkin/000077500000000000000000000000001443521372500212405ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/tracing/zipkin/README.md000066400000000000000000000055171443521372500225270ustar00rootroot00000000000000# Zipkin ## Development and Testing Set-up Great efforts have been made to make [Zipkin] easier to test, develop and experiment against. [Zipkin] can now be run from a single Docker container or by running its self-contained executable jar without extensive configuration. In its default configuration you will run [Zipkin] with a HTTP collector, In memory Span storage backend and web UI on port 9411. Example: ``` docker run -d -p 9411:9411 openzipkin/zipkin ``` [zipkin]: http://zipkin.io ## Middleware Usage Follow the [addsvc] example to check out how to wire the [Zipkin] Middleware. The changes should be relatively minor. The [zipkin-go] package has Reporters to send Spans to the [Zipkin] HTTP and Kafka Collectors. ### Configuring the Zipkin HTTP Reporter To use the HTTP Reporter with a [Zipkin] instance running on localhost you bootstrap [zipkin-go] like this: ```go var ( serviceName = "MyService" serviceHostPort = "localhost:8000" zipkinHTTPEndpoint = "http://localhost:9411/api/v2/spans" ) // create an instance of the HTTP Reporter. reporter := zipkin.NewReporter(zipkinHTTPEndpoint) // create our tracer's local endpoint (how the service is identified in Zipkin). localEndpoint, err := zipkin.NewEndpoint(serviceName, serviceHostPort) // create our tracer instance. tracer, err = zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(localEndpoint)) ... ``` [zipkin-go]: https://github.com/openzipkin/zipkin-go [addsvc]: https://github.com/go-kit/examples/tree/master/addsvc [Log]: https://github.com/go-kit/kit/tree/master/log ### Tracing Resources Here is an example of how you could trace resources and work with local spans. ```go import ( zipkin "github.com/openzipkin/zipkin-go" ) func (svc *Service) GetMeSomeExamples(ctx context.Context, ...) ([]Examples, error) { // Example of annotating a database query: var ( spanContext model.SpanContext serviceName = "MySQL" serviceHost = "mysql.example.com:3306" queryLabel = "GetExamplesByParam" query = "select * from example where param = :value" ) // retrieve the parent span from context to use as parent if available. if parentSpan := zipkin.SpanFromContext(ctx); parentSpan != nil { spanContext = parentSpan.Context() } // create the remote Zipkin endpoint ep, _ := zipkin.NewEndpoint(serviceName, serviceHost) // create a new span to record the resource interaction span := zipkin.StartSpan( queryLabel, zipkin.Parent(parentSpan.Context()), zipkin.WithRemoteEndpoint(ep), ) // add interesting key/value pair to our span span.SetTag("query", query) // add interesting timed event to our span span.Annotate(time.Now(), "query:start") // do the actual query... // let's annotate the end... span.Annotate(time.Now(), "query:end") // we're done with this span. span.Finish() // do other stuff ... } ``` golang-github-go-kit-kit-0.13.0/tracing/zipkin/doc.go000066400000000000000000000004661443521372500223420ustar00rootroot00000000000000// Package zipkin provides Go kit integration to the OpenZipkin project through // the use of zipkin-go, the official OpenZipkin tracer implementation for Go. // OpenZipkin is the most used open source distributed tracing ecosystem with // many different libraries and interoperability options. package zipkin golang-github-go-kit-kit-0.13.0/tracing/zipkin/endpoint.go000066400000000000000000000016471443521372500234170ustar00rootroot00000000000000package zipkin import ( "context" "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/model" "github.com/go-kit/kit/endpoint" ) // TraceEndpoint returns an Endpoint middleware, tracing a Go kit endpoint. // This endpoint tracer should be used in combination with a Go kit Transport // tracing middleware or custom before and after transport functions as // propagation of SpanContext is not provided in this middleware. func TraceEndpoint(tracer *zipkin.Tracer, name string) endpoint.Middleware { return func(next endpoint.Endpoint) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { var sc model.SpanContext if parentSpan := zipkin.SpanFromContext(ctx); parentSpan != nil { sc = parentSpan.Context() } sp := tracer.StartSpan(name, zipkin.Parent(sc)) defer sp.Finish() ctx = zipkin.NewContext(ctx, sp) return next(ctx, request) } } } golang-github-go-kit-kit-0.13.0/tracing/zipkin/endpoint_test.go000066400000000000000000000013211443521372500244430ustar00rootroot00000000000000package zipkin_test import ( "context" "testing" "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/reporter/recorder" "github.com/go-kit/kit/endpoint" zipkinkit "github.com/go-kit/kit/tracing/zipkin" ) const spanName = "test" func TestTraceEndpoint(t *testing.T) { rec := recorder.NewReporter() tr, _ := zipkin.NewTracer(rec) mw := zipkinkit.TraceEndpoint(tr, spanName) mw(endpoint.Nop)(context.Background(), nil) spans := rec.Flush() if want, have := 1, len(spans); want != have { t.Fatalf("incorrect number of spans, wanted %d, got %d", want, have) } if want, have := spanName, spans[0].Name; want != have { t.Fatalf("incorrect span name, wanted %s, got %s", want, have) } } golang-github-go-kit-kit-0.13.0/tracing/zipkin/grpc.go000066400000000000000000000134031443521372500225230ustar00rootroot00000000000000package zipkin import ( "context" "strconv" zipkin "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation/b3" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" kitgrpc "github.com/go-kit/kit/transport/grpc" "github.com/go-kit/log" ) // GRPCClientTrace enables native Zipkin tracing of a Go kit gRPC transport // Client. // // Go kit creates gRPC transport clients per remote endpoint. This middleware // can be set-up individually by adding the endpoint name for each of the Go kit // transport clients using the Name() TracerOption. // If wanting to use the gRPC FullMethod (/service/method) as Span name you can // create a global client tracer omitting the Name() TracerOption, which you can // then feed to each Go kit gRPC transport client. // If instrumenting a client to an external (not on your platform) service, you // will probably want to disallow propagation of SpanContext using the // AllowPropagation TracerOption and setting it to false. func GRPCClientTrace(tracer *zipkin.Tracer, options ...TracerOption) kitgrpc.ClientOption { config := tracerOptions{ tags: make(map[string]string), name: "", logger: log.NewNopLogger(), propagate: true, } for _, option := range options { option(&config) } clientBefore := kitgrpc.ClientBefore( func(ctx context.Context, md *metadata.MD) context.Context { var ( spanContext model.SpanContext name string ) if config.name != "" { name = config.name } else { name = ctx.Value(kitgrpc.ContextKeyRequestMethod).(string) } if parent := zipkin.SpanFromContext(ctx); parent != nil { spanContext = parent.Context() } span := tracer.StartSpan( name, zipkin.Kind(model.Client), zipkin.Tags(config.tags), zipkin.Parent(spanContext), zipkin.FlushOnFinish(false), ) if config.propagate { if err := b3.InjectGRPC(md)(span.Context()); err != nil { config.logger.Log("err", err) } } return zipkin.NewContext(ctx, span) }, ) clientAfter := kitgrpc.ClientAfter( func(ctx context.Context, _ metadata.MD, _ metadata.MD) context.Context { if span := zipkin.SpanFromContext(ctx); span != nil { span.Finish() } return ctx }, ) clientFinalizer := kitgrpc.ClientFinalizer( func(ctx context.Context, err error) { if span := zipkin.SpanFromContext(ctx); span != nil { if err != nil { zipkin.TagError.Set(span, err.Error()) } // calling span.Finish() a second time is a noop, if we didn't get to // ClientAfter we can at least time the early bail out by calling it // here. span.Finish() // send span to the Reporter span.Flush() } }, ) return func(c *kitgrpc.Client) { clientBefore(c) clientAfter(c) clientFinalizer(c) } } // GRPCServerTrace enables native Zipkin tracing of a Go kit gRPC transport // Server. // // Go kit creates gRPC transport servers per gRPC method. This middleware can be // set-up individually by adding the method name for each of the Go kit method // servers using the Name() TracerOption. // If wanting to use the gRPC FullMethod (/service/method) as Span name you can // create a global server tracer omitting the Name() TracerOption, which you can // then feed to each Go kit method server. For this to work you will need to // wire the Go kit gRPC Interceptor too. // If instrumenting a service to external (not on your platform) clients, you // will probably want to disallow propagation of a client SpanContext using // the AllowPropagation TracerOption and setting it to false. func GRPCServerTrace(tracer *zipkin.Tracer, options ...TracerOption) kitgrpc.ServerOption { config := tracerOptions{ tags: make(map[string]string), name: "", logger: log.NewNopLogger(), propagate: true, } for _, option := range options { option(&config) } serverBefore := kitgrpc.ServerBefore( func(ctx context.Context, md metadata.MD) context.Context { var ( spanContext model.SpanContext name string tags = make(map[string]string) ) rpcMethod, ok := ctx.Value(kitgrpc.ContextKeyRequestMethod).(string) if !ok { config.logger.Log("err", "unable to retrieve method name: missing gRPC interceptor hook") } else { tags["grpc.method"] = rpcMethod } if config.name != "" { name = config.name } else { name = rpcMethod } if config.propagate { spanContext = tracer.Extract(b3.ExtractGRPC(&md)) if spanContext.Err != nil { config.logger.Log("err", spanContext.Err) } } span := tracer.StartSpan( name, zipkin.Kind(model.Server), zipkin.Tags(config.tags), zipkin.Tags(tags), zipkin.Parent(spanContext), zipkin.FlushOnFinish(false), ) return zipkin.NewContext(ctx, span) }, ) serverAfter := kitgrpc.ServerAfter( func(ctx context.Context, _ *metadata.MD, _ *metadata.MD) context.Context { if span := zipkin.SpanFromContext(ctx); span != nil { span.Finish() } return ctx }, ) serverFinalizer := kitgrpc.ServerFinalizer( func(ctx context.Context, err error) { if span := zipkin.SpanFromContext(ctx); span != nil { if err != nil { if status, ok := status.FromError(err); ok { statusCode := strconv.FormatUint(uint64(status.Code()), 10) zipkin.TagGRPCStatusCode.Set(span, statusCode) zipkin.TagError.Set(span, status.Message()) } else { zipkin.TagError.Set(span, err.Error()) } } // calling span.Finish() a second time is a noop, if we didn't get to // ServerAfter we can at least time the early bail out by calling it // here. span.Finish() // send span to the Reporter span.Flush() } }, ) return func(s *kitgrpc.Server) { serverBefore(s) serverAfter(s) serverFinalizer(s) } } golang-github-go-kit-kit-0.13.0/tracing/zipkin/grpc_test.go000066400000000000000000000055261443521372500235710ustar00rootroot00000000000000package zipkin_test import ( "context" "testing" zipkin "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/propagation/b3" "github.com/openzipkin/zipkin-go/reporter/recorder" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "github.com/go-kit/kit/endpoint" kitzipkin "github.com/go-kit/kit/tracing/zipkin" grpctransport "github.com/go-kit/kit/transport/grpc" ) type dummy struct{} func unaryInterceptor( ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption, ) error { return nil } func TestGRPCClientTrace(t *testing.T) { rec := recorder.NewReporter() defer rec.Close() tr, _ := zipkin.NewTracer(rec) clientTracer := kitzipkin.GRPCClientTrace(tr) cc, err := grpc.Dial( "", grpc.WithUnaryInterceptor(unaryInterceptor), grpc.WithInsecure(), ) if err != nil { t.Fatalf("unable to create gRPC dialer: %s", err.Error()) } ep := grpctransport.NewClient( cc, "dummyService", "dummyMethod", func(context.Context, interface{}) (interface{}, error) { return nil, nil }, func(context.Context, interface{}) (interface{}, error) { return nil, nil }, dummy{}, clientTracer, ).Endpoint() parentSpan := tr.StartSpan("test") ctx := zipkin.NewContext(context.Background(), parentSpan) if _, err = ep(ctx, nil); err != nil { t.Errorf("unexpected error: %s", err.Error()) } spans := rec.Flush() if want, have := 1, len(spans); want != have { t.Fatalf("incorrect number of spans, want %d, have %d", want, have) } if spans[0].SpanContext.ParentID == nil { t.Fatalf("incorrect parent ID, want %s have nil", parentSpan.Context().ID) } if want, have := parentSpan.Context().ID, *spans[0].SpanContext.ParentID; want != have { t.Fatalf("incorrect parent ID, want %s, have %s", want, have) } } func TestGRPCServerTrace(t *testing.T) { rec := recorder.NewReporter() defer rec.Close() tr, _ := zipkin.NewTracer(rec) serverTracer := kitzipkin.GRPCServerTrace(tr) server := grpctransport.NewServer( endpoint.Nop, func(context.Context, interface{}) (interface{}, error) { return nil, nil }, func(context.Context, interface{}) (interface{}, error) { return nil, nil }, serverTracer, ) md := metadata.MD{} parentSpan := tr.StartSpan("test") b3.InjectGRPC(&md)(parentSpan.Context()) ctx := metadata.NewIncomingContext(context.Background(), md) server.ServeGRPC(ctx, nil) spans := rec.Flush() if want, have := 1, len(spans); want != have { t.Fatalf("incorrect number of spans, want %d, have %d", want, have) } if want, have := parentSpan.Context().TraceID, spans[0].SpanContext.TraceID; want != have { t.Errorf("incorrect TraceID, want %+v, have %+v", want, have) } if want, have := parentSpan.Context().ID, spans[0].SpanContext.ID; want != have { t.Errorf("incorrect span ID, want %d, have %d", want, have) } } golang-github-go-kit-kit-0.13.0/tracing/zipkin/http.go000066400000000000000000000140511443521372500225470ustar00rootroot00000000000000package zipkin import ( "context" "net/http" "strconv" zipkin "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation/b3" kithttp "github.com/go-kit/kit/transport/http" "github.com/go-kit/log" ) // HTTPClientTrace enables native Zipkin tracing of a Go kit HTTP transport // Client. // // Go kit creates HTTP transport clients per remote endpoint. This middleware // can be set-up individually by adding the endpoint name for each of the Go kit // transport clients using the Name() TracerOption. // If wanting to use the HTTP Method (Get, Post, Put, etc.) as Span name you can // create a global client tracer omitting the Name() TracerOption, which you can // then feed to each Go kit transport client. // If instrumenting a client to an external (not on your platform) service, you // will probably want to disallow propagation of SpanContext using the // AllowPropagation TracerOption and setting it to false. func HTTPClientTrace(tracer *zipkin.Tracer, options ...TracerOption) kithttp.ClientOption { config := tracerOptions{ tags: make(map[string]string), name: "", logger: log.NewNopLogger(), propagate: true, } for _, option := range options { option(&config) } clientBefore := kithttp.ClientBefore( func(ctx context.Context, req *http.Request) context.Context { var ( spanContext model.SpanContext name string ) if config.name != "" { name = config.name } else { name = req.Method } if parent := zipkin.SpanFromContext(ctx); parent != nil { spanContext = parent.Context() } tags := map[string]string{ string(zipkin.TagHTTPMethod): req.Method, string(zipkin.TagHTTPUrl): req.URL.String(), } span := tracer.StartSpan( name, zipkin.Kind(model.Client), zipkin.Tags(config.tags), zipkin.Tags(tags), zipkin.Parent(spanContext), zipkin.FlushOnFinish(false), ) if config.propagate { if err := b3.InjectHTTP(req)(span.Context()); err != nil { config.logger.Log("err", err) } } return zipkin.NewContext(ctx, span) }, ) clientAfter := kithttp.ClientAfter( func(ctx context.Context, res *http.Response) context.Context { if span := zipkin.SpanFromContext(ctx); span != nil { zipkin.TagHTTPResponseSize.Set(span, strconv.FormatInt(res.ContentLength, 10)) zipkin.TagHTTPStatusCode.Set(span, strconv.Itoa(res.StatusCode)) if res.StatusCode > 399 { zipkin.TagError.Set(span, strconv.Itoa(res.StatusCode)) } span.Finish() } return ctx }, ) clientFinalizer := kithttp.ClientFinalizer( func(ctx context.Context, err error) { if span := zipkin.SpanFromContext(ctx); span != nil { if err != nil { zipkin.TagError.Set(span, err.Error()) } // calling span.Finish() a second time is a noop, if we didn't get to // ClientAfter we can at least time the early bail out by calling it // here. span.Finish() // send span to the Reporter span.Flush() } }, ) return func(c *kithttp.Client) { clientBefore(c) clientAfter(c) clientFinalizer(c) } } // HTTPServerTrace enables native Zipkin tracing of a Go kit HTTP transport // Server. // // Go kit creates HTTP transport servers per HTTP endpoint. This middleware can // be set-up individually by adding the method name for each of the Go kit // method servers using the Name() TracerOption. // If wanting to use the HTTP method (Get, Post, Put, etc.) as Span name you can // create a global server tracer omitting the Name() TracerOption, which you can // then feed to each Go kit method server. // // If instrumenting a service to external (not on your platform) clients, you // will probably want to disallow propagation of a client SpanContext using // the AllowPropagation TracerOption and setting it to false. func HTTPServerTrace(tracer *zipkin.Tracer, options ...TracerOption) kithttp.ServerOption { config := tracerOptions{ tags: make(map[string]string), name: "", logger: log.NewNopLogger(), propagate: true, } for _, option := range options { option(&config) } serverBefore := kithttp.ServerBefore( func(ctx context.Context, req *http.Request) context.Context { var ( spanContext model.SpanContext name string ) if config.name != "" { name = config.name } else { name = req.Method } if config.propagate { spanContext = tracer.Extract(b3.ExtractHTTP(req)) if spanContext.Sampled == nil && config.requestSampler != nil { sample := config.requestSampler(req) spanContext.Sampled = &sample } if spanContext.Err != nil { config.logger.Log("err", spanContext.Err) } } tags := map[string]string{ string(zipkin.TagHTTPMethod): req.Method, string(zipkin.TagHTTPPath): req.URL.Path, } span := tracer.StartSpan( name, zipkin.Kind(model.Server), zipkin.Tags(config.tags), zipkin.Tags(tags), zipkin.Parent(spanContext), zipkin.FlushOnFinish(false), ) return zipkin.NewContext(ctx, span) }, ) serverAfter := kithttp.ServerAfter( func(ctx context.Context, _ http.ResponseWriter) context.Context { if span := zipkin.SpanFromContext(ctx); span != nil { span.Finish() } return ctx }, ) serverFinalizer := kithttp.ServerFinalizer( func(ctx context.Context, code int, r *http.Request) { if span := zipkin.SpanFromContext(ctx); span != nil { zipkin.TagHTTPStatusCode.Set(span, strconv.Itoa(code)) if code > 399 { // set http status as error tag (if already set, this is a noop) zipkin.TagError.Set(span, http.StatusText(code)) } if rs, ok := ctx.Value(kithttp.ContextKeyResponseSize).(int64); ok { zipkin.TagHTTPResponseSize.Set(span, strconv.FormatInt(rs, 10)) } // calling span.Finish() a second time is a noop, if we didn't get to // ServerAfter we can at least time the early bail out by calling it // here. span.Finish() // send span to the Reporter span.Flush() } }, ) return func(s *kithttp.Server) { serverBefore(s) serverAfter(s) serverFinalizer(s) } } golang-github-go-kit-kit-0.13.0/tracing/zipkin/http_test.go000066400000000000000000000153451443521372500236150ustar00rootroot00000000000000package zipkin_test import ( "context" "errors" "fmt" "net/http" "net/http/httptest" "net/url" "reflect" "testing" zipkin "github.com/openzipkin/zipkin-go" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/propagation/b3" "github.com/openzipkin/zipkin-go/reporter/recorder" "github.com/go-kit/kit/endpoint" zipkinkit "github.com/go-kit/kit/tracing/zipkin" kithttp "github.com/go-kit/kit/transport/http" ) const ( testName = "test" testBody = "test_body" testTagKey = "test_key" testTagValue = "test_value" ) func TestHTTPClientTracePropagatesParentSpan(t *testing.T) { rec := recorder.NewReporter() defer rec.Close() tr, _ := zipkin.NewTracer(rec) rURL, _ := url.Parse("https://httpbin.org/get") clientTracer := zipkinkit.HTTPClientTrace(tr) ep := kithttp.NewClient( "GET", rURL, func(ctx context.Context, r *http.Request, i interface{}) error { return nil }, func(ctx context.Context, r *http.Response) (response interface{}, err error) { return nil, nil }, clientTracer, ).Endpoint() parentSpan := tr.StartSpan("test") ctx := zipkin.NewContext(context.Background(), parentSpan) _, err := ep(ctx, nil) if err != nil { t.Fatalf("unexpected error: %s", err.Error()) } spans := rec.Flush() if want, have := 1, len(spans); want != have { t.Fatalf("incorrect number of spans, want %d, have %d", want, have) } span := spans[0] if span.SpanContext.ParentID == nil { t.Fatalf("incorrect parent ID, want %s have nil", parentSpan.Context().ID) } if want, have := parentSpan.Context().ID, *span.SpanContext.ParentID; want != have { t.Fatalf("incorrect parent ID, want %s, have %s", want, have) } } func TestHTTPClientTraceAddsExpectedTags(t *testing.T) { dataProvider := []struct { ResponseStatusCode int ErrorTagValue string }{ {http.StatusOK, ""}, {http.StatusForbidden, fmt.Sprint(http.StatusForbidden)}, } for _, data := range dataProvider { testHTTPClientTraceCase(t, data.ResponseStatusCode, data.ErrorTagValue) } } func testHTTPClientTraceCase(t *testing.T, responseStatusCode int, errTagValue string) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(responseStatusCode) w.Write([]byte(testBody)) })) defer ts.Close() rec := recorder.NewReporter() defer rec.Close() tr, err := zipkin.NewTracer(rec) if err != nil { t.Errorf("Unwanted error: %s", err.Error()) } rMethod := "GET" rURL, _ := url.Parse(ts.URL) clientTracer := zipkinkit.HTTPClientTrace( tr, zipkinkit.Name(testName), zipkinkit.Tags(map[string]string{testTagKey: testTagValue}), ) ep := kithttp.NewClient( rMethod, rURL, func(ctx context.Context, r *http.Request, i interface{}) error { return nil }, func(ctx context.Context, r *http.Response) (response interface{}, err error) { return nil, nil }, clientTracer, ).Endpoint() _, err = ep(context.Background(), nil) if err != nil { t.Fatalf("unwanted error: %s", err.Error()) } spans := rec.Flush() if want, have := 1, len(spans); want != have { t.Fatalf("incorrect number of spans, wanted %d, got %d", want, have) } span := spans[0] if span.SpanContext.ParentID != nil { t.Fatalf("incorrect parentID, wanted nil, got %s", span.SpanContext.ParentID) } if want, have := testName, span.Name; want != have { t.Fatalf("incorrect span name, wanted %s, got %s", want, have) } if want, have := model.Client, span.Kind; want != have { t.Fatalf("incorrect span kind, wanted %s, got %s", want, have) } tags := map[string]string{ testTagKey: testTagValue, string(zipkin.TagHTTPStatusCode): fmt.Sprint(responseStatusCode), string(zipkin.TagHTTPMethod): rMethod, string(zipkin.TagHTTPUrl): rURL.String(), string(zipkin.TagHTTPResponseSize): fmt.Sprint(len(testBody)), } if errTagValue != "" { tags[string(zipkin.TagError)] = fmt.Sprint(errTagValue) } if !reflect.DeepEqual(span.Tags, tags) { t.Fatalf("invalid tags set, wanted %+v, got %+v", tags, span.Tags) } } func TestHTTPServerTrace(t *testing.T) { rec := recorder.NewReporter() defer rec.Close() // explicitly show we use the default of RPC shared spans in Zipkin as it // is idiomatic for Zipkin to share span identifiers between client and // server side. tr, _ := zipkin.NewTracer(rec, zipkin.WithSharedSpans(true)) handler := kithttp.NewServer( endpoint.Nop, func(context.Context, *http.Request) (interface{}, error) { return nil, nil }, func(context.Context, http.ResponseWriter, interface{}) error { return errors.New("dummy") }, zipkinkit.HTTPServerTrace(tr), ) server := httptest.NewServer(handler) defer server.Close() const httpMethod = "GET" req, err := http.NewRequest(httpMethod, server.URL, nil) if err != nil { t.Fatalf("unable to create HTTP request: %s", err.Error()) } parentSpan := tr.StartSpan("Dummy") b3.InjectHTTP(req)(parentSpan.Context()) client := http.Client{} resp, err := client.Do(req) if err != nil { t.Fatalf("unable to send HTTP request: %s", err.Error()) } resp.Body.Close() spans := rec.Flush() if want, have := 1, len(spans); want != have { t.Fatalf("incorrect number of spans, want %d, have %d", want, have) } if want, have := parentSpan.Context().TraceID, spans[0].SpanContext.TraceID; want != have { t.Errorf("incorrect TraceID, want %+v, have %+v", want, have) } if want, have := parentSpan.Context().ID, spans[0].SpanContext.ID; want != have { t.Errorf("incorrect span ID, want %d, have %d", want, have) } if want, have := httpMethod, spans[0].Name; want != have { t.Errorf("incorrect span name, want %s, have %s", want, have) } if want, have := http.StatusText(500), spans[0].Tags["error"]; want != have { t.Fatalf("incorrect error tag, want %s, have %s", want, have) } } func TestHTTPServerTraceIsRequestBasedSampled(t *testing.T) { rec := recorder.NewReporter() defer rec.Close() const httpMethod = "DELETE" tr, _ := zipkin.NewTracer(rec) handler := kithttp.NewServer( endpoint.Nop, func(context.Context, *http.Request) (interface{}, error) { return nil, nil }, func(context.Context, http.ResponseWriter, interface{}) error { return nil }, zipkinkit.HTTPServerTrace(tr, zipkinkit.RequestSampler(func(r *http.Request) bool { return r.Method == httpMethod })), ) server := httptest.NewServer(handler) defer server.Close() req, err := http.NewRequest(httpMethod, server.URL, nil) if err != nil { t.Fatalf("unable to create HTTP request: %s", err.Error()) } client := http.Client{} resp, err := client.Do(req) if err != nil { t.Fatalf("unable to send HTTP request: %s", err.Error()) } resp.Body.Close() spans := rec.Flush() if want, have := 1, len(spans); want != have { t.Fatalf("incorrect number of spans, want %d, have %d", want, have) } } golang-github-go-kit-kit-0.13.0/tracing/zipkin/options.go000066400000000000000000000036711443521372500232710ustar00rootroot00000000000000package zipkin import ( "net/http" "github.com/go-kit/log" ) // TracerOption allows for functional options to our Zipkin tracing middleware. type TracerOption func(o *tracerOptions) // Name sets the name for an instrumented transport endpoint. If name is omitted // at tracing middleware creation, the method of the transport or transport rpc // name is used. func Name(name string) TracerOption { return func(o *tracerOptions) { o.name = name } } // Tags adds default tags to our Zipkin transport spans. func Tags(tags map[string]string) TracerOption { return func(o *tracerOptions) { for k, v := range tags { o.tags[k] = v } } } // Logger adds a Go kit logger to our Zipkin Middleware to log SpanContext // extract / inject errors if they occur. Default is Noop. func Logger(logger log.Logger) TracerOption { return func(o *tracerOptions) { if logger != nil { o.logger = logger } } } // AllowPropagation instructs the tracer to allow or deny propagation of the // span context between this instrumented client or service and its peers. If // the instrumented client connects to services outside its own platform or if // the instrumented service receives requests from untrusted clients it is // strongly advised to disallow propagation. Propagation between services inside // your own platform benefit from propagation. Default for both TraceClient and // TraceServer is to allow propagation. func AllowPropagation(propagate bool) TracerOption { return func(o *tracerOptions) { o.propagate = propagate } } // RequestSampler allows one to set the sampling decision based on the details // found in the http.Request. func RequestSampler(sampleFunc func(r *http.Request) bool) TracerOption { return func(o *tracerOptions) { o.requestSampler = sampleFunc } } type tracerOptions struct { tags map[string]string name string logger log.Logger propagate bool requestSampler func(r *http.Request) bool } golang-github-go-kit-kit-0.13.0/transport/000077500000000000000000000000001443521372500203415ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/transport/amqp/000077500000000000000000000000001443521372500212775ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/transport/amqp/doc.go000066400000000000000000000000731443521372500223730ustar00rootroot00000000000000// Package amqp implements an AMQP transport. package amqp golang-github-go-kit-kit-0.13.0/transport/amqp/encode_decode.go000066400000000000000000000017331443521372500243720ustar00rootroot00000000000000package amqp import ( "context" amqp "github.com/rabbitmq/amqp091-go" ) // DecodeRequestFunc extracts a user-domain request object from // an AMQP Delivery object. It is designed to be used in AMQP Subscribers. type DecodeRequestFunc func(context.Context, *amqp.Delivery) (request interface{}, err error) // EncodeRequestFunc encodes the passed request object into // an AMQP Publishing object. It is designed to be used in AMQP Publishers. type EncodeRequestFunc func(context.Context, *amqp.Publishing, interface{}) error // EncodeResponseFunc encodes the passed response object to // an AMQP Publishing object. It is designed to be used in AMQP Subscribers. type EncodeResponseFunc func(context.Context, *amqp.Publishing, interface{}) error // DecodeResponseFunc extracts a user-domain response object from // an AMQP Delivery object. It is designed to be used in AMQP Publishers. type DecodeResponseFunc func(context.Context, *amqp.Delivery) (response interface{}, err error) golang-github-go-kit-kit-0.13.0/transport/amqp/publisher.go000066400000000000000000000110521443521372500236220ustar00rootroot00000000000000package amqp import ( "context" "time" "github.com/go-kit/kit/endpoint" amqp "github.com/rabbitmq/amqp091-go" ) // The golang AMQP implementation requires the []byte representation of // correlation id strings to have a maximum length of 255 bytes. const maxCorrelationIdLength = 255 // Publisher wraps an AMQP channel and queue, and provides a method that // implements endpoint.Endpoint. type Publisher struct { ch Channel q *amqp.Queue enc EncodeRequestFunc dec DecodeResponseFunc before []RequestFunc after []PublisherResponseFunc deliverer Deliverer timeout time.Duration } // NewPublisher constructs a usable Publisher for a single remote method. func NewPublisher( ch Channel, q *amqp.Queue, enc EncodeRequestFunc, dec DecodeResponseFunc, options ...PublisherOption, ) *Publisher { p := &Publisher{ ch: ch, q: q, enc: enc, dec: dec, deliverer: DefaultDeliverer, timeout: 10 * time.Second, } for _, option := range options { option(p) } return p } // PublisherOption sets an optional parameter for clients. type PublisherOption func(*Publisher) // PublisherBefore sets the RequestFuncs that are applied to the outgoing AMQP // request before it's invoked. func PublisherBefore(before ...RequestFunc) PublisherOption { return func(p *Publisher) { p.before = append(p.before, before...) } } // PublisherAfter sets the ClientResponseFuncs applied to the incoming AMQP // request prior to it being decoded. This is useful for obtaining anything off // of the response and adding onto the context prior to decoding. func PublisherAfter(after ...PublisherResponseFunc) PublisherOption { return func(p *Publisher) { p.after = append(p.after, after...) } } // PublisherDeliverer sets the deliverer function that the Publisher invokes. func PublisherDeliverer(deliverer Deliverer) PublisherOption { return func(p *Publisher) { p.deliverer = deliverer } } // PublisherTimeout sets the available timeout for an AMQP request. func PublisherTimeout(timeout time.Duration) PublisherOption { return func(p *Publisher) { p.timeout = timeout } } // Endpoint returns a usable endpoint that invokes the remote endpoint. func (p Publisher) Endpoint() endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { ctx, cancel := context.WithTimeout(ctx, p.timeout) defer cancel() pub := amqp.Publishing{ ReplyTo: p.q.Name, CorrelationId: randomString(randInt(5, maxCorrelationIdLength)), } if err := p.enc(ctx, &pub, request); err != nil { return nil, err } for _, f := range p.before { // Affect only amqp.Publishing ctx = f(ctx, &pub, nil) } deliv, err := p.deliverer(ctx, p, &pub) if err != nil { return nil, err } for _, f := range p.after { ctx = f(ctx, deliv) } response, err := p.dec(ctx, deliv) if err != nil { return nil, err } return response, nil } } // Deliverer is invoked by the Publisher to publish the specified Publishing, and to // retrieve the appropriate response Delivery object. type Deliverer func( context.Context, Publisher, *amqp.Publishing, ) (*amqp.Delivery, error) // DefaultDeliverer is a deliverer that publishes the specified Publishing // and returns the first Delivery object with the matching correlationId. // If the context times out while waiting for a reply, an error will be returned. func DefaultDeliverer( ctx context.Context, p Publisher, pub *amqp.Publishing, ) (*amqp.Delivery, error) { err := p.ch.Publish( getPublishExchange(ctx), getPublishKey(ctx), false, //mandatory false, //immediate *pub, ) if err != nil { return nil, err } autoAck := getConsumeAutoAck(ctx) msg, err := p.ch.Consume( p.q.Name, "", //consumer autoAck, false, //exclusive false, //noLocal false, //noWait getConsumeArgs(ctx), ) if err != nil { return nil, err } for { select { case d := <-msg: if d.CorrelationId == pub.CorrelationId { if !autoAck { d.Ack(false) //multiple } return &d, nil } case <-ctx.Done(): return nil, ctx.Err() } } } // SendAndForgetDeliverer delivers the supplied publishing and // returns a nil response. // When using this deliverer please ensure that the supplied DecodeResponseFunc and // PublisherResponseFunc are able to handle nil-type responses. func SendAndForgetDeliverer( ctx context.Context, p Publisher, pub *amqp.Publishing, ) (*amqp.Delivery, error) { err := p.ch.Publish( getPublishExchange(ctx), getPublishKey(ctx), false, //mandatory false, //immediate *pub, ) return nil, err } golang-github-go-kit-kit-0.13.0/transport/amqp/publisher_test.go000066400000000000000000000137041443521372500246670ustar00rootroot00000000000000package amqp_test import ( "context" "encoding/json" "errors" "testing" "time" amqptransport "github.com/go-kit/kit/transport/amqp" amqp "github.com/rabbitmq/amqp091-go" ) var ( defaultContentType = "" defaultContentEncoding = "" ) // TestBadEncode tests if encode errors are handled properly. func TestBadEncode(t *testing.T) { ch := &mockChannel{f: nullFunc} q := &amqp.Queue{Name: "some queue"} pub := amqptransport.NewPublisher( ch, q, func(context.Context, *amqp.Publishing, interface{}) error { return errors.New("err!") }, func(context.Context, *amqp.Delivery) (response interface{}, err error) { return struct{}{}, nil }, ) errChan := make(chan error, 1) var err error go func() { _, err := pub.Endpoint()(context.Background(), struct{}{}) errChan <- err }() select { case err = <-errChan: break case <-time.After(100 * time.Millisecond): t.Fatal("Timed out waiting for result") } if err == nil { t.Error("expected error") } if want, have := "err!", err.Error(); want != have { t.Errorf("want %s, have %s", want, have) } } // TestBadDecode tests if decode errors are handled properly. func TestBadDecode(t *testing.T) { cid := "correlation" ch := &mockChannel{ f: nullFunc, c: make(chan amqp.Publishing, 1), deliveries: []amqp.Delivery{ amqp.Delivery{ CorrelationId: cid, }, }, } q := &amqp.Queue{Name: "some queue"} pub := amqptransport.NewPublisher( ch, q, func(context.Context, *amqp.Publishing, interface{}) error { return nil }, func(context.Context, *amqp.Delivery) (response interface{}, err error) { return struct{}{}, errors.New("err!") }, amqptransport.PublisherBefore( amqptransport.SetCorrelationID(cid), ), ) var err error errChan := make(chan error, 1) go func() { _, err := pub.Endpoint()(context.Background(), struct{}{}) errChan <- err }() select { case err = <-errChan: break case <-time.After(100 * time.Millisecond): t.Fatal("Timed out waiting for result") } if err == nil { t.Error("expected error") } if want, have := "err!", err.Error(); want != have { t.Errorf("want %s, have %s", want, have) } } // TestPublisherTimeout ensures that the publisher timeout mechanism works. func TestPublisherTimeout(t *testing.T) { ch := &mockChannel{ f: nullFunc, c: make(chan amqp.Publishing, 1), deliveries: []amqp.Delivery{}, // no reply from mock subscriber } q := &amqp.Queue{Name: "some queue"} pub := amqptransport.NewPublisher( ch, q, func(context.Context, *amqp.Publishing, interface{}) error { return nil }, func(context.Context, *amqp.Delivery) (response interface{}, err error) { return struct{}{}, nil }, amqptransport.PublisherTimeout(50*time.Millisecond), ) var err error errChan := make(chan error, 1) go func() { _, err := pub.Endpoint()(context.Background(), struct{}{}) errChan <- err }() select { case err = <-errChan: break case <-time.After(100 * time.Millisecond): t.Fatal("timed out waiting for result") } if err == nil { t.Error("expected error") } if want, have := context.DeadlineExceeded.Error(), err.Error(); want != have { t.Errorf("want %s, have %s", want, have) } } func TestSuccessfulPublisher(t *testing.T) { cid := "correlation" mockReq := testReq{437} mockRes := testRes{ Squadron: mockReq.Squadron, Name: names[mockReq.Squadron], } b, err := json.Marshal(mockRes) if err != nil { t.Fatal(err) } reqChan := make(chan amqp.Publishing, 1) ch := &mockChannel{ f: nullFunc, c: reqChan, deliveries: []amqp.Delivery{ amqp.Delivery{ CorrelationId: cid, Body: b, }, }, } q := &amqp.Queue{Name: "some queue"} pub := amqptransport.NewPublisher( ch, q, testReqEncoder, testResDeliveryDecoder, amqptransport.PublisherBefore( amqptransport.SetCorrelationID(cid), ), ) var publishing amqp.Publishing var res testRes var ok bool resChan := make(chan interface{}, 1) errChan := make(chan error, 1) go func() { res, err := pub.Endpoint()(context.Background(), mockReq) if err != nil { errChan <- err } else { resChan <- res } }() select { case publishing = <-reqChan: break case <-time.After(100 * time.Millisecond): t.Fatal("timed out waiting for request") } if want, have := defaultContentType, publishing.ContentType; want != have { t.Errorf("want %s, have %s", want, have) } if want, have := defaultContentEncoding, publishing.ContentEncoding; want != have { t.Errorf("want %s, have %s", want, have) } select { case response := <-resChan: res, ok = response.(testRes) if !ok { t.Error("failed to assert endpoint response type") } break case err = <-errChan: break case <-time.After(100 * time.Millisecond): t.Fatal("timed out waiting for result") } if err != nil { t.Fatal(err) } if want, have := mockRes.Name, res.Name; want != have { t.Errorf("want %s, have %s", want, have) } } // TestSendAndForgetPublisher tests that the SendAndForgetDeliverer is working func TestSendAndForgetPublisher(t *testing.T) { ch := &mockChannel{ f: nullFunc, c: make(chan amqp.Publishing, 1), deliveries: []amqp.Delivery{}, // no reply from mock subscriber } q := &amqp.Queue{Name: "some queue"} pub := amqptransport.NewPublisher( ch, q, func(context.Context, *amqp.Publishing, interface{}) error { return nil }, func(context.Context, *amqp.Delivery) (response interface{}, err error) { return struct{}{}, nil }, amqptransport.PublisherDeliverer(amqptransport.SendAndForgetDeliverer), amqptransport.PublisherTimeout(50*time.Millisecond), ) var err error errChan := make(chan error, 1) finishChan := make(chan bool, 1) go func() { _, err := pub.Endpoint()(context.Background(), struct{}{}) if err != nil { errChan <- err } else { finishChan <- true } }() select { case <-finishChan: break case err = <-errChan: t.Errorf("unexpected error %s", err) case <-time.After(100 * time.Millisecond): t.Fatal("timed out waiting for result") } } golang-github-go-kit-kit-0.13.0/transport/amqp/request_response_func.go000066400000000000000000000141541443521372500262540ustar00rootroot00000000000000package amqp import ( "context" "time" amqp "github.com/rabbitmq/amqp091-go" ) // RequestFunc may take information from a publisher request and put it into a // request context. In Subscribers, RequestFuncs are executed prior to invoking // the endpoint. type RequestFunc func(context.Context, *amqp.Publishing, *amqp.Delivery) context.Context // SubscriberResponseFunc may take information from a request context and use it to // manipulate a Publisher. SubscriberResponseFuncs are only executed in // subscribers, after invoking the endpoint but prior to publishing a reply. type SubscriberResponseFunc func(context.Context, *amqp.Delivery, Channel, *amqp.Publishing, ) context.Context // PublisherResponseFunc may take information from an AMQP request and make the // response available for consumption. PublisherResponseFunc are only executed // in publishers, after a request has been made, but prior to it being decoded. type PublisherResponseFunc func(context.Context, *amqp.Delivery) context.Context // SetPublishExchange returns a RequestFunc that sets the Exchange field // of an AMQP Publish call. func SetPublishExchange(publishExchange string) RequestFunc { return func(ctx context.Context, pub *amqp.Publishing, _ *amqp.Delivery) context.Context { return context.WithValue(ctx, ContextKeyExchange, publishExchange) } } // SetPublishKey returns a RequestFunc that sets the Key field // of an AMQP Publish call. func SetPublishKey(publishKey string) RequestFunc { return func(ctx context.Context, pub *amqp.Publishing, _ *amqp.Delivery) context.Context { return context.WithValue(ctx, ContextKeyPublishKey, publishKey) } } // SetPublishDeliveryMode sets the delivery mode of a Publishing. // Please refer to AMQP delivery mode constants in the AMQP package. func SetPublishDeliveryMode(dmode uint8) RequestFunc { return func(ctx context.Context, pub *amqp.Publishing, _ *amqp.Delivery) context.Context { pub.DeliveryMode = dmode return ctx } } // SetNackSleepDuration returns a RequestFunc that sets the amount of time // to sleep in the event of a Nack. // This has to be used in conjunction with an error encoder that Nack and sleeps. // One example is the SingleNackRequeueErrorEncoder. // It is designed to be used by Subscribers. func SetNackSleepDuration(duration time.Duration) RequestFunc { return func(ctx context.Context, pub *amqp.Publishing, _ *amqp.Delivery) context.Context { return context.WithValue(ctx, ContextKeyNackSleepDuration, duration) } } // SetConsumeAutoAck returns a RequestFunc that sets whether or not to autoAck // messages when consuming. // When set to false, the publisher will Ack the first message it receives with // a matching correlationId. // It is designed to be used by Publishers. func SetConsumeAutoAck(autoAck bool) RequestFunc { return func(ctx context.Context, pub *amqp.Publishing, _ *amqp.Delivery) context.Context { return context.WithValue(ctx, ContextKeyAutoAck, autoAck) } } // SetConsumeArgs returns a RequestFunc that set the arguments for amqp Consume // function. // It is designed to be used by Publishers. func SetConsumeArgs(args amqp.Table) RequestFunc { return func(ctx context.Context, pub *amqp.Publishing, _ *amqp.Delivery) context.Context { return context.WithValue(ctx, ContextKeyConsumeArgs, args) } } // SetContentType returns a RequestFunc that sets the ContentType field of // an AMQP Publishing. func SetContentType(contentType string) RequestFunc { return func(ctx context.Context, pub *amqp.Publishing, _ *amqp.Delivery) context.Context { pub.ContentType = contentType return ctx } } // SetContentEncoding returns a RequestFunc that sets the ContentEncoding field // of an AMQP Publishing. func SetContentEncoding(contentEncoding string) RequestFunc { return func(ctx context.Context, pub *amqp.Publishing, _ *amqp.Delivery) context.Context { pub.ContentEncoding = contentEncoding return ctx } } // SetCorrelationID returns a RequestFunc that sets the CorrelationId field // of an AMQP Publishing. func SetCorrelationID(cid string) RequestFunc { return func(ctx context.Context, pub *amqp.Publishing, _ *amqp.Delivery) context.Context { pub.CorrelationId = cid return ctx } } // SetAckAfterEndpoint returns a SubscriberResponseFunc that prompts the service // to Ack the Delivery object after successfully evaluating the endpoint, // and before it encodes the response. // It is designed to be used by Subscribers. func SetAckAfterEndpoint(multiple bool) SubscriberResponseFunc { return func(ctx context.Context, deliv *amqp.Delivery, ch Channel, pub *amqp.Publishing, ) context.Context { deliv.Ack(multiple) return ctx } } func getPublishExchange(ctx context.Context) string { if exchange := ctx.Value(ContextKeyExchange); exchange != nil { return exchange.(string) } return "" } func getPublishKey(ctx context.Context) string { if publishKey := ctx.Value(ContextKeyPublishKey); publishKey != nil { return publishKey.(string) } return "" } func getNackSleepDuration(ctx context.Context) time.Duration { if duration := ctx.Value(ContextKeyNackSleepDuration); duration != nil { return duration.(time.Duration) } return 0 } func getConsumeAutoAck(ctx context.Context) bool { if autoAck := ctx.Value(ContextKeyAutoAck); autoAck != nil { return autoAck.(bool) } return false } func getConsumeArgs(ctx context.Context) amqp.Table { if args := ctx.Value(ContextKeyConsumeArgs); args != nil { return args.(amqp.Table) } return nil } type contextKey int const ( // ContextKeyExchange is the value of the reply Exchange in // amqp.Publish. ContextKeyExchange contextKey = iota // ContextKeyPublishKey is the value of the ReplyTo field in // amqp.Publish. ContextKeyPublishKey // ContextKeyNackSleepDuration is the duration to sleep for if the // service Nack and requeues a message. // This is to prevent sporadic send-resending of message // when a message is constantly Nack'd and requeued. ContextKeyNackSleepDuration // ContextKeyAutoAck is the value of autoAck field when calling // amqp.Channel.Consume. ContextKeyAutoAck // ContextKeyConsumeArgs is the value of consumeArgs field when calling // amqp.Channel.Consume. ContextKeyConsumeArgs ) golang-github-go-kit-kit-0.13.0/transport/amqp/subscriber.go000066400000000000000000000212501443521372500237710ustar00rootroot00000000000000package amqp import ( "context" "encoding/json" "time" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/transport" "github.com/go-kit/log" amqp "github.com/rabbitmq/amqp091-go" ) // Subscriber wraps an endpoint and provides a handler for AMQP Delivery messages. type Subscriber struct { e endpoint.Endpoint dec DecodeRequestFunc enc EncodeResponseFunc before []RequestFunc after []SubscriberResponseFunc responsePublisher ResponsePublisher errorEncoder ErrorEncoder errorHandler transport.ErrorHandler } // NewSubscriber constructs a new subscriber, which provides a handler // for AMQP Delivery messages. func NewSubscriber( e endpoint.Endpoint, dec DecodeRequestFunc, enc EncodeResponseFunc, options ...SubscriberOption, ) *Subscriber { s := &Subscriber{ e: e, dec: dec, enc: enc, responsePublisher: DefaultResponsePublisher, errorEncoder: DefaultErrorEncoder, errorHandler: transport.NewLogErrorHandler(log.NewNopLogger()), } for _, option := range options { option(s) } return s } // SubscriberOption sets an optional parameter for subscribers. type SubscriberOption func(*Subscriber) // SubscriberBefore functions are executed on the publisher delivery object // before the request is decoded. func SubscriberBefore(before ...RequestFunc) SubscriberOption { return func(s *Subscriber) { s.before = append(s.before, before...) } } // SubscriberAfter functions are executed on the subscriber reply after the // endpoint is invoked, but before anything is published to the reply. func SubscriberAfter(after ...SubscriberResponseFunc) SubscriberOption { return func(s *Subscriber) { s.after = append(s.after, after...) } } // SubscriberResponsePublisher is used by the subscriber to deliver response // objects to the original sender. // By default, the DefaultResponsePublisher is used. func SubscriberResponsePublisher(rp ResponsePublisher) SubscriberOption { return func(s *Subscriber) { s.responsePublisher = rp } } // SubscriberErrorEncoder is used to encode errors to the subscriber reply // whenever they're encountered in the processing of a request. Clients can // use this to provide custom error formatting. By default, // errors will be published with the DefaultErrorEncoder. func SubscriberErrorEncoder(ee ErrorEncoder) SubscriberOption { return func(s *Subscriber) { s.errorEncoder = ee } } // SubscriberErrorLogger is used to log non-terminal errors. By default, no errors // are logged. This is intended as a diagnostic measure. Finer-grained control // of error handling, including logging in more detail, should be performed in a // custom SubscriberErrorEncoder which has access to the context. // Deprecated: Use SubscriberErrorHandler instead. func SubscriberErrorLogger(logger log.Logger) SubscriberOption { return func(s *Subscriber) { s.errorHandler = transport.NewLogErrorHandler(logger) } } // SubscriberErrorHandler is used to handle non-terminal errors. By default, non-terminal errors // are ignored. This is intended as a diagnostic measure. Finer-grained control // of error handling, including logging in more detail, should be performed in a // custom SubscriberErrorEncoder which has access to the context. func SubscriberErrorHandler(errorHandler transport.ErrorHandler) SubscriberOption { return func(s *Subscriber) { s.errorHandler = errorHandler } } // ServeDelivery handles AMQP Delivery messages // It is strongly recommended to use *amqp.Channel as the // Channel interface implementation. func (s Subscriber) ServeDelivery(ch Channel) func(deliv *amqp.Delivery) { return func(deliv *amqp.Delivery) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() pub := amqp.Publishing{} for _, f := range s.before { ctx = f(ctx, &pub, deliv) } request, err := s.dec(ctx, deliv) if err != nil { s.errorHandler.Handle(ctx, err) s.errorEncoder(ctx, err, deliv, ch, &pub) return } response, err := s.e(ctx, request) if err != nil { s.errorHandler.Handle(ctx, err) s.errorEncoder(ctx, err, deliv, ch, &pub) return } for _, f := range s.after { ctx = f(ctx, deliv, ch, &pub) } if err := s.enc(ctx, &pub, response); err != nil { s.errorHandler.Handle(ctx, err) s.errorEncoder(ctx, err, deliv, ch, &pub) return } if err := s.responsePublisher(ctx, deliv, ch, &pub); err != nil { s.errorHandler.Handle(ctx, err) s.errorEncoder(ctx, err, deliv, ch, &pub) return } } } // EncodeJSONResponse marshals the response as JSON as part of the // payload of the AMQP Publishing object. func EncodeJSONResponse( ctx context.Context, pub *amqp.Publishing, response interface{}, ) error { b, err := json.Marshal(response) if err != nil { return err } pub.Body = b return nil } // EncodeNopResponse is a response function that does nothing. func EncodeNopResponse( ctx context.Context, pub *amqp.Publishing, response interface{}, ) error { return nil } // ResponsePublisher functions are executed by the subscriber to // publish response object to the original sender. // Please note that the word "publisher" does not refer // to the publisher of pub/sub. // Rather, publisher is merely a function that publishes, or sends responses. type ResponsePublisher func( context.Context, *amqp.Delivery, Channel, *amqp.Publishing, ) error // DefaultResponsePublisher extracts the reply exchange and reply key // from the request, and sends the response object to that destination. func DefaultResponsePublisher( ctx context.Context, deliv *amqp.Delivery, ch Channel, pub *amqp.Publishing, ) error { if pub.CorrelationId == "" { pub.CorrelationId = deliv.CorrelationId } replyExchange := getPublishExchange(ctx) replyTo := getPublishKey(ctx) if replyTo == "" { replyTo = deliv.ReplyTo } return ch.Publish( replyExchange, replyTo, false, // mandatory false, // immediate *pub, ) } // NopResponsePublisher does not deliver a response to the original sender. // This response publisher is used when the user wants the subscriber to // receive and forget. func NopResponsePublisher( ctx context.Context, deliv *amqp.Delivery, ch Channel, pub *amqp.Publishing, ) error { return nil } // ErrorEncoder is responsible for encoding an error to the subscriber reply. // Users are encouraged to use custom ErrorEncoders to encode errors to // their replies, and will likely want to pass and check for their own error // types. type ErrorEncoder func(ctx context.Context, err error, deliv *amqp.Delivery, ch Channel, pub *amqp.Publishing) // DefaultErrorEncoder simply ignores the message. It does not reply // nor Ack/Nack the message. func DefaultErrorEncoder(ctx context.Context, err error, deliv *amqp.Delivery, ch Channel, pub *amqp.Publishing) { } // SingleNackRequeueErrorEncoder issues a Nack to the delivery with multiple flag set as false // and requeue flag set as true. It does not reply the message. func SingleNackRequeueErrorEncoder(ctx context.Context, err error, deliv *amqp.Delivery, ch Channel, pub *amqp.Publishing) { deliv.Nack( false, //multiple true, //requeue ) duration := getNackSleepDuration(ctx) time.Sleep(duration) } // ReplyErrorEncoder serializes the error message as a DefaultErrorResponse // JSON and sends the message to the ReplyTo address. func ReplyErrorEncoder( ctx context.Context, err error, deliv *amqp.Delivery, ch Channel, pub *amqp.Publishing, ) { if pub.CorrelationId == "" { pub.CorrelationId = deliv.CorrelationId } replyExchange := getPublishExchange(ctx) replyTo := getPublishKey(ctx) if replyTo == "" { replyTo = deliv.ReplyTo } response := DefaultErrorResponse{err.Error()} b, err := json.Marshal(response) if err != nil { return } pub.Body = b ch.Publish( replyExchange, replyTo, false, // mandatory false, // immediate *pub, ) } // ReplyAndAckErrorEncoder serializes the error message as a DefaultErrorResponse // JSON and sends the message to the ReplyTo address then Acks the original // message. func ReplyAndAckErrorEncoder(ctx context.Context, err error, deliv *amqp.Delivery, ch Channel, pub *amqp.Publishing) { ReplyErrorEncoder(ctx, err, deliv, ch, pub) deliv.Ack(false) } // DefaultErrorResponse is the default structure of responses in the event // of an error. type DefaultErrorResponse struct { Error string `json:"err"` } // Channel is a channel interface to make testing possible. // It is highly recommended to use *amqp.Channel as the interface implementation. type Channel interface { Publish(exchange, key string, mandatory, immediate bool, msg amqp.Publishing) error Consume(queue, consumer string, autoAck, exclusive, noLocal, noWail bool, args amqp.Table) (<-chan amqp.Delivery, error) } golang-github-go-kit-kit-0.13.0/transport/amqp/subscriber_test.go000066400000000000000000000265301443521372500250360ustar00rootroot00000000000000package amqp_test import ( "context" "encoding/json" "errors" "testing" "time" amqptransport "github.com/go-kit/kit/transport/amqp" amqp "github.com/rabbitmq/amqp091-go" ) var ( errTypeAssertion = errors.New("type assertion error") ) // mockChannel is a mock of *amqp.Channel. type mockChannel struct { f func(exchange, key string, mandatory, immediate bool) c chan<- amqp.Publishing deliveries []amqp.Delivery } // Publish runs a test function f and sends resultant message to a channel. func (ch *mockChannel) Publish(exchange, key string, mandatory, immediate bool, msg amqp.Publishing) error { ch.f(exchange, key, mandatory, immediate) ch.c <- msg return nil } var nullFunc = func(exchange, key string, mandatory, immediate bool) { } func (ch *mockChannel) Consume(queue, consumer string, autoAck, exclusive, noLocal, noWail bool, args amqp.Table) (<-chan amqp.Delivery, error) { c := make(chan amqp.Delivery, len(ch.deliveries)) for _, d := range ch.deliveries { c <- d } return c, nil } // TestSubscriberBadDecode checks if decoder errors are handled properly. func TestSubscriberBadDecode(t *testing.T) { sub := amqptransport.NewSubscriber( func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, func(context.Context, *amqp.Delivery) (interface{}, error) { return nil, errors.New("err!") }, func(context.Context, *amqp.Publishing, interface{}) error { return nil }, amqptransport.SubscriberErrorEncoder(amqptransport.ReplyErrorEncoder), ) outputChan := make(chan amqp.Publishing, 1) ch := &mockChannel{f: nullFunc, c: outputChan} sub.ServeDelivery(ch)(&amqp.Delivery{}) var msg amqp.Publishing select { case msg = <-outputChan: break case <-time.After(100 * time.Millisecond): t.Fatal("Timed out waiting for publishing") } res, err := decodeSubscriberError(msg) if err != nil { t.Fatal(err) } if want, have := "err!", res.Error; want != have { t.Errorf("want %s, have %s", want, have) } } // TestSubscriberBadEndpoint checks if endpoint errors are handled properly. func TestSubscriberBadEndpoint(t *testing.T) { sub := amqptransport.NewSubscriber( func(context.Context, interface{}) (interface{}, error) { return nil, errors.New("err!") }, func(context.Context, *amqp.Delivery) (interface{}, error) { return struct{}{}, nil }, func(context.Context, *amqp.Publishing, interface{}) error { return nil }, amqptransport.SubscriberErrorEncoder(amqptransport.ReplyErrorEncoder), ) outputChan := make(chan amqp.Publishing, 1) ch := &mockChannel{f: nullFunc, c: outputChan} sub.ServeDelivery(ch)(&amqp.Delivery{}) var msg amqp.Publishing select { case msg = <-outputChan: break case <-time.After(100 * time.Millisecond): t.Fatal("Timed out waiting for publishing") } res, err := decodeSubscriberError(msg) if err != nil { t.Fatal(err) } if want, have := "err!", res.Error; want != have { t.Errorf("want %s, have %s", want, have) } } // TestSubscriberBadEncoder checks if encoder errors are handled properly. func TestSubscriberBadEncoder(t *testing.T) { sub := amqptransport.NewSubscriber( func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, func(context.Context, *amqp.Delivery) (interface{}, error) { return struct{}{}, nil }, func(context.Context, *amqp.Publishing, interface{}) error { return errors.New("err!") }, amqptransport.SubscriberErrorEncoder(amqptransport.ReplyErrorEncoder), ) outputChan := make(chan amqp.Publishing, 1) ch := &mockChannel{f: nullFunc, c: outputChan} sub.ServeDelivery(ch)(&amqp.Delivery{}) var msg amqp.Publishing select { case msg = <-outputChan: break case <-time.After(100 * time.Millisecond): t.Fatal("Timed out waiting for publishing") } res, err := decodeSubscriberError(msg) if err != nil { t.Fatal(err) } if want, have := "err!", res.Error; want != have { t.Errorf("want %s, have %s", want, have) } } // TestSubscriberSuccess checks if CorrelationId and ReplyTo are set properly // and if the payload is encoded properly. func TestSubscriberSuccess(t *testing.T) { cid := "correlation" replyTo := "sender" obj := testReq{ Squadron: 436, } b, err := json.Marshal(obj) if err != nil { t.Fatal(err) } sub := amqptransport.NewSubscriber( testEndpoint, testReqDecoder, amqptransport.EncodeJSONResponse, amqptransport.SubscriberErrorEncoder(amqptransport.ReplyErrorEncoder), ) checkReplyToFunc := func(exchange, key string, mandatory, immediate bool) { if want, have := replyTo, key; want != have { t.Errorf("want %s, have %s", want, have) } } outputChan := make(chan amqp.Publishing, 1) ch := &mockChannel{f: checkReplyToFunc, c: outputChan} sub.ServeDelivery(ch)(&amqp.Delivery{ CorrelationId: cid, ReplyTo: replyTo, Body: b, }) var msg amqp.Publishing select { case msg = <-outputChan: break case <-time.After(100 * time.Millisecond): t.Fatal("Timed out waiting for publishing") } if want, have := cid, msg.CorrelationId; want != have { t.Errorf("want %s, have %s", want, have) } // check if error is not thrown errRes, err := decodeSubscriberError(msg) if err != nil { t.Fatal(err) } if errRes.Error != "" { t.Error("Received error from subscriber", errRes.Error) return } // check obj vals response, err := testResDecoder(msg.Body) if err != nil { t.Fatal(err) } res, ok := response.(testRes) if !ok { t.Error(errTypeAssertion) } if want, have := obj.Squadron, res.Squadron; want != have { t.Errorf("want %d, have %d", want, have) } if want, have := names[obj.Squadron], res.Name; want != have { t.Errorf("want %s, have %s", want, have) } } // TestNopResponseSubscriber checks if setting responsePublisher to // NopResponsePublisher works properly by disabling response. func TestNopResponseSubscriber(t *testing.T) { cid := "correlation" replyTo := "sender" obj := testReq{ Squadron: 436, } b, err := json.Marshal(obj) if err != nil { t.Fatal(err) } sub := amqptransport.NewSubscriber( testEndpoint, testReqDecoder, amqptransport.EncodeJSONResponse, amqptransport.SubscriberResponsePublisher(amqptransport.NopResponsePublisher), amqptransport.SubscriberErrorEncoder(amqptransport.ReplyErrorEncoder), ) checkReplyToFunc := func(exchange, key string, mandatory, immediate bool) {} outputChan := make(chan amqp.Publishing, 1) ch := &mockChannel{f: checkReplyToFunc, c: outputChan} sub.ServeDelivery(ch)(&amqp.Delivery{ CorrelationId: cid, ReplyTo: replyTo, Body: b, }) select { case <-outputChan: t.Fatal("Subscriber with NopResponsePublisher replied.") case <-time.After(100 * time.Millisecond): break } } // TestSubscriberMultipleBefore checks if options to set exchange, key, deliveryMode // are working. func TestSubscriberMultipleBefore(t *testing.T) { exchange := "some exchange" key := "some key" deliveryMode := uint8(127) contentType := "some content type" contentEncoding := "some content encoding" sub := amqptransport.NewSubscriber( func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, func(context.Context, *amqp.Delivery) (interface{}, error) { return struct{}{}, nil }, amqptransport.EncodeJSONResponse, amqptransport.SubscriberErrorEncoder(amqptransport.ReplyErrorEncoder), amqptransport.SubscriberBefore( amqptransport.SetPublishExchange(exchange), amqptransport.SetPublishKey(key), amqptransport.SetPublishDeliveryMode(deliveryMode), amqptransport.SetContentType(contentType), amqptransport.SetContentEncoding(contentEncoding), ), ) checkReplyToFunc := func(exch, k string, mandatory, immediate bool) { if want, have := exchange, exch; want != have { t.Errorf("want %s, have %s", want, have) } if want, have := key, k; want != have { t.Errorf("want %s, have %s", want, have) } } outputChan := make(chan amqp.Publishing, 1) ch := &mockChannel{f: checkReplyToFunc, c: outputChan} sub.ServeDelivery(ch)(&amqp.Delivery{}) var msg amqp.Publishing select { case msg = <-outputChan: break case <-time.After(100 * time.Millisecond): t.Fatal("Timed out waiting for publishing") } // check if error is not thrown errRes, err := decodeSubscriberError(msg) if err != nil { t.Fatal(err) } if errRes.Error != "" { t.Error("Received error from subscriber", errRes.Error) return } if want, have := contentType, msg.ContentType; want != have { t.Errorf("want %s, have %s", want, have) } if want, have := contentEncoding, msg.ContentEncoding; want != have { t.Errorf("want %s, have %s", want, have) } if want, have := deliveryMode, msg.DeliveryMode; want != have { t.Errorf("want %d, have %d", want, have) } } // TestDefaultContentMetaData checks that default ContentType and Content-Encoding // is not set as mentioned by AMQP specification. func TestDefaultContentMetaData(t *testing.T) { defaultContentType := "" defaultContentEncoding := "" sub := amqptransport.NewSubscriber( func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, func(context.Context, *amqp.Delivery) (interface{}, error) { return struct{}{}, nil }, amqptransport.EncodeJSONResponse, amqptransport.SubscriberErrorEncoder(amqptransport.ReplyErrorEncoder), ) checkReplyToFunc := func(exch, k string, mandatory, immediate bool) {} outputChan := make(chan amqp.Publishing, 1) ch := &mockChannel{f: checkReplyToFunc, c: outputChan} sub.ServeDelivery(ch)(&amqp.Delivery{}) var msg amqp.Publishing select { case msg = <-outputChan: break case <-time.After(100 * time.Millisecond): t.Fatal("Timed out waiting for publishing") } // check if error is not thrown errRes, err := decodeSubscriberError(msg) if err != nil { t.Fatal(err) } if errRes.Error != "" { t.Error("Received error from subscriber", errRes.Error) return } if want, have := defaultContentType, msg.ContentType; want != have { t.Errorf("want %s, have %s", want, have) } if want, have := defaultContentEncoding, msg.ContentEncoding; want != have { t.Errorf("want %s, have %s", want, have) } } func decodeSubscriberError(pub amqp.Publishing) (amqptransport.DefaultErrorResponse, error) { var res amqptransport.DefaultErrorResponse err := json.Unmarshal(pub.Body, &res) return res, err } type testReq struct { Squadron int `json:"s"` } type testRes struct { Squadron int `json:"s"` Name string `json:"n"` } func testEndpoint(_ context.Context, request interface{}) (interface{}, error) { req, ok := request.(testReq) if !ok { return nil, errTypeAssertion } name, prs := names[req.Squadron] if !prs { return nil, errors.New("unknown squadron name") } res := testRes{ Squadron: req.Squadron, Name: name, } return res, nil } func testReqDecoder(_ context.Context, d *amqp.Delivery) (interface{}, error) { var obj testReq err := json.Unmarshal(d.Body, &obj) return obj, err } func testReqEncoder(_ context.Context, p *amqp.Publishing, request interface{}) error { req, ok := request.(testReq) if !ok { return errors.New("type assertion failure") } b, err := json.Marshal(req) if err != nil { return err } p.Body = b return nil } func testResDeliveryDecoder(_ context.Context, d *amqp.Delivery) (interface{}, error) { return testResDecoder(d.Body) } func testResDecoder(b []byte) (interface{}, error) { var obj testRes err := json.Unmarshal(b, &obj) return obj, err } var names = map[int]string{ 424: "tiger", 426: "thunderbird", 429: "bison", 436: "tusker", 437: "husky", } golang-github-go-kit-kit-0.13.0/transport/amqp/util.go000066400000000000000000000004041443521372500226010ustar00rootroot00000000000000package amqp import ( "math/rand" ) func randomString(l int) string { bytes := make([]byte, l) for i := 0; i < l; i++ { bytes[i] = byte(randInt(65, 90)) } return string(bytes) } func randInt(min int, max int) int { return min + rand.Intn(max-min) } golang-github-go-kit-kit-0.13.0/transport/awslambda/000077500000000000000000000000001443521372500222745ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/transport/awslambda/doc.go000066400000000000000000000001171443521372500233670ustar00rootroot00000000000000// Package awslambda provides an AWS Lambda transport layer. package awslambda golang-github-go-kit-kit-0.13.0/transport/awslambda/encode_decode.go000066400000000000000000000010121443521372500253550ustar00rootroot00000000000000package awslambda import ( "context" ) // DecodeRequestFunc extracts a user-domain request object from an // AWS Lambda payload. type DecodeRequestFunc func(context.Context, []byte) (interface{}, error) // EncodeResponseFunc encodes the passed response object into []byte, // ready to be sent as AWS Lambda response. type EncodeResponseFunc func(context.Context, interface{}) ([]byte, error) // ErrorEncoder is responsible for encoding an error. type ErrorEncoder func(ctx context.Context, err error) ([]byte, error) golang-github-go-kit-kit-0.13.0/transport/awslambda/handler.go000066400000000000000000000066071443521372500242510ustar00rootroot00000000000000package awslambda import ( "context" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/transport" "github.com/go-kit/log" ) // Handler wraps an endpoint. type Handler struct { e endpoint.Endpoint dec DecodeRequestFunc enc EncodeResponseFunc before []HandlerRequestFunc after []HandlerResponseFunc errorEncoder ErrorEncoder finalizer []HandlerFinalizerFunc errorHandler transport.ErrorHandler } // NewHandler constructs a new handler, which implements // the AWS lambda.Handler interface. func NewHandler( e endpoint.Endpoint, dec DecodeRequestFunc, enc EncodeResponseFunc, options ...HandlerOption, ) *Handler { h := &Handler{ e: e, dec: dec, enc: enc, errorEncoder: DefaultErrorEncoder, errorHandler: transport.NewLogErrorHandler(log.NewNopLogger()), } for _, option := range options { option(h) } return h } // HandlerOption sets an optional parameter for handlers. type HandlerOption func(*Handler) // HandlerBefore functions are executed on the payload byte, // before the request is decoded. func HandlerBefore(before ...HandlerRequestFunc) HandlerOption { return func(h *Handler) { h.before = append(h.before, before...) } } // HandlerAfter functions are only executed after invoking the endpoint // but prior to returning a response. func HandlerAfter(after ...HandlerResponseFunc) HandlerOption { return func(h *Handler) { h.after = append(h.after, after...) } } // HandlerErrorLogger is used to log non-terminal errors. // By default, no errors are logged. // Deprecated: Use HandlerErrorHandler instead. func HandlerErrorLogger(logger log.Logger) HandlerOption { return func(h *Handler) { h.errorHandler = transport.NewLogErrorHandler(logger) } } // HandlerErrorHandler is used to handle non-terminal errors. // By default, non-terminal errors are ignored. func HandlerErrorHandler(errorHandler transport.ErrorHandler) HandlerOption { return func(h *Handler) { h.errorHandler = errorHandler } } // HandlerErrorEncoder is used to encode errors. func HandlerErrorEncoder(ee ErrorEncoder) HandlerOption { return func(h *Handler) { h.errorEncoder = ee } } // HandlerFinalizer sets finalizer which are called at the end of // request. By default no finalizer is registered. func HandlerFinalizer(f ...HandlerFinalizerFunc) HandlerOption { return func(h *Handler) { h.finalizer = append(h.finalizer, f...) } } // DefaultErrorEncoder defines the default behavior of encoding an error response, // where it returns nil, and the error itself. func DefaultErrorEncoder(ctx context.Context, err error) ([]byte, error) { return nil, err } // Invoke represents implementation of the AWS lambda.Handler interface. func (h *Handler) Invoke( ctx context.Context, payload []byte, ) (resp []byte, err error) { if len(h.finalizer) > 0 { defer func() { for _, f := range h.finalizer { f(ctx, resp, err) } }() } for _, f := range h.before { ctx = f(ctx, payload) } request, err := h.dec(ctx, payload) if err != nil { h.errorHandler.Handle(ctx, err) return h.errorEncoder(ctx, err) } response, err := h.e(ctx, request) if err != nil { h.errorHandler.Handle(ctx, err) return h.errorEncoder(ctx, err) } for _, f := range h.after { ctx = f(ctx, response) } if resp, err = h.enc(ctx, response); err != nil { h.errorHandler.Handle(ctx, err) return h.errorEncoder(ctx, err) } return resp, err } golang-github-go-kit-kit-0.13.0/transport/awslambda/handler_test.go000066400000000000000000000210571443521372500253040ustar00rootroot00000000000000package awslambda import ( "context" "encoding/json" "fmt" "testing" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/transport" "github.com/go-kit/log" ) type key int const ( KeyBeforeOne key = iota KeyBeforeTwo key = iota KeyAfterOne key = iota KeyEncMode key = iota ) // Created based on github.com/aws/aws-lambda-go@v1.13.3/events.APIGatewayProxyRequest for the purposes of the tests below. type apiGatewayProxyRequest struct { Body string `json:"body"` } // Created based on github.com/aws/aws-lambda-go@v1.13.3/events.APIGatewayProxyResponse for the purposes of the tests below. type apiGatewayProxyResponse struct { StatusCode int `json:"statusCode"` Body string `json:"body"` } func TestDefaultErrorEncoder(t *testing.T) { ctx := context.Background() rootErr := fmt.Errorf("root") b, err := DefaultErrorEncoder(ctx, rootErr) if b != nil { t.Fatalf("DefaultErrorEncoder should return nil as []byte") } if err != rootErr { t.Fatalf("DefaultErrorEncoder expects return back the given error.") } } func TestInvokeHappyPath(t *testing.T) { svc := serviceTest01{} helloHandler := NewHandler( makeTest01HelloEndpoint(svc), decodeHelloRequestWithTwoBefores, encodeResponse, HandlerErrorHandler(transport.NewLogErrorHandler(log.NewNopLogger())), HandlerBefore(func( ctx context.Context, payload []byte, ) context.Context { ctx = context.WithValue(ctx, KeyBeforeOne, "bef1") return ctx }), HandlerBefore(func( ctx context.Context, payload []byte, ) context.Context { ctx = context.WithValue(ctx, KeyBeforeTwo, "bef2") return ctx }), HandlerAfter(func( ctx context.Context, response interface{}, ) context.Context { ctx = context.WithValue(ctx, KeyAfterOne, "af1") return ctx }), HandlerAfter(func( ctx context.Context, response interface{}, ) context.Context { if _, ok := ctx.Value(KeyAfterOne).(string); !ok { t.Fatalf("Value was not set properly during multi HandlerAfter") } return ctx }), HandlerFinalizer(func( _ context.Context, resp []byte, _ error, ) { apigwResp := apiGatewayProxyResponse{} err := json.Unmarshal(resp, &apigwResp) if err != nil { t.Fatalf("Should have no error, but got: %+v", err) } response := helloResponse{} err = json.Unmarshal([]byte(apigwResp.Body), &response) if err != nil { t.Fatalf("Should have no error, but got: %+v", err) } expectedGreeting := "hello john doe bef1 bef2" if response.Greeting != expectedGreeting { t.Fatalf( "Expect: %s, Actual: %s", expectedGreeting, response.Greeting) } }), ) ctx := context.Background() req, _ := json.Marshal(apiGatewayProxyRequest{ Body: `{"name":"john doe"}`, }) resp, err := helloHandler.Invoke(ctx, req) if err != nil { t.Fatalf("Should have no error, but got: %+v", err) } apigwResp := apiGatewayProxyResponse{} err = json.Unmarshal(resp, &apigwResp) if err != nil { t.Fatalf("Should have no error, but got: %+v", err) } response := helloResponse{} err = json.Unmarshal([]byte(apigwResp.Body), &response) if err != nil { t.Fatalf("Should have no error, but got: %+v", err) } expectedGreeting := "hello john doe bef1 bef2" if response.Greeting != expectedGreeting { t.Fatalf( "Expect: %s, Actual: %s", expectedGreeting, response.Greeting) } } func TestInvokeFailDecode(t *testing.T) { svc := serviceTest01{} helloHandler := NewHandler( makeTest01HelloEndpoint(svc), decodeHelloRequestWithTwoBefores, encodeResponse, HandlerErrorEncoder(func( ctx context.Context, err error, ) ([]byte, error) { apigwResp := apiGatewayProxyResponse{} apigwResp.Body = `{"error":"yes"}` apigwResp.StatusCode = 500 resp, err := json.Marshal(apigwResp) return resp, err }), ) ctx := context.Background() req, _ := json.Marshal(apiGatewayProxyRequest{ Body: `{"name":"john doe"}`, }) resp, err := helloHandler.Invoke(ctx, req) if err != nil { t.Fatalf("Should have no error, but got: %+v", err) } apigwResp := apiGatewayProxyResponse{} json.Unmarshal(resp, &apigwResp) if apigwResp.StatusCode != 500 { t.Fatalf("Expect status code of 500, instead of %d", apigwResp.StatusCode) } } func TestInvokeFailEndpoint(t *testing.T) { svc := serviceTest01{} helloHandler := NewHandler( makeTest01FailEndpoint(svc), decodeHelloRequestWithTwoBefores, encodeResponse, HandlerBefore(func( ctx context.Context, payload []byte, ) context.Context { ctx = context.WithValue(ctx, KeyBeforeOne, "bef1") return ctx }), HandlerBefore(func( ctx context.Context, payload []byte, ) context.Context { ctx = context.WithValue(ctx, KeyBeforeTwo, "bef2") return ctx }), HandlerErrorEncoder(func( ctx context.Context, err error, ) ([]byte, error) { apigwResp := apiGatewayProxyResponse{} apigwResp.Body = `{"error":"yes"}` apigwResp.StatusCode = 500 resp, err := json.Marshal(apigwResp) return resp, err }), ) ctx := context.Background() req, _ := json.Marshal(apiGatewayProxyRequest{ Body: `{"name":"john doe"}`, }) resp, err := helloHandler.Invoke(ctx, req) if err != nil { t.Fatalf("Should have no error, but got: %+v", err) } apigwResp := apiGatewayProxyResponse{} json.Unmarshal(resp, &apigwResp) if apigwResp.StatusCode != 500 { t.Fatalf("Expect status code of 500, instead of %d", apigwResp.StatusCode) } } func TestInvokeFailEncode(t *testing.T) { svc := serviceTest01{} helloHandler := NewHandler( makeTest01HelloEndpoint(svc), decodeHelloRequestWithTwoBefores, encodeResponse, HandlerBefore(func( ctx context.Context, payload []byte, ) context.Context { ctx = context.WithValue(ctx, KeyBeforeOne, "bef1") return ctx }), HandlerBefore(func( ctx context.Context, payload []byte, ) context.Context { ctx = context.WithValue(ctx, KeyBeforeTwo, "bef2") return ctx }), HandlerAfter(func( ctx context.Context, response interface{}, ) context.Context { ctx = context.WithValue(ctx, KeyEncMode, "fail_encode") return ctx }), HandlerErrorEncoder(func( ctx context.Context, err error, ) ([]byte, error) { // convert error into proper APIGateway response. apigwResp := apiGatewayProxyResponse{} apigwResp.Body = `{"error":"yes"}` apigwResp.StatusCode = 500 resp, err := json.Marshal(apigwResp) return resp, err }), ) ctx := context.Background() req, _ := json.Marshal(apiGatewayProxyRequest{ Body: `{"name":"john doe"}`, }) resp, err := helloHandler.Invoke(ctx, req) if err != nil { t.Fatalf("Should have no error, but got: %+v", err) } apigwResp := apiGatewayProxyResponse{} json.Unmarshal(resp, &apigwResp) if apigwResp.StatusCode != 500 { t.Fatalf("Expect status code of 500, instead of %d", apigwResp.StatusCode) } } func decodeHelloRequestWithTwoBefores( ctx context.Context, req []byte, ) (interface{}, error) { apigwReq := apiGatewayProxyRequest{} err := json.Unmarshal([]byte(req), &apigwReq) if err != nil { return apigwReq, err } request := helloRequest{} err = json.Unmarshal([]byte(apigwReq.Body), &request) if err != nil { return request, err } valOne, ok := ctx.Value(KeyBeforeOne).(string) if !ok { return request, fmt.Errorf( "Value was not set properly when multiple HandlerBefores are used") } valTwo, ok := ctx.Value(KeyBeforeTwo).(string) if !ok { return request, fmt.Errorf( "Value was not set properly when multiple HandlerBefores are used") } request.Name += " " + valOne + " " + valTwo return request, err } func encodeResponse( ctx context.Context, response interface{}, ) ([]byte, error) { apigwResp := apiGatewayProxyResponse{} mode, ok := ctx.Value(KeyEncMode).(string) if ok && mode == "fail_encode" { return nil, fmt.Errorf("fail encoding") } respByte, err := json.Marshal(response) if err != nil { return nil, err } apigwResp.Body = string(respByte) apigwResp.StatusCode = 200 resp, err := json.Marshal(apigwResp) return resp, err } type helloRequest struct { Name string `json:"name"` } type helloResponse struct { Greeting string `json:"greeting"` } func makeTest01HelloEndpoint(svc serviceTest01) endpoint.Endpoint { return func(_ context.Context, request interface{}) (interface{}, error) { req := request.(helloRequest) greeting := svc.hello(req.Name) return helloResponse{greeting}, nil } } func makeTest01FailEndpoint(_ serviceTest01) endpoint.Endpoint { return func(_ context.Context, request interface{}) (interface{}, error) { return nil, fmt.Errorf("test error endpoint") } } type serviceTest01 struct{} func (ts *serviceTest01) hello(name string) string { return fmt.Sprintf("hello %s", name) } golang-github-go-kit-kit-0.13.0/transport/awslambda/request_response_funcs.go000066400000000000000000000015241443521372500274310ustar00rootroot00000000000000package awslambda import ( "context" ) // HandlerRequestFunc may take information from the received // payload and use it to place items in the request scoped context. // HandlerRequestFuncs are executed prior to invoking the endpoint and // decoding of the payload. type HandlerRequestFunc func(ctx context.Context, payload []byte) context.Context // HandlerResponseFunc may take information from a request context // and use it to manipulate the response before it's marshaled. // HandlerResponseFunc are executed after invoking the endpoint // but prior to returning a response. type HandlerResponseFunc func(ctx context.Context, response interface{}) context.Context // HandlerFinalizerFunc is executed at the end of Invoke. // This can be used for logging purposes. type HandlerFinalizerFunc func(ctx context.Context, resp []byte, err error) golang-github-go-kit-kit-0.13.0/transport/doc.go000066400000000000000000000001401443521372500214300ustar00rootroot00000000000000// Package transport contains helpers applicable to all supported transports. package transport golang-github-go-kit-kit-0.13.0/transport/error_handler.go000066400000000000000000000017371443521372500235260ustar00rootroot00000000000000package transport import ( "context" "github.com/go-kit/log" ) // ErrorHandler receives a transport error to be processed for diagnostic purposes. // Usually this means logging the error. type ErrorHandler interface { Handle(ctx context.Context, err error) } // LogErrorHandler is a transport error handler implementation which logs an error. type LogErrorHandler struct { logger log.Logger } func NewLogErrorHandler(logger log.Logger) *LogErrorHandler { return &LogErrorHandler{ logger: logger, } } func (h *LogErrorHandler) Handle(ctx context.Context, err error) { h.logger.Log("err", err) } // The ErrorHandlerFunc type is an adapter to allow the use of // ordinary function as ErrorHandler. If f is a function // with the appropriate signature, ErrorHandlerFunc(f) is a // ErrorHandler that calls f. type ErrorHandlerFunc func(ctx context.Context, err error) // Handle calls f(ctx, err). func (f ErrorHandlerFunc) Handle(ctx context.Context, err error) { f(ctx, err) } golang-github-go-kit-kit-0.13.0/transport/error_handler_test.go000066400000000000000000000010651443521372500245570ustar00rootroot00000000000000package transport_test import ( "context" "errors" "testing" "github.com/go-kit/kit/transport" "github.com/go-kit/log" ) func TestLogErrorHandler(t *testing.T) { var output []interface{} logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error { output = append(output, keyvals...) return nil })) errorHandler := transport.NewLogErrorHandler(logger) err := errors.New("error") errorHandler.Handle(context.Background(), err) if output[1] != err { t.Errorf("expected an error log event: have %v, want %v", output[1], err) } } golang-github-go-kit-kit-0.13.0/transport/grpc/000077500000000000000000000000001443521372500212745ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/transport/grpc/README.md000066400000000000000000000037631443521372500225640ustar00rootroot00000000000000# grpc [gRPC](http://www.grpc.io/) is an excellent, modern IDL and transport for microservices. If you're starting a greenfield project, go-kit strongly recommends gRPC as your default transport. One important note is that while gRPC supports streaming requests and replies, go-kit does not. You can still use streams in your service, but their implementation will not be able to take advantage of many go-kit features like middleware. Using gRPC and go-kit together is very simple. First, define your service using protobuf3. This is explained [in gRPC documentation](http://www.grpc.io/docs/#defining-a-service). See [addsvc.proto](https://github.com/go-kit/examples/blob/master/addsvc/pb/addsvc.proto) for an example. Make sure the proto definition matches your service's go-kit (interface) definition. Next, get the protoc compiler. You can download pre-compiled binaries from the [protobuf release page](https://github.com/google/protobuf/releases). You will unzip a folder called `protoc3` with a subdirectory `bin` containing an executable. Move that executable somewhere in your `$PATH` and you're good to go! It can also be built from source. ```sh brew install autoconf automake libtool git clone https://github.com/google/protobuf cd protobuf ./autogen.sh ; ./configure ; make ; make install ``` Then, compile your service definition, from .proto to .go. ```sh protoc add.proto --go_out=plugins=grpc:. ``` Finally, write a tiny binding from your service definition to the gRPC definition. It's a simple conversion from one domain to another. See [grpc.go](https://github.com/go-kit/examples/blob/master/addsvc/pkg/addtransport/grpc.go) for an example. That's it! The gRPC binding can be bound to a listener and serve normal gRPC requests. And within your service, you can use standard go-kit components and idioms. See [addsvc](https://github.com/go-kit/examples/tree/master/addsvc/) for a complete working example with gRPC support. And remember: go-kit services can support multiple transports simultaneously. golang-github-go-kit-kit-0.13.0/transport/grpc/_grpc_test/000077500000000000000000000000001443521372500234255ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/transport/grpc/_grpc_test/client.go000066400000000000000000000020241443521372500252300ustar00rootroot00000000000000package test import ( "context" "google.golang.org/grpc" "github.com/go-kit/kit/endpoint" grpctransport "github.com/go-kit/kit/transport/grpc" "github.com/go-kit/kit/transport/grpc/_grpc_test/pb" ) type clientBinding struct { test endpoint.Endpoint } func (c *clientBinding) Test(ctx context.Context, a string, b int64) (context.Context, string, error) { response, err := c.test(ctx, TestRequest{A: a, B: b}) if err != nil { return nil, "", err } r := response.(*TestResponse) return r.Ctx, r.V, nil } func NewClient(cc *grpc.ClientConn) Service { return &clientBinding{ test: grpctransport.NewClient( cc, "pb.Test", "Test", encodeRequest, decodeResponse, &pb.TestResponse{}, grpctransport.ClientBefore( injectCorrelationID, ), grpctransport.ClientBefore( displayClientRequestHeaders, ), grpctransport.ClientAfter( displayClientResponseHeaders, displayClientResponseTrailers, ), grpctransport.ClientAfter( extractConsumedCorrelationID, ), ).Endpoint(), } } golang-github-go-kit-kit-0.13.0/transport/grpc/_grpc_test/context_metadata.go000066400000000000000000000077711443521372500273140ustar00rootroot00000000000000package test import ( "context" "fmt" "google.golang.org/grpc/metadata" ) type metaContext string const ( correlationID metaContext = "correlation-id" responseHDR metaContext = "my-response-header" responseTRLR metaContext = "my-response-trailer" correlationIDTRLR metaContext = "correlation-id-consumed" ) /* client before functions */ func injectCorrelationID(ctx context.Context, md *metadata.MD) context.Context { if hdr, ok := ctx.Value(correlationID).(string); ok { fmt.Printf("\tClient found correlationID %q in context, set metadata header\n", hdr) (*md)[string(correlationID)] = append((*md)[string(correlationID)], hdr) } return ctx } func displayClientRequestHeaders(ctx context.Context, md *metadata.MD) context.Context { if len(*md) > 0 { fmt.Println("\tClient >> Request Headers:") for key, val := range *md { fmt.Printf("\t\t%s: %s\n", key, val[len(val)-1]) } } return ctx } /* server before functions */ func extractCorrelationID(ctx context.Context, md metadata.MD) context.Context { if hdr, ok := md[string(correlationID)]; ok { cID := hdr[len(hdr)-1] ctx = context.WithValue(ctx, correlationID, cID) fmt.Printf("\tServer received correlationID %q in metadata header, set context\n", cID) } return ctx } func displayServerRequestHeaders(ctx context.Context, md metadata.MD) context.Context { if len(md) > 0 { fmt.Println("\tServer << Request Headers:") for key, val := range md { fmt.Printf("\t\t%s: %s\n", key, val[len(val)-1]) } } return ctx } /* server after functions */ func injectResponseHeader(ctx context.Context, md *metadata.MD, _ *metadata.MD) context.Context { *md = metadata.Join(*md, metadata.Pairs(string(responseHDR), "has-a-value")) return ctx } func displayServerResponseHeaders(ctx context.Context, md *metadata.MD, _ *metadata.MD) context.Context { if len(*md) > 0 { fmt.Println("\tServer >> Response Headers:") for key, val := range *md { fmt.Printf("\t\t%s: %s\n", key, val[len(val)-1]) } } return ctx } func injectResponseTrailer(ctx context.Context, _ *metadata.MD, md *metadata.MD) context.Context { *md = metadata.Join(*md, metadata.Pairs(string(responseTRLR), "has-a-value-too")) return ctx } func injectConsumedCorrelationID(ctx context.Context, _ *metadata.MD, md *metadata.MD) context.Context { if hdr, ok := ctx.Value(correlationID).(string); ok { fmt.Printf("\tServer found correlationID %q in context, set consumed trailer\n", hdr) *md = metadata.Join(*md, metadata.Pairs(string(correlationIDTRLR), hdr)) } return ctx } func displayServerResponseTrailers(ctx context.Context, _ *metadata.MD, md *metadata.MD) context.Context { if len(*md) > 0 { fmt.Println("\tServer >> Response Trailers:") for key, val := range *md { fmt.Printf("\t\t%s: %s\n", key, val[len(val)-1]) } } return ctx } /* client after functions */ func displayClientResponseHeaders(ctx context.Context, md metadata.MD, _ metadata.MD) context.Context { if len(md) > 0 { fmt.Println("\tClient << Response Headers:") for key, val := range md { fmt.Printf("\t\t%s: %s\n", key, val[len(val)-1]) } } return ctx } func displayClientResponseTrailers(ctx context.Context, _ metadata.MD, md metadata.MD) context.Context { if len(md) > 0 { fmt.Println("\tClient << Response Trailers:") for key, val := range md { fmt.Printf("\t\t%s: %s\n", key, val[len(val)-1]) } } return ctx } func extractConsumedCorrelationID(ctx context.Context, _ metadata.MD, md metadata.MD) context.Context { if hdr, ok := md[string(correlationIDTRLR)]; ok { fmt.Printf("\tClient received consumed correlationID %q in metadata trailer, set context\n", hdr[len(hdr)-1]) ctx = context.WithValue(ctx, correlationIDTRLR, hdr[len(hdr)-1]) } return ctx } /* CorrelationID context handlers */ func SetCorrelationID(ctx context.Context, v string) context.Context { return context.WithValue(ctx, correlationID, v) } func GetConsumedCorrelationID(ctx context.Context) string { if trlr, ok := ctx.Value(correlationIDTRLR).(string); ok { return trlr } return "" } golang-github-go-kit-kit-0.13.0/transport/grpc/_grpc_test/pb/000077500000000000000000000000001443521372500240265ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/transport/grpc/_grpc_test/pb/generate.go000066400000000000000000000004371443521372500261530ustar00rootroot00000000000000package pb //go:generate protoc test.proto --go_out=. --go-grpc_out=. --go_opt=Mtest.proto=github.com/go-kit/kit/transport/grpc/_grpc_test/pb --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative --go-grpc_opt=Mtest.proto=github.com/go-kit/kit/transport/grpc/_grpc_test/pb golang-github-go-kit-kit-0.13.0/transport/grpc/_grpc_test/pb/test.pb.go000066400000000000000000000136131443521372500257400ustar00rootroot00000000000000// Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 // protoc v3.16.0 // source: test.proto package pb import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) type TestRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields A string `protobuf:"bytes,1,opt,name=a,proto3" json:"a,omitempty"` B int64 `protobuf:"varint,2,opt,name=b,proto3" json:"b,omitempty"` } func (x *TestRequest) Reset() { *x = TestRequest{} if protoimpl.UnsafeEnabled { mi := &file_test_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *TestRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*TestRequest) ProtoMessage() {} func (x *TestRequest) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TestRequest.ProtoReflect.Descriptor instead. func (*TestRequest) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{0} } func (x *TestRequest) GetA() string { if x != nil { return x.A } return "" } func (x *TestRequest) GetB() int64 { if x != nil { return x.B } return 0 } type TestResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields V string `protobuf:"bytes,1,opt,name=v,proto3" json:"v,omitempty"` } func (x *TestResponse) Reset() { *x = TestResponse{} if protoimpl.UnsafeEnabled { mi := &file_test_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *TestResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*TestResponse) ProtoMessage() {} func (x *TestResponse) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TestResponse.ProtoReflect.Descriptor instead. func (*TestResponse) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{1} } func (x *TestResponse) GetV() string { if x != nil { return x.V } return "" } var File_test_proto protoreflect.FileDescriptor var file_test_proto_rawDesc = []byte{ 0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x70, 0x62, 0x22, 0x29, 0x0a, 0x0b, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0c, 0x0a, 0x01, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x61, 0x12, 0x0c, 0x0a, 0x01, 0x62, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x01, 0x62, 0x22, 0x1c, 0x0a, 0x0c, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0c, 0x0a, 0x01, 0x76, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x76, 0x32, 0x33, 0x0a, 0x04, 0x54, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x04, 0x54, 0x65, 0x73, 0x74, 0x12, 0x0f, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_test_proto_rawDescOnce sync.Once file_test_proto_rawDescData = file_test_proto_rawDesc ) func file_test_proto_rawDescGZIP() []byte { file_test_proto_rawDescOnce.Do(func() { file_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData) }) return file_test_proto_rawDescData } var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_test_proto_goTypes = []interface{}{ (*TestRequest)(nil), // 0: pb.TestRequest (*TestResponse)(nil), // 1: pb.TestResponse } var file_test_proto_depIdxs = []int32{ 0, // 0: pb.Test.Test:input_type -> pb.TestRequest 1, // 1: pb.Test.Test:output_type -> pb.TestResponse 1, // [1:2] is the sub-list for method output_type 0, // [0:1] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_test_proto_init() } func file_test_proto_init() { if File_test_proto != nil { return } if !protoimpl.UnsafeEnabled { file_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TestRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TestResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_test_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 1, }, GoTypes: file_test_proto_goTypes, DependencyIndexes: file_test_proto_depIdxs, MessageInfos: file_test_proto_msgTypes, }.Build() File_test_proto = out.File file_test_proto_rawDesc = nil file_test_proto_goTypes = nil file_test_proto_depIdxs = nil } golang-github-go-kit-kit-0.13.0/transport/grpc/_grpc_test/pb/test.proto000066400000000000000000000003071443521372500260720ustar00rootroot00000000000000syntax = "proto3"; package pb; service Test { rpc Test (TestRequest) returns (TestResponse) {} } message TestRequest { string a = 1; int64 b = 2; } message TestResponse { string v = 1; } golang-github-go-kit-kit-0.13.0/transport/grpc/_grpc_test/pb/test_grpc.pb.go000066400000000000000000000062011443521372500267460ustar00rootroot00000000000000// Code generated by protoc-gen-go-grpc. DO NOT EDIT. package pb import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // TestClient is the client API for Test service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type TestClient interface { Test(ctx context.Context, in *TestRequest, opts ...grpc.CallOption) (*TestResponse, error) } type testClient struct { cc grpc.ClientConnInterface } func NewTestClient(cc grpc.ClientConnInterface) TestClient { return &testClient{cc} } func (c *testClient) Test(ctx context.Context, in *TestRequest, opts ...grpc.CallOption) (*TestResponse, error) { out := new(TestResponse) err := c.cc.Invoke(ctx, "/pb.Test/Test", in, out, opts...) if err != nil { return nil, err } return out, nil } // TestServer is the server API for Test service. // All implementations must embed UnimplementedTestServer // for forward compatibility type TestServer interface { Test(context.Context, *TestRequest) (*TestResponse, error) mustEmbedUnimplementedTestServer() } // UnimplementedTestServer must be embedded to have forward compatible implementations. type UnimplementedTestServer struct { } func (UnimplementedTestServer) Test(context.Context, *TestRequest) (*TestResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Test not implemented") } func (UnimplementedTestServer) mustEmbedUnimplementedTestServer() {} // UnsafeTestServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to TestServer will // result in compilation errors. type UnsafeTestServer interface { mustEmbedUnimplementedTestServer() } func RegisterTestServer(s grpc.ServiceRegistrar, srv TestServer) { s.RegisterService(&Test_ServiceDesc, srv) } func _Test_Test_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(TestRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(TestServer).Test(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/pb.Test/Test", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(TestServer).Test(ctx, req.(*TestRequest)) } return interceptor(ctx, in, info, handler) } // Test_ServiceDesc is the grpc.ServiceDesc for Test service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var Test_ServiceDesc = grpc.ServiceDesc{ ServiceName: "pb.Test", HandlerType: (*TestServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Test", Handler: _Test_Test_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "test.proto", } golang-github-go-kit-kit-0.13.0/transport/grpc/_grpc_test/request_response.go000066400000000000000000000013011443521372500273550ustar00rootroot00000000000000package test import ( "context" "github.com/go-kit/kit/transport/grpc/_grpc_test/pb" ) func encodeRequest(ctx context.Context, req interface{}) (interface{}, error) { r := req.(TestRequest) return &pb.TestRequest{A: r.A, B: r.B}, nil } func decodeRequest(ctx context.Context, req interface{}) (interface{}, error) { r := req.(*pb.TestRequest) return TestRequest{A: r.A, B: r.B}, nil } func encodeResponse(ctx context.Context, resp interface{}) (interface{}, error) { r := resp.(*TestResponse) return &pb.TestResponse{V: r.V}, nil } func decodeResponse(ctx context.Context, resp interface{}) (interface{}, error) { r := resp.(*pb.TestResponse) return &TestResponse{V: r.V, Ctx: ctx}, nil } golang-github-go-kit-kit-0.13.0/transport/grpc/_grpc_test/server.go000066400000000000000000000027771443521372500252770ustar00rootroot00000000000000package test import ( "context" "fmt" "github.com/go-kit/kit/endpoint" grpctransport "github.com/go-kit/kit/transport/grpc" "github.com/go-kit/kit/transport/grpc/_grpc_test/pb" ) type service struct{} func (service) Test(ctx context.Context, a string, b int64) (context.Context, string, error) { return nil, fmt.Sprintf("%s = %d", a, b), nil } func NewService() Service { return service{} } func makeTestEndpoint(svc Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { req := request.(TestRequest) newCtx, v, err := svc.Test(ctx, req.A, req.B) return &TestResponse{ V: v, Ctx: newCtx, }, err } } type serverBinding struct { pb.UnimplementedTestServer test grpctransport.Handler } func (b *serverBinding) Test(ctx context.Context, req *pb.TestRequest) (*pb.TestResponse, error) { _, response, err := b.test.ServeGRPC(ctx, req) if err != nil { return nil, err } return response.(*pb.TestResponse), nil } func NewBinding(svc Service) *serverBinding { return &serverBinding{ test: grpctransport.NewServer( makeTestEndpoint(svc), decodeRequest, encodeResponse, grpctransport.ServerBefore( extractCorrelationID, ), grpctransport.ServerBefore( displayServerRequestHeaders, ), grpctransport.ServerAfter( injectResponseHeader, injectResponseTrailer, injectConsumedCorrelationID, ), grpctransport.ServerAfter( displayServerResponseHeaders, displayServerResponseTrailers, ), ), } } golang-github-go-kit-kit-0.13.0/transport/grpc/_grpc_test/service.go000066400000000000000000000003711443521372500254150ustar00rootroot00000000000000package test import "context" type Service interface { Test(ctx context.Context, a string, b int64) (context.Context, string, error) } type TestRequest struct { A string B int64 } type TestResponse struct { Ctx context.Context V string } golang-github-go-kit-kit-0.13.0/transport/grpc/client.go000066400000000000000000000075641443521372500231150ustar00rootroot00000000000000package grpc import ( "context" "fmt" "reflect" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "github.com/go-kit/kit/endpoint" ) // Client wraps a gRPC connection and provides a method that implements // endpoint.Endpoint. type Client struct { client *grpc.ClientConn serviceName string method string enc EncodeRequestFunc dec DecodeResponseFunc grpcReply reflect.Type before []ClientRequestFunc after []ClientResponseFunc finalizer []ClientFinalizerFunc } // NewClient constructs a usable Client for a single remote endpoint. // Pass an zero-value protobuf message of the RPC response type as // the grpcReply argument. func NewClient( cc *grpc.ClientConn, serviceName string, method string, enc EncodeRequestFunc, dec DecodeResponseFunc, grpcReply interface{}, options ...ClientOption, ) *Client { c := &Client{ client: cc, method: fmt.Sprintf("/%s/%s", serviceName, method), enc: enc, dec: dec, // We are using reflect.Indirect here to allow both reply structs and // pointers to these reply structs. New consumers of the client should // use structs directly, while existing consumers will not break if they // remain to use pointers to structs. grpcReply: reflect.TypeOf( reflect.Indirect( reflect.ValueOf(grpcReply), ).Interface(), ), before: []ClientRequestFunc{}, after: []ClientResponseFunc{}, } for _, option := range options { option(c) } return c } // ClientOption sets an optional parameter for clients. type ClientOption func(*Client) // ClientBefore sets the RequestFuncs that are applied to the outgoing gRPC // request before it's invoked. func ClientBefore(before ...ClientRequestFunc) ClientOption { return func(c *Client) { c.before = append(c.before, before...) } } // ClientAfter sets the ClientResponseFuncs that are applied to the incoming // gRPC response prior to it being decoded. This is useful for obtaining // response metadata and adding onto the context prior to decoding. func ClientAfter(after ...ClientResponseFunc) ClientOption { return func(c *Client) { c.after = append(c.after, after...) } } // ClientFinalizer is executed at the end of every gRPC request. // By default, no finalizer is registered. func ClientFinalizer(f ...ClientFinalizerFunc) ClientOption { return func(s *Client) { s.finalizer = append(s.finalizer, f...) } } // Endpoint returns a usable endpoint that will invoke the gRPC specified by the // client. func (c Client) Endpoint() endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { ctx, cancel := context.WithCancel(ctx) defer cancel() if c.finalizer != nil { defer func() { for _, f := range c.finalizer { f(ctx, err) } }() } ctx = context.WithValue(ctx, ContextKeyRequestMethod, c.method) req, err := c.enc(ctx, request) if err != nil { return nil, err } md := &metadata.MD{} for _, f := range c.before { ctx = f(ctx, md) } ctx = metadata.NewOutgoingContext(ctx, *md) var header, trailer metadata.MD grpcReply := reflect.New(c.grpcReply).Interface() if err = c.client.Invoke( ctx, c.method, req, grpcReply, grpc.Header(&header), grpc.Trailer(&trailer), ); err != nil { return nil, err } for _, f := range c.after { ctx = f(ctx, header, trailer) } response, err = c.dec(ctx, grpcReply) if err != nil { return nil, err } return response, nil } } // ClientFinalizerFunc can be used to perform work at the end of a client gRPC // request, after the response is returned. The principal // intended use is for error logging. Additional response parameters are // provided in the context under keys with the ContextKeyResponse prefix. // Note: err may be nil. There maybe also no additional response parameters depending on // when an error occurs. type ClientFinalizerFunc func(ctx context.Context, err error) golang-github-go-kit-kit-0.13.0/transport/grpc/client_test.go000066400000000000000000000023351443521372500241430ustar00rootroot00000000000000package grpc_test import ( "context" "fmt" "net" "testing" "google.golang.org/grpc" test "github.com/go-kit/kit/transport/grpc/_grpc_test" "github.com/go-kit/kit/transport/grpc/_grpc_test/pb" ) const ( hostPort string = "localhost:8002" ) func TestGRPCClient(t *testing.T) { var ( server = grpc.NewServer() service = test.NewService() ) sc, err := net.Listen("tcp", hostPort) if err != nil { t.Fatalf("unable to listen: %+v", err) } defer server.GracefulStop() go func() { pb.RegisterTestServer(server, test.NewBinding(service)) _ = server.Serve(sc) }() cc, err := grpc.Dial(hostPort, grpc.WithInsecure()) if err != nil { t.Fatalf("unable to Dial: %+v", err) } client := test.NewClient(cc) var ( a = "the answer to life the universe and everything" b = int64(42) cID = "request-1" ctx = test.SetCorrelationID(context.Background(), cID) ) responseCTX, v, err := client.Test(ctx, a, b) if err != nil { t.Fatalf("unable to Test: %+v", err) } if want, have := fmt.Sprintf("%s = %d", a, b), v; want != have { t.Fatalf("want %q, have %q", want, have) } if want, have := cID, test.GetConsumedCorrelationID(responseCTX); want != have { t.Fatalf("want %q, have %q", want, have) } } golang-github-go-kit-kit-0.13.0/transport/grpc/doc.go000066400000000000000000000001041443521372500223630ustar00rootroot00000000000000// Package grpc provides a gRPC binding for endpoints. package grpc golang-github-go-kit-kit-0.13.0/transport/grpc/encode_decode.go000066400000000000000000000030321443521372500243610ustar00rootroot00000000000000package grpc import ( "context" ) // DecodeRequestFunc extracts a user-domain request object from a gRPC request. // It's designed to be used in gRPC servers, for server-side endpoints. One // straightforward DecodeRequestFunc could be something that decodes from the // gRPC request message to the concrete request type. type DecodeRequestFunc func(context.Context, interface{}) (request interface{}, err error) // EncodeRequestFunc encodes the passed request object into the gRPC request // object. It's designed to be used in gRPC clients, for client-side endpoints. // One straightforward EncodeRequestFunc could something that encodes the object // directly to the gRPC request message. type EncodeRequestFunc func(context.Context, interface{}) (request interface{}, err error) // EncodeResponseFunc encodes the passed response object to the gRPC response // message. It's designed to be used in gRPC servers, for server-side endpoints. // One straightforward EncodeResponseFunc could be something that encodes the // object directly to the gRPC response message. type EncodeResponseFunc func(context.Context, interface{}) (response interface{}, err error) // DecodeResponseFunc extracts a user-domain response object from a gRPC // response object. It's designed to be used in gRPC clients, for client-side // endpoints. One straightforward DecodeResponseFunc could be something that // decodes from the gRPC response message to the concrete response type. type DecodeResponseFunc func(context.Context, interface{}) (response interface{}, err error) golang-github-go-kit-kit-0.13.0/transport/grpc/request_response_funcs.go000066400000000000000000000054211443521372500264310ustar00rootroot00000000000000package grpc import ( "context" "encoding/base64" "strings" "google.golang.org/grpc/metadata" ) const ( binHdrSuffix = "-bin" ) // ClientRequestFunc may take information from context and use it to construct // metadata headers to be transported to the server. ClientRequestFuncs are // executed after creating the request but prior to sending the gRPC request to // the server. type ClientRequestFunc func(context.Context, *metadata.MD) context.Context // ServerRequestFunc may take information from the received metadata header and // use it to place items in the request scoped context. ServerRequestFuncs are // executed prior to invoking the endpoint. type ServerRequestFunc func(context.Context, metadata.MD) context.Context // ServerResponseFunc may take information from a request context and use it to // manipulate the gRPC response metadata headers and trailers. ResponseFuncs are // only executed in servers, after invoking the endpoint but prior to writing a // response. type ServerResponseFunc func(ctx context.Context, header *metadata.MD, trailer *metadata.MD) context.Context // ClientResponseFunc may take information from a gRPC metadata header and/or // trailer and make the responses available for consumption. ClientResponseFuncs // are only executed in clients, after a request has been made, but prior to it // being decoded. type ClientResponseFunc func(ctx context.Context, header metadata.MD, trailer metadata.MD) context.Context // SetRequestHeader returns a ClientRequestFunc that sets the specified metadata // key-value pair. func SetRequestHeader(key, val string) ClientRequestFunc { return func(ctx context.Context, md *metadata.MD) context.Context { key, val := EncodeKeyValue(key, val) (*md)[key] = append((*md)[key], val) return ctx } } // SetResponseHeader returns a ResponseFunc that sets the specified metadata // key-value pair. func SetResponseHeader(key, val string) ServerResponseFunc { return func(ctx context.Context, md *metadata.MD, _ *metadata.MD) context.Context { key, val := EncodeKeyValue(key, val) (*md)[key] = append((*md)[key], val) return ctx } } // SetResponseTrailer returns a ResponseFunc that sets the specified metadata // key-value pair. func SetResponseTrailer(key, val string) ServerResponseFunc { return func(ctx context.Context, _ *metadata.MD, md *metadata.MD) context.Context { key, val := EncodeKeyValue(key, val) (*md)[key] = append((*md)[key], val) return ctx } } // EncodeKeyValue sanitizes a key-value pair for use in gRPC metadata headers. func EncodeKeyValue(key, val string) (string, string) { key = strings.ToLower(key) if strings.HasSuffix(key, binHdrSuffix) { val = base64.StdEncoding.EncodeToString([]byte(val)) } return key, val } type contextKey int const ( ContextKeyRequestMethod contextKey = iota ) golang-github-go-kit-kit-0.13.0/transport/grpc/server.go000066400000000000000000000114671443521372500231420ustar00rootroot00000000000000package grpc import ( "context" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/transport" "github.com/go-kit/log" ) // Handler which should be called from the gRPC binding of the service // implementation. The incoming request parameter, and returned response // parameter, are both gRPC types, not user-domain. type Handler interface { ServeGRPC(ctx context.Context, request interface{}) (context.Context, interface{}, error) } // Server wraps an endpoint and implements grpc.Handler. type Server struct { e endpoint.Endpoint dec DecodeRequestFunc enc EncodeResponseFunc before []ServerRequestFunc after []ServerResponseFunc finalizer []ServerFinalizerFunc errorHandler transport.ErrorHandler } // NewServer constructs a new server, which implements wraps the provided // endpoint and implements the Handler interface. Consumers should write // bindings that adapt the concrete gRPC methods from their compiled protobuf // definitions to individual handlers. Request and response objects are from the // caller business domain, not gRPC request and reply types. func NewServer( e endpoint.Endpoint, dec DecodeRequestFunc, enc EncodeResponseFunc, options ...ServerOption, ) *Server { s := &Server{ e: e, dec: dec, enc: enc, errorHandler: transport.NewLogErrorHandler(log.NewNopLogger()), } for _, option := range options { option(s) } return s } // ServerOption sets an optional parameter for servers. type ServerOption func(*Server) // ServerBefore functions are executed on the gRPC request object before the // request is decoded. func ServerBefore(before ...ServerRequestFunc) ServerOption { return func(s *Server) { s.before = append(s.before, before...) } } // ServerAfter functions are executed on the gRPC response writer after the // endpoint is invoked, but before anything is written to the client. func ServerAfter(after ...ServerResponseFunc) ServerOption { return func(s *Server) { s.after = append(s.after, after...) } } // ServerErrorLogger is used to log non-terminal errors. By default, no errors // are logged. // Deprecated: Use ServerErrorHandler instead. func ServerErrorLogger(logger log.Logger) ServerOption { return func(s *Server) { s.errorHandler = transport.NewLogErrorHandler(logger) } } // ServerErrorHandler is used to handle non-terminal errors. By default, non-terminal errors // are ignored. func ServerErrorHandler(errorHandler transport.ErrorHandler) ServerOption { return func(s *Server) { s.errorHandler = errorHandler } } // ServerFinalizer is executed at the end of every gRPC request. // By default, no finalizer is registered. func ServerFinalizer(f ...ServerFinalizerFunc) ServerOption { return func(s *Server) { s.finalizer = append(s.finalizer, f...) } } // ServeGRPC implements the Handler interface. func (s Server) ServeGRPC(ctx context.Context, req interface{}) (retctx context.Context, resp interface{}, err error) { // Retrieve gRPC metadata. md, ok := metadata.FromIncomingContext(ctx) if !ok { md = metadata.MD{} } if len(s.finalizer) > 0 { defer func() { for _, f := range s.finalizer { f(ctx, err) } }() } for _, f := range s.before { ctx = f(ctx, md) } var ( request interface{} response interface{} grpcResp interface{} ) request, err = s.dec(ctx, req) if err != nil { s.errorHandler.Handle(ctx, err) return ctx, nil, err } response, err = s.e(ctx, request) if err != nil { s.errorHandler.Handle(ctx, err) return ctx, nil, err } var mdHeader, mdTrailer metadata.MD for _, f := range s.after { ctx = f(ctx, &mdHeader, &mdTrailer) } grpcResp, err = s.enc(ctx, response) if err != nil { s.errorHandler.Handle(ctx, err) return ctx, nil, err } if len(mdHeader) > 0 { if err = grpc.SendHeader(ctx, mdHeader); err != nil { s.errorHandler.Handle(ctx, err) return ctx, nil, err } } if len(mdTrailer) > 0 { if err = grpc.SetTrailer(ctx, mdTrailer); err != nil { s.errorHandler.Handle(ctx, err) return ctx, nil, err } } return ctx, grpcResp, nil } // ServerFinalizerFunc can be used to perform work at the end of an gRPC // request, after the response has been written to the client. type ServerFinalizerFunc func(ctx context.Context, err error) // Interceptor is a grpc UnaryInterceptor that injects the method name into // context so it can be consumed by Go kit gRPC middlewares. The Interceptor // typically is added at creation time of the grpc-go server. // Like this: `grpc.NewServer(grpc.UnaryInterceptor(kitgrpc.Interceptor))` func Interceptor( ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, ) (resp interface{}, err error) { ctx = context.WithValue(ctx, ContextKeyRequestMethod, info.FullMethod) return handler(ctx, req) } golang-github-go-kit-kit-0.13.0/transport/http/000077500000000000000000000000001443521372500213205ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/transport/http/client.go000066400000000000000000000147721443521372500231400ustar00rootroot00000000000000package http import ( "bytes" "context" "encoding/json" "encoding/xml" "io" "io/ioutil" "net/http" "net/url" "github.com/go-kit/kit/endpoint" ) // HTTPClient is an interface that models *http.Client. type HTTPClient interface { Do(req *http.Request) (*http.Response, error) } // Client wraps a URL and provides a method that implements endpoint.Endpoint. type Client struct { client HTTPClient req CreateRequestFunc dec DecodeResponseFunc before []RequestFunc after []ClientResponseFunc finalizer []ClientFinalizerFunc bufferedStream bool } // NewClient constructs a usable Client for a single remote method. func NewClient(method string, tgt *url.URL, enc EncodeRequestFunc, dec DecodeResponseFunc, options ...ClientOption) *Client { return NewExplicitClient(makeCreateRequestFunc(method, tgt, enc), dec, options...) } // NewExplicitClient is like NewClient but uses a CreateRequestFunc instead of a // method, target URL, and EncodeRequestFunc, which allows for more control over // the outgoing HTTP request. func NewExplicitClient(req CreateRequestFunc, dec DecodeResponseFunc, options ...ClientOption) *Client { c := &Client{ client: http.DefaultClient, req: req, dec: dec, } for _, option := range options { option(c) } return c } // ClientOption sets an optional parameter for clients. type ClientOption func(*Client) // SetClient sets the underlying HTTP client used for requests. // By default, http.DefaultClient is used. func SetClient(client HTTPClient) ClientOption { return func(c *Client) { c.client = client } } // ClientBefore adds one or more RequestFuncs to be applied to the outgoing HTTP // request before it's invoked. func ClientBefore(before ...RequestFunc) ClientOption { return func(c *Client) { c.before = append(c.before, before...) } } // ClientAfter adds one or more ClientResponseFuncs, which are applied to the // incoming HTTP response prior to it being decoded. This is useful for // obtaining anything off of the response and adding it into the context prior // to decoding. func ClientAfter(after ...ClientResponseFunc) ClientOption { return func(c *Client) { c.after = append(c.after, after...) } } // ClientFinalizer adds one or more ClientFinalizerFuncs to be executed at the // end of every HTTP request. Finalizers are executed in the order in which they // were added. By default, no finalizer is registered. func ClientFinalizer(f ...ClientFinalizerFunc) ClientOption { return func(s *Client) { s.finalizer = append(s.finalizer, f...) } } // BufferedStream sets whether the HTTP response body is left open, allowing it // to be read from later. Useful for transporting a file as a buffered stream. // That body has to be drained and closed to properly end the request. func BufferedStream(buffered bool) ClientOption { return func(c *Client) { c.bufferedStream = buffered } } // Endpoint returns a usable Go kit endpoint that calls the remote HTTP endpoint. func (c Client) Endpoint() endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { ctx, cancel := context.WithCancel(ctx) var ( resp *http.Response err error ) if c.finalizer != nil { defer func() { if resp != nil { ctx = context.WithValue(ctx, ContextKeyResponseHeaders, resp.Header) ctx = context.WithValue(ctx, ContextKeyResponseSize, resp.ContentLength) } for _, f := range c.finalizer { f(ctx, err) } }() } req, err := c.req(ctx, request) if err != nil { cancel() return nil, err } for _, f := range c.before { ctx = f(ctx, req) } resp, err = c.client.Do(req.WithContext(ctx)) if err != nil { cancel() return nil, err } // If the caller asked for a buffered stream, we don't cancel the // context when the endpoint returns. Instead, we should call the // cancel func when closing the response body. if c.bufferedStream { resp.Body = bodyWithCancel{ReadCloser: resp.Body, cancel: cancel} } else { defer resp.Body.Close() defer cancel() } for _, f := range c.after { ctx = f(ctx, resp) } response, err := c.dec(ctx, resp) if err != nil { return nil, err } return response, nil } } // bodyWithCancel is a wrapper for an io.ReadCloser with also a // cancel function which is called when the Close is used type bodyWithCancel struct { io.ReadCloser cancel context.CancelFunc } func (bwc bodyWithCancel) Close() error { bwc.ReadCloser.Close() bwc.cancel() return nil } // ClientFinalizerFunc can be used to perform work at the end of a client HTTP // request, after the response is returned. The principal // intended use is for error logging. Additional response parameters are // provided in the context under keys with the ContextKeyResponse prefix. // Note: err may be nil. There maybe also no additional response parameters // depending on when an error occurs. type ClientFinalizerFunc func(ctx context.Context, err error) // EncodeJSONRequest is an EncodeRequestFunc that serializes the request as a // JSON object to the Request body. Many JSON-over-HTTP services can use it as // a sensible default. If the request implements Headerer, the provided headers // will be applied to the request. func EncodeJSONRequest(c context.Context, r *http.Request, request interface{}) error { r.Header.Set("Content-Type", "application/json; charset=utf-8") if headerer, ok := request.(Headerer); ok { for k := range headerer.Headers() { r.Header.Set(k, headerer.Headers().Get(k)) } } var b bytes.Buffer r.Body = ioutil.NopCloser(&b) return json.NewEncoder(&b).Encode(request) } // EncodeXMLRequest is an EncodeRequestFunc that serializes the request as a // XML object to the Request body. If the request implements Headerer, // the provided headers will be applied to the request. func EncodeXMLRequest(c context.Context, r *http.Request, request interface{}) error { r.Header.Set("Content-Type", "text/xml; charset=utf-8") if headerer, ok := request.(Headerer); ok { for k := range headerer.Headers() { r.Header.Set(k, headerer.Headers().Get(k)) } } var b bytes.Buffer r.Body = ioutil.NopCloser(&b) return xml.NewEncoder(&b).Encode(request) } // // // func makeCreateRequestFunc(method string, target *url.URL, enc EncodeRequestFunc) CreateRequestFunc { return func(ctx context.Context, request interface{}) (*http.Request, error) { req, err := http.NewRequest(method, target.String(), nil) if err != nil { return nil, err } if err = enc(ctx, req, request); err != nil { return nil, err } return req, nil } } golang-github-go-kit-kit-0.13.0/transport/http/client_test.go000066400000000000000000000214741443521372500241740ustar00rootroot00000000000000package http_test import ( "bytes" "context" "fmt" "io" "io/ioutil" "net/http" "net/http/httptest" "net/url" "strings" "testing" "time" httptransport "github.com/go-kit/kit/transport/http" ) type TestResponse struct { Body io.ReadCloser String string } func TestHTTPClient(t *testing.T) { var ( testbody = "testbody" encode = func(context.Context, *http.Request, interface{}) error { return nil } decode = func(_ context.Context, r *http.Response) (interface{}, error) { buffer := make([]byte, len(testbody)) r.Body.Read(buffer) return TestResponse{r.Body, string(buffer)}, nil } headers = make(chan string, 1) headerKey = "X-Foo" headerVal = "abcde" afterHeaderKey = "X-The-Dude" afterHeaderVal = "Abides" afterVal = "" afterFunc = func(ctx context.Context, r *http.Response) context.Context { afterVal = r.Header.Get(afterHeaderKey) return ctx } ) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { headers <- r.Header.Get(headerKey) w.Header().Set(afterHeaderKey, afterHeaderVal) w.WriteHeader(http.StatusOK) w.Write([]byte(testbody)) })) client := httptransport.NewClient( "GET", mustParse(server.URL), encode, decode, httptransport.ClientBefore(httptransport.SetRequestHeader(headerKey, headerVal)), httptransport.ClientAfter(afterFunc), ) res, err := client.Endpoint()(context.Background(), struct{}{}) if err != nil { t.Fatal(err) } var have string select { case have = <-headers: case <-time.After(time.Millisecond): t.Fatalf("timeout waiting for %s", headerKey) } // Check that Request Header was successfully received if want := headerVal; want != have { t.Errorf("want %q, have %q", want, have) } // Check that Response header set from server was received in SetClientAfter if want, have := afterVal, afterHeaderVal; want != have { t.Errorf("want %q, have %q", want, have) } // Check that the response was successfully decoded response, ok := res.(TestResponse) if !ok { t.Fatal("response should be TestResponse") } if want, have := testbody, response.String; want != have { t.Errorf("want %q, have %q", want, have) } // Check that response body was closed b := make([]byte, 1) _, err = response.Body.Read(b) if err == nil { t.Fatal("wanted error, got none") } if doNotWant, have := io.EOF, err; doNotWant == have { t.Errorf("do not want %q, have %q", doNotWant, have) } } func TestHTTPClientBufferedStream(t *testing.T) { // bodysize has a size big enought to make the resopnse.Body not an instant read // so if the response is cancelled it wount be all readed and the test would fail // The 6000 has not a particular meaning, it big enough to fulfill the usecase. const bodysize = 6000 var ( testbody = string(make([]byte, bodysize)) encode = func(context.Context, *http.Request, interface{}) error { return nil } decode = func(_ context.Context, r *http.Response) (interface{}, error) { return TestResponse{r.Body, ""}, nil } ) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(testbody)) })) client := httptransport.NewClient( "GET", mustParse(server.URL), encode, decode, httptransport.BufferedStream(true), ) res, err := client.Endpoint()(context.Background(), struct{}{}) if err != nil { t.Fatal(err) } // Check that the response was successfully decoded response, ok := res.(TestResponse) if !ok { t.Fatal("response should be TestResponse") } defer response.Body.Close() // Faking work time.Sleep(time.Second * 1) // Check that response body was NOT closed b := make([]byte, len(testbody)) _, err = response.Body.Read(b) if want, have := io.EOF, err; have != want { t.Fatalf("want %q, have %q", want, have) } if want, have := testbody, string(b); want != have { t.Errorf("want %q, have %q", want, have) } } func TestClientFinalizer(t *testing.T) { var ( headerKey = "X-Henlo-Lizer" headerVal = "Helllo you stinky lizard" responseBody = "go eat a fly ugly\n" done = make(chan struct{}) encode = func(context.Context, *http.Request, interface{}) error { return nil } decode = func(_ context.Context, r *http.Response) (interface{}, error) { return TestResponse{r.Body, ""}, nil } ) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set(headerKey, headerVal) w.Write([]byte(responseBody)) })) defer server.Close() client := httptransport.NewClient( "GET", mustParse(server.URL), encode, decode, httptransport.ClientFinalizer(func(ctx context.Context, err error) { responseHeader := ctx.Value(httptransport.ContextKeyResponseHeaders).(http.Header) if want, have := headerVal, responseHeader.Get(headerKey); want != have { t.Errorf("%s: want %q, have %q", headerKey, want, have) } responseSize := ctx.Value(httptransport.ContextKeyResponseSize).(int64) if want, have := int64(len(responseBody)), responseSize; want != have { t.Errorf("response size: want %d, have %d", want, have) } close(done) }), ) _, err := client.Endpoint()(context.Background(), struct{}{}) if err != nil { t.Fatal(err) } select { case <-done: case <-time.After(time.Second): t.Fatal("timeout waiting for finalizer") } } func TestEncodeJSONRequest(t *testing.T) { var header http.Header var body string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { b, err := ioutil.ReadAll(r.Body) if err != nil && err != io.EOF { t.Fatal(err) } header = r.Header body = string(b) })) defer server.Close() serverURL, err := url.Parse(server.URL) if err != nil { t.Fatal(err) } client := httptransport.NewClient( "POST", serverURL, httptransport.EncodeJSONRequest, func(context.Context, *http.Response) (interface{}, error) { return nil, nil }, ).Endpoint() for _, test := range []struct { value interface{} body string }{ {nil, "null\n"}, {12, "12\n"}, {1.2, "1.2\n"}, {true, "true\n"}, {"test", "\"test\"\n"}, {enhancedRequest{Foo: "foo"}, "{\"foo\":\"foo\"}\n"}, } { if _, err := client(context.Background(), test.value); err != nil { t.Error(err) continue } if body != test.body { t.Errorf("%v: actual %#v, expected %#v", test.value, body, test.body) } } if _, err := client(context.Background(), enhancedRequest{Foo: "foo"}); err != nil { t.Fatal(err) } if _, ok := header["X-Edward"]; !ok { t.Fatalf("X-Edward value: actual %v, expected %v", nil, []string{"Snowden"}) } if v := header.Get("X-Edward"); v != "Snowden" { t.Errorf("X-Edward string: actual %v, expected %v", v, "Snowden") } } func TestSetClient(t *testing.T) { var ( encode = func(context.Context, *http.Request, interface{}) error { return nil } decode = func(_ context.Context, r *http.Response) (interface{}, error) { t, err := ioutil.ReadAll(r.Body) if err != nil { return nil, err } return string(t), nil } ) testHttpClient := httpClientFunc(func(req *http.Request) (*http.Response, error) { return &http.Response{ StatusCode: http.StatusOK, Request: req, Body: ioutil.NopCloser(bytes.NewBufferString("hello, world!")), }, nil }) client := httptransport.NewClient( "GET", &url.URL{}, encode, decode, httptransport.SetClient(testHttpClient), ).Endpoint() resp, err := client(context.Background(), nil) if err != nil { t.Fatal(err) } if r, ok := resp.(string); !ok || r != "hello, world!" { t.Fatal("Expected response to be 'hello, world!' string") } } func TestNewExplicitClient(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "%d", r.ContentLength) })) defer srv.Close() req := func(ctx context.Context, request interface{}) (*http.Request, error) { req, _ := http.NewRequest("POST", srv.URL, strings.NewReader(request.(string))) return req, nil } dec := func(_ context.Context, resp *http.Response) (response interface{}, err error) { buf, err := ioutil.ReadAll(resp.Body) resp.Body.Close() return string(buf), err } client := httptransport.NewExplicitClient(req, dec) request := "hello world" response, err := client.Endpoint()(context.Background(), request) if err != nil { t.Fatal(err) } if want, have := "11", response.(string); want != have { t.Fatalf("want %q, have %q", want, have) } } func mustParse(s string) *url.URL { u, err := url.Parse(s) if err != nil { panic(err) } return u } type enhancedRequest struct { Foo string `json:"foo"` } func (e enhancedRequest) Headers() http.Header { return http.Header{"X-Edward": []string{"Snowden"}} } type httpClientFunc func(req *http.Request) (*http.Response, error) func (f httpClientFunc) Do(req *http.Request) (*http.Response, error) { return f(req) } golang-github-go-kit-kit-0.13.0/transport/http/doc.go000066400000000000000000000001241443521372500224110ustar00rootroot00000000000000// Package http provides a general purpose HTTP binding for endpoints. package http golang-github-go-kit-kit-0.13.0/transport/http/encode_decode.go000066400000000000000000000036221443521372500244120ustar00rootroot00000000000000package http import ( "context" "net/http" ) // DecodeRequestFunc extracts a user-domain request object from an HTTP // request object. It's designed to be used in HTTP servers, for server-side // endpoints. One straightforward DecodeRequestFunc could be something that // JSON decodes from the request body to the concrete request type. type DecodeRequestFunc func(context.Context, *http.Request) (request interface{}, err error) // EncodeRequestFunc encodes the passed request object into the HTTP request // object. It's designed to be used in HTTP clients, for client-side // endpoints. One straightforward EncodeRequestFunc could be something that JSON // encodes the object directly to the request body. type EncodeRequestFunc func(context.Context, *http.Request, interface{}) error // CreateRequestFunc creates an outgoing HTTP request based on the passed // request object. It's designed to be used in HTTP clients, for client-side // endpoints. It's a more powerful version of EncodeRequestFunc, and can be used // if more fine-grained control of the HTTP request is required. type CreateRequestFunc func(context.Context, interface{}) (*http.Request, error) // EncodeResponseFunc encodes the passed response object to the HTTP response // writer. It's designed to be used in HTTP servers, for server-side // endpoints. One straightforward EncodeResponseFunc could be something that // JSON encodes the object directly to the response body. type EncodeResponseFunc func(context.Context, http.ResponseWriter, interface{}) error // DecodeResponseFunc extracts a user-domain response object from an HTTP // response object. It's designed to be used in HTTP clients, for client-side // endpoints. One straightforward DecodeResponseFunc could be something that // JSON decodes from the response body to the concrete response type. type DecodeResponseFunc func(context.Context, *http.Response) (response interface{}, err error) golang-github-go-kit-kit-0.13.0/transport/http/example_test.go000066400000000000000000000021231443521372500243370ustar00rootroot00000000000000package http import ( "context" "fmt" "net/http" "net/http/httptest" ) func ExamplePopulateRequestContext() { handler := NewServer( func(ctx context.Context, request interface{}) (response interface{}, err error) { fmt.Println("Method", ctx.Value(ContextKeyRequestMethod).(string)) fmt.Println("RequestPath", ctx.Value(ContextKeyRequestPath).(string)) fmt.Println("RequestURI", ctx.Value(ContextKeyRequestURI).(string)) fmt.Println("X-Request-ID", ctx.Value(ContextKeyRequestXRequestID).(string)) return struct{}{}, nil }, func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, func(context.Context, http.ResponseWriter, interface{}) error { return nil }, ServerBefore(PopulateRequestContext), ) server := httptest.NewServer(handler) defer server.Close() req, _ := http.NewRequest("PATCH", fmt.Sprintf("%s/search?q=sympatico", server.URL), nil) req.Header.Set("X-Request-Id", "a1b2c3d4e5") http.DefaultClient.Do(req) // Output: // Method PATCH // RequestPath /search // RequestURI /search?q=sympatico // X-Request-ID a1b2c3d4e5 } golang-github-go-kit-kit-0.13.0/transport/http/intercepting_writer.go000066400000000000000000000132301443521372500257350ustar00rootroot00000000000000package http import ( "io" "net/http" ) type interceptingWriter struct { http.ResponseWriter code int written int64 } // WriteHeader may not be explicitly called, so care must be taken to // initialize w.code to its default value of http.StatusOK. func (w *interceptingWriter) WriteHeader(code int) { w.code = code w.ResponseWriter.WriteHeader(code) } func (w *interceptingWriter) Write(p []byte) (int, error) { n, err := w.ResponseWriter.Write(p) w.written += int64(n) return n, err } // reimplementInterfaces returns a wrapped version of the embedded ResponseWriter // and selectively implements the same combination of additional interfaces as // the wrapped one. The interfaces it may implement are: http.Hijacker, // http.CloseNotifier, http.Pusher, http.Flusher and io.ReaderFrom. The standard // library is known to assert the existence of these interfaces and behaves // differently. This implementation is derived from // https://github.com/felixge/httpsnoop. func (w *interceptingWriter) reimplementInterfaces() http.ResponseWriter { var ( hj, i0 = w.ResponseWriter.(http.Hijacker) cn, i1 = w.ResponseWriter.(http.CloseNotifier) pu, i2 = w.ResponseWriter.(http.Pusher) fl, i3 = w.ResponseWriter.(http.Flusher) rf, i4 = w.ResponseWriter.(io.ReaderFrom) ) switch { case !i0 && !i1 && !i2 && !i3 && !i4: return struct { http.ResponseWriter }{w} case !i0 && !i1 && !i2 && !i3 && i4: return struct { http.ResponseWriter io.ReaderFrom }{w, rf} case !i0 && !i1 && !i2 && i3 && !i4: return struct { http.ResponseWriter http.Flusher }{w, fl} case !i0 && !i1 && !i2 && i3 && i4: return struct { http.ResponseWriter http.Flusher io.ReaderFrom }{w, fl, rf} case !i0 && !i1 && i2 && !i3 && !i4: return struct { http.ResponseWriter http.Pusher }{w, pu} case !i0 && !i1 && i2 && !i3 && i4: return struct { http.ResponseWriter http.Pusher io.ReaderFrom }{w, pu, rf} case !i0 && !i1 && i2 && i3 && !i4: return struct { http.ResponseWriter http.Pusher http.Flusher }{w, pu, fl} case !i0 && !i1 && i2 && i3 && i4: return struct { http.ResponseWriter http.Pusher http.Flusher io.ReaderFrom }{w, pu, fl, rf} case !i0 && i1 && !i2 && !i3 && !i4: return struct { http.ResponseWriter http.CloseNotifier }{w, cn} case !i0 && i1 && !i2 && !i3 && i4: return struct { http.ResponseWriter http.CloseNotifier io.ReaderFrom }{w, cn, rf} case !i0 && i1 && !i2 && i3 && !i4: return struct { http.ResponseWriter http.CloseNotifier http.Flusher }{w, cn, fl} case !i0 && i1 && !i2 && i3 && i4: return struct { http.ResponseWriter http.CloseNotifier http.Flusher io.ReaderFrom }{w, cn, fl, rf} case !i0 && i1 && i2 && !i3 && !i4: return struct { http.ResponseWriter http.CloseNotifier http.Pusher }{w, cn, pu} case !i0 && i1 && i2 && !i3 && i4: return struct { http.ResponseWriter http.CloseNotifier http.Pusher io.ReaderFrom }{w, cn, pu, rf} case !i0 && i1 && i2 && i3 && !i4: return struct { http.ResponseWriter http.CloseNotifier http.Pusher http.Flusher }{w, cn, pu, fl} case !i0 && i1 && i2 && i3 && i4: return struct { http.ResponseWriter http.CloseNotifier http.Pusher http.Flusher io.ReaderFrom }{w, cn, pu, fl, rf} case i0 && !i1 && !i2 && !i3 && !i4: return struct { http.ResponseWriter http.Hijacker }{w, hj} case i0 && !i1 && !i2 && !i3 && i4: return struct { http.ResponseWriter http.Hijacker io.ReaderFrom }{w, hj, rf} case i0 && !i1 && !i2 && i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.Flusher }{w, hj, fl} case i0 && !i1 && !i2 && i3 && i4: return struct { http.ResponseWriter http.Hijacker http.Flusher io.ReaderFrom }{w, hj, fl, rf} case i0 && !i1 && i2 && !i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.Pusher }{w, hj, pu} case i0 && !i1 && i2 && !i3 && i4: return struct { http.ResponseWriter http.Hijacker http.Pusher io.ReaderFrom }{w, hj, pu, rf} case i0 && !i1 && i2 && i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.Pusher http.Flusher }{w, hj, pu, fl} case i0 && !i1 && i2 && i3 && i4: return struct { http.ResponseWriter http.Hijacker http.Pusher http.Flusher io.ReaderFrom }{w, hj, pu, fl, rf} case i0 && i1 && !i2 && !i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier }{w, hj, cn} case i0 && i1 && !i2 && !i3 && i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier io.ReaderFrom }{w, hj, cn, rf} case i0 && i1 && !i2 && i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier http.Flusher }{w, hj, cn, fl} case i0 && i1 && !i2 && i3 && i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier http.Flusher io.ReaderFrom }{w, hj, cn, fl, rf} case i0 && i1 && i2 && !i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier http.Pusher }{w, hj, cn, pu} case i0 && i1 && i2 && !i3 && i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier http.Pusher io.ReaderFrom }{w, hj, cn, pu, rf} case i0 && i1 && i2 && i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier http.Pusher http.Flusher }{w, hj, cn, pu, fl} case i0 && i1 && i2 && i3 && i4: return struct { http.ResponseWriter http.Hijacker http.CloseNotifier http.Pusher http.Flusher io.ReaderFrom }{w, hj, cn, pu, fl, rf} default: return struct { http.ResponseWriter }{w} } } golang-github-go-kit-kit-0.13.0/transport/http/intercepting_writer_test.go000066400000000000000000000642331443521372500270050ustar00rootroot00000000000000package http import ( "bufio" "io" "net" "net/http" "testing" ) type versatileWriter struct { http.ResponseWriter closeNotifyCalled bool hijackCalled bool readFromCalled bool pushCalled bool flushCalled bool } func (v *versatileWriter) Flush() { v.flushCalled = true } func (v *versatileWriter) Push(target string, opts *http.PushOptions) error { v.pushCalled = true return nil } func (v *versatileWriter) ReadFrom(r io.Reader) (n int64, err error) { v.readFromCalled = true return 0, nil } func (v *versatileWriter) CloseNotify() <-chan bool { v.closeNotifyCalled = true; return nil } func (v *versatileWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { v.hijackCalled = true return nil, nil, nil } func TestInterceptingWriter_passthroughs(t *testing.T) { w := &versatileWriter{} iw := (&interceptingWriter{ResponseWriter: w}).reimplementInterfaces() iw.(http.Flusher).Flush() iw.(http.Pusher).Push("", nil) iw.(http.CloseNotifier).CloseNotify() iw.(http.Hijacker).Hijack() iw.(io.ReaderFrom).ReadFrom(nil) if !w.flushCalled { t.Error("Flush not called") } if !w.pushCalled { t.Error("Push not called") } if !w.closeNotifyCalled { t.Error("CloseNotify not called") } if !w.hijackCalled { t.Error("Hijack not called") } if !w.readFromCalled { t.Error("ReadFrom not called") } } // TestInterceptingWriter_reimplementInterfaces is also derived from // https://github.com/felixge/httpsnoop, like interceptingWriter. func TestInterceptingWriter_reimplementInterfaces(t *testing.T) { // combination 1/32 { t.Log("http.ResponseWriter") inner := struct { http.ResponseWriter }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != false { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != false { t.Error("unexpected interface") } } // combination 2/32 { t.Log("http.ResponseWriter, http.Pusher") inner := struct { http.ResponseWriter http.Pusher }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != false { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != true { t.Error("unexpected interface") } } // combination 3/32 { t.Log("http.ResponseWriter, io.ReaderFrom") inner := struct { http.ResponseWriter io.ReaderFrom }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != false { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != false { t.Error("unexpected interface") } } // combination 4/32 { t.Log("http.ResponseWriter, io.ReaderFrom, http.Pusher") inner := struct { http.ResponseWriter io.ReaderFrom http.Pusher }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != false { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != true { t.Error("unexpected interface") } } // combination 5/32 { t.Log("http.ResponseWriter, http.Hijacker") inner := struct { http.ResponseWriter http.Hijacker }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != true { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != false { t.Error("unexpected interface") } } // combination 6/32 { t.Log("http.ResponseWriter, http.Hijacker, http.Pusher") inner := struct { http.ResponseWriter http.Hijacker http.Pusher }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != true { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != true { t.Error("unexpected interface") } } // combination 7/32 { t.Log("http.ResponseWriter, http.Hijacker, io.ReaderFrom") inner := struct { http.ResponseWriter http.Hijacker io.ReaderFrom }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != true { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != false { t.Error("unexpected interface") } } // combination 8/32 { t.Log("http.ResponseWriter, http.Hijacker, io.ReaderFrom, http.Pusher") inner := struct { http.ResponseWriter http.Hijacker io.ReaderFrom http.Pusher }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != true { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != true { t.Error("unexpected interface") } } // combination 9/32 { t.Log("http.ResponseWriter, http.CloseNotifier") inner := struct { http.ResponseWriter http.CloseNotifier }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != false { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != false { t.Error("unexpected interface") } } // combination 10/32 { t.Log("http.ResponseWriter, http.CloseNotifier, http.Pusher") inner := struct { http.ResponseWriter http.CloseNotifier http.Pusher }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != false { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != true { t.Error("unexpected interface") } } // combination 11/32 { t.Log("http.ResponseWriter, http.CloseNotifier, io.ReaderFrom") inner := struct { http.ResponseWriter http.CloseNotifier io.ReaderFrom }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != false { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != false { t.Error("unexpected interface") } } // combination 12/32 { t.Log("http.ResponseWriter, http.CloseNotifier, io.ReaderFrom, http.Pusher") inner := struct { http.ResponseWriter http.CloseNotifier io.ReaderFrom http.Pusher }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != false { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != true { t.Error("unexpected interface") } } // combination 13/32 { t.Log("http.ResponseWriter, http.CloseNotifier, http.Hijacker") inner := struct { http.ResponseWriter http.CloseNotifier http.Hijacker }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != true { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != false { t.Error("unexpected interface") } } // combination 14/32 { t.Log("http.ResponseWriter, http.CloseNotifier, http.Hijacker, http.Pusher") inner := struct { http.ResponseWriter http.CloseNotifier http.Hijacker http.Pusher }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != true { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != true { t.Error("unexpected interface") } } // combination 15/32 { t.Log("http.ResponseWriter, http.CloseNotifier, http.Hijacker, io.ReaderFrom") inner := struct { http.ResponseWriter http.CloseNotifier http.Hijacker io.ReaderFrom }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != true { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != false { t.Error("unexpected interface") } } // combination 16/32 { t.Log("http.ResponseWriter, http.CloseNotifier, http.Hijacker, io.ReaderFrom, http.Pusher") inner := struct { http.ResponseWriter http.CloseNotifier http.Hijacker io.ReaderFrom http.Pusher }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != true { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != true { t.Error("unexpected interface") } } // combination 17/32 { t.Log("http.ResponseWriter, http.Flusher") inner := struct { http.ResponseWriter http.Flusher }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != false { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != false { t.Error("unexpected interface") } } // combination 18/32 { t.Log("http.ResponseWriter, http.Flusher, http.Pusher") inner := struct { http.ResponseWriter http.Flusher http.Pusher }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != false { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != true { t.Error("unexpected interface") } } // combination 19/32 { t.Log("http.ResponseWriter, http.Flusher, io.ReaderFrom") inner := struct { http.ResponseWriter http.Flusher io.ReaderFrom }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != false { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != false { t.Error("unexpected interface") } } // combination 20/32 { t.Log("http.ResponseWriter, http.Flusher, io.ReaderFrom, http.Pusher") inner := struct { http.ResponseWriter http.Flusher io.ReaderFrom http.Pusher }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != false { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != true { t.Error("unexpected interface") } } // combination 21/32 { t.Log("http.ResponseWriter, http.Flusher, http.Hijacker") inner := struct { http.ResponseWriter http.Flusher http.Hijacker }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != true { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != false { t.Error("unexpected interface") } } // combination 22/32 { t.Log("http.ResponseWriter, http.Flusher, http.Hijacker, http.Pusher") inner := struct { http.ResponseWriter http.Flusher http.Hijacker http.Pusher }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != true { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != true { t.Error("unexpected interface") } } // combination 23/32 { t.Log("http.ResponseWriter, http.Flusher, http.Hijacker, io.ReaderFrom") inner := struct { http.ResponseWriter http.Flusher http.Hijacker io.ReaderFrom }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != true { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != false { t.Error("unexpected interface") } } // combination 24/32 { t.Log("http.ResponseWriter, http.Flusher, http.Hijacker, io.ReaderFrom, http.Pusher") inner := struct { http.ResponseWriter http.Flusher http.Hijacker io.ReaderFrom http.Pusher }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != true { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != true { t.Error("unexpected interface") } } // combination 25/32 { t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier") inner := struct { http.ResponseWriter http.Flusher http.CloseNotifier }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != false { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != false { t.Error("unexpected interface") } } // combination 26/32 { t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, http.Pusher") inner := struct { http.ResponseWriter http.Flusher http.CloseNotifier http.Pusher }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != false { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != true { t.Error("unexpected interface") } } // combination 27/32 { t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, io.ReaderFrom") inner := struct { http.ResponseWriter http.Flusher http.CloseNotifier io.ReaderFrom }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != false { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != false { t.Error("unexpected interface") } } // combination 28/32 { t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, io.ReaderFrom, http.Pusher") inner := struct { http.ResponseWriter http.Flusher http.CloseNotifier io.ReaderFrom http.Pusher }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != false { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != true { t.Error("unexpected interface") } } // combination 29/32 { t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, http.Hijacker") inner := struct { http.ResponseWriter http.Flusher http.CloseNotifier http.Hijacker }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != true { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != false { t.Error("unexpected interface") } } // combination 30/32 { t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, http.Hijacker, http.Pusher") inner := struct { http.ResponseWriter http.Flusher http.CloseNotifier http.Hijacker http.Pusher }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != true { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != false { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != true { t.Error("unexpected interface") } } // combination 31/32 { t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, http.Hijacker, io.ReaderFrom") inner := struct { http.ResponseWriter http.Flusher http.CloseNotifier http.Hijacker io.ReaderFrom }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != true { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != false { t.Error("unexpected interface") } } // combination 32/32 { t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, http.Hijacker, io.ReaderFrom, http.Pusher") inner := struct { http.ResponseWriter http.Flusher http.CloseNotifier http.Hijacker io.ReaderFrom http.Pusher }{} w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() if _, ok := w.(http.ResponseWriter); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Flusher); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.CloseNotifier); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Hijacker); ok != true { t.Error("unexpected interface") } if _, ok := w.(io.ReaderFrom); ok != true { t.Error("unexpected interface") } if _, ok := w.(http.Pusher); ok != true { t.Error("unexpected interface") } } } golang-github-go-kit-kit-0.13.0/transport/http/jsonrpc/000077500000000000000000000000001443521372500227765ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/transport/http/jsonrpc/README.md000066400000000000000000000066051443521372500242640ustar00rootroot00000000000000# JSON RPC [JSON RPC](http://www.jsonrpc.org) is "A light weight remote procedure call protocol". It allows for the creation of simple RPC-style APIs with human-readable messages that are front-end friendly. ## Using JSON RPC with Go-Kit Using JSON RPC and go-kit together is quite simple. A JSON RPC _server_ acts as an [HTTP Handler](https://godoc.org/net/http#Handler), receiving all requests to the JSON RPC's URL. The server looks at the `method` property of the [Request Object](http://www.jsonrpc.org/specification#request_object), and routes it to the corresponding code. Each JSON RPC _method_ is implemented as an `EndpointCodec`, a go-kit [Endpoint](https://godoc.org/github.com/go-kit/kit/endpoint#Endpoint), sandwiched between a decoder and encoder. The decoder picks apart the JSON RPC request params, which can be passed to your endpoint. The encoder receives the output from the endpoint and encodes a JSON-RPC result. ## Example — Add Service Let's say we want a service that adds two ints together. We'll serve this at `http://localhost/rpc`. So a request to our `sum` method will be a POST to `http://localhost/rpc` with a request body of: { "id": 123, "jsonrpc": "2.0", "method": "sum", "params": { "A": 2, "B": 2 } } ### `EndpointCodecMap` The routing table for incoming JSON RPC requests is the `EndpointCodecMap`. The key of the map is the JSON RPC method name. Here, we're routing the `sum` method to an `EndpointCodec` wrapped around `sumEndpoint`. jsonrpc.EndpointCodecMap{ "sum": jsonrpc.EndpointCodec{ Endpoint: sumEndpoint, Decode: decodeSumRequest, Encode: encodeSumResponse, }, } ### Decoder type DecodeRequestFunc func(context.Context, json.RawMessage) (request interface{}, err error) A `DecodeRequestFunc` is given the raw JSON from the `params` property of the Request object, _not_ the whole request object. It returns an object that will be the input to the Endpoint. For our purposes, the output should be a SumRequest, like this: type SumRequest struct { A, B int } So here's our decoder: func decodeSumRequest(ctx context.Context, msg json.RawMessage) (interface{}, error) { var req SumRequest err := json.Unmarshal(msg, &req) if err != nil { return nil, err } return req, nil } So our `SumRequest` will now be passed to the endpoint. Once the endpoint has done its work, we hand over to the… ### Encoder The encoder takes the output of the endpoint, and builds the raw JSON message that will form the `result` field of a [Response Object](http://www.jsonrpc.org/specification#response_object). Our result is going to be a plain int. Here's our encoder: func encodeSumResponse(ctx context.Context, result interface{}) (json.RawMessage, error) { sum, ok := result.(int) if !ok { return nil, errors.New("result is not an int") } b, err := json.Marshal(sum) if err != nil { return nil, err } return b, nil } ### Server Now that we have an EndpointCodec with decoder, endpoint, and encoder, we can wire up the server: handler := jsonrpc.NewServer(jsonrpc.EndpointCodecMap{ "sum": jsonrpc.EndpointCodec{ Endpoint: sumEndpoint, Decode: decodeSumRequest, Encode: encodeSumResponse, }, }) http.Handle("/rpc", handler) http.ListenAndServe(":80", nil) With all of this done, our example request above should result in a response like this: { "jsonrpc": "2.0", "result": 4 } golang-github-go-kit-kit-0.13.0/transport/http/jsonrpc/client.go000066400000000000000000000155441443521372500246140ustar00rootroot00000000000000package jsonrpc import ( "bytes" "context" "encoding/json" "io/ioutil" "net/http" "net/url" "sync/atomic" "github.com/go-kit/kit/endpoint" httptransport "github.com/go-kit/kit/transport/http" ) // Client wraps a JSON RPC method and provides a method that implements endpoint.Endpoint. type Client struct { client httptransport.HTTPClient // JSON RPC endpoint URL tgt *url.URL // JSON RPC method name. method string enc EncodeRequestFunc dec DecodeResponseFunc before []httptransport.RequestFunc after []httptransport.ClientResponseFunc finalizer httptransport.ClientFinalizerFunc requestID RequestIDGenerator bufferedStream bool } type clientRequest struct { JSONRPC string `json:"jsonrpc"` Method string `json:"method"` Params json.RawMessage `json:"params"` ID interface{} `json:"id"` } // NewClient constructs a usable Client for a single remote method. func NewClient( tgt *url.URL, method string, options ...ClientOption, ) *Client { c := &Client{ client: http.DefaultClient, method: method, tgt: tgt, enc: DefaultRequestEncoder, dec: DefaultResponseDecoder, before: []httptransport.RequestFunc{}, after: []httptransport.ClientResponseFunc{}, requestID: NewAutoIncrementID(0), bufferedStream: false, } for _, option := range options { option(c) } return c } // DefaultRequestEncoder marshals the given request to JSON. func DefaultRequestEncoder(_ context.Context, req interface{}) (json.RawMessage, error) { return json.Marshal(req) } // DefaultResponseDecoder unmarshals the result to interface{}, or returns an // error, if found. func DefaultResponseDecoder(_ context.Context, res Response) (interface{}, error) { if res.Error != nil { return nil, *res.Error } var result interface{} err := json.Unmarshal(res.Result, &result) if err != nil { return nil, err } return result, nil } // ClientOption sets an optional parameter for clients. type ClientOption func(*Client) // SetClient sets the underlying HTTP client used for requests. // By default, http.DefaultClient is used. func SetClient(client httptransport.HTTPClient) ClientOption { return func(c *Client) { c.client = client } } // ClientBefore sets the RequestFuncs that are applied to the outgoing HTTP // request before it's invoked. func ClientBefore(before ...httptransport.RequestFunc) ClientOption { return func(c *Client) { c.before = append(c.before, before...) } } // ClientAfter sets the ClientResponseFuncs applied to the server's HTTP // response prior to it being decoded. This is useful for obtaining anything // from the response and adding onto the context prior to decoding. func ClientAfter(after ...httptransport.ClientResponseFunc) ClientOption { return func(c *Client) { c.after = append(c.after, after...) } } // ClientFinalizer is executed at the end of every HTTP request. // By default, no finalizer is registered. func ClientFinalizer(f httptransport.ClientFinalizerFunc) ClientOption { return func(c *Client) { c.finalizer = f } } // ClientRequestEncoder sets the func used to encode the request params to JSON. // If not set, DefaultRequestEncoder is used. func ClientRequestEncoder(enc EncodeRequestFunc) ClientOption { return func(c *Client) { c.enc = enc } } // ClientResponseDecoder sets the func used to decode the response params from // JSON. If not set, DefaultResponseDecoder is used. func ClientResponseDecoder(dec DecodeResponseFunc) ClientOption { return func(c *Client) { c.dec = dec } } // RequestIDGenerator returns an ID for the request. type RequestIDGenerator interface { Generate() interface{} } // ClientRequestIDGenerator is executed before each request to generate an ID // for the request. // By default, AutoIncrementRequestID is used. func ClientRequestIDGenerator(g RequestIDGenerator) ClientOption { return func(c *Client) { c.requestID = g } } // BufferedStream sets whether the Response.Body is left open, allowing it // to be read from later. Useful for transporting a file as a buffered stream. func BufferedStream(buffered bool) ClientOption { return func(c *Client) { c.bufferedStream = buffered } } // Endpoint returns a usable endpoint that invokes the remote endpoint. func (c Client) Endpoint() endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { ctx, cancel := context.WithCancel(ctx) defer cancel() var ( resp *http.Response err error ) if c.finalizer != nil { defer func() { if resp != nil { ctx = context.WithValue(ctx, httptransport.ContextKeyResponseHeaders, resp.Header) ctx = context.WithValue(ctx, httptransport.ContextKeyResponseSize, resp.ContentLength) } c.finalizer(ctx, err) }() } ctx = context.WithValue(ctx, ContextKeyRequestMethod, c.method) var params json.RawMessage if params, err = c.enc(ctx, request); err != nil { return nil, err } rpcReq := clientRequest{ JSONRPC: Version, Method: c.method, Params: params, ID: c.requestID.Generate(), } req, err := http.NewRequest("POST", c.tgt.String(), nil) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json; charset=utf-8") var b bytes.Buffer req.Body = ioutil.NopCloser(&b) err = json.NewEncoder(&b).Encode(rpcReq) if err != nil { return nil, err } for _, f := range c.before { ctx = f(ctx, req) } resp, err = c.client.Do(req.WithContext(ctx)) if err != nil { return nil, err } if !c.bufferedStream { defer resp.Body.Close() } for _, f := range c.after { ctx = f(ctx, resp) } // Decode the body into an object var rpcRes Response err = json.NewDecoder(resp.Body).Decode(&rpcRes) if err != nil { return nil, err } response, err := c.dec(ctx, rpcRes) if err != nil { return nil, err } return response, nil } } // ClientFinalizerFunc can be used to perform work at the end of a client HTTP // request, after the response is returned. The principal // intended use is for error logging. Additional response parameters are // provided in the context under keys with the ContextKeyResponse prefix. // Note: err may be nil. There maybe also no additional response parameters // depending on when an error occurs. type ClientFinalizerFunc func(ctx context.Context, err error) // autoIncrementID is a RequestIDGenerator that generates // auto-incrementing integer IDs. type autoIncrementID struct { v *uint64 } // NewAutoIncrementID returns an auto-incrementing request ID generator, // initialised with the given value. func NewAutoIncrementID(init uint64) RequestIDGenerator { // Offset by one so that the first generated value = init. v := init - 1 return &autoIncrementID{v: &v} } // Generate satisfies RequestIDGenerator func (i *autoIncrementID) Generate() interface{} { id := atomic.AddUint64(i.v, 1) return id } golang-github-go-kit-kit-0.13.0/transport/http/jsonrpc/client_test.go000066400000000000000000000171701443521372500256500ustar00rootroot00000000000000package jsonrpc_test import ( "context" "encoding/json" "io" "io/ioutil" "net/http" "net/http/httptest" "net/url" "testing" "github.com/go-kit/kit/transport/http/jsonrpc" ) type TestResponse struct { Body io.ReadCloser String string } type testServerResponseOptions struct { Body string Status int } func httptestServer(t *testing.T) *httptest.Server { return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() var testReq jsonrpc.Request if err := json.NewDecoder(r.Body).Decode(&testReq); err != nil { t.Fatal(err) } var options testServerResponseOptions if err := json.Unmarshal(testReq.Params, &options); err != nil { t.Fatal(err) } if options.Status == 0 { options.Status = http.StatusOK } w.WriteHeader(options.Status) w.Write([]byte(options.Body)) })) } func TestBeforeAfterFuncs(t *testing.T) { t.Parallel() var tests = []struct { name string status int body string }{ { name: "empty body", body: "", }, { name: "empty body 500", body: "", status: 500, }, { name: "empty json body", body: "{}", }, { name: "error", body: `{"jsonrpc":"2.0","error":{"code":32603,"message":"Bad thing happened."}}`, }, } server := httptestServer(t) defer server.Close() testUrl, err := url.Parse(server.URL) if err != nil { t.Fatal(err) } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { beforeCalled := false afterCalled := false finalizerCalled := false sut := jsonrpc.NewClient( testUrl, "dummy", jsonrpc.ClientBefore(func(ctx context.Context, req *http.Request) context.Context { beforeCalled = true return ctx }), jsonrpc.ClientAfter(func(ctx context.Context, resp *http.Response) context.Context { afterCalled = true return ctx }), jsonrpc.ClientFinalizer(func(ctx context.Context, err error) { finalizerCalled = true }), ) sut.Endpoint()(context.TODO(), testServerResponseOptions{Body: tt.body, Status: tt.status}) if !beforeCalled { t.Fatal("Expected client before func to be called. Wasn't.") } if !afterCalled { t.Fatal("Expected client after func to be called. Wasn't.") } if !finalizerCalled { t.Fatal("Expected client finalizer func to be called. Wasn't.") } }) } } type staticIDGenerator int func (g staticIDGenerator) Generate() interface{} { return g } func TestClientHappyPath(t *testing.T) { t.Parallel() var ( afterCalledKey = "AC" beforeHeaderKey = "BF" beforeHeaderValue = "beforeFuncWozEre" testbody = `{"jsonrpc":"2.0", "result":5}` requestBody []byte beforeFunc = func(ctx context.Context, r *http.Request) context.Context { r.Header.Add(beforeHeaderKey, beforeHeaderValue) return ctx } encode = func(ctx context.Context, req interface{}) (json.RawMessage, error) { return json.Marshal(req) } afterFunc = func(ctx context.Context, r *http.Response) context.Context { return context.WithValue(ctx, afterCalledKey, true) } finalizerCalled = false fin = func(ctx context.Context, err error) { finalizerCalled = true } decode = func(ctx context.Context, res jsonrpc.Response) (interface{}, error) { if ac := ctx.Value(afterCalledKey); ac == nil { t.Fatal("after not called") } var result int err := json.Unmarshal(res.Result, &result) if err != nil { return nil, err } return result, nil } wantID = 666 gen = staticIDGenerator(wantID) ) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Header.Get(beforeHeaderKey) != beforeHeaderValue { t.Fatal("Header not set by before func.") } b, err := ioutil.ReadAll(r.Body) if err != nil && err != io.EOF { t.Fatal(err) } requestBody = b w.WriteHeader(http.StatusOK) w.Write([]byte(testbody)) })) defer server.Close() sut := jsonrpc.NewClient( mustParse(server.URL), "add", jsonrpc.ClientRequestEncoder(encode), jsonrpc.ClientResponseDecoder(decode), jsonrpc.ClientBefore(beforeFunc), jsonrpc.ClientAfter(afterFunc), jsonrpc.ClientRequestIDGenerator(gen), jsonrpc.ClientFinalizer(fin), jsonrpc.SetClient(http.DefaultClient), jsonrpc.BufferedStream(false), ) type addRequest struct { A int B int } in := addRequest{2, 2} result, err := sut.Endpoint()(context.Background(), in) if err != nil { t.Fatal(err) } ri, ok := result.(int) if !ok { t.Fatalf("result is not int: (%T)%+v", result, result) } if ri != 5 { t.Fatalf("want=5, got=%d", ri) } var requestAtServer jsonrpc.Request err = json.Unmarshal(requestBody, &requestAtServer) if err != nil { t.Fatal(err) } if id, _ := requestAtServer.ID.Int(); id != wantID { t.Fatalf("Request ID at server: want=%d, got=%d", wantID, id) } if requestAtServer.JSONRPC != jsonrpc.Version { t.Fatalf("JSON-RPC version at server: want=%s, got=%s", jsonrpc.Version, requestAtServer.JSONRPC) } var paramsAtServer addRequest err = json.Unmarshal(requestAtServer.Params, ¶msAtServer) if err != nil { t.Fatal(err) } if paramsAtServer != in { t.Fatalf("want=%+v, got=%+v", in, paramsAtServer) } if !finalizerCalled { t.Fatal("Expected finalizer to be called. Wasn't.") } } func TestCanUseDefaults(t *testing.T) { t.Parallel() var ( testbody = `{"jsonrpc":"2.0", "result":"boogaloo"}` requestBody []byte ) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { b, err := ioutil.ReadAll(r.Body) if err != nil && err != io.EOF { t.Fatal(err) } requestBody = b w.WriteHeader(http.StatusOK) w.Write([]byte(testbody)) })) defer server.Close() sut := jsonrpc.NewClient( mustParse(server.URL), "add", ) type addRequest struct { A int B int } in := addRequest{2, 2} result, err := sut.Endpoint()(context.Background(), in) if err != nil { t.Fatal(err) } rs, ok := result.(string) if !ok { t.Fatalf("result is not string: (%T)%+v", result, result) } if rs != "boogaloo" { t.Fatalf("want=boogaloo, got=%s", rs) } var requestAtServer jsonrpc.Request err = json.Unmarshal(requestBody, &requestAtServer) if err != nil { t.Fatal(err) } var paramsAtServer addRequest err = json.Unmarshal(requestAtServer.Params, ¶msAtServer) if err != nil { t.Fatal(err) } if paramsAtServer != in { t.Fatalf("want=%+v, got=%+v", in, paramsAtServer) } } func TestClientCanHandleJSONRPCError(t *testing.T) { t.Parallel() var testbody = `{ "jsonrpc": "2.0", "error": { "code": -32603, "message": "Bad thing happened." } }` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(testbody)) })) defer server.Close() sut := jsonrpc.NewClient(mustParse(server.URL), "add") _, err := sut.Endpoint()(context.Background(), 5) if err == nil { t.Fatal("Expected error, got none.") } { want := "Bad thing happened." got := err.Error() if got != want { t.Fatalf("error message: want=%s, got=%s", want, got) } } type errorCoder interface { ErrorCode() int } ec, ok := err.(errorCoder) if !ok { t.Fatal("Error is not errorCoder") } { want := -32603 got := ec.ErrorCode() if got != want { t.Fatalf("error code: want=%d, got=%d", want, got) } } } func TestDefaultAutoIncrementer(t *testing.T) { t.Parallel() sut := jsonrpc.NewAutoIncrementID(0) var want uint64 for ; want < 100; want++ { got := sut.Generate() if got != want { t.Fatalf("want=%d, got=%d", want, got) } } } func mustParse(s string) *url.URL { u, err := url.Parse(s) if err != nil { panic(err) } return u } golang-github-go-kit-kit-0.13.0/transport/http/jsonrpc/doc.go000066400000000000000000000002011443521372500240630ustar00rootroot00000000000000// Package jsonrpc provides a JSON RPC (v2.0) binding for endpoints. // See http://www.jsonrpc.org/specification package jsonrpc golang-github-go-kit-kit-0.13.0/transport/http/jsonrpc/encode_decode.go000066400000000000000000000036151443521372500260720ustar00rootroot00000000000000package jsonrpc import ( "encoding/json" "github.com/go-kit/kit/endpoint" "context" ) // Server-Side Codec // EndpointCodec defines a server Endpoint and its associated codecs type EndpointCodec struct { Endpoint endpoint.Endpoint Decode DecodeRequestFunc Encode EncodeResponseFunc } // EndpointCodecMap maps the Request.Method to the proper EndpointCodec type EndpointCodecMap map[string]EndpointCodec // DecodeRequestFunc extracts a user-domain request object from raw JSON // It's designed to be used in JSON RPC servers, for server-side endpoints. // One straightforward DecodeRequestFunc could be something that unmarshals // JSON from the request body to the concrete request type. type DecodeRequestFunc func(context.Context, json.RawMessage) (request interface{}, err error) // EncodeResponseFunc encodes the passed response object to a JSON RPC result. // It's designed to be used in HTTP servers, for server-side endpoints. // One straightforward EncodeResponseFunc could be something that JSON encodes // the object directly. type EncodeResponseFunc func(context.Context, interface{}) (response json.RawMessage, err error) // Client-Side Codec // EncodeRequestFunc encodes the given request object to raw JSON. // It's designed to be used in JSON RPC clients, for client-side // endpoints. One straightforward EncodeResponseFunc could be something that // JSON encodes the object directly. type EncodeRequestFunc func(context.Context, interface{}) (request json.RawMessage, err error) // DecodeResponseFunc extracts a user-domain response object from an JSON RPC // response object. It's designed to be used in JSON RPC clients, for // client-side endpoints. It is the responsibility of this function to decide // whether any error present in the JSON RPC response should be surfaced to the // client endpoint. type DecodeResponseFunc func(context.Context, Response) (response interface{}, err error) golang-github-go-kit-kit-0.13.0/transport/http/jsonrpc/error.go000066400000000000000000000046631443521372500244670ustar00rootroot00000000000000package jsonrpc // Error defines a JSON RPC error that can be returned // in a Response from the spec // http://www.jsonrpc.org/specification#error_object type Error struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data,omitempty"` } // Error implements error. func (e Error) Error() string { if e.Message != "" { return e.Message } return errorMessage[e.Code] } // ErrorCode returns the JSON RPC error code associated with the error. func (e Error) ErrorCode() int { return e.Code } const ( // ParseError defines invalid JSON was received by the server. // An error occurred on the server while parsing the JSON text. ParseError int = -32700 // InvalidRequestError defines the JSON sent is not a valid Request object. InvalidRequestError int = -32600 // MethodNotFoundError defines the method does not exist / is not available. MethodNotFoundError int = -32601 // InvalidParamsError defines invalid method parameter(s). InvalidParamsError int = -32602 // InternalError defines a server error InternalError int = -32603 ) var errorMessage = map[int]string{ ParseError: "An error occurred on the server while parsing the JSON text.", InvalidRequestError: "The JSON sent is not a valid Request object.", MethodNotFoundError: "The method does not exist / is not available.", InvalidParamsError: "Invalid method parameter(s).", InternalError: "Internal JSON-RPC error.", } // ErrorMessage returns a message for the JSON RPC error code. It returns the empty // string if the code is unknown. func ErrorMessage(code int) string { return errorMessage[code] } type parseError string func (e parseError) Error() string { return string(e) } func (e parseError) ErrorCode() int { return ParseError } type invalidRequestError string func (e invalidRequestError) Error() string { return string(e) } func (e invalidRequestError) ErrorCode() int { return InvalidRequestError } type methodNotFoundError string func (e methodNotFoundError) Error() string { return string(e) } func (e methodNotFoundError) ErrorCode() int { return MethodNotFoundError } type invalidParamsError string func (e invalidParamsError) Error() string { return string(e) } func (e invalidParamsError) ErrorCode() int { return InvalidParamsError } type internalError string func (e internalError) Error() string { return string(e) } func (e internalError) ErrorCode() int { return InternalError } golang-github-go-kit-kit-0.13.0/transport/http/jsonrpc/error_test.go000066400000000000000000000022231443521372500255140ustar00rootroot00000000000000package jsonrpc import "testing" func TestError(t *testing.T) { wantCode := ParseError sut := Error{ Code: wantCode, } gotCode := sut.ErrorCode() if gotCode != wantCode { t.Fatalf("want=%d, got=%d", gotCode, wantCode) } if sut.Error() == "" { t.Fatal("Empty error string.") } want := "override" sut.Message = want got := sut.Error() if sut.Error() != want { t.Fatalf("overridden error message: want=%s, got=%s", want, got) } } func TestErrorsSatisfyError(t *testing.T) { errs := []interface{}{ parseError("parseError"), invalidRequestError("invalidRequestError"), methodNotFoundError("methodNotFoundError"), invalidParamsError("invalidParamsError"), internalError("internalError"), } for _, e := range errs { err, ok := e.(error) if !ok { t.Fatalf("Couldn't assert %s as error.", e) } errString := err.Error() if errString == "" { t.Fatal("Empty error string") } ec, ok := e.(ErrorCoder) if !ok { t.Fatalf("Couldn't assert %s as ErrorCoder.", e) } if ErrorMessage(ec.ErrorCode()) == "" { t.Fatalf("Error type %s returned code of %d, which does not map to error string", e, ec.ErrorCode()) } } } golang-github-go-kit-kit-0.13.0/transport/http/jsonrpc/request_response_types.go000066400000000000000000000053421443521372500301630ustar00rootroot00000000000000package jsonrpc import ( "context" "encoding/json" "net/http" ) // Request defines a JSON RPC request from the spec // http://www.jsonrpc.org/specification#request_object type Request struct { JSONRPC string `json:"jsonrpc"` Method string `json:"method"` Params json.RawMessage `json:"params"` ID *RequestID `json:"id"` } // RequestID defines a request ID that can be string, number, or null. // An identifier established by the Client that MUST contain a String, // Number, or NULL value if included. // If it is not included it is assumed to be a notification. // The value SHOULD normally not be Null and // Numbers SHOULD NOT contain fractional parts. type RequestID struct { intValue int intError error floatValue float32 floatError error stringValue string stringError error } // RequestFunc may take information from decoded json body and place in // request context. In Servers, RequestFuncs are executed after json is parsed // but prior to invoking the codec type RequestFunc func(context.Context, *http.Request, Request) context.Context // UnmarshalJSON satisfies json.Unmarshaler func (id *RequestID) UnmarshalJSON(b []byte) error { id.intError = json.Unmarshal(b, &id.intValue) id.floatError = json.Unmarshal(b, &id.floatValue) id.stringError = json.Unmarshal(b, &id.stringValue) return nil } func (id *RequestID) MarshalJSON() ([]byte, error) { if id.intError == nil { return json.Marshal(id.intValue) } else if id.floatError == nil { return json.Marshal(id.floatValue) } else { return json.Marshal(id.stringValue) } } // Int returns the ID as an integer value. // An error is returned if the ID can't be treated as an int. func (id *RequestID) Int() (int, error) { return id.intValue, id.intError } // Float32 returns the ID as a float value. // An error is returned if the ID can't be treated as an float. func (id *RequestID) Float32() (float32, error) { return id.floatValue, id.floatError } // String returns the ID as a string value. // An error is returned if the ID can't be treated as an string. func (id *RequestID) String() (string, error) { return id.stringValue, id.stringError } // Response defines a JSON RPC response from the spec // http://www.jsonrpc.org/specification#response_object type Response struct { JSONRPC string `json:"jsonrpc"` Result json.RawMessage `json:"result,omitempty"` Error *Error `json:"error,omitempty"` ID *RequestID `json:"id"` } const ( // Version defines the version of the JSON RPC implementation Version string = "2.0" // ContentType defines the content type to be served. ContentType string = "application/json; charset=utf-8" ) type contextKey int const ( ContextKeyRequestMethod contextKey = iota ) golang-github-go-kit-kit-0.13.0/transport/http/jsonrpc/request_response_types_test.go000066400000000000000000000060061443521372500312200ustar00rootroot00000000000000package jsonrpc_test import ( "encoding/json" "fmt" "testing" "github.com/go-kit/kit/transport/http/jsonrpc" ) func TestCanUnMarshalID(t *testing.T) { cases := []struct { JSON string expType string expValue interface{} }{ {`12345`, "int", 12345}, {`12345.6`, "float", 12345.6}, {`"stringaling"`, "string", "stringaling"}, } for _, c := range cases { r := jsonrpc.Request{} JSON := fmt.Sprintf(`{"id":%s}`, c.JSON) var foo interface{} _ = json.Unmarshal([]byte(JSON), &foo) err := json.Unmarshal([]byte(JSON), &r) if err != nil { t.Fatalf("Unexpected error unmarshaling JSON into request: %s\n", err) } id := r.ID switch c.expType { case "int": want := c.expValue.(int) got, err := id.Int() if err != nil { t.Fatal(err) } if got != want { t.Fatalf("'%s' Int(): want %d, got %d.", c.JSON, want, got) } // Allow an int ID to be interpreted as a float. wantf := float32(c.expValue.(int)) gotf, err := id.Float32() if err != nil { t.Fatal(err) } if gotf != wantf { t.Fatalf("'%s' Int value as Float32(): want %f, got %f.", c.JSON, wantf, gotf) } _, err = id.String() if err == nil { t.Fatal("Expected String() to error for int value. Didn't.") } case "string": want := c.expValue.(string) got, err := id.String() if err != nil { t.Fatal(err) } if got != want { t.Fatalf("'%s' String(): want %s, got %s.", c.JSON, want, got) } _, err = id.Int() if err == nil { t.Fatal("Expected Int() to error for string value. Didn't.") } _, err = id.Float32() if err == nil { t.Fatal("Expected Float32() to error for string value. Didn't.") } case "float32": want := c.expValue.(float32) got, err := id.Float32() if err != nil { t.Fatal(err) } if got != want { t.Fatalf("'%s' Float32(): want %f, got %f.", c.JSON, want, got) } _, err = id.String() if err == nil { t.Fatal("Expected String() to error for float value. Didn't.") } _, err = id.Int() if err == nil { t.Fatal("Expected Int() to error for float value. Didn't.") } } } } func TestCanUnmarshalNullID(t *testing.T) { r := jsonrpc.Request{} JSON := `{"id":null}` err := json.Unmarshal([]byte(JSON), &r) if err != nil { t.Fatalf("Unexpected error unmarshaling JSON into request: %s\n", err) } if r.ID != nil { t.Fatalf("Expected ID to be nil, got %+v.\n", r.ID) } } func TestCanMarshalID(t *testing.T) { cases := []struct { JSON string expType string expValue interface{} }{ {`12345`, "int", 12345}, {`12345.6`, "float", 12345.6}, {`"stringaling"`, "string", "stringaling"}, {`null`, "null", nil}, } for _, c := range cases { req := jsonrpc.Request{} JSON := fmt.Sprintf(`{"jsonrpc":"2.0","id":%s}`, c.JSON) json.Unmarshal([]byte(JSON), &req) resp := jsonrpc.Response{ID: req.ID, JSONRPC: req.JSONRPC} want := JSON bol, _ := json.Marshal(resp) got := string(bol) if got != want { t.Fatalf("'%s': want %s, got %s.", c.expType, want, got) } } } golang-github-go-kit-kit-0.13.0/transport/http/jsonrpc/server.go000066400000000000000000000150521443521372500246360ustar00rootroot00000000000000package jsonrpc import ( "context" "encoding/json" "fmt" "io" "net/http" httptransport "github.com/go-kit/kit/transport/http" "github.com/go-kit/log" ) type requestIDKeyType struct{} var requestIDKey requestIDKeyType // Server wraps an endpoint and implements http.Handler. type Server struct { ecm EndpointCodecMap before []httptransport.RequestFunc beforeCodec []RequestFunc after []httptransport.ServerResponseFunc errorEncoder httptransport.ErrorEncoder finalizer httptransport.ServerFinalizerFunc logger log.Logger } // NewServer constructs a new server, which implements http.Server. func NewServer( ecm EndpointCodecMap, options ...ServerOption, ) *Server { s := &Server{ ecm: ecm, errorEncoder: DefaultErrorEncoder, logger: log.NewNopLogger(), } for _, option := range options { option(s) } return s } // ServerOption sets an optional parameter for servers. type ServerOption func(*Server) // ServerBefore functions are executed on the HTTP request object before the // request is decoded. func ServerBefore(before ...httptransport.RequestFunc) ServerOption { return func(s *Server) { s.before = append(s.before, before...) } } // ServerBeforeCodec functions are executed after the JSON request body has been // decoded, but before the method's decoder is called. This provides an opportunity // for middleware to inspect the contents of the rpc request before being passed // to the codec. func ServerBeforeCodec(beforeCodec ...RequestFunc) ServerOption { return func(s *Server) { s.beforeCodec = append(s.beforeCodec, beforeCodec...) } } // ServerAfter functions are executed on the HTTP response writer after the // endpoint is invoked, but before anything is written to the client. func ServerAfter(after ...httptransport.ServerResponseFunc) ServerOption { return func(s *Server) { s.after = append(s.after, after...) } } // ServerErrorEncoder is used to encode errors to the http.ResponseWriter // whenever they're encountered in the processing of a request. Clients can // use this to provide custom error formatting and response codes. By default, // errors will be written with the DefaultErrorEncoder. func ServerErrorEncoder(ee httptransport.ErrorEncoder) ServerOption { return func(s *Server) { s.errorEncoder = ee } } // ServerErrorLogger is used to log non-terminal errors. By default, no errors // are logged. This is intended as a diagnostic measure. Finer-grained control // of error handling, including logging in more detail, should be performed in a // custom ServerErrorEncoder or ServerFinalizer, both of which have access to // the context. func ServerErrorLogger(logger log.Logger) ServerOption { return func(s *Server) { s.logger = logger } } // ServerFinalizer is executed at the end of every HTTP request. // By default, no finalizer is registered. func ServerFinalizer(f httptransport.ServerFinalizerFunc) ServerOption { return func(s *Server) { s.finalizer = f } } // ServeHTTP implements http.Handler. func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(http.StatusMethodNotAllowed) _, _ = io.WriteString(w, "405 must POST\n") return } ctx := r.Context() if s.finalizer != nil { iw := &interceptingWriter{w, http.StatusOK} defer func() { s.finalizer(ctx, iw.code, r) }() w = iw } for _, f := range s.before { ctx = f(ctx, r) } // Decode the body into an object var req Request err := json.NewDecoder(r.Body).Decode(&req) if err != nil { rpcerr := parseError("JSON could not be decoded: " + err.Error()) s.logger.Log("err", rpcerr) s.errorEncoder(ctx, rpcerr, w) return } ctx = context.WithValue(ctx, requestIDKey, req.ID) ctx = context.WithValue(ctx, ContextKeyRequestMethod, req.Method) for _, f := range s.beforeCodec { ctx = f(ctx, r, req) } // Get the endpoint and codecs from the map using the method // defined in the JSON object ecm, ok := s.ecm[req.Method] if !ok { err := methodNotFoundError(fmt.Sprintf("Method %s was not found.", req.Method)) s.logger.Log("err", err) s.errorEncoder(ctx, err, w) return } // Decode the JSON "params" reqParams, err := ecm.Decode(ctx, req.Params) if err != nil { s.logger.Log("err", err) s.errorEncoder(ctx, err, w) return } // Call the Endpoint with the params response, err := ecm.Endpoint(ctx, reqParams) if err != nil { s.logger.Log("err", err) s.errorEncoder(ctx, err, w) return } for _, f := range s.after { ctx = f(ctx, w) } res := Response{ ID: req.ID, JSONRPC: Version, } // Encode the response from the Endpoint resParams, err := ecm.Encode(ctx, response) if err != nil { s.logger.Log("err", err) s.errorEncoder(ctx, err, w) return } res.Result = resParams w.Header().Set("Content-Type", ContentType) _ = json.NewEncoder(w).Encode(res) } // DefaultErrorEncoder writes the error to the ResponseWriter, // as a json-rpc error response, with an InternalError status code. // The Error() string of the error will be used as the response error message. // If the error implements ErrorCoder, the provided code will be set on the // response error. // If the error implements Headerer, the given headers will be set. func DefaultErrorEncoder(ctx context.Context, err error, w http.ResponseWriter) { w.Header().Set("Content-Type", ContentType) if headerer, ok := err.(httptransport.Headerer); ok { for k := range headerer.Headers() { w.Header().Set(k, headerer.Headers().Get(k)) } } e := Error{ Code: InternalError, Message: err.Error(), } if sc, ok := err.(ErrorCoder); ok { e.Code = sc.ErrorCode() } w.WriteHeader(http.StatusOK) var requestID *RequestID if v := ctx.Value(requestIDKey); v != nil { requestID = v.(*RequestID) } _ = json.NewEncoder(w).Encode(Response{ ID: requestID, JSONRPC: Version, Error: &e, }) } // ErrorCoder is checked by DefaultErrorEncoder. If an error value implements // ErrorCoder, the integer result of ErrorCode() will be used as the JSONRPC // error code when encoding the error. // // By default, InternalError (-32603) is used. type ErrorCoder interface { ErrorCode() int } // interceptingWriter intercepts calls to WriteHeader, so that a finalizer // can be given the correct status code. type interceptingWriter struct { http.ResponseWriter code int } // WriteHeader may not be explicitly called, so care must be taken to // initialize w.code to its default value of http.StatusOK. func (w *interceptingWriter) WriteHeader(code int) { w.code = code w.ResponseWriter.WriteHeader(code) } golang-github-go-kit-kit-0.13.0/transport/http/jsonrpc/server_test.go000066400000000000000000000256061443521372500257030ustar00rootroot00000000000000package jsonrpc_test import ( "context" "encoding/json" "errors" "io" "io/ioutil" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/transport/http/jsonrpc" ) func addBody() io.Reader { return body(`{"jsonrpc": "2.0", "method": "add", "params": [3, 2], "id": 1}`) } func body(in string) io.Reader { return strings.NewReader(in) } func unmarshalResponse(body []byte) (resp jsonrpc.Response, err error) { err = json.Unmarshal(body, &resp) return } func expectErrorCode(t *testing.T, want int, body []byte) { t.Helper() r, err := unmarshalResponse(body) if err != nil { t.Fatalf("Can't decode response: %v (%s)", err, body) } if r.Error == nil { t.Fatalf("Expected error on response. Got none: %s", body) } if have := r.Error.Code; want != have { t.Fatalf("Unexpected error code. Want %d, have %d: %s", want, have, body) } } func expectValidRequestID(t *testing.T, want int, body []byte) { t.Helper() r, err := unmarshalResponse(body) if err != nil { t.Fatalf("Can't decode response: %v (%s)", err, body) } have, err := r.ID.Int() if err != nil { t.Fatalf("Can't get requestID in response. err=%s, body=%s", err, body) } if want != have { t.Fatalf("Request ID: want %d, have %d (%s)", want, have, body) } } func expectNilRequestID(t *testing.T, body []byte) { t.Helper() r, err := unmarshalResponse(body) if err != nil { t.Fatalf("Can't decode response: %v (%s)", err, body) } if r.ID != nil { t.Fatalf("Request ID: want nil, have %v", r.ID) } } func nopDecoder(context.Context, json.RawMessage) (interface{}, error) { return struct{}{}, nil } func nopEncoder(context.Context, interface{}) (json.RawMessage, error) { return []byte("[]"), nil } type mockLogger struct { Called bool LastArgs []interface{} } func (l *mockLogger) Log(keyvals ...interface{}) error { l.Called = true l.LastArgs = append(l.LastArgs, keyvals) return nil } func TestServerBadDecode(t *testing.T) { ecm := jsonrpc.EndpointCodecMap{ "add": jsonrpc.EndpointCodec{ Endpoint: endpoint.Nop, Decode: func(context.Context, json.RawMessage) (interface{}, error) { return struct{}{}, errors.New("oof") }, Encode: nopEncoder, }, } logger := mockLogger{} handler := jsonrpc.NewServer(ecm, jsonrpc.ServerErrorLogger(&logger)) server := httptest.NewServer(handler) defer server.Close() resp, _ := http.Post(server.URL, "application/json", addBody()) buf, _ := ioutil.ReadAll(resp.Body) if want, have := http.StatusOK, resp.StatusCode; want != have { t.Errorf("want %d, have %d: %s", want, have, buf) } expectErrorCode(t, jsonrpc.InternalError, buf) if !logger.Called { t.Fatal("Expected logger to be called with error. Wasn't.") } } func TestServerBadEndpoint(t *testing.T) { ecm := jsonrpc.EndpointCodecMap{ "add": jsonrpc.EndpointCodec{ Endpoint: func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errors.New("oof") }, Decode: nopDecoder, Encode: nopEncoder, }, } handler := jsonrpc.NewServer(ecm) server := httptest.NewServer(handler) defer server.Close() resp, _ := http.Post(server.URL, "application/json", addBody()) if want, have := http.StatusOK, resp.StatusCode; want != have { t.Errorf("want %d, have %d", want, have) } buf, _ := ioutil.ReadAll(resp.Body) expectErrorCode(t, jsonrpc.InternalError, buf) expectValidRequestID(t, 1, buf) } func TestServerBadEncode(t *testing.T) { ecm := jsonrpc.EndpointCodecMap{ "add": jsonrpc.EndpointCodec{ Endpoint: endpoint.Nop, Decode: nopDecoder, Encode: func(context.Context, interface{}) (json.RawMessage, error) { return []byte{}, errors.New("oof") }, }, } handler := jsonrpc.NewServer(ecm) server := httptest.NewServer(handler) defer server.Close() resp, _ := http.Post(server.URL, "application/json", addBody()) if want, have := http.StatusOK, resp.StatusCode; want != have { t.Errorf("want %d, have %d", want, have) } buf, _ := ioutil.ReadAll(resp.Body) expectErrorCode(t, jsonrpc.InternalError, buf) expectValidRequestID(t, 1, buf) } func TestServerErrorEncoder(t *testing.T) { errTeapot := errors.New("teapot") code := func(err error) int { if errors.Is(err, errTeapot) { return http.StatusTeapot } return http.StatusInternalServerError } ecm := jsonrpc.EndpointCodecMap{ "add": jsonrpc.EndpointCodec{ Endpoint: func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errTeapot }, Decode: nopDecoder, Encode: nopEncoder, }, } handler := jsonrpc.NewServer( ecm, jsonrpc.ServerErrorEncoder(func(_ context.Context, err error, w http.ResponseWriter) { w.WriteHeader(code(err)) }), ) server := httptest.NewServer(handler) defer server.Close() resp, _ := http.Post(server.URL, "application/json", addBody()) if want, have := http.StatusTeapot, resp.StatusCode; want != have { t.Errorf("want %d, have %d", want, have) } } func TestCanRejectNonPostRequest(t *testing.T) { ecm := jsonrpc.EndpointCodecMap{} handler := jsonrpc.NewServer(ecm) server := httptest.NewServer(handler) defer server.Close() resp, _ := http.Get(server.URL) if want, have := http.StatusMethodNotAllowed, resp.StatusCode; want != have { t.Errorf("want %d, have %d", want, have) } } func TestCanRejectInvalidJSON(t *testing.T) { ecm := jsonrpc.EndpointCodecMap{} handler := jsonrpc.NewServer(ecm) server := httptest.NewServer(handler) defer server.Close() resp, _ := http.Post(server.URL, "application/json", body("clearlynotjson")) if want, have := http.StatusOK, resp.StatusCode; want != have { t.Errorf("want %d, have %d", want, have) } buf, _ := ioutil.ReadAll(resp.Body) expectErrorCode(t, jsonrpc.ParseError, buf) expectNilRequestID(t, buf) } func TestServerUnregisteredMethod(t *testing.T) { ecm := jsonrpc.EndpointCodecMap{} handler := jsonrpc.NewServer(ecm) server := httptest.NewServer(handler) defer server.Close() resp, _ := http.Post(server.URL, "application/json", addBody()) if want, have := http.StatusOK, resp.StatusCode; want != have { t.Errorf("want %d, have %d", want, have) } buf, _ := ioutil.ReadAll(resp.Body) expectErrorCode(t, jsonrpc.MethodNotFoundError, buf) } func TestServerHappyPath(t *testing.T) { step, response := testServer(t) step() resp := <-response defer resp.Body.Close() // nolint buf, _ := ioutil.ReadAll(resp.Body) if want, have := http.StatusOK, resp.StatusCode; want != have { t.Errorf("want %d, have %d (%s)", want, have, buf) } r, err := unmarshalResponse(buf) if err != nil { t.Fatalf("Can't decode response. err=%s, body=%s", err, buf) } if r.JSONRPC != jsonrpc.Version { t.Fatalf("JSONRPC Version: want=%s, got=%s", jsonrpc.Version, r.JSONRPC) } if r.Error != nil { t.Fatalf("Unxpected error on response: %s", buf) } } func TestMultipleServerBeforeCodec(t *testing.T) { var done = make(chan struct{}) ecm := jsonrpc.EndpointCodecMap{ "add": jsonrpc.EndpointCodec{ Endpoint: endpoint.Nop, Decode: nopDecoder, Encode: nopEncoder, }, } handler := jsonrpc.NewServer( ecm, jsonrpc.ServerBeforeCodec(func(ctx context.Context, r *http.Request, req jsonrpc.Request) context.Context { ctx = context.WithValue(ctx, "one", 1) return ctx }), jsonrpc.ServerBeforeCodec(func(ctx context.Context, r *http.Request, req jsonrpc.Request) context.Context { if _, ok := ctx.Value("one").(int); !ok { t.Error("Value was not set properly when multiple ServerBeforeCodecs are used") } close(done) return ctx }), ) server := httptest.NewServer(handler) defer server.Close() http.Post(server.URL, "application/json", addBody()) // nolint select { case <-done: case <-time.After(time.Second): t.Fatal("timeout waiting for finalizer") } } func TestMultipleServerBefore(t *testing.T) { var done = make(chan struct{}) ecm := jsonrpc.EndpointCodecMap{ "add": jsonrpc.EndpointCodec{ Endpoint: endpoint.Nop, Decode: nopDecoder, Encode: nopEncoder, }, } handler := jsonrpc.NewServer( ecm, jsonrpc.ServerBefore(func(ctx context.Context, r *http.Request) context.Context { ctx = context.WithValue(ctx, "one", 1) return ctx }), jsonrpc.ServerBefore(func(ctx context.Context, r *http.Request) context.Context { if _, ok := ctx.Value("one").(int); !ok { t.Error("Value was not set properly when multiple ServerBefores are used") } close(done) return ctx }), ) server := httptest.NewServer(handler) defer server.Close() http.Post(server.URL, "application/json", addBody()) // nolint select { case <-done: case <-time.After(time.Second): t.Fatal("timeout waiting for finalizer") } } func TestMultipleServerAfter(t *testing.T) { var done = make(chan struct{}) ecm := jsonrpc.EndpointCodecMap{ "add": jsonrpc.EndpointCodec{ Endpoint: endpoint.Nop, Decode: nopDecoder, Encode: nopEncoder, }, } handler := jsonrpc.NewServer( ecm, jsonrpc.ServerAfter(func(ctx context.Context, w http.ResponseWriter) context.Context { ctx = context.WithValue(ctx, "one", 1) return ctx }), jsonrpc.ServerAfter(func(ctx context.Context, w http.ResponseWriter) context.Context { if _, ok := ctx.Value("one").(int); !ok { t.Error("Value was not set properly when multiple ServerAfters are used") } close(done) return ctx }), ) server := httptest.NewServer(handler) defer server.Close() http.Post(server.URL, "application/json", addBody()) // nolint select { case <-done: case <-time.After(time.Second): t.Fatal("timeout waiting for finalizer") } } func TestCanFinalize(t *testing.T) { var done = make(chan struct{}) var finalizerCalled bool ecm := jsonrpc.EndpointCodecMap{ "add": jsonrpc.EndpointCodec{ Endpoint: endpoint.Nop, Decode: nopDecoder, Encode: nopEncoder, }, } handler := jsonrpc.NewServer( ecm, jsonrpc.ServerFinalizer(func(ctx context.Context, code int, req *http.Request) { finalizerCalled = true close(done) }), ) server := httptest.NewServer(handler) defer server.Close() http.Post(server.URL, "application/json", addBody()) // nolint select { case <-done: case <-time.After(time.Second): t.Fatal("timeout waiting for finalizer") } if !finalizerCalled { t.Fatal("Finalizer was not called.") } } func testServer(t *testing.T) (step func(), resp <-chan *http.Response) { var ( stepch = make(chan bool) endpoint = func(ctx context.Context, request interface{}) (response interface{}, err error) { <-stepch return struct{}{}, nil } response = make(chan *http.Response) ecm = jsonrpc.EndpointCodecMap{ "add": jsonrpc.EndpointCodec{ Endpoint: endpoint, Decode: nopDecoder, Encode: nopEncoder, }, } handler = jsonrpc.NewServer(ecm) ) go func() { server := httptest.NewServer(handler) defer server.Close() rb := strings.NewReader(`{"jsonrpc": "2.0", "method": "add", "params": [3, 2], "id": 1}`) resp, err := http.Post(server.URL, "application/json", rb) if err != nil { t.Error(err) return } response <- resp }() return func() { stepch <- true }, response } golang-github-go-kit-kit-0.13.0/transport/http/proto/000077500000000000000000000000001443521372500224635ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/transport/http/proto/client.go000066400000000000000000000017461443521372500243000ustar00rootroot00000000000000package proto import ( "bytes" "context" "errors" "io/ioutil" "net/http" httptransport "github.com/go-kit/kit/transport/http" "google.golang.org/protobuf/proto" ) // EncodeProtoRequest is an EncodeRequestFunc that serializes the request as Protobuf. // If the request implements Headerer, the provided headers will be applied // to the request. If the given request does not implement proto.Message, an error will // be returned. func EncodeProtoRequest(_ context.Context, r *http.Request, preq interface{}) error { r.Header.Set("Content-Type", "application/x-protobuf") if headerer, ok := preq.(httptransport.Headerer); ok { for k := range headerer.Headers() { r.Header.Set(k, headerer.Headers().Get(k)) } } req, ok := preq.(proto.Message) if !ok { return errors.New("response does not implement proto.Message") } b, err := proto.Marshal(req) if err != nil { return err } r.ContentLength = int64(len(b)) r.Body = ioutil.NopCloser(bytes.NewReader(b)) return nil } golang-github-go-kit-kit-0.13.0/transport/http/proto/generate.go000066400000000000000000000003301443521372500246000ustar00rootroot00000000000000package proto //go:generate protoc proto_test.proto --go_out=. --go_opt=Mproto_test.proto=github.com/go-kit/kit/transport/http/proto --go_opt=paths=source_relative //go:generate mv proto_test.pb.go proto_pb_test.go golang-github-go-kit-kit-0.13.0/transport/http/proto/proto_pb_test.go000066400000000000000000000105151443521372500256770ustar00rootroot00000000000000// Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 // protoc v3.16.0 // source: proto_test.proto package proto import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) type Cat struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Age int32 `protobuf:"varint,1,opt,name=Age,proto3" json:"Age,omitempty"` Breed string `protobuf:"bytes,2,opt,name=Breed,proto3" json:"Breed,omitempty"` Name string `protobuf:"bytes,3,opt,name=Name,proto3" json:"Name,omitempty"` } func (x *Cat) Reset() { *x = Cat{} if protoimpl.UnsafeEnabled { mi := &file_proto_test_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Cat) String() string { return protoimpl.X.MessageStringOf(x) } func (*Cat) ProtoMessage() {} func (x *Cat) ProtoReflect() protoreflect.Message { mi := &file_proto_test_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Cat.ProtoReflect.Descriptor instead. func (*Cat) Descriptor() ([]byte, []int) { return file_proto_test_proto_rawDescGZIP(), []int{0} } func (x *Cat) GetAge() int32 { if x != nil { return x.Age } return 0 } func (x *Cat) GetBreed() string { if x != nil { return x.Breed } return "" } func (x *Cat) GetName() string { if x != nil { return x.Name } return "" } var File_proto_test_proto protoreflect.FileDescriptor var file_proto_test_proto_rawDesc = []byte{ 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x41, 0x0a, 0x03, 0x43, 0x61, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x41, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x41, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x42, 0x72, 0x65, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x42, 0x72, 0x65, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_proto_test_proto_rawDescOnce sync.Once file_proto_test_proto_rawDescData = file_proto_test_proto_rawDesc ) func file_proto_test_proto_rawDescGZIP() []byte { file_proto_test_proto_rawDescOnce.Do(func() { file_proto_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_test_proto_rawDescData) }) return file_proto_test_proto_rawDescData } var file_proto_test_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_proto_test_proto_goTypes = []interface{}{ (*Cat)(nil), // 0: Cat } var file_proto_test_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_proto_test_proto_init() } func file_proto_test_proto_init() { if File_proto_test_proto != nil { return } if !protoimpl.UnsafeEnabled { file_proto_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Cat); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_test_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_proto_test_proto_goTypes, DependencyIndexes: file_proto_test_proto_depIdxs, MessageInfos: file_proto_test_proto_msgTypes, }.Build() File_proto_test_proto = out.File file_proto_test_proto_rawDesc = nil file_proto_test_proto_goTypes = nil file_proto_test_proto_depIdxs = nil } golang-github-go-kit-kit-0.13.0/transport/http/proto/proto_test.go000066400000000000000000000040231443521372500252130ustar00rootroot00000000000000package proto import ( "context" "io/ioutil" "net/http" "net/http/httptest" "testing" "google.golang.org/protobuf/proto" ) func TestEncodeProtoRequest(t *testing.T) { cat := &Cat{Name: "Ziggy", Age: 13, Breed: "Lumpy"} r := httptest.NewRequest(http.MethodGet, "/cat", nil) err := EncodeProtoRequest(context.TODO(), r, cat) if err != nil { t.Errorf("expected no encoding errors but got: %s", err) return } const xproto = "application/x-protobuf" if typ := r.Header.Get("Content-Type"); typ != xproto { t.Errorf("expected content type of %q, got %q", xproto, typ) return } bod, err := ioutil.ReadAll(r.Body) if err != nil { t.Errorf("expected no read errors but got: %s", err) return } defer r.Body.Close() var got Cat err = proto.Unmarshal(bod, &got) if err != nil { t.Errorf("expected no proto errors but got: %s", err) return } if !proto.Equal(&got, cat) { t.Errorf("expected cats to be equal but got:\n\n%#v\n\nwant:\n\n%#v", got, cat) return } } func TestEncodeProtoResponse(t *testing.T) { cat := &Cat{Name: "Ziggy", Age: 13, Breed: "Lumpy"} wr := httptest.NewRecorder() err := EncodeProtoResponse(context.TODO(), wr, cat) if err != nil { t.Errorf("expected no encoding errors but got: %s", err) return } w := wr.Result() const xproto = "application/x-protobuf" if typ := w.Header.Get("Content-Type"); typ != xproto { t.Errorf("expected content type of %q, got %q", xproto, typ) return } if w.StatusCode != http.StatusTeapot { t.Errorf("expected status code of %d, got %d", http.StatusTeapot, w.StatusCode) return } bod, err := ioutil.ReadAll(w.Body) if err != nil { t.Errorf("expected no read errors but got: %s", err) return } defer w.Body.Close() var got Cat err = proto.Unmarshal(bod, &got) if err != nil { t.Errorf("expected no proto errors but got: %s", err) return } if !proto.Equal(&got, cat) { t.Errorf("expected cats to be equal but got:\n\n%#v\n\nwant:\n\n%#v", got, cat) return } } func (c *Cat) StatusCode() int { return http.StatusTeapot } golang-github-go-kit-kit-0.13.0/transport/http/proto/proto_test.proto000066400000000000000000000001421443521372500257470ustar00rootroot00000000000000syntax = "proto3"; message Cat { int32 Age = 1; string Breed = 2; string Name = 3; } golang-github-go-kit-kit-0.13.0/transport/http/proto/server.go000066400000000000000000000023401443521372500243170ustar00rootroot00000000000000package proto import ( "context" "errors" "net/http" httptransport "github.com/go-kit/kit/transport/http" "google.golang.org/protobuf/proto" ) // EncodeProtoResponse is an EncodeResponseFunc that serializes the response as Protobuf. // Many Proto-over-HTTP services can use it as a sensible default. If the response // implements Headerer, the provided headers will be applied to the response. If the // response implements StatusCoder, the provided StatusCode will be used instead of 200. func EncodeProtoResponse(ctx context.Context, w http.ResponseWriter, pres interface{}) error { res, ok := pres.(proto.Message) if !ok { return errors.New("response does not implement proto.Message") } w.Header().Set("Content-Type", "application/x-protobuf") if headerer, ok := w.(httptransport.Headerer); ok { for k := range headerer.Headers() { w.Header().Set(k, headerer.Headers().Get(k)) } } code := http.StatusOK if sc, ok := pres.(httptransport.StatusCoder); ok { code = sc.StatusCode() } w.WriteHeader(code) if code == http.StatusNoContent { return nil } if res == nil { return nil } b, err := proto.Marshal(res) if err != nil { return err } _, err = w.Write(b) if err != nil { return err } return nil } golang-github-go-kit-kit-0.13.0/transport/http/request_response_funcs.go000066400000000000000000000122351443521372500264560ustar00rootroot00000000000000package http import ( "context" "net/http" ) // RequestFunc may take information from an HTTP request and put it into a // request context. In Servers, RequestFuncs are executed prior to invoking the // endpoint. In Clients, RequestFuncs are executed after creating the request // but prior to invoking the HTTP client. type RequestFunc func(context.Context, *http.Request) context.Context // ServerResponseFunc may take information from a request context and use it to // manipulate a ResponseWriter. ServerResponseFuncs are only executed in // servers, after invoking the endpoint but prior to writing a response. type ServerResponseFunc func(context.Context, http.ResponseWriter) context.Context // ClientResponseFunc may take information from an HTTP request and make the // response available for consumption. ClientResponseFuncs are only executed in // clients, after a request has been made, but prior to it being decoded. type ClientResponseFunc func(context.Context, *http.Response) context.Context // SetContentType returns a ServerResponseFunc that sets the Content-Type header // to the provided value. func SetContentType(contentType string) ServerResponseFunc { return SetResponseHeader("Content-Type", contentType) } // SetResponseHeader returns a ServerResponseFunc that sets the given header. func SetResponseHeader(key, val string) ServerResponseFunc { return func(ctx context.Context, w http.ResponseWriter) context.Context { w.Header().Set(key, val) return ctx } } // SetRequestHeader returns a RequestFunc that sets the given header. func SetRequestHeader(key, val string) RequestFunc { return func(ctx context.Context, r *http.Request) context.Context { r.Header.Set(key, val) return ctx } } // PopulateRequestContext is a RequestFunc that populates several values into // the context from the HTTP request. Those values may be extracted using the // corresponding ContextKey type in this package. func PopulateRequestContext(ctx context.Context, r *http.Request) context.Context { for k, v := range map[contextKey]string{ ContextKeyRequestMethod: r.Method, ContextKeyRequestURI: r.RequestURI, ContextKeyRequestPath: r.URL.Path, ContextKeyRequestProto: r.Proto, ContextKeyRequestHost: r.Host, ContextKeyRequestRemoteAddr: r.RemoteAddr, ContextKeyRequestXForwardedFor: r.Header.Get("X-Forwarded-For"), ContextKeyRequestXForwardedProto: r.Header.Get("X-Forwarded-Proto"), ContextKeyRequestAuthorization: r.Header.Get("Authorization"), ContextKeyRequestReferer: r.Header.Get("Referer"), ContextKeyRequestUserAgent: r.Header.Get("User-Agent"), ContextKeyRequestXRequestID: r.Header.Get("X-Request-Id"), ContextKeyRequestAccept: r.Header.Get("Accept"), } { ctx = context.WithValue(ctx, k, v) } return ctx } type contextKey int const ( // ContextKeyRequestMethod is populated in the context by // PopulateRequestContext. Its value is r.Method. ContextKeyRequestMethod contextKey = iota // ContextKeyRequestURI is populated in the context by // PopulateRequestContext. Its value is r.RequestURI. ContextKeyRequestURI // ContextKeyRequestPath is populated in the context by // PopulateRequestContext. Its value is r.URL.Path. ContextKeyRequestPath // ContextKeyRequestProto is populated in the context by // PopulateRequestContext. Its value is r.Proto. ContextKeyRequestProto // ContextKeyRequestHost is populated in the context by // PopulateRequestContext. Its value is r.Host. ContextKeyRequestHost // ContextKeyRequestRemoteAddr is populated in the context by // PopulateRequestContext. Its value is r.RemoteAddr. ContextKeyRequestRemoteAddr // ContextKeyRequestXForwardedFor is populated in the context by // PopulateRequestContext. Its value is r.Header.Get("X-Forwarded-For"). ContextKeyRequestXForwardedFor // ContextKeyRequestXForwardedProto is populated in the context by // PopulateRequestContext. Its value is r.Header.Get("X-Forwarded-Proto"). ContextKeyRequestXForwardedProto // ContextKeyRequestAuthorization is populated in the context by // PopulateRequestContext. Its value is r.Header.Get("Authorization"). ContextKeyRequestAuthorization // ContextKeyRequestReferer is populated in the context by // PopulateRequestContext. Its value is r.Header.Get("Referer"). ContextKeyRequestReferer // ContextKeyRequestUserAgent is populated in the context by // PopulateRequestContext. Its value is r.Header.Get("User-Agent"). ContextKeyRequestUserAgent // ContextKeyRequestXRequestID is populated in the context by // PopulateRequestContext. Its value is r.Header.Get("X-Request-Id"). ContextKeyRequestXRequestID // ContextKeyRequestAccept is populated in the context by // PopulateRequestContext. Its value is r.Header.Get("Accept"). ContextKeyRequestAccept // ContextKeyResponseHeaders is populated in the context whenever a // ServerFinalizerFunc is specified. Its value is of type http.Header, and // is captured only once the entire response has been written. ContextKeyResponseHeaders // ContextKeyResponseSize is populated in the context whenever a // ServerFinalizerFunc is specified. Its value is of type int64. ContextKeyResponseSize ) golang-github-go-kit-kit-0.13.0/transport/http/request_response_funcs_test.go000066400000000000000000000013141443521372500275110ustar00rootroot00000000000000package http_test import ( "context" "net/http/httptest" "testing" httptransport "github.com/go-kit/kit/transport/http" ) func TestSetHeader(t *testing.T) { const ( key = "X-Foo" val = "12345" ) r := httptest.NewRecorder() httptransport.SetResponseHeader(key, val)(context.Background(), r) if want, have := val, r.Header().Get(key); want != have { t.Errorf("want %q, have %q", want, have) } } func TestSetContentType(t *testing.T) { const contentType = "application/json" r := httptest.NewRecorder() httptransport.SetContentType(contentType)(context.Background(), r) if want, have := contentType, r.Header().Get("Content-Type"); want != have { t.Errorf("want %q, have %q", want, have) } } golang-github-go-kit-kit-0.13.0/transport/http/server.go000066400000000000000000000172151443521372500231630ustar00rootroot00000000000000package http import ( "context" "encoding/json" "net/http" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/transport" "github.com/go-kit/log" ) // Server wraps an endpoint and implements http.Handler. type Server struct { e endpoint.Endpoint dec DecodeRequestFunc enc EncodeResponseFunc before []RequestFunc after []ServerResponseFunc errorEncoder ErrorEncoder finalizer []ServerFinalizerFunc errorHandler transport.ErrorHandler } // NewServer constructs a new server, which implements http.Handler and wraps // the provided endpoint. func NewServer( e endpoint.Endpoint, dec DecodeRequestFunc, enc EncodeResponseFunc, options ...ServerOption, ) *Server { s := &Server{ e: e, dec: dec, enc: enc, errorEncoder: DefaultErrorEncoder, errorHandler: transport.NewLogErrorHandler(log.NewNopLogger()), } for _, option := range options { option(s) } return s } // ServerOption sets an optional parameter for servers. type ServerOption func(*Server) // ServerBefore functions are executed on the HTTP request object before the // request is decoded. func ServerBefore(before ...RequestFunc) ServerOption { return func(s *Server) { s.before = append(s.before, before...) } } // ServerAfter functions are executed on the HTTP response writer after the // endpoint is invoked, but before anything is written to the client. func ServerAfter(after ...ServerResponseFunc) ServerOption { return func(s *Server) { s.after = append(s.after, after...) } } // ServerErrorEncoder is used to encode errors to the http.ResponseWriter // whenever they're encountered in the processing of a request. Clients can // use this to provide custom error formatting and response codes. By default, // errors will be written with the DefaultErrorEncoder. func ServerErrorEncoder(ee ErrorEncoder) ServerOption { return func(s *Server) { s.errorEncoder = ee } } // ServerErrorLogger is used to log non-terminal errors. By default, no errors // are logged. This is intended as a diagnostic measure. Finer-grained control // of error handling, including logging in more detail, should be performed in a // custom ServerErrorEncoder or ServerFinalizer, both of which have access to // the context. // Deprecated: Use ServerErrorHandler instead. func ServerErrorLogger(logger log.Logger) ServerOption { return func(s *Server) { s.errorHandler = transport.NewLogErrorHandler(logger) } } // ServerErrorHandler is used to handle non-terminal errors. By default, non-terminal errors // are ignored. This is intended as a diagnostic measure. Finer-grained control // of error handling, including logging in more detail, should be performed in a // custom ServerErrorEncoder or ServerFinalizer, both of which have access to // the context. func ServerErrorHandler(errorHandler transport.ErrorHandler) ServerOption { return func(s *Server) { s.errorHandler = errorHandler } } // ServerFinalizer is executed at the end of every HTTP request. // By default, no finalizer is registered. func ServerFinalizer(f ...ServerFinalizerFunc) ServerOption { return func(s *Server) { s.finalizer = append(s.finalizer, f...) } } // ServeHTTP implements http.Handler. func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if len(s.finalizer) > 0 { iw := &interceptingWriter{w, http.StatusOK, 0} defer func() { ctx = context.WithValue(ctx, ContextKeyResponseHeaders, iw.Header()) ctx = context.WithValue(ctx, ContextKeyResponseSize, iw.written) for _, f := range s.finalizer { f(ctx, iw.code, r) } }() w = iw.reimplementInterfaces() } for _, f := range s.before { ctx = f(ctx, r) } request, err := s.dec(ctx, r) if err != nil { s.errorHandler.Handle(ctx, err) s.errorEncoder(ctx, err, w) return } response, err := s.e(ctx, request) if err != nil { s.errorHandler.Handle(ctx, err) s.errorEncoder(ctx, err, w) return } for _, f := range s.after { ctx = f(ctx, w) } if err := s.enc(ctx, w, response); err != nil { s.errorHandler.Handle(ctx, err) s.errorEncoder(ctx, err, w) return } } // ErrorEncoder is responsible for encoding an error to the ResponseWriter. // Users are encouraged to use custom ErrorEncoders to encode HTTP errors to // their clients, and will likely want to pass and check for their own error // types. See the example shipping/handling service. type ErrorEncoder func(ctx context.Context, err error, w http.ResponseWriter) // ServerFinalizerFunc can be used to perform work at the end of an HTTP // request, after the response has been written to the client. The principal // intended use is for request logging. In addition to the response code // provided in the function signature, additional response parameters are // provided in the context under keys with the ContextKeyResponse prefix. type ServerFinalizerFunc func(ctx context.Context, code int, r *http.Request) // NopRequestDecoder is a DecodeRequestFunc that can be used for requests that do not // need to be decoded, and simply returns nil, nil. func NopRequestDecoder(ctx context.Context, r *http.Request) (interface{}, error) { return nil, nil } // EncodeJSONResponse is a EncodeResponseFunc that serializes the response as a // JSON object to the ResponseWriter. Many JSON-over-HTTP services can use it as // a sensible default. If the response implements Headerer, the provided headers // will be applied to the response. If the response implements StatusCoder, the // provided StatusCode will be used instead of 200. func EncodeJSONResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") if headerer, ok := response.(Headerer); ok { for k, values := range headerer.Headers() { for _, v := range values { w.Header().Add(k, v) } } } code := http.StatusOK if sc, ok := response.(StatusCoder); ok { code = sc.StatusCode() } w.WriteHeader(code) if code == http.StatusNoContent { return nil } return json.NewEncoder(w).Encode(response) } // DefaultErrorEncoder writes the error to the ResponseWriter, by default a // content type of text/plain, a body of the plain text of the error, and a // status code of 500. If the error implements Headerer, the provided headers // will be applied to the response. If the error implements json.Marshaler, and // the marshaling succeeds, a content type of application/json and the JSON // encoded form of the error will be used. If the error implements StatusCoder, // the provided StatusCode will be used instead of 500. func DefaultErrorEncoder(_ context.Context, err error, w http.ResponseWriter) { contentType, body := "text/plain; charset=utf-8", []byte(err.Error()) if marshaler, ok := err.(json.Marshaler); ok { if jsonBody, marshalErr := marshaler.MarshalJSON(); marshalErr == nil { contentType, body = "application/json; charset=utf-8", jsonBody } } w.Header().Set("Content-Type", contentType) if headerer, ok := err.(Headerer); ok { for k, values := range headerer.Headers() { for _, v := range values { w.Header().Add(k, v) } } } code := http.StatusInternalServerError if sc, ok := err.(StatusCoder); ok { code = sc.StatusCode() } w.WriteHeader(code) w.Write(body) } // StatusCoder is checked by DefaultErrorEncoder. If an error value implements // StatusCoder, the StatusCode will be used when encoding the error. By default, // StatusInternalServerError (500) is used. type StatusCoder interface { StatusCode() int } // Headerer is checked by DefaultErrorEncoder. If an error value implements // Headerer, the provided headers will be applied to the response writer, after // the Content-Type is set. type Headerer interface { Headers() http.Header } golang-github-go-kit-kit-0.13.0/transport/http/server_test.go000066400000000000000000000326541443521372500242260ustar00rootroot00000000000000package http_test import ( "context" "errors" "io/ioutil" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/go-kit/kit/endpoint" httptransport "github.com/go-kit/kit/transport/http" ) func TestServerBadDecode(t *testing.T) { handler := httptransport.NewServer( func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, errors.New("dang") }, func(context.Context, http.ResponseWriter, interface{}) error { return nil }, ) server := httptest.NewServer(handler) defer server.Close() resp, _ := http.Get(server.URL) if want, have := http.StatusInternalServerError, resp.StatusCode; want != have { t.Errorf("want %d, have %d", want, have) } } func TestServerBadEndpoint(t *testing.T) { handler := httptransport.NewServer( func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errors.New("dang") }, func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, func(context.Context, http.ResponseWriter, interface{}) error { return nil }, ) server := httptest.NewServer(handler) defer server.Close() resp, _ := http.Get(server.URL) if want, have := http.StatusInternalServerError, resp.StatusCode; want != have { t.Errorf("want %d, have %d", want, have) } } func TestServerBadEncode(t *testing.T) { handler := httptransport.NewServer( func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, func(context.Context, http.ResponseWriter, interface{}) error { return errors.New("dang") }, ) server := httptest.NewServer(handler) defer server.Close() resp, _ := http.Get(server.URL) if want, have := http.StatusInternalServerError, resp.StatusCode; want != have { t.Errorf("want %d, have %d", want, have) } } func TestServerErrorEncoder(t *testing.T) { errTeapot := errors.New("teapot") code := func(err error) int { if errors.Is(err, errTeapot) { return http.StatusTeapot } return http.StatusInternalServerError } handler := httptransport.NewServer( func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errTeapot }, func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, func(context.Context, http.ResponseWriter, interface{}) error { return nil }, httptransport.ServerErrorEncoder(func(_ context.Context, err error, w http.ResponseWriter) { w.WriteHeader(code(err)) }), ) server := httptest.NewServer(handler) defer server.Close() resp, _ := http.Get(server.URL) if want, have := http.StatusTeapot, resp.StatusCode; want != have { t.Errorf("want %d, have %d", want, have) } } func TestServerHappyPath(t *testing.T) { step, response := testServer(t) step() resp := <-response defer resp.Body.Close() buf, _ := ioutil.ReadAll(resp.Body) if want, have := http.StatusOK, resp.StatusCode; want != have { t.Errorf("want %d, have %d (%s)", want, have, buf) } } func TestMultipleServerBefore(t *testing.T) { var ( headerKey = "X-Henlo-Lizer" headerVal = "Helllo you stinky lizard" statusCode = http.StatusTeapot responseBody = "go eat a fly ugly\n" done = make(chan struct{}) ) handler := httptransport.NewServer( endpoint.Nop, func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, func(_ context.Context, w http.ResponseWriter, _ interface{}) error { w.Header().Set(headerKey, headerVal) w.WriteHeader(statusCode) w.Write([]byte(responseBody)) return nil }, httptransport.ServerBefore(func(ctx context.Context, r *http.Request) context.Context { ctx = context.WithValue(ctx, "one", 1) return ctx }), httptransport.ServerBefore(func(ctx context.Context, r *http.Request) context.Context { if _, ok := ctx.Value("one").(int); !ok { t.Error("Value was not set properly when multiple ServerBefores are used") } close(done) return ctx }), ) server := httptest.NewServer(handler) defer server.Close() go http.Get(server.URL) select { case <-done: case <-time.After(time.Second): t.Fatal("timeout waiting for finalizer") } } func TestMultipleServerAfter(t *testing.T) { var ( headerKey = "X-Henlo-Lizer" headerVal = "Helllo you stinky lizard" statusCode = http.StatusTeapot responseBody = "go eat a fly ugly\n" done = make(chan struct{}) ) handler := httptransport.NewServer( endpoint.Nop, func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, func(_ context.Context, w http.ResponseWriter, _ interface{}) error { w.Header().Set(headerKey, headerVal) w.WriteHeader(statusCode) w.Write([]byte(responseBody)) return nil }, httptransport.ServerAfter(func(ctx context.Context, w http.ResponseWriter) context.Context { ctx = context.WithValue(ctx, "one", 1) return ctx }), httptransport.ServerAfter(func(ctx context.Context, w http.ResponseWriter) context.Context { if _, ok := ctx.Value("one").(int); !ok { t.Error("Value was not set properly when multiple ServerAfters are used") } close(done) return ctx }), ) server := httptest.NewServer(handler) defer server.Close() go http.Get(server.URL) select { case <-done: case <-time.After(time.Second): t.Fatal("timeout waiting for finalizer") } } func TestServerFinalizer(t *testing.T) { var ( headerKey = "X-Henlo-Lizer" headerVal = "Helllo you stinky lizard" statusCode = http.StatusTeapot responseBody = "go eat a fly ugly\n" done = make(chan struct{}) ) handler := httptransport.NewServer( endpoint.Nop, func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, func(_ context.Context, w http.ResponseWriter, _ interface{}) error { w.Header().Set(headerKey, headerVal) w.WriteHeader(statusCode) w.Write([]byte(responseBody)) return nil }, httptransport.ServerFinalizer(func(ctx context.Context, code int, _ *http.Request) { if want, have := statusCode, code; want != have { t.Errorf("StatusCode: want %d, have %d", want, have) } responseHeader := ctx.Value(httptransport.ContextKeyResponseHeaders).(http.Header) if want, have := headerVal, responseHeader.Get(headerKey); want != have { t.Errorf("%s: want %q, have %q", headerKey, want, have) } responseSize := ctx.Value(httptransport.ContextKeyResponseSize).(int64) if want, have := int64(len(responseBody)), responseSize; want != have { t.Errorf("response size: want %d, have %d", want, have) } close(done) }), ) server := httptest.NewServer(handler) defer server.Close() go http.Get(server.URL) select { case <-done: case <-time.After(time.Second): t.Fatal("timeout waiting for finalizer") } } type enhancedResponse struct { Foo string `json:"foo"` } func (e enhancedResponse) StatusCode() int { return http.StatusPaymentRequired } func (e enhancedResponse) Headers() http.Header { return http.Header{"X-Edward": []string{"Snowden"}} } func TestEncodeJSONResponse(t *testing.T) { handler := httptransport.NewServer( func(context.Context, interface{}) (interface{}, error) { return enhancedResponse{Foo: "bar"}, nil }, func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, httptransport.EncodeJSONResponse, ) server := httptest.NewServer(handler) defer server.Close() resp, err := http.Get(server.URL) if err != nil { t.Fatal(err) } if want, have := http.StatusPaymentRequired, resp.StatusCode; want != have { t.Errorf("StatusCode: want %d, have %d", want, have) } if want, have := "Snowden", resp.Header.Get("X-Edward"); want != have { t.Errorf("X-Edward: want %q, have %q", want, have) } buf, _ := ioutil.ReadAll(resp.Body) if want, have := `{"foo":"bar"}`, strings.TrimSpace(string(buf)); want != have { t.Errorf("Body: want %s, have %s", want, have) } } type multiHeaderResponse struct{} func (_ multiHeaderResponse) Headers() http.Header { return http.Header{"Vary": []string{"Origin", "User-Agent"}} } func TestAddMultipleHeaders(t *testing.T) { handler := httptransport.NewServer( func(context.Context, interface{}) (interface{}, error) { return multiHeaderResponse{}, nil }, func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, httptransport.EncodeJSONResponse, ) server := httptest.NewServer(handler) defer server.Close() resp, err := http.Get(server.URL) if err != nil { t.Fatal(err) } expect := map[string]map[string]struct{}{"Vary": map[string]struct{}{"Origin": struct{}{}, "User-Agent": struct{}{}}} for k, vls := range resp.Header { for _, v := range vls { delete((expect[k]), v) } if len(expect[k]) != 0 { t.Errorf("Header: unexpected header %s: %v", k, expect[k]) } } } type multiHeaderResponseError struct { multiHeaderResponse msg string } func (m multiHeaderResponseError) Error() string { return m.msg } func TestAddMultipleHeadersErrorEncoder(t *testing.T) { errStr := "oh no" handler := httptransport.NewServer( func(context.Context, interface{}) (interface{}, error) { return nil, multiHeaderResponseError{msg: errStr} }, func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, httptransport.EncodeJSONResponse, ) server := httptest.NewServer(handler) defer server.Close() resp, err := http.Get(server.URL) if err != nil { t.Fatal(err) } expect := map[string]map[string]struct{}{"Vary": map[string]struct{}{"Origin": struct{}{}, "User-Agent": struct{}{}}} for k, vls := range resp.Header { for _, v := range vls { delete((expect[k]), v) } if len(expect[k]) != 0 { t.Errorf("Header: unexpected header %s: %v", k, expect[k]) } } if b, _ := ioutil.ReadAll(resp.Body); errStr != string(b) { t.Errorf("ErrorEncoder: got: %q, expected: %q", b, errStr) } } type noContentResponse struct{} func (e noContentResponse) StatusCode() int { return http.StatusNoContent } func TestEncodeNoContent(t *testing.T) { handler := httptransport.NewServer( func(context.Context, interface{}) (interface{}, error) { return noContentResponse{}, nil }, func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, httptransport.EncodeJSONResponse, ) server := httptest.NewServer(handler) defer server.Close() resp, err := http.Get(server.URL) if err != nil { t.Fatal(err) } if want, have := http.StatusNoContent, resp.StatusCode; want != have { t.Errorf("StatusCode: want %d, have %d", want, have) } buf, _ := ioutil.ReadAll(resp.Body) if want, have := 0, len(buf); want != have { t.Errorf("Body: want no content, have %d bytes", have) } } type enhancedError struct{} func (e enhancedError) Error() string { return "enhanced error" } func (e enhancedError) StatusCode() int { return http.StatusTeapot } func (e enhancedError) MarshalJSON() ([]byte, error) { return []byte(`{"err":"enhanced"}`), nil } func (e enhancedError) Headers() http.Header { return http.Header{"X-Enhanced": []string{"1"}} } func TestEnhancedError(t *testing.T) { handler := httptransport.NewServer( func(context.Context, interface{}) (interface{}, error) { return nil, enhancedError{} }, func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, func(_ context.Context, w http.ResponseWriter, _ interface{}) error { return nil }, ) server := httptest.NewServer(handler) defer server.Close() resp, err := http.Get(server.URL) if err != nil { t.Fatal(err) } defer resp.Body.Close() if want, have := http.StatusTeapot, resp.StatusCode; want != have { t.Errorf("StatusCode: want %d, have %d", want, have) } if want, have := "1", resp.Header.Get("X-Enhanced"); want != have { t.Errorf("X-Enhanced: want %q, have %q", want, have) } buf, _ := ioutil.ReadAll(resp.Body) if want, have := `{"err":"enhanced"}`, strings.TrimSpace(string(buf)); want != have { t.Errorf("Body: want %s, have %s", want, have) } } func TestNoOpRequestDecoder(t *testing.T) { resw := httptest.NewRecorder() req, err := http.NewRequest(http.MethodGet, "/", nil) if err != nil { t.Error("Failed to create request") } handler := httptransport.NewServer( func(ctx context.Context, request interface{}) (interface{}, error) { if request != nil { t.Error("Expected nil request in endpoint when using NopRequestDecoder") } return nil, nil }, httptransport.NopRequestDecoder, httptransport.EncodeJSONResponse, ) handler.ServeHTTP(resw, req) if resw.Code != http.StatusOK { t.Errorf("Expected status code %d but got %d", http.StatusOK, resw.Code) } } func testServer(t *testing.T) (step func(), resp <-chan *http.Response) { var ( stepch = make(chan bool) endpoint = func(context.Context, interface{}) (interface{}, error) { <-stepch; return struct{}{}, nil } response = make(chan *http.Response) handler = httptransport.NewServer( endpoint, func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, func(context.Context, http.ResponseWriter, interface{}) error { return nil }, httptransport.ServerBefore(func(ctx context.Context, r *http.Request) context.Context { return ctx }), httptransport.ServerAfter(func(ctx context.Context, w http.ResponseWriter) context.Context { return ctx }), ) ) go func() { server := httptest.NewServer(handler) defer server.Close() resp, err := http.Get(server.URL) if err != nil { t.Error(err) return } response <- resp }() return func() { stepch <- true }, response } golang-github-go-kit-kit-0.13.0/transport/httprp/000077500000000000000000000000001443521372500216625ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/transport/httprp/doc.go000066400000000000000000000003401443521372500227530ustar00rootroot00000000000000// Package httprp provides an HTTP reverse-proxy transport. HTTP handlers that // need to proxy requests to another HTTP service can do so with this package by // specifying the URL to forward the request to. package httprp golang-github-go-kit-kit-0.13.0/transport/httprp/server.go000066400000000000000000000027141443521372500235230ustar00rootroot00000000000000package httprp import ( "context" "net/http" "net/http/httputil" "net/url" ) // RequestFunc may take information from an HTTP request and put it into a // request context. BeforeFuncs are executed prior to invoking the // endpoint. type RequestFunc func(context.Context, *http.Request) context.Context // Server is a proxying request handler. type Server struct { proxy http.Handler before []RequestFunc errorEncoder func(w http.ResponseWriter, err error) } // NewServer constructs a new server that implements http.Server and will proxy // requests to the given base URL using its scheme, host, and base path. // If the target's path is "/base" and the incoming request was for "/dir", // the target request will be for /base/dir. func NewServer( baseURL *url.URL, options ...ServerOption, ) *Server { s := &Server{ proxy: httputil.NewSingleHostReverseProxy(baseURL), } for _, option := range options { option(s) } return s } // ServerOption sets an optional parameter for servers. type ServerOption func(*Server) // ServerBefore functions are executed on the HTTP request object before the // request is decoded. func ServerBefore(before ...RequestFunc) ServerOption { return func(s *Server) { s.before = append(s.before, before...) } } // ServeHTTP implements http.Handler. func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx := r.Context() for _, f := range s.before { ctx = f(ctx, r) } s.proxy.ServeHTTP(w, r) } golang-github-go-kit-kit-0.13.0/transport/httprp/server_test.go000066400000000000000000000103631443521372500245610ustar00rootroot00000000000000package httprp_test import ( "context" "io/ioutil" "net/http" "net/http/httptest" "net/url" "testing" httptransport "github.com/go-kit/kit/transport/httprp" ) func TestServerHappyPathSingleServer(t *testing.T) { originServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("hey")) })) defer originServer.Close() originURL, _ := url.Parse(originServer.URL) handler := httptransport.NewServer( originURL, ) proxyServer := httptest.NewServer(handler) defer proxyServer.Close() resp, _ := http.Get(proxyServer.URL) if want, have := http.StatusOK, resp.StatusCode; want != have { t.Errorf("want %d, have %d", want, have) } responseBody, _ := ioutil.ReadAll(resp.Body) if want, have := "hey", string(responseBody); want != have { t.Errorf("want %q, have %q", want, have) } } func TestServerHappyPathSingleServerWithServerOptions(t *testing.T) { const ( headerKey = "X-TEST-HEADER" headerVal = "go-kit-proxy" ) originServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if want, have := headerVal, r.Header.Get(headerKey); want != have { t.Errorf("want %q, have %q", want, have) } w.WriteHeader(http.StatusOK) w.Write([]byte("hey")) })) defer originServer.Close() originURL, _ := url.Parse(originServer.URL) handler := httptransport.NewServer( originURL, httptransport.ServerBefore(func(ctx context.Context, r *http.Request) context.Context { r.Header.Add(headerKey, headerVal) return ctx }), ) proxyServer := httptest.NewServer(handler) defer proxyServer.Close() resp, _ := http.Get(proxyServer.URL) if want, have := http.StatusOK, resp.StatusCode; want != have { t.Errorf("want %d, have %d", want, have) } responseBody, _ := ioutil.ReadAll(resp.Body) if want, have := "hey", string(responseBody); want != have { t.Errorf("want %q, have %q", want, have) } } func TestServerOriginServerNotFoundResponse(t *testing.T) { originServer := httptest.NewServer(http.NotFoundHandler()) defer originServer.Close() originURL, _ := url.Parse(originServer.URL) handler := httptransport.NewServer( originURL, ) proxyServer := httptest.NewServer(handler) defer proxyServer.Close() resp, _ := http.Get(proxyServer.URL) if want, have := http.StatusNotFound, resp.StatusCode; want != have { t.Errorf("want %d, have %d", want, have) } } func TestServerOriginServerUnreachable(t *testing.T) { // create a server, then promptly shut it down originServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })) originURL, _ := url.Parse(originServer.URL) originServer.Close() handler := httptransport.NewServer( originURL, ) proxyServer := httptest.NewServer(handler) defer proxyServer.Close() resp, _ := http.Get(proxyServer.URL) switch resp.StatusCode { case http.StatusBadGateway: // go1.7 and beyond break case http.StatusInternalServerError: // to go1.7 break default: t.Errorf("want %d or %d, have %d", http.StatusBadGateway, http.StatusInternalServerError, resp.StatusCode) } } func TestMultipleServerBefore(t *testing.T) { const ( headerKey = "X-TEST-HEADER" headerVal = "go-kit-proxy" ) originServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if want, have := headerVal, r.Header.Get(headerKey); want != have { t.Errorf("want %q, have %q", want, have) } w.WriteHeader(http.StatusOK) w.Write([]byte("hey")) })) defer originServer.Close() originURL, _ := url.Parse(originServer.URL) handler := httptransport.NewServer( originURL, httptransport.ServerBefore(func(ctx context.Context, r *http.Request) context.Context { r.Header.Add(headerKey, headerVal) return ctx }), httptransport.ServerBefore(func(ctx context.Context, r *http.Request) context.Context { return ctx }), ) proxyServer := httptest.NewServer(handler) defer proxyServer.Close() resp, _ := http.Get(proxyServer.URL) if want, have := http.StatusOK, resp.StatusCode; want != have { t.Errorf("want %d, have %d", want, have) } responseBody, _ := ioutil.ReadAll(resp.Body) if want, have := "hey", string(responseBody); want != have { t.Errorf("want %q, have %q", want, have) } } golang-github-go-kit-kit-0.13.0/transport/nats/000077500000000000000000000000001443521372500213065ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/transport/nats/doc.go000066400000000000000000000000701443521372500223770ustar00rootroot00000000000000// Package nats provides a NATS transport. package nats golang-github-go-kit-kit-0.13.0/transport/nats/encode_decode.go000066400000000000000000000030741443521372500244010ustar00rootroot00000000000000package nats import ( "context" "github.com/nats-io/nats.go" ) // DecodeRequestFunc extracts a user-domain request object from a publisher // request object. It's designed to be used in NATS subscribers, for subscriber-side // endpoints. One straightforward DecodeRequestFunc could be something that // JSON decodes from the request body to the concrete response type. type DecodeRequestFunc func(context.Context, *nats.Msg) (request interface{}, err error) // EncodeRequestFunc encodes the passed request object into the NATS request // object. It's designed to be used in NATS publishers, for publisher-side // endpoints. One straightforward EncodeRequestFunc could something that JSON // encodes the object directly to the request payload. type EncodeRequestFunc func(context.Context, *nats.Msg, interface{}) error // EncodeResponseFunc encodes the passed response object to the subscriber reply. // It's designed to be used in NATS subscribers, for subscriber-side // endpoints. One straightforward EncodeResponseFunc could be something that // JSON encodes the object directly to the response body. type EncodeResponseFunc func(context.Context, string, *nats.Conn, interface{}) error // DecodeResponseFunc extracts a user-domain response object from an NATS // response object. It's designed to be used in NATS publisher, for publisher-side // endpoints. One straightforward DecodeResponseFunc could be something that // JSON decodes from the response payload to the concrete response type. type DecodeResponseFunc func(context.Context, *nats.Msg) (response interface{}, err error) golang-github-go-kit-kit-0.13.0/transport/nats/publisher.go000066400000000000000000000054021443521372500236330ustar00rootroot00000000000000package nats import ( "context" "encoding/json" "github.com/go-kit/kit/endpoint" "github.com/nats-io/nats.go" "time" ) // Publisher wraps a URL and provides a method that implements endpoint.Endpoint. type Publisher struct { publisher *nats.Conn subject string enc EncodeRequestFunc dec DecodeResponseFunc before []RequestFunc after []PublisherResponseFunc timeout time.Duration } // NewPublisher constructs a usable Publisher for a single remote method. func NewPublisher( publisher *nats.Conn, subject string, enc EncodeRequestFunc, dec DecodeResponseFunc, options ...PublisherOption, ) *Publisher { p := &Publisher{ publisher: publisher, subject: subject, enc: enc, dec: dec, timeout: 10 * time.Second, } for _, option := range options { option(p) } return p } // PublisherOption sets an optional parameter for clients. type PublisherOption func(*Publisher) // PublisherBefore sets the RequestFuncs that are applied to the outgoing NATS // request before it's invoked. func PublisherBefore(before ...RequestFunc) PublisherOption { return func(p *Publisher) { p.before = append(p.before, before...) } } // PublisherAfter sets the ClientResponseFuncs applied to the incoming NATS // request prior to it being decoded. This is useful for obtaining anything off // of the response and adding onto the context prior to decoding. func PublisherAfter(after ...PublisherResponseFunc) PublisherOption { return func(p *Publisher) { p.after = append(p.after, after...) } } // PublisherTimeout sets the available timeout for NATS request. func PublisherTimeout(timeout time.Duration) PublisherOption { return func(p *Publisher) { p.timeout = timeout } } // Endpoint returns a usable endpoint that invokes the remote endpoint. func (p Publisher) Endpoint() endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { ctx, cancel := context.WithTimeout(ctx, p.timeout) defer cancel() msg := nats.Msg{Subject: p.subject} if err := p.enc(ctx, &msg, request); err != nil { return nil, err } for _, f := range p.before { ctx = f(ctx, &msg) } resp, err := p.publisher.RequestWithContext(ctx, msg.Subject, msg.Data) if err != nil { return nil, err } for _, f := range p.after { ctx = f(ctx, resp) } response, err := p.dec(ctx, resp) if err != nil { return nil, err } return response, nil } } // EncodeJSONRequest is an EncodeRequestFunc that serializes the request as a // JSON object to the Data of the Msg. Many JSON-over-NATS services can use it as // a sensible default. func EncodeJSONRequest(_ context.Context, msg *nats.Msg, request interface{}) error { b, err := json.Marshal(request) if err != nil { return err } msg.Data = b return nil } golang-github-go-kit-kit-0.13.0/transport/nats/publisher_test.go000066400000000000000000000144721443521372500247010ustar00rootroot00000000000000package nats_test import ( "context" "strings" "testing" "time" natstransport "github.com/go-kit/kit/transport/nats" "github.com/nats-io/nats.go" ) func TestPublisher(t *testing.T) { var ( testdata = "testdata" encode = func(context.Context, *nats.Msg, interface{}) error { return nil } decode = func(_ context.Context, msg *nats.Msg) (interface{}, error) { return TestResponse{string(msg.Data), ""}, nil } ) s, c := newNATSConn(t) defer func() { s.Shutdown(); s.WaitForShutdown() }() defer c.Close() sub, err := c.QueueSubscribe("natstransport.test", "natstransport", func(msg *nats.Msg) { if err := c.Publish(msg.Reply, []byte(testdata)); err != nil { t.Fatal(err) } }) if err != nil { t.Fatal(err) } defer sub.Unsubscribe() publisher := natstransport.NewPublisher( c, "natstransport.test", encode, decode, ) res, err := publisher.Endpoint()(context.Background(), struct{}{}) if err != nil { t.Fatal(err) } response, ok := res.(TestResponse) if !ok { t.Fatal("response should be TestResponse") } if want, have := testdata, response.String; want != have { t.Errorf("want %q, have %q", want, have) } } func TestPublisherBefore(t *testing.T) { var ( testdata = "testdata" encode = func(context.Context, *nats.Msg, interface{}) error { return nil } decode = func(_ context.Context, msg *nats.Msg) (interface{}, error) { return TestResponse{string(msg.Data), ""}, nil } ) s, c := newNATSConn(t) defer func() { s.Shutdown(); s.WaitForShutdown() }() defer c.Close() sub, err := c.QueueSubscribe("natstransport.test", "natstransport", func(msg *nats.Msg) { if err := c.Publish(msg.Reply, msg.Data); err != nil { t.Fatal(err) } }) if err != nil { t.Fatal(err) } defer sub.Unsubscribe() publisher := natstransport.NewPublisher( c, "natstransport.test", encode, decode, natstransport.PublisherBefore(func(ctx context.Context, msg *nats.Msg) context.Context { msg.Data = []byte(strings.ToUpper(string(testdata))) return ctx }), ) res, err := publisher.Endpoint()(context.Background(), struct{}{}) if err != nil { t.Fatal(err) } response, ok := res.(TestResponse) if !ok { t.Fatal("response should be TestResponse") } if want, have := strings.ToUpper(testdata), response.String; want != have { t.Errorf("want %q, have %q", want, have) } } func TestPublisherAfter(t *testing.T) { var ( testdata = "testdata" encode = func(context.Context, *nats.Msg, interface{}) error { return nil } decode = func(_ context.Context, msg *nats.Msg) (interface{}, error) { return TestResponse{string(msg.Data), ""}, nil } ) s, c := newNATSConn(t) defer func() { s.Shutdown(); s.WaitForShutdown() }() defer c.Close() sub, err := c.QueueSubscribe("natstransport.test", "natstransport", func(msg *nats.Msg) { if err := c.Publish(msg.Reply, []byte(testdata)); err != nil { t.Fatal(err) } }) if err != nil { t.Fatal(err) } defer sub.Unsubscribe() publisher := natstransport.NewPublisher( c, "natstransport.test", encode, decode, natstransport.PublisherAfter(func(ctx context.Context, msg *nats.Msg) context.Context { msg.Data = []byte(strings.ToUpper(string(msg.Data))) return ctx }), ) res, err := publisher.Endpoint()(context.Background(), struct{}{}) if err != nil { t.Fatal(err) } response, ok := res.(TestResponse) if !ok { t.Fatal("response should be TestResponse") } if want, have := strings.ToUpper(testdata), response.String; want != have { t.Errorf("want %q, have %q", want, have) } } func TestPublisherTimeout(t *testing.T) { var ( encode = func(context.Context, *nats.Msg, interface{}) error { return nil } decode = func(_ context.Context, msg *nats.Msg) (interface{}, error) { return TestResponse{string(msg.Data), ""}, nil } ) s, c := newNATSConn(t) defer func() { s.Shutdown(); s.WaitForShutdown() }() defer c.Close() ch := make(chan struct{}) defer close(ch) sub, err := c.QueueSubscribe("natstransport.test", "natstransport", func(msg *nats.Msg) { <-ch }) if err != nil { t.Fatal(err) } defer sub.Unsubscribe() publisher := natstransport.NewPublisher( c, "natstransport.test", encode, decode, natstransport.PublisherTimeout(time.Second), ) _, err = publisher.Endpoint()(context.Background(), struct{}{}) if err != context.DeadlineExceeded { t.Errorf("want %s, have %s", context.DeadlineExceeded, err) } } func TestPublisherCancellation(t *testing.T) { var ( testdata = "testdata" encode = func(context.Context, *nats.Msg, interface{}) error { return nil } decode = func(_ context.Context, msg *nats.Msg) (interface{}, error) { return TestResponse{string(msg.Data), ""}, nil } ) s, c := newNATSConn(t) defer func() { s.Shutdown(); s.WaitForShutdown() }() defer c.Close() sub, err := c.QueueSubscribe("natstransport.test", "natstransport", func(msg *nats.Msg) { if err := c.Publish(msg.Reply, []byte(testdata)); err != nil { t.Fatal(err) } }) if err != nil { t.Fatal(err) } defer sub.Unsubscribe() publisher := natstransport.NewPublisher( c, "natstransport.test", encode, decode, ) ctx, cancel := context.WithCancel(context.Background()) cancel() _, err = publisher.Endpoint()(ctx, struct{}{}) if err != context.Canceled { t.Errorf("want %s, have %s", context.Canceled, err) } } func TestEncodeJSONRequest(t *testing.T) { var data string s, c := newNATSConn(t) defer func() { s.Shutdown(); s.WaitForShutdown() }() defer c.Close() sub, err := c.QueueSubscribe("natstransport.test", "natstransport", func(msg *nats.Msg) { data = string(msg.Data) if err := c.Publish(msg.Reply, []byte("")); err != nil { t.Fatal(err) } }) if err != nil { t.Fatal(err) } defer sub.Unsubscribe() publisher := natstransport.NewPublisher( c, "natstransport.test", natstransport.EncodeJSONRequest, func(context.Context, *nats.Msg) (interface{}, error) { return nil, nil }, ).Endpoint() for _, test := range []struct { value interface{} body string }{ {nil, "null"}, {12, "12"}, {1.2, "1.2"}, {true, "true"}, {"test", "\"test\""}, {struct { Foo string `json:"foo"` }{"foo"}, "{\"foo\":\"foo\"}"}, } { if _, err := publisher(context.Background(), test.value); err != nil { t.Fatal(err) continue } if data != test.body { t.Errorf("%v: actual %#v, expected %#v", test.value, data, test.body) } } } golang-github-go-kit-kit-0.13.0/transport/nats/request_response_funcs.go000066400000000000000000000016451443521372500264470ustar00rootroot00000000000000package nats import ( "context" "github.com/nats-io/nats.go" ) // RequestFunc may take information from a publisher request and put it into a // request context. In Subscribers, RequestFuncs are executed prior to invoking the // endpoint. type RequestFunc func(context.Context, *nats.Msg) context.Context // SubscriberResponseFunc may take information from a request context and use it to // manipulate a Publisher. SubscriberResponseFuncs are only executed in // subscribers, after invoking the endpoint but prior to publishing a reply. type SubscriberResponseFunc func(context.Context, *nats.Conn) context.Context // PublisherResponseFunc may take information from an NATS request and make the // response available for consumption. ClientResponseFuncs are only executed in // clients, after a request has been made, but prior to it being decoded. type PublisherResponseFunc func(context.Context, *nats.Msg) context.Context golang-github-go-kit-kit-0.13.0/transport/nats/subscriber.go000066400000000000000000000135651443521372500240120ustar00rootroot00000000000000package nats import ( "context" "encoding/json" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/transport" "github.com/go-kit/log" "github.com/nats-io/nats.go" ) // Subscriber wraps an endpoint and provides nats.MsgHandler. type Subscriber struct { e endpoint.Endpoint dec DecodeRequestFunc enc EncodeResponseFunc before []RequestFunc after []SubscriberResponseFunc errorEncoder ErrorEncoder finalizer []SubscriberFinalizerFunc errorHandler transport.ErrorHandler } // NewSubscriber constructs a new subscriber, which provides nats.MsgHandler and wraps // the provided endpoint. func NewSubscriber( e endpoint.Endpoint, dec DecodeRequestFunc, enc EncodeResponseFunc, options ...SubscriberOption, ) *Subscriber { s := &Subscriber{ e: e, dec: dec, enc: enc, errorEncoder: DefaultErrorEncoder, errorHandler: transport.NewLogErrorHandler(log.NewNopLogger()), } for _, option := range options { option(s) } return s } // SubscriberOption sets an optional parameter for subscribers. type SubscriberOption func(*Subscriber) // SubscriberBefore functions are executed on the publisher request object before the // request is decoded. func SubscriberBefore(before ...RequestFunc) SubscriberOption { return func(s *Subscriber) { s.before = append(s.before, before...) } } // SubscriberAfter functions are executed on the subscriber reply after the // endpoint is invoked, but before anything is published to the reply. func SubscriberAfter(after ...SubscriberResponseFunc) SubscriberOption { return func(s *Subscriber) { s.after = append(s.after, after...) } } // SubscriberErrorEncoder is used to encode errors to the subscriber reply // whenever they're encountered in the processing of a request. Clients can // use this to provide custom error formatting. By default, // errors will be published with the DefaultErrorEncoder. func SubscriberErrorEncoder(ee ErrorEncoder) SubscriberOption { return func(s *Subscriber) { s.errorEncoder = ee } } // SubscriberErrorLogger is used to log non-terminal errors. By default, no errors // are logged. This is intended as a diagnostic measure. Finer-grained control // of error handling, including logging in more detail, should be performed in a // custom SubscriberErrorEncoder which has access to the context. // Deprecated: Use SubscriberErrorHandler instead. func SubscriberErrorLogger(logger log.Logger) SubscriberOption { return func(s *Subscriber) { s.errorHandler = transport.NewLogErrorHandler(logger) } } // SubscriberErrorHandler is used to handle non-terminal errors. By default, non-terminal errors // are ignored. This is intended as a diagnostic measure. Finer-grained control // of error handling, including logging in more detail, should be performed in a // custom SubscriberErrorEncoder which has access to the context. func SubscriberErrorHandler(errorHandler transport.ErrorHandler) SubscriberOption { return func(s *Subscriber) { s.errorHandler = errorHandler } } // SubscriberFinalizer is executed at the end of every request from a publisher through NATS. // By default, no finalizer is registered. func SubscriberFinalizer(f ...SubscriberFinalizerFunc) SubscriberOption { return func(s *Subscriber) { s.finalizer = f } } // ServeMsg provides nats.MsgHandler. func (s Subscriber) ServeMsg(nc *nats.Conn) func(msg *nats.Msg) { return func(msg *nats.Msg) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() if len(s.finalizer) > 0 { defer func() { for _, f := range s.finalizer { f(ctx, msg) } }() } for _, f := range s.before { ctx = f(ctx, msg) } request, err := s.dec(ctx, msg) if err != nil { s.errorHandler.Handle(ctx, err) if msg.Reply == "" { return } s.errorEncoder(ctx, err, msg.Reply, nc) return } response, err := s.e(ctx, request) if err != nil { s.errorHandler.Handle(ctx, err) if msg.Reply == "" { return } s.errorEncoder(ctx, err, msg.Reply, nc) return } for _, f := range s.after { ctx = f(ctx, nc) } if msg.Reply == "" { return } if err := s.enc(ctx, msg.Reply, nc, response); err != nil { s.errorHandler.Handle(ctx, err) s.errorEncoder(ctx, err, msg.Reply, nc) return } } } // ErrorEncoder is responsible for encoding an error to the subscriber reply. // Users are encouraged to use custom ErrorEncoders to encode errors to // their replies, and will likely want to pass and check for their own error // types. type ErrorEncoder func(ctx context.Context, err error, reply string, nc *nats.Conn) // SubscriberFinalizerFunc can be used to perform work at the end of an request // from a publisher, after the response has been written to the publisher. The principal // intended use is for request logging. type SubscriberFinalizerFunc func(ctx context.Context, msg *nats.Msg) // NopRequestDecoder is a DecodeRequestFunc that can be used for requests that do not // need to be decoded, and simply returns nil, nil. func NopRequestDecoder(_ context.Context, _ *nats.Msg) (interface{}, error) { return nil, nil } // EncodeJSONResponse is a EncodeResponseFunc that serializes the response as a // JSON object to the subscriber reply. Many JSON-over services can use it as // a sensible default. func EncodeJSONResponse(_ context.Context, reply string, nc *nats.Conn, response interface{}) error { b, err := json.Marshal(response) if err != nil { return err } return nc.Publish(reply, b) } // DefaultErrorEncoder writes the error to the subscriber reply. func DefaultErrorEncoder(_ context.Context, err error, reply string, nc *nats.Conn) { logger := log.NewNopLogger() type Response struct { Error string `json:"err"` } var response Response response.Error = err.Error() b, err := json.Marshal(response) if err != nil { logger.Log("err", err) return } if err := nc.Publish(reply, b); err != nil { logger.Log("err", err) } } golang-github-go-kit-kit-0.13.0/transport/nats/subscriber_test.go000066400000000000000000000317651443521372500250530ustar00rootroot00000000000000package nats_test import ( "context" "encoding/json" "errors" "strings" "sync" "testing" "time" "github.com/nats-io/nats-server/v2/server" "github.com/nats-io/nats.go" "github.com/go-kit/kit/endpoint" natstransport "github.com/go-kit/kit/transport/nats" ) type TestResponse struct { String string `json:"str"` Error string `json:"err"` } func newNATSConn(t *testing.T) (*server.Server, *nats.Conn) { s, err := server.NewServer(&server.Options{ Host: "localhost", Port: 0, }) if err != nil { t.Fatal(err) } go s.Start() for i := 0; i < 5 && !s.Running(); i++ { t.Logf("Running %v", s.Running()) time.Sleep(time.Second) } if !s.Running() { s.Shutdown() s.WaitForShutdown() t.Fatal("not yet running") } if ok := s.ReadyForConnections(5 * time.Second); !ok { t.Fatal("not ready for connections") } c, err := nats.Connect("nats://"+s.Addr().String(), nats.Name(t.Name())) if err != nil { t.Fatalf("failed to connect to NATS server: %s", err) } return s, c } func TestSubscriberBadDecode(t *testing.T) { s, c := newNATSConn(t) defer func() { s.Shutdown(); s.WaitForShutdown() }() defer c.Close() handler := natstransport.NewSubscriber( func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, errors.New("dang") }, func(context.Context, string, *nats.Conn, interface{}) error { return nil }, ) resp := testRequest(t, c, handler) if want, have := "dang", resp.Error; want != have { t.Errorf("want %s, have %s", want, have) } } func TestSubscriberBadEndpoint(t *testing.T) { s, c := newNATSConn(t) defer func() { s.Shutdown(); s.WaitForShutdown() }() defer c.Close() handler := natstransport.NewSubscriber( func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errors.New("dang") }, func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, nil }, func(context.Context, string, *nats.Conn, interface{}) error { return nil }, ) resp := testRequest(t, c, handler) if want, have := "dang", resp.Error; want != have { t.Errorf("want %s, have %s", want, have) } } func TestSubscriberBadEncode(t *testing.T) { s, c := newNATSConn(t) defer func() { s.Shutdown(); s.WaitForShutdown() }() defer c.Close() handler := natstransport.NewSubscriber( func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, nil }, func(context.Context, string, *nats.Conn, interface{}) error { return errors.New("dang") }, ) resp := testRequest(t, c, handler) if want, have := "dang", resp.Error; want != have { t.Errorf("want %s, have %s", want, have) } } func TestSubscriberErrorEncoder(t *testing.T) { s, c := newNATSConn(t) defer func() { s.Shutdown(); s.WaitForShutdown() }() defer c.Close() errTeapot := errors.New("teapot") code := func(err error) error { if errors.Is(err, errTeapot) { return err } return errors.New("dang") } handler := natstransport.NewSubscriber( func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errTeapot }, func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, nil }, func(context.Context, string, *nats.Conn, interface{}) error { return nil }, natstransport.SubscriberErrorEncoder(func(_ context.Context, err error, reply string, nc *nats.Conn) { var r TestResponse r.Error = code(err).Error() b, err := json.Marshal(r) if err != nil { t.Fatal(err) } if err := c.Publish(reply, b); err != nil { t.Fatal(err) } }), ) resp := testRequest(t, c, handler) if want, have := errTeapot.Error(), resp.Error; want != have { t.Errorf("want %s, have %s", want, have) } } func TestSubscriberHappySubject(t *testing.T) { step, response := testSubscriber(t) step() r := <-response var resp TestResponse err := json.Unmarshal(r.Data, &resp) if err != nil { t.Fatal(err) } if want, have := "", resp.Error; want != have { t.Errorf("want %s, have %s (%s)", want, have, r.Data) } } func TestMultipleSubscriberBefore(t *testing.T) { s, c := newNATSConn(t) defer func() { s.Shutdown(); s.WaitForShutdown() }() defer c.Close() var ( response = struct{ Body string }{"go eat a fly ugly\n"} wg sync.WaitGroup done = make(chan struct{}) ) handler := natstransport.NewSubscriber( endpoint.Nop, func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, nil }, func(_ context.Context, reply string, nc *nats.Conn, _ interface{}) error { b, err := json.Marshal(response) if err != nil { return err } return c.Publish(reply, b) }, natstransport.SubscriberBefore(func(ctx context.Context, _ *nats.Msg) context.Context { ctx = context.WithValue(ctx, "one", 1) return ctx }), natstransport.SubscriberBefore(func(ctx context.Context, _ *nats.Msg) context.Context { if _, ok := ctx.Value("one").(int); !ok { t.Error("Value was not set properly when multiple ServerBefores are used") } close(done) return ctx }), ) sub, err := c.QueueSubscribe("natstransport.test", "natstransport", handler.ServeMsg(c)) if err != nil { t.Fatal(err) } defer sub.Unsubscribe() wg.Add(1) go func() { defer wg.Done() _, err := c.Request("natstransport.test", []byte("test data"), 2*time.Second) if err != nil { t.Fatal(err) } }() select { case <-done: case <-time.After(time.Second): t.Fatal("timeout waiting for finalizer") } wg.Wait() } func TestMultipleSubscriberAfter(t *testing.T) { s, c := newNATSConn(t) defer func() { s.Shutdown(); s.WaitForShutdown() }() defer c.Close() var ( response = struct{ Body string }{"go eat a fly ugly\n"} wg sync.WaitGroup done = make(chan struct{}) ) handler := natstransport.NewSubscriber( endpoint.Nop, func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, nil }, func(_ context.Context, reply string, nc *nats.Conn, _ interface{}) error { b, err := json.Marshal(response) if err != nil { return err } return c.Publish(reply, b) }, natstransport.SubscriberAfter(func(ctx context.Context, nc *nats.Conn) context.Context { return context.WithValue(ctx, "one", 1) }), natstransport.SubscriberAfter(func(ctx context.Context, nc *nats.Conn) context.Context { if _, ok := ctx.Value("one").(int); !ok { t.Error("Value was not set properly when multiple ServerAfters are used") } close(done) return ctx }), ) sub, err := c.QueueSubscribe("natstransport.test", "natstransport", handler.ServeMsg(c)) if err != nil { t.Fatal(err) } defer sub.Unsubscribe() wg.Add(1) go func() { defer wg.Done() _, err := c.Request("natstransport.test", []byte("test data"), 2*time.Second) if err != nil { t.Fatal(err) } }() select { case <-done: case <-time.After(time.Second): t.Fatal("timeout waiting for finalizer") } wg.Wait() } func TestSubscriberFinalizerFunc(t *testing.T) { s, c := newNATSConn(t) defer func() { s.Shutdown(); s.WaitForShutdown() }() defer c.Close() var ( response = struct{ Body string }{"go eat a fly ugly\n"} wg sync.WaitGroup done = make(chan struct{}) ) handler := natstransport.NewSubscriber( endpoint.Nop, func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, nil }, func(_ context.Context, reply string, nc *nats.Conn, _ interface{}) error { b, err := json.Marshal(response) if err != nil { return err } return c.Publish(reply, b) }, natstransport.SubscriberFinalizer(func(ctx context.Context, _ *nats.Msg) { close(done) }), ) sub, err := c.QueueSubscribe("natstransport.test", "natstransport", handler.ServeMsg(c)) if err != nil { t.Fatal(err) } defer sub.Unsubscribe() wg.Add(1) go func() { defer wg.Done() _, err := c.Request("natstransport.test", []byte("test data"), 2*time.Second) if err != nil { t.Fatal(err) } }() select { case <-done: case <-time.After(time.Second): t.Fatal("timeout waiting for finalizer") } wg.Wait() } func TestEncodeJSONResponse(t *testing.T) { s, c := newNATSConn(t) defer func() { s.Shutdown(); s.WaitForShutdown() }() defer c.Close() handler := natstransport.NewSubscriber( func(context.Context, interface{}) (interface{}, error) { return struct { Foo string `json:"foo"` }{"bar"}, nil }, func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, nil }, natstransport.EncodeJSONResponse, ) sub, err := c.QueueSubscribe("natstransport.test", "natstransport", handler.ServeMsg(c)) if err != nil { t.Fatal(err) } defer sub.Unsubscribe() r, err := c.Request("natstransport.test", []byte("test data"), 2*time.Second) if err != nil { t.Fatal(err) } if want, have := `{"foo":"bar"}`, strings.TrimSpace(string(r.Data)); want != have { t.Errorf("Body: want %s, have %s", want, have) } } type responseError struct { msg string } func (m responseError) Error() string { return m.msg } func TestErrorEncoder(t *testing.T) { s, c := newNATSConn(t) defer func() { s.Shutdown(); s.WaitForShutdown() }() defer c.Close() errResp := struct { Error string `json:"err"` }{"oh no"} handler := natstransport.NewSubscriber( func(context.Context, interface{}) (interface{}, error) { return nil, responseError{msg: errResp.Error} }, func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, nil }, natstransport.EncodeJSONResponse, ) sub, err := c.QueueSubscribe("natstransport.test", "natstransport", handler.ServeMsg(c)) if err != nil { t.Fatal(err) } defer sub.Unsubscribe() r, err := c.Request("natstransport.test", []byte("test data"), 2*time.Second) if err != nil { t.Fatal(err) } b, err := json.Marshal(errResp) if err != nil { t.Fatal(err) } if string(b) != string(r.Data) { t.Errorf("ErrorEncoder: got: %q, expected: %q", r.Data, b) } } type noContentResponse struct{} func TestEncodeNoContent(t *testing.T) { s, c := newNATSConn(t) defer func() { s.Shutdown(); s.WaitForShutdown() }() defer c.Close() handler := natstransport.NewSubscriber( func(context.Context, interface{}) (interface{}, error) { return noContentResponse{}, nil }, func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, nil }, natstransport.EncodeJSONResponse, ) sub, err := c.QueueSubscribe("natstransport.test", "natstransport", handler.ServeMsg(c)) if err != nil { t.Fatal(err) } defer sub.Unsubscribe() r, err := c.Request("natstransport.test", []byte("test data"), 2*time.Second) if err != nil { t.Fatal(err) } if want, have := `{}`, strings.TrimSpace(string(r.Data)); want != have { t.Errorf("Body: want %s, have %s", want, have) } } func TestNoOpRequestDecoder(t *testing.T) { s, c := newNATSConn(t) defer func() { s.Shutdown(); s.WaitForShutdown() }() defer c.Close() handler := natstransport.NewSubscriber( func(ctx context.Context, request interface{}) (interface{}, error) { if request != nil { t.Error("Expected nil request in endpoint when using NopRequestDecoder") } return nil, nil }, natstransport.NopRequestDecoder, natstransport.EncodeJSONResponse, ) sub, err := c.QueueSubscribe("natstransport.test", "natstransport", handler.ServeMsg(c)) if err != nil { t.Fatal(err) } defer sub.Unsubscribe() r, err := c.Request("natstransport.test", []byte("test data"), 2*time.Second) if err != nil { t.Fatal(err) } if want, have := `null`, strings.TrimSpace(string(r.Data)); want != have { t.Errorf("Body: want %s, have %s", want, have) } } func testSubscriber(t *testing.T) (step func(), resp <-chan *nats.Msg) { var ( stepch = make(chan bool) endpoint = func(context.Context, interface{}) (interface{}, error) { <-stepch return struct{}{}, nil } response = make(chan *nats.Msg) handler = natstransport.NewSubscriber( endpoint, func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, nil }, natstransport.EncodeJSONResponse, natstransport.SubscriberBefore(func(ctx context.Context, msg *nats.Msg) context.Context { return ctx }), natstransport.SubscriberAfter(func(ctx context.Context, nc *nats.Conn) context.Context { return ctx }), ) ) go func() { s, c := newNATSConn(t) defer func() { s.Shutdown(); s.WaitForShutdown() }() defer c.Close() sub, err := c.QueueSubscribe("natstransport.test", "natstransport", handler.ServeMsg(c)) if err != nil { t.Fatal(err) } defer sub.Unsubscribe() r, err := c.Request("natstransport.test", []byte("test data"), 2*time.Second) if err != nil { t.Fatal(err) } response <- r }() return func() { stepch <- true }, response } func testRequest(t *testing.T, c *nats.Conn, handler *natstransport.Subscriber) TestResponse { sub, err := c.QueueSubscribe("natstransport.test", "natstransport", handler.ServeMsg(c)) if err != nil { t.Fatal(err) } defer sub.Unsubscribe() r, err := c.Request("natstransport.test", []byte("test data"), 2*time.Second) if err != nil { t.Fatal(err) } var resp TestResponse err = json.Unmarshal(r.Data, &resp) if err != nil { t.Fatal(err) } return resp } golang-github-go-kit-kit-0.13.0/transport/netrpc/000077500000000000000000000000001443521372500216345ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/transport/netrpc/README.md000066400000000000000000000015761443521372500231240ustar00rootroot00000000000000# net/rpc [net/rpc](https://golang.org/pkg/net/rpc) is an RPC transport that's part of the Go standard library. It's a simple and fast transport that's appropriate when all of your services are written in Go. Using net/rpc with Go kit is very simple. Just write a simple binding from your service definition to the net/rpc definition. See [netrpc_binding.go](https://github.com/go-kit/kit/blob/ec8b02591ee873433565a1ae9d317353412d1d27/examples/addsvc/netrpc_binding.go) for an example. That's it! The net/rpc binding can be registered to a name, and bound to an HTTP handler, the same as any other net/rpc endpoint. And within your service, you can use standard Go kit components and idioms. See [addsvc](https://github.com/go-kit/examples/tree/master/addsvc) for a complete working example with net/rpc support. And remember: Go kit services can support multiple transports simultaneously. golang-github-go-kit-kit-0.13.0/transport/thrift/000077500000000000000000000000001443521372500216415ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/transport/thrift/README.md000066400000000000000000000034541443521372500231260ustar00rootroot00000000000000# Thrift [Thrift](https://thrift.apache.org/) is a large IDL and transport package from Apache, popularized by Facebook. Thrift is well-supported in Go kit, for organizations that already have significant Thrift investment. And using Thrift with Go kit is very simple. First, define your service in the Thrift IDL. The [Thrift IDL documentation](https://thrift.apache.org/docs/idl) provides more details. See [addsvc.thrift](https://github.com/go-kit/examples/blob/master/addsvc/thrift/addsvc.thrift) for an example. Make sure the Thrift definition matches your service's Go kit (interface) definition. Next, [download Thrift](https://thrift.apache.org/download) and [install the compiler](https://thrift.apache.org/docs/install/). On a Mac, you may be able to `brew install thrift`. Then, compile your service definition, from .thrift to .go. You'll probably want to specify the package_prefix option to the --gen go flag. See [THRIFT-3021](https://issues.apache.org/jira/browse/THRIFT-3021) for more details. ``` thrift -r --gen go:package_prefix=github.com/my-org/my-repo/thrift/gen-go/ add.thrift ``` Finally, write a tiny binding from your service definition to the Thrift definition. It's a straightforward conversion from one domain to the other. See [thrift.go](https://github.com/go-kit/examples/blob/master/addsvc/pkg/addtransport/thrift.go) for an example. That's it! The Thrift binding can be bound to a listener and serve normal Thrift requests. And within your service, you can use standard Go kit components and idioms. Unfortunately, setting up a Thrift listener is rather laborious and nonidiomatic in Go. Fortunately, [addsvc](https://github.com/go-kit/examples/tree/master/addsvc) is a complete working example with Thrift support. And remember: Go kit services can support multiple transports simultaneously. golang-github-go-kit-kit-0.13.0/util/000077500000000000000000000000001443521372500172625ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/util/README.md000066400000000000000000000003101443521372500205330ustar00rootroot00000000000000# util This directory holds packages of general utility to multiple consumers within Go kit, and potentially other consumers in the wider Go ecosystem. There is no `package util` and will never be. golang-github-go-kit-kit-0.13.0/util/conn/000077500000000000000000000000001443521372500202175ustar00rootroot00000000000000golang-github-go-kit-kit-0.13.0/util/conn/doc.go000066400000000000000000000001101443521372500213030ustar00rootroot00000000000000// Package conn provides utilities related to connections. package conn golang-github-go-kit-kit-0.13.0/util/conn/manager.go000066400000000000000000000106411443521372500221620ustar00rootroot00000000000000package conn import ( "errors" "math/rand" "net" "time" "github.com/go-kit/log" ) // Dialer imitates net.Dial. Dialer is assumed to yield connections that are // safe for use by multiple concurrent goroutines. type Dialer func(network, address string) (net.Conn, error) // AfterFunc imitates time.After. type AfterFunc func(time.Duration) <-chan time.Time // Manager manages a net.Conn. // // Clients provide a way to create the connection with a Dialer, network, and // address. Clients should Take the connection when they want to use it, and Put // back whatever error they receive from its use. When a non-nil error is Put, // the connection is invalidated, and a new connection is established. // Connection failures are retried after an exponential backoff. type Manager struct { dialer Dialer network string address string after AfterFunc logger log.Logger takec chan net.Conn putc chan error } // NewManager returns a connection manager using the passed Dialer, network, and // address. The AfterFunc is used to control exponential backoff and retries. // The logger is used to log errors; pass a log.NopLogger if you don't care to // receive them. For normal use, prefer NewDefaultManager. func NewManager(d Dialer, network, address string, after AfterFunc, logger log.Logger) *Manager { m := &Manager{ dialer: d, network: network, address: address, after: after, logger: logger, takec: make(chan net.Conn), putc: make(chan error), } go m.loop() return m } // NewDefaultManager is a helper constructor, suitable for most normal use in // real (non-test) code. It uses the real net.Dial and time.After functions. func NewDefaultManager(network, address string, logger log.Logger) *Manager { return NewManager(net.Dial, network, address, time.After, logger) } // Take yields the current connection. It may be nil. func (m *Manager) Take() net.Conn { return <-m.takec } // Put accepts an error that came from a previously yielded connection. If the // error is non-nil, the manager will invalidate the current connection and try // to reconnect, with exponential backoff. Putting a nil error is a no-op. func (m *Manager) Put(err error) { m.putc <- err } // Write writes the passed data to the connection in a single Take/Put cycle. func (m *Manager) Write(b []byte) (int, error) { conn := m.Take() if conn == nil { return 0, ErrConnectionUnavailable } n, err := conn.Write(b) defer m.Put(err) return n, err } func (m *Manager) loop() { var ( conn = dial(m.dialer, m.network, m.address, m.logger) // may block slightly connc = make(chan net.Conn, 1) reconnectc <-chan time.Time // initially nil backoff = time.Second ) // If the initial dial fails, we need to trigger a reconnect via the loop // body, below. If we did this in a goroutine, we would race on the conn // variable. So we use a buffered chan instead. connc <- conn for { select { case <-reconnectc: reconnectc = nil // one-shot go func() { connc <- dial(m.dialer, m.network, m.address, m.logger) }() case conn = <-connc: if conn == nil { // didn't work backoff = Exponential(backoff) // wait longer reconnectc = m.after(backoff) // try again } else { // worked! backoff = time.Second // reset wait time reconnectc = nil // no retry necessary } case m.takec <- conn: case err := <-m.putc: if err != nil && conn != nil { m.logger.Log("err", err) conn.Close() conn = nil // connection is bad reconnectc = m.after(time.Nanosecond) // trigger immediately } } } } func dial(d Dialer, network, address string, logger log.Logger) net.Conn { conn, err := d(network, address) if err != nil { logger.Log("err", err) conn = nil // just to be sure } return conn } // Exponential takes a duration and returns another one that is twice as long, +/- 50%. It is // used to provide backoff for operations that may fail and should avoid thundering herds. // See https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ for rationale func Exponential(d time.Duration) time.Duration { d *= 2 jitter := rand.Float64() + 0.5 d = time.Duration(int64(float64(d.Nanoseconds()) * jitter)) if d > time.Minute { d = time.Minute } return d } // ErrConnectionUnavailable is returned by the Manager's Write method when the // manager cannot yield a good connection. var ErrConnectionUnavailable = errors.New("connection unavailable") golang-github-go-kit-kit-0.13.0/util/conn/manager_test.go000066400000000000000000000077651443521372500232360ustar00rootroot00000000000000package conn import ( "errors" "net" "sync/atomic" "testing" "time" "github.com/go-kit/log" ) func TestManager(t *testing.T) { var ( tickc = make(chan time.Time) after = func(time.Duration) <-chan time.Time { return tickc } dialconn = &mockConn{} dialerr = error(nil) dialer = func(string, string) (net.Conn, error) { return dialconn, dialerr } mgr = NewManager(dialer, "netw", "addr", after, log.NewNopLogger()) ) // First conn should be fine. conn := mgr.Take() if conn == nil { t.Fatal("nil conn") } // Write and check it went through. if _, err := conn.Write([]byte{1, 2, 3}); err != nil { t.Fatal(err) } if want, have := uint64(3), atomic.LoadUint64(&dialconn.wr); want != have { t.Errorf("want %d, have %d", want, have) } // Put an error to kill the conn. mgr.Put(errors.New("should kill the connection")) // First takes should fail. for i := 0; i < 10; i++ { if conn = mgr.Take(); conn != nil { t.Fatalf("iteration %d: want nil conn, got real conn", i) } } // Trigger the reconnect. tickc <- time.Now() // The dial should eventually succeed and yield a good conn. if !within(100*time.Millisecond, func() bool { conn = mgr.Take() return conn != nil }) { t.Fatal("conn remained nil") } // Write and check it went through. if _, err := conn.Write([]byte{4, 5}); err != nil { t.Fatal(err) } if want, have := uint64(5), atomic.LoadUint64(&dialconn.wr); want != have { t.Errorf("want %d, have %d", want, have) } // Dial starts failing. dialconn, dialerr = nil, errors.New("oh noes") mgr.Put(errors.New("trigger that reconnect y'all")) if conn = mgr.Take(); conn != nil { t.Fatalf("want nil conn, got real conn") } // As many reconnects as they want. go func() { done := time.After(100 * time.Millisecond) for { select { case tickc <- time.Now(): case <-done: return } } }() // The dial should never succeed. if within(100*time.Millisecond, func() bool { conn = mgr.Take() return conn != nil }) { t.Fatal("eventually got a good conn, despite failing dialer") } } func TestIssue292(t *testing.T) { // The util/conn.Manager won't attempt to reconnect to the provided endpoint // if the endpoint is initially unavailable (e.g. dial tcp :8080: // getsockopt: connection refused). If the endpoint is up when // conn.NewManager is called and then goes down/up, it reconnects just fine. var ( tickc = make(chan time.Time) after = func(time.Duration) <-chan time.Time { return tickc } dialconn = net.Conn(nil) dialerr = errors.New("fail") dialer = func(string, string) (net.Conn, error) { return dialconn, dialerr } mgr = NewManager(dialer, "netw", "addr", after, log.NewNopLogger()) ) if conn := mgr.Take(); conn != nil { t.Fatal("first Take should have yielded nil conn, but didn't") } dialconn, dialerr = &mockConn{}, nil select { case tickc <- time.Now(): case <-time.After(time.Second): t.Fatal("manager isn't listening for a tick, despite a failed dial") } if !within(time.Second, func() bool { return mgr.Take() != nil }) { t.Fatal("second Take should have yielded good conn, but didn't") } } type mockConn struct { rd, wr uint64 } func (c *mockConn) Read(b []byte) (n int, err error) { atomic.AddUint64(&c.rd, uint64(len(b))) return len(b), nil } func (c *mockConn) Write(b []byte) (n int, err error) { atomic.AddUint64(&c.wr, uint64(len(b))) return len(b), nil } func (c *mockConn) Close() error { return nil } func (c *mockConn) LocalAddr() net.Addr { return nil } func (c *mockConn) RemoteAddr() net.Addr { return nil } func (c *mockConn) SetDeadline(t time.Time) error { return nil } func (c *mockConn) SetReadDeadline(t time.Time) error { return nil } func (c *mockConn) SetWriteDeadline(t time.Time) error { return nil } func within(d time.Duration, f func() bool) bool { deadline := time.Now().Add(d) for { if time.Now().After(deadline) { return false } if f() { return true } time.Sleep(d / 10) } }