Codebase list golang-github-go-kit-kit / run/3b9658d9-4065-4804-9af8-16961c419f60/upstream
Import upstream version 0.12.0+git20220826.1.a7ba4fa Debian Janitor 1 year, 3 months ago
306 changed file(s) with 15115 addition(s) and 11444 deletion(s). Raw diff Collapse all Expand all
+0
-45
.gitignore less more
0 examples/addsvc/addsvc
1 examples/addsvc/client/client
2 examples/apigateway/apigateway
3 examples/profilesvc/profilesvc
4 examples/stringsvc1/stringsvc1
5 examples/stringsvc2/stringsvc2
6 examples/stringsvc3/stringsvc3
7 *.coverprofile
8
9 # Compiled Object files, Static and Dynamic libs (Shared Objects)
10 *.o
11 *.a
12 *.so
13
14 # Folders
15 _obj
16 _test
17 _old*
18
19 # Architecture specific extensions/prefixes
20 *.[568vq]
21 [568vq].out
22
23 *.cgo1.go
24 *.cgo2.c
25 _cgo_defun.c
26 _cgo_gotypes.go
27 _cgo_export.*
28
29 _testmain.go
30
31 *.exe
32
33 # https://github.com/github/gitignore/blob/master/Global/Vim.gitignore
34 # swap
35 [._]*.s[a-w][a-z]
36 [._]s[a-w][a-z]
37 # session
38 Session.vim
39 # temporary
40 .netrwhist
41 *~
42 # auto-generated tag files
43 tags
44
+0
-16
.travis.yml less more
0 language: go
1
2 env:
3 - COVERALLS_TOKEN=MYSkSqcsWXd6DmP6TnSeiDhtvuL4u6ndp
4
5 before_install:
6 - go get github.com/mattn/goveralls
7 - go get github.com/modocache/gover
8
9 script:
10 - go test -race -v ./...
11 - ./coveralls.bash
12
13 go:
14 - 1.9.x
15 - tip
0 # Go kit [![Circle CI](https://circleci.com/gh/go-kit/kit.svg?style=shield)](https://circleci.com/gh/go-kit/kit) [![Travis CI](https://travis-ci.org/go-kit/kit.svg?branch=master)](https://travis-ci.org/go-kit/kit) [![GoDoc](https://godoc.org/github.com/go-kit/kit?status.svg)](https://godoc.org/github.com/go-kit/kit) [![Coverage Status](https://coveralls.io/repos/go-kit/kit/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-kit/kit?branch=master) [![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)
0 # Go kit
11
2 **Go kit** is a **programming toolkit** for building microservices
3 (or elegant monoliths) in Go. We solve common problems in distributed
2 ![GitHub Workflow Status](https://github.com/go-kit/kit/workflows/CI/badge.svg)
3 [![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)
4 [![codecov](https://codecov.io/gh/go-kit/kit/branch/master/graph/badge.svg)](https://codecov.io/gh/go-kit/kit)
5 [![Go Report Card](https://goreportcard.com/badge/go-kit/kit)](https://goreportcard.com/report/go-kit/kit)
6 [![Sourcegraph](https://sourcegraph.com/github.com/go-kit/kit/-/badge.svg)](https://sourcegraph.com/github.com/go-kit/kit?badge)
7
8 **Go kit** is a **programming toolkit** for building microservices
9 (or elegant monoliths) in Go. We solve common problems in distributed
410 systems and application architecture so you can focus on delivering
511 business value.
612
713 - Website: [gokit.io](https://gokit.io)
814 - Mailing list: [go-kit](https://groups.google.com/forum/#!forum/go-kit)
915 - Slack: [gophers.slack.com](https://gophers.slack.com) **#go-kit** ([invite](https://gophersinvite.herokuapp.com/))
16
17 ## Sponsors
18
19 Click [here](https://github.com/sponsors/peterbourgon) or Sponsor, above, for more information on sponsorship.
1020
1121 ## Motivation
1222
4959
5060 ## Dependency management
5161
52 Go kit is a library, designed to be imported into a binary package. Vendoring
53 is currently the best way for binary package authors to ensure reliable,
54 reproducible builds. Therefore, we strongly recommend our users use vendoring
55 for all of their dependencies, including Go kit. To avoid compatibility and
56 availability issues, Go kit doesn't vendor its own dependencies, and
57 doesn't recommend use of third-party import proxies.
62 Go kit is [modules](https://github.com/golang/go/wiki/Modules) aware, and we
63 encourage users to use the standard modules tooling. But Go kit is at major
64 version 0, so it should be compatible with non-modules environments.
5865
59 There are several tools which make vendoring easier, including
60 [dep](https://github.com/golang/dep),
61 [gb](http://getgb.io),
62 [glide](https://github.com/Masterminds/glide),
63 [gvt](https://github.com/FiloSottile/gvt), and
64 [govendor](https://github.com/kardianos/govendor).
65 In addition, Go kit uses a variety of continuous integration providers
66 to find and fix compatibility problems as soon as they occur.
66 ## Code generators
67
68 There are several third-party tools that can generate Go kit code based on
69 different starting assumptions.
70
71 - [devimteam/microgen](https://github.com/devimteam/microgen)
72 - [GrantZheng/kit](https://github.com/GrantZheng/kit)
73 - [chaseSpace/kit](https://github.com/chaseSpace/kit)
74 - [kujtimiihoxha/kit](https://github.com/kujtimiihoxha/kit) (unmaintained)
75 - [nytimes/marvin](https://github.com/nytimes/marvin)
76 - [sagikazarmark/mga](https://github.com/sagikazarmark/mga)
77 - [sagikazarmark/protoc-gen-go-kit](https://github.com/sagikazarmark/protoc-gen-go-kit)
78 - [tuneinc/truss](https://github.com/tuneinc/truss)
6779
6880 ## Related projects
6981
7284 ### Service frameworks
7385
7486 - [gizmo](https://github.com/nytimes/gizmo), a microservice toolkit from The New York Times ★
75 - [go-micro](https://github.com/myodc/go-micro), a microservices client/server library ★
87 - [go-micro](https://github.com/micro/go-micro), a distributed systems development framework ★
7688 - [gotalk](https://github.com/rsms/gotalk), async peer communication protocol & library
7789 - [Kite](https://github.com/koding/kite), a micro-service framework
7890 - [gocircuit](https://github.com/gocircuit/circuit), dynamic cloud orchestration
90102 - [mattheath/phosphor](https://github.com/mondough/phosphor), distributed system tracing
91103 - [pivotal-golang/lager](https://github.com/pivotal-golang/lager), an opinionated logging library
92104 - [rubyist/circuitbreaker](https://github.com/rubyist/circuitbreaker), circuit breaker library
93 - [Sirupsen/logrus](https://github.com/Sirupsen/logrus), structured, pluggable logging for Go ★
105 - [sirupsen/logrus](https://github.com/sirupsen/logrus), structured, pluggable logging for Go ★
94106 - [sourcegraph/appdash](https://github.com/sourcegraph/appdash), application tracing system based on Google's Dapper
95107 - [spacemonkeygo/monitor](https://github.com/spacemonkeygo/monitor), data collection, monitoring, instrumentation, and Zipkin client library
96108 - [streadway/handy](https://github.com/streadway/handy), net/http handler filters
100112 ### Web frameworks
101113
102114 - [Gorilla](http://www.gorillatoolkit.org)
103 - [Gin](https://gin-gonic.github.io/gin/)
115 - [Gin](https://gin-gonic.com/)
104116 - [Negroni](https://github.com/codegangsta/negroni)
105117 - [Goji](https://github.com/zenazn/goji)
106118 - [Martini](https://github.com/go-martini/martini)
107 - [Beego](http://beego.me/)
119 - [Beego](https://beego.vip/)
108120 - [Revel](https://revel.github.io/) (considered [harmful](https://github.com/go-kit/kit/issues/350))
121 - [GoBuffalo](https://gobuffalo.io/)
109122
110123 ## Additional reading
111124
112 - [Architecting for the Cloud](http://fr.slideshare.net/stonse/architecting-for-the-cloud-using-netflixoss-codemash-workshop-29852233) — Netflix
125 - [Architecting for the Cloud](https://slideshare.net/stonse/architecting-for-the-cloud-using-netflixoss-codemash-workshop-29852233) — Netflix
113126 - [Dapper, a Large-Scale Distributed Systems Tracing Infrastructure](http://research.google.com/pubs/pub36356.html) — Google
114127 - [Your Server as a Function](http://monkey.org/~marius/funsrv.pdf) (PDF) — Twitter
115
116 ---
117
118 Development supported by [DigitalOcean](https://digitalocean.com).
+0
-17
ROADMAP.md less more
0 # Roadmap
1
2 This is a coarse-grained roadmap of Go kit development direction in the short
3 to mid-term future. It will be kept reasonably up-to-date by the project
4 maintainers. Suggest new ideas, enhancements, and features using the standard
5 [issues](https://github.com/go-kit/kit/issues) model.
6
7 ## Prioritized
8
9 1. kitgen code generation (#308, #70)
10 1. package pubsub (#298, #295)
11
12 ## Unprioritized
13
14 - package log/levels refactor (#250, #269, #252)
15 - package auth/jwt (#255)
16
0 package casbin
1
2 import (
3 "context"
4 "errors"
5
6 stdcasbin "github.com/casbin/casbin/v2"
7 "github.com/go-kit/kit/endpoint"
8 )
9
10 type contextKey string
11
12 const (
13 // CasbinModelContextKey holds the key to store the access control model
14 // in context, it can be a path to configuration file or a casbin/model
15 // Model.
16 CasbinModelContextKey contextKey = "CasbinModel"
17
18 // CasbinPolicyContextKey holds the key to store the access control policy
19 // in context, it can be a path to policy file or an implementation of
20 // casbin/persist Adapter interface.
21 CasbinPolicyContextKey contextKey = "CasbinPolicy"
22
23 // CasbinEnforcerContextKey holds the key to retrieve the active casbin
24 // Enforcer.
25 CasbinEnforcerContextKey contextKey = "CasbinEnforcer"
26 )
27
28 var (
29 // ErrModelContextMissing denotes a casbin model was not passed into
30 // the parsing of middleware's context.
31 ErrModelContextMissing = errors.New("CasbinModel is required in context")
32
33 // ErrPolicyContextMissing denotes a casbin policy was not passed into
34 // the parsing of middleware's context.
35 ErrPolicyContextMissing = errors.New("CasbinPolicy is required in context")
36
37 // ErrUnauthorized denotes the subject is not authorized to do the action
38 // intended on the given object, based on the context model and policy.
39 ErrUnauthorized = errors.New("Unauthorized Access")
40 )
41
42 // NewEnforcer checks whether the subject is authorized to do the specified
43 // action on the given object. If a valid access control model and policy
44 // is given, then the generated casbin Enforcer is stored in the context
45 // with CasbinEnforcer as the key.
46 func NewEnforcer(
47 subject string, object interface{}, action string,
48 ) endpoint.Middleware {
49 return func(next endpoint.Endpoint) endpoint.Endpoint {
50 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
51 casbinModel := ctx.Value(CasbinModelContextKey)
52 casbinPolicy := ctx.Value(CasbinPolicyContextKey)
53 enforcer, err := stdcasbin.NewEnforcer(casbinModel, casbinPolicy)
54 if err != nil {
55 return nil, err
56 }
57
58 ctx = context.WithValue(ctx, CasbinEnforcerContextKey, enforcer)
59 ok, err := enforcer.Enforce(subject, object, action)
60 if err != nil {
61 return nil, err
62 }
63 if !ok {
64 return nil, ErrUnauthorized
65 }
66
67 return next(ctx, request)
68 }
69 }
70 }
0 package casbin
1
2 import (
3 "context"
4 "testing"
5
6 stdcasbin "github.com/casbin/casbin/v2"
7 "github.com/casbin/casbin/v2/model"
8 fileadapter "github.com/casbin/casbin/v2/persist/file-adapter"
9 )
10
11 func TestStructBaseContext(t *testing.T) {
12 e := func(ctx context.Context, i interface{}) (interface{}, error) { return ctx, nil }
13
14 m := model.NewModel()
15 m.AddDef("r", "r", "sub, obj, act")
16 m.AddDef("p", "p", "sub, obj, act")
17 m.AddDef("e", "e", "some(where (p.eft == allow))")
18 m.AddDef("m", "m", "r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)")
19
20 a := fileadapter.NewAdapter("testdata/keymatch_policy.csv")
21
22 ctx := context.WithValue(context.Background(), CasbinModelContextKey, m)
23 ctx = context.WithValue(ctx, CasbinPolicyContextKey, a)
24
25 // positive case
26 middleware := NewEnforcer("alice", "/alice_data/resource1", "GET")(e)
27 ctx1, err := middleware(ctx, struct{}{})
28 if err != nil {
29 t.Fatalf("Enforcer returned error: %s", err)
30 }
31 _, ok := ctx1.(context.Context).Value(CasbinEnforcerContextKey).(*stdcasbin.Enforcer)
32 if !ok {
33 t.Fatalf("context should contains the active enforcer")
34 }
35
36 // negative case
37 middleware = NewEnforcer("alice", "/alice_data/resource2", "POST")(e)
38 _, err = middleware(ctx, struct{}{})
39 if err == nil {
40 t.Fatalf("Enforcer should return error")
41 }
42 }
43
44 func TestFileBaseContext(t *testing.T) {
45 e := func(ctx context.Context, i interface{}) (interface{}, error) { return ctx, nil }
46 ctx := context.WithValue(context.Background(), CasbinModelContextKey, "testdata/basic_model.conf")
47 ctx = context.WithValue(ctx, CasbinPolicyContextKey, "testdata/basic_policy.csv")
48
49 // positive case
50 middleware := NewEnforcer("alice", "data1", "read")(e)
51 _, err := middleware(ctx, struct{}{})
52 if err != nil {
53 t.Fatalf("Enforcer returned error: %s", err)
54 }
55 }
0 [request_definition]
1 r = sub, obj, act
2
3 [policy_definition]
4 p = sub, obj, act
5
6 [policy_effect]
7 e = some(where (p.eft == allow))
8
9 [matchers]
10 m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
0 p, alice, data1, read
1 p, bob, data2, write
0 p, alice, /alice_data/*, GET
1 p, alice, /alice_data/resource1, POST
2
3 p, bob, /alice_data/resource2, GET
4 p, bob, /bob_data/*, POST
5
6 p, cathy, /cathy_data, (GET)|(POST)
66
77 NewParser takes a key function and an expected signing method and returns an
88 `endpoint.Middleware`. The middleware will parse a token passed into the
9 context via the `jwt.JWTTokenContextKey`. If the token is valid, any claims
9 context via the `jwt.JWTContextKey`. If the token is valid, any claims
1010 will be added to the context via the `jwt.JWTClaimsContextKey`.
1111
1212 ```go
1313 import (
14 stdjwt "github.com/dgrijalva/jwt-go"
14 stdjwt "github.com/golang-jwt/jwt/v4"
1515
1616 "github.com/go-kit/kit/auth/jwt"
1717 "github.com/go-kit/kit/endpoint"
2929
3030 NewSigner takes a JWT key ID header, the signing key, signing method, and a
3131 claims object. It returns an `endpoint.Middleware`. The middleware will build
32 the token string and add it to the context via the `jwt.JWTTokenContextKey`.
32 the token string and add it to the context via the `jwt.JWTContextKey`.
3333
3434 ```go
3535 import (
36 stdjwt "github.com/dgrijalva/jwt-go"
36 stdjwt "github.com/golang-jwt/jwt/v4"
3737
3838 "github.com/go-kit/kit/auth/jwt"
3939 "github.com/go-kit/kit/endpoint"
5454 ```
5555
5656 In order for the parser and the signer to work, the authorization headers need
57 to be passed between the request and the context. `ToHTTPContext()`,
58 `FromHTTPContext()`, `ToGRPCContext()`, and `FromGRPCContext()` are given as
57 to be passed between the request and the context. `HTTPToContext()`,
58 `ContextToHTTP()`, `GRPCToContext()`, and `ContextToGRPC()` are given as
5959 helpers to do this. These functions implement the correlating transport's
6060 RequestFunc interface and can be passed as ClientBefore or ServerBefore
6161 options.
6464
6565 ```go
6666 import (
67 stdjwt "github.com/dgrijalva/jwt-go"
67 stdjwt "github.com/golang-jwt/jwt/v4"
6868
6969 grpctransport "github.com/go-kit/kit/transport/grpc"
7070 "github.com/go-kit/kit/auth/jwt"
7676 options := []httptransport.ClientOption{}
7777 var exampleEndpoint endpoint.Endpoint
7878 {
79 exampleEndpoint = grpctransport.NewClient(..., grpctransport.ClientBefore(jwt.FromGRPCContext())).Endpoint()
79 exampleEndpoint = grpctransport.NewClient(..., grpctransport.ClientBefore(jwt.ContextToGRPC())).Endpoint()
8080 exampleEndpoint = jwt.NewSigner(
8181 "kid-header",
8282 []byte("SigningString"),
9494 "context"
9595
9696 "github.com/go-kit/kit/auth/jwt"
97 "github.com/go-kit/kit/log"
97 "github.com/go-kit/log"
9898 grpctransport "github.com/go-kit/kit/transport/grpc"
9999 )
100100
107107 endpoints.CreateUserEndpoint,
108108 DecodeGRPCCreateUserRequest,
109109 EncodeGRPCCreateUserResponse,
110 append(options, grpctransport.ServerBefore(jwt.ToGRPCContext()))...,
110 append(options, grpctransport.ServerBefore(jwt.GRPCToContext()))...,
111111 ),
112112 getUser: grpctransport.NewServer(
113113 ctx,
33 "context"
44 "errors"
55
6 jwt "github.com/dgrijalva/jwt-go"
7
86 "github.com/go-kit/kit/endpoint"
7 "github.com/golang-jwt/jwt/v4"
98 )
109
1110 type contextKey string
1211
1312 const (
14 // JWTTokenContextKey holds the key used to store a JWT Token in the
15 // context.
16 JWTTokenContextKey contextKey = "JWTToken"
13 // JWTContextKey holds the key used to store a JWT in the context.
14 JWTContextKey contextKey = "JWTToken"
15
16 // JWTTokenContextKey is an alias for JWTContextKey.
17 //
18 // Deprecated: prefer JWTContextKey.
19 JWTTokenContextKey = JWTContextKey
1720
1821 // JWTClaimsContextKey holds the key used to store the JWT Claims in the
1922 // context.
2629 ErrTokenContextMissing = errors.New("token up for parsing was not passed through the context")
2730
2831 // ErrTokenInvalid denotes a token was not able to be validated.
29 ErrTokenInvalid = errors.New("JWT Token was invalid")
32 ErrTokenInvalid = errors.New("JWT was invalid")
3033
3134 // ErrTokenExpired denotes a token's expire header (exp) has since passed.
32 ErrTokenExpired = errors.New("JWT Token is expired")
35 ErrTokenExpired = errors.New("JWT is expired")
3336
34 // ErrTokenMalformed denotes a token was not formatted as a JWT token.
35 ErrTokenMalformed = errors.New("JWT Token is malformed")
37 // ErrTokenMalformed denotes a token was not formatted as a JWT.
38 ErrTokenMalformed = errors.New("JWT is malformed")
3639
3740 // ErrTokenNotActive denotes a token's not before header (nbf) is in the
3841 // future.
4346 ErrUnexpectedSigningMethod = errors.New("unexpected signing method")
4447 )
4548
46 // NewSigner creates a new JWT token generating middleware, specifying key ID,
49 // NewSigner creates a new JWT generating middleware, specifying key ID,
4750 // signing string, signing method and the claims you would like it to contain.
4851 // Tokens are signed with a Key ID header (kid) which is useful for determining
4952 // the key to use for parsing. Particularly useful for clients.
5861 if err != nil {
5962 return nil, err
6063 }
61 ctx = context.WithValue(ctx, JWTTokenContextKey, tokenString)
64 ctx = context.WithValue(ctx, JWTContextKey, tokenString)
6265
6366 return next(ctx, request)
6467 }
8184 return &jwt.StandardClaims{}
8285 }
8386
84 // NewParser creates a new JWT token parsing middleware, specifying a
87 // NewParser creates a new JWT parsing middleware, specifying a
8588 // jwt.Keyfunc interface, the signing method and the claims type to be used. NewParser
8689 // adds the resulting claims to endpoint context or returns error on invalid token.
8790 // Particularly useful for servers.
8992 return func(next endpoint.Endpoint) endpoint.Endpoint {
9093 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
9194 // tokenString is stored in the context from the transport handlers.
92 tokenString, ok := ctx.Value(JWTTokenContextKey).(string)
95 tokenString, ok := ctx.Value(JWTContextKey).(string)
9396 if !ok {
9497 return nil, ErrTokenContextMissing
9598 }
77
88 "crypto/subtle"
99
10 jwt "github.com/dgrijalva/jwt-go"
1110 "github.com/go-kit/kit/endpoint"
11 "github.com/golang-jwt/jwt/v4"
1212 )
1313
1414 type customClaims struct {
4343 t.Fatalf("Signer returned error: %s", err)
4444 }
4545
46 token, ok := ctx.(context.Context).Value(JWTTokenContextKey).(string)
46 token, ok := ctx.(context.Context).Value(JWTContextKey).(string)
4747 if !ok {
4848 t.Fatal("Token did not exist in context")
4949 }
5050
5151 if token != expectedKey {
52 t.Fatalf("JWT tokens did not match: expecting %s got %s", expectedKey, token)
52 t.Fatalf("JWTs did not match: expecting %s got %s", expectedKey, token)
5353 }
5454 }
5555
8686 }
8787
8888 // Invalid Token is passed into the parser
89 ctx := context.WithValue(context.Background(), JWTTokenContextKey, invalidKey)
89 ctx := context.WithValue(context.Background(), JWTContextKey, invalidKey)
9090 _, err = parser(ctx, struct{}{})
9191 if err == nil {
9292 t.Error("Parser should have returned an error")
9494
9595 // Invalid Method is used in the parser
9696 badParser := NewParser(keys, invalidMethod, MapClaimsFactory)(e)
97 ctx = context.WithValue(context.Background(), JWTTokenContextKey, signedKey)
97 ctx = context.WithValue(context.Background(), JWTContextKey, signedKey)
9898 _, err = badParser(ctx, struct{}{})
9999 if err == nil {
100100 t.Error("Parser should have returned an error")
110110 }
111111
112112 badParser = NewParser(invalidKeys, method, MapClaimsFactory)(e)
113 ctx = context.WithValue(context.Background(), JWTTokenContextKey, signedKey)
113 ctx = context.WithValue(context.Background(), JWTContextKey, signedKey)
114114 _, err = badParser(ctx, struct{}{})
115115 if err == nil {
116116 t.Error("Parser should have returned an error")
117117 }
118118
119119 // Correct token is passed into the parser
120 ctx = context.WithValue(context.Background(), JWTTokenContextKey, signedKey)
120 ctx = context.WithValue(context.Background(), JWTContextKey, signedKey)
121121 ctx1, err := parser(ctx, struct{}{})
122122 if err != nil {
123123 t.Fatalf("Parser returned error: %s", err)
134134
135135 // Test for malformed token error response
136136 parser = NewParser(keys, method, StandardClaimsFactory)(e)
137 ctx = context.WithValue(context.Background(), JWTTokenContextKey, malformedKey)
137 ctx = context.WithValue(context.Background(), JWTContextKey, malformedKey)
138138 ctx1, err = parser(ctx, struct{}{})
139139 if want, have := ErrTokenMalformed, err; want != have {
140140 t.Fatalf("Expected %+v, got %+v", want, have)
147147 if err != nil {
148148 t.Fatalf("Unable to Sign Token: %+v", err)
149149 }
150 ctx = context.WithValue(context.Background(), JWTTokenContextKey, token)
150 ctx = context.WithValue(context.Background(), JWTContextKey, token)
151151 ctx1, err = parser(ctx, struct{}{})
152152 if want, have := ErrTokenExpired, err; want != have {
153153 t.Fatalf("Expected %+v, got %+v", want, have)
160160 if err != nil {
161161 t.Fatalf("Unable to Sign Token: %+v", err)
162162 }
163 ctx = context.WithValue(context.Background(), JWTTokenContextKey, token)
163 ctx = context.WithValue(context.Background(), JWTContextKey, token)
164164 ctx1, err = parser(ctx, struct{}{})
165165 if want, have := ErrTokenNotActive, err; want != have {
166166 t.Fatalf("Expected %+v, got %+v", want, have)
168168
169169 // test valid standard claims token
170170 parser = NewParser(keys, method, StandardClaimsFactory)(e)
171 ctx = context.WithValue(context.Background(), JWTTokenContextKey, standardSignedKey)
171 ctx = context.WithValue(context.Background(), JWTContextKey, standardSignedKey)
172172 ctx1, err = parser(ctx, struct{}{})
173173 if err != nil {
174174 t.Fatalf("Parser returned error: %s", err)
183183
184184 // test valid customized claims token
185185 parser = NewParser(keys, method, func() jwt.Claims { return &customClaims{} })(e)
186 ctx = context.WithValue(context.Background(), JWTTokenContextKey, customSignedKey)
186 ctx = context.WithValue(context.Background(), JWTContextKey, customSignedKey)
187187 ctx1, err = parser(ctx, struct{}{})
188188 if err != nil {
189189 t.Fatalf("Parser returned error: %s", err)
204204 var (
205205 kf = func(token *jwt.Token) (interface{}, error) { return []byte("secret"), nil }
206206 e = NewParser(kf, jwt.SigningMethodHS256, MapClaimsFactory)(endpoint.Nop)
207 key = JWTTokenContextKey
207 key = JWTContextKey
208208 val = "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtpZCIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiZ28ta2l0In0.14M2VmYyApdSlV_LZ88ajjwuaLeIFplB8JpyNy0A19E"
209209 ctx = context.WithValue(context.Background(), key, val)
210210 )
2525 return ctx
2626 }
2727
28 return context.WithValue(ctx, JWTTokenContextKey, token)
28 return context.WithValue(ctx, JWTContextKey, token)
2929 }
3030 }
3131
3333 // useful for clients.
3434 func ContextToHTTP() http.RequestFunc {
3535 return func(ctx context.Context, r *stdhttp.Request) context.Context {
36 token, ok := ctx.Value(JWTTokenContextKey).(string)
36 token, ok := ctx.Value(JWTContextKey).(string)
3737 if ok {
3838 r.Header.Add("Authorization", generateAuthHeaderFromToken(token))
3939 }
5353
5454 token, ok := extractTokenFromAuthHeader(authHeader[0])
5555 if ok {
56 ctx = context.WithValue(ctx, JWTTokenContextKey, token)
56 ctx = context.WithValue(ctx, JWTContextKey, token)
5757 }
5858
5959 return ctx
6464 // useful for clients.
6565 func ContextToGRPC() grpc.ClientRequestFunc {
6666 return func(ctx context.Context, md *metadata.MD) context.Context {
67 token, ok := ctx.Value(JWTTokenContextKey).(string)
67 token, ok := ctx.Value(JWTContextKey).(string)
6868 if ok {
6969 // capital "Key" is illegal in HTTP/2.
7070 (*md)["authorization"] = []string{generateAuthHeaderFromToken(token)}
7676
7777 func extractTokenFromAuthHeader(val string) (token string, ok bool) {
7878 authHeaderParts := strings.Split(val, " ")
79 if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != bearer {
79 if len(authHeaderParts) != 2 || !strings.EqualFold(authHeaderParts[0], bearer) {
8080 return "", false
8181 }
8282
1414 // When the header doesn't exist
1515 ctx := reqFunc(context.Background(), &http.Request{})
1616
17 if ctx.Value(JWTTokenContextKey) != nil {
17 if ctx.Value(JWTContextKey) != nil {
1818 t.Error("Context shouldn't contain the encoded JWT")
1919 }
2020
2323 header.Set("Authorization", "no expected auth header format value")
2424 ctx = reqFunc(context.Background(), &http.Request{Header: header})
2525
26 if ctx.Value(JWTTokenContextKey) != nil {
26 if ctx.Value(JWTContextKey) != nil {
2727 t.Error("Context shouldn't contain the encoded JWT")
2828 }
2929
3131 header.Set("Authorization", generateAuthHeaderFromToken(signedKey))
3232 ctx = reqFunc(context.Background(), &http.Request{Header: header})
3333
34 token := ctx.Value(JWTTokenContextKey).(string)
34 token := ctx.Value(JWTContextKey).(string)
3535 if token != signedKey {
3636 t.Errorf("Context doesn't contain the expected encoded token value; expected: %s, got: %s", signedKey, token)
3737 }
4040 func TestContextToHTTP(t *testing.T) {
4141 reqFunc := ContextToHTTP()
4242
43 // No JWT Token is passed in the context
43 // No JWT is passed in the context
4444 ctx := context.Background()
4545 r := http.Request{}
4646 reqFunc(ctx, &r)
5050 t.Error("authorization key should not exist in metadata")
5151 }
5252
53 // Correct JWT Token is passed in the context
54 ctx = context.WithValue(context.Background(), JWTTokenContextKey, signedKey)
53 // Correct JWT is passed in the context
54 ctx = context.WithValue(context.Background(), JWTContextKey, signedKey)
5555 r = http.Request{Header: http.Header{}}
5656 reqFunc(ctx, &r)
5757
5959 expected := generateAuthHeaderFromToken(signedKey)
6060
6161 if token != expected {
62 t.Errorf("Authorization header does not contain the expected JWT token; expected %s, got %s", expected, token)
62 t.Errorf("Authorization header does not contain the expected JWT; expected %s, got %s", expected, token)
6363 }
6464 }
6565
6969
7070 // No Authorization header is passed
7171 ctx := reqFunc(context.Background(), md)
72 token := ctx.Value(JWTTokenContextKey)
72 token := ctx.Value(JWTContextKey)
7373 if token != nil {
74 t.Error("Context should not contain a JWT Token")
74 t.Error("Context should not contain a JWT")
7575 }
7676
7777 // Invalid Authorization header is passed
78 md["authorization"] = []string{fmt.Sprintf("%s", signedKey)}
78 md["authorization"] = []string{signedKey}
7979 ctx = reqFunc(context.Background(), md)
80 token = ctx.Value(JWTTokenContextKey)
80 token = ctx.Value(JWTContextKey)
8181 if token != nil {
82 t.Error("Context should not contain a JWT Token")
82 t.Error("Context should not contain a JWT")
8383 }
8484
8585 // Authorization header is correct
8686 md["authorization"] = []string{fmt.Sprintf("Bearer %s", signedKey)}
8787 ctx = reqFunc(context.Background(), md)
88 token, ok := ctx.Value(JWTTokenContextKey).(string)
88 token, ok := ctx.Value(JWTContextKey).(string)
8989 if !ok {
90 t.Fatal("JWT Token not passed to context correctly")
90 t.Fatal("JWT not passed to context correctly")
9191 }
9292
9393 if token != signedKey {
94 t.Errorf("JWT tokens did not match: expecting %s got %s", signedKey, token)
94 t.Errorf("JWTs did not match: expecting %s got %s", signedKey, token)
9595 }
9696 }
9797
9898 func TestContextToGRPC(t *testing.T) {
9999 reqFunc := ContextToGRPC()
100100
101 // No JWT Token is passed in the context
101 // No JWT is passed in the context
102102 ctx := context.Background()
103103 md := metadata.MD{}
104104 reqFunc(ctx, &md)
108108 t.Error("authorization key should not exist in metadata")
109109 }
110110
111 // Correct JWT Token is passed in the context
112 ctx = context.WithValue(context.Background(), JWTTokenContextKey, signedKey)
111 // Correct JWT is passed in the context
112 ctx = context.WithValue(context.Background(), JWTContextKey, signedKey)
113113 md = metadata.MD{}
114114 reqFunc(ctx, &md)
115115
116116 token, ok := md["authorization"]
117117 if !ok {
118 t.Fatal("JWT Token not passed to metadata correctly")
118 t.Fatal("JWT not passed to metadata correctly")
119119 }
120120
121121 if token[0] != generateAuthHeaderFromToken(signedKey) {
122 t.Errorf("JWT tokens did not match: expecting %s got %s", signedKey, token[0])
122 t.Errorf("JWTs did not match: expecting %s got %s", signedKey, token[0])
123123 }
124124 }
+0
-27
circle.yml less more
0 machine:
1 pre:
2 - curl -sSL https://s3.amazonaws.com/circle-downloads/install-circleci-docker.sh | bash -s -- 1.10.0
3 - sudo rm -rf /usr/local/go
4 - curl -sSL https://storage.googleapis.com/golang/go1.9.linux-amd64.tar.gz | sudo tar xz -C /usr/local
5 services:
6 - docker
7
8 dependencies:
9 pre:
10 - sudo curl -L "https://github.com/docker/compose/releases/download/1.10.0/docker-compose-linux-x86_64" -o /usr/local/bin/docker-compose
11 - sudo chmod +x /usr/local/bin/docker-compose
12 - docker-compose -f docker-compose-integration.yml up -d --force-recreate
13
14 test:
15 pre:
16 - mkdir -p /home/ubuntu/.go_workspace/src/github.com/go-kit
17 - mv /home/ubuntu/kit /home/ubuntu/.go_workspace/src/github.com/go-kit
18 - ln -s /home/ubuntu/.go_workspace/src/github.com/go-kit/kit /home/ubuntu/kit
19 - go get -t github.com/go-kit/kit/...
20 override:
21 - go test -v -race -tags integration github.com/go-kit/kit/...:
22 environment:
23 ETCD_ADDR: http://localhost:2379
24 CONSUL_ADDR: localhost:8500
25 ZK_ADDR: localhost:2181
26 EUREKA_ADDR: http://localhost:8761/eureka
0 comment: false
+0
-31
coveralls.bash less more
0 #!/usr/bin/env bash
1
2 if ! type -P gover
3 then
4 echo gover missing: go get github.com/modocache/gover
5 exit 1
6 fi
7
8 if ! type -P goveralls
9 then
10 echo goveralls missing: go get github.com/mattn/goveralls
11 exit 1
12 fi
13
14 if [[ "$COVERALLS_TOKEN" == "" ]]
15 then
16 echo COVERALLS_TOKEN not set
17 exit 1
18 fi
19
20 go list ./... | grep -v '/examples/' | cut -d'/' -f 4- | while read d
21 do
22 cd $d
23 go test -covermode count -coverprofile coverage.coverprofile
24 cd -
25 done
26
27 gover
28 goveralls -coverprofile gover.coverprofile -service travis-ci -repotoken $COVERALLS_TOKEN
29 find . -name '*.coverprofile' -delete
30
00 version: '2'
11 services:
22 etcd:
3 image: quay.io/coreos/etcd
3 image: gcr.io/etcd-development/etcd:v3.5.0
44 ports:
55 - "2379:2379"
6 command: /usr/local/bin/etcd -advertise-client-urls http://0.0.0.0:2379,http://0.0.0.0:4001 -listen-client-urls "http://0.0.0.0:2379,http://0.0.0.0:4001"
6 environment:
7 ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
8 ETCD_ADVERTISE_CLIENT_URLS: http://0.0.0.0:2379
9
710 consul:
8 image: progrium/consul
11 image: consul:1.7
912 ports:
1013 - "8500:8500"
11 command: -server -bootstrap
14
1215 zk:
13 image: zookeeper
16 image: zookeeper:3.5
1417 ports:
1518 - "2181:2181"
19
1620 eureka:
1721 image: springcloud/eureka
1822 environment:
2525 return outer(next)
2626 }
2727 }
28
29 // Failer may be implemented by Go kit response types that contain business
30 // logic error details. If Failed returns a non-nil error, the Go kit transport
31 // layer may interpret this as a business logic error, and may encode it
32 // differently than a regular, successful response.
33 //
34 // It's not necessary for your response types to implement Failer, but it may
35 // help for more sophisticated use cases. The addsvc example shows how Failer
36 // should be used by a complete application.
37 type Failer interface {
38 Failed() error
39 }
00 # Examples
11
2 For more information about these examples,
3 including a walkthrough of the stringsvc example,
4 see [gokit.io/examples](https://gokit.io/examples).
2 Examples have been relocated to a separate repository: https://github.com/go-kit/examples
+0
-17
examples/addsvc/README.md less more
0 # addsvc
1
2 addsvc is an example microservice which takes full advantage of most of Go
3 kit's features, including both service- and transport-level middlewares,
4 speaking multiple transports simultaneously, distributed tracing, and rich
5 error definitions. The server binary is available in cmd/addsvc. The client
6 binary is available in cmd/addcli.
7
8 Finally, the addtransport package provides both server and clients for each
9 supported transport. The client structs bake-in certain middlewares, in order to
10 demonstrate the _client library pattern_. But beware: client libraries are
11 generally a bad idea, because they easily lead to the
12 [distributed monolith antipattern](https://www.microservices.com/talks/dont-build-a-distributed-monolith/).
13 If you don't _know_ you need to use one in your organization, it's probably best
14 avoided: prefer moving that logic to consumers, and relying on
15 [contract testing](https://docs.pact.io/best_practices/contract_tests_not_functional_tests.html)
16 to detect incompatibilities.
+0
-198
examples/addsvc/cmd/addcli/addcli.go less more
0 package main
1
2 import (
3 "context"
4 "flag"
5 "fmt"
6 "os"
7 "strconv"
8 "text/tabwriter"
9 "time"
10
11 "google.golang.org/grpc"
12
13 "github.com/apache/thrift/lib/go/thrift"
14 lightstep "github.com/lightstep/lightstep-tracer-go"
15 stdopentracing "github.com/opentracing/opentracing-go"
16 zipkin "github.com/openzipkin/zipkin-go-opentracing"
17 "sourcegraph.com/sourcegraph/appdash"
18 appdashot "sourcegraph.com/sourcegraph/appdash/opentracing"
19
20 "github.com/go-kit/kit/log"
21
22 "github.com/go-kit/kit/examples/addsvc/pkg/addservice"
23 "github.com/go-kit/kit/examples/addsvc/pkg/addtransport"
24 addthrift "github.com/go-kit/kit/examples/addsvc/thrift/gen-go/addsvc"
25 )
26
27 func main() {
28 // The addcli presumes no service discovery system, and expects users to
29 // provide the direct address of an addsvc. This presumption is reflected in
30 // the addcli binary and the client packages: the -transport.addr flags
31 // and various client constructors both expect host:port strings. For an
32 // example service with a client built on top of a service discovery system,
33 // see profilesvc.
34 fs := flag.NewFlagSet("addcli", flag.ExitOnError)
35 var (
36 httpAddr = fs.String("http-addr", "", "HTTP address of addsvc")
37 grpcAddr = fs.String("grpc-addr", "", "gRPC address of addsvc")
38 thriftAddr = fs.String("thrift-addr", "", "Thrift address of addsvc")
39 thriftProtocol = fs.String("thrift-protocol", "binary", "binary, compact, json, simplejson")
40 thriftBuffer = fs.Int("thrift-buffer", 0, "0 for unbuffered")
41 thriftFramed = fs.Bool("thrift-framed", false, "true to enable framing")
42 zipkinURL = fs.String("zipkin-url", "", "Enable Zipkin tracing via a collector URL e.g. http://localhost:9411/api/v1/spans")
43 lightstepToken = flag.String("lightstep-token", "", "Enable LightStep tracing via a LightStep access token")
44 appdashAddr = flag.String("appdash-addr", "", "Enable Appdash tracing via an Appdash server host:port")
45 method = fs.String("method", "sum", "sum, concat")
46 )
47 fs.Usage = usageFor(fs, os.Args[0]+" [flags] <a> <b>")
48 fs.Parse(os.Args[1:])
49 if len(fs.Args()) != 2 {
50 fs.Usage()
51 os.Exit(1)
52 }
53
54 // This is a demonstration client, which supports multiple tracers.
55 // Your clients will probably just use one tracer.
56 var tracer stdopentracing.Tracer
57 {
58 if *zipkinURL != "" {
59 collector, err := zipkin.NewHTTPCollector(*zipkinURL)
60 if err != nil {
61 fmt.Fprintln(os.Stderr, err.Error())
62 os.Exit(1)
63 }
64 defer collector.Close()
65 var (
66 debug = false
67 hostPort = "localhost:80"
68 serviceName = "addsvc"
69 )
70 recorder := zipkin.NewRecorder(collector, debug, hostPort, serviceName)
71 tracer, err = zipkin.NewTracer(recorder)
72 if err != nil {
73 fmt.Fprintln(os.Stderr, err.Error())
74 os.Exit(1)
75 }
76 } else if *lightstepToken != "" {
77 tracer = lightstep.NewTracer(lightstep.Options{
78 AccessToken: *lightstepToken,
79 })
80 defer lightstep.FlushLightStepTracer(tracer)
81 } else if *appdashAddr != "" {
82 tracer = appdashot.NewTracer(appdash.NewRemoteCollector(*appdashAddr))
83 } else {
84 tracer = stdopentracing.GlobalTracer() // no-op
85 }
86 }
87
88 // This is a demonstration client, which supports multiple transports.
89 // Your clients will probably just define and stick with 1 transport.
90 var (
91 svc addservice.Service
92 err error
93 )
94 if *httpAddr != "" {
95 svc, err = addtransport.NewHTTPClient(*httpAddr, tracer, log.NewNopLogger())
96 } else if *grpcAddr != "" {
97 conn, err := grpc.Dial(*grpcAddr, grpc.WithInsecure(), grpc.WithTimeout(time.Second))
98 if err != nil {
99 fmt.Fprintf(os.Stderr, "error: %v", err)
100 os.Exit(1)
101 }
102 defer conn.Close()
103 svc = addtransport.NewGRPCClient(conn, tracer, log.NewNopLogger())
104 } else if *thriftAddr != "" {
105 // It's necessary to do all of this construction in the func main,
106 // because (among other reasons) we need to control the lifecycle of the
107 // Thrift transport, i.e. close it eventually.
108 var protocolFactory thrift.TProtocolFactory
109 switch *thriftProtocol {
110 case "compact":
111 protocolFactory = thrift.NewTCompactProtocolFactory()
112 case "simplejson":
113 protocolFactory = thrift.NewTSimpleJSONProtocolFactory()
114 case "json":
115 protocolFactory = thrift.NewTJSONProtocolFactory()
116 case "binary", "":
117 protocolFactory = thrift.NewTBinaryProtocolFactoryDefault()
118 default:
119 fmt.Fprintf(os.Stderr, "error: invalid protocol %q\n", *thriftProtocol)
120 os.Exit(1)
121 }
122 var transportFactory thrift.TTransportFactory
123 if *thriftBuffer > 0 {
124 transportFactory = thrift.NewTBufferedTransportFactory(*thriftBuffer)
125 } else {
126 transportFactory = thrift.NewTTransportFactory()
127 }
128 if *thriftFramed {
129 transportFactory = thrift.NewTFramedTransportFactory(transportFactory)
130 }
131 transportSocket, err := thrift.NewTSocket(*thriftAddr)
132 if err != nil {
133 fmt.Fprintf(os.Stderr, "error: %v\n", err)
134 os.Exit(1)
135 }
136 transport, err := transportFactory.GetTransport(transportSocket)
137 if err != nil {
138 fmt.Fprintf(os.Stderr, "error: %v\n", err)
139 os.Exit(1)
140 }
141 if err := transport.Open(); err != nil {
142 fmt.Fprintf(os.Stderr, "error: %v\n", err)
143 os.Exit(1)
144 }
145 defer transport.Close()
146 client := addthrift.NewAddServiceClientFactory(transport, protocolFactory)
147 svc = addtransport.NewThriftClient(client)
148 } else {
149 fmt.Fprintf(os.Stderr, "error: no remote address specified\n")
150 os.Exit(1)
151 }
152 if err != nil {
153 fmt.Fprintf(os.Stderr, "error: %v\n", err)
154 os.Exit(1)
155 }
156
157 switch *method {
158 case "sum":
159 a, _ := strconv.ParseInt(fs.Args()[0], 10, 64)
160 b, _ := strconv.ParseInt(fs.Args()[1], 10, 64)
161 v, err := svc.Sum(context.Background(), int(a), int(b))
162 if err != nil {
163 fmt.Fprintf(os.Stderr, "error: %v\n", err)
164 os.Exit(1)
165 }
166 fmt.Fprintf(os.Stdout, "%d + %d = %d\n", a, b, v)
167
168 case "concat":
169 a := fs.Args()[0]
170 b := fs.Args()[1]
171 v, err := svc.Concat(context.Background(), a, b)
172 if err != nil {
173 fmt.Fprintf(os.Stderr, "error: %v\n", err)
174 os.Exit(1)
175 }
176 fmt.Fprintf(os.Stdout, "%q + %q = %q\n", a, b, v)
177
178 default:
179 fmt.Fprintf(os.Stderr, "error: invalid method %q\n", method)
180 os.Exit(1)
181 }
182 }
183
184 func usageFor(fs *flag.FlagSet, short string) func() {
185 return func() {
186 fmt.Fprintf(os.Stderr, "USAGE\n")
187 fmt.Fprintf(os.Stderr, " %s\n", short)
188 fmt.Fprintf(os.Stderr, "\n")
189 fmt.Fprintf(os.Stderr, "FLAGS\n")
190 w := tabwriter.NewWriter(os.Stderr, 0, 2, 2, ' ', 0)
191 fs.VisitAll(func(f *flag.Flag) {
192 fmt.Fprintf(w, "\t-%s %s\t%s\n", f.Name, f.DefValue, f.Usage)
193 })
194 w.Flush()
195 fmt.Fprintf(os.Stderr, "\n")
196 }
197 }
+0
-279
examples/addsvc/cmd/addsvc/addsvc.go less more
0 package main
1
2 import (
3 "flag"
4 "fmt"
5 "net"
6 "net/http"
7 "os"
8 "os/signal"
9 "syscall"
10 "text/tabwriter"
11
12 "github.com/apache/thrift/lib/go/thrift"
13 lightstep "github.com/lightstep/lightstep-tracer-go"
14 "github.com/oklog/oklog/pkg/group"
15 stdopentracing "github.com/opentracing/opentracing-go"
16 zipkin "github.com/openzipkin/zipkin-go-opentracing"
17 stdprometheus "github.com/prometheus/client_golang/prometheus"
18 "github.com/prometheus/client_golang/prometheus/promhttp"
19 "google.golang.org/grpc"
20 "sourcegraph.com/sourcegraph/appdash"
21 appdashot "sourcegraph.com/sourcegraph/appdash/opentracing"
22
23 "github.com/go-kit/kit/log"
24 "github.com/go-kit/kit/metrics"
25 "github.com/go-kit/kit/metrics/prometheus"
26
27 addpb "github.com/go-kit/kit/examples/addsvc/pb"
28 "github.com/go-kit/kit/examples/addsvc/pkg/addendpoint"
29 "github.com/go-kit/kit/examples/addsvc/pkg/addservice"
30 "github.com/go-kit/kit/examples/addsvc/pkg/addtransport"
31 addthrift "github.com/go-kit/kit/examples/addsvc/thrift/gen-go/addsvc"
32 )
33
34 func main() {
35 // Define our flags. Your service probably won't need to bind listeners for
36 // *all* supported transports, or support both Zipkin and LightStep, and so
37 // on, but we do it here for demonstration purposes.
38 fs := flag.NewFlagSet("addsvc", flag.ExitOnError)
39 var (
40 debugAddr = fs.String("debug.addr", ":8080", "Debug and metrics listen address")
41 httpAddr = fs.String("http-addr", ":8081", "HTTP listen address")
42 grpcAddr = fs.String("grpc-addr", ":8082", "gRPC listen address")
43 thriftAddr = fs.String("thrift-addr", ":8083", "Thrift listen address")
44 thriftProtocol = fs.String("thrift-protocol", "binary", "binary, compact, json, simplejson")
45 thriftBuffer = fs.Int("thrift-buffer", 0, "0 for unbuffered")
46 thriftFramed = fs.Bool("thrift-framed", false, "true to enable framing")
47 zipkinURL = fs.String("zipkin-url", "", "Enable Zipkin tracing via a collector URL e.g. http://localhost:9411/api/v1/spans")
48 lightstepToken = flag.String("lightstep-token", "", "Enable LightStep tracing via a LightStep access token")
49 appdashAddr = flag.String("appdash-addr", "", "Enable Appdash tracing via an Appdash server host:port")
50 )
51 fs.Usage = usageFor(fs, os.Args[0]+" [flags]")
52 fs.Parse(os.Args[1:])
53
54 // Create a single logger, which we'll use and give to other components.
55 var logger log.Logger
56 {
57 logger = log.NewLogfmtLogger(os.Stderr)
58 logger = log.With(logger, "ts", log.DefaultTimestampUTC)
59 logger = log.With(logger, "caller", log.DefaultCaller)
60 }
61
62 // Determine which tracer to use. We'll pass the tracer to all the
63 // components that use it, as a dependency.
64 var tracer stdopentracing.Tracer
65 {
66 if *zipkinURL != "" {
67 logger.Log("tracer", "Zipkin", "URL", *zipkinURL)
68 collector, err := zipkin.NewHTTPCollector(*zipkinURL)
69 if err != nil {
70 logger.Log("err", err)
71 os.Exit(1)
72 }
73 defer collector.Close()
74 var (
75 debug = false
76 hostPort = "localhost:80"
77 serviceName = "addsvc"
78 )
79 recorder := zipkin.NewRecorder(collector, debug, hostPort, serviceName)
80 tracer, err = zipkin.NewTracer(recorder)
81 if err != nil {
82 logger.Log("err", err)
83 os.Exit(1)
84 }
85 } else if *lightstepToken != "" {
86 logger.Log("tracer", "LightStep") // probably don't want to print out the token :)
87 tracer = lightstep.NewTracer(lightstep.Options{
88 AccessToken: *lightstepToken,
89 })
90 defer lightstep.FlushLightStepTracer(tracer)
91 } else if *appdashAddr != "" {
92 logger.Log("tracer", "Appdash", "addr", *appdashAddr)
93 tracer = appdashot.NewTracer(appdash.NewRemoteCollector(*appdashAddr))
94 } else {
95 logger.Log("tracer", "none")
96 tracer = stdopentracing.GlobalTracer() // no-op
97 }
98 }
99
100 // Create the (sparse) metrics we'll use in the service. They, too, are
101 // dependencies that we pass to components that use them.
102 var ints, chars metrics.Counter
103 {
104 // Business-level metrics.
105 ints = prometheus.NewCounterFrom(stdprometheus.CounterOpts{
106 Namespace: "example",
107 Subsystem: "addsvc",
108 Name: "integers_summed",
109 Help: "Total count of integers summed via the Sum method.",
110 }, []string{})
111 chars = prometheus.NewCounterFrom(stdprometheus.CounterOpts{
112 Namespace: "example",
113 Subsystem: "addsvc",
114 Name: "characters_concatenated",
115 Help: "Total count of characters concatenated via the Concat method.",
116 }, []string{})
117 }
118 var duration metrics.Histogram
119 {
120 // Endpoint-level metrics.
121 duration = prometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
122 Namespace: "example",
123 Subsystem: "addsvc",
124 Name: "request_duration_seconds",
125 Help: "Request duration in seconds.",
126 }, []string{"method", "success"})
127 }
128 http.DefaultServeMux.Handle("/metrics", promhttp.Handler())
129
130 // Build the layers of the service "onion" from the inside out. First, the
131 // business logic service; then, the set of endpoints that wrap the service;
132 // and finally, a series of concrete transport adapters. The adapters, like
133 // the HTTP handler or the gRPC server, are the bridge between Go kit and
134 // the interfaces that the transports expect. Note that we're not binding
135 // them to ports or anything yet; we'll do that next.
136 var (
137 service = addservice.New(logger, ints, chars)
138 endpoints = addendpoint.New(service, logger, duration, tracer)
139 httpHandler = addtransport.NewHTTPHandler(endpoints, tracer, logger)
140 grpcServer = addtransport.NewGRPCServer(endpoints, tracer, logger)
141 thriftServer = addtransport.NewThriftServer(endpoints)
142 )
143
144 // Now we're to the part of the func main where we want to start actually
145 // running things, like servers bound to listeners to receive connections.
146 //
147 // The method is the same for each component: add a new actor to the group
148 // struct, which is a combination of 2 anonymous functions: the first
149 // function actually runs the component, and the second function should
150 // interrupt the first function and cause it to return. It's in these
151 // functions that we actually bind the Go kit server/handler structs to the
152 // concrete transports and run them.
153 //
154 // Putting each component into its own block is mostly for aesthetics: it
155 // clearly demarcates the scope in which each listener/socket may be used.
156 var g group.Group
157 {
158 // The debug listener mounts the http.DefaultServeMux, and serves up
159 // stuff like the Prometheus metrics route, the Go debug and profiling
160 // routes, and so on.
161 debugListener, err := net.Listen("tcp", *debugAddr)
162 if err != nil {
163 logger.Log("transport", "debug/HTTP", "during", "Listen", "err", err)
164 os.Exit(1)
165 }
166 g.Add(func() error {
167 logger.Log("transport", "debug/HTTP", "addr", *debugAddr)
168 return http.Serve(debugListener, http.DefaultServeMux)
169 }, func(error) {
170 debugListener.Close()
171 })
172 }
173 {
174 // The HTTP listener mounts the Go kit HTTP handler we created.
175 httpListener, err := net.Listen("tcp", *httpAddr)
176 if err != nil {
177 logger.Log("transport", "HTTP", "during", "Listen", "err", err)
178 os.Exit(1)
179 }
180 g.Add(func() error {
181 logger.Log("transport", "HTTP", "addr", *httpAddr)
182 return http.Serve(httpListener, httpHandler)
183 }, func(error) {
184 httpListener.Close()
185 })
186 }
187 {
188 // The gRPC listener mounts the Go kit gRPC server we created.
189 grpcListener, err := net.Listen("tcp", *grpcAddr)
190 if err != nil {
191 logger.Log("transport", "gRPC", "during", "Listen", "err", err)
192 os.Exit(1)
193 }
194 g.Add(func() error {
195 logger.Log("transport", "gRPC", "addr", *grpcAddr)
196 baseServer := grpc.NewServer()
197 addpb.RegisterAddServer(baseServer, grpcServer)
198 return baseServer.Serve(grpcListener)
199 }, func(error) {
200 grpcListener.Close()
201 })
202 }
203 {
204 // The Thrift socket mounts the Go kit Thrift server we created earlier.
205 // There's a lot of boilerplate involved here, related to configuring
206 // the protocol and transport; blame Thrift.
207 thriftSocket, err := thrift.NewTServerSocket(*thriftAddr)
208 if err != nil {
209 logger.Log("transport", "Thrift", "during", "Listen", "err", err)
210 os.Exit(1)
211 }
212 g.Add(func() error {
213 logger.Log("transport", "Thrift", "addr", *thriftAddr)
214 var protocolFactory thrift.TProtocolFactory
215 switch *thriftProtocol {
216 case "binary":
217 protocolFactory = thrift.NewTBinaryProtocolFactoryDefault()
218 case "compact":
219 protocolFactory = thrift.NewTCompactProtocolFactory()
220 case "json":
221 protocolFactory = thrift.NewTJSONProtocolFactory()
222 case "simplejson":
223 protocolFactory = thrift.NewTSimpleJSONProtocolFactory()
224 default:
225 return fmt.Errorf("invalid Thrift protocol %q", *thriftProtocol)
226 }
227 var transportFactory thrift.TTransportFactory
228 if *thriftBuffer > 0 {
229 transportFactory = thrift.NewTBufferedTransportFactory(*thriftBuffer)
230 } else {
231 transportFactory = thrift.NewTTransportFactory()
232 }
233 if *thriftFramed {
234 transportFactory = thrift.NewTFramedTransportFactory(transportFactory)
235 }
236 return thrift.NewTSimpleServer4(
237 addthrift.NewAddServiceProcessor(thriftServer),
238 thriftSocket,
239 transportFactory,
240 protocolFactory,
241 ).Serve()
242 }, func(error) {
243 thriftSocket.Close()
244 })
245 }
246 {
247 // This function just sits and waits for ctrl-C.
248 cancelInterrupt := make(chan struct{})
249 g.Add(func() error {
250 c := make(chan os.Signal, 1)
251 signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
252 select {
253 case sig := <-c:
254 return fmt.Errorf("received signal %s", sig)
255 case <-cancelInterrupt:
256 return nil
257 }
258 }, func(error) {
259 close(cancelInterrupt)
260 })
261 }
262 logger.Log("exit", g.Run())
263 }
264
265 func usageFor(fs *flag.FlagSet, short string) func() {
266 return func() {
267 fmt.Fprintf(os.Stderr, "USAGE\n")
268 fmt.Fprintf(os.Stderr, " %s\n", short)
269 fmt.Fprintf(os.Stderr, "\n")
270 fmt.Fprintf(os.Stderr, "FLAGS\n")
271 w := tabwriter.NewWriter(os.Stderr, 0, 2, 2, ' ', 0)
272 fs.VisitAll(func(f *flag.Flag) {
273 fmt.Fprintf(w, "\t-%s %s\t%s\n", f.Name, f.DefValue, f.Usage)
274 })
275 w.Flush()
276 fmt.Fprintf(os.Stderr, "\n")
277 }
278 }
+0
-55
examples/addsvc/cmd/addsvc/pact_test.go less more
0 package main
1
2 import (
3 "fmt"
4 "net/http"
5 "os"
6 "strings"
7 "testing"
8
9 "github.com/pact-foundation/pact-go/dsl"
10 )
11
12 func TestPactStringsvcUppercase(t *testing.T) {
13 if os.Getenv("WRITE_PACTS") == "" {
14 t.Skip("skipping Pact contracts; set WRITE_PACTS environment variable to enable")
15 }
16
17 pact := dsl.Pact{
18 Port: 6666,
19 Consumer: "addsvc",
20 Provider: "stringsvc",
21 }
22 defer pact.Teardown()
23
24 pact.AddInteraction().
25 UponReceiving("stringsvc uppercase").
26 WithRequest(dsl.Request{
27 Headers: map[string]string{"Content-Type": "application/json; charset=utf-8"},
28 Method: "POST",
29 Path: "/uppercase",
30 Body: `{"s":"foo"}`,
31 }).
32 WillRespondWith(dsl.Response{
33 Status: 200,
34 Headers: map[string]string{"Content-Type": "application/json; charset=utf-8"},
35 Body: `{"v":"FOO"}`,
36 })
37
38 if err := pact.Verify(func() error {
39 u := fmt.Sprintf("http://localhost:%d/uppercase", pact.Server.Port)
40 req, err := http.NewRequest("POST", u, strings.NewReader(`{"s":"foo"}`))
41 if err != nil {
42 return err
43 }
44 req.Header.Set("Content-Type", "application/json; charset=utf-8")
45 if _, err = http.DefaultClient.Do(req); err != nil {
46 return err
47 }
48 return nil
49 }); err != nil {
50 t.Fatal(err)
51 }
52
53 pact.WritePact()
54 }
+0
-40
examples/addsvc/cmd/addsvc/wiring_test.go less more
0 package main
1
2 import (
3 "io/ioutil"
4 "net/http"
5 "net/http/httptest"
6 "strings"
7 "testing"
8
9 "github.com/opentracing/opentracing-go"
10
11 "github.com/go-kit/kit/log"
12 "github.com/go-kit/kit/metrics/discard"
13
14 "github.com/go-kit/kit/examples/addsvc/pkg/addendpoint"
15 "github.com/go-kit/kit/examples/addsvc/pkg/addservice"
16 "github.com/go-kit/kit/examples/addsvc/pkg/addtransport"
17 )
18
19 func TestHTTP(t *testing.T) {
20 svc := addservice.New(log.NewNopLogger(), discard.NewCounter(), discard.NewCounter())
21 eps := addendpoint.New(svc, log.NewNopLogger(), discard.NewHistogram(), opentracing.GlobalTracer())
22 mux := addtransport.NewHTTPHandler(eps, opentracing.GlobalTracer(), log.NewNopLogger())
23 srv := httptest.NewServer(mux)
24 defer srv.Close()
25
26 for _, testcase := range []struct {
27 method, url, body, want string
28 }{
29 {"GET", srv.URL + "/concat", `{"a":"1","b":"2"}`, `{"v":"12"}`},
30 {"GET", srv.URL + "/sum", `{"a":1,"b":2}`, `{"v":3}`},
31 } {
32 req, _ := http.NewRequest(testcase.method, testcase.url, strings.NewReader(testcase.body))
33 resp, _ := http.DefaultClient.Do(req)
34 body, _ := ioutil.ReadAll(resp.Body)
35 if want, have := testcase.want, strings.TrimSpace(string(body)); want != have {
36 t.Errorf("%s %s %s: want %q, have %q", testcase.method, testcase.url, testcase.body, want, have)
37 }
38 }
39 }
+0
-270
examples/addsvc/pb/addsvc.pb.go less more
0 // Code generated by protoc-gen-go. DO NOT EDIT.
1 // source: addsvc.proto
2
3 /*
4 Package pb is a generated protocol buffer package.
5
6 It is generated from these files:
7 addsvc.proto
8
9 It has these top-level messages:
10 SumRequest
11 SumReply
12 ConcatRequest
13 ConcatReply
14 */
15 package pb
16
17 import proto "github.com/golang/protobuf/proto"
18 import fmt "fmt"
19 import math "math"
20
21 import (
22 context "golang.org/x/net/context"
23 grpc "google.golang.org/grpc"
24 )
25
26 // Reference imports to suppress errors if they are not otherwise used.
27 var _ = proto.Marshal
28 var _ = fmt.Errorf
29 var _ = math.Inf
30
31 // This is a compile-time assertion to ensure that this generated file
32 // is compatible with the proto package it is being compiled against.
33 // A compilation error at this line likely means your copy of the
34 // proto package needs to be updated.
35 const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
36
37 // The sum request contains two parameters.
38 type SumRequest struct {
39 A int64 `protobuf:"varint,1,opt,name=a" json:"a,omitempty"`
40 B int64 `protobuf:"varint,2,opt,name=b" json:"b,omitempty"`
41 }
42
43 func (m *SumRequest) Reset() { *m = SumRequest{} }
44 func (m *SumRequest) String() string { return proto.CompactTextString(m) }
45 func (*SumRequest) ProtoMessage() {}
46 func (*SumRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
47
48 func (m *SumRequest) GetA() int64 {
49 if m != nil {
50 return m.A
51 }
52 return 0
53 }
54
55 func (m *SumRequest) GetB() int64 {
56 if m != nil {
57 return m.B
58 }
59 return 0
60 }
61
62 // The sum response contains the result of the calculation.
63 type SumReply struct {
64 V int64 `protobuf:"varint,1,opt,name=v" json:"v,omitempty"`
65 Err string `protobuf:"bytes,2,opt,name=err" json:"err,omitempty"`
66 }
67
68 func (m *SumReply) Reset() { *m = SumReply{} }
69 func (m *SumReply) String() string { return proto.CompactTextString(m) }
70 func (*SumReply) ProtoMessage() {}
71 func (*SumReply) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
72
73 func (m *SumReply) GetV() int64 {
74 if m != nil {
75 return m.V
76 }
77 return 0
78 }
79
80 func (m *SumReply) GetErr() string {
81 if m != nil {
82 return m.Err
83 }
84 return ""
85 }
86
87 // The Concat request contains two parameters.
88 type ConcatRequest struct {
89 A string `protobuf:"bytes,1,opt,name=a" json:"a,omitempty"`
90 B string `protobuf:"bytes,2,opt,name=b" json:"b,omitempty"`
91 }
92
93 func (m *ConcatRequest) Reset() { *m = ConcatRequest{} }
94 func (m *ConcatRequest) String() string { return proto.CompactTextString(m) }
95 func (*ConcatRequest) ProtoMessage() {}
96 func (*ConcatRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
97
98 func (m *ConcatRequest) GetA() string {
99 if m != nil {
100 return m.A
101 }
102 return ""
103 }
104
105 func (m *ConcatRequest) GetB() string {
106 if m != nil {
107 return m.B
108 }
109 return ""
110 }
111
112 // The Concat response contains the result of the concatenation.
113 type ConcatReply struct {
114 V string `protobuf:"bytes,1,opt,name=v" json:"v,omitempty"`
115 Err string `protobuf:"bytes,2,opt,name=err" json:"err,omitempty"`
116 }
117
118 func (m *ConcatReply) Reset() { *m = ConcatReply{} }
119 func (m *ConcatReply) String() string { return proto.CompactTextString(m) }
120 func (*ConcatReply) ProtoMessage() {}
121 func (*ConcatReply) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
122
123 func (m *ConcatReply) GetV() string {
124 if m != nil {
125 return m.V
126 }
127 return ""
128 }
129
130 func (m *ConcatReply) GetErr() string {
131 if m != nil {
132 return m.Err
133 }
134 return ""
135 }
136
137 func init() {
138 proto.RegisterType((*SumRequest)(nil), "pb.SumRequest")
139 proto.RegisterType((*SumReply)(nil), "pb.SumReply")
140 proto.RegisterType((*ConcatRequest)(nil), "pb.ConcatRequest")
141 proto.RegisterType((*ConcatReply)(nil), "pb.ConcatReply")
142 }
143
144 // Reference imports to suppress errors if they are not otherwise used.
145 var _ context.Context
146 var _ grpc.ClientConn
147
148 // This is a compile-time assertion to ensure that this generated file
149 // is compatible with the grpc package it is being compiled against.
150 const _ = grpc.SupportPackageIsVersion4
151
152 // Client API for Add service
153
154 type AddClient interface {
155 // Sums two integers.
156 Sum(ctx context.Context, in *SumRequest, opts ...grpc.CallOption) (*SumReply, error)
157 // Concatenates two strings
158 Concat(ctx context.Context, in *ConcatRequest, opts ...grpc.CallOption) (*ConcatReply, error)
159 }
160
161 type addClient struct {
162 cc *grpc.ClientConn
163 }
164
165 func NewAddClient(cc *grpc.ClientConn) AddClient {
166 return &addClient{cc}
167 }
168
169 func (c *addClient) Sum(ctx context.Context, in *SumRequest, opts ...grpc.CallOption) (*SumReply, error) {
170 out := new(SumReply)
171 err := grpc.Invoke(ctx, "/pb.Add/Sum", in, out, c.cc, opts...)
172 if err != nil {
173 return nil, err
174 }
175 return out, nil
176 }
177
178 func (c *addClient) Concat(ctx context.Context, in *ConcatRequest, opts ...grpc.CallOption) (*ConcatReply, error) {
179 out := new(ConcatReply)
180 err := grpc.Invoke(ctx, "/pb.Add/Concat", in, out, c.cc, opts...)
181 if err != nil {
182 return nil, err
183 }
184 return out, nil
185 }
186
187 // Server API for Add service
188
189 type AddServer interface {
190 // Sums two integers.
191 Sum(context.Context, *SumRequest) (*SumReply, error)
192 // Concatenates two strings
193 Concat(context.Context, *ConcatRequest) (*ConcatReply, error)
194 }
195
196 func RegisterAddServer(s *grpc.Server, srv AddServer) {
197 s.RegisterService(&_Add_serviceDesc, srv)
198 }
199
200 func _Add_Sum_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
201 in := new(SumRequest)
202 if err := dec(in); err != nil {
203 return nil, err
204 }
205 if interceptor == nil {
206 return srv.(AddServer).Sum(ctx, in)
207 }
208 info := &grpc.UnaryServerInfo{
209 Server: srv,
210 FullMethod: "/pb.Add/Sum",
211 }
212 handler := func(ctx context.Context, req interface{}) (interface{}, error) {
213 return srv.(AddServer).Sum(ctx, req.(*SumRequest))
214 }
215 return interceptor(ctx, in, info, handler)
216 }
217
218 func _Add_Concat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
219 in := new(ConcatRequest)
220 if err := dec(in); err != nil {
221 return nil, err
222 }
223 if interceptor == nil {
224 return srv.(AddServer).Concat(ctx, in)
225 }
226 info := &grpc.UnaryServerInfo{
227 Server: srv,
228 FullMethod: "/pb.Add/Concat",
229 }
230 handler := func(ctx context.Context, req interface{}) (interface{}, error) {
231 return srv.(AddServer).Concat(ctx, req.(*ConcatRequest))
232 }
233 return interceptor(ctx, in, info, handler)
234 }
235
236 var _Add_serviceDesc = grpc.ServiceDesc{
237 ServiceName: "pb.Add",
238 HandlerType: (*AddServer)(nil),
239 Methods: []grpc.MethodDesc{
240 {
241 MethodName: "Sum",
242 Handler: _Add_Sum_Handler,
243 },
244 {
245 MethodName: "Concat",
246 Handler: _Add_Concat_Handler,
247 },
248 },
249 Streams: []grpc.StreamDesc{},
250 Metadata: "addsvc.proto",
251 }
252
253 func init() { proto.RegisterFile("addsvc.proto", fileDescriptor0) }
254
255 var fileDescriptor0 = []byte{
256 // 189 bytes of a gzipped FileDescriptorProto
257 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x4c, 0x49, 0x29,
258 0x2e, 0x4b, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2a, 0x48, 0x52, 0xd2, 0xe0, 0xe2,
259 0x0a, 0x2e, 0xcd, 0x0d, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0xe2, 0xe1, 0x62, 0x4c, 0x94,
260 0x60, 0x54, 0x60, 0xd4, 0x60, 0x0e, 0x62, 0x4c, 0x04, 0xf1, 0x92, 0x24, 0x98, 0x20, 0xbc, 0x24,
261 0x25, 0x2d, 0x2e, 0x0e, 0xb0, 0xca, 0x82, 0x9c, 0x4a, 0x90, 0x4c, 0x19, 0x4c, 0x5d, 0x99, 0x90,
262 0x00, 0x17, 0x73, 0x6a, 0x51, 0x11, 0x58, 0x25, 0x67, 0x10, 0x88, 0xa9, 0xa4, 0xcd, 0xc5, 0xeb,
263 0x9c, 0x9f, 0x97, 0x9c, 0x58, 0x82, 0x61, 0x30, 0x27, 0x8a, 0xc1, 0x9c, 0x20, 0x83, 0x75, 0xb9,
264 0xb8, 0x61, 0x8a, 0x51, 0xcc, 0xe6, 0xc4, 0x6a, 0xb6, 0x51, 0x0c, 0x17, 0xb3, 0x63, 0x4a, 0x8a,
265 0x90, 0x2a, 0x17, 0x73, 0x70, 0x69, 0xae, 0x10, 0x9f, 0x5e, 0x41, 0x92, 0x1e, 0xc2, 0x07, 0x52,
266 0x3c, 0x70, 0x7e, 0x41, 0x4e, 0xa5, 0x12, 0x83, 0x90, 0x1e, 0x17, 0x1b, 0xc4, 0x70, 0x21, 0x41,
267 0x90, 0x0c, 0x8a, 0xab, 0xa4, 0xf8, 0x91, 0x85, 0xc0, 0xea, 0x93, 0xd8, 0xc0, 0x41, 0x63, 0x0c,
268 0x08, 0x00, 0x00, 0xff, 0xff, 0xdc, 0x37, 0x81, 0x99, 0x2a, 0x01, 0x00, 0x00,
269 }
+0
-36
examples/addsvc/pb/addsvc.proto less more
0 syntax = "proto3";
1
2 package pb;
3
4 // The Add service definition.
5 service Add {
6 // Sums two integers.
7 rpc Sum (SumRequest) returns (SumReply) {}
8
9 // Concatenates two strings
10 rpc Concat (ConcatRequest) returns (ConcatReply) {}
11 }
12
13 // The sum request contains two parameters.
14 message SumRequest {
15 int64 a = 1;
16 int64 b = 2;
17 }
18
19 // The sum response contains the result of the calculation.
20 message SumReply {
21 int64 v = 1;
22 string err = 2;
23 }
24
25 // The Concat request contains two parameters.
26 message ConcatRequest {
27 string a = 1;
28 string b = 2;
29 }
30
31 // The Concat response contains the result of the concatenation.
32 message ConcatReply {
33 string v = 1;
34 string err = 2;
35 }
+0
-14
examples/addsvc/pb/compile.sh less more
0 #!/usr/bin/env sh
1
2 # Install proto3 from source
3 # brew install autoconf automake libtool
4 # git clone https://github.com/google/protobuf
5 # ./autogen.sh ; ./configure ; make ; make install
6 #
7 # Update protoc Go bindings via
8 # go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
9 #
10 # See also
11 # https://github.com/grpc/grpc-go/tree/master/examples
12
13 protoc addsvc.proto --go_out=plugins=grpc:.
+0
-43
examples/addsvc/pkg/addendpoint/middleware.go less more
0 package addendpoint
1
2 import (
3 "context"
4 "fmt"
5 "time"
6
7 "github.com/go-kit/kit/endpoint"
8 "github.com/go-kit/kit/log"
9 "github.com/go-kit/kit/metrics"
10 )
11
12 // InstrumentingMiddleware returns an endpoint middleware that records
13 // the duration of each invocation to the passed histogram. The middleware adds
14 // a single field: "success", which is "true" if no error is returned, and
15 // "false" otherwise.
16 func InstrumentingMiddleware(duration metrics.Histogram) endpoint.Middleware {
17 return func(next endpoint.Endpoint) endpoint.Endpoint {
18 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
19
20 defer func(begin time.Time) {
21 duration.With("success", fmt.Sprint(err == nil)).Observe(time.Since(begin).Seconds())
22 }(time.Now())
23 return next(ctx, request)
24
25 }
26 }
27 }
28
29 // LoggingMiddleware returns an endpoint middleware that logs the
30 // duration of each invocation, and the resulting error, if any.
31 func LoggingMiddleware(logger log.Logger) endpoint.Middleware {
32 return func(next endpoint.Endpoint) endpoint.Endpoint {
33 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
34
35 defer func(begin time.Time) {
36 logger.Log("transport_error", err, "took", time.Since(begin))
37 }(time.Now())
38 return next(ctx, request)
39
40 }
41 }
42 }
+0
-128
examples/addsvc/pkg/addendpoint/set.go less more
0 package addendpoint
1
2 import (
3 "context"
4
5 rl "github.com/juju/ratelimit"
6 stdopentracing "github.com/opentracing/opentracing-go"
7 "github.com/sony/gobreaker"
8
9 "github.com/go-kit/kit/circuitbreaker"
10 "github.com/go-kit/kit/endpoint"
11 "github.com/go-kit/kit/log"
12 "github.com/go-kit/kit/metrics"
13 "github.com/go-kit/kit/ratelimit"
14 "github.com/go-kit/kit/tracing/opentracing"
15
16 "github.com/go-kit/kit/examples/addsvc/pkg/addservice"
17 )
18
19 // Set collects all of the endpoints that compose an add service. It's meant to
20 // be used as a helper struct, to collect all of the endpoints into a single
21 // parameter.
22 type Set struct {
23 SumEndpoint endpoint.Endpoint
24 ConcatEndpoint endpoint.Endpoint
25 }
26
27 // New returns a Set that wraps the provided server, and wires in all of the
28 // expected endpoint middlewares via the various parameters.
29 func New(svc addservice.Service, logger log.Logger, duration metrics.Histogram, trace stdopentracing.Tracer) Set {
30 var sumEndpoint endpoint.Endpoint
31 {
32 sumEndpoint = MakeSumEndpoint(svc)
33 sumEndpoint = ratelimit.NewTokenBucketLimiter(rl.NewBucketWithRate(1, 1))(sumEndpoint)
34 sumEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{}))(sumEndpoint)
35 sumEndpoint = opentracing.TraceServer(trace, "Sum")(sumEndpoint)
36 sumEndpoint = LoggingMiddleware(log.With(logger, "method", "Sum"))(sumEndpoint)
37 sumEndpoint = InstrumentingMiddleware(duration.With("method", "Sum"))(sumEndpoint)
38 }
39 var concatEndpoint endpoint.Endpoint
40 {
41 concatEndpoint = MakeConcatEndpoint(svc)
42 concatEndpoint = ratelimit.NewTokenBucketLimiter(rl.NewBucketWithRate(100, 100))(concatEndpoint)
43 concatEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{}))(concatEndpoint)
44 concatEndpoint = opentracing.TraceServer(trace, "Concat")(concatEndpoint)
45 concatEndpoint = LoggingMiddleware(log.With(logger, "method", "Concat"))(concatEndpoint)
46 concatEndpoint = InstrumentingMiddleware(duration.With("method", "Concat"))(concatEndpoint)
47 }
48 return Set{
49 SumEndpoint: sumEndpoint,
50 ConcatEndpoint: concatEndpoint,
51 }
52 }
53
54 // Sum implements the service interface, so Set may be used as a service.
55 // This is primarily useful in the context of a client library.
56 func (s Set) Sum(ctx context.Context, a, b int) (int, error) {
57 resp, err := s.SumEndpoint(ctx, SumRequest{A: a, B: b})
58 if err != nil {
59 return 0, err
60 }
61 response := resp.(SumResponse)
62 return response.V, response.Err
63 }
64
65 // Concat implements the service interface, so Set may be used as a
66 // service. This is primarily useful in the context of a client library.
67 func (s Set) Concat(ctx context.Context, a, b string) (string, error) {
68 resp, err := s.ConcatEndpoint(ctx, ConcatRequest{A: a, B: b})
69 if err != nil {
70 return "", err
71 }
72 response := resp.(ConcatResponse)
73 return response.V, response.Err
74 }
75
76 // MakeSumEndpoint constructs a Sum endpoint wrapping the service.
77 func MakeSumEndpoint(s addservice.Service) endpoint.Endpoint {
78 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
79 req := request.(SumRequest)
80 v, err := s.Sum(ctx, req.A, req.B)
81 return SumResponse{V: v, Err: err}, nil
82 }
83 }
84
85 // MakeConcatEndpoint constructs a Concat endpoint wrapping the service.
86 func MakeConcatEndpoint(s addservice.Service) endpoint.Endpoint {
87 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
88 req := request.(ConcatRequest)
89 v, err := s.Concat(ctx, req.A, req.B)
90 return ConcatResponse{V: v, Err: err}, nil
91 }
92 }
93
94 // Failer is an interface that should be implemented by response types.
95 // Response encoders can check if responses are Failer, and if so if they've
96 // failed, and if so encode them using a separate write path based on the error.
97 type Failer interface {
98 Failed() error
99 }
100
101 // SumRequest collects the request parameters for the Sum method.
102 type SumRequest struct {
103 A, B int
104 }
105
106 // SumResponse collects the response values for the Sum method.
107 type SumResponse struct {
108 V int `json:"v"`
109 Err error `json:"-"` // should be intercepted by Failed/errorEncoder
110 }
111
112 // Failed implements Failer.
113 func (r SumResponse) Failed() error { return r.Err }
114
115 // ConcatRequest collects the request parameters for the Concat method.
116 type ConcatRequest struct {
117 A, B string
118 }
119
120 // ConcatResponse collects the response values for the Concat method.
121 type ConcatResponse struct {
122 V string `json:"v"`
123 Err error `json:"-"`
124 }
125
126 // Failed implements Failer.
127 func (r ConcatResponse) Failed() error { return r.Err }
+0
-69
examples/addsvc/pkg/addservice/middleware.go less more
0 package addservice
1
2 import (
3 "context"
4
5 "github.com/go-kit/kit/log"
6 "github.com/go-kit/kit/metrics"
7 )
8
9 // Middleware describes a service (as opposed to endpoint) middleware.
10 type Middleware func(Service) Service
11
12 // LoggingMiddleware takes a logger as a dependency
13 // and returns a ServiceMiddleware.
14 func LoggingMiddleware(logger log.Logger) Middleware {
15 return func(next Service) Service {
16 return loggingMiddleware{logger, next}
17 }
18 }
19
20 type loggingMiddleware struct {
21 logger log.Logger
22 next Service
23 }
24
25 func (mw loggingMiddleware) Sum(ctx context.Context, a, b int) (v int, err error) {
26 defer func() {
27 mw.logger.Log("method", "Sum", "a", a, "b", b, "v", v, "err", err)
28 }()
29 return mw.next.Sum(ctx, a, b)
30 }
31
32 func (mw loggingMiddleware) Concat(ctx context.Context, a, b string) (v string, err error) {
33 defer func() {
34 mw.logger.Log("method", "Concat", "a", a, "b", b, "v", v, "err", err)
35 }()
36 return mw.next.Concat(ctx, a, b)
37 }
38
39 // InstrumentingMiddleware returns a service middleware that instruments
40 // the number of integers summed and characters concatenated over the lifetime of
41 // the service.
42 func InstrumentingMiddleware(ints, chars metrics.Counter) Middleware {
43 return func(next Service) Service {
44 return instrumentingMiddleware{
45 ints: ints,
46 chars: chars,
47 next: next,
48 }
49 }
50 }
51
52 type instrumentingMiddleware struct {
53 ints metrics.Counter
54 chars metrics.Counter
55 next Service
56 }
57
58 func (mw instrumentingMiddleware) Sum(ctx context.Context, a, b int) (int, error) {
59 v, err := mw.next.Sum(ctx, a, b)
60 mw.ints.Add(float64(v))
61 return v, err
62 }
63
64 func (mw instrumentingMiddleware) Concat(ctx context.Context, a, b string) (string, error) {
65 v, err := mw.next.Concat(ctx, a, b)
66 mw.chars.Add(float64(len(v)))
67 return v, err
68 }
+0
-71
examples/addsvc/pkg/addservice/service.go less more
0 package addservice
1
2 import (
3 "context"
4 "errors"
5
6 "github.com/go-kit/kit/log"
7 "github.com/go-kit/kit/metrics"
8 )
9
10 // Service describes a service that adds things together.
11 type Service interface {
12 Sum(ctx context.Context, a, b int) (int, error)
13 Concat(ctx context.Context, a, b string) (string, error)
14 }
15
16 // New returns a basic Service with all of the expected middlewares wired in.
17 func New(logger log.Logger, ints, chars metrics.Counter) Service {
18 var svc Service
19 {
20 svc = NewBasicService()
21 svc = LoggingMiddleware(logger)(svc)
22 svc = InstrumentingMiddleware(ints, chars)(svc)
23 }
24 return svc
25 }
26
27 var (
28 // ErrTwoZeroes is an arbitrary business rule for the Add method.
29 ErrTwoZeroes = errors.New("can't sum two zeroes")
30
31 // ErrIntOverflow protects the Add method. We've decided that this error
32 // indicates a misbehaving service and should count against e.g. circuit
33 // breakers. So, we return it directly in endpoints, to illustrate the
34 // difference. In a real service, this probably wouldn't be the case.
35 ErrIntOverflow = errors.New("integer overflow")
36
37 // ErrMaxSizeExceeded protects the Concat method.
38 ErrMaxSizeExceeded = errors.New("result exceeds maximum size")
39 )
40
41 // NewBasicService returns a naïve, stateless implementation of Service.
42 func NewBasicService() Service {
43 return basicService{}
44 }
45
46 type basicService struct{}
47
48 const (
49 intMax = 1<<31 - 1
50 intMin = -(intMax + 1)
51 maxLen = 10
52 )
53
54 func (s basicService) Sum(_ context.Context, a, b int) (int, error) {
55 if a == 0 && b == 0 {
56 return 0, ErrTwoZeroes
57 }
58 if (b > 0 && a > (intMax-b)) || (b < 0 && a < (intMin-b)) {
59 return 0, ErrIntOverflow
60 }
61 return a + b, nil
62 }
63
64 // Concat implements Service.
65 func (s basicService) Concat(_ context.Context, a, b string) (string, error) {
66 if len(a)+len(b) > maxLen {
67 return "", ErrMaxSizeExceeded
68 }
69 return a + b, nil
70 }
+0
-210
examples/addsvc/pkg/addtransport/grpc.go less more
0 package addtransport
1
2 import (
3 "context"
4 "errors"
5 "time"
6
7 "google.golang.org/grpc"
8
9 jujuratelimit "github.com/juju/ratelimit"
10 stdopentracing "github.com/opentracing/opentracing-go"
11 "github.com/sony/gobreaker"
12 oldcontext "golang.org/x/net/context"
13
14 "github.com/go-kit/kit/circuitbreaker"
15 "github.com/go-kit/kit/endpoint"
16 "github.com/go-kit/kit/log"
17 "github.com/go-kit/kit/ratelimit"
18 "github.com/go-kit/kit/tracing/opentracing"
19 grpctransport "github.com/go-kit/kit/transport/grpc"
20
21 "github.com/go-kit/kit/examples/addsvc/pb"
22 "github.com/go-kit/kit/examples/addsvc/pkg/addendpoint"
23 "github.com/go-kit/kit/examples/addsvc/pkg/addservice"
24 )
25
26 type grpcServer struct {
27 sum grpctransport.Handler
28 concat grpctransport.Handler
29 }
30
31 // NewGRPCServer makes a set of endpoints available as a gRPC AddServer.
32 func NewGRPCServer(endpoints addendpoint.Set, tracer stdopentracing.Tracer, logger log.Logger) pb.AddServer {
33 options := []grpctransport.ServerOption{
34 grpctransport.ServerErrorLogger(logger),
35 }
36 return &grpcServer{
37 sum: grpctransport.NewServer(
38 endpoints.SumEndpoint,
39 decodeGRPCSumRequest,
40 encodeGRPCSumResponse,
41 append(options, grpctransport.ServerBefore(opentracing.GRPCToContext(tracer, "Sum", logger)))...,
42 ),
43 concat: grpctransport.NewServer(
44 endpoints.ConcatEndpoint,
45 decodeGRPCConcatRequest,
46 encodeGRPCConcatResponse,
47 append(options, grpctransport.ServerBefore(opentracing.GRPCToContext(tracer, "Concat", logger)))...,
48 ),
49 }
50 }
51
52 func (s *grpcServer) Sum(ctx oldcontext.Context, req *pb.SumRequest) (*pb.SumReply, error) {
53 _, rep, err := s.sum.ServeGRPC(ctx, req)
54 if err != nil {
55 return nil, err
56 }
57 return rep.(*pb.SumReply), nil
58 }
59
60 func (s *grpcServer) Concat(ctx oldcontext.Context, req *pb.ConcatRequest) (*pb.ConcatReply, error) {
61 _, rep, err := s.concat.ServeGRPC(ctx, req)
62 if err != nil {
63 return nil, err
64 }
65 return rep.(*pb.ConcatReply), nil
66 }
67
68 // NewGRPCClient returns an AddService backed by a gRPC server at the other end
69 // of the conn. The caller is responsible for constructing the conn, and
70 // eventually closing the underlying transport. We bake-in certain middlewares,
71 // implementing the client library pattern.
72 func NewGRPCClient(conn *grpc.ClientConn, tracer stdopentracing.Tracer, logger log.Logger) addservice.Service {
73 // We construct a single ratelimiter middleware, to limit the total outgoing
74 // QPS from this client to all methods on the remote instance. We also
75 // construct per-endpoint circuitbreaker middlewares to demonstrate how
76 // that's done, although they could easily be combined into a single breaker
77 // for the entire remote instance, too.
78 limiter := ratelimit.NewTokenBucketLimiter(jujuratelimit.NewBucketWithRate(100, 100))
79
80 // Each individual endpoint is an http/transport.Client (which implements
81 // endpoint.Endpoint) that gets wrapped with various middlewares. If you
82 // made your own client library, you'd do this work there, so your server
83 // could rely on a consistent set of client behavior.
84 var sumEndpoint endpoint.Endpoint
85 {
86 sumEndpoint = grpctransport.NewClient(
87 conn,
88 "pb.Add",
89 "Sum",
90 encodeGRPCSumRequest,
91 decodeGRPCSumResponse,
92 pb.SumReply{},
93 grpctransport.ClientBefore(opentracing.ContextToGRPC(tracer, logger)),
94 ).Endpoint()
95 sumEndpoint = opentracing.TraceClient(tracer, "Sum")(sumEndpoint)
96 sumEndpoint = limiter(sumEndpoint)
97 sumEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{
98 Name: "Sum",
99 Timeout: 30 * time.Second,
100 }))(sumEndpoint)
101 }
102
103 // The Concat endpoint is the same thing, with slightly different
104 // middlewares to demonstrate how to specialize per-endpoint.
105 var concatEndpoint endpoint.Endpoint
106 {
107 concatEndpoint = grpctransport.NewClient(
108 conn,
109 "pb.Add",
110 "Concat",
111 encodeGRPCConcatRequest,
112 decodeGRPCConcatResponse,
113 pb.ConcatReply{},
114 grpctransport.ClientBefore(opentracing.ContextToGRPC(tracer, logger)),
115 ).Endpoint()
116 concatEndpoint = opentracing.TraceClient(tracer, "Concat")(concatEndpoint)
117 concatEndpoint = limiter(concatEndpoint)
118 concatEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{
119 Name: "Concat",
120 Timeout: 10 * time.Second,
121 }))(concatEndpoint)
122 }
123
124 // Returning the endpoint.Set as a service.Service relies on the
125 // endpoint.Set implementing the Service methods. That's just a simple bit
126 // of glue code.
127 return addendpoint.Set{
128 SumEndpoint: sumEndpoint,
129 ConcatEndpoint: concatEndpoint,
130 }
131 }
132
133 // decodeGRPCSumRequest is a transport/grpc.DecodeRequestFunc that converts a
134 // gRPC sum request to a user-domain sum request. Primarily useful in a server.
135 func decodeGRPCSumRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
136 req := grpcReq.(*pb.SumRequest)
137 return addendpoint.SumRequest{A: int(req.A), B: int(req.B)}, nil
138 }
139
140 // decodeGRPCConcatRequest is a transport/grpc.DecodeRequestFunc that converts a
141 // gRPC concat request to a user-domain concat request. Primarily useful in a
142 // server.
143 func decodeGRPCConcatRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
144 req := grpcReq.(*pb.ConcatRequest)
145 return addendpoint.ConcatRequest{A: req.A, B: req.B}, nil
146 }
147
148 // decodeGRPCSumResponse is a transport/grpc.DecodeResponseFunc that converts a
149 // gRPC sum reply to a user-domain sum response. Primarily useful in a client.
150 func decodeGRPCSumResponse(_ context.Context, grpcReply interface{}) (interface{}, error) {
151 reply := grpcReply.(*pb.SumReply)
152 return addendpoint.SumResponse{V: int(reply.V), Err: str2err(reply.Err)}, nil
153 }
154
155 // decodeGRPCConcatResponse is a transport/grpc.DecodeResponseFunc that converts
156 // a gRPC concat reply to a user-domain concat response. Primarily useful in a
157 // client.
158 func decodeGRPCConcatResponse(_ context.Context, grpcReply interface{}) (interface{}, error) {
159 reply := grpcReply.(*pb.ConcatReply)
160 return addendpoint.ConcatResponse{V: reply.V, Err: str2err(reply.Err)}, nil
161 }
162
163 // encodeGRPCSumResponse is a transport/grpc.EncodeResponseFunc that converts a
164 // user-domain sum response to a gRPC sum reply. Primarily useful in a server.
165 func encodeGRPCSumResponse(_ context.Context, response interface{}) (interface{}, error) {
166 resp := response.(addendpoint.SumResponse)
167 return &pb.SumReply{V: int64(resp.V), Err: err2str(resp.Err)}, nil
168 }
169
170 // encodeGRPCConcatResponse is a transport/grpc.EncodeResponseFunc that converts
171 // a user-domain concat response to a gRPC concat reply. Primarily useful in a
172 // server.
173 func encodeGRPCConcatResponse(_ context.Context, response interface{}) (interface{}, error) {
174 resp := response.(addendpoint.ConcatResponse)
175 return &pb.ConcatReply{V: resp.V, Err: err2str(resp.Err)}, nil
176 }
177
178 // encodeGRPCSumRequest is a transport/grpc.EncodeRequestFunc that converts a
179 // user-domain sum request to a gRPC sum request. Primarily useful in a client.
180 func encodeGRPCSumRequest(_ context.Context, request interface{}) (interface{}, error) {
181 req := request.(addendpoint.SumRequest)
182 return &pb.SumRequest{A: int64(req.A), B: int64(req.B)}, nil
183 }
184
185 // encodeGRPCConcatRequest is a transport/grpc.EncodeRequestFunc that converts a
186 // user-domain concat request to a gRPC concat request. Primarily useful in a
187 // client.
188 func encodeGRPCConcatRequest(_ context.Context, request interface{}) (interface{}, error) {
189 req := request.(addendpoint.ConcatRequest)
190 return &pb.ConcatRequest{A: req.A, B: req.B}, nil
191 }
192
193 // These annoying helper functions are required to translate Go error types to
194 // and from strings, which is the type we use in our IDLs to represent errors.
195 // There is special casing to treat empty strings as nil errors.
196
197 func str2err(s string) error {
198 if s == "" {
199 return nil
200 }
201 return errors.New(s)
202 }
203
204 func err2str(err error) string {
205 if err == nil {
206 return ""
207 }
208 return err.Error()
209 }
+0
-219
examples/addsvc/pkg/addtransport/http.go less more
0 package addtransport
1
2 import (
3 "bytes"
4 "context"
5 "encoding/json"
6 "errors"
7 "io/ioutil"
8 "net/http"
9 "net/url"
10 "strings"
11 "time"
12
13 jujuratelimit "github.com/juju/ratelimit"
14 stdopentracing "github.com/opentracing/opentracing-go"
15 "github.com/sony/gobreaker"
16
17 "github.com/go-kit/kit/circuitbreaker"
18 "github.com/go-kit/kit/endpoint"
19 "github.com/go-kit/kit/log"
20 "github.com/go-kit/kit/ratelimit"
21 "github.com/go-kit/kit/tracing/opentracing"
22 httptransport "github.com/go-kit/kit/transport/http"
23
24 "github.com/go-kit/kit/examples/addsvc/pkg/addendpoint"
25 "github.com/go-kit/kit/examples/addsvc/pkg/addservice"
26 )
27
28 // NewHTTPHandler returns an HTTP handler that makes a set of endpoints
29 // available on predefined paths.
30 func NewHTTPHandler(endpoints addendpoint.Set, tracer stdopentracing.Tracer, logger log.Logger) http.Handler {
31 options := []httptransport.ServerOption{
32 httptransport.ServerErrorEncoder(errorEncoder),
33 httptransport.ServerErrorLogger(logger),
34 }
35 m := http.NewServeMux()
36 m.Handle("/sum", httptransport.NewServer(
37 endpoints.SumEndpoint,
38 decodeHTTPSumRequest,
39 encodeHTTPGenericResponse,
40 append(options, httptransport.ServerBefore(opentracing.HTTPToContext(tracer, "Sum", logger)))...,
41 ))
42 m.Handle("/concat", httptransport.NewServer(
43 endpoints.ConcatEndpoint,
44 decodeHTTPConcatRequest,
45 encodeHTTPGenericResponse,
46 append(options, httptransport.ServerBefore(opentracing.HTTPToContext(tracer, "Concat", logger)))...,
47 ))
48 return m
49 }
50
51 // NewHTTPClient returns an AddService backed by an HTTP server living at the
52 // remote instance. We expect instance to come from a service discovery system,
53 // so likely of the form "host:port". We bake-in certain middlewares,
54 // implementing the client library pattern.
55 func NewHTTPClient(instance string, tracer stdopentracing.Tracer, logger log.Logger) (addservice.Service, error) {
56 // Quickly sanitize the instance string.
57 if !strings.HasPrefix(instance, "http") {
58 instance = "http://" + instance
59 }
60 u, err := url.Parse(instance)
61 if err != nil {
62 return nil, err
63 }
64
65 // We construct a single ratelimiter middleware, to limit the total outgoing
66 // QPS from this client to all methods on the remote instance. We also
67 // construct per-endpoint circuitbreaker middlewares to demonstrate how
68 // that's done, although they could easily be combined into a single breaker
69 // for the entire remote instance, too.
70 limiter := ratelimit.NewTokenBucketLimiter(jujuratelimit.NewBucketWithRate(100, 100))
71
72 // Each individual endpoint is an http/transport.Client (which implements
73 // endpoint.Endpoint) that gets wrapped with various middlewares. If you
74 // made your own client library, you'd do this work there, so your server
75 // could rely on a consistent set of client behavior.
76 var sumEndpoint endpoint.Endpoint
77 {
78 sumEndpoint = httptransport.NewClient(
79 "POST",
80 copyURL(u, "/sum"),
81 encodeHTTPGenericRequest,
82 decodeHTTPSumResponse,
83 httptransport.ClientBefore(opentracing.ContextToHTTP(tracer, logger)),
84 ).Endpoint()
85 sumEndpoint = opentracing.TraceClient(tracer, "Sum")(sumEndpoint)
86 sumEndpoint = limiter(sumEndpoint)
87 sumEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{
88 Name: "Sum",
89 Timeout: 30 * time.Second,
90 }))(sumEndpoint)
91 }
92
93 // The Concat endpoint is the same thing, with slightly different
94 // middlewares to demonstrate how to specialize per-endpoint.
95 var concatEndpoint endpoint.Endpoint
96 {
97 concatEndpoint = httptransport.NewClient(
98 "POST",
99 copyURL(u, "/concat"),
100 encodeHTTPGenericRequest,
101 decodeHTTPConcatResponse,
102 httptransport.ClientBefore(opentracing.ContextToHTTP(tracer, logger)),
103 ).Endpoint()
104 concatEndpoint = opentracing.TraceClient(tracer, "Concat")(concatEndpoint)
105 concatEndpoint = limiter(concatEndpoint)
106 concatEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{
107 Name: "Concat",
108 Timeout: 10 * time.Second,
109 }))(concatEndpoint)
110 }
111
112 // Returning the endpoint.Set as a service.Service relies on the
113 // endpoint.Set implementing the Service methods. That's just a simple bit
114 // of glue code.
115 return addendpoint.Set{
116 SumEndpoint: sumEndpoint,
117 ConcatEndpoint: concatEndpoint,
118 }, nil
119 }
120
121 func copyURL(base *url.URL, path string) *url.URL {
122 next := *base
123 next.Path = path
124 return &next
125 }
126
127 func errorEncoder(_ context.Context, err error, w http.ResponseWriter) {
128 w.WriteHeader(err2code(err))
129 json.NewEncoder(w).Encode(errorWrapper{Error: err.Error()})
130 }
131
132 func err2code(err error) int {
133 switch err {
134 case addservice.ErrTwoZeroes, addservice.ErrMaxSizeExceeded, addservice.ErrIntOverflow:
135 return http.StatusBadRequest
136 }
137 return http.StatusInternalServerError
138 }
139
140 func errorDecoder(r *http.Response) error {
141 var w errorWrapper
142 if err := json.NewDecoder(r.Body).Decode(&w); err != nil {
143 return err
144 }
145 return errors.New(w.Error)
146 }
147
148 type errorWrapper struct {
149 Error string `json:"error"`
150 }
151
152 // decodeHTTPSumRequest is a transport/http.DecodeRequestFunc that decodes a
153 // JSON-encoded sum request from the HTTP request body. Primarily useful in a
154 // server.
155 func decodeHTTPSumRequest(_ context.Context, r *http.Request) (interface{}, error) {
156 var req addendpoint.SumRequest
157 err := json.NewDecoder(r.Body).Decode(&req)
158 return req, err
159 }
160
161 // decodeHTTPConcatRequest is a transport/http.DecodeRequestFunc that decodes a
162 // JSON-encoded concat request from the HTTP request body. Primarily useful in a
163 // server.
164 func decodeHTTPConcatRequest(_ context.Context, r *http.Request) (interface{}, error) {
165 var req addendpoint.ConcatRequest
166 err := json.NewDecoder(r.Body).Decode(&req)
167 return req, err
168 }
169
170 // decodeHTTPSumResponse is a transport/http.DecodeResponseFunc that decodes a
171 // JSON-encoded sum response from the HTTP response body. If the response has a
172 // non-200 status code, we will interpret that as an error and attempt to decode
173 // the specific error message from the response body. Primarily useful in a
174 // client.
175 func decodeHTTPSumResponse(_ context.Context, r *http.Response) (interface{}, error) {
176 if r.StatusCode != http.StatusOK {
177 return nil, errors.New(r.Status)
178 }
179 var resp addendpoint.SumResponse
180 err := json.NewDecoder(r.Body).Decode(&resp)
181 return resp, err
182 }
183
184 // decodeHTTPConcatResponse is a transport/http.DecodeResponseFunc that decodes
185 // a JSON-encoded concat response from the HTTP response body. If the response
186 // has a non-200 status code, we will interpret that as an error and attempt to
187 // decode the specific error message from the response body. Primarily useful in
188 // a client.
189 func decodeHTTPConcatResponse(_ context.Context, r *http.Response) (interface{}, error) {
190 if r.StatusCode != http.StatusOK {
191 return nil, errors.New(r.Status)
192 }
193 var resp addendpoint.ConcatResponse
194 err := json.NewDecoder(r.Body).Decode(&resp)
195 return resp, err
196 }
197
198 // encodeHTTPGenericRequest is a transport/http.EncodeRequestFunc that
199 // JSON-encodes any request to the request body. Primarily useful in a client.
200 func encodeHTTPGenericRequest(_ context.Context, r *http.Request, request interface{}) error {
201 var buf bytes.Buffer
202 if err := json.NewEncoder(&buf).Encode(request); err != nil {
203 return err
204 }
205 r.Body = ioutil.NopCloser(&buf)
206 return nil
207 }
208
209 // encodeHTTPGenericResponse is a transport/http.EncodeResponseFunc that encodes
210 // the response as JSON to the response writer. Primarily useful in a server.
211 func encodeHTTPGenericResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
212 if f, ok := response.(addendpoint.Failer); ok && f.Failed() != nil {
213 errorEncoder(ctx, f.Failed(), w)
214 return nil
215 }
216 w.Header().Set("Content-Type", "application/json; charset=utf-8")
217 return json.NewEncoder(w).Encode(response)
218 }
+0
-119
examples/addsvc/pkg/addtransport/thrift.go less more
0 package addtransport
1
2 import (
3 "context"
4 "time"
5
6 jujuratelimit "github.com/juju/ratelimit"
7 "github.com/sony/gobreaker"
8
9 "github.com/go-kit/kit/circuitbreaker"
10 "github.com/go-kit/kit/endpoint"
11 "github.com/go-kit/kit/ratelimit"
12
13 "github.com/go-kit/kit/examples/addsvc/pkg/addendpoint"
14 "github.com/go-kit/kit/examples/addsvc/pkg/addservice"
15 addthrift "github.com/go-kit/kit/examples/addsvc/thrift/gen-go/addsvc"
16 )
17
18 type thriftServer struct {
19 ctx context.Context
20 endpoints addendpoint.Set
21 }
22
23 // NewThriftServer makes a set of endpoints available as a Thrift service.
24 func NewThriftServer(endpoints addendpoint.Set) addthrift.AddService {
25 return &thriftServer{
26 endpoints: endpoints,
27 }
28 }
29
30 func (s *thriftServer) Sum(ctx context.Context, a int64, b int64) (*addthrift.SumReply, error) {
31 request := addendpoint.SumRequest{A: int(a), B: int(b)}
32 response, err := s.endpoints.SumEndpoint(ctx, request)
33 if err != nil {
34 return nil, err
35 }
36 resp := response.(addendpoint.SumResponse)
37 return &addthrift.SumReply{Value: int64(resp.V), Err: err2str(resp.Err)}, nil
38 }
39
40 func (s *thriftServer) Concat(ctx context.Context, a string, b string) (*addthrift.ConcatReply, error) {
41 request := addendpoint.ConcatRequest{A: a, B: b}
42 response, err := s.endpoints.ConcatEndpoint(ctx, request)
43 if err != nil {
44 return nil, err
45 }
46 resp := response.(addendpoint.ConcatResponse)
47 return &addthrift.ConcatReply{Value: resp.V, Err: err2str(resp.Err)}, nil
48 }
49
50 // NewThriftClient returns an AddService backed by a Thrift server described by
51 // the provided client. The caller is responsible for constructing the client,
52 // and eventually closing the underlying transport. We bake-in certain middlewares,
53 // implementing the client library pattern.
54 func NewThriftClient(client *addthrift.AddServiceClient) addservice.Service {
55 // We construct a single ratelimiter middleware, to limit the total outgoing
56 // QPS from this client to all methods on the remote instance. We also
57 // construct per-endpoint circuitbreaker middlewares to demonstrate how
58 // that's done, although they could easily be combined into a single breaker
59 // for the entire remote instance, too.
60 limiter := ratelimit.NewTokenBucketLimiter(jujuratelimit.NewBucketWithRate(100, 100))
61
62 // Each individual endpoint is an http/transport.Client (which implements
63 // endpoint.Endpoint) that gets wrapped with various middlewares. If you
64 // could rely on a consistent set of client behavior.
65 var sumEndpoint endpoint.Endpoint
66 {
67 sumEndpoint = MakeThriftSumEndpoint(client)
68 sumEndpoint = limiter(sumEndpoint)
69 sumEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{
70 Name: "Sum",
71 Timeout: 30 * time.Second,
72 }))(sumEndpoint)
73 }
74
75 // The Concat endpoint is the same thing, with slightly different
76 // middlewares to demonstrate how to specialize per-endpoint.
77 var concatEndpoint endpoint.Endpoint
78 {
79 concatEndpoint = MakeThriftConcatEndpoint(client)
80 concatEndpoint = limiter(concatEndpoint)
81 concatEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{
82 Name: "Concat",
83 Timeout: 10 * time.Second,
84 }))(concatEndpoint)
85 }
86
87 // Returning the endpoint.Set as a service.Service relies on the
88 // endpoint.Set implementing the Service methods. That's just a simple bit
89 // of glue code.
90 return addendpoint.Set{
91 SumEndpoint: sumEndpoint,
92 ConcatEndpoint: concatEndpoint,
93 }
94 }
95
96 // MakeThriftSumEndpoint returns an endpoint that invokes the passed Thrift client.
97 // Useful only in clients, and only until a proper transport/thrift.Client exists.
98 func MakeThriftSumEndpoint(client *addthrift.AddServiceClient) endpoint.Endpoint {
99 return func(ctx context.Context, request interface{}) (interface{}, error) {
100 req := request.(addendpoint.SumRequest)
101 reply, err := client.Sum(ctx, int64(req.A), int64(req.B))
102 if err == addservice.ErrIntOverflow {
103 return nil, err // special case; see comment on ErrIntOverflow
104 }
105 return addendpoint.SumResponse{V: int(reply.Value), Err: err}, nil
106 }
107 }
108
109 // MakeThriftConcatEndpoint returns an endpoint that invokes the passed Thrift
110 // client. Useful only in clients, and only until a proper
111 // transport/thrift.Client exists.
112 func MakeThriftConcatEndpoint(client *addthrift.AddServiceClient) endpoint.Endpoint {
113 return func(ctx context.Context, request interface{}) (interface{}, error) {
114 req := request.(addendpoint.ConcatRequest)
115 reply, err := client.Concat(ctx, req.A, req.B)
116 return addendpoint.ConcatResponse{V: reply.Value, Err: err}, nil
117 }
118 }
+0
-14
examples/addsvc/thrift/addsvc.thrift less more
0 struct SumReply {
1 1: i64 value
2 2: string err
3 }
4
5 struct ConcatReply {
6 1: string value
7 2: string err
8 }
9
10 service AddService {
11 SumReply Sum(1: i64 a, 2: i64 b)
12 ConcatReply Concat(1: string a, 2: string b)
13 }
+0
-5
examples/addsvc/thrift/compile.sh less more
0 #!/usr/bin/env sh
1
2 # See also https://thrift.apache.org/tutorial/go
3
4 thrift -r --gen "go:package_prefix=github.com/go-kit/kit/examples/addsvc/thrift/gen-go/,thrift_import=github.com/apache/thrift/lib/go/thrift" addsvc.thrift
+0
-7
examples/addsvc/thrift/gen-go/addsvc/GoUnusedProtection__.go less more
0 // Autogenerated by Thrift Compiler (1.0.0-dev)
1 // DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
2
3 package addsvc
4
5 var GoUnusedProtection__ int;
6
+0
-159
examples/addsvc/thrift/gen-go/addsvc/add_service-remote/add_service-remote.go less more
0 // Autogenerated by Thrift Compiler (1.0.0-dev)
1 // DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
2
3 package main
4
5 import (
6 "context"
7 "flag"
8 "fmt"
9 "math"
10 "net"
11 "net/url"
12 "os"
13 "strconv"
14 "strings"
15 "github.com/apache/thrift/lib/go/thrift"
16 "github.com/go-kit/kit/examples/addsvc/thrift/gen-go/addsvc"
17 )
18
19
20 func Usage() {
21 fmt.Fprintln(os.Stderr, "Usage of ", os.Args[0], " [-h host:port] [-u url] [-f[ramed]] function [arg1 [arg2...]]:")
22 flag.PrintDefaults()
23 fmt.Fprintln(os.Stderr, "\nFunctions:")
24 fmt.Fprintln(os.Stderr, " SumReply Sum(i64 a, i64 b)")
25 fmt.Fprintln(os.Stderr, " ConcatReply Concat(string a, string b)")
26 fmt.Fprintln(os.Stderr)
27 os.Exit(0)
28 }
29
30 func main() {
31 flag.Usage = Usage
32 var host string
33 var port int
34 var protocol string
35 var urlString string
36 var framed bool
37 var useHttp bool
38 var parsedUrl url.URL
39 var trans thrift.TTransport
40 _ = strconv.Atoi
41 _ = math.Abs
42 flag.Usage = Usage
43 flag.StringVar(&host, "h", "localhost", "Specify host and port")
44 flag.IntVar(&port, "p", 9090, "Specify port")
45 flag.StringVar(&protocol, "P", "binary", "Specify the protocol (binary, compact, simplejson, json)")
46 flag.StringVar(&urlString, "u", "", "Specify the url")
47 flag.BoolVar(&framed, "framed", false, "Use framed transport")
48 flag.BoolVar(&useHttp, "http", false, "Use http")
49 flag.Parse()
50
51 if len(urlString) > 0 {
52 parsedUrl, err := url.Parse(urlString)
53 if err != nil {
54 fmt.Fprintln(os.Stderr, "Error parsing URL: ", err)
55 flag.Usage()
56 }
57 host = parsedUrl.Host
58 useHttp = len(parsedUrl.Scheme) <= 0 || parsedUrl.Scheme == "http"
59 } else if useHttp {
60 _, err := url.Parse(fmt.Sprint("http://", host, ":", port))
61 if err != nil {
62 fmt.Fprintln(os.Stderr, "Error parsing URL: ", err)
63 flag.Usage()
64 }
65 }
66
67 cmd := flag.Arg(0)
68 var err error
69 if useHttp {
70 trans, err = thrift.NewTHttpClient(parsedUrl.String())
71 } else {
72 portStr := fmt.Sprint(port)
73 if strings.Contains(host, ":") {
74 host, portStr, err = net.SplitHostPort(host)
75 if err != nil {
76 fmt.Fprintln(os.Stderr, "error with host:", err)
77 os.Exit(1)
78 }
79 }
80 trans, err = thrift.NewTSocket(net.JoinHostPort(host, portStr))
81 if err != nil {
82 fmt.Fprintln(os.Stderr, "error resolving address:", err)
83 os.Exit(1)
84 }
85 if framed {
86 trans = thrift.NewTFramedTransport(trans)
87 }
88 }
89 if err != nil {
90 fmt.Fprintln(os.Stderr, "Error creating transport", err)
91 os.Exit(1)
92 }
93 defer trans.Close()
94 var protocolFactory thrift.TProtocolFactory
95 switch protocol {
96 case "compact":
97 protocolFactory = thrift.NewTCompactProtocolFactory()
98 break
99 case "simplejson":
100 protocolFactory = thrift.NewTSimpleJSONProtocolFactory()
101 break
102 case "json":
103 protocolFactory = thrift.NewTJSONProtocolFactory()
104 break
105 case "binary", "":
106 protocolFactory = thrift.NewTBinaryProtocolFactoryDefault()
107 break
108 default:
109 fmt.Fprintln(os.Stderr, "Invalid protocol specified: ", protocol)
110 Usage()
111 os.Exit(1)
112 }
113 client := addsvc.NewAddServiceClientFactory(trans, protocolFactory)
114 if err := trans.Open(); err != nil {
115 fmt.Fprintln(os.Stderr, "Error opening socket to ", host, ":", port, " ", err)
116 os.Exit(1)
117 }
118
119 switch cmd {
120 case "Sum":
121 if flag.NArg() - 1 != 2 {
122 fmt.Fprintln(os.Stderr, "Sum requires 2 args")
123 flag.Usage()
124 }
125 argvalue0, err6 := (strconv.ParseInt(flag.Arg(1), 10, 64))
126 if err6 != nil {
127 Usage()
128 return
129 }
130 value0 := argvalue0
131 argvalue1, err7 := (strconv.ParseInt(flag.Arg(2), 10, 64))
132 if err7 != nil {
133 Usage()
134 return
135 }
136 value1 := argvalue1
137 fmt.Print(client.Sum(context.Background(), value0, value1))
138 fmt.Print("\n")
139 break
140 case "Concat":
141 if flag.NArg() - 1 != 2 {
142 fmt.Fprintln(os.Stderr, "Concat requires 2 args")
143 flag.Usage()
144 }
145 argvalue0 := flag.Arg(1)
146 value0 := argvalue0
147 argvalue1 := flag.Arg(2)
148 value1 := argvalue1
149 fmt.Print(client.Concat(context.Background(), value0, value1))
150 fmt.Print("\n")
151 break
152 case "":
153 Usage()
154 break
155 default:
156 fmt.Fprintln(os.Stderr, "Invalid function ", cmd)
157 }
158 }
+0
-24
examples/addsvc/thrift/gen-go/addsvc/addsvc-consts.go less more
0 // Autogenerated by Thrift Compiler (1.0.0-dev)
1 // DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
2
3 package addsvc
4
5 import (
6 "bytes"
7 "reflect"
8 "context"
9 "fmt"
10 "github.com/apache/thrift/lib/go/thrift"
11 )
12
13 // (needed to ensure safety because of naive import list construction.)
14 var _ = thrift.ZERO
15 var _ = fmt.Printf
16 var _ = context.Background
17 var _ = reflect.DeepEqual
18 var _ = bytes.Equal
19
20
21 func init() {
22 }
23
+0
-1065
examples/addsvc/thrift/gen-go/addsvc/addsvc.go less more
0 // Autogenerated by Thrift Compiler (1.0.0-dev)
1 // DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
2
3 package addsvc
4
5 import (
6 "bytes"
7 "reflect"
8 "context"
9 "fmt"
10 "github.com/apache/thrift/lib/go/thrift"
11 )
12
13 // (needed to ensure safety because of naive import list construction.)
14 var _ = thrift.ZERO
15 var _ = fmt.Printf
16 var _ = context.Background
17 var _ = reflect.DeepEqual
18 var _ = bytes.Equal
19
20 // Attributes:
21 // - Value
22 // - Err
23 type SumReply struct {
24 Value int64 `thrift:"value,1" db:"value" json:"value"`
25 Err string `thrift:"err,2" db:"err" json:"err"`
26 }
27
28 func NewSumReply() *SumReply {
29 return &SumReply{}
30 }
31
32
33 func (p *SumReply) GetValue() int64 {
34 return p.Value
35 }
36
37 func (p *SumReply) GetErr() string {
38 return p.Err
39 }
40 func (p *SumReply) Read(iprot thrift.TProtocol) error {
41 if _, err := iprot.ReadStructBegin(); err != nil {
42 return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err)
43 }
44
45
46 for {
47 _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin()
48 if err != nil {
49 return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err)
50 }
51 if fieldTypeId == thrift.STOP { break; }
52 switch fieldId {
53 case 1:
54 if fieldTypeId == thrift.I64 {
55 if err := p.ReadField1(iprot); err != nil {
56 return err
57 }
58 } else {
59 if err := iprot.Skip(fieldTypeId); err != nil {
60 return err
61 }
62 }
63 case 2:
64 if fieldTypeId == thrift.STRING {
65 if err := p.ReadField2(iprot); err != nil {
66 return err
67 }
68 } else {
69 if err := iprot.Skip(fieldTypeId); err != nil {
70 return err
71 }
72 }
73 default:
74 if err := iprot.Skip(fieldTypeId); err != nil {
75 return err
76 }
77 }
78 if err := iprot.ReadFieldEnd(); err != nil {
79 return err
80 }
81 }
82 if err := iprot.ReadStructEnd(); err != nil {
83 return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
84 }
85 return nil
86 }
87
88 func (p *SumReply) ReadField1(iprot thrift.TProtocol) error {
89 if v, err := iprot.ReadI64(); err != nil {
90 return thrift.PrependError("error reading field 1: ", err)
91 } else {
92 p.Value = v
93 }
94 return nil
95 }
96
97 func (p *SumReply) ReadField2(iprot thrift.TProtocol) error {
98 if v, err := iprot.ReadString(); err != nil {
99 return thrift.PrependError("error reading field 2: ", err)
100 } else {
101 p.Err = v
102 }
103 return nil
104 }
105
106 func (p *SumReply) Write(oprot thrift.TProtocol) error {
107 if err := oprot.WriteStructBegin("SumReply"); err != nil {
108 return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) }
109 if p != nil {
110 if err := p.writeField1(oprot); err != nil { return err }
111 if err := p.writeField2(oprot); err != nil { return err }
112 }
113 if err := oprot.WriteFieldStop(); err != nil {
114 return thrift.PrependError("write field stop error: ", err) }
115 if err := oprot.WriteStructEnd(); err != nil {
116 return thrift.PrependError("write struct stop error: ", err) }
117 return nil
118 }
119
120 func (p *SumReply) writeField1(oprot thrift.TProtocol) (err error) {
121 if err := oprot.WriteFieldBegin("value", thrift.I64, 1); err != nil {
122 return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:value: ", p), err) }
123 if err := oprot.WriteI64(int64(p.Value)); err != nil {
124 return thrift.PrependError(fmt.Sprintf("%T.value (1) field write error: ", p), err) }
125 if err := oprot.WriteFieldEnd(); err != nil {
126 return thrift.PrependError(fmt.Sprintf("%T write field end error 1:value: ", p), err) }
127 return err
128 }
129
130 func (p *SumReply) writeField2(oprot thrift.TProtocol) (err error) {
131 if err := oprot.WriteFieldBegin("err", thrift.STRING, 2); err != nil {
132 return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:err: ", p), err) }
133 if err := oprot.WriteString(string(p.Err)); err != nil {
134 return thrift.PrependError(fmt.Sprintf("%T.err (2) field write error: ", p), err) }
135 if err := oprot.WriteFieldEnd(); err != nil {
136 return thrift.PrependError(fmt.Sprintf("%T write field end error 2:err: ", p), err) }
137 return err
138 }
139
140 func (p *SumReply) String() string {
141 if p == nil {
142 return "<nil>"
143 }
144 return fmt.Sprintf("SumReply(%+v)", *p)
145 }
146
147 // Attributes:
148 // - Value
149 // - Err
150 type ConcatReply struct {
151 Value string `thrift:"value,1" db:"value" json:"value"`
152 Err string `thrift:"err,2" db:"err" json:"err"`
153 }
154
155 func NewConcatReply() *ConcatReply {
156 return &ConcatReply{}
157 }
158
159
160 func (p *ConcatReply) GetValue() string {
161 return p.Value
162 }
163
164 func (p *ConcatReply) GetErr() string {
165 return p.Err
166 }
167 func (p *ConcatReply) Read(iprot thrift.TProtocol) error {
168 if _, err := iprot.ReadStructBegin(); err != nil {
169 return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err)
170 }
171
172
173 for {
174 _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin()
175 if err != nil {
176 return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err)
177 }
178 if fieldTypeId == thrift.STOP { break; }
179 switch fieldId {
180 case 1:
181 if fieldTypeId == thrift.STRING {
182 if err := p.ReadField1(iprot); err != nil {
183 return err
184 }
185 } else {
186 if err := iprot.Skip(fieldTypeId); err != nil {
187 return err
188 }
189 }
190 case 2:
191 if fieldTypeId == thrift.STRING {
192 if err := p.ReadField2(iprot); err != nil {
193 return err
194 }
195 } else {
196 if err := iprot.Skip(fieldTypeId); err != nil {
197 return err
198 }
199 }
200 default:
201 if err := iprot.Skip(fieldTypeId); err != nil {
202 return err
203 }
204 }
205 if err := iprot.ReadFieldEnd(); err != nil {
206 return err
207 }
208 }
209 if err := iprot.ReadStructEnd(); err != nil {
210 return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
211 }
212 return nil
213 }
214
215 func (p *ConcatReply) ReadField1(iprot thrift.TProtocol) error {
216 if v, err := iprot.ReadString(); err != nil {
217 return thrift.PrependError("error reading field 1: ", err)
218 } else {
219 p.Value = v
220 }
221 return nil
222 }
223
224 func (p *ConcatReply) ReadField2(iprot thrift.TProtocol) error {
225 if v, err := iprot.ReadString(); err != nil {
226 return thrift.PrependError("error reading field 2: ", err)
227 } else {
228 p.Err = v
229 }
230 return nil
231 }
232
233 func (p *ConcatReply) Write(oprot thrift.TProtocol) error {
234 if err := oprot.WriteStructBegin("ConcatReply"); err != nil {
235 return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) }
236 if p != nil {
237 if err := p.writeField1(oprot); err != nil { return err }
238 if err := p.writeField2(oprot); err != nil { return err }
239 }
240 if err := oprot.WriteFieldStop(); err != nil {
241 return thrift.PrependError("write field stop error: ", err) }
242 if err := oprot.WriteStructEnd(); err != nil {
243 return thrift.PrependError("write struct stop error: ", err) }
244 return nil
245 }
246
247 func (p *ConcatReply) writeField1(oprot thrift.TProtocol) (err error) {
248 if err := oprot.WriteFieldBegin("value", thrift.STRING, 1); err != nil {
249 return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:value: ", p), err) }
250 if err := oprot.WriteString(string(p.Value)); err != nil {
251 return thrift.PrependError(fmt.Sprintf("%T.value (1) field write error: ", p), err) }
252 if err := oprot.WriteFieldEnd(); err != nil {
253 return thrift.PrependError(fmt.Sprintf("%T write field end error 1:value: ", p), err) }
254 return err
255 }
256
257 func (p *ConcatReply) writeField2(oprot thrift.TProtocol) (err error) {
258 if err := oprot.WriteFieldBegin("err", thrift.STRING, 2); err != nil {
259 return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:err: ", p), err) }
260 if err := oprot.WriteString(string(p.Err)); err != nil {
261 return thrift.PrependError(fmt.Sprintf("%T.err (2) field write error: ", p), err) }
262 if err := oprot.WriteFieldEnd(); err != nil {
263 return thrift.PrependError(fmt.Sprintf("%T write field end error 2:err: ", p), err) }
264 return err
265 }
266
267 func (p *ConcatReply) String() string {
268 if p == nil {
269 return "<nil>"
270 }
271 return fmt.Sprintf("ConcatReply(%+v)", *p)
272 }
273
274 type AddService interface {
275 // Parameters:
276 // - A
277 // - B
278 Sum(ctx context.Context, a int64, b int64) (r *SumReply, err error)
279 // Parameters:
280 // - A
281 // - B
282 Concat(ctx context.Context, a string, b string) (r *ConcatReply, err error)
283 }
284
285 type AddServiceClient struct {
286 Transport thrift.TTransport
287 ProtocolFactory thrift.TProtocolFactory
288 InputProtocol thrift.TProtocol
289 OutputProtocol thrift.TProtocol
290 SeqId int32
291 }
292
293 func NewAddServiceClientFactory(t thrift.TTransport, f thrift.TProtocolFactory) *AddServiceClient {
294 return &AddServiceClient{Transport: t,
295 ProtocolFactory: f,
296 InputProtocol: f.GetProtocol(t),
297 OutputProtocol: f.GetProtocol(t),
298 SeqId: 0,
299 }
300 }
301
302 func NewAddServiceClientProtocol(t thrift.TTransport, iprot thrift.TProtocol, oprot thrift.TProtocol) *AddServiceClient {
303 return &AddServiceClient{Transport: t,
304 ProtocolFactory: nil,
305 InputProtocol: iprot,
306 OutputProtocol: oprot,
307 SeqId: 0,
308 }
309 }
310
311 // Parameters:
312 // - A
313 // - B
314 func (p *AddServiceClient) Sum(ctx context.Context, a int64, b int64) (r *SumReply, err error) {
315 if err = p.sendSum(a, b); err != nil { return }
316 return p.recvSum()
317 }
318
319 func (p *AddServiceClient) sendSum(a int64, b int64)(err error) {
320 oprot := p.OutputProtocol
321 if oprot == nil {
322 oprot = p.ProtocolFactory.GetProtocol(p.Transport)
323 p.OutputProtocol = oprot
324 }
325 p.SeqId++
326 if err = oprot.WriteMessageBegin("Sum", thrift.CALL, p.SeqId); err != nil {
327 return
328 }
329 args := AddServiceSumArgs{
330 A : a,
331 B : b,
332 }
333 if err = args.Write(oprot); err != nil {
334 return
335 }
336 if err = oprot.WriteMessageEnd(); err != nil {
337 return
338 }
339 return oprot.Flush()
340 }
341
342
343 func (p *AddServiceClient) recvSum() (value *SumReply, err error) {
344 iprot := p.InputProtocol
345 if iprot == nil {
346 iprot = p.ProtocolFactory.GetProtocol(p.Transport)
347 p.InputProtocol = iprot
348 }
349 method, mTypeId, seqId, err := iprot.ReadMessageBegin()
350 if err != nil {
351 return
352 }
353 if method != "Sum" {
354 err = thrift.NewTApplicationException(thrift.WRONG_METHOD_NAME, "Sum failed: wrong method name")
355 return
356 }
357 if p.SeqId != seqId {
358 err = thrift.NewTApplicationException(thrift.BAD_SEQUENCE_ID, "Sum failed: out of sequence response")
359 return
360 }
361 if mTypeId == thrift.EXCEPTION {
362 error0 := thrift.NewTApplicationException(thrift.UNKNOWN_APPLICATION_EXCEPTION, "Unknown Exception")
363 var error1 error
364 error1, err = error0.Read(iprot)
365 if err != nil {
366 return
367 }
368 if err = iprot.ReadMessageEnd(); err != nil {
369 return
370 }
371 err = error1
372 return
373 }
374 if mTypeId != thrift.REPLY {
375 err = thrift.NewTApplicationException(thrift.INVALID_MESSAGE_TYPE_EXCEPTION, "Sum failed: invalid message type")
376 return
377 }
378 result := AddServiceSumResult{}
379 if err = result.Read(iprot); err != nil {
380 return
381 }
382 if err = iprot.ReadMessageEnd(); err != nil {
383 return
384 }
385 value = result.GetSuccess()
386 return
387 }
388
389 // Parameters:
390 // - A
391 // - B
392 func (p *AddServiceClient) Concat(ctx context.Context, a string, b string) (r *ConcatReply, err error) {
393 if err = p.sendConcat(a, b); err != nil { return }
394 return p.recvConcat()
395 }
396
397 func (p *AddServiceClient) sendConcat(a string, b string)(err error) {
398 oprot := p.OutputProtocol
399 if oprot == nil {
400 oprot = p.ProtocolFactory.GetProtocol(p.Transport)
401 p.OutputProtocol = oprot
402 }
403 p.SeqId++
404 if err = oprot.WriteMessageBegin("Concat", thrift.CALL, p.SeqId); err != nil {
405 return
406 }
407 args := AddServiceConcatArgs{
408 A : a,
409 B : b,
410 }
411 if err = args.Write(oprot); err != nil {
412 return
413 }
414 if err = oprot.WriteMessageEnd(); err != nil {
415 return
416 }
417 return oprot.Flush()
418 }
419
420
421 func (p *AddServiceClient) recvConcat() (value *ConcatReply, err error) {
422 iprot := p.InputProtocol
423 if iprot == nil {
424 iprot = p.ProtocolFactory.GetProtocol(p.Transport)
425 p.InputProtocol = iprot
426 }
427 method, mTypeId, seqId, err := iprot.ReadMessageBegin()
428 if err != nil {
429 return
430 }
431 if method != "Concat" {
432 err = thrift.NewTApplicationException(thrift.WRONG_METHOD_NAME, "Concat failed: wrong method name")
433 return
434 }
435 if p.SeqId != seqId {
436 err = thrift.NewTApplicationException(thrift.BAD_SEQUENCE_ID, "Concat failed: out of sequence response")
437 return
438 }
439 if mTypeId == thrift.EXCEPTION {
440 error2 := thrift.NewTApplicationException(thrift.UNKNOWN_APPLICATION_EXCEPTION, "Unknown Exception")
441 var error3 error
442 error3, err = error2.Read(iprot)
443 if err != nil {
444 return
445 }
446 if err = iprot.ReadMessageEnd(); err != nil {
447 return
448 }
449 err = error3
450 return
451 }
452 if mTypeId != thrift.REPLY {
453 err = thrift.NewTApplicationException(thrift.INVALID_MESSAGE_TYPE_EXCEPTION, "Concat failed: invalid message type")
454 return
455 }
456 result := AddServiceConcatResult{}
457 if err = result.Read(iprot); err != nil {
458 return
459 }
460 if err = iprot.ReadMessageEnd(); err != nil {
461 return
462 }
463 value = result.GetSuccess()
464 return
465 }
466
467
468 type AddServiceProcessor struct {
469 processorMap map[string]thrift.TProcessorFunction
470 handler AddService
471 }
472
473 func (p *AddServiceProcessor) AddToProcessorMap(key string, processor thrift.TProcessorFunction) {
474 p.processorMap[key] = processor
475 }
476
477 func (p *AddServiceProcessor) GetProcessorFunction(key string) (processor thrift.TProcessorFunction, ok bool) {
478 processor, ok = p.processorMap[key]
479 return processor, ok
480 }
481
482 func (p *AddServiceProcessor) ProcessorMap() map[string]thrift.TProcessorFunction {
483 return p.processorMap
484 }
485
486 func NewAddServiceProcessor(handler AddService) *AddServiceProcessor {
487
488 self4 := &AddServiceProcessor{handler:handler, processorMap:make(map[string]thrift.TProcessorFunction)}
489 self4.processorMap["Sum"] = &addServiceProcessorSum{handler:handler}
490 self4.processorMap["Concat"] = &addServiceProcessorConcat{handler:handler}
491 return self4
492 }
493
494 func (p *AddServiceProcessor) Process(ctx context.Context, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) {
495 name, _, seqId, err := iprot.ReadMessageBegin()
496 if err != nil { return false, err }
497 if processor, ok := p.GetProcessorFunction(name); ok {
498 return processor.Process(ctx, seqId, iprot, oprot)
499 }
500 iprot.Skip(thrift.STRUCT)
501 iprot.ReadMessageEnd()
502 x5 := thrift.NewTApplicationException(thrift.UNKNOWN_METHOD, "Unknown function " + name)
503 oprot.WriteMessageBegin(name, thrift.EXCEPTION, seqId)
504 x5.Write(oprot)
505 oprot.WriteMessageEnd()
506 oprot.Flush()
507 return false, x5
508
509 }
510
511 type addServiceProcessorSum struct {
512 handler AddService
513 }
514
515 func (p *addServiceProcessorSum) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) {
516 args := AddServiceSumArgs{}
517 if err = args.Read(iprot); err != nil {
518 iprot.ReadMessageEnd()
519 x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error())
520 oprot.WriteMessageBegin("Sum", thrift.EXCEPTION, seqId)
521 x.Write(oprot)
522 oprot.WriteMessageEnd()
523 oprot.Flush()
524 return false, err
525 }
526
527 iprot.ReadMessageEnd()
528 result := AddServiceSumResult{}
529 var retval *SumReply
530 var err2 error
531 if retval, err2 = p.handler.Sum(ctx, args.A, args.B); err2 != nil {
532 x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing Sum: " + err2.Error())
533 oprot.WriteMessageBegin("Sum", thrift.EXCEPTION, seqId)
534 x.Write(oprot)
535 oprot.WriteMessageEnd()
536 oprot.Flush()
537 return true, err2
538 } else {
539 result.Success = retval
540 }
541 if err2 = oprot.WriteMessageBegin("Sum", thrift.REPLY, seqId); err2 != nil {
542 err = err2
543 }
544 if err2 = result.Write(oprot); err == nil && err2 != nil {
545 err = err2
546 }
547 if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil {
548 err = err2
549 }
550 if err2 = oprot.Flush(); err == nil && err2 != nil {
551 err = err2
552 }
553 if err != nil {
554 return
555 }
556 return true, err
557 }
558
559 type addServiceProcessorConcat struct {
560 handler AddService
561 }
562
563 func (p *addServiceProcessorConcat) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) {
564 args := AddServiceConcatArgs{}
565 if err = args.Read(iprot); err != nil {
566 iprot.ReadMessageEnd()
567 x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error())
568 oprot.WriteMessageBegin("Concat", thrift.EXCEPTION, seqId)
569 x.Write(oprot)
570 oprot.WriteMessageEnd()
571 oprot.Flush()
572 return false, err
573 }
574
575 iprot.ReadMessageEnd()
576 result := AddServiceConcatResult{}
577 var retval *ConcatReply
578 var err2 error
579 if retval, err2 = p.handler.Concat(ctx, args.A, args.B); err2 != nil {
580 x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing Concat: " + err2.Error())
581 oprot.WriteMessageBegin("Concat", thrift.EXCEPTION, seqId)
582 x.Write(oprot)
583 oprot.WriteMessageEnd()
584 oprot.Flush()
585 return true, err2
586 } else {
587 result.Success = retval
588 }
589 if err2 = oprot.WriteMessageBegin("Concat", thrift.REPLY, seqId); err2 != nil {
590 err = err2
591 }
592 if err2 = result.Write(oprot); err == nil && err2 != nil {
593 err = err2
594 }
595 if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil {
596 err = err2
597 }
598 if err2 = oprot.Flush(); err == nil && err2 != nil {
599 err = err2
600 }
601 if err != nil {
602 return
603 }
604 return true, err
605 }
606
607
608 // HELPER FUNCTIONS AND STRUCTURES
609
610 // Attributes:
611 // - A
612 // - B
613 type AddServiceSumArgs struct {
614 A int64 `thrift:"a,1" db:"a" json:"a"`
615 B int64 `thrift:"b,2" db:"b" json:"b"`
616 }
617
618 func NewAddServiceSumArgs() *AddServiceSumArgs {
619 return &AddServiceSumArgs{}
620 }
621
622
623 func (p *AddServiceSumArgs) GetA() int64 {
624 return p.A
625 }
626
627 func (p *AddServiceSumArgs) GetB() int64 {
628 return p.B
629 }
630 func (p *AddServiceSumArgs) Read(iprot thrift.TProtocol) error {
631 if _, err := iprot.ReadStructBegin(); err != nil {
632 return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err)
633 }
634
635
636 for {
637 _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin()
638 if err != nil {
639 return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err)
640 }
641 if fieldTypeId == thrift.STOP { break; }
642 switch fieldId {
643 case 1:
644 if fieldTypeId == thrift.I64 {
645 if err := p.ReadField1(iprot); err != nil {
646 return err
647 }
648 } else {
649 if err := iprot.Skip(fieldTypeId); err != nil {
650 return err
651 }
652 }
653 case 2:
654 if fieldTypeId == thrift.I64 {
655 if err := p.ReadField2(iprot); err != nil {
656 return err
657 }
658 } else {
659 if err := iprot.Skip(fieldTypeId); err != nil {
660 return err
661 }
662 }
663 default:
664 if err := iprot.Skip(fieldTypeId); err != nil {
665 return err
666 }
667 }
668 if err := iprot.ReadFieldEnd(); err != nil {
669 return err
670 }
671 }
672 if err := iprot.ReadStructEnd(); err != nil {
673 return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
674 }
675 return nil
676 }
677
678 func (p *AddServiceSumArgs) ReadField1(iprot thrift.TProtocol) error {
679 if v, err := iprot.ReadI64(); err != nil {
680 return thrift.PrependError("error reading field 1: ", err)
681 } else {
682 p.A = v
683 }
684 return nil
685 }
686
687 func (p *AddServiceSumArgs) ReadField2(iprot thrift.TProtocol) error {
688 if v, err := iprot.ReadI64(); err != nil {
689 return thrift.PrependError("error reading field 2: ", err)
690 } else {
691 p.B = v
692 }
693 return nil
694 }
695
696 func (p *AddServiceSumArgs) Write(oprot thrift.TProtocol) error {
697 if err := oprot.WriteStructBegin("Sum_args"); err != nil {
698 return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) }
699 if p != nil {
700 if err := p.writeField1(oprot); err != nil { return err }
701 if err := p.writeField2(oprot); err != nil { return err }
702 }
703 if err := oprot.WriteFieldStop(); err != nil {
704 return thrift.PrependError("write field stop error: ", err) }
705 if err := oprot.WriteStructEnd(); err != nil {
706 return thrift.PrependError("write struct stop error: ", err) }
707 return nil
708 }
709
710 func (p *AddServiceSumArgs) writeField1(oprot thrift.TProtocol) (err error) {
711 if err := oprot.WriteFieldBegin("a", thrift.I64, 1); err != nil {
712 return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:a: ", p), err) }
713 if err := oprot.WriteI64(int64(p.A)); err != nil {
714 return thrift.PrependError(fmt.Sprintf("%T.a (1) field write error: ", p), err) }
715 if err := oprot.WriteFieldEnd(); err != nil {
716 return thrift.PrependError(fmt.Sprintf("%T write field end error 1:a: ", p), err) }
717 return err
718 }
719
720 func (p *AddServiceSumArgs) writeField2(oprot thrift.TProtocol) (err error) {
721 if err := oprot.WriteFieldBegin("b", thrift.I64, 2); err != nil {
722 return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:b: ", p), err) }
723 if err := oprot.WriteI64(int64(p.B)); err != nil {
724 return thrift.PrependError(fmt.Sprintf("%T.b (2) field write error: ", p), err) }
725 if err := oprot.WriteFieldEnd(); err != nil {
726 return thrift.PrependError(fmt.Sprintf("%T write field end error 2:b: ", p), err) }
727 return err
728 }
729
730 func (p *AddServiceSumArgs) String() string {
731 if p == nil {
732 return "<nil>"
733 }
734 return fmt.Sprintf("AddServiceSumArgs(%+v)", *p)
735 }
736
737 // Attributes:
738 // - Success
739 type AddServiceSumResult struct {
740 Success *SumReply `thrift:"success,0" db:"success" json:"success,omitempty"`
741 }
742
743 func NewAddServiceSumResult() *AddServiceSumResult {
744 return &AddServiceSumResult{}
745 }
746
747 var AddServiceSumResult_Success_DEFAULT *SumReply
748 func (p *AddServiceSumResult) GetSuccess() *SumReply {
749 if !p.IsSetSuccess() {
750 return AddServiceSumResult_Success_DEFAULT
751 }
752 return p.Success
753 }
754 func (p *AddServiceSumResult) IsSetSuccess() bool {
755 return p.Success != nil
756 }
757
758 func (p *AddServiceSumResult) Read(iprot thrift.TProtocol) error {
759 if _, err := iprot.ReadStructBegin(); err != nil {
760 return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err)
761 }
762
763
764 for {
765 _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin()
766 if err != nil {
767 return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err)
768 }
769 if fieldTypeId == thrift.STOP { break; }
770 switch fieldId {
771 case 0:
772 if fieldTypeId == thrift.STRUCT {
773 if err := p.ReadField0(iprot); err != nil {
774 return err
775 }
776 } else {
777 if err := iprot.Skip(fieldTypeId); err != nil {
778 return err
779 }
780 }
781 default:
782 if err := iprot.Skip(fieldTypeId); err != nil {
783 return err
784 }
785 }
786 if err := iprot.ReadFieldEnd(); err != nil {
787 return err
788 }
789 }
790 if err := iprot.ReadStructEnd(); err != nil {
791 return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
792 }
793 return nil
794 }
795
796 func (p *AddServiceSumResult) ReadField0(iprot thrift.TProtocol) error {
797 p.Success = &SumReply{}
798 if err := p.Success.Read(iprot); err != nil {
799 return thrift.PrependError(fmt.Sprintf("%T error reading struct: ", p.Success), err)
800 }
801 return nil
802 }
803
804 func (p *AddServiceSumResult) Write(oprot thrift.TProtocol) error {
805 if err := oprot.WriteStructBegin("Sum_result"); err != nil {
806 return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) }
807 if p != nil {
808 if err := p.writeField0(oprot); err != nil { return err }
809 }
810 if err := oprot.WriteFieldStop(); err != nil {
811 return thrift.PrependError("write field stop error: ", err) }
812 if err := oprot.WriteStructEnd(); err != nil {
813 return thrift.PrependError("write struct stop error: ", err) }
814 return nil
815 }
816
817 func (p *AddServiceSumResult) writeField0(oprot thrift.TProtocol) (err error) {
818 if p.IsSetSuccess() {
819 if err := oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil {
820 return thrift.PrependError(fmt.Sprintf("%T write field begin error 0:success: ", p), err) }
821 if err := p.Success.Write(oprot); err != nil {
822 return thrift.PrependError(fmt.Sprintf("%T error writing struct: ", p.Success), err)
823 }
824 if err := oprot.WriteFieldEnd(); err != nil {
825 return thrift.PrependError(fmt.Sprintf("%T write field end error 0:success: ", p), err) }
826 }
827 return err
828 }
829
830 func (p *AddServiceSumResult) String() string {
831 if p == nil {
832 return "<nil>"
833 }
834 return fmt.Sprintf("AddServiceSumResult(%+v)", *p)
835 }
836
837 // Attributes:
838 // - A
839 // - B
840 type AddServiceConcatArgs struct {
841 A string `thrift:"a,1" db:"a" json:"a"`
842 B string `thrift:"b,2" db:"b" json:"b"`
843 }
844
845 func NewAddServiceConcatArgs() *AddServiceConcatArgs {
846 return &AddServiceConcatArgs{}
847 }
848
849
850 func (p *AddServiceConcatArgs) GetA() string {
851 return p.A
852 }
853
854 func (p *AddServiceConcatArgs) GetB() string {
855 return p.B
856 }
857 func (p *AddServiceConcatArgs) Read(iprot thrift.TProtocol) error {
858 if _, err := iprot.ReadStructBegin(); err != nil {
859 return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err)
860 }
861
862
863 for {
864 _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin()
865 if err != nil {
866 return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err)
867 }
868 if fieldTypeId == thrift.STOP { break; }
869 switch fieldId {
870 case 1:
871 if fieldTypeId == thrift.STRING {
872 if err := p.ReadField1(iprot); err != nil {
873 return err
874 }
875 } else {
876 if err := iprot.Skip(fieldTypeId); err != nil {
877 return err
878 }
879 }
880 case 2:
881 if fieldTypeId == thrift.STRING {
882 if err := p.ReadField2(iprot); err != nil {
883 return err
884 }
885 } else {
886 if err := iprot.Skip(fieldTypeId); err != nil {
887 return err
888 }
889 }
890 default:
891 if err := iprot.Skip(fieldTypeId); err != nil {
892 return err
893 }
894 }
895 if err := iprot.ReadFieldEnd(); err != nil {
896 return err
897 }
898 }
899 if err := iprot.ReadStructEnd(); err != nil {
900 return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
901 }
902 return nil
903 }
904
905 func (p *AddServiceConcatArgs) ReadField1(iprot thrift.TProtocol) error {
906 if v, err := iprot.ReadString(); err != nil {
907 return thrift.PrependError("error reading field 1: ", err)
908 } else {
909 p.A = v
910 }
911 return nil
912 }
913
914 func (p *AddServiceConcatArgs) ReadField2(iprot thrift.TProtocol) error {
915 if v, err := iprot.ReadString(); err != nil {
916 return thrift.PrependError("error reading field 2: ", err)
917 } else {
918 p.B = v
919 }
920 return nil
921 }
922
923 func (p *AddServiceConcatArgs) Write(oprot thrift.TProtocol) error {
924 if err := oprot.WriteStructBegin("Concat_args"); err != nil {
925 return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) }
926 if p != nil {
927 if err := p.writeField1(oprot); err != nil { return err }
928 if err := p.writeField2(oprot); err != nil { return err }
929 }
930 if err := oprot.WriteFieldStop(); err != nil {
931 return thrift.PrependError("write field stop error: ", err) }
932 if err := oprot.WriteStructEnd(); err != nil {
933 return thrift.PrependError("write struct stop error: ", err) }
934 return nil
935 }
936
937 func (p *AddServiceConcatArgs) writeField1(oprot thrift.TProtocol) (err error) {
938 if err := oprot.WriteFieldBegin("a", thrift.STRING, 1); err != nil {
939 return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:a: ", p), err) }
940 if err := oprot.WriteString(string(p.A)); err != nil {
941 return thrift.PrependError(fmt.Sprintf("%T.a (1) field write error: ", p), err) }
942 if err := oprot.WriteFieldEnd(); err != nil {
943 return thrift.PrependError(fmt.Sprintf("%T write field end error 1:a: ", p), err) }
944 return err
945 }
946
947 func (p *AddServiceConcatArgs) writeField2(oprot thrift.TProtocol) (err error) {
948 if err := oprot.WriteFieldBegin("b", thrift.STRING, 2); err != nil {
949 return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:b: ", p), err) }
950 if err := oprot.WriteString(string(p.B)); err != nil {
951 return thrift.PrependError(fmt.Sprintf("%T.b (2) field write error: ", p), err) }
952 if err := oprot.WriteFieldEnd(); err != nil {
953 return thrift.PrependError(fmt.Sprintf("%T write field end error 2:b: ", p), err) }
954 return err
955 }
956
957 func (p *AddServiceConcatArgs) String() string {
958 if p == nil {
959 return "<nil>"
960 }
961 return fmt.Sprintf("AddServiceConcatArgs(%+v)", *p)
962 }
963
964 // Attributes:
965 // - Success
966 type AddServiceConcatResult struct {
967 Success *ConcatReply `thrift:"success,0" db:"success" json:"success,omitempty"`
968 }
969
970 func NewAddServiceConcatResult() *AddServiceConcatResult {
971 return &AddServiceConcatResult{}
972 }
973
974 var AddServiceConcatResult_Success_DEFAULT *ConcatReply
975 func (p *AddServiceConcatResult) GetSuccess() *ConcatReply {
976 if !p.IsSetSuccess() {
977 return AddServiceConcatResult_Success_DEFAULT
978 }
979 return p.Success
980 }
981 func (p *AddServiceConcatResult) IsSetSuccess() bool {
982 return p.Success != nil
983 }
984
985 func (p *AddServiceConcatResult) Read(iprot thrift.TProtocol) error {
986 if _, err := iprot.ReadStructBegin(); err != nil {
987 return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err)
988 }
989
990
991 for {
992 _, fieldTypeId, fieldId, err := iprot.ReadFieldBegin()
993 if err != nil {
994 return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err)
995 }
996 if fieldTypeId == thrift.STOP { break; }
997 switch fieldId {
998 case 0:
999 if fieldTypeId == thrift.STRUCT {
1000 if err := p.ReadField0(iprot); err != nil {
1001 return err
1002 }
1003 } else {
1004 if err := iprot.Skip(fieldTypeId); err != nil {
1005 return err
1006 }
1007 }
1008 default:
1009 if err := iprot.Skip(fieldTypeId); err != nil {
1010 return err
1011 }
1012 }
1013 if err := iprot.ReadFieldEnd(); err != nil {
1014 return err
1015 }
1016 }
1017 if err := iprot.ReadStructEnd(); err != nil {
1018 return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
1019 }
1020 return nil
1021 }
1022
1023 func (p *AddServiceConcatResult) ReadField0(iprot thrift.TProtocol) error {
1024 p.Success = &ConcatReply{}
1025 if err := p.Success.Read(iprot); err != nil {
1026 return thrift.PrependError(fmt.Sprintf("%T error reading struct: ", p.Success), err)
1027 }
1028 return nil
1029 }
1030
1031 func (p *AddServiceConcatResult) Write(oprot thrift.TProtocol) error {
1032 if err := oprot.WriteStructBegin("Concat_result"); err != nil {
1033 return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) }
1034 if p != nil {
1035 if err := p.writeField0(oprot); err != nil { return err }
1036 }
1037 if err := oprot.WriteFieldStop(); err != nil {
1038 return thrift.PrependError("write field stop error: ", err) }
1039 if err := oprot.WriteStructEnd(); err != nil {
1040 return thrift.PrependError("write struct stop error: ", err) }
1041 return nil
1042 }
1043
1044 func (p *AddServiceConcatResult) writeField0(oprot thrift.TProtocol) (err error) {
1045 if p.IsSetSuccess() {
1046 if err := oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil {
1047 return thrift.PrependError(fmt.Sprintf("%T write field begin error 0:success: ", p), err) }
1048 if err := p.Success.Write(oprot); err != nil {
1049 return thrift.PrependError(fmt.Sprintf("%T error writing struct: ", p.Success), err)
1050 }
1051 if err := oprot.WriteFieldEnd(); err != nil {
1052 return thrift.PrependError(fmt.Sprintf("%T write field end error 0:success: ", p), err) }
1053 }
1054 return err
1055 }
1056
1057 func (p *AddServiceConcatResult) String() string {
1058 if p == nil {
1059 return "<nil>"
1060 }
1061 return fmt.Sprintf("AddServiceConcatResult(%+v)", *p)
1062 }
1063
1064
+0
-285
examples/apigateway/main.go less more
0 package main
1
2 import (
3 "bytes"
4 "context"
5 "encoding/json"
6 "flag"
7 "fmt"
8 "io"
9 "io/ioutil"
10 "net/http"
11 "net/url"
12 "os"
13 "os/signal"
14 "strings"
15 "syscall"
16 "time"
17
18 consulsd "github.com/go-kit/kit/sd/consul"
19 "github.com/gorilla/mux"
20 "github.com/hashicorp/consul/api"
21 stdopentracing "github.com/opentracing/opentracing-go"
22 "google.golang.org/grpc"
23
24 "github.com/go-kit/kit/endpoint"
25 "github.com/go-kit/kit/log"
26 "github.com/go-kit/kit/sd"
27 "github.com/go-kit/kit/sd/lb"
28 httptransport "github.com/go-kit/kit/transport/http"
29
30 "github.com/go-kit/kit/examples/addsvc/pkg/addendpoint"
31 "github.com/go-kit/kit/examples/addsvc/pkg/addservice"
32 "github.com/go-kit/kit/examples/addsvc/pkg/addtransport"
33 )
34
35 func main() {
36 var (
37 httpAddr = flag.String("http.addr", ":8000", "Address for HTTP (JSON) server")
38 consulAddr = flag.String("consul.addr", "", "Consul agent address")
39 retryMax = flag.Int("retry.max", 3, "per-request retries to different instances")
40 retryTimeout = flag.Duration("retry.timeout", 500*time.Millisecond, "per-request timeout, including retries")
41 )
42 flag.Parse()
43
44 // Logging domain.
45 var logger log.Logger
46 {
47 logger = log.NewLogfmtLogger(os.Stderr)
48 logger = log.With(logger, "ts", log.DefaultTimestampUTC)
49 logger = log.With(logger, "caller", log.DefaultCaller)
50 }
51
52 // Service discovery domain. In this example we use Consul.
53 var client consulsd.Client
54 {
55 consulConfig := api.DefaultConfig()
56 if len(*consulAddr) > 0 {
57 consulConfig.Address = *consulAddr
58 }
59 consulClient, err := api.NewClient(consulConfig)
60 if err != nil {
61 logger.Log("err", err)
62 os.Exit(1)
63 }
64 client = consulsd.NewClient(consulClient)
65 }
66
67 // Transport domain.
68 tracer := stdopentracing.GlobalTracer() // no-op
69 ctx := context.Background()
70 r := mux.NewRouter()
71
72 // Now we begin installing the routes. Each route corresponds to a single
73 // method: sum, concat, uppercase, and count.
74
75 // addsvc routes.
76 {
77 // Each method gets constructed with a factory. Factories take an
78 // instance string, and return a specific endpoint. In the factory we
79 // dial the instance string we get from Consul, and then leverage an
80 // addsvc client package to construct a complete service. We can then
81 // leverage the addsvc.Make{Sum,Concat}Endpoint constructors to convert
82 // the complete service to specific endpoint.
83 var (
84 tags = []string{}
85 passingOnly = true
86 endpoints = addendpoint.Set{}
87 instancer = consulsd.NewInstancer(client, logger, "addsvc", tags, passingOnly)
88 )
89 {
90 factory := addsvcFactory(addendpoint.MakeSumEndpoint, tracer, logger)
91 endpointer := sd.NewEndpointer(instancer, factory, logger)
92 balancer := lb.NewRoundRobin(endpointer)
93 retry := lb.Retry(*retryMax, *retryTimeout, balancer)
94 endpoints.SumEndpoint = retry
95 }
96 {
97 factory := addsvcFactory(addendpoint.MakeConcatEndpoint, tracer, logger)
98 endpointer := sd.NewEndpointer(instancer, factory, logger)
99 balancer := lb.NewRoundRobin(endpointer)
100 retry := lb.Retry(*retryMax, *retryTimeout, balancer)
101 endpoints.ConcatEndpoint = retry
102 }
103
104 // Here we leverage the fact that addsvc comes with a constructor for an
105 // HTTP handler, and just install it under a particular path prefix in
106 // our router.
107
108 r.PathPrefix("/addsvc").Handler(http.StripPrefix("/addsvc", addtransport.NewHTTPHandler(endpoints, tracer, logger)))
109 }
110
111 // stringsvc routes.
112 {
113 // addsvc had lots of nice importable Go packages we could leverage.
114 // With stringsvc we are not so fortunate, it just has some endpoints
115 // that we assume will exist. So we have to write that logic here. This
116 // is by design, so you can see two totally different methods of
117 // proxying to a remote service.
118
119 var (
120 tags = []string{}
121 passingOnly = true
122 uppercase endpoint.Endpoint
123 count endpoint.Endpoint
124 instancer = consulsd.NewInstancer(client, logger, "stringsvc", tags, passingOnly)
125 )
126 {
127 factory := stringsvcFactory(ctx, "GET", "/uppercase")
128 endpointer := sd.NewEndpointer(instancer, factory, logger)
129 balancer := lb.NewRoundRobin(endpointer)
130 retry := lb.Retry(*retryMax, *retryTimeout, balancer)
131 uppercase = retry
132 }
133 {
134 factory := stringsvcFactory(ctx, "GET", "/count")
135 endpointer := sd.NewEndpointer(instancer, factory, logger)
136 balancer := lb.NewRoundRobin(endpointer)
137 retry := lb.Retry(*retryMax, *retryTimeout, balancer)
138 count = retry
139 }
140
141 // We can use the transport/http.Server to act as our handler, all we
142 // have to do provide it with the encode and decode functions for our
143 // stringsvc methods.
144
145 r.Handle("/stringsvc/uppercase", httptransport.NewServer(uppercase, decodeUppercaseRequest, encodeJSONResponse))
146 r.Handle("/stringsvc/count", httptransport.NewServer(count, decodeCountRequest, encodeJSONResponse))
147 }
148
149 // Interrupt handler.
150 errc := make(chan error)
151 go func() {
152 c := make(chan os.Signal)
153 signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
154 errc <- fmt.Errorf("%s", <-c)
155 }()
156
157 // HTTP transport.
158 go func() {
159 logger.Log("transport", "HTTP", "addr", *httpAddr)
160 errc <- http.ListenAndServe(*httpAddr, r)
161 }()
162
163 // Run!
164 logger.Log("exit", <-errc)
165 }
166
167 func addsvcFactory(makeEndpoint func(addservice.Service) endpoint.Endpoint, tracer stdopentracing.Tracer, logger log.Logger) sd.Factory {
168 return func(instance string) (endpoint.Endpoint, io.Closer, error) {
169 // We could just as easily use the HTTP or Thrift client package to make
170 // the connection to addsvc. We've chosen gRPC arbitrarily. Note that
171 // the transport is an implementation detail: it doesn't leak out of
172 // this function. Nice!
173
174 conn, err := grpc.Dial(instance, grpc.WithInsecure())
175 if err != nil {
176 return nil, nil, err
177 }
178 service := addtransport.NewGRPCClient(conn, tracer, logger)
179 endpoint := makeEndpoint(service)
180
181 // Notice that the addsvc gRPC client converts the connection to a
182 // complete addsvc, and we just throw away everything except the method
183 // we're interested in. A smarter factory would mux multiple methods
184 // over the same connection. But that would require more work to manage
185 // the returned io.Closer, e.g. reference counting. Since this is for
186 // the purposes of demonstration, we'll just keep it simple.
187
188 return endpoint, conn, nil
189 }
190 }
191
192 func stringsvcFactory(ctx context.Context, method, path string) sd.Factory {
193 return func(instance string) (endpoint.Endpoint, io.Closer, error) {
194 if !strings.HasPrefix(instance, "http") {
195 instance = "http://" + instance
196 }
197 tgt, err := url.Parse(instance)
198 if err != nil {
199 return nil, nil, err
200 }
201 tgt.Path = path
202
203 // Since stringsvc doesn't have any kind of package we can import, or
204 // any formal spec, we are forced to just assert where the endpoints
205 // live, and write our own code to encode and decode requests and
206 // responses. Ideally, if you write the service, you will want to
207 // provide stronger guarantees to your clients.
208
209 var (
210 enc httptransport.EncodeRequestFunc
211 dec httptransport.DecodeResponseFunc
212 )
213 switch path {
214 case "/uppercase":
215 enc, dec = encodeJSONRequest, decodeUppercaseResponse
216 case "/count":
217 enc, dec = encodeJSONRequest, decodeCountResponse
218 default:
219 return nil, nil, fmt.Errorf("unknown stringsvc path %q", path)
220 }
221
222 return httptransport.NewClient(method, tgt, enc, dec).Endpoint(), nil, nil
223 }
224 }
225
226 func encodeJSONRequest(_ context.Context, req *http.Request, request interface{}) error {
227 // Both uppercase and count requests are encoded in the same way:
228 // simple JSON serialization to the request body.
229 var buf bytes.Buffer
230 if err := json.NewEncoder(&buf).Encode(request); err != nil {
231 return err
232 }
233 req.Body = ioutil.NopCloser(&buf)
234 return nil
235 }
236
237 func encodeJSONResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
238 w.Header().Set("Content-Type", "application/json; charset=utf-8")
239 return json.NewEncoder(w).Encode(response)
240 }
241
242 // I've just copied these functions from stringsvc3/transport.go, inlining the
243 // struct definitions.
244
245 func decodeUppercaseResponse(ctx context.Context, resp *http.Response) (interface{}, error) {
246 var response struct {
247 V string `json:"v"`
248 Err string `json:"err,omitempty"`
249 }
250 if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
251 return nil, err
252 }
253 return response, nil
254 }
255
256 func decodeCountResponse(ctx context.Context, resp *http.Response) (interface{}, error) {
257 var response struct {
258 V int `json:"v"`
259 }
260 if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
261 return nil, err
262 }
263 return response, nil
264 }
265
266 func decodeUppercaseRequest(ctx context.Context, req *http.Request) (interface{}, error) {
267 var request struct {
268 S string `json:"s"`
269 }
270 if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
271 return nil, err
272 }
273 return request, nil
274 }
275
276 func decodeCountRequest(ctx context.Context, req *http.Request) (interface{}, error) {
277 var request struct {
278 S string `json:"s"`
279 }
280 if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
281 return nil, err
282 }
283 return request, nil
284 }
+0
-4
examples/profilesvc/README.md less more
0 # profilesvc
1
2 This example demonstrates how to use Go kit to implement a REST-y HTTP service.
3 It leverages the excellent [gorilla mux package](https://github.com/gorilla/mux) for routing.
+0
-121
examples/profilesvc/client/client.go less more
0 // Package client provides a profilesvc client based on a predefined Consul
1 // service name and relevant tags. Users must only provide the address of a
2 // Consul server.
3 package client
4
5 import (
6 "io"
7 "time"
8
9 consulapi "github.com/hashicorp/consul/api"
10
11 "github.com/go-kit/kit/endpoint"
12 "github.com/go-kit/kit/examples/profilesvc"
13 "github.com/go-kit/kit/log"
14 "github.com/go-kit/kit/sd"
15 "github.com/go-kit/kit/sd/consul"
16 "github.com/go-kit/kit/sd/lb"
17 )
18
19 // New returns a service that's load-balanced over instances of profilesvc found
20 // in the provided Consul server. The mechanism of looking up profilesvc
21 // instances in Consul is hard-coded into the client.
22 func New(consulAddr string, logger log.Logger) (profilesvc.Service, error) {
23 apiclient, err := consulapi.NewClient(&consulapi.Config{
24 Address: consulAddr,
25 })
26 if err != nil {
27 return nil, err
28 }
29
30 // As the implementer of profilesvc, we declare and enforce these
31 // parameters for all of the profilesvc consumers.
32 var (
33 consulService = "profilesvc"
34 consulTags = []string{"prod"}
35 passingOnly = true
36 retryMax = 3
37 retryTimeout = 500 * time.Millisecond
38 )
39
40 var (
41 sdclient = consul.NewClient(apiclient)
42 instancer = consul.NewInstancer(sdclient, logger, consulService, consulTags, passingOnly)
43 endpoints profilesvc.Endpoints
44 )
45 {
46 factory := factoryFor(profilesvc.MakePostProfileEndpoint)
47 endpointer := sd.NewEndpointer(instancer, factory, logger)
48 balancer := lb.NewRoundRobin(endpointer)
49 retry := lb.Retry(retryMax, retryTimeout, balancer)
50 endpoints.PostProfileEndpoint = retry
51 }
52 {
53 factory := factoryFor(profilesvc.MakeGetProfileEndpoint)
54 endpointer := sd.NewEndpointer(instancer, factory, logger)
55 balancer := lb.NewRoundRobin(endpointer)
56 retry := lb.Retry(retryMax, retryTimeout, balancer)
57 endpoints.GetProfileEndpoint = retry
58 }
59 {
60 factory := factoryFor(profilesvc.MakePutProfileEndpoint)
61 endpointer := sd.NewEndpointer(instancer, factory, logger)
62 balancer := lb.NewRoundRobin(endpointer)
63 retry := lb.Retry(retryMax, retryTimeout, balancer)
64 endpoints.PutProfileEndpoint = retry
65 }
66 {
67 factory := factoryFor(profilesvc.MakePatchProfileEndpoint)
68 endpointer := sd.NewEndpointer(instancer, factory, logger)
69 balancer := lb.NewRoundRobin(endpointer)
70 retry := lb.Retry(retryMax, retryTimeout, balancer)
71 endpoints.PatchProfileEndpoint = retry
72 }
73 {
74 factory := factoryFor(profilesvc.MakeDeleteProfileEndpoint)
75 endpointer := sd.NewEndpointer(instancer, factory, logger)
76 balancer := lb.NewRoundRobin(endpointer)
77 retry := lb.Retry(retryMax, retryTimeout, balancer)
78 endpoints.DeleteProfileEndpoint = retry
79 }
80 {
81 factory := factoryFor(profilesvc.MakeGetAddressesEndpoint)
82 endpointer := sd.NewEndpointer(instancer, factory, logger)
83 balancer := lb.NewRoundRobin(endpointer)
84 retry := lb.Retry(retryMax, retryTimeout, balancer)
85 endpoints.GetAddressesEndpoint = retry
86 }
87 {
88 factory := factoryFor(profilesvc.MakeGetAddressEndpoint)
89 endpointer := sd.NewEndpointer(instancer, factory, logger)
90 balancer := lb.NewRoundRobin(endpointer)
91 retry := lb.Retry(retryMax, retryTimeout, balancer)
92 endpoints.GetAddressEndpoint = retry
93 }
94 {
95 factory := factoryFor(profilesvc.MakePostAddressEndpoint)
96 endpointer := sd.NewEndpointer(instancer, factory, logger)
97 balancer := lb.NewRoundRobin(endpointer)
98 retry := lb.Retry(retryMax, retryTimeout, balancer)
99 endpoints.PostAddressEndpoint = retry
100 }
101 {
102 factory := factoryFor(profilesvc.MakeDeleteAddressEndpoint)
103 endpointer := sd.NewEndpointer(instancer, factory, logger)
104 balancer := lb.NewRoundRobin(endpointer)
105 retry := lb.Retry(retryMax, retryTimeout, balancer)
106 endpoints.DeleteAddressEndpoint = retry
107 }
108
109 return endpoints, nil
110 }
111
112 func factoryFor(makeEndpoint func(profilesvc.Service) endpoint.Endpoint) sd.Factory {
113 return func(instance string) (endpoint.Endpoint, io.Closer, error) {
114 service, err := profilesvc.MakeClientEndpoints(instance)
115 if err != nil {
116 return nil, nil, err
117 }
118 return makeEndpoint(service), nil, nil
119 }
120 }
+0
-52
examples/profilesvc/cmd/profilesvc/main.go less more
0 package main
1
2 import (
3 "flag"
4 "fmt"
5 "net/http"
6 "os"
7 "os/signal"
8 "syscall"
9
10 "github.com/go-kit/kit/examples/profilesvc"
11 "github.com/go-kit/kit/log"
12 )
13
14 func main() {
15 var (
16 httpAddr = flag.String("http.addr", ":8080", "HTTP listen address")
17 )
18 flag.Parse()
19
20 var logger log.Logger
21 {
22 logger = log.NewLogfmtLogger(os.Stderr)
23 logger = log.With(logger, "ts", log.DefaultTimestampUTC)
24 logger = log.With(logger, "caller", log.DefaultCaller)
25 }
26
27 var s profilesvc.Service
28 {
29 s = profilesvc.NewInmemService()
30 s = profilesvc.LoggingMiddleware(logger)(s)
31 }
32
33 var h http.Handler
34 {
35 h = profilesvc.MakeHTTPHandler(s, log.With(logger, "component", "HTTP"))
36 }
37
38 errs := make(chan error)
39 go func() {
40 c := make(chan os.Signal)
41 signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
42 errs <- fmt.Errorf("%s", <-c)
43 }()
44
45 go func() {
46 logger.Log("transport", "HTTP", "addr", *httpAddr)
47 errs <- http.ListenAndServe(*httpAddr, h)
48 }()
49
50 logger.Log("exit", <-errs)
51 }
+0
-387
examples/profilesvc/endpoints.go less more
0 package profilesvc
1
2 import (
3 "context"
4 "net/url"
5 "strings"
6
7 "github.com/go-kit/kit/endpoint"
8 httptransport "github.com/go-kit/kit/transport/http"
9 )
10
11 // Endpoints collects all of the endpoints that compose a profile service. It's
12 // meant to be used as a helper struct, to collect all of the endpoints into a
13 // single parameter.
14 //
15 // In a server, it's useful for functions that need to operate on a per-endpoint
16 // basis. For example, you might pass an Endpoints to a function that produces
17 // an http.Handler, with each method (endpoint) wired up to a specific path. (It
18 // is probably a mistake in design to invoke the Service methods on the
19 // Endpoints struct in a server.)
20 //
21 // In a client, it's useful to collect individually constructed endpoints into a
22 // single type that implements the Service interface. For example, you might
23 // construct individual endpoints using transport/http.NewClient, combine them
24 // into an Endpoints, and return it to the caller as a Service.
25 type Endpoints struct {
26 PostProfileEndpoint endpoint.Endpoint
27 GetProfileEndpoint endpoint.Endpoint
28 PutProfileEndpoint endpoint.Endpoint
29 PatchProfileEndpoint endpoint.Endpoint
30 DeleteProfileEndpoint endpoint.Endpoint
31 GetAddressesEndpoint endpoint.Endpoint
32 GetAddressEndpoint endpoint.Endpoint
33 PostAddressEndpoint endpoint.Endpoint
34 DeleteAddressEndpoint endpoint.Endpoint
35 }
36
37 // MakeServerEndpoints returns an Endpoints struct where each endpoint invokes
38 // the corresponding method on the provided service. Useful in a profilesvc
39 // server.
40 func MakeServerEndpoints(s Service) Endpoints {
41 return Endpoints{
42 PostProfileEndpoint: MakePostProfileEndpoint(s),
43 GetProfileEndpoint: MakeGetProfileEndpoint(s),
44 PutProfileEndpoint: MakePutProfileEndpoint(s),
45 PatchProfileEndpoint: MakePatchProfileEndpoint(s),
46 DeleteProfileEndpoint: MakeDeleteProfileEndpoint(s),
47 GetAddressesEndpoint: MakeGetAddressesEndpoint(s),
48 GetAddressEndpoint: MakeGetAddressEndpoint(s),
49 PostAddressEndpoint: MakePostAddressEndpoint(s),
50 DeleteAddressEndpoint: MakeDeleteAddressEndpoint(s),
51 }
52 }
53
54 // MakeClientEndpoints returns an Endpoints struct where each endpoint invokes
55 // the corresponding method on the remote instance, via a transport/http.Client.
56 // Useful in a profilesvc client.
57 func MakeClientEndpoints(instance string) (Endpoints, error) {
58 if !strings.HasPrefix(instance, "http") {
59 instance = "http://" + instance
60 }
61 tgt, err := url.Parse(instance)
62 if err != nil {
63 return Endpoints{}, err
64 }
65 tgt.Path = ""
66
67 options := []httptransport.ClientOption{}
68
69 // Note that the request encoders need to modify the request URL, changing
70 // the path and method. That's fine: we simply need to provide specific
71 // encoders for each endpoint.
72
73 return Endpoints{
74 PostProfileEndpoint: httptransport.NewClient("POST", tgt, encodePostProfileRequest, decodePostProfileResponse, options...).Endpoint(),
75 GetProfileEndpoint: httptransport.NewClient("GET", tgt, encodeGetProfileRequest, decodeGetProfileResponse, options...).Endpoint(),
76 PutProfileEndpoint: httptransport.NewClient("PUT", tgt, encodePutProfileRequest, decodePutProfileResponse, options...).Endpoint(),
77 PatchProfileEndpoint: httptransport.NewClient("PATCH", tgt, encodePatchProfileRequest, decodePatchProfileResponse, options...).Endpoint(),
78 DeleteProfileEndpoint: httptransport.NewClient("DELETE", tgt, encodeDeleteProfileRequest, decodeDeleteProfileResponse, options...).Endpoint(),
79 GetAddressesEndpoint: httptransport.NewClient("GET", tgt, encodeGetAddressesRequest, decodeGetAddressesResponse, options...).Endpoint(),
80 GetAddressEndpoint: httptransport.NewClient("GET", tgt, encodeGetAddressRequest, decodeGetAddressResponse, options...).Endpoint(),
81 PostAddressEndpoint: httptransport.NewClient("POST", tgt, encodePostAddressRequest, decodePostAddressResponse, options...).Endpoint(),
82 DeleteAddressEndpoint: httptransport.NewClient("DELETE", tgt, encodeDeleteAddressRequest, decodeDeleteAddressResponse, options...).Endpoint(),
83 }, nil
84 }
85
86 // PostProfile implements Service. Primarily useful in a client.
87 func (e Endpoints) PostProfile(ctx context.Context, p Profile) error {
88 request := postProfileRequest{Profile: p}
89 response, err := e.PostProfileEndpoint(ctx, request)
90 if err != nil {
91 return err
92 }
93 resp := response.(postProfileResponse)
94 return resp.Err
95 }
96
97 // GetProfile implements Service. Primarily useful in a client.
98 func (e Endpoints) GetProfile(ctx context.Context, id string) (Profile, error) {
99 request := getProfileRequest{ID: id}
100 response, err := e.GetProfileEndpoint(ctx, request)
101 if err != nil {
102 return Profile{}, err
103 }
104 resp := response.(getProfileResponse)
105 return resp.Profile, resp.Err
106 }
107
108 // PutProfile implements Service. Primarily useful in a client.
109 func (e Endpoints) PutProfile(ctx context.Context, id string, p Profile) error {
110 request := putProfileRequest{ID: id, Profile: p}
111 response, err := e.PutProfileEndpoint(ctx, request)
112 if err != nil {
113 return err
114 }
115 resp := response.(putProfileResponse)
116 return resp.Err
117 }
118
119 // PatchProfile implements Service. Primarily useful in a client.
120 func (e Endpoints) PatchProfile(ctx context.Context, id string, p Profile) error {
121 request := patchProfileRequest{ID: id, Profile: p}
122 response, err := e.PatchProfileEndpoint(ctx, request)
123 if err != nil {
124 return err
125 }
126 resp := response.(patchProfileResponse)
127 return resp.Err
128 }
129
130 // DeleteProfile implements Service. Primarily useful in a client.
131 func (e Endpoints) DeleteProfile(ctx context.Context, id string) error {
132 request := deleteProfileRequest{ID: id}
133 response, err := e.DeleteProfileEndpoint(ctx, request)
134 if err != nil {
135 return err
136 }
137 resp := response.(deleteProfileResponse)
138 return resp.Err
139 }
140
141 // GetAddresses implements Service. Primarily useful in a client.
142 func (e Endpoints) GetAddresses(ctx context.Context, profileID string) ([]Address, error) {
143 request := getAddressesRequest{ProfileID: profileID}
144 response, err := e.GetAddressesEndpoint(ctx, request)
145 if err != nil {
146 return nil, err
147 }
148 resp := response.(getAddressesResponse)
149 return resp.Addresses, resp.Err
150 }
151
152 // GetAddress implements Service. Primarily useful in a client.
153 func (e Endpoints) GetAddress(ctx context.Context, profileID string, addressID string) (Address, error) {
154 request := getAddressRequest{ProfileID: profileID, AddressID: addressID}
155 response, err := e.GetAddressEndpoint(ctx, request)
156 if err != nil {
157 return Address{}, err
158 }
159 resp := response.(getAddressResponse)
160 return resp.Address, resp.Err
161 }
162
163 // PostAddress implements Service. Primarily useful in a client.
164 func (e Endpoints) PostAddress(ctx context.Context, profileID string, a Address) error {
165 request := postAddressRequest{ProfileID: profileID, Address: a}
166 response, err := e.PostAddressEndpoint(ctx, request)
167 if err != nil {
168 return err
169 }
170 resp := response.(postAddressResponse)
171 return resp.Err
172 }
173
174 // DeleteAddress implements Service. Primarily useful in a client.
175 func (e Endpoints) DeleteAddress(ctx context.Context, profileID string, addressID string) error {
176 request := deleteAddressRequest{ProfileID: profileID, AddressID: addressID}
177 response, err := e.DeleteAddressEndpoint(ctx, request)
178 if err != nil {
179 return err
180 }
181 resp := response.(deleteAddressResponse)
182 return resp.Err
183 }
184
185 // MakePostProfileEndpoint returns an endpoint via the passed service.
186 // Primarily useful in a server.
187 func MakePostProfileEndpoint(s Service) endpoint.Endpoint {
188 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
189 req := request.(postProfileRequest)
190 e := s.PostProfile(ctx, req.Profile)
191 return postProfileResponse{Err: e}, nil
192 }
193 }
194
195 // MakeGetProfileEndpoint returns an endpoint via the passed service.
196 // Primarily useful in a server.
197 func MakeGetProfileEndpoint(s Service) endpoint.Endpoint {
198 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
199 req := request.(getProfileRequest)
200 p, e := s.GetProfile(ctx, req.ID)
201 return getProfileResponse{Profile: p, Err: e}, nil
202 }
203 }
204
205 // MakePutProfileEndpoint returns an endpoint via the passed service.
206 // Primarily useful in a server.
207 func MakePutProfileEndpoint(s Service) endpoint.Endpoint {
208 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
209 req := request.(putProfileRequest)
210 e := s.PutProfile(ctx, req.ID, req.Profile)
211 return putProfileResponse{Err: e}, nil
212 }
213 }
214
215 // MakePatchProfileEndpoint returns an endpoint via the passed service.
216 // Primarily useful in a server.
217 func MakePatchProfileEndpoint(s Service) endpoint.Endpoint {
218 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
219 req := request.(patchProfileRequest)
220 e := s.PatchProfile(ctx, req.ID, req.Profile)
221 return patchProfileResponse{Err: e}, nil
222 }
223 }
224
225 // MakeDeleteProfileEndpoint returns an endpoint via the passed service.
226 // Primarily useful in a server.
227 func MakeDeleteProfileEndpoint(s Service) endpoint.Endpoint {
228 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
229 req := request.(deleteProfileRequest)
230 e := s.DeleteProfile(ctx, req.ID)
231 return deleteProfileResponse{Err: e}, nil
232 }
233 }
234
235 // MakeGetAddressesEndpoint returns an endpoint via the passed service.
236 // Primarily useful in a server.
237 func MakeGetAddressesEndpoint(s Service) endpoint.Endpoint {
238 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
239 req := request.(getAddressesRequest)
240 a, e := s.GetAddresses(ctx, req.ProfileID)
241 return getAddressesResponse{Addresses: a, Err: e}, nil
242 }
243 }
244
245 // MakeGetAddressEndpoint returns an endpoint via the passed service.
246 // Primarily useful in a server.
247 func MakeGetAddressEndpoint(s Service) endpoint.Endpoint {
248 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
249 req := request.(getAddressRequest)
250 a, e := s.GetAddress(ctx, req.ProfileID, req.AddressID)
251 return getAddressResponse{Address: a, Err: e}, nil
252 }
253 }
254
255 // MakePostAddressEndpoint returns an endpoint via the passed service.
256 // Primarily useful in a server.
257 func MakePostAddressEndpoint(s Service) endpoint.Endpoint {
258 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
259 req := request.(postAddressRequest)
260 e := s.PostAddress(ctx, req.ProfileID, req.Address)
261 return postAddressResponse{Err: e}, nil
262 }
263 }
264
265 // MakeDeleteAddressEndpoint returns an endpoint via the passed service.
266 // Primarily useful in a server.
267 func MakeDeleteAddressEndpoint(s Service) endpoint.Endpoint {
268 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
269 req := request.(deleteAddressRequest)
270 e := s.DeleteAddress(ctx, req.ProfileID, req.AddressID)
271 return deleteAddressResponse{Err: e}, nil
272 }
273 }
274
275 // We have two options to return errors from the business logic.
276 //
277 // We could return the error via the endpoint itself. That makes certain things
278 // a little bit easier, like providing non-200 HTTP responses to the client. But
279 // Go kit assumes that endpoint errors are (or may be treated as)
280 // transport-domain errors. For example, an endpoint error will count against a
281 // circuit breaker error count.
282 //
283 // Therefore, it's often better to return service (business logic) errors in the
284 // response object. This means we have to do a bit more work in the HTTP
285 // response encoder to detect e.g. a not-found error and provide a proper HTTP
286 // status code. That work is done with the errorer interface, in transport.go.
287 // Response types that may contain business-logic errors implement that
288 // interface.
289
290 type postProfileRequest struct {
291 Profile Profile
292 }
293
294 type postProfileResponse struct {
295 Err error `json:"err,omitempty"`
296 }
297
298 func (r postProfileResponse) error() error { return r.Err }
299
300 type getProfileRequest struct {
301 ID string
302 }
303
304 type getProfileResponse struct {
305 Profile Profile `json:"profile,omitempty"`
306 Err error `json:"err,omitempty"`
307 }
308
309 func (r getProfileResponse) error() error { return r.Err }
310
311 type putProfileRequest struct {
312 ID string
313 Profile Profile
314 }
315
316 type putProfileResponse struct {
317 Err error `json:"err,omitempty"`
318 }
319
320 func (r putProfileResponse) error() error { return nil }
321
322 type patchProfileRequest struct {
323 ID string
324 Profile Profile
325 }
326
327 type patchProfileResponse struct {
328 Err error `json:"err,omitempty"`
329 }
330
331 func (r patchProfileResponse) error() error { return r.Err }
332
333 type deleteProfileRequest struct {
334 ID string
335 }
336
337 type deleteProfileResponse struct {
338 Err error `json:"err,omitempty"`
339 }
340
341 func (r deleteProfileResponse) error() error { return r.Err }
342
343 type getAddressesRequest struct {
344 ProfileID string
345 }
346
347 type getAddressesResponse struct {
348 Addresses []Address `json:"addresses,omitempty"`
349 Err error `json:"err,omitempty"`
350 }
351
352 func (r getAddressesResponse) error() error { return r.Err }
353
354 type getAddressRequest struct {
355 ProfileID string
356 AddressID string
357 }
358
359 type getAddressResponse struct {
360 Address Address `json:"address,omitempty"`
361 Err error `json:"err,omitempty"`
362 }
363
364 func (r getAddressResponse) error() error { return r.Err }
365
366 type postAddressRequest struct {
367 ProfileID string
368 Address Address
369 }
370
371 type postAddressResponse struct {
372 Err error `json:"err,omitempty"`
373 }
374
375 func (r postAddressResponse) error() error { return r.Err }
376
377 type deleteAddressRequest struct {
378 ProfileID string
379 AddressID string
380 }
381
382 type deleteAddressResponse struct {
383 Err error `json:"err,omitempty"`
384 }
385
386 func (r deleteAddressResponse) error() error { return r.Err }
+0
-88
examples/profilesvc/middlewares.go less more
0 package profilesvc
1
2 import (
3 "context"
4 "time"
5
6 "github.com/go-kit/kit/log"
7 )
8
9 // Middleware describes a service (as opposed to endpoint) middleware.
10 type Middleware func(Service) Service
11
12 func LoggingMiddleware(logger log.Logger) Middleware {
13 return func(next Service) Service {
14 return &loggingMiddleware{
15 next: next,
16 logger: logger,
17 }
18 }
19 }
20
21 type loggingMiddleware struct {
22 next Service
23 logger log.Logger
24 }
25
26 func (mw loggingMiddleware) PostProfile(ctx context.Context, p Profile) (err error) {
27 defer func(begin time.Time) {
28 mw.logger.Log("method", "PostProfile", "id", p.ID, "took", time.Since(begin), "err", err)
29 }(time.Now())
30 return mw.next.PostProfile(ctx, p)
31 }
32
33 func (mw loggingMiddleware) GetProfile(ctx context.Context, id string) (p Profile, err error) {
34 defer func(begin time.Time) {
35 mw.logger.Log("method", "GetProfile", "id", id, "took", time.Since(begin), "err", err)
36 }(time.Now())
37 return mw.next.GetProfile(ctx, id)
38 }
39
40 func (mw loggingMiddleware) PutProfile(ctx context.Context, id string, p Profile) (err error) {
41 defer func(begin time.Time) {
42 mw.logger.Log("method", "PutProfile", "id", id, "took", time.Since(begin), "err", err)
43 }(time.Now())
44 return mw.next.PutProfile(ctx, id, p)
45 }
46
47 func (mw loggingMiddleware) PatchProfile(ctx context.Context, id string, p Profile) (err error) {
48 defer func(begin time.Time) {
49 mw.logger.Log("method", "PatchProfile", "id", id, "took", time.Since(begin), "err", err)
50 }(time.Now())
51 return mw.next.PatchProfile(ctx, id, p)
52 }
53
54 func (mw loggingMiddleware) DeleteProfile(ctx context.Context, id string) (err error) {
55 defer func(begin time.Time) {
56 mw.logger.Log("method", "DeleteProfile", "id", id, "took", time.Since(begin), "err", err)
57 }(time.Now())
58 return mw.next.DeleteProfile(ctx, id)
59 }
60
61 func (mw loggingMiddleware) GetAddresses(ctx context.Context, profileID string) (addresses []Address, err error) {
62 defer func(begin time.Time) {
63 mw.logger.Log("method", "GetAddresses", "profileID", profileID, "took", time.Since(begin), "err", err)
64 }(time.Now())
65 return mw.next.GetAddresses(ctx, profileID)
66 }
67
68 func (mw loggingMiddleware) GetAddress(ctx context.Context, profileID string, addressID string) (a Address, err error) {
69 defer func(begin time.Time) {
70 mw.logger.Log("method", "GetAddress", "profileID", profileID, "addressID", addressID, "took", time.Since(begin), "err", err)
71 }(time.Now())
72 return mw.next.GetAddress(ctx, profileID, addressID)
73 }
74
75 func (mw loggingMiddleware) PostAddress(ctx context.Context, profileID string, a Address) (err error) {
76 defer func(begin time.Time) {
77 mw.logger.Log("method", "PostAddress", "profileID", profileID, "took", time.Since(begin), "err", err)
78 }(time.Now())
79 return mw.next.PostAddress(ctx, profileID, a)
80 }
81
82 func (mw loggingMiddleware) DeleteAddress(ctx context.Context, profileID string, addressID string) (err error) {
83 defer func(begin time.Time) {
84 mw.logger.Log("method", "DeleteAddress", "profileID", profileID, "addressID", addressID, "took", time.Since(begin), "err", err)
85 }(time.Now())
86 return mw.next.DeleteAddress(ctx, profileID, addressID)
87 }
+0
-185
examples/profilesvc/service.go less more
0 package profilesvc
1
2 import (
3 "context"
4 "errors"
5 "sync"
6 )
7
8 // Service is a simple CRUD interface for user profiles.
9 type Service interface {
10 PostProfile(ctx context.Context, p Profile) error
11 GetProfile(ctx context.Context, id string) (Profile, error)
12 PutProfile(ctx context.Context, id string, p Profile) error
13 PatchProfile(ctx context.Context, id string, p Profile) error
14 DeleteProfile(ctx context.Context, id string) error
15 GetAddresses(ctx context.Context, profileID string) ([]Address, error)
16 GetAddress(ctx context.Context, profileID string, addressID string) (Address, error)
17 PostAddress(ctx context.Context, profileID string, a Address) error
18 DeleteAddress(ctx context.Context, profileID string, addressID string) error
19 }
20
21 // Profile represents a single user profile.
22 // ID should be globally unique.
23 type Profile struct {
24 ID string `json:"id"`
25 Name string `json:"name,omitempty"`
26 Addresses []Address `json:"addresses,omitempty"`
27 }
28
29 // Address is a field of a user profile.
30 // ID should be unique within the profile (at a minimum).
31 type Address struct {
32 ID string `json:"id"`
33 Location string `json:"location,omitempty"`
34 }
35
36 var (
37 ErrInconsistentIDs = errors.New("inconsistent IDs")
38 ErrAlreadyExists = errors.New("already exists")
39 ErrNotFound = errors.New("not found")
40 )
41
42 type inmemService struct {
43 mtx sync.RWMutex
44 m map[string]Profile
45 }
46
47 func NewInmemService() Service {
48 return &inmemService{
49 m: map[string]Profile{},
50 }
51 }
52
53 func (s *inmemService) PostProfile(ctx context.Context, p Profile) error {
54 s.mtx.Lock()
55 defer s.mtx.Unlock()
56 if _, ok := s.m[p.ID]; ok {
57 return ErrAlreadyExists // POST = create, don't overwrite
58 }
59 s.m[p.ID] = p
60 return nil
61 }
62
63 func (s *inmemService) GetProfile(ctx context.Context, id string) (Profile, error) {
64 s.mtx.RLock()
65 defer s.mtx.RUnlock()
66 p, ok := s.m[id]
67 if !ok {
68 return Profile{}, ErrNotFound
69 }
70 return p, nil
71 }
72
73 func (s *inmemService) PutProfile(ctx context.Context, id string, p Profile) error {
74 if id != p.ID {
75 return ErrInconsistentIDs
76 }
77 s.mtx.Lock()
78 defer s.mtx.Unlock()
79 s.m[id] = p // PUT = create or update
80 return nil
81 }
82
83 func (s *inmemService) PatchProfile(ctx context.Context, id string, p Profile) error {
84 if p.ID != "" && id != p.ID {
85 return ErrInconsistentIDs
86 }
87
88 s.mtx.Lock()
89 defer s.mtx.Unlock()
90
91 existing, ok := s.m[id]
92 if !ok {
93 return ErrNotFound // PATCH = update existing, don't create
94 }
95
96 // We assume that it's not possible to PATCH the ID, and that it's not
97 // possible to PATCH any field to its zero value. That is, the zero value
98 // means not specified. The way around this is to use e.g. Name *string in
99 // the Profile definition. But since this is just a demonstrative example,
100 // I'm leaving that out.
101
102 if p.Name != "" {
103 existing.Name = p.Name
104 }
105 if len(p.Addresses) > 0 {
106 existing.Addresses = p.Addresses
107 }
108 s.m[id] = existing
109 return nil
110 }
111
112 func (s *inmemService) DeleteProfile(ctx context.Context, id string) error {
113 s.mtx.Lock()
114 defer s.mtx.Unlock()
115 if _, ok := s.m[id]; !ok {
116 return ErrNotFound
117 }
118 delete(s.m, id)
119 return nil
120 }
121
122 func (s *inmemService) GetAddresses(ctx context.Context, profileID string) ([]Address, error) {
123 s.mtx.RLock()
124 defer s.mtx.RUnlock()
125 p, ok := s.m[profileID]
126 if !ok {
127 return []Address{}, ErrNotFound
128 }
129 return p.Addresses, nil
130 }
131
132 func (s *inmemService) GetAddress(ctx context.Context, profileID string, addressID string) (Address, error) {
133 s.mtx.RLock()
134 defer s.mtx.RUnlock()
135 p, ok := s.m[profileID]
136 if !ok {
137 return Address{}, ErrNotFound
138 }
139 for _, address := range p.Addresses {
140 if address.ID == addressID {
141 return address, nil
142 }
143 }
144 return Address{}, ErrNotFound
145 }
146
147 func (s *inmemService) PostAddress(ctx context.Context, profileID string, a Address) error {
148 s.mtx.Lock()
149 defer s.mtx.Unlock()
150 p, ok := s.m[profileID]
151 if !ok {
152 return ErrNotFound
153 }
154 for _, address := range p.Addresses {
155 if address.ID == a.ID {
156 return ErrAlreadyExists
157 }
158 }
159 p.Addresses = append(p.Addresses, a)
160 s.m[profileID] = p
161 return nil
162 }
163
164 func (s *inmemService) DeleteAddress(ctx context.Context, profileID string, addressID string) error {
165 s.mtx.Lock()
166 defer s.mtx.Unlock()
167 p, ok := s.m[profileID]
168 if !ok {
169 return ErrNotFound
170 }
171 newAddresses := make([]Address, 0, len(p.Addresses))
172 for _, address := range p.Addresses {
173 if address.ID == addressID {
174 continue // delete
175 }
176 newAddresses = append(newAddresses, address)
177 }
178 if len(newAddresses) == len(p.Addresses) {
179 return ErrNotFound
180 }
181 p.Addresses = newAddresses
182 s.m[profileID] = p
183 return nil
184 }
+0
-400
examples/profilesvc/transport.go less more
0 package profilesvc
1
2 // The profilesvc is just over HTTP, so we just have a single transport.go.
3
4 import (
5 "bytes"
6 "context"
7 "encoding/json"
8 "errors"
9 "io/ioutil"
10 "net/http"
11 "net/url"
12
13 "github.com/gorilla/mux"
14
15 "github.com/go-kit/kit/log"
16 httptransport "github.com/go-kit/kit/transport/http"
17 )
18
19 var (
20 // ErrBadRouting is returned when an expected path variable is missing.
21 // It always indicates programmer error.
22 ErrBadRouting = errors.New("inconsistent mapping between route and handler (programmer error)")
23 )
24
25 // MakeHTTPHandler mounts all of the service endpoints into an http.Handler.
26 // Useful in a profilesvc server.
27 func MakeHTTPHandler(s Service, logger log.Logger) http.Handler {
28 r := mux.NewRouter()
29 e := MakeServerEndpoints(s)
30 options := []httptransport.ServerOption{
31 httptransport.ServerErrorLogger(logger),
32 httptransport.ServerErrorEncoder(encodeError),
33 }
34
35 // POST /profiles/ adds another profile
36 // GET /profiles/:id retrieves the given profile by id
37 // PUT /profiles/:id post updated profile information about the profile
38 // PATCH /profiles/:id partial updated profile information
39 // DELETE /profiles/:id remove the given profile
40 // GET /profiles/:id/addresses/ retrieve addresses associated with the profile
41 // GET /profiles/:id/addresses/:addressID retrieve a particular profile address
42 // POST /profiles/:id/addresses/ add a new address
43 // DELETE /profiles/:id/addresses/:addressID remove an address
44
45 r.Methods("POST").Path("/profiles/").Handler(httptransport.NewServer(
46 e.PostProfileEndpoint,
47 decodePostProfileRequest,
48 encodeResponse,
49 options...,
50 ))
51 r.Methods("GET").Path("/profiles/{id}").Handler(httptransport.NewServer(
52 e.GetProfileEndpoint,
53 decodeGetProfileRequest,
54 encodeResponse,
55 options...,
56 ))
57 r.Methods("PUT").Path("/profiles/{id}").Handler(httptransport.NewServer(
58 e.PutProfileEndpoint,
59 decodePutProfileRequest,
60 encodeResponse,
61 options...,
62 ))
63 r.Methods("PATCH").Path("/profiles/{id}").Handler(httptransport.NewServer(
64 e.PatchProfileEndpoint,
65 decodePatchProfileRequest,
66 encodeResponse,
67 options...,
68 ))
69 r.Methods("DELETE").Path("/profiles/{id}").Handler(httptransport.NewServer(
70 e.DeleteProfileEndpoint,
71 decodeDeleteProfileRequest,
72 encodeResponse,
73 options...,
74 ))
75 r.Methods("GET").Path("/profiles/{id}/addresses/").Handler(httptransport.NewServer(
76 e.GetAddressesEndpoint,
77 decodeGetAddressesRequest,
78 encodeResponse,
79 options...,
80 ))
81 r.Methods("GET").Path("/profiles/{id}/addresses/{addressID}").Handler(httptransport.NewServer(
82 e.GetAddressEndpoint,
83 decodeGetAddressRequest,
84 encodeResponse,
85 options...,
86 ))
87 r.Methods("POST").Path("/profiles/{id}/addresses/").Handler(httptransport.NewServer(
88 e.PostAddressEndpoint,
89 decodePostAddressRequest,
90 encodeResponse,
91 options...,
92 ))
93 r.Methods("DELETE").Path("/profiles/{id}/addresses/{addressID}").Handler(httptransport.NewServer(
94 e.DeleteAddressEndpoint,
95 decodeDeleteAddressRequest,
96 encodeResponse,
97 options...,
98 ))
99 return r
100 }
101
102 func decodePostProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
103 var req postProfileRequest
104 if e := json.NewDecoder(r.Body).Decode(&req.Profile); e != nil {
105 return nil, e
106 }
107 return req, nil
108 }
109
110 func decodeGetProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
111 vars := mux.Vars(r)
112 id, ok := vars["id"]
113 if !ok {
114 return nil, ErrBadRouting
115 }
116 return getProfileRequest{ID: id}, nil
117 }
118
119 func decodePutProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
120 vars := mux.Vars(r)
121 id, ok := vars["id"]
122 if !ok {
123 return nil, ErrBadRouting
124 }
125 var profile Profile
126 if err := json.NewDecoder(r.Body).Decode(&profile); err != nil {
127 return nil, err
128 }
129 return putProfileRequest{
130 ID: id,
131 Profile: profile,
132 }, nil
133 }
134
135 func decodePatchProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
136 vars := mux.Vars(r)
137 id, ok := vars["id"]
138 if !ok {
139 return nil, ErrBadRouting
140 }
141 var profile Profile
142 if err := json.NewDecoder(r.Body).Decode(&profile); err != nil {
143 return nil, err
144 }
145 return patchProfileRequest{
146 ID: id,
147 Profile: profile,
148 }, nil
149 }
150
151 func decodeDeleteProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
152 vars := mux.Vars(r)
153 id, ok := vars["id"]
154 if !ok {
155 return nil, ErrBadRouting
156 }
157 return deleteProfileRequest{ID: id}, nil
158 }
159
160 func decodeGetAddressesRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
161 vars := mux.Vars(r)
162 id, ok := vars["id"]
163 if !ok {
164 return nil, ErrBadRouting
165 }
166 return getAddressesRequest{ProfileID: id}, nil
167 }
168
169 func decodeGetAddressRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
170 vars := mux.Vars(r)
171 id, ok := vars["id"]
172 if !ok {
173 return nil, ErrBadRouting
174 }
175 addressID, ok := vars["addressID"]
176 if !ok {
177 return nil, ErrBadRouting
178 }
179 return getAddressRequest{
180 ProfileID: id,
181 AddressID: addressID,
182 }, nil
183 }
184
185 func decodePostAddressRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
186 vars := mux.Vars(r)
187 id, ok := vars["id"]
188 if !ok {
189 return nil, ErrBadRouting
190 }
191 var address Address
192 if err := json.NewDecoder(r.Body).Decode(&address); err != nil {
193 return nil, err
194 }
195 return postAddressRequest{
196 ProfileID: id,
197 Address: address,
198 }, nil
199 }
200
201 func decodeDeleteAddressRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
202 vars := mux.Vars(r)
203 id, ok := vars["id"]
204 if !ok {
205 return nil, ErrBadRouting
206 }
207 addressID, ok := vars["addressID"]
208 if !ok {
209 return nil, ErrBadRouting
210 }
211 return deleteAddressRequest{
212 ProfileID: id,
213 AddressID: addressID,
214 }, nil
215 }
216
217 func encodePostProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
218 // r.Methods("POST").Path("/profiles/")
219 req.Method, req.URL.Path = "POST", "/profiles/"
220 return encodeRequest(ctx, req, request)
221 }
222
223 func encodeGetProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
224 // r.Methods("GET").Path("/profiles/{id}")
225 r := request.(getProfileRequest)
226 profileID := url.QueryEscape(r.ID)
227 req.Method, req.URL.Path = "GET", "/profiles/"+profileID
228 return encodeRequest(ctx, req, request)
229 }
230
231 func encodePutProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
232 // r.Methods("PUT").Path("/profiles/{id}")
233 r := request.(putProfileRequest)
234 profileID := url.QueryEscape(r.ID)
235 req.Method, req.URL.Path = "PUT", "/profiles/"+profileID
236 return encodeRequest(ctx, req, request)
237 }
238
239 func encodePatchProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
240 // r.Methods("PATCH").Path("/profiles/{id}")
241 r := request.(patchProfileRequest)
242 profileID := url.QueryEscape(r.ID)
243 req.Method, req.URL.Path = "PATCH", "/profiles/"+profileID
244 return encodeRequest(ctx, req, request)
245 }
246
247 func encodeDeleteProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
248 // r.Methods("DELETE").Path("/profiles/{id}")
249 r := request.(deleteProfileRequest)
250 profileID := url.QueryEscape(r.ID)
251 req.Method, req.URL.Path = "DELETE", "/profiles/"+profileID
252 return encodeRequest(ctx, req, request)
253 }
254
255 func encodeGetAddressesRequest(ctx context.Context, req *http.Request, request interface{}) error {
256 // r.Methods("GET").Path("/profiles/{id}/addresses/")
257 r := request.(getAddressesRequest)
258 profileID := url.QueryEscape(r.ProfileID)
259 req.Method, req.URL.Path = "GET", "/profiles/"+profileID+"/addresses/"
260 return encodeRequest(ctx, req, request)
261 }
262
263 func encodeGetAddressRequest(ctx context.Context, req *http.Request, request interface{}) error {
264 // r.Methods("GET").Path("/profiles/{id}/addresses/{addressID}")
265 r := request.(getAddressRequest)
266 profileID := url.QueryEscape(r.ProfileID)
267 addressID := url.QueryEscape(r.AddressID)
268 req.Method, req.URL.Path = "GET", "/profiles/"+profileID+"/addresses/"+addressID
269 return encodeRequest(ctx, req, request)
270 }
271
272 func encodePostAddressRequest(ctx context.Context, req *http.Request, request interface{}) error {
273 // r.Methods("POST").Path("/profiles/{id}/addresses/")
274 r := request.(postAddressRequest)
275 profileID := url.QueryEscape(r.ProfileID)
276 req.Method, req.URL.Path = "POST", "/profiles/"+profileID+"/addresses/"
277 return encodeRequest(ctx, req, request)
278 }
279
280 func encodeDeleteAddressRequest(ctx context.Context, req *http.Request, request interface{}) error {
281 // r.Methods("DELETE").Path("/profiles/{id}/addresses/{addressID}")
282 r := request.(deleteAddressRequest)
283 profileID := url.QueryEscape(r.ProfileID)
284 addressID := url.QueryEscape(r.AddressID)
285 req.Method, req.URL.Path = "DELETE", "/profiles/"+profileID+"/addresses/"+addressID
286 return encodeRequest(ctx, req, request)
287 }
288
289 func decodePostProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
290 var response postProfileResponse
291 err := json.NewDecoder(resp.Body).Decode(&response)
292 return response, err
293 }
294
295 func decodeGetProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
296 var response getProfileResponse
297 err := json.NewDecoder(resp.Body).Decode(&response)
298 return response, err
299 }
300
301 func decodePutProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
302 var response putProfileResponse
303 err := json.NewDecoder(resp.Body).Decode(&response)
304 return response, err
305 }
306
307 func decodePatchProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
308 var response patchProfileResponse
309 err := json.NewDecoder(resp.Body).Decode(&response)
310 return response, err
311 }
312
313 func decodeDeleteProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
314 var response deleteProfileResponse
315 err := json.NewDecoder(resp.Body).Decode(&response)
316 return response, err
317 }
318
319 func decodeGetAddressesResponse(_ context.Context, resp *http.Response) (interface{}, error) {
320 var response getAddressesResponse
321 err := json.NewDecoder(resp.Body).Decode(&response)
322 return response, err
323 }
324
325 func decodeGetAddressResponse(_ context.Context, resp *http.Response) (interface{}, error) {
326 var response getAddressResponse
327 err := json.NewDecoder(resp.Body).Decode(&response)
328 return response, err
329 }
330
331 func decodePostAddressResponse(_ context.Context, resp *http.Response) (interface{}, error) {
332 var response postAddressResponse
333 err := json.NewDecoder(resp.Body).Decode(&response)
334 return response, err
335 }
336
337 func decodeDeleteAddressResponse(_ context.Context, resp *http.Response) (interface{}, error) {
338 var response deleteAddressResponse
339 err := json.NewDecoder(resp.Body).Decode(&response)
340 return response, err
341 }
342
343 // errorer is implemented by all concrete response types that may contain
344 // errors. It allows us to change the HTTP response code without needing to
345 // trigger an endpoint (transport-level) error. For more information, read the
346 // big comment in endpoints.go.
347 type errorer interface {
348 error() error
349 }
350
351 // encodeResponse is the common method to encode all response types to the
352 // client. I chose to do it this way because, since we're using JSON, there's no
353 // reason to provide anything more specific. It's certainly possible to
354 // specialize on a per-response (per-method) basis.
355 func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
356 if e, ok := response.(errorer); ok && e.error() != nil {
357 // Not a Go kit transport error, but a business-logic error.
358 // Provide those as HTTP errors.
359 encodeError(ctx, e.error(), w)
360 return nil
361 }
362 w.Header().Set("Content-Type", "application/json; charset=utf-8")
363 return json.NewEncoder(w).Encode(response)
364 }
365
366 // encodeRequest likewise JSON-encodes the request to the HTTP request body.
367 // Don't use it directly as a transport/http.Client EncodeRequestFunc:
368 // profilesvc endpoints require mutating the HTTP method and request path.
369 func encodeRequest(_ context.Context, req *http.Request, request interface{}) error {
370 var buf bytes.Buffer
371 err := json.NewEncoder(&buf).Encode(request)
372 if err != nil {
373 return err
374 }
375 req.Body = ioutil.NopCloser(&buf)
376 return nil
377 }
378
379 func encodeError(_ context.Context, err error, w http.ResponseWriter) {
380 if err == nil {
381 panic("encodeError with nil error")
382 }
383 w.Header().Set("Content-Type", "application/json; charset=utf-8")
384 w.WriteHeader(codeFrom(err))
385 json.NewEncoder(w).Encode(map[string]interface{}{
386 "error": err.Error(),
387 })
388 }
389
390 func codeFrom(err error) int {
391 switch err {
392 case ErrNotFound:
393 return http.StatusNotFound
394 case ErrAlreadyExists, ErrInconsistentIDs:
395 return http.StatusBadRequest
396 default:
397 return http.StatusInternalServerError
398 }
399 }
+0
-25
examples/shipping/README.md less more
0 # shipping
1
2 This example demonstrates a more real-world application consisting of multiple services.
3
4 ## Description
5
6 The implementation is based on the container shipping domain from the [Domain Driven Design](http://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215) book by Eric Evans, which was [originally](http://dddsample.sourceforge.net/) implemented in Java but has since been ported to Go. This example is a somewhat stripped down version to demonstrate the use of Go kit. The [original Go application](https://github.com/marcusolsson/goddd) is maintained separately and accompanied by an [AngularJS application](https://github.com/marcusolsson/dddelivery-angularjs) as well as a mock [routing service](https://github.com/marcusolsson/pathfinder).
7
8 ### Organization
9
10 The application consists of three application services, `booking`, `handling` and `tracking`. Each of these is an individual Go kit service as seen in previous examples.
11
12 - __booking__ - used by the shipping company to book and route cargos.
13 - __handling__ - used by our staff around the world to register whenever the cargo has been received, loaded etc.
14 - __tracking__ - used by the customer to track the cargo along the route
15
16 There are also a few pure domain packages that contain some intricate business-logic. They provide domain objects and services that are used by each application service to provide interesting use-cases for the user.
17
18 `inmem` contains in-memory implementations for the repositories found in the domain packages.
19
20 The `routing` package provides a _domain service_ that is used to query an external application for possible routes.
21
22 ## Contributing
23
24 As with all Go kit examples you are more than welcome to contribute. If you do however, please consider contributing back to the original project as well.
+0
-139
examples/shipping/booking/endpoint.go less more
0 package booking
1
2 import (
3 "context"
4 "time"
5
6 "github.com/go-kit/kit/endpoint"
7
8 "github.com/go-kit/kit/examples/shipping/cargo"
9 "github.com/go-kit/kit/examples/shipping/location"
10 )
11
12 type bookCargoRequest struct {
13 Origin location.UNLocode
14 Destination location.UNLocode
15 ArrivalDeadline time.Time
16 }
17
18 type bookCargoResponse struct {
19 ID cargo.TrackingID `json:"tracking_id,omitempty"`
20 Err error `json:"error,omitempty"`
21 }
22
23 func (r bookCargoResponse) error() error { return r.Err }
24
25 func makeBookCargoEndpoint(s Service) endpoint.Endpoint {
26 return func(ctx context.Context, request interface{}) (interface{}, error) {
27 req := request.(bookCargoRequest)
28 id, err := s.BookNewCargo(req.Origin, req.Destination, req.ArrivalDeadline)
29 return bookCargoResponse{ID: id, Err: err}, nil
30 }
31 }
32
33 type loadCargoRequest struct {
34 ID cargo.TrackingID
35 }
36
37 type loadCargoResponse struct {
38 Cargo *Cargo `json:"cargo,omitempty"`
39 Err error `json:"error,omitempty"`
40 }
41
42 func (r loadCargoResponse) error() error { return r.Err }
43
44 func makeLoadCargoEndpoint(s Service) endpoint.Endpoint {
45 return func(ctx context.Context, request interface{}) (interface{}, error) {
46 req := request.(loadCargoRequest)
47 c, err := s.LoadCargo(req.ID)
48 return loadCargoResponse{Cargo: &c, Err: err}, nil
49 }
50 }
51
52 type requestRoutesRequest struct {
53 ID cargo.TrackingID
54 }
55
56 type requestRoutesResponse struct {
57 Routes []cargo.Itinerary `json:"routes,omitempty"`
58 Err error `json:"error,omitempty"`
59 }
60
61 func (r requestRoutesResponse) error() error { return r.Err }
62
63 func makeRequestRoutesEndpoint(s Service) endpoint.Endpoint {
64 return func(ctx context.Context, request interface{}) (interface{}, error) {
65 req := request.(requestRoutesRequest)
66 itin := s.RequestPossibleRoutesForCargo(req.ID)
67 return requestRoutesResponse{Routes: itin, Err: nil}, nil
68 }
69 }
70
71 type assignToRouteRequest struct {
72 ID cargo.TrackingID
73 Itinerary cargo.Itinerary
74 }
75
76 type assignToRouteResponse struct {
77 Err error `json:"error,omitempty"`
78 }
79
80 func (r assignToRouteResponse) error() error { return r.Err }
81
82 func makeAssignToRouteEndpoint(s Service) endpoint.Endpoint {
83 return func(ctx context.Context, request interface{}) (interface{}, error) {
84 req := request.(assignToRouteRequest)
85 err := s.AssignCargoToRoute(req.ID, req.Itinerary)
86 return assignToRouteResponse{Err: err}, nil
87 }
88 }
89
90 type changeDestinationRequest struct {
91 ID cargo.TrackingID
92 Destination location.UNLocode
93 }
94
95 type changeDestinationResponse struct {
96 Err error `json:"error,omitempty"`
97 }
98
99 func (r changeDestinationResponse) error() error { return r.Err }
100
101 func makeChangeDestinationEndpoint(s Service) endpoint.Endpoint {
102 return func(ctx context.Context, request interface{}) (interface{}, error) {
103 req := request.(changeDestinationRequest)
104 err := s.ChangeDestination(req.ID, req.Destination)
105 return changeDestinationResponse{Err: err}, nil
106 }
107 }
108
109 type listCargosRequest struct{}
110
111 type listCargosResponse struct {
112 Cargos []Cargo `json:"cargos,omitempty"`
113 Err error `json:"error,omitempty"`
114 }
115
116 func (r listCargosResponse) error() error { return r.Err }
117
118 func makeListCargosEndpoint(s Service) endpoint.Endpoint {
119 return func(ctx context.Context, request interface{}) (interface{}, error) {
120 _ = request.(listCargosRequest)
121 return listCargosResponse{Cargos: s.Cargos(), Err: nil}, nil
122 }
123 }
124
125 type listLocationsRequest struct {
126 }
127
128 type listLocationsResponse struct {
129 Locations []Location `json:"locations,omitempty"`
130 Err error `json:"error,omitempty"`
131 }
132
133 func makeListLocationsEndpoint(s Service) endpoint.Endpoint {
134 return func(ctx context.Context, request interface{}) (interface{}, error) {
135 _ = request.(listLocationsRequest)
136 return listLocationsResponse{Locations: s.Locations(), Err: nil}, nil
137 }
138 }
+0
-88
examples/shipping/booking/instrumenting.go less more
0 package booking
1
2 import (
3 "time"
4
5 "github.com/go-kit/kit/metrics"
6
7 "github.com/go-kit/kit/examples/shipping/cargo"
8 "github.com/go-kit/kit/examples/shipping/location"
9 )
10
11 type instrumentingService struct {
12 requestCount metrics.Counter
13 requestLatency metrics.Histogram
14 Service
15 }
16
17 // NewInstrumentingService returns an instance of an instrumenting Service.
18 func NewInstrumentingService(counter metrics.Counter, latency metrics.Histogram, s Service) Service {
19 return &instrumentingService{
20 requestCount: counter,
21 requestLatency: latency,
22 Service: s,
23 }
24 }
25
26 func (s *instrumentingService) BookNewCargo(origin, destination location.UNLocode, deadline time.Time) (cargo.TrackingID, error) {
27 defer func(begin time.Time) {
28 s.requestCount.With("method", "book").Add(1)
29 s.requestLatency.With("method", "book").Observe(time.Since(begin).Seconds())
30 }(time.Now())
31
32 return s.Service.BookNewCargo(origin, destination, deadline)
33 }
34
35 func (s *instrumentingService) LoadCargo(id cargo.TrackingID) (c Cargo, err error) {
36 defer func(begin time.Time) {
37 s.requestCount.With("method", "load").Add(1)
38 s.requestLatency.With("method", "load").Observe(time.Since(begin).Seconds())
39 }(time.Now())
40
41 return s.Service.LoadCargo(id)
42 }
43
44 func (s *instrumentingService) RequestPossibleRoutesForCargo(id cargo.TrackingID) []cargo.Itinerary {
45 defer func(begin time.Time) {
46 s.requestCount.With("method", "request_routes").Add(1)
47 s.requestLatency.With("method", "request_routes").Observe(time.Since(begin).Seconds())
48 }(time.Now())
49
50 return s.Service.RequestPossibleRoutesForCargo(id)
51 }
52
53 func (s *instrumentingService) AssignCargoToRoute(id cargo.TrackingID, itinerary cargo.Itinerary) (err error) {
54 defer func(begin time.Time) {
55 s.requestCount.With("method", "assign_to_route").Add(1)
56 s.requestLatency.With("method", "assign_to_route").Observe(time.Since(begin).Seconds())
57 }(time.Now())
58
59 return s.Service.AssignCargoToRoute(id, itinerary)
60 }
61
62 func (s *instrumentingService) ChangeDestination(id cargo.TrackingID, l location.UNLocode) (err error) {
63 defer func(begin time.Time) {
64 s.requestCount.With("method", "change_destination").Add(1)
65 s.requestLatency.With("method", "change_destination").Observe(time.Since(begin).Seconds())
66 }(time.Now())
67
68 return s.Service.ChangeDestination(id, l)
69 }
70
71 func (s *instrumentingService) Cargos() []Cargo {
72 defer func(begin time.Time) {
73 s.requestCount.With("method", "list_cargos").Add(1)
74 s.requestLatency.With("method", "list_cargos").Observe(time.Since(begin).Seconds())
75 }(time.Now())
76
77 return s.Service.Cargos()
78 }
79
80 func (s *instrumentingService) Locations() []Location {
81 defer func(begin time.Time) {
82 s.requestCount.With("method", "list_locations").Add(1)
83 s.requestLatency.With("method", "list_locations").Observe(time.Since(begin).Seconds())
84 }(time.Now())
85
86 return s.Service.Locations()
87 }
+0
-102
examples/shipping/booking/logging.go less more
0 package booking
1
2 import (
3 "time"
4
5 "github.com/go-kit/kit/log"
6
7 "github.com/go-kit/kit/examples/shipping/cargo"
8 "github.com/go-kit/kit/examples/shipping/location"
9 )
10
11 type loggingService struct {
12 logger log.Logger
13 Service
14 }
15
16 // NewLoggingService returns a new instance of a logging Service.
17 func NewLoggingService(logger log.Logger, s Service) Service {
18 return &loggingService{logger, s}
19 }
20
21 func (s *loggingService) BookNewCargo(origin location.UNLocode, destination location.UNLocode, deadline time.Time) (id cargo.TrackingID, err error) {
22 defer func(begin time.Time) {
23 s.logger.Log(
24 "method", "book",
25 "origin", origin,
26 "destination", destination,
27 "arrival_deadline", deadline,
28 "took", time.Since(begin),
29 "err", err,
30 )
31 }(time.Now())
32 return s.Service.BookNewCargo(origin, destination, deadline)
33 }
34
35 func (s *loggingService) LoadCargo(id cargo.TrackingID) (c Cargo, err error) {
36 defer func(begin time.Time) {
37 s.logger.Log(
38 "method", "load",
39 "tracking_id", id,
40 "took", time.Since(begin),
41 "err", err,
42 )
43 }(time.Now())
44 return s.Service.LoadCargo(id)
45 }
46
47 func (s *loggingService) RequestPossibleRoutesForCargo(id cargo.TrackingID) []cargo.Itinerary {
48 defer func(begin time.Time) {
49 s.logger.Log(
50 "method", "request_routes",
51 "tracking_id", id,
52 "took", time.Since(begin),
53 )
54 }(time.Now())
55 return s.Service.RequestPossibleRoutesForCargo(id)
56 }
57
58 func (s *loggingService) AssignCargoToRoute(id cargo.TrackingID, itinerary cargo.Itinerary) (err error) {
59 defer func(begin time.Time) {
60 s.logger.Log(
61 "method", "assign_to_route",
62 "tracking_id", id,
63 "took", time.Since(begin),
64 "err", err,
65 )
66 }(time.Now())
67 return s.Service.AssignCargoToRoute(id, itinerary)
68 }
69
70 func (s *loggingService) ChangeDestination(id cargo.TrackingID, l location.UNLocode) (err error) {
71 defer func(begin time.Time) {
72 s.logger.Log(
73 "method", "change_destination",
74 "tracking_id", id,
75 "destination", l,
76 "took", time.Since(begin),
77 "err", err,
78 )
79 }(time.Now())
80 return s.Service.ChangeDestination(id, l)
81 }
82
83 func (s *loggingService) Cargos() []Cargo {
84 defer func(begin time.Time) {
85 s.logger.Log(
86 "method", "list_cargos",
87 "took", time.Since(begin),
88 )
89 }(time.Now())
90 return s.Service.Cargos()
91 }
92
93 func (s *loggingService) Locations() []Location {
94 defer func(begin time.Time) {
95 s.logger.Log(
96 "method", "list_locations",
97 "took", time.Since(begin),
98 )
99 }(time.Now())
100 return s.Service.Locations()
101 }
+0
-197
examples/shipping/booking/service.go less more
0 // Package booking provides the use-case of booking a cargo. Used by views
1 // facing an administrator.
2 package booking
3
4 import (
5 "errors"
6 "time"
7
8 "github.com/go-kit/kit/examples/shipping/cargo"
9 "github.com/go-kit/kit/examples/shipping/location"
10 "github.com/go-kit/kit/examples/shipping/routing"
11 )
12
13 // ErrInvalidArgument is returned when one or more arguments are invalid.
14 var ErrInvalidArgument = errors.New("invalid argument")
15
16 // Service is the interface that provides booking methods.
17 type Service interface {
18 // BookNewCargo registers a new cargo in the tracking system, not yet
19 // routed.
20 BookNewCargo(origin location.UNLocode, destination location.UNLocode, deadline time.Time) (cargo.TrackingID, error)
21
22 // LoadCargo returns a read model of a cargo.
23 LoadCargo(id cargo.TrackingID) (Cargo, error)
24
25 // RequestPossibleRoutesForCargo requests a list of itineraries describing
26 // possible routes for this cargo.
27 RequestPossibleRoutesForCargo(id cargo.TrackingID) []cargo.Itinerary
28
29 // AssignCargoToRoute assigns a cargo to the route specified by the
30 // itinerary.
31 AssignCargoToRoute(id cargo.TrackingID, itinerary cargo.Itinerary) error
32
33 // ChangeDestination changes the destination of a cargo.
34 ChangeDestination(id cargo.TrackingID, destination location.UNLocode) error
35
36 // Cargos returns a list of all cargos that have been booked.
37 Cargos() []Cargo
38
39 // Locations returns a list of registered locations.
40 Locations() []Location
41 }
42
43 type service struct {
44 cargos cargo.Repository
45 locations location.Repository
46 handlingEvents cargo.HandlingEventRepository
47 routingService routing.Service
48 }
49
50 func (s *service) AssignCargoToRoute(id cargo.TrackingID, itinerary cargo.Itinerary) error {
51 if id == "" || len(itinerary.Legs) == 0 {
52 return ErrInvalidArgument
53 }
54
55 c, err := s.cargos.Find(id)
56 if err != nil {
57 return err
58 }
59
60 c.AssignToRoute(itinerary)
61
62 return s.cargos.Store(c)
63 }
64
65 func (s *service) BookNewCargo(origin, destination location.UNLocode, deadline time.Time) (cargo.TrackingID, error) {
66 if origin == "" || destination == "" || deadline.IsZero() {
67 return "", ErrInvalidArgument
68 }
69
70 id := cargo.NextTrackingID()
71 rs := cargo.RouteSpecification{
72 Origin: origin,
73 Destination: destination,
74 ArrivalDeadline: deadline,
75 }
76
77 c := cargo.New(id, rs)
78
79 if err := s.cargos.Store(c); err != nil {
80 return "", err
81 }
82
83 return c.TrackingID, nil
84 }
85
86 func (s *service) LoadCargo(id cargo.TrackingID) (Cargo, error) {
87 if id == "" {
88 return Cargo{}, ErrInvalidArgument
89 }
90
91 c, err := s.cargos.Find(id)
92 if err != nil {
93 return Cargo{}, err
94 }
95
96 return assemble(c, s.handlingEvents), nil
97 }
98
99 func (s *service) ChangeDestination(id cargo.TrackingID, destination location.UNLocode) error {
100 if id == "" || destination == "" {
101 return ErrInvalidArgument
102 }
103
104 c, err := s.cargos.Find(id)
105 if err != nil {
106 return err
107 }
108
109 l, err := s.locations.Find(destination)
110 if err != nil {
111 return err
112 }
113
114 c.SpecifyNewRoute(cargo.RouteSpecification{
115 Origin: c.Origin,
116 Destination: l.UNLocode,
117 ArrivalDeadline: c.RouteSpecification.ArrivalDeadline,
118 })
119
120 if err := s.cargos.Store(c); err != nil {
121 return err
122 }
123
124 return nil
125 }
126
127 func (s *service) RequestPossibleRoutesForCargo(id cargo.TrackingID) []cargo.Itinerary {
128 if id == "" {
129 return nil
130 }
131
132 c, err := s.cargos.Find(id)
133 if err != nil {
134 return []cargo.Itinerary{}
135 }
136
137 return s.routingService.FetchRoutesForSpecification(c.RouteSpecification)
138 }
139
140 func (s *service) Cargos() []Cargo {
141 var result []Cargo
142 for _, c := range s.cargos.FindAll() {
143 result = append(result, assemble(c, s.handlingEvents))
144 }
145 return result
146 }
147
148 func (s *service) Locations() []Location {
149 var result []Location
150 for _, v := range s.locations.FindAll() {
151 result = append(result, Location{
152 UNLocode: string(v.UNLocode),
153 Name: v.Name,
154 })
155 }
156 return result
157 }
158
159 // NewService creates a booking service with necessary dependencies.
160 func NewService(cargos cargo.Repository, locations location.Repository, events cargo.HandlingEventRepository, rs routing.Service) Service {
161 return &service{
162 cargos: cargos,
163 locations: locations,
164 handlingEvents: events,
165 routingService: rs,
166 }
167 }
168
169 // Location is a read model for booking views.
170 type Location struct {
171 UNLocode string `json:"locode"`
172 Name string `json:"name"`
173 }
174
175 // Cargo is a read model for booking views.
176 type Cargo struct {
177 ArrivalDeadline time.Time `json:"arrival_deadline"`
178 Destination string `json:"destination"`
179 Legs []cargo.Leg `json:"legs,omitempty"`
180 Misrouted bool `json:"misrouted"`
181 Origin string `json:"origin"`
182 Routed bool `json:"routed"`
183 TrackingID string `json:"tracking_id"`
184 }
185
186 func assemble(c *cargo.Cargo, events cargo.HandlingEventRepository) Cargo {
187 return Cargo{
188 TrackingID: string(c.TrackingID),
189 Origin: string(c.Origin),
190 Destination: string(c.RouteSpecification.Destination),
191 Misrouted: c.Delivery.RoutingStatus == cargo.Misrouted,
192 Routed: !c.Itinerary.IsEmpty(),
193 ArrivalDeadline: c.RouteSpecification.ArrivalDeadline,
194 Legs: c.Itinerary.Legs,
195 }
196 }
+0
-194
examples/shipping/booking/transport.go less more
0 package booking
1
2 import (
3 "context"
4 "encoding/json"
5 "errors"
6 "net/http"
7 "time"
8
9 "github.com/gorilla/mux"
10
11 kitlog "github.com/go-kit/kit/log"
12 kithttp "github.com/go-kit/kit/transport/http"
13
14 "github.com/go-kit/kit/examples/shipping/cargo"
15 "github.com/go-kit/kit/examples/shipping/location"
16 )
17
18 // MakeHandler returns a handler for the booking service.
19 func MakeHandler(bs Service, logger kitlog.Logger) http.Handler {
20 opts := []kithttp.ServerOption{
21 kithttp.ServerErrorLogger(logger),
22 kithttp.ServerErrorEncoder(encodeError),
23 }
24
25 bookCargoHandler := kithttp.NewServer(
26 makeBookCargoEndpoint(bs),
27 decodeBookCargoRequest,
28 encodeResponse,
29 opts...,
30 )
31 loadCargoHandler := kithttp.NewServer(
32 makeLoadCargoEndpoint(bs),
33 decodeLoadCargoRequest,
34 encodeResponse,
35 opts...,
36 )
37 requestRoutesHandler := kithttp.NewServer(
38 makeRequestRoutesEndpoint(bs),
39 decodeRequestRoutesRequest,
40 encodeResponse,
41 opts...,
42 )
43 assignToRouteHandler := kithttp.NewServer(
44 makeAssignToRouteEndpoint(bs),
45 decodeAssignToRouteRequest,
46 encodeResponse,
47 opts...,
48 )
49 changeDestinationHandler := kithttp.NewServer(
50 makeChangeDestinationEndpoint(bs),
51 decodeChangeDestinationRequest,
52 encodeResponse,
53 opts...,
54 )
55 listCargosHandler := kithttp.NewServer(
56 makeListCargosEndpoint(bs),
57 decodeListCargosRequest,
58 encodeResponse,
59 opts...,
60 )
61 listLocationsHandler := kithttp.NewServer(
62 makeListLocationsEndpoint(bs),
63 decodeListLocationsRequest,
64 encodeResponse,
65 opts...,
66 )
67
68 r := mux.NewRouter()
69
70 r.Handle("/booking/v1/cargos", bookCargoHandler).Methods("POST")
71 r.Handle("/booking/v1/cargos", listCargosHandler).Methods("GET")
72 r.Handle("/booking/v1/cargos/{id}", loadCargoHandler).Methods("GET")
73 r.Handle("/booking/v1/cargos/{id}/request_routes", requestRoutesHandler).Methods("GET")
74 r.Handle("/booking/v1/cargos/{id}/assign_to_route", assignToRouteHandler).Methods("POST")
75 r.Handle("/booking/v1/cargos/{id}/change_destination", changeDestinationHandler).Methods("POST")
76 r.Handle("/booking/v1/locations", listLocationsHandler).Methods("GET")
77
78 return r
79 }
80
81 var errBadRoute = errors.New("bad route")
82
83 func decodeBookCargoRequest(_ context.Context, r *http.Request) (interface{}, error) {
84 var body struct {
85 Origin string `json:"origin"`
86 Destination string `json:"destination"`
87 ArrivalDeadline time.Time `json:"arrival_deadline"`
88 }
89
90 if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
91 return nil, err
92 }
93
94 return bookCargoRequest{
95 Origin: location.UNLocode(body.Origin),
96 Destination: location.UNLocode(body.Destination),
97 ArrivalDeadline: body.ArrivalDeadline,
98 }, nil
99 }
100
101 func decodeLoadCargoRequest(_ context.Context, r *http.Request) (interface{}, error) {
102 vars := mux.Vars(r)
103 id, ok := vars["id"]
104 if !ok {
105 return nil, errBadRoute
106 }
107 return loadCargoRequest{ID: cargo.TrackingID(id)}, nil
108 }
109
110 func decodeRequestRoutesRequest(_ context.Context, r *http.Request) (interface{}, error) {
111 vars := mux.Vars(r)
112 id, ok := vars["id"]
113 if !ok {
114 return nil, errBadRoute
115 }
116 return requestRoutesRequest{ID: cargo.TrackingID(id)}, nil
117 }
118
119 func decodeAssignToRouteRequest(_ context.Context, r *http.Request) (interface{}, error) {
120 vars := mux.Vars(r)
121 id, ok := vars["id"]
122 if !ok {
123 return nil, errBadRoute
124 }
125
126 var itinerary cargo.Itinerary
127 if err := json.NewDecoder(r.Body).Decode(&itinerary); err != nil {
128 return nil, err
129 }
130
131 return assignToRouteRequest{
132 ID: cargo.TrackingID(id),
133 Itinerary: itinerary,
134 }, nil
135 }
136
137 func decodeChangeDestinationRequest(_ context.Context, r *http.Request) (interface{}, error) {
138 vars := mux.Vars(r)
139 id, ok := vars["id"]
140 if !ok {
141 return nil, errBadRoute
142 }
143
144 var body struct {
145 Destination string `json:"destination"`
146 }
147
148 if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
149 return nil, err
150 }
151
152 return changeDestinationRequest{
153 ID: cargo.TrackingID(id),
154 Destination: location.UNLocode(body.Destination),
155 }, nil
156 }
157
158 func decodeListCargosRequest(_ context.Context, r *http.Request) (interface{}, error) {
159 return listCargosRequest{}, nil
160 }
161
162 func decodeListLocationsRequest(_ context.Context, r *http.Request) (interface{}, error) {
163 return listLocationsRequest{}, nil
164 }
165
166 func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
167 if e, ok := response.(errorer); ok && e.error() != nil {
168 encodeError(ctx, e.error(), w)
169 return nil
170 }
171 w.Header().Set("Content-Type", "application/json; charset=utf-8")
172 return json.NewEncoder(w).Encode(response)
173 }
174
175 type errorer interface {
176 error() error
177 }
178
179 // encode errors from business-logic
180 func encodeError(_ context.Context, err error, w http.ResponseWriter) {
181 w.Header().Set("Content-Type", "application/json; charset=utf-8")
182 switch err {
183 case cargo.ErrUnknown:
184 w.WriteHeader(http.StatusNotFound)
185 case ErrInvalidArgument:
186 w.WriteHeader(http.StatusBadRequest)
187 default:
188 w.WriteHeader(http.StatusInternalServerError)
189 }
190 json.NewEncoder(w).Encode(map[string]interface{}{
191 "error": err.Error(),
192 })
193 }
+0
-137
examples/shipping/cargo/cargo.go less more
0 // Package cargo contains the heart of the domain model.
1 package cargo
2
3 import (
4 "errors"
5 "strings"
6 "time"
7
8 "github.com/pborman/uuid"
9
10 "github.com/go-kit/kit/examples/shipping/location"
11 )
12
13 // TrackingID uniquely identifies a particular cargo.
14 type TrackingID string
15
16 // Cargo is the central class in the domain model.
17 type Cargo struct {
18 TrackingID TrackingID
19 Origin location.UNLocode
20 RouteSpecification RouteSpecification
21 Itinerary Itinerary
22 Delivery Delivery
23 }
24
25 // SpecifyNewRoute specifies a new route for this cargo.
26 func (c *Cargo) SpecifyNewRoute(rs RouteSpecification) {
27 c.RouteSpecification = rs
28 c.Delivery = c.Delivery.UpdateOnRouting(c.RouteSpecification, c.Itinerary)
29 }
30
31 // AssignToRoute attaches a new itinerary to this cargo.
32 func (c *Cargo) AssignToRoute(itinerary Itinerary) {
33 c.Itinerary = itinerary
34 c.Delivery = c.Delivery.UpdateOnRouting(c.RouteSpecification, c.Itinerary)
35 }
36
37 // DeriveDeliveryProgress updates all aspects of the cargo aggregate status
38 // based on the current route specification, itinerary and handling of the cargo.
39 func (c *Cargo) DeriveDeliveryProgress(history HandlingHistory) {
40 c.Delivery = DeriveDeliveryFrom(c.RouteSpecification, c.Itinerary, history)
41 }
42
43 // New creates a new, unrouted cargo.
44 func New(id TrackingID, rs RouteSpecification) *Cargo {
45 itinerary := Itinerary{}
46 history := HandlingHistory{make([]HandlingEvent, 0)}
47
48 return &Cargo{
49 TrackingID: id,
50 Origin: rs.Origin,
51 RouteSpecification: rs,
52 Delivery: DeriveDeliveryFrom(rs, itinerary, history),
53 }
54 }
55
56 // Repository provides access a cargo store.
57 type Repository interface {
58 Store(cargo *Cargo) error
59 Find(id TrackingID) (*Cargo, error)
60 FindAll() []*Cargo
61 }
62
63 // ErrUnknown is used when a cargo could not be found.
64 var ErrUnknown = errors.New("unknown cargo")
65
66 // NextTrackingID generates a new tracking ID.
67 // TODO: Move to infrastructure(?)
68 func NextTrackingID() TrackingID {
69 return TrackingID(strings.Split(strings.ToUpper(uuid.New()), "-")[0])
70 }
71
72 // RouteSpecification Contains information about a route: its origin,
73 // destination and arrival deadline.
74 type RouteSpecification struct {
75 Origin location.UNLocode
76 Destination location.UNLocode
77 ArrivalDeadline time.Time
78 }
79
80 // IsSatisfiedBy checks whether provided itinerary satisfies this
81 // specification.
82 func (s RouteSpecification) IsSatisfiedBy(itinerary Itinerary) bool {
83 return itinerary.Legs != nil &&
84 s.Origin == itinerary.InitialDepartureLocation() &&
85 s.Destination == itinerary.FinalArrivalLocation()
86 }
87
88 // RoutingStatus describes status of cargo routing.
89 type RoutingStatus int
90
91 // Valid routing statuses.
92 const (
93 NotRouted RoutingStatus = iota
94 Misrouted
95 Routed
96 )
97
98 func (s RoutingStatus) String() string {
99 switch s {
100 case NotRouted:
101 return "Not routed"
102 case Misrouted:
103 return "Misrouted"
104 case Routed:
105 return "Routed"
106 }
107 return ""
108 }
109
110 // TransportStatus describes status of cargo transportation.
111 type TransportStatus int
112
113 // Valid transport statuses.
114 const (
115 NotReceived TransportStatus = iota
116 InPort
117 OnboardCarrier
118 Claimed
119 Unknown
120 )
121
122 func (s TransportStatus) String() string {
123 switch s {
124 case NotReceived:
125 return "Not received"
126 case InPort:
127 return "In port"
128 case OnboardCarrier:
129 return "Onboard carrier"
130 case Claimed:
131 return "Claimed"
132 case Unknown:
133 return "Unknown"
134 }
135 return ""
136 }
+0
-174
examples/shipping/cargo/delivery.go less more
0 package cargo
1
2 import (
3 "time"
4
5 "github.com/go-kit/kit/examples/shipping/location"
6 "github.com/go-kit/kit/examples/shipping/voyage"
7 )
8
9 // Delivery is the actual transportation of the cargo, as opposed to the
10 // customer requirement (RouteSpecification) and the plan (Itinerary).
11 type Delivery struct {
12 Itinerary Itinerary
13 RouteSpecification RouteSpecification
14 RoutingStatus RoutingStatus
15 TransportStatus TransportStatus
16 NextExpectedActivity HandlingActivity
17 LastEvent HandlingEvent
18 LastKnownLocation location.UNLocode
19 CurrentVoyage voyage.Number
20 ETA time.Time
21 IsMisdirected bool
22 IsUnloadedAtDestination bool
23 }
24
25 // UpdateOnRouting creates a new delivery snapshot to reflect changes in
26 // routing, i.e. when the route specification or the itinerary has changed but
27 // no additional handling of the cargo has been performed.
28 func (d Delivery) UpdateOnRouting(rs RouteSpecification, itinerary Itinerary) Delivery {
29 return newDelivery(d.LastEvent, itinerary, rs)
30 }
31
32 // IsOnTrack checks if the delivery is on track.
33 func (d Delivery) IsOnTrack() bool {
34 return d.RoutingStatus == Routed && !d.IsMisdirected
35 }
36
37 // DeriveDeliveryFrom creates a new delivery snapshot based on the complete
38 // handling history of a cargo, as well as its route specification and
39 // itinerary.
40 func DeriveDeliveryFrom(rs RouteSpecification, itinerary Itinerary, history HandlingHistory) Delivery {
41 lastEvent, _ := history.MostRecentlyCompletedEvent()
42 return newDelivery(lastEvent, itinerary, rs)
43 }
44
45 // newDelivery creates a up-to-date delivery based on an handling event,
46 // itinerary and a route specification.
47 func newDelivery(lastEvent HandlingEvent, itinerary Itinerary, rs RouteSpecification) Delivery {
48 var (
49 routingStatus = calculateRoutingStatus(itinerary, rs)
50 transportStatus = calculateTransportStatus(lastEvent)
51 lastKnownLocation = calculateLastKnownLocation(lastEvent)
52 isMisdirected = calculateMisdirectedStatus(lastEvent, itinerary)
53 isUnloadedAtDestination = calculateUnloadedAtDestination(lastEvent, rs)
54 currentVoyage = calculateCurrentVoyage(transportStatus, lastEvent)
55 )
56
57 d := Delivery{
58 LastEvent: lastEvent,
59 Itinerary: itinerary,
60 RouteSpecification: rs,
61 RoutingStatus: routingStatus,
62 TransportStatus: transportStatus,
63 LastKnownLocation: lastKnownLocation,
64 IsMisdirected: isMisdirected,
65 IsUnloadedAtDestination: isUnloadedAtDestination,
66 CurrentVoyage: currentVoyage,
67 }
68
69 d.NextExpectedActivity = calculateNextExpectedActivity(d)
70 d.ETA = calculateETA(d)
71
72 return d
73 }
74
75 // Below are internal functions used when creating a new delivery.
76
77 func calculateRoutingStatus(itinerary Itinerary, rs RouteSpecification) RoutingStatus {
78 if itinerary.Legs == nil {
79 return NotRouted
80 }
81
82 if rs.IsSatisfiedBy(itinerary) {
83 return Routed
84 }
85
86 return Misrouted
87 }
88
89 func calculateMisdirectedStatus(event HandlingEvent, itinerary Itinerary) bool {
90 if event.Activity.Type == NotHandled {
91 return false
92 }
93
94 return !itinerary.IsExpected(event)
95 }
96
97 func calculateUnloadedAtDestination(event HandlingEvent, rs RouteSpecification) bool {
98 if event.Activity.Type == NotHandled {
99 return false
100 }
101
102 return event.Activity.Type == Unload && rs.Destination == event.Activity.Location
103 }
104
105 func calculateTransportStatus(event HandlingEvent) TransportStatus {
106 switch event.Activity.Type {
107 case NotHandled:
108 return NotReceived
109 case Load:
110 return OnboardCarrier
111 case Unload:
112 return InPort
113 case Receive:
114 return InPort
115 case Customs:
116 return InPort
117 case Claim:
118 return Claimed
119 }
120 return Unknown
121 }
122
123 func calculateLastKnownLocation(event HandlingEvent) location.UNLocode {
124 return event.Activity.Location
125 }
126
127 func calculateNextExpectedActivity(d Delivery) HandlingActivity {
128 if !d.IsOnTrack() {
129 return HandlingActivity{}
130 }
131
132 switch d.LastEvent.Activity.Type {
133 case NotHandled:
134 return HandlingActivity{Type: Receive, Location: d.RouteSpecification.Origin}
135 case Receive:
136 l := d.Itinerary.Legs[0]
137 return HandlingActivity{Type: Load, Location: l.LoadLocation, VoyageNumber: l.VoyageNumber}
138 case Load:
139 for _, l := range d.Itinerary.Legs {
140 if l.LoadLocation == d.LastEvent.Activity.Location {
141 return HandlingActivity{Type: Unload, Location: l.UnloadLocation, VoyageNumber: l.VoyageNumber}
142 }
143 }
144 case Unload:
145 for i, l := range d.Itinerary.Legs {
146 if l.UnloadLocation == d.LastEvent.Activity.Location {
147 if i < len(d.Itinerary.Legs)-1 {
148 return HandlingActivity{Type: Load, Location: d.Itinerary.Legs[i+1].LoadLocation, VoyageNumber: d.Itinerary.Legs[i+1].VoyageNumber}
149 }
150
151 return HandlingActivity{Type: Claim, Location: l.UnloadLocation}
152 }
153 }
154 }
155
156 return HandlingActivity{}
157 }
158
159 func calculateCurrentVoyage(transportStatus TransportStatus, event HandlingEvent) voyage.Number {
160 if transportStatus == OnboardCarrier && event.Activity.Type != NotHandled {
161 return event.Activity.VoyageNumber
162 }
163
164 return voyage.Number("")
165 }
166
167 func calculateETA(d Delivery) time.Time {
168 if !d.IsOnTrack() {
169 return time.Time{}
170 }
171
172 return d.Itinerary.FinalArrivalTime()
173 }
+0
-121
examples/shipping/cargo/handling.go less more
0 package cargo
1
2 // TODO: It would make sense to have this in its own package. Unfortunately,
3 // then there would be a circular dependency between the cargo and handling
4 // packages since cargo.Delivery would use handling.HandlingEvent and
5 // handling.HandlingEvent would use cargo.TrackingID. Also,
6 // HandlingEventFactory depends on the cargo repository.
7 //
8 // It would make sense not having the cargo package depend on handling.
9
10 import (
11 "errors"
12 "time"
13
14 "github.com/go-kit/kit/examples/shipping/location"
15 "github.com/go-kit/kit/examples/shipping/voyage"
16 )
17
18 // HandlingActivity represents how and where a cargo can be handled, and can
19 // be used to express predictions about what is expected to happen to a cargo
20 // in the future.
21 type HandlingActivity struct {
22 Type HandlingEventType
23 Location location.UNLocode
24 VoyageNumber voyage.Number
25 }
26
27 // HandlingEvent is used to register the event when, for instance, a cargo is
28 // unloaded from a carrier at a some location at a given time.
29 type HandlingEvent struct {
30 TrackingID TrackingID
31 Activity HandlingActivity
32 }
33
34 // HandlingEventType describes type of a handling event.
35 type HandlingEventType int
36
37 // Valid handling event types.
38 const (
39 NotHandled HandlingEventType = iota
40 Load
41 Unload
42 Receive
43 Claim
44 Customs
45 )
46
47 func (t HandlingEventType) String() string {
48 switch t {
49 case NotHandled:
50 return "Not Handled"
51 case Load:
52 return "Load"
53 case Unload:
54 return "Unload"
55 case Receive:
56 return "Receive"
57 case Claim:
58 return "Claim"
59 case Customs:
60 return "Customs"
61 }
62
63 return ""
64 }
65
66 // HandlingHistory is the handling history of a cargo.
67 type HandlingHistory struct {
68 HandlingEvents []HandlingEvent
69 }
70
71 // MostRecentlyCompletedEvent returns most recently completed handling event.
72 func (h HandlingHistory) MostRecentlyCompletedEvent() (HandlingEvent, error) {
73 if len(h.HandlingEvents) == 0 {
74 return HandlingEvent{}, errors.New("delivery history is empty")
75 }
76
77 return h.HandlingEvents[len(h.HandlingEvents)-1], nil
78 }
79
80 // HandlingEventRepository provides access a handling event store.
81 type HandlingEventRepository interface {
82 Store(e HandlingEvent)
83 QueryHandlingHistory(TrackingID) HandlingHistory
84 }
85
86 // HandlingEventFactory creates handling events.
87 type HandlingEventFactory struct {
88 CargoRepository Repository
89 VoyageRepository voyage.Repository
90 LocationRepository location.Repository
91 }
92
93 // CreateHandlingEvent creates a validated handling event.
94 func (f *HandlingEventFactory) CreateHandlingEvent(registered time.Time, completed time.Time, id TrackingID,
95 voyageNumber voyage.Number, unLocode location.UNLocode, eventType HandlingEventType) (HandlingEvent, error) {
96
97 if _, err := f.CargoRepository.Find(id); err != nil {
98 return HandlingEvent{}, err
99 }
100
101 if _, err := f.VoyageRepository.Find(voyageNumber); err != nil {
102 // TODO: This is pretty ugly, but when creating a Receive event, the voyage number is not known.
103 if len(voyageNumber) > 0 {
104 return HandlingEvent{}, err
105 }
106 }
107
108 if _, err := f.LocationRepository.Find(unLocode); err != nil {
109 return HandlingEvent{}, err
110 }
111
112 return HandlingEvent{
113 TrackingID: id,
114 Activity: HandlingActivity{
115 Type: eventType,
116 Location: unLocode,
117 VoyageNumber: voyageNumber,
118 },
119 }, nil
120 }
+0
-91
examples/shipping/cargo/itinerary.go less more
0 package cargo
1
2 import (
3 "time"
4
5 "github.com/go-kit/kit/examples/shipping/location"
6 "github.com/go-kit/kit/examples/shipping/voyage"
7 )
8
9 // Leg describes the transportation between two locations on a voyage.
10 type Leg struct {
11 VoyageNumber voyage.Number `json:"voyage_number"`
12 LoadLocation location.UNLocode `json:"from"`
13 UnloadLocation location.UNLocode `json:"to"`
14 LoadTime time.Time `json:"load_time"`
15 UnloadTime time.Time `json:"unload_time"`
16 }
17
18 // NewLeg creates a new itinerary leg.
19 func NewLeg(voyageNumber voyage.Number, loadLocation, unloadLocation location.UNLocode, loadTime, unloadTime time.Time) Leg {
20 return Leg{
21 VoyageNumber: voyageNumber,
22 LoadLocation: loadLocation,
23 UnloadLocation: unloadLocation,
24 LoadTime: loadTime,
25 UnloadTime: unloadTime,
26 }
27 }
28
29 // Itinerary specifies steps required to transport a cargo from its origin to
30 // destination.
31 type Itinerary struct {
32 Legs []Leg `json:"legs"`
33 }
34
35 // InitialDepartureLocation returns the start of the itinerary.
36 func (i Itinerary) InitialDepartureLocation() location.UNLocode {
37 if i.IsEmpty() {
38 return location.UNLocode("")
39 }
40 return i.Legs[0].LoadLocation
41 }
42
43 // FinalArrivalLocation returns the end of the itinerary.
44 func (i Itinerary) FinalArrivalLocation() location.UNLocode {
45 if i.IsEmpty() {
46 return location.UNLocode("")
47 }
48 return i.Legs[len(i.Legs)-1].UnloadLocation
49 }
50
51 // FinalArrivalTime returns the expected arrival time at final destination.
52 func (i Itinerary) FinalArrivalTime() time.Time {
53 return i.Legs[len(i.Legs)-1].UnloadTime
54 }
55
56 // IsEmpty checks if the itinerary contains at least one leg.
57 func (i Itinerary) IsEmpty() bool {
58 return i.Legs == nil || len(i.Legs) == 0
59 }
60
61 // IsExpected checks if the given handling event is expected when executing
62 // this itinerary.
63 func (i Itinerary) IsExpected(event HandlingEvent) bool {
64 if i.IsEmpty() {
65 return true
66 }
67
68 switch event.Activity.Type {
69 case Receive:
70 return i.InitialDepartureLocation() == event.Activity.Location
71 case Load:
72 for _, l := range i.Legs {
73 if l.LoadLocation == event.Activity.Location && l.VoyageNumber == event.Activity.VoyageNumber {
74 return true
75 }
76 }
77 return false
78 case Unload:
79 for _, l := range i.Legs {
80 if l.UnloadLocation == event.Activity.Location && l.VoyageNumber == event.Activity.VoyageNumber {
81 return true
82 }
83 }
84 return false
85 case Claim:
86 return i.FinalArrivalLocation() == event.Activity.Location
87 }
88
89 return true
90 }
+0
-34
examples/shipping/handling/endpoint.go less more
0 package handling
1
2 import (
3 "context"
4 "time"
5
6 "github.com/go-kit/kit/endpoint"
7
8 "github.com/go-kit/kit/examples/shipping/cargo"
9 "github.com/go-kit/kit/examples/shipping/location"
10 "github.com/go-kit/kit/examples/shipping/voyage"
11 )
12
13 type registerIncidentRequest struct {
14 ID cargo.TrackingID
15 Location location.UNLocode
16 Voyage voyage.Number
17 EventType cargo.HandlingEventType
18 CompletionTime time.Time
19 }
20
21 type registerIncidentResponse struct {
22 Err error `json:"error,omitempty"`
23 }
24
25 func (r registerIncidentResponse) error() error { return r.Err }
26
27 func makeRegisterIncidentEndpoint(hs Service) endpoint.Endpoint {
28 return func(ctx context.Context, request interface{}) (interface{}, error) {
29 req := request.(registerIncidentRequest)
30 err := hs.RegisterHandlingEvent(req.CompletionTime, req.ID, req.Voyage, req.Location, req.EventType)
31 return registerIncidentResponse{Err: err}, nil
32 }
33 }
+0
-37
examples/shipping/handling/instrumenting.go less more
0 package handling
1
2 import (
3 "time"
4
5 "github.com/go-kit/kit/metrics"
6
7 "github.com/go-kit/kit/examples/shipping/cargo"
8 "github.com/go-kit/kit/examples/shipping/location"
9 "github.com/go-kit/kit/examples/shipping/voyage"
10 )
11
12 type instrumentingService struct {
13 requestCount metrics.Counter
14 requestLatency metrics.Histogram
15 Service
16 }
17
18 // NewInstrumentingService returns an instance of an instrumenting Service.
19 func NewInstrumentingService(counter metrics.Counter, latency metrics.Histogram, s Service) Service {
20 return &instrumentingService{
21 requestCount: counter,
22 requestLatency: latency,
23 Service: s,
24 }
25 }
26
27 func (s *instrumentingService) RegisterHandlingEvent(completed time.Time, id cargo.TrackingID, voyageNumber voyage.Number,
28 loc location.UNLocode, eventType cargo.HandlingEventType) error {
29
30 defer func(begin time.Time) {
31 s.requestCount.With("method", "register_incident").Add(1)
32 s.requestLatency.With("method", "register_incident").Observe(time.Since(begin).Seconds())
33 }(time.Now())
34
35 return s.Service.RegisterHandlingEvent(completed, id, voyageNumber, loc, eventType)
36 }
+0
-38
examples/shipping/handling/logging.go less more
0 package handling
1
2 import (
3 "time"
4
5 "github.com/go-kit/kit/log"
6
7 "github.com/go-kit/kit/examples/shipping/cargo"
8 "github.com/go-kit/kit/examples/shipping/location"
9 "github.com/go-kit/kit/examples/shipping/voyage"
10 )
11
12 type loggingService struct {
13 logger log.Logger
14 Service
15 }
16
17 // NewLoggingService returns a new instance of a logging Service.
18 func NewLoggingService(logger log.Logger, s Service) Service {
19 return &loggingService{logger, s}
20 }
21
22 func (s *loggingService) RegisterHandlingEvent(completed time.Time, id cargo.TrackingID, voyageNumber voyage.Number,
23 unLocode location.UNLocode, eventType cargo.HandlingEventType) (err error) {
24 defer func(begin time.Time) {
25 s.logger.Log(
26 "method", "register_incident",
27 "tracking_id", id,
28 "location", unLocode,
29 "voyage", voyageNumber,
30 "event_type", eventType,
31 "completion_time", completed,
32 "took", time.Since(begin),
33 "err", err,
34 )
35 }(time.Now())
36 return s.Service.RegisterHandlingEvent(completed, id, voyageNumber, unLocode, eventType)
37 }
+0
-76
examples/shipping/handling/service.go less more
0 // Package handling provides the use-case for registering incidents. Used by
1 // views facing the people handling the cargo along its route.
2 package handling
3
4 import (
5 "errors"
6 "time"
7
8 "github.com/go-kit/kit/examples/shipping/cargo"
9 "github.com/go-kit/kit/examples/shipping/inspection"
10 "github.com/go-kit/kit/examples/shipping/location"
11 "github.com/go-kit/kit/examples/shipping/voyage"
12 )
13
14 // ErrInvalidArgument is returned when one or more arguments are invalid.
15 var ErrInvalidArgument = errors.New("invalid argument")
16
17 // EventHandler provides a means of subscribing to registered handling events.
18 type EventHandler interface {
19 CargoWasHandled(cargo.HandlingEvent)
20 }
21
22 // Service provides handling operations.
23 type Service interface {
24 // RegisterHandlingEvent registers a handling event in the system, and
25 // notifies interested parties that a cargo has been handled.
26 RegisterHandlingEvent(completed time.Time, id cargo.TrackingID, voyageNumber voyage.Number,
27 unLocode location.UNLocode, eventType cargo.HandlingEventType) error
28 }
29
30 type service struct {
31 handlingEventRepository cargo.HandlingEventRepository
32 handlingEventFactory cargo.HandlingEventFactory
33 handlingEventHandler EventHandler
34 }
35
36 func (s *service) RegisterHandlingEvent(completed time.Time, id cargo.TrackingID, voyageNumber voyage.Number,
37 loc location.UNLocode, eventType cargo.HandlingEventType) error {
38 if completed.IsZero() || id == "" || loc == "" || eventType == cargo.NotHandled {
39 return ErrInvalidArgument
40 }
41
42 e, err := s.handlingEventFactory.CreateHandlingEvent(time.Now(), completed, id, voyageNumber, loc, eventType)
43 if err != nil {
44 return err
45 }
46
47 s.handlingEventRepository.Store(e)
48 s.handlingEventHandler.CargoWasHandled(e)
49
50 return nil
51 }
52
53 // NewService creates a handling event service with necessary dependencies.
54 func NewService(r cargo.HandlingEventRepository, f cargo.HandlingEventFactory, h EventHandler) Service {
55 return &service{
56 handlingEventRepository: r,
57 handlingEventFactory: f,
58 handlingEventHandler: h,
59 }
60 }
61
62 type handlingEventHandler struct {
63 InspectionService inspection.Service
64 }
65
66 func (h *handlingEventHandler) CargoWasHandled(event cargo.HandlingEvent) {
67 h.InspectionService.InspectCargo(event.TrackingID)
68 }
69
70 // NewEventHandler returns a new instance of a EventHandler.
71 func NewEventHandler(s inspection.Service) EventHandler {
72 return &handlingEventHandler{
73 InspectionService: s,
74 }
75 }
+0
-100
examples/shipping/handling/transport.go less more
0 package handling
1
2 import (
3 "context"
4 "encoding/json"
5 "net/http"
6 "time"
7
8 "github.com/gorilla/mux"
9
10 kitlog "github.com/go-kit/kit/log"
11 kithttp "github.com/go-kit/kit/transport/http"
12
13 "github.com/go-kit/kit/examples/shipping/cargo"
14 "github.com/go-kit/kit/examples/shipping/location"
15 "github.com/go-kit/kit/examples/shipping/voyage"
16 )
17
18 // MakeHandler returns a handler for the handling service.
19 func MakeHandler(hs Service, logger kitlog.Logger) http.Handler {
20 r := mux.NewRouter()
21
22 opts := []kithttp.ServerOption{
23 kithttp.ServerErrorLogger(logger),
24 kithttp.ServerErrorEncoder(encodeError),
25 }
26
27 registerIncidentHandler := kithttp.NewServer(
28 makeRegisterIncidentEndpoint(hs),
29 decodeRegisterIncidentRequest,
30 encodeResponse,
31 opts...,
32 )
33
34 r.Handle("/handling/v1/incidents", registerIncidentHandler).Methods("POST")
35
36 return r
37 }
38
39 func decodeRegisterIncidentRequest(_ context.Context, r *http.Request) (interface{}, error) {
40 var body struct {
41 CompletionTime time.Time `json:"completion_time"`
42 TrackingID string `json:"tracking_id"`
43 VoyageNumber string `json:"voyage"`
44 Location string `json:"location"`
45 EventType string `json:"event_type"`
46 }
47
48 if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
49 return nil, err
50 }
51
52 return registerIncidentRequest{
53 CompletionTime: body.CompletionTime,
54 ID: cargo.TrackingID(body.TrackingID),
55 Voyage: voyage.Number(body.VoyageNumber),
56 Location: location.UNLocode(body.Location),
57 EventType: stringToEventType(body.EventType),
58 }, nil
59 }
60
61 func stringToEventType(s string) cargo.HandlingEventType {
62 types := map[string]cargo.HandlingEventType{
63 cargo.Receive.String(): cargo.Receive,
64 cargo.Load.String(): cargo.Load,
65 cargo.Unload.String(): cargo.Unload,
66 cargo.Customs.String(): cargo.Customs,
67 cargo.Claim.String(): cargo.Claim,
68 }
69 return types[s]
70 }
71
72 func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
73 if e, ok := response.(errorer); ok && e.error() != nil {
74 encodeError(ctx, e.error(), w)
75 return nil
76 }
77 w.Header().Set("Content-Type", "application/json; charset=utf-8")
78 return json.NewEncoder(w).Encode(response)
79 }
80
81 type errorer interface {
82 error() error
83 }
84
85 // encode errors from business-logic
86 func encodeError(_ context.Context, err error, w http.ResponseWriter) {
87 w.Header().Set("Content-Type", "application/json; charset=utf-8")
88 switch err {
89 case cargo.ErrUnknown:
90 w.WriteHeader(http.StatusNotFound)
91 case ErrInvalidArgument:
92 w.WriteHeader(http.StatusBadRequest)
93 default:
94 w.WriteHeader(http.StatusInternalServerError)
95 }
96 json.NewEncoder(w).Encode(map[string]interface{}{
97 "error": err.Error(),
98 })
99 }
+0
-142
examples/shipping/inmem/inmem.go less more
0 // Package inmem provides in-memory implementations of all the domain repositories.
1 package inmem
2
3 import (
4 "sync"
5
6 "github.com/go-kit/kit/examples/shipping/cargo"
7 "github.com/go-kit/kit/examples/shipping/location"
8 "github.com/go-kit/kit/examples/shipping/voyage"
9 )
10
11 type cargoRepository struct {
12 mtx sync.RWMutex
13 cargos map[cargo.TrackingID]*cargo.Cargo
14 }
15
16 func (r *cargoRepository) Store(c *cargo.Cargo) error {
17 r.mtx.Lock()
18 defer r.mtx.Unlock()
19 r.cargos[c.TrackingID] = c
20 return nil
21 }
22
23 func (r *cargoRepository) Find(id cargo.TrackingID) (*cargo.Cargo, error) {
24 r.mtx.RLock()
25 defer r.mtx.RUnlock()
26 if val, ok := r.cargos[id]; ok {
27 return val, nil
28 }
29 return nil, cargo.ErrUnknown
30 }
31
32 func (r *cargoRepository) FindAll() []*cargo.Cargo {
33 r.mtx.RLock()
34 defer r.mtx.RUnlock()
35 c := make([]*cargo.Cargo, 0, len(r.cargos))
36 for _, val := range r.cargos {
37 c = append(c, val)
38 }
39 return c
40 }
41
42 // NewCargoRepository returns a new instance of a in-memory cargo repository.
43 func NewCargoRepository() cargo.Repository {
44 return &cargoRepository{
45 cargos: make(map[cargo.TrackingID]*cargo.Cargo),
46 }
47 }
48
49 type locationRepository struct {
50 locations map[location.UNLocode]*location.Location
51 }
52
53 func (r *locationRepository) Find(locode location.UNLocode) (*location.Location, error) {
54 if l, ok := r.locations[locode]; ok {
55 return l, nil
56 }
57 return nil, location.ErrUnknown
58 }
59
60 func (r *locationRepository) FindAll() []*location.Location {
61 l := make([]*location.Location, 0, len(r.locations))
62 for _, val := range r.locations {
63 l = append(l, val)
64 }
65 return l
66 }
67
68 // NewLocationRepository returns a new instance of a in-memory location repository.
69 func NewLocationRepository() location.Repository {
70 r := &locationRepository{
71 locations: make(map[location.UNLocode]*location.Location),
72 }
73
74 r.locations[location.SESTO] = location.Stockholm
75 r.locations[location.AUMEL] = location.Melbourne
76 r.locations[location.CNHKG] = location.Hongkong
77 r.locations[location.JNTKO] = location.Tokyo
78 r.locations[location.NLRTM] = location.Rotterdam
79 r.locations[location.DEHAM] = location.Hamburg
80
81 return r
82 }
83
84 type voyageRepository struct {
85 voyages map[voyage.Number]*voyage.Voyage
86 }
87
88 func (r *voyageRepository) Find(voyageNumber voyage.Number) (*voyage.Voyage, error) {
89 if v, ok := r.voyages[voyageNumber]; ok {
90 return v, nil
91 }
92
93 return nil, voyage.ErrUnknown
94 }
95
96 // NewVoyageRepository returns a new instance of a in-memory voyage repository.
97 func NewVoyageRepository() voyage.Repository {
98 r := &voyageRepository{
99 voyages: make(map[voyage.Number]*voyage.Voyage),
100 }
101
102 r.voyages[voyage.V100.Number] = voyage.V100
103 r.voyages[voyage.V300.Number] = voyage.V300
104 r.voyages[voyage.V400.Number] = voyage.V400
105
106 r.voyages[voyage.V0100S.Number] = voyage.V0100S
107 r.voyages[voyage.V0200T.Number] = voyage.V0200T
108 r.voyages[voyage.V0300A.Number] = voyage.V0300A
109 r.voyages[voyage.V0301S.Number] = voyage.V0301S
110 r.voyages[voyage.V0400S.Number] = voyage.V0400S
111
112 return r
113 }
114
115 type handlingEventRepository struct {
116 mtx sync.RWMutex
117 events map[cargo.TrackingID][]cargo.HandlingEvent
118 }
119
120 func (r *handlingEventRepository) Store(e cargo.HandlingEvent) {
121 r.mtx.Lock()
122 defer r.mtx.Unlock()
123 // Make array if it's the first event with this tracking ID.
124 if _, ok := r.events[e.TrackingID]; !ok {
125 r.events[e.TrackingID] = make([]cargo.HandlingEvent, 0)
126 }
127 r.events[e.TrackingID] = append(r.events[e.TrackingID], e)
128 }
129
130 func (r *handlingEventRepository) QueryHandlingHistory(id cargo.TrackingID) cargo.HandlingHistory {
131 r.mtx.RLock()
132 defer r.mtx.RUnlock()
133 return cargo.HandlingHistory{HandlingEvents: r.events[id]}
134 }
135
136 // NewHandlingEventRepository returns a new instance of a in-memory handling event repository.
137 func NewHandlingEventRepository() cargo.HandlingEventRepository {
138 return &handlingEventRepository{
139 events: make(map[cargo.TrackingID][]cargo.HandlingEvent),
140 }
141 }
+0
-53
examples/shipping/inspection/inspection.go less more
0 // Package inspection provides means to inspect cargos.
1 package inspection
2
3 import (
4 "github.com/go-kit/kit/examples/shipping/cargo"
5 )
6
7 // EventHandler provides means of subscribing to inspection events.
8 type EventHandler interface {
9 CargoWasMisdirected(*cargo.Cargo)
10 CargoHasArrived(*cargo.Cargo)
11 }
12
13 // Service provides cargo inspection operations.
14 type Service interface {
15 // InspectCargo inspects cargo and send relevant notifications to
16 // interested parties, for example if a cargo has been misdirected, or
17 // unloaded at the final destination.
18 InspectCargo(id cargo.TrackingID)
19 }
20
21 type service struct {
22 cargos cargo.Repository
23 events cargo.HandlingEventRepository
24 handler EventHandler
25 }
26
27 // TODO: Should be transactional
28 func (s *service) InspectCargo(id cargo.TrackingID) {
29 c, err := s.cargos.Find(id)
30 if err != nil {
31 return
32 }
33
34 h := s.events.QueryHandlingHistory(id)
35
36 c.DeriveDeliveryProgress(h)
37
38 if c.Delivery.IsMisdirected {
39 s.handler.CargoWasMisdirected(c)
40 }
41
42 if c.Delivery.IsUnloadedAtDestination {
43 s.handler.CargoHasArrived(c)
44 }
45
46 s.cargos.Store(c)
47 }
48
49 // NewService creates a inspection service with necessary dependencies.
50 func NewService(cargos cargo.Repository, events cargo.HandlingEventRepository, handler EventHandler) Service {
51 return &service{cargos, events, handler}
52 }
+0
-29
examples/shipping/location/location.go less more
0 // Package location provides the Location aggregate.
1 package location
2
3 import (
4 "errors"
5 )
6
7 // UNLocode is the United Nations location code that uniquely identifies a
8 // particular location.
9 //
10 // http://www.unece.org/cefact/locode/
11 // http://www.unece.org/cefact/locode/DocColumnDescription.htm#LOCODE
12 type UNLocode string
13
14 // Location is a location is our model is stops on a journey, such as cargo
15 // origin or destination, or carrier movement endpoints.
16 type Location struct {
17 UNLocode UNLocode
18 Name string
19 }
20
21 // ErrUnknown is used when a location could not be found.
22 var ErrUnknown = errors.New("unknown location")
23
24 // Repository provides access a location store.
25 type Repository interface {
26 Find(locode UNLocode) (*Location, error)
27 FindAll() []*Location
28 }
+0
-27
examples/shipping/location/sample_locations.go less more
0 package location
1
2 // Sample UN locodes.
3 var (
4 SESTO UNLocode = "SESTO"
5 AUMEL UNLocode = "AUMEL"
6 CNHKG UNLocode = "CNHKG"
7 USNYC UNLocode = "USNYC"
8 USCHI UNLocode = "USCHI"
9 JNTKO UNLocode = "JNTKO"
10 DEHAM UNLocode = "DEHAM"
11 NLRTM UNLocode = "NLRTM"
12 FIHEL UNLocode = "FIHEL"
13 )
14
15 // Sample locations.
16 var (
17 Stockholm = &Location{SESTO, "Stockholm"}
18 Melbourne = &Location{AUMEL, "Melbourne"}
19 Hongkong = &Location{CNHKG, "Hongkong"}
20 NewYork = &Location{USNYC, "New York"}
21 Chicago = &Location{USCHI, "Chicago"}
22 Tokyo = &Location{JNTKO, "Tokyo"}
23 Hamburg = &Location{DEHAM, "Hamburg"}
24 Rotterdam = &Location{NLRTM, "Rotterdam"}
25 Helsinki = &Location{FIHEL, "Helsinki"}
26 )
+0
-200
examples/shipping/main.go less more
0 package main
1
2 import (
3 "context"
4 "flag"
5 "fmt"
6 "net/http"
7 "os"
8 "os/signal"
9 "syscall"
10 "time"
11
12 stdprometheus "github.com/prometheus/client_golang/prometheus"
13 "github.com/prometheus/client_golang/prometheus/promhttp"
14
15 "github.com/go-kit/kit/log"
16 kitprometheus "github.com/go-kit/kit/metrics/prometheus"
17
18 "github.com/go-kit/kit/examples/shipping/booking"
19 "github.com/go-kit/kit/examples/shipping/cargo"
20 "github.com/go-kit/kit/examples/shipping/handling"
21 "github.com/go-kit/kit/examples/shipping/inmem"
22 "github.com/go-kit/kit/examples/shipping/inspection"
23 "github.com/go-kit/kit/examples/shipping/location"
24 "github.com/go-kit/kit/examples/shipping/routing"
25 "github.com/go-kit/kit/examples/shipping/tracking"
26 )
27
28 const (
29 defaultPort = "8080"
30 defaultRoutingServiceURL = "http://localhost:7878"
31 )
32
33 func main() {
34 var (
35 addr = envString("PORT", defaultPort)
36 rsurl = envString("ROUTINGSERVICE_URL", defaultRoutingServiceURL)
37
38 httpAddr = flag.String("http.addr", ":"+addr, "HTTP listen address")
39 routingServiceURL = flag.String("service.routing", rsurl, "routing service URL")
40
41 ctx = context.Background()
42 )
43
44 flag.Parse()
45
46 var logger log.Logger
47 logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
48 logger = log.With(logger, "ts", log.DefaultTimestampUTC)
49
50 var (
51 cargos = inmem.NewCargoRepository()
52 locations = inmem.NewLocationRepository()
53 voyages = inmem.NewVoyageRepository()
54 handlingEvents = inmem.NewHandlingEventRepository()
55 )
56
57 // Configure some questionable dependencies.
58 var (
59 handlingEventFactory = cargo.HandlingEventFactory{
60 CargoRepository: cargos,
61 VoyageRepository: voyages,
62 LocationRepository: locations,
63 }
64 handlingEventHandler = handling.NewEventHandler(
65 inspection.NewService(cargos, handlingEvents, nil),
66 )
67 )
68
69 // Facilitate testing by adding some cargos.
70 storeTestData(cargos)
71
72 fieldKeys := []string{"method"}
73
74 var rs routing.Service
75 rs = routing.NewProxyingMiddleware(ctx, *routingServiceURL)(rs)
76
77 var bs booking.Service
78 bs = booking.NewService(cargos, locations, handlingEvents, rs)
79 bs = booking.NewLoggingService(log.With(logger, "component", "booking"), bs)
80 bs = booking.NewInstrumentingService(
81 kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
82 Namespace: "api",
83 Subsystem: "booking_service",
84 Name: "request_count",
85 Help: "Number of requests received.",
86 }, fieldKeys),
87 kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
88 Namespace: "api",
89 Subsystem: "booking_service",
90 Name: "request_latency_microseconds",
91 Help: "Total duration of requests in microseconds.",
92 }, fieldKeys),
93 bs,
94 )
95
96 var ts tracking.Service
97 ts = tracking.NewService(cargos, handlingEvents)
98 ts = tracking.NewLoggingService(log.With(logger, "component", "tracking"), ts)
99 ts = tracking.NewInstrumentingService(
100 kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
101 Namespace: "api",
102 Subsystem: "tracking_service",
103 Name: "request_count",
104 Help: "Number of requests received.",
105 }, fieldKeys),
106 kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
107 Namespace: "api",
108 Subsystem: "tracking_service",
109 Name: "request_latency_microseconds",
110 Help: "Total duration of requests in microseconds.",
111 }, fieldKeys),
112 ts,
113 )
114
115 var hs handling.Service
116 hs = handling.NewService(handlingEvents, handlingEventFactory, handlingEventHandler)
117 hs = handling.NewLoggingService(log.With(logger, "component", "handling"), hs)
118 hs = handling.NewInstrumentingService(
119 kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
120 Namespace: "api",
121 Subsystem: "handling_service",
122 Name: "request_count",
123 Help: "Number of requests received.",
124 }, fieldKeys),
125 kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
126 Namespace: "api",
127 Subsystem: "handling_service",
128 Name: "request_latency_microseconds",
129 Help: "Total duration of requests in microseconds.",
130 }, fieldKeys),
131 hs,
132 )
133
134 httpLogger := log.With(logger, "component", "http")
135
136 mux := http.NewServeMux()
137
138 mux.Handle("/booking/v1/", booking.MakeHandler(bs, httpLogger))
139 mux.Handle("/tracking/v1/", tracking.MakeHandler(ts, httpLogger))
140 mux.Handle("/handling/v1/", handling.MakeHandler(hs, httpLogger))
141
142 http.Handle("/", accessControl(mux))
143 http.Handle("/metrics", promhttp.Handler())
144
145 errs := make(chan error, 2)
146 go func() {
147 logger.Log("transport", "http", "address", *httpAddr, "msg", "listening")
148 errs <- http.ListenAndServe(*httpAddr, nil)
149 }()
150 go func() {
151 c := make(chan os.Signal)
152 signal.Notify(c, syscall.SIGINT)
153 errs <- fmt.Errorf("%s", <-c)
154 }()
155
156 logger.Log("terminated", <-errs)
157 }
158
159 func accessControl(h http.Handler) http.Handler {
160 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
161 w.Header().Set("Access-Control-Allow-Origin", "*")
162 w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
163 w.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type")
164
165 if r.Method == "OPTIONS" {
166 return
167 }
168
169 h.ServeHTTP(w, r)
170 })
171 }
172
173 func envString(env, fallback string) string {
174 e := os.Getenv(env)
175 if e == "" {
176 return fallback
177 }
178 return e
179 }
180
181 func storeTestData(r cargo.Repository) {
182 test1 := cargo.New("FTL456", cargo.RouteSpecification{
183 Origin: location.AUMEL,
184 Destination: location.SESTO,
185 ArrivalDeadline: time.Now().AddDate(0, 0, 7),
186 })
187 if err := r.Store(test1); err != nil {
188 panic(err)
189 }
190
191 test2 := cargo.New("ABC123", cargo.RouteSpecification{
192 Origin: location.SESTO,
193 Destination: location.CNHKG,
194 ArrivalDeadline: time.Now().AddDate(0, 0, 14),
195 })
196 if err := r.Store(test2); err != nil {
197 panic(err)
198 }
199 }
+0
-117
examples/shipping/routing/proxying.go less more
0 package routing
1
2 import (
3 "context"
4 "encoding/json"
5 "net/http"
6 "net/url"
7 "time"
8
9 "github.com/go-kit/kit/circuitbreaker"
10 "github.com/go-kit/kit/endpoint"
11 kithttp "github.com/go-kit/kit/transport/http"
12
13 "github.com/go-kit/kit/examples/shipping/cargo"
14 "github.com/go-kit/kit/examples/shipping/location"
15 "github.com/go-kit/kit/examples/shipping/voyage"
16 )
17
18 type proxyService struct {
19 context.Context
20 FetchRoutesEndpoint endpoint.Endpoint
21 Service
22 }
23
24 func (s proxyService) FetchRoutesForSpecification(rs cargo.RouteSpecification) []cargo.Itinerary {
25 response, err := s.FetchRoutesEndpoint(s.Context, fetchRoutesRequest{
26 From: string(rs.Origin),
27 To: string(rs.Destination),
28 })
29 if err != nil {
30 return []cargo.Itinerary{}
31 }
32
33 resp := response.(fetchRoutesResponse)
34
35 var itineraries []cargo.Itinerary
36 for _, r := range resp.Paths {
37 var legs []cargo.Leg
38 for _, e := range r.Edges {
39 legs = append(legs, cargo.Leg{
40 VoyageNumber: voyage.Number(e.Voyage),
41 LoadLocation: location.UNLocode(e.Origin),
42 UnloadLocation: location.UNLocode(e.Destination),
43 LoadTime: e.Departure,
44 UnloadTime: e.Arrival,
45 })
46 }
47
48 itineraries = append(itineraries, cargo.Itinerary{Legs: legs})
49 }
50
51 return itineraries
52 }
53
54 // ServiceMiddleware defines a middleware for a routing service.
55 type ServiceMiddleware func(Service) Service
56
57 // NewProxyingMiddleware returns a new instance of a proxying middleware.
58 func NewProxyingMiddleware(ctx context.Context, proxyURL string) ServiceMiddleware {
59 return func(next Service) Service {
60 var e endpoint.Endpoint
61 e = makeFetchRoutesEndpoint(ctx, proxyURL)
62 e = circuitbreaker.Hystrix("fetch-routes")(e)
63 return proxyService{ctx, e, next}
64 }
65 }
66
67 type fetchRoutesRequest struct {
68 From string
69 To string
70 }
71
72 type fetchRoutesResponse struct {
73 Paths []struct {
74 Edges []struct {
75 Origin string `json:"origin"`
76 Destination string `json:"destination"`
77 Voyage string `json:"voyage"`
78 Departure time.Time `json:"departure"`
79 Arrival time.Time `json:"arrival"`
80 } `json:"edges"`
81 } `json:"paths"`
82 }
83
84 func makeFetchRoutesEndpoint(ctx context.Context, instance string) endpoint.Endpoint {
85 u, err := url.Parse(instance)
86 if err != nil {
87 panic(err)
88 }
89 if u.Path == "" {
90 u.Path = "/paths"
91 }
92 return kithttp.NewClient(
93 "GET", u,
94 encodeFetchRoutesRequest,
95 decodeFetchRoutesResponse,
96 ).Endpoint()
97 }
98
99 func decodeFetchRoutesResponse(_ context.Context, resp *http.Response) (interface{}, error) {
100 var response fetchRoutesResponse
101 if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
102 return nil, err
103 }
104 return response, nil
105 }
106
107 func encodeFetchRoutesRequest(_ context.Context, r *http.Request, request interface{}) error {
108 req := request.(fetchRoutesRequest)
109
110 vals := r.URL.Query()
111 vals.Add("from", req.From)
112 vals.Add("to", req.To)
113 r.URL.RawQuery = vals.Encode()
114
115 return nil
116 }
+0
-15
examples/shipping/routing/routing.go less more
0 // Package routing provides the routing domain service. It does not actually
1 // implement the routing service but merely acts as a proxy for a separate
2 // bounded context.
3 package routing
4
5 import (
6 "github.com/go-kit/kit/examples/shipping/cargo"
7 )
8
9 // Service provides access to an external routing service.
10 type Service interface {
11 // FetchRoutesForSpecification finds all possible routes that satisfy a
12 // given specification.
13 FetchRoutesForSpecification(rs cargo.RouteSpecification) []cargo.Itinerary
14 }
+0
-26
examples/shipping/tracking/endpoint.go less more
0 package tracking
1
2 import (
3 "context"
4
5 "github.com/go-kit/kit/endpoint"
6 )
7
8 type trackCargoRequest struct {
9 ID string
10 }
11
12 type trackCargoResponse struct {
13 Cargo *Cargo `json:"cargo,omitempty"`
14 Err error `json:"error,omitempty"`
15 }
16
17 func (r trackCargoResponse) error() error { return r.Err }
18
19 func makeTrackCargoEndpoint(ts Service) endpoint.Endpoint {
20 return func(ctx context.Context, request interface{}) (interface{}, error) {
21 req := request.(trackCargoRequest)
22 c, err := ts.Track(req.ID)
23 return trackCargoResponse{Cargo: &c, Err: err}, nil
24 }
25 }
+0
-31
examples/shipping/tracking/instrumenting.go less more
0 package tracking
1
2 import (
3 "time"
4
5 "github.com/go-kit/kit/metrics"
6 )
7
8 type instrumentingService struct {
9 requestCount metrics.Counter
10 requestLatency metrics.Histogram
11 Service
12 }
13
14 // NewInstrumentingService returns an instance of an instrumenting Service.
15 func NewInstrumentingService(counter metrics.Counter, latency metrics.Histogram, s Service) Service {
16 return &instrumentingService{
17 requestCount: counter,
18 requestLatency: latency,
19 Service: s,
20 }
21 }
22
23 func (s *instrumentingService) Track(id string) (Cargo, error) {
24 defer func(begin time.Time) {
25 s.requestCount.With("method", "track").Add(1)
26 s.requestLatency.With("method", "track").Observe(time.Since(begin).Seconds())
27 }(time.Now())
28
29 return s.Service.Track(id)
30 }
+0
-24
examples/shipping/tracking/logging.go less more
0 package tracking
1
2 import (
3 "time"
4
5 "github.com/go-kit/kit/log"
6 )
7
8 type loggingService struct {
9 logger log.Logger
10 Service
11 }
12
13 // NewLoggingService returns a new instance of a logging Service.
14 func NewLoggingService(logger log.Logger, s Service) Service {
15 return &loggingService{logger, s}
16 }
17
18 func (s *loggingService) Track(id string) (c Cargo, err error) {
19 defer func(begin time.Time) {
20 s.logger.Log("method", "track", "tracking_id", id, "took", time.Since(begin), "err", err)
21 }(time.Now())
22 return s.Service.Track(id)
23 }
+0
-163
examples/shipping/tracking/service.go less more
0 // Package tracking provides the use-case of tracking a cargo. Used by views
1 // facing the end-user.
2 package tracking
3
4 import (
5 "errors"
6 "fmt"
7 "strings"
8 "time"
9
10 "github.com/go-kit/kit/examples/shipping/cargo"
11 )
12
13 // ErrInvalidArgument is returned when one or more arguments are invalid.
14 var ErrInvalidArgument = errors.New("invalid argument")
15
16 // Service is the interface that provides the basic Track method.
17 type Service interface {
18 // Track returns a cargo matching a tracking ID.
19 Track(id string) (Cargo, error)
20 }
21
22 type service struct {
23 cargos cargo.Repository
24 handlingEvents cargo.HandlingEventRepository
25 }
26
27 func (s *service) Track(id string) (Cargo, error) {
28 if id == "" {
29 return Cargo{}, ErrInvalidArgument
30 }
31 c, err := s.cargos.Find(cargo.TrackingID(id))
32 if err != nil {
33 return Cargo{}, err
34 }
35 return assemble(c, s.handlingEvents), nil
36 }
37
38 // NewService returns a new instance of the default Service.
39 func NewService(cargos cargo.Repository, events cargo.HandlingEventRepository) Service {
40 return &service{
41 cargos: cargos,
42 handlingEvents: events,
43 }
44 }
45
46 // Cargo is a read model for tracking views.
47 type Cargo struct {
48 TrackingID string `json:"tracking_id"`
49 StatusText string `json:"status_text"`
50 Origin string `json:"origin"`
51 Destination string `json:"destination"`
52 ETA time.Time `json:"eta"`
53 NextExpectedActivity string `json:"next_expected_activity"`
54 ArrivalDeadline time.Time `json:"arrival_deadline"`
55 Events []Event `json:"events"`
56 }
57
58 // Leg is a read model for booking views.
59 type Leg struct {
60 VoyageNumber string `json:"voyage_number"`
61 From string `json:"from"`
62 To string `json:"to"`
63 LoadTime time.Time `json:"load_time"`
64 UnloadTime time.Time `json:"unload_time"`
65 }
66
67 // Event is a read model for tracking views.
68 type Event struct {
69 Description string `json:"description"`
70 Expected bool `json:"expected"`
71 }
72
73 func assemble(c *cargo.Cargo, events cargo.HandlingEventRepository) Cargo {
74 return Cargo{
75 TrackingID: string(c.TrackingID),
76 Origin: string(c.Origin),
77 Destination: string(c.RouteSpecification.Destination),
78 ETA: c.Delivery.ETA,
79 NextExpectedActivity: nextExpectedActivity(c),
80 ArrivalDeadline: c.RouteSpecification.ArrivalDeadline,
81 StatusText: assembleStatusText(c),
82 Events: assembleEvents(c, events),
83 }
84 }
85
86 func assembleLegs(c cargo.Cargo) []Leg {
87 var legs []Leg
88 for _, l := range c.Itinerary.Legs {
89 legs = append(legs, Leg{
90 VoyageNumber: string(l.VoyageNumber),
91 From: string(l.LoadLocation),
92 To: string(l.UnloadLocation),
93 LoadTime: l.LoadTime,
94 UnloadTime: l.UnloadTime,
95 })
96 }
97 return legs
98 }
99
100 func nextExpectedActivity(c *cargo.Cargo) string {
101 a := c.Delivery.NextExpectedActivity
102 prefix := "Next expected activity is to"
103
104 switch a.Type {
105 case cargo.Load:
106 return fmt.Sprintf("%s %s cargo onto voyage %s in %s.", prefix, strings.ToLower(a.Type.String()), a.VoyageNumber, a.Location)
107 case cargo.Unload:
108 return fmt.Sprintf("%s %s cargo off of voyage %s in %s.", prefix, strings.ToLower(a.Type.String()), a.VoyageNumber, a.Location)
109 case cargo.NotHandled:
110 return "There are currently no expected activities for this cargo."
111 }
112
113 return fmt.Sprintf("%s %s cargo in %s.", prefix, strings.ToLower(a.Type.String()), a.Location)
114 }
115
116 func assembleStatusText(c *cargo.Cargo) string {
117 switch c.Delivery.TransportStatus {
118 case cargo.NotReceived:
119 return "Not received"
120 case cargo.InPort:
121 return fmt.Sprintf("In port %s", c.Delivery.LastKnownLocation)
122 case cargo.OnboardCarrier:
123 return fmt.Sprintf("Onboard voyage %s", c.Delivery.CurrentVoyage)
124 case cargo.Claimed:
125 return "Claimed"
126 default:
127 return "Unknown"
128 }
129 }
130
131 func assembleEvents(c *cargo.Cargo, handlingEvents cargo.HandlingEventRepository) []Event {
132 h := handlingEvents.QueryHandlingHistory(c.TrackingID)
133
134 var events []Event
135 for _, e := range h.HandlingEvents {
136 var description string
137
138 switch e.Activity.Type {
139 case cargo.NotHandled:
140 description = "Cargo has not yet been received."
141 case cargo.Receive:
142 description = fmt.Sprintf("Received in %s, at %s", e.Activity.Location, time.Now().Format(time.RFC3339))
143 case cargo.Load:
144 description = fmt.Sprintf("Loaded onto voyage %s in %s, at %s.", e.Activity.VoyageNumber, e.Activity.Location, time.Now().Format(time.RFC3339))
145 case cargo.Unload:
146 description = fmt.Sprintf("Unloaded off voyage %s in %s, at %s.", e.Activity.VoyageNumber, e.Activity.Location, time.Now().Format(time.RFC3339))
147 case cargo.Claim:
148 description = fmt.Sprintf("Claimed in %s, at %s.", e.Activity.Location, time.Now().Format(time.RFC3339))
149 case cargo.Customs:
150 description = fmt.Sprintf("Cleared customs in %s, at %s.", e.Activity.Location, time.Now().Format(time.RFC3339))
151 default:
152 description = "[Unknown status]"
153 }
154
155 events = append(events, Event{
156 Description: description,
157 Expected: c.Itinerary.IsExpected(e),
158 })
159 }
160
161 return events
162 }
+0
-74
examples/shipping/tracking/transport.go less more
0 package tracking
1
2 import (
3 "context"
4 "encoding/json"
5 "errors"
6 "net/http"
7
8 "github.com/gorilla/mux"
9
10 kitlog "github.com/go-kit/kit/log"
11 kithttp "github.com/go-kit/kit/transport/http"
12
13 "github.com/go-kit/kit/examples/shipping/cargo"
14 )
15
16 // MakeHandler returns a handler for the tracking service.
17 func MakeHandler(ts Service, logger kitlog.Logger) http.Handler {
18 r := mux.NewRouter()
19
20 opts := []kithttp.ServerOption{
21 kithttp.ServerErrorLogger(logger),
22 kithttp.ServerErrorEncoder(encodeError),
23 }
24
25 trackCargoHandler := kithttp.NewServer(
26 makeTrackCargoEndpoint(ts),
27 decodeTrackCargoRequest,
28 encodeResponse,
29 opts...,
30 )
31
32 r.Handle("/tracking/v1/cargos/{id}", trackCargoHandler).Methods("GET")
33
34 return r
35 }
36
37 func decodeTrackCargoRequest(_ context.Context, r *http.Request) (interface{}, error) {
38 vars := mux.Vars(r)
39 id, ok := vars["id"]
40 if !ok {
41 return nil, errors.New("bad route")
42 }
43 return trackCargoRequest{ID: id}, nil
44 }
45
46 func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
47 if e, ok := response.(errorer); ok && e.error() != nil {
48 encodeError(ctx, e.error(), w)
49 return nil
50 }
51 w.Header().Set("Content-Type", "application/json; charset=utf-8")
52 return json.NewEncoder(w).Encode(response)
53 }
54
55 type errorer interface {
56 error() error
57 }
58
59 // encode errors from business-logic
60 func encodeError(_ context.Context, err error, w http.ResponseWriter) {
61 w.Header().Set("Content-Type", "application/json; charset=utf-8")
62 switch err {
63 case cargo.ErrUnknown:
64 w.WriteHeader(http.StatusNotFound)
65 case ErrInvalidArgument:
66 w.WriteHeader(http.StatusBadRequest)
67 default:
68 w.WriteHeader(http.StatusInternalServerError)
69 }
70 json.NewEncoder(w).Encode(map[string]interface{}{
71 "error": err.Error(),
72 })
73 }
+0
-40
examples/shipping/voyage/sample_voyages.go less more
0 package voyage
1
2 import "github.com/go-kit/kit/examples/shipping/location"
3
4 // A set of sample voyages.
5 var (
6 V100 = New("V100", Schedule{
7 []CarrierMovement{
8 {DepartureLocation: location.CNHKG, ArrivalLocation: location.JNTKO},
9 {DepartureLocation: location.JNTKO, ArrivalLocation: location.USNYC},
10 },
11 })
12
13 V300 = New("V300", Schedule{
14 []CarrierMovement{
15 {DepartureLocation: location.JNTKO, ArrivalLocation: location.NLRTM},
16 {DepartureLocation: location.NLRTM, ArrivalLocation: location.DEHAM},
17 {DepartureLocation: location.DEHAM, ArrivalLocation: location.AUMEL},
18 {DepartureLocation: location.AUMEL, ArrivalLocation: location.JNTKO},
19 },
20 })
21
22 V400 = New("V400", Schedule{
23 []CarrierMovement{
24 {DepartureLocation: location.DEHAM, ArrivalLocation: location.SESTO},
25 {DepartureLocation: location.SESTO, ArrivalLocation: location.FIHEL},
26 {DepartureLocation: location.FIHEL, ArrivalLocation: location.DEHAM},
27 },
28 })
29 )
30
31 // These voyages are hard-coded into the current pathfinder. Make sure
32 // they exist.
33 var (
34 V0100S = New("0100S", Schedule{[]CarrierMovement{}})
35 V0200T = New("0200T", Schedule{[]CarrierMovement{}})
36 V0300A = New("0300A", Schedule{[]CarrierMovement{}})
37 V0301S = New("0301S", Schedule{[]CarrierMovement{}})
38 V0400S = New("0400S", Schedule{[]CarrierMovement{}})
39 )
+0
-44
examples/shipping/voyage/voyage.go less more
0 // Package voyage provides the Voyage aggregate.
1 package voyage
2
3 import (
4 "errors"
5 "time"
6
7 "github.com/go-kit/kit/examples/shipping/location"
8 )
9
10 // Number uniquely identifies a particular Voyage.
11 type Number string
12
13 // Voyage is a uniquely identifiable series of carrier movements.
14 type Voyage struct {
15 Number Number
16 Schedule Schedule
17 }
18
19 // New creates a voyage with a voyage number and a provided schedule.
20 func New(n Number, s Schedule) *Voyage {
21 return &Voyage{Number: n, Schedule: s}
22 }
23
24 // Schedule describes a voyage schedule.
25 type Schedule struct {
26 CarrierMovements []CarrierMovement
27 }
28
29 // CarrierMovement is a vessel voyage from one location to another.
30 type CarrierMovement struct {
31 DepartureLocation location.UNLocode
32 ArrivalLocation location.UNLocode
33 DepartureTime time.Time
34 ArrivalTime time.Time
35 }
36
37 // ErrUnknown is used when a voyage could not be found.
38 var ErrUnknown = errors.New("unknown voyage")
39
40 // Repository provides access a voyage store.
41 type Repository interface {
42 Find(Number) (*Voyage, error)
43 }
+0
-111
examples/stringsvc1/main.go less more
0 package main
1
2 import (
3 "context"
4 "encoding/json"
5 "errors"
6 "log"
7 "net/http"
8 "strings"
9
10 "github.com/go-kit/kit/endpoint"
11 httptransport "github.com/go-kit/kit/transport/http"
12 )
13
14 // StringService provides operations on strings.
15 type StringService interface {
16 Uppercase(string) (string, error)
17 Count(string) int
18 }
19
20 type stringService struct{}
21
22 func (stringService) Uppercase(s string) (string, error) {
23 if s == "" {
24 return "", ErrEmpty
25 }
26 return strings.ToUpper(s), nil
27 }
28
29 func (stringService) Count(s string) int {
30 return len(s)
31 }
32
33 func main() {
34 svc := stringService{}
35
36 uppercaseHandler := httptransport.NewServer(
37 makeUppercaseEndpoint(svc),
38 decodeUppercaseRequest,
39 encodeResponse,
40 )
41
42 countHandler := httptransport.NewServer(
43 makeCountEndpoint(svc),
44 decodeCountRequest,
45 encodeResponse,
46 )
47
48 http.Handle("/uppercase", uppercaseHandler)
49 http.Handle("/count", countHandler)
50 log.Fatal(http.ListenAndServe(":8080", nil))
51 }
52
53 func makeUppercaseEndpoint(svc StringService) endpoint.Endpoint {
54 return func(ctx context.Context, request interface{}) (interface{}, error) {
55 req := request.(uppercaseRequest)
56 v, err := svc.Uppercase(req.S)
57 if err != nil {
58 return uppercaseResponse{v, err.Error()}, nil
59 }
60 return uppercaseResponse{v, ""}, nil
61 }
62 }
63
64 func makeCountEndpoint(svc StringService) endpoint.Endpoint {
65 return func(ctx context.Context, request interface{}) (interface{}, error) {
66 req := request.(countRequest)
67 v := svc.Count(req.S)
68 return countResponse{v}, nil
69 }
70 }
71
72 func decodeUppercaseRequest(_ context.Context, r *http.Request) (interface{}, error) {
73 var request uppercaseRequest
74 if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
75 return nil, err
76 }
77 return request, nil
78 }
79
80 func decodeCountRequest(_ context.Context, r *http.Request) (interface{}, error) {
81 var request countRequest
82 if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
83 return nil, err
84 }
85 return request, nil
86 }
87
88 func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
89 return json.NewEncoder(w).Encode(response)
90 }
91
92 type uppercaseRequest struct {
93 S string `json:"s"`
94 }
95
96 type uppercaseResponse struct {
97 V string `json:"v"`
98 Err string `json:"err,omitempty"` // errors don't define JSON marshaling
99 }
100
101 type countRequest struct {
102 S string `json:"s"`
103 }
104
105 type countResponse struct {
106 V int `json:"v"`
107 }
108
109 // ErrEmpty is returned when an input string is empty.
110 var ErrEmpty = errors.New("empty string")
+0
-38
examples/stringsvc2/instrumenting.go less more
0 package main
1
2 import (
3 "fmt"
4 "time"
5
6 "github.com/go-kit/kit/metrics"
7 )
8
9 type instrumentingMiddleware struct {
10 requestCount metrics.Counter
11 requestLatency metrics.Histogram
12 countResult metrics.Histogram
13 next StringService
14 }
15
16 func (mw instrumentingMiddleware) Uppercase(s string) (output string, err error) {
17 defer func(begin time.Time) {
18 lvs := []string{"method", "uppercase", "error", fmt.Sprint(err != nil)}
19 mw.requestCount.With(lvs...).Add(1)
20 mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
21 }(time.Now())
22
23 output, err = mw.next.Uppercase(s)
24 return
25 }
26
27 func (mw instrumentingMiddleware) Count(s string) (n int) {
28 defer func(begin time.Time) {
29 lvs := []string{"method", "count", "error", "false"}
30 mw.requestCount.With(lvs...).Add(1)
31 mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
32 mw.countResult.Observe(float64(n))
33 }(time.Now())
34
35 n = mw.next.Count(s)
36 return
37 }
+0
-41
examples/stringsvc2/logging.go less more
0 package main
1
2 import (
3 "time"
4
5 "github.com/go-kit/kit/log"
6 )
7
8 type loggingMiddleware struct {
9 logger log.Logger
10 next StringService
11 }
12
13 func (mw loggingMiddleware) Uppercase(s string) (output string, err error) {
14 defer func(begin time.Time) {
15 _ = mw.logger.Log(
16 "method", "uppercase",
17 "input", s,
18 "output", output,
19 "err", err,
20 "took", time.Since(begin),
21 )
22 }(time.Now())
23
24 output, err = mw.next.Uppercase(s)
25 return
26 }
27
28 func (mw loggingMiddleware) Count(s string) (n int) {
29 defer func(begin time.Time) {
30 _ = mw.logger.Log(
31 "method", "count",
32 "input", s,
33 "n", n,
34 "took", time.Since(begin),
35 )
36 }(time.Now())
37
38 n = mw.next.Count(s)
39 return
40 }
+0
-60
examples/stringsvc2/main.go less more
0 package main
1
2 import (
3 "net/http"
4 "os"
5
6 stdprometheus "github.com/prometheus/client_golang/prometheus"
7 "github.com/prometheus/client_golang/prometheus/promhttp"
8
9 "github.com/go-kit/kit/log"
10 kitprometheus "github.com/go-kit/kit/metrics/prometheus"
11 httptransport "github.com/go-kit/kit/transport/http"
12 )
13
14 func main() {
15 logger := log.NewLogfmtLogger(os.Stderr)
16
17 fieldKeys := []string{"method", "error"}
18 requestCount := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
19 Namespace: "my_group",
20 Subsystem: "string_service",
21 Name: "request_count",
22 Help: "Number of requests received.",
23 }, fieldKeys)
24 requestLatency := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
25 Namespace: "my_group",
26 Subsystem: "string_service",
27 Name: "request_latency_microseconds",
28 Help: "Total duration of requests in microseconds.",
29 }, fieldKeys)
30 countResult := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
31 Namespace: "my_group",
32 Subsystem: "string_service",
33 Name: "count_result",
34 Help: "The result of each count method.",
35 }, []string{}) // no fields here
36
37 var svc StringService
38 svc = stringService{}
39 svc = loggingMiddleware{logger, svc}
40 svc = instrumentingMiddleware{requestCount, requestLatency, countResult, svc}
41
42 uppercaseHandler := httptransport.NewServer(
43 makeUppercaseEndpoint(svc),
44 decodeUppercaseRequest,
45 encodeResponse,
46 )
47
48 countHandler := httptransport.NewServer(
49 makeCountEndpoint(svc),
50 decodeCountRequest,
51 encodeResponse,
52 )
53
54 http.Handle("/uppercase", uppercaseHandler)
55 http.Handle("/count", countHandler)
56 http.Handle("/metrics", promhttp.Handler())
57 logger.Log("msg", "HTTP", "addr", ":8080")
58 logger.Log("err", http.ListenAndServe(":8080", nil))
59 }
+0
-28
examples/stringsvc2/service.go less more
0 package main
1
2 import (
3 "errors"
4 "strings"
5 )
6
7 // StringService provides operations on strings.
8 type StringService interface {
9 Uppercase(string) (string, error)
10 Count(string) int
11 }
12
13 type stringService struct{}
14
15 func (stringService) Uppercase(s string) (string, error) {
16 if s == "" {
17 return "", ErrEmpty
18 }
19 return strings.ToUpper(s), nil
20 }
21
22 func (stringService) Count(s string) int {
23 return len(s)
24 }
25
26 // ErrEmpty is returned when an input string is empty.
27 var ErrEmpty = errors.New("empty string")
+0
-65
examples/stringsvc2/transport.go less more
0 package main
1
2 import (
3 "context"
4 "encoding/json"
5 "net/http"
6
7 "github.com/go-kit/kit/endpoint"
8 )
9
10 func makeUppercaseEndpoint(svc StringService) endpoint.Endpoint {
11 return func(ctx context.Context, request interface{}) (interface{}, error) {
12 req := request.(uppercaseRequest)
13 v, err := svc.Uppercase(req.S)
14 if err != nil {
15 return uppercaseResponse{v, err.Error()}, nil
16 }
17 return uppercaseResponse{v, ""}, nil
18 }
19 }
20
21 func makeCountEndpoint(svc StringService) endpoint.Endpoint {
22 return func(ctx context.Context, request interface{}) (interface{}, error) {
23 req := request.(countRequest)
24 v := svc.Count(req.S)
25 return countResponse{v}, nil
26 }
27 }
28
29 func decodeUppercaseRequest(_ context.Context, r *http.Request) (interface{}, error) {
30 var request uppercaseRequest
31 if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
32 return nil, err
33 }
34 return request, nil
35 }
36
37 func decodeCountRequest(_ context.Context, r *http.Request) (interface{}, error) {
38 var request countRequest
39 if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
40 return nil, err
41 }
42 return request, nil
43 }
44
45 func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
46 return json.NewEncoder(w).Encode(response)
47 }
48
49 type uppercaseRequest struct {
50 S string `json:"s"`
51 }
52
53 type uppercaseResponse struct {
54 V string `json:"v"`
55 Err string `json:"err,omitempty"`
56 }
57
58 type countRequest struct {
59 S string `json:"s"`
60 }
61
62 type countResponse struct {
63 V int `json:"v"`
64 }
+0
-48
examples/stringsvc3/instrumenting.go less more
0 package main
1
2 import (
3 "fmt"
4 "time"
5
6 "github.com/go-kit/kit/metrics"
7 )
8
9 func instrumentingMiddleware(
10 requestCount metrics.Counter,
11 requestLatency metrics.Histogram,
12 countResult metrics.Histogram,
13 ) ServiceMiddleware {
14 return func(next StringService) StringService {
15 return instrmw{requestCount, requestLatency, countResult, next}
16 }
17 }
18
19 type instrmw struct {
20 requestCount metrics.Counter
21 requestLatency metrics.Histogram
22 countResult metrics.Histogram
23 StringService
24 }
25
26 func (mw instrmw) Uppercase(s string) (output string, err error) {
27 defer func(begin time.Time) {
28 lvs := []string{"method", "uppercase", "error", fmt.Sprint(err != nil)}
29 mw.requestCount.With(lvs...).Add(1)
30 mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
31 }(time.Now())
32
33 output, err = mw.StringService.Uppercase(s)
34 return
35 }
36
37 func (mw instrmw) Count(s string) (n int) {
38 defer func(begin time.Time) {
39 lvs := []string{"method", "count", "error", "false"}
40 mw.requestCount.With(lvs...).Add(1)
41 mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
42 mw.countResult.Observe(float64(n))
43 }(time.Now())
44
45 n = mw.StringService.Count(s)
46 return
47 }
+0
-47
examples/stringsvc3/logging.go less more
0 package main
1
2 import (
3 "time"
4
5 "github.com/go-kit/kit/log"
6 )
7
8 func loggingMiddleware(logger log.Logger) ServiceMiddleware {
9 return func(next StringService) StringService {
10 return logmw{logger, next}
11 }
12 }
13
14 type logmw struct {
15 logger log.Logger
16 StringService
17 }
18
19 func (mw logmw) Uppercase(s string) (output string, err error) {
20 defer func(begin time.Time) {
21 _ = mw.logger.Log(
22 "method", "uppercase",
23 "input", s,
24 "output", output,
25 "err", err,
26 "took", time.Since(begin),
27 )
28 }(time.Now())
29
30 output, err = mw.StringService.Uppercase(s)
31 return
32 }
33
34 func (mw logmw) Count(s string) (n int) {
35 defer func(begin time.Time) {
36 _ = mw.logger.Log(
37 "method", "count",
38 "input", s,
39 "n", n,
40 "took", time.Since(begin),
41 )
42 }(time.Now())
43
44 n = mw.StringService.Count(s)
45 return
46 }
+0
-70
examples/stringsvc3/main.go less more
0 package main
1
2 import (
3 "context"
4 "flag"
5 "net/http"
6 "os"
7
8 stdprometheus "github.com/prometheus/client_golang/prometheus"
9 "github.com/prometheus/client_golang/prometheus/promhttp"
10
11 "github.com/go-kit/kit/log"
12 kitprometheus "github.com/go-kit/kit/metrics/prometheus"
13 httptransport "github.com/go-kit/kit/transport/http"
14 )
15
16 func main() {
17 var (
18 listen = flag.String("listen", ":8080", "HTTP listen address")
19 proxy = flag.String("proxy", "", "Optional comma-separated list of URLs to proxy uppercase requests")
20 )
21 flag.Parse()
22
23 var logger log.Logger
24 logger = log.NewLogfmtLogger(os.Stderr)
25 logger = log.With(logger, "listen", *listen, "caller", log.DefaultCaller)
26
27 fieldKeys := []string{"method", "error"}
28 requestCount := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
29 Namespace: "my_group",
30 Subsystem: "string_service",
31 Name: "request_count",
32 Help: "Number of requests received.",
33 }, fieldKeys)
34 requestLatency := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
35 Namespace: "my_group",
36 Subsystem: "string_service",
37 Name: "request_latency_microseconds",
38 Help: "Total duration of requests in microseconds.",
39 }, fieldKeys)
40 countResult := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
41 Namespace: "my_group",
42 Subsystem: "string_service",
43 Name: "count_result",
44 Help: "The result of each count method.",
45 }, []string{})
46
47 var svc StringService
48 svc = stringService{}
49 svc = proxyingMiddleware(context.Background(), *proxy, logger)(svc)
50 svc = loggingMiddleware(logger)(svc)
51 svc = instrumentingMiddleware(requestCount, requestLatency, countResult)(svc)
52
53 uppercaseHandler := httptransport.NewServer(
54 makeUppercaseEndpoint(svc),
55 decodeUppercaseRequest,
56 encodeResponse,
57 )
58 countHandler := httptransport.NewServer(
59 makeCountEndpoint(svc),
60 decodeCountRequest,
61 encodeResponse,
62 )
63
64 http.Handle("/uppercase", uppercaseHandler)
65 http.Handle("/count", countHandler)
66 http.Handle("/metrics", promhttp.Handler())
67 logger.Log("msg", "HTTP", "addr", *listen)
68 logger.Log("err", http.ListenAndServe(*listen, nil))
69 }
+0
-116
examples/stringsvc3/proxying.go less more
0 package main
1
2 import (
3 "context"
4 "errors"
5 "fmt"
6 "net/url"
7 "strings"
8 "time"
9
10 jujuratelimit "github.com/juju/ratelimit"
11 "github.com/sony/gobreaker"
12
13 "github.com/go-kit/kit/circuitbreaker"
14 "github.com/go-kit/kit/endpoint"
15 "github.com/go-kit/kit/log"
16 "github.com/go-kit/kit/ratelimit"
17 "github.com/go-kit/kit/sd"
18 "github.com/go-kit/kit/sd/lb"
19 httptransport "github.com/go-kit/kit/transport/http"
20 )
21
22 func proxyingMiddleware(ctx context.Context, instances string, logger log.Logger) ServiceMiddleware {
23 // If instances is empty, don't proxy.
24 if instances == "" {
25 logger.Log("proxy_to", "none")
26 return func(next StringService) StringService { return next }
27 }
28
29 // Set some parameters for our client.
30 var (
31 qps = 100 // beyond which we will return an error
32 maxAttempts = 3 // per request, before giving up
33 maxTime = 250 * time.Millisecond // wallclock time, before giving up
34 )
35
36 // Otherwise, construct an endpoint for each instance in the list, and add
37 // it to a fixed set of endpoints. In a real service, rather than doing this
38 // by hand, you'd probably use package sd's support for your service
39 // discovery system.
40 var (
41 instanceList = split(instances)
42 endpointer sd.FixedEndpointer
43 )
44 logger.Log("proxy_to", fmt.Sprint(instanceList))
45 for _, instance := range instanceList {
46 var e endpoint.Endpoint
47 e = makeUppercaseProxy(ctx, instance)
48 e = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{}))(e)
49 e = ratelimit.NewTokenBucketLimiter(jujuratelimit.NewBucketWithRate(float64(qps), int64(qps)))(e)
50 endpointer = append(endpointer, e)
51 }
52
53 // Now, build a single, retrying, load-balancing endpoint out of all of
54 // those individual endpoints.
55 balancer := lb.NewRoundRobin(endpointer)
56 retry := lb.Retry(maxAttempts, maxTime, balancer)
57
58 // And finally, return the ServiceMiddleware, implemented by proxymw.
59 return func(next StringService) StringService {
60 return proxymw{ctx, next, retry}
61 }
62 }
63
64 // proxymw implements StringService, forwarding Uppercase requests to the
65 // provided endpoint, and serving all other (i.e. Count) requests via the
66 // next StringService.
67 type proxymw struct {
68 ctx context.Context
69 next StringService // Serve most requests via this service...
70 uppercase endpoint.Endpoint // ...except Uppercase, which gets served by this endpoint
71 }
72
73 func (mw proxymw) Count(s string) int {
74 return mw.next.Count(s)
75 }
76
77 func (mw proxymw) Uppercase(s string) (string, error) {
78 response, err := mw.uppercase(mw.ctx, uppercaseRequest{S: s})
79 if err != nil {
80 return "", err
81 }
82
83 resp := response.(uppercaseResponse)
84 if resp.Err != "" {
85 return resp.V, errors.New(resp.Err)
86 }
87 return resp.V, nil
88 }
89
90 func makeUppercaseProxy(ctx context.Context, instance string) endpoint.Endpoint {
91 if !strings.HasPrefix(instance, "http") {
92 instance = "http://" + instance
93 }
94 u, err := url.Parse(instance)
95 if err != nil {
96 panic(err)
97 }
98 if u.Path == "" {
99 u.Path = "/uppercase"
100 }
101 return httptransport.NewClient(
102 "GET",
103 u,
104 encodeRequest,
105 decodeUppercaseResponse,
106 ).Endpoint()
107 }
108
109 func split(s string) []string {
110 a := strings.Split(s, ",")
111 for i := range a {
112 a[i] = strings.TrimSpace(a[i])
113 }
114 return a
115 }
+0
-31
examples/stringsvc3/service.go less more
0 package main
1
2 import (
3 "errors"
4 "strings"
5 )
6
7 // StringService provides operations on strings.
8 type StringService interface {
9 Uppercase(string) (string, error)
10 Count(string) int
11 }
12
13 type stringService struct{}
14
15 func (stringService) Uppercase(s string) (string, error) {
16 if s == "" {
17 return "", ErrEmpty
18 }
19 return strings.ToUpper(s), nil
20 }
21
22 func (stringService) Count(s string) int {
23 return len(s)
24 }
25
26 // ErrEmpty is returned when an input string is empty.
27 var ErrEmpty = errors.New("empty string")
28
29 // ServiceMiddleware is a chainable behavior modifier for StringService.
30 type ServiceMiddleware func(StringService) StringService
+0
-84
examples/stringsvc3/transport.go less more
0 package main
1
2 import (
3 "bytes"
4 "context"
5 "encoding/json"
6 "io/ioutil"
7 "net/http"
8
9 "github.com/go-kit/kit/endpoint"
10 )
11
12 func makeUppercaseEndpoint(svc StringService) endpoint.Endpoint {
13 return func(ctx context.Context, request interface{}) (interface{}, error) {
14 req := request.(uppercaseRequest)
15 v, err := svc.Uppercase(req.S)
16 if err != nil {
17 return uppercaseResponse{v, err.Error()}, nil
18 }
19 return uppercaseResponse{v, ""}, nil
20 }
21 }
22
23 func makeCountEndpoint(svc StringService) endpoint.Endpoint {
24 return func(ctx context.Context, request interface{}) (interface{}, error) {
25 req := request.(countRequest)
26 v := svc.Count(req.S)
27 return countResponse{v}, nil
28 }
29 }
30
31 func decodeUppercaseRequest(_ context.Context, r *http.Request) (interface{}, error) {
32 var request uppercaseRequest
33 if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
34 return nil, err
35 }
36 return request, nil
37 }
38
39 func decodeCountRequest(_ context.Context, r *http.Request) (interface{}, error) {
40 var request countRequest
41 if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
42 return nil, err
43 }
44 return request, nil
45 }
46
47 func decodeUppercaseResponse(_ context.Context, r *http.Response) (interface{}, error) {
48 var response uppercaseResponse
49 if err := json.NewDecoder(r.Body).Decode(&response); err != nil {
50 return nil, err
51 }
52 return response, nil
53 }
54
55 func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
56 return json.NewEncoder(w).Encode(response)
57 }
58
59 func encodeRequest(_ context.Context, r *http.Request, request interface{}) error {
60 var buf bytes.Buffer
61 if err := json.NewEncoder(&buf).Encode(request); err != nil {
62 return err
63 }
64 r.Body = ioutil.NopCloser(&buf)
65 return nil
66 }
67
68 type uppercaseRequest struct {
69 S string `json:"s"`
70 }
71
72 type uppercaseResponse struct {
73 V string `json:"v"`
74 Err string `json:"err,omitempty"`
75 }
76
77 type countRequest struct {
78 S string `json:"s"`
79 }
80
81 type countResponse struct {
82 V int `json:"v"`
83 }
0 module github.com/go-kit/kit
1
2 go 1.17
3
4 require (
5 github.com/VividCortex/gohistogram v1.0.0
6 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5
7 github.com/aws/aws-sdk-go v1.40.45
8 github.com/aws/aws-sdk-go-v2 v1.9.1
9 github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1
10 github.com/casbin/casbin/v2 v2.37.0
11 github.com/go-kit/log v0.2.0
12 github.com/go-zookeeper/zk v1.0.2
13 github.com/golang-jwt/jwt/v4 v4.0.0
14 github.com/hashicorp/consul/api v1.14.0
15 github.com/hudl/fargo v1.4.0
16 github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab
17 github.com/nats-io/nats-server/v2 v2.8.4
18 github.com/nats-io/nats.go v1.15.0
19 github.com/opentracing/opentracing-go v1.2.0
20 github.com/openzipkin/zipkin-go v0.2.5
21 github.com/performancecopilot/speed/v4 v4.0.0
22 github.com/prometheus/client_golang v1.11.1
23 github.com/rabbitmq/amqp091-go v1.2.0
24 github.com/sirupsen/logrus v1.8.1
25 github.com/sony/gobreaker v0.4.1
26 github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e
27 go.etcd.io/etcd/client/pkg/v3 v3.5.0
28 go.etcd.io/etcd/client/v2 v2.305.0
29 go.etcd.io/etcd/client/v3 v3.5.0
30 go.opencensus.io v0.23.0
31 go.uber.org/zap v1.19.1
32 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
33 golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11
34 google.golang.org/grpc v1.40.0
35 google.golang.org/protobuf v1.27.1
36 )
37
38 require (
39 github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
40 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect
41 github.com/armon/go-metrics v0.4.0 // indirect
42 github.com/aws/smithy-go v1.8.0 // indirect
43 github.com/beorn7/perks v1.0.1 // indirect
44 github.com/cenkalti/backoff/v4 v4.1.1 // indirect
45 github.com/cespare/xxhash/v2 v2.1.2 // indirect
46 github.com/clbanning/mxj v1.8.4 // indirect
47 github.com/coreos/go-semver v0.3.0 // indirect
48 github.com/coreos/go-systemd/v22 v22.3.2 // indirect
49 github.com/edsrzf/mmap-go v1.0.0 // indirect
50 github.com/fatih/color v1.13.0 // indirect
51 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8 // indirect
52 github.com/go-logfmt/logfmt v0.5.1 // indirect
53 github.com/gogo/protobuf v1.3.2 // indirect
54 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
55 github.com/golang/protobuf v1.5.2 // indirect
56 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
57 github.com/hashicorp/go-hclog v1.2.2 // indirect
58 github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
59 github.com/hashicorp/go-rootcerts v1.0.2 // indirect
60 github.com/hashicorp/golang-lru v0.5.4 // indirect
61 github.com/hashicorp/serf v0.10.0 // indirect
62 github.com/jmespath/go-jmespath v0.4.0 // indirect
63 github.com/json-iterator/go v1.1.12 // indirect
64 github.com/klauspost/compress v1.14.4 // indirect
65 github.com/mattn/go-colorable v0.1.13 // indirect
66 github.com/mattn/go-isatty v0.0.16 // indirect
67 github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
68 github.com/miekg/dns v1.1.43 // indirect
69 github.com/minio/highwayhash v1.0.2 // indirect
70 github.com/mitchellh/go-homedir v1.1.0 // indirect
71 github.com/mitchellh/mapstructure v1.5.0 // indirect
72 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
73 github.com/modern-go/reflect2 v1.0.2 // indirect
74 github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a // indirect
75 github.com/nats-io/nkeys v0.3.0 // indirect
76 github.com/nats-io/nuid v1.0.1 // indirect
77 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect
78 github.com/pkg/errors v0.9.1 // indirect
79 github.com/prometheus/client_model v0.2.0 // indirect
80 github.com/prometheus/common v0.30.0 // indirect
81 github.com/prometheus/procfs v0.7.3 // indirect
82 go.etcd.io/etcd/api/v3 v3.5.0 // indirect
83 go.uber.org/atomic v1.9.0 // indirect
84 go.uber.org/multierr v1.7.0 // indirect
85 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
86 golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
87 golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24 // indirect
88 golang.org/x/text v0.3.7 // indirect
89 google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4 // indirect
90 gopkg.in/gcfg.v1 v1.2.3 // indirect
91 gopkg.in/warnings.v0 v0.1.2 // indirect
92 )
0 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
1 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
3 cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
4 cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
5 cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
6 cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
7 cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
8 cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
9 cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
10 cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
11 cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
12 cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
13 cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
14 cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
15 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
16 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
17 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
18 cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
19 cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
20 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
21 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
22 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
23 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
24 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
25 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
26 cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
27 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
28 cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
29 cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
30 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
31 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
32 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
33 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
34 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
35 github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
36 github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
37 github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
38 github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
39 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
40 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
41 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
42 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
43 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
44 github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
45 github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
46 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw=
47 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
48 github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
49 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
50 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
51 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
52 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
53 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
54 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
55 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
56 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
57 github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
58 github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q=
59 github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
60 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
61 github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
62 github.com/aws/aws-sdk-go v1.40.45 h1:QN1nsY27ssD/JmW4s83qmSb+uL6DG4GmCDzjmJB4xUI=
63 github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
64 github.com/aws/aws-sdk-go-v2 v1.9.1 h1:ZbovGV/qo40nrOJ4q8G33AGICzaPI45FHQWJ9650pF4=
65 github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
66 github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1 h1:w/fPGB0t5rWwA43mux4e9ozFSH5zF1moQemlA131PWc=
67 github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o=
68 github.com/aws/smithy-go v1.8.0 h1:AEwwwXQZtUwP5Mz506FeXXrKBe0jA8gVM+1gEcSRooc=
69 github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
70 github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
71 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
72 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
73 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
74 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
75 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
76 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
77 github.com/casbin/casbin/v2 v2.37.0 h1:/poEwPSovi4bTOcP752/CsTQiRz2xycyVKFG7GUhbDw=
78 github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
79 github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ=
80 github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
81 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
82 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
83 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
84 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
85 github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
86 github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
87 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
88 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
89 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
90 github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
91 github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
92 github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
93 github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
94 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
95 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
96 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
97 github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
98 github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
99 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
100 github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
101 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
102 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
103 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
104 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
105 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
106 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
107 github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
108 github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
109 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
110 github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
111 github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
112 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
113 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
114 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
115 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
116 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
117 github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
118 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
119 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
120 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
121 github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
122 github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
123 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
124 github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2 h1:cZqz+yOJ/R64LcKjNQOdARott/jP7BnUQ9Ah7KaZCvw=
125 github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo=
126 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8 h1:a9ENSRDFBUPkJ5lCgVZh26+ZbGyoVJG7yb5SSzF5H54=
127 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
128 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
129 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
130 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
131 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
132 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
133 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
134 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
135 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
136 github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
137 github.com/go-kit/log v0.2.0 h1:7i2K3eKTos3Vc0enKCfnVcgHh2olr/MyfboYq7cAcFw=
138 github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
139 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
140 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
141 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
142 github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
143 github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
144 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
145 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
146 github.com/go-zookeeper/zk v1.0.2 h1:4mx0EYENAdX/B/rbunjlt5+4RTA/a9SMHBRuSKdGxPM=
147 github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
148 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
149 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
150 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
151 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
152 github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o=
153 github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
154 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
155 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
156 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
157 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
158 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
159 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
160 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
161 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
162 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
163 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
164 github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
165 github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
166 github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
167 github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
168 github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
169 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
170 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
171 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
172 github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
173 github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
174 github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
175 github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
176 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
177 github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
178 github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
179 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
180 github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
181 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
182 github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
183 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
184 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
185 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
186 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
187 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
188 github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
189 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
190 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
191 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
192 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
193 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
194 github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
195 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
196 github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
197 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
198 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
199 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
200 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
201 github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
202 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
203 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
204 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
205 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
206 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
207 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
208 github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
209 github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
210 github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
211 github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
212 github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
213 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
214 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
215 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
216 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
217 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
218 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
219 github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
220 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
221 github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
222 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
223 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
224 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
225 github.com/hashicorp/consul/api v1.14.0 h1:Y64GIJ8hYTu+tuGekwO4G4ardXoiCivX9wv1iP/kihk=
226 github.com/hashicorp/consul/api v1.14.0/go.mod h1:bcaw5CSZ7NE9qfOfKCI1xb7ZKjzu/MyvQkCLTfqLqxQ=
227 github.com/hashicorp/consul/sdk v0.10.0 h1:rGLEh2AWK4K0KCMvqWAz2EYxQqgciIfMagWZ0nVe5MI=
228 github.com/hashicorp/consul/sdk v0.10.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw=
229 github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
230 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
231 github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
232 github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
233 github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
234 github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
235 github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
236 github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
237 github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M=
238 github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
239 github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
240 github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
241 github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
242 github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
243 github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
244 github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
245 github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
246 github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
247 github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
248 github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
249 github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
250 github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
251 github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
252 github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
253 github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
254 github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
255 github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
256 github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
257 github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
258 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
259 github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
260 github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
261 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
262 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
263 github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
264 github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
265 github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
266 github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
267 github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
268 github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
269 github.com/hashicorp/memberlist v0.4.0 h1:k3uda5gZcltmafuFF+UFqNEl5PrH+yPZ4zkjp1f/H/8=
270 github.com/hashicorp/memberlist v0.4.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
271 github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
272 github.com/hashicorp/serf v0.10.0 h1:89qvvpfMQnz6c2y4pv7j2vUUmeT1+5TSZMexuTbtsPs=
273 github.com/hashicorp/serf v0.10.0/go.mod h1:bXN03oZc5xlH46k/K1qTrpXb9ERKyY1/i/N5mxvgrZw=
274 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
275 github.com/hudl/fargo v1.4.0 h1:ZDDILMbB37UlAVLlWcJ2Iz1XuahZZTDZfdCKeclfq2s=
276 github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo=
277 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
278 github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab h1:HqW4xhhynfjrtEiiSGcQUd6vrK23iMam1FO8rI7mwig=
279 github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
280 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
281 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
282 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
283 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
284 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
285 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
286 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
287 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
288 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
289 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
290 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
291 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
292 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
293 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
294 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
295 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
296 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
297 github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
298 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
299 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
300 github.com/klauspost/compress v1.14.4 h1:eijASRJcobkVtSt81Olfh7JX43osYLwy5krOJo6YEu4=
301 github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
302 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
303 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
304 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
305 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
306 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
307 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
308 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
309 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
310 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
311 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
312 github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
313 github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
314 github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
315 github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
316 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
317 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
318 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
319 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
320 github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
321 github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
322 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
323 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
324 github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
325 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
326 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
327 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
328 github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
329 github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
330 github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
331 github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
332 github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
333 github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
334 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
335 github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
336 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
337 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
338 github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
339 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
340 github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
341 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
342 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
343 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
344 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
345 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
346 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
347 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
348 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
349 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
350 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
351 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
352 github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a h1:lem6QCvxR0Y28gth9P+wV2K/zYUUAkJ+55U8cpS0p5I=
353 github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
354 github.com/nats-io/nats-server/v2 v2.8.4 h1:0jQzze1T9mECg8YZEl8+WYUXb9JKluJfCBriPUtluB4=
355 github.com/nats-io/nats-server/v2 v2.8.4/go.mod h1:8zZa+Al3WsESfmgSs98Fi06dRWLH5Bnq90m5bKD/eT4=
356 github.com/nats-io/nats.go v1.15.0 h1:3IXNBolWrwIUf2soxh6Rla8gPzYWEZQBUBK6RV21s+o=
357 github.com/nats-io/nats.go v1.15.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
358 github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=
359 github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
360 github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
361 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
362 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
363 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
364 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
365 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
366 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
367 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
368 github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
369 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
370 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
371 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
372 github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
373 github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
374 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
375 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
376 github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
377 github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
378 github.com/openzipkin/zipkin-go v0.2.5 h1:UwtQQx2pyPIgWYHRg+epgdx1/HnBQTgN3/oIYEJTQzU=
379 github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE=
380 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
381 github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
382 github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
383 github.com/performancecopilot/speed/v4 v4.0.0 h1:VxEDCmdkfbQYDlcr/GC9YoN9PQ6p8ulk9xVsepYy9ZY=
384 github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM=
385 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
386 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
387 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
388 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
389 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
390 github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
391 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
392 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
393 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
394 github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
395 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
396 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
397 github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
398 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
399 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
400 github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s=
401 github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
402 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
403 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
404 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
405 github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
406 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
407 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
408 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
409 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
410 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
411 github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug=
412 github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
413 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
414 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
415 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
416 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
417 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
418 github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
419 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
420 github.com/rabbitmq/amqp091-go v1.2.0 h1:1pHBxAsQh54R9eX/xo679fUEAfv3loMqi0pvRFOj2nk=
421 github.com/rabbitmq/amqp091-go v1.2.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM=
422 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
423 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
424 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
425 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
426 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
427 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
428 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
429 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
430 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
431 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
432 github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
433 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
434 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
435 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
436 github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
437 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
438 github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ=
439 github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
440 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
441 github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
442 github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e h1:mOtuXaRAbVZsxAHVdPR3IjfmN8T1h2iczJLynhLybf8=
443 github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
444 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
445 github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
446 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
447 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
448 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
449 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
450 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
451 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
452 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
453 github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
454 github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
455 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
456 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
457 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
458 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
459 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
460 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
461 go.etcd.io/etcd/api/v3 v3.5.0 h1:GsV3S+OfZEOCNXdtNkBSR7kgLobAa/SO6tCxRa0GAYw=
462 go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
463 go.etcd.io/etcd/client/pkg/v3 v3.5.0 h1:2aQv6F436YnN7I4VbI8PPYrBhu+SmrTaADcf8Mi/6PU=
464 go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
465 go.etcd.io/etcd/client/v2 v2.305.0 h1:ftQ0nOOHMcbMS3KIaDQ0g5Qcd6bhaBrQT6b89DfwLTs=
466 go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
467 go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek=
468 go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
469 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
470 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
471 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
472 go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
473 go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
474 go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
475 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
476 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
477 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
478 go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
479 go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
480 go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4=
481 go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
482 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
483 go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=
484 go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
485 go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
486 go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
487 go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
488 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
489 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
490 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
491 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
492 golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
493 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
494 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
495 golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
496 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
497 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
498 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
499 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
500 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
501 golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
502 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
503 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
504 golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
505 golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
506 golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
507 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
508 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
509 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
510 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y=
511 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
512 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
513 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
514 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
515 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
516 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
517 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
518 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
519 golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
520 golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
521 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
522 golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
523 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
524 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
525 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
526 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
527 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
528 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
529 golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
530 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
531 golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
532 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
533 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
534 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
535 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
536 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
537 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
538 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
539 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
540 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
541 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
542 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
543 golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
544 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
545 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
546 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
547 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
548 golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
549 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
550 golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
551 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
552 golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
553 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
554 golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
555 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
556 golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
557 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
558 golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
559 golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
560 golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
561 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
562 golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
563 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
564 golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
565 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
566 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
567 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
568 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
569 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
570 golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
571 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
572 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
573 golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
574 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
575 golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM=
576 golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
577 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
578 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
579 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
580 golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
581 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
582 golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
583 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
584 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
585 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
586 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
587 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
588 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
589 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
590 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
591 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
592 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
593 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
594 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
595 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
596 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
597 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
598 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
599 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
600 golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
601 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
602 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
603 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
604 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
605 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
606 golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
607 golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
608 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
609 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
610 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
611 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
612 golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
613 golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
614 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
615 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
616 golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
617 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
618 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
619 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
620 golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
621 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
622 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
623 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
624 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
625 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
626 golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
627 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
628 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
629 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
630 golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
631 golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
632 golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
633 golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
634 golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
635 golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
636 golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
637 golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
638 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
639 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
640 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
641 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
642 golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
643 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
644 golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
645 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
646 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
647 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
648 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
649 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
650 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
651 golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
652 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
653 golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
654 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
655 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
656 golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24 h1:TyKJRhyo17yWxOMCTHKWrc5rddHORMlnZ/j57umaUd8=
657 golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
658 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
659 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
660 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
661 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
662 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
663 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
664 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
665 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
666 golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
667 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
668 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
669 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
670 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
671 golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
672 golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
673 golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
674 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
675 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
676 golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
677 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
678 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
679 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
680 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
681 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
682 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
683 golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
684 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
685 golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
686 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
687 golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
688 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
689 golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
690 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
691 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
692 golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
693 golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
694 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
695 golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
696 golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
697 golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
698 golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
699 golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
700 golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
701 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
702 golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
703 golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
704 golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
705 golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
706 golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
707 golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
708 golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
709 golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
710 golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
711 golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
712 golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
713 golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
714 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
715 golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
716 golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
717 golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
718 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
719 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
720 golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
721 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
722 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
723 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
724 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
725 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
726 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
727 gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
728 gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM=
729 gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
730 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
731 gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
732 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
733 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
734 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
735 google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
736 google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
737 google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
738 google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
739 google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
740 google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
741 google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
742 google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
743 google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
744 google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
745 google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
746 google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
747 google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
748 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
749 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
750 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
751 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
752 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
753 google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
754 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
755 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
756 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
757 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
758 google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
759 google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
760 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
761 google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
762 google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
763 google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
764 google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
765 google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
766 google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
767 google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
768 google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
769 google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
770 google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
771 google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
772 google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
773 google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
774 google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
775 google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
776 google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
777 google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
778 google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
779 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
780 google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
781 google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
782 google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
783 google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
784 google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
785 google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4 h1:ysnBoUyeL/H6RCvNRhWHjKoDEmguI+mPU+qHgK8qv/w=
786 google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
787 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
788 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
789 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
790 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
791 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
792 google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
793 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
794 google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
795 google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
796 google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
797 google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
798 google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
799 google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
800 google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
801 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
802 google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
803 google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
804 google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
805 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
806 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
807 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
808 google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
809 google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
810 google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
811 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
812 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
813 google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
814 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
815 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
816 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
817 google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
818 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
819 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
820 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
821 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
822 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
823 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
824 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
825 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
826 gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs=
827 gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
828 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
829 gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
830 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
831 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
832 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
833 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
834 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
835 gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
836 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
837 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
838 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
839 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
840 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
841 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
842 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
843 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
844 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
845 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
846 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
847 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
848 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
849 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
850 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
851 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
852 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
853 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
854 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
855 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
00 # package log
1
2 **Deprecation notice:** The core Go kit log packages (log, log/level, log/term, and
3 log/syslog) have been moved to their own repository at github.com/go-kit/log.
4 The corresponding packages in this directory remain for backwards compatibility.
5 Their types alias the types and their functions call the functions provided by
6 the new repository. Using either import path should be equivalent. Prefer the
7 new import path when practical.
8
9 ______
110
211 `package log` provides a minimal interface for structured logging in services.
312 It may be wrapped to encode conventions, enforce type-safety, provide leveled
102111 // ts=2016-01-01T12:34:56Z caller=main.go:15 msg=hello
103112 ```
104113
114 ## Levels
115
116 Log levels are supported via the [level package](https://godoc.org/github.com/go-kit/kit/log/level).
117
105118 ## Supported output formats
106119
107120 - [Logfmt](https://brandur.org/logfmt) ([see also](https://blog.codeship.com/logfmt-a-log-format-thats-easy-to-read-and-write))
+0
-21
log/benchmark_test.go less more
0 package log_test
1
2 import (
3 "testing"
4
5 "github.com/go-kit/kit/log"
6 )
7
8 func benchmarkRunner(b *testing.B, logger log.Logger, f func(log.Logger)) {
9 lc := log.With(logger, "common_key", "common_value")
10 b.ReportAllocs()
11 b.ResetTimer()
12 for i := 0; i < b.N; i++ {
13 f(lc)
14 }
15 }
16
17 var (
18 baseMessage = func(logger log.Logger) { logger.Log("foo_key", "foo_value") }
19 withMessage = func(logger log.Logger) { log.With(logger, "a", "b").Log("c", "d") }
20 )
+0
-40
log/concurrency_test.go less more
0 package log_test
1
2 import (
3 "math"
4 "testing"
5
6 "github.com/go-kit/kit/log"
7 )
8
9 // These test are designed to be run with the race detector.
10
11 func testConcurrency(t *testing.T, logger log.Logger, total int) {
12 n := int(math.Sqrt(float64(total)))
13 share := total / n
14
15 errC := make(chan error, n)
16
17 for i := 0; i < n; i++ {
18 go func() {
19 errC <- spam(logger, share)
20 }()
21 }
22
23 for i := 0; i < n; i++ {
24 err := <-errC
25 if err != nil {
26 t.Fatalf("concurrent logging error: %v", err)
27 }
28 }
29 }
30
31 func spam(logger log.Logger, count int) error {
32 for i := 0; i < count; i++ {
33 err := logger.Log("key", i)
34 if err != nil {
35 return err
36 }
37 }
38 return nil
39 }
0 // Package levels implements leveled logging on top of Go kit's log package.
1 //
2 // Deprecated: Use github.com/go-kit/log/level instead.
03 package levels
14
2 import "github.com/go-kit/kit/log"
5 import "github.com/go-kit/log"
36
47 // Levels provides a leveled logging wrapper around a logger. It has five
58 // levels: debug, info, warning (warn), error, and critical (crit). If you
44 "os"
55 "testing"
66
7 "github.com/go-kit/kit/log"
87 levels "github.com/go-kit/kit/log/deprecated_levels"
8 "github.com/go-kit/log"
99 )
1010
1111 func TestDefaultLevels(t *testing.T) {
00 // Package log provides a structured logger.
1 //
2 // Deprecated: Use github.com/go-kit/log instead.
13 //
24 // Structured logging produces logs easily consumed later by humans or
35 // machines. Humans might be interested in debugging errors, or tracing
3840 //
3941 // A contextual logger stores keyvals that it includes in all log events.
4042 // Building appropriate contextual loggers reduces repetition and aids
41 // consistency in the resulting log output. With and WithPrefix add context to
42 // a logger. We can use With to improve the RunTask example.
43 // consistency in the resulting log output. With, WithPrefix, and WithSuffix
44 // add context to a logger. We can use With to improve the RunTask example.
4345 //
4446 // func RunTask(task Task, logger log.Logger) string {
4547 // logger = log.With(logger, "taskID", task.ID)
00 package log
11
22 import (
3 "encoding"
4 "encoding/json"
5 "fmt"
63 "io"
7 "reflect"
4
5 "github.com/go-kit/log"
86 )
9
10 type jsonLogger struct {
11 io.Writer
12 }
137
148 // NewJSONLogger returns a Logger that encodes keyvals to the Writer as a
159 // single JSON object. Each log event produces no more than one call to
1610 // w.Write. The passed Writer must be safe for concurrent use by multiple
1711 // goroutines if the returned Logger will be used concurrently.
1812 func NewJSONLogger(w io.Writer) Logger {
19 return &jsonLogger{w}
13 return log.NewJSONLogger(w)
2014 }
21
22 func (l *jsonLogger) Log(keyvals ...interface{}) error {
23 n := (len(keyvals) + 1) / 2 // +1 to handle case when len is odd
24 m := make(map[string]interface{}, n)
25 for i := 0; i < len(keyvals); i += 2 {
26 k := keyvals[i]
27 var v interface{} = ErrMissingValue
28 if i+1 < len(keyvals) {
29 v = keyvals[i+1]
30 }
31 merge(m, k, v)
32 }
33 return json.NewEncoder(l.Writer).Encode(m)
34 }
35
36 func merge(dst map[string]interface{}, k, v interface{}) {
37 var key string
38 switch x := k.(type) {
39 case string:
40 key = x
41 case fmt.Stringer:
42 key = safeString(x)
43 default:
44 key = fmt.Sprint(x)
45 }
46
47 // We want json.Marshaler and encoding.TextMarshaller to take priority over
48 // err.Error() and v.String(). But json.Marshall (called later) does that by
49 // default so we force a no-op if it's one of those 2 case.
50 switch x := v.(type) {
51 case json.Marshaler:
52 case encoding.TextMarshaler:
53 case error:
54 v = safeError(x)
55 case fmt.Stringer:
56 v = safeString(x)
57 }
58
59 dst[key] = v
60 }
61
62 func safeString(str fmt.Stringer) (s string) {
63 defer func() {
64 if panicVal := recover(); panicVal != nil {
65 if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() {
66 s = "NULL"
67 } else {
68 panic(panicVal)
69 }
70 }
71 }()
72 s = str.String()
73 return
74 }
75
76 func safeError(err error) (s interface{}) {
77 defer func() {
78 if panicVal := recover(); panicVal != nil {
79 if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() {
80 s = nil
81 } else {
82 panic(panicVal)
83 }
84 }
85 }()
86 s = err.Error()
87 return
88 }
+0
-162
log/json_logger_test.go less more
0 package log_test
1
2 import (
3 "bytes"
4 "errors"
5 "io/ioutil"
6 "testing"
7
8 "github.com/go-kit/kit/log"
9 )
10
11 func TestJSONLoggerCaller(t *testing.T) {
12 t.Parallel()
13 buf := &bytes.Buffer{}
14 logger := log.NewJSONLogger(buf)
15 logger = log.With(logger, "caller", log.DefaultCaller)
16
17 if err := logger.Log(); err != nil {
18 t.Fatal(err)
19 }
20 if want, have := `{"caller":"json_logger_test.go:18"}`+"\n", buf.String(); want != have {
21 t.Errorf("\nwant %#v\nhave %#v", want, have)
22 }
23 }
24
25 func TestJSONLogger(t *testing.T) {
26 t.Parallel()
27 buf := &bytes.Buffer{}
28 logger := log.NewJSONLogger(buf)
29 if err := logger.Log("err", errors.New("err"), "m", map[string]int{"0": 0}, "a", []int{1, 2, 3}); err != nil {
30 t.Fatal(err)
31 }
32 if want, have := `{"a":[1,2,3],"err":"err","m":{"0":0}}`+"\n", buf.String(); want != have {
33 t.Errorf("\nwant %#v\nhave %#v", want, have)
34 }
35 }
36
37 func TestJSONLoggerMissingValue(t *testing.T) {
38 t.Parallel()
39 buf := &bytes.Buffer{}
40 logger := log.NewJSONLogger(buf)
41 if err := logger.Log("k"); err != nil {
42 t.Fatal(err)
43 }
44 if want, have := `{"k":"(MISSING)"}`+"\n", buf.String(); want != have {
45 t.Errorf("\nwant %#v\nhave %#v", want, have)
46 }
47 }
48
49 func TestJSONLoggerNilStringerKey(t *testing.T) {
50 t.Parallel()
51
52 buf := &bytes.Buffer{}
53 logger := log.NewJSONLogger(buf)
54 if err := logger.Log((*stringer)(nil), "v"); err != nil {
55 t.Fatal(err)
56 }
57 if want, have := `{"NULL":"v"}`+"\n", buf.String(); want != have {
58 t.Errorf("\nwant %#v\nhave %#v", want, have)
59 }
60 }
61
62 func TestJSONLoggerNilErrorValue(t *testing.T) {
63 t.Parallel()
64
65 buf := &bytes.Buffer{}
66 logger := log.NewJSONLogger(buf)
67 if err := logger.Log("err", (*stringError)(nil)); err != nil {
68 t.Fatal(err)
69 }
70 if want, have := `{"err":null}`+"\n", buf.String(); want != have {
71 t.Errorf("\nwant %#v\nhave %#v", want, have)
72 }
73 }
74
75 // aller implements json.Marshaler, encoding.TextMarshaler, and fmt.Stringer.
76 type aller struct{}
77
78 func (aller) MarshalJSON() ([]byte, error) {
79 return []byte("\"json\""), nil
80 }
81
82 func (aller) MarshalText() ([]byte, error) {
83 return []byte("text"), nil
84 }
85
86 func (aller) String() string {
87 return "string"
88 }
89
90 func (aller) Error() string {
91 return "error"
92 }
93
94 // textstringer implements encoding.TextMarshaler and fmt.Stringer.
95 type textstringer struct{}
96
97 func (textstringer) MarshalText() ([]byte, error) {
98 return []byte("text"), nil
99 }
100
101 func (textstringer) String() string {
102 return "string"
103 }
104
105 func TestJSONLoggerStringValue(t *testing.T) {
106 t.Parallel()
107 tests := []struct {
108 v interface{}
109 expected string
110 }{
111 {
112 v: aller{},
113 expected: `{"v":"json"}`,
114 },
115 {
116 v: textstringer{},
117 expected: `{"v":"text"}`,
118 },
119 {
120 v: stringer("string"),
121 expected: `{"v":"string"}`,
122 },
123 }
124
125 for _, test := range tests {
126 buf := &bytes.Buffer{}
127 logger := log.NewJSONLogger(buf)
128 if err := logger.Log("v", test.v); err != nil {
129 t.Fatal(err)
130 }
131
132 if want, have := test.expected+"\n", buf.String(); want != have {
133 t.Errorf("\nwant %#v\nhave %#v", want, have)
134 }
135 }
136 }
137
138 type stringer string
139
140 func (s stringer) String() string {
141 return string(s)
142 }
143
144 type stringError string
145
146 func (s stringError) Error() string {
147 return string(s)
148 }
149
150 func BenchmarkJSONLoggerSimple(b *testing.B) {
151 benchmarkRunner(b, log.NewJSONLogger(ioutil.Discard), baseMessage)
152 }
153
154 func BenchmarkJSONLoggerContextual(b *testing.B) {
155 benchmarkRunner(b, log.NewJSONLogger(ioutil.Discard), withMessage)
156 }
157
158 func TestJSONLoggerConcurrency(t *testing.T) {
159 t.Parallel()
160 testConcurrency(t, log.NewJSONLogger(ioutil.Discard), 10000)
161 }
+0
-72
log/level/benchmark_test.go less more
0 package level_test
1
2 import (
3 "io/ioutil"
4 "testing"
5
6 "github.com/go-kit/kit/log"
7 "github.com/go-kit/kit/log/level"
8 )
9
10 func Benchmark(b *testing.B) {
11 contexts := []struct {
12 name string
13 context func(log.Logger) log.Logger
14 }{
15 {"NoContext", func(l log.Logger) log.Logger {
16 return l
17 }},
18 {"TimeContext", func(l log.Logger) log.Logger {
19 return log.With(l, "time", log.DefaultTimestampUTC)
20 }},
21 {"CallerContext", func(l log.Logger) log.Logger {
22 return log.With(l, "caller", log.DefaultCaller)
23 }},
24 {"TimeCallerReqIDContext", func(l log.Logger) log.Logger {
25 return log.With(l, "time", log.DefaultTimestampUTC, "caller", log.DefaultCaller, "reqID", 29)
26 }},
27 }
28
29 loggers := []struct {
30 name string
31 logger log.Logger
32 }{
33 {"Nop", log.NewNopLogger()},
34 {"Logfmt", log.NewLogfmtLogger(ioutil.Discard)},
35 {"JSON", log.NewJSONLogger(ioutil.Discard)},
36 }
37
38 filters := []struct {
39 name string
40 filter func(log.Logger) log.Logger
41 }{
42 {"Baseline", func(l log.Logger) log.Logger {
43 return l
44 }},
45 {"DisallowedLevel", func(l log.Logger) log.Logger {
46 return level.NewFilter(l, level.AllowInfo())
47 }},
48 {"AllowedLevel", func(l log.Logger) log.Logger {
49 return level.NewFilter(l, level.AllowAll())
50 }},
51 }
52
53 for _, c := range contexts {
54 b.Run(c.name, func(b *testing.B) {
55 for _, f := range filters {
56 b.Run(f.name, func(b *testing.B) {
57 for _, l := range loggers {
58 b.Run(l.name, func(b *testing.B) {
59 logger := c.context(f.filter(l.logger))
60 b.ResetTimer()
61 b.ReportAllocs()
62 for i := 0; i < b.N; i++ {
63 level.Debug(logger).Log("foo", "bar")
64 }
65 })
66 }
67 })
68 }
69 })
70 }
71 }
0 // Package level implements leveled logging on top of package log. To use the
1 // level package, create a logger as per normal in your func main, and wrap it
2 // with level.NewFilter.
0 // Package level implements leveled logging on top of Go kit's log package.
1 //
2 // Deprecated: Use github.com/go-kit/log/level instead.
3 //
4 // To use the level package, create a logger as per normal in your func main,
5 // and wrap it with level.NewFilter.
36 //
47 // var logger log.Logger
58 // logger = log.NewLogfmtLogger(os.Stderr)
6 // logger = level.NewFilter(logger, level.AllowInfoAndAbove()) // <--
9 // logger = level.NewFilter(logger, level.AllowInfo()) // <--
710 // logger = log.With(logger, "ts", log.DefaultTimestampUTC)
811 //
912 // Then, at the callsites, use one of the level.Debug, Info, Warn, or Error
88 )
99
1010 func Example_basic() {
11 // setup logger with level filter
11 logger := log.NewLogfmtLogger(os.Stdout)
12 level.Debug(logger).Log("msg", "this message is at the debug level")
13 level.Info(logger).Log("msg", "this message is at the info level")
14 level.Warn(logger).Log("msg", "this message is at the warn level")
15 level.Error(logger).Log("msg", "this message is at the error level")
16
17 // Output:
18 // level=debug msg="this message is at the debug level"
19 // level=info msg="this message is at the info level"
20 // level=warn msg="this message is at the warn level"
21 // level=error msg="this message is at the error level"
22 }
23
24 func Example_filtered() {
25 // Set up logger with level filter.
1226 logger := log.NewLogfmtLogger(os.Stdout)
1327 logger = level.NewFilter(logger, level.AllowInfo())
1428 logger = log.With(logger, "caller", log.DefaultCaller)
1529
16 // use level helpers to log at different levels
30 // Use level helpers to log at different levels.
1731 level.Error(logger).Log("err", errors.New("bad data"))
1832 level.Info(logger).Log("event", "data saved")
1933 level.Debug(logger).Log("next item", 17) // filtered
2034
2135 // Output:
22 // level=error caller=example_test.go:18 err="bad data"
23 // level=info caller=example_test.go:19 event="data saved"
36 // level=error caller=example_test.go:32 err="bad data"
37 // level=info caller=example_test.go:33 event="data saved"
2438 }
00 package level
11
2 import "github.com/go-kit/kit/log"
2 import (
3 "github.com/go-kit/log"
4 "github.com/go-kit/log/level"
5 )
36
47 // Error returns a logger that includes a Key/ErrorValue pair.
58 func Error(logger log.Logger) log.Logger {
6 return log.WithPrefix(logger, Key(), ErrorValue())
9 return level.Error(logger)
710 }
811
912 // Warn returns a logger that includes a Key/WarnValue pair.
1013 func Warn(logger log.Logger) log.Logger {
11 return log.WithPrefix(logger, Key(), WarnValue())
14 return level.Warn(logger)
1215 }
1316
1417 // Info returns a logger that includes a Key/InfoValue pair.
1518 func Info(logger log.Logger) log.Logger {
16 return log.WithPrefix(logger, Key(), InfoValue())
19 return level.Info(logger)
1720 }
1821
1922 // Debug returns a logger that includes a Key/DebugValue pair.
2023 func Debug(logger log.Logger) log.Logger {
21 return log.WithPrefix(logger, Key(), DebugValue())
24 return level.Debug(logger)
2225 }
2326
2427 // NewFilter wraps next and implements level filtering. See the commentary on
2730 // Info, Warn or Error helper methods are squelched and non-leveled log
2831 // events are passed to next unmodified.
2932 func NewFilter(next log.Logger, options ...Option) log.Logger {
30 l := &logger{
31 next: next,
32 }
33 for _, option := range options {
34 option(l)
35 }
36 return l
37 }
38
39 type logger struct {
40 next log.Logger
41 allowed level
42 squelchNoLevel bool
43 errNotAllowed error
44 errNoLevel error
45 }
46
47 func (l *logger) Log(keyvals ...interface{}) error {
48 var hasLevel, levelAllowed bool
49 for i := 1; i < len(keyvals); i += 2 {
50 if v, ok := keyvals[i].(*levelValue); ok {
51 hasLevel = true
52 levelAllowed = l.allowed&v.level != 0
53 break
54 }
55 }
56 if !hasLevel && l.squelchNoLevel {
57 return l.errNoLevel
58 }
59 if hasLevel && !levelAllowed {
60 return l.errNotAllowed
61 }
62 return l.next.Log(keyvals...)
33 return level.NewFilter(next, options...)
6334 }
6435
6536 // Option sets a parameter for the leveled logger.
66 type Option func(*logger)
37 type Option = level.Option
6738
6839 // AllowAll is an alias for AllowDebug.
6940 func AllowAll() Option {
70 return AllowDebug()
41 return level.AllowAll()
7142 }
7243
7344 // AllowDebug allows error, warn, info and debug level log events to pass.
7445 func AllowDebug() Option {
75 return allowed(levelError | levelWarn | levelInfo | levelDebug)
46 return level.AllowDebug()
7647 }
7748
7849 // AllowInfo allows error, warn and info level log events to pass.
7950 func AllowInfo() Option {
80 return allowed(levelError | levelWarn | levelInfo)
51 return level.AllowInfo()
8152 }
8253
8354 // AllowWarn allows error and warn level log events to pass.
8455 func AllowWarn() Option {
85 return allowed(levelError | levelWarn)
56 return level.AllowWarn()
8657 }
8758
8859 // AllowError allows only error level log events to pass.
8960 func AllowError() Option {
90 return allowed(levelError)
61 return level.AllowError()
9162 }
9263
9364 // AllowNone allows no leveled log events to pass.
9465 func AllowNone() Option {
95 return allowed(0)
96 }
97
98 func allowed(allowed level) Option {
99 return func(l *logger) { l.allowed = allowed }
66 return level.AllowNone()
10067 }
10168
10269 // ErrNotAllowed sets the error to return from Log when it squelches a log
10471 // ErrNotAllowed is nil; in this case the log event is squelched with no
10572 // error.
10673 func ErrNotAllowed(err error) Option {
107 return func(l *logger) { l.errNotAllowed = err }
74 return level.ErrNotAllowed(err)
10875 }
10976
11077 // SquelchNoLevel instructs Log to squelch log events with no level, so that
11279 // to true and a log event is squelched in this way, the error value
11380 // configured with ErrNoLevel is returned to the caller.
11481 func SquelchNoLevel(squelch bool) Option {
115 return func(l *logger) { l.squelchNoLevel = squelch }
82 return level.SquelchNoLevel(squelch)
11683 }
11784
11885 // ErrNoLevel sets the error to return from Log when it squelches a log event
11986 // with no level. By default, ErrNoLevel is nil; in this case the log event is
12087 // squelched with no error.
12188 func ErrNoLevel(err error) Option {
122 return func(l *logger) { l.errNoLevel = err }
89 return level.ErrNoLevel(err)
12390 }
12491
12592 // NewInjector wraps next and returns a logger that adds a Key/level pair to
12693 // the beginning of log events that don't already contain a level. In effect,
12794 // this gives a default level to logs without a level.
128 func NewInjector(next log.Logger, level Value) log.Logger {
129 return &injector{
130 next: next,
131 level: level,
132 }
133 }
134
135 type injector struct {
136 next log.Logger
137 level interface{}
138 }
139
140 func (l *injector) Log(keyvals ...interface{}) error {
141 for i := 1; i < len(keyvals); i += 2 {
142 if _, ok := keyvals[i].(*levelValue); ok {
143 return l.next.Log(keyvals...)
144 }
145 }
146 kvs := make([]interface{}, len(keyvals)+2)
147 kvs[0], kvs[1] = key, l.level
148 copy(kvs[2:], keyvals)
149 return l.next.Log(kvs...)
95 func NewInjector(next log.Logger, lvl Value) log.Logger {
96 return level.NewInjector(next, lvl)
15097 }
15198
15299 // Value is the interface that each of the canonical level values implement.
153100 // It contains unexported methods that prevent types from other packages from
154101 // implementing it and guaranteeing that NewFilter can distinguish the levels
155102 // defined in this package from all other values.
156 type Value interface {
157 String() string
158 levelVal()
159 }
103 type Value = level.Value
160104
161105 // Key returns the unique key added to log events by the loggers in this
162106 // package.
163 func Key() interface{} { return key }
107 func Key() interface{} { return level.Key() }
164108
165109 // ErrorValue returns the unique value added to log events by Error.
166 func ErrorValue() Value { return errorValue }
110 func ErrorValue() Value { return level.ErrorValue() }
167111
168112 // WarnValue returns the unique value added to log events by Warn.
169 func WarnValue() Value { return warnValue }
113 func WarnValue() Value { return level.WarnValue() }
170114
171115 // InfoValue returns the unique value added to log events by Info.
172 func InfoValue() Value { return infoValue }
116 func InfoValue() Value { return level.InfoValue() }
173117
174 // DebugValue returns the unique value added to log events by Warn.
175 func DebugValue() Value { return debugValue }
176
177 var (
178 // key is of type interfae{} so that it allocates once during package
179 // initialization and avoids allocating every type the value is added to a
180 // []interface{} later.
181 key interface{} = "level"
182
183 errorValue = &levelValue{level: levelError, name: "error"}
184 warnValue = &levelValue{level: levelWarn, name: "warn"}
185 infoValue = &levelValue{level: levelInfo, name: "info"}
186 debugValue = &levelValue{level: levelDebug, name: "debug"}
187 )
188
189 type level byte
190
191 const (
192 levelDebug level = 1 << iota
193 levelInfo
194 levelWarn
195 levelError
196 )
197
198 type levelValue struct {
199 name string
200 level
201 }
202
203 func (v *levelValue) String() string { return v.name }
204 func (v *levelValue) levelVal() {}
118 // DebugValue returns the unique value added to log events by Debug.
119 func DebugValue() Value { return level.DebugValue() }
+0
-235
log/level/level_test.go less more
0 package level_test
1
2 import (
3 "bytes"
4 "errors"
5 "io"
6 "strings"
7 "testing"
8
9 "github.com/go-kit/kit/log"
10 "github.com/go-kit/kit/log/level"
11 )
12
13 func TestVariousLevels(t *testing.T) {
14 testCases := []struct {
15 name string
16 allowed level.Option
17 want string
18 }{
19 {
20 "AllowAll",
21 level.AllowAll(),
22 strings.Join([]string{
23 `{"level":"debug","this is":"debug log"}`,
24 `{"level":"info","this is":"info log"}`,
25 `{"level":"warn","this is":"warn log"}`,
26 `{"level":"error","this is":"error log"}`,
27 }, "\n"),
28 },
29 {
30 "AllowDebug",
31 level.AllowDebug(),
32 strings.Join([]string{
33 `{"level":"debug","this is":"debug log"}`,
34 `{"level":"info","this is":"info log"}`,
35 `{"level":"warn","this is":"warn log"}`,
36 `{"level":"error","this is":"error log"}`,
37 }, "\n"),
38 },
39 {
40 "AllowInfo",
41 level.AllowInfo(),
42 strings.Join([]string{
43 `{"level":"info","this is":"info log"}`,
44 `{"level":"warn","this is":"warn log"}`,
45 `{"level":"error","this is":"error log"}`,
46 }, "\n"),
47 },
48 {
49 "AllowWarn",
50 level.AllowWarn(),
51 strings.Join([]string{
52 `{"level":"warn","this is":"warn log"}`,
53 `{"level":"error","this is":"error log"}`,
54 }, "\n"),
55 },
56 {
57 "AllowError",
58 level.AllowError(),
59 strings.Join([]string{
60 `{"level":"error","this is":"error log"}`,
61 }, "\n"),
62 },
63 {
64 "AllowNone",
65 level.AllowNone(),
66 ``,
67 },
68 }
69
70 for _, tc := range testCases {
71 t.Run(tc.name, func(t *testing.T) {
72 var buf bytes.Buffer
73 logger := level.NewFilter(log.NewJSONLogger(&buf), tc.allowed)
74
75 level.Debug(logger).Log("this is", "debug log")
76 level.Info(logger).Log("this is", "info log")
77 level.Warn(logger).Log("this is", "warn log")
78 level.Error(logger).Log("this is", "error log")
79
80 if want, have := tc.want, strings.TrimSpace(buf.String()); want != have {
81 t.Errorf("\nwant:\n%s\nhave:\n%s", want, have)
82 }
83 })
84 }
85 }
86
87 func TestErrNotAllowed(t *testing.T) {
88 myError := errors.New("squelched!")
89 opts := []level.Option{
90 level.AllowWarn(),
91 level.ErrNotAllowed(myError),
92 }
93 logger := level.NewFilter(log.NewNopLogger(), opts...)
94
95 if want, have := myError, level.Info(logger).Log("foo", "bar"); want != have {
96 t.Errorf("want %#+v, have %#+v", want, have)
97 }
98
99 if want, have := error(nil), level.Warn(logger).Log("foo", "bar"); want != have {
100 t.Errorf("want %#+v, have %#+v", want, have)
101 }
102 }
103
104 func TestErrNoLevel(t *testing.T) {
105 myError := errors.New("no level specified")
106
107 var buf bytes.Buffer
108 opts := []level.Option{
109 level.SquelchNoLevel(true),
110 level.ErrNoLevel(myError),
111 }
112 logger := level.NewFilter(log.NewJSONLogger(&buf), opts...)
113
114 if want, have := myError, logger.Log("foo", "bar"); want != have {
115 t.Errorf("want %v, have %v", want, have)
116 }
117 if want, have := ``, strings.TrimSpace(buf.String()); want != have {
118 t.Errorf("\nwant '%s'\nhave '%s'", want, have)
119 }
120 }
121
122 func TestAllowNoLevel(t *testing.T) {
123 var buf bytes.Buffer
124 opts := []level.Option{
125 level.SquelchNoLevel(false),
126 level.ErrNoLevel(errors.New("I should never be returned!")),
127 }
128 logger := level.NewFilter(log.NewJSONLogger(&buf), opts...)
129
130 if want, have := error(nil), logger.Log("foo", "bar"); want != have {
131 t.Errorf("want %v, have %v", want, have)
132 }
133 if want, have := `{"foo":"bar"}`, strings.TrimSpace(buf.String()); want != have {
134 t.Errorf("\nwant '%s'\nhave '%s'", want, have)
135 }
136 }
137
138 func TestLevelContext(t *testing.T) {
139 var buf bytes.Buffer
140
141 // Wrapping the level logger with a context allows users to use
142 // log.DefaultCaller as per normal.
143 var logger log.Logger
144 logger = log.NewLogfmtLogger(&buf)
145 logger = level.NewFilter(logger, level.AllowAll())
146 logger = log.With(logger, "caller", log.DefaultCaller)
147
148 level.Info(logger).Log("foo", "bar")
149 if want, have := `level=info caller=level_test.go:149 foo=bar`, strings.TrimSpace(buf.String()); want != have {
150 t.Errorf("\nwant '%s'\nhave '%s'", want, have)
151 }
152 }
153
154 func TestContextLevel(t *testing.T) {
155 var buf bytes.Buffer
156
157 // Wrapping a context with the level logger still works, but requires users
158 // to specify a higher callstack depth value.
159 var logger log.Logger
160 logger = log.NewLogfmtLogger(&buf)
161 logger = log.With(logger, "caller", log.Caller(5))
162 logger = level.NewFilter(logger, level.AllowAll())
163
164 level.Info(logger).Log("foo", "bar")
165 if want, have := `caller=level_test.go:165 level=info foo=bar`, strings.TrimSpace(buf.String()); want != have {
166 t.Errorf("\nwant '%s'\nhave '%s'", want, have)
167 }
168 }
169
170 func TestLevelFormatting(t *testing.T) {
171 testCases := []struct {
172 name string
173 format func(io.Writer) log.Logger
174 output string
175 }{
176 {
177 name: "logfmt",
178 format: log.NewLogfmtLogger,
179 output: `level=info foo=bar`,
180 },
181 {
182 name: "JSON",
183 format: log.NewJSONLogger,
184 output: `{"foo":"bar","level":"info"}`,
185 },
186 }
187
188 for _, tc := range testCases {
189 t.Run(tc.name, func(t *testing.T) {
190 var buf bytes.Buffer
191
192 logger := tc.format(&buf)
193 level.Info(logger).Log("foo", "bar")
194 if want, have := tc.output, strings.TrimSpace(buf.String()); want != have {
195 t.Errorf("\nwant: '%s'\nhave '%s'", want, have)
196 }
197 })
198 }
199 }
200
201 func TestInjector(t *testing.T) {
202 var (
203 output []interface{}
204 logger log.Logger
205 )
206
207 logger = log.LoggerFunc(func(keyvals ...interface{}) error {
208 output = keyvals
209 return nil
210 })
211 logger = level.NewInjector(logger, level.InfoValue())
212
213 logger.Log("foo", "bar")
214 if got, want := len(output), 4; got != want {
215 t.Errorf("missing level not injected: got len==%d, want len==%d", got, want)
216 }
217 if got, want := output[0], level.Key(); got != want {
218 t.Errorf("wrong level key: got %#v, want %#v", got, want)
219 }
220 if got, want := output[1], level.InfoValue(); got != want {
221 t.Errorf("wrong level value: got %#v, want %#v", got, want)
222 }
223
224 level.Error(logger).Log("foo", "bar")
225 if got, want := len(output), 4; got != want {
226 t.Errorf("leveled record modified: got len==%d, want len==%d", got, want)
227 }
228 if got, want := output[0], level.Key(); got != want {
229 t.Errorf("wrong level key: got %#v, want %#v", got, want)
230 }
231 if got, want := output[1], level.ErrorValue(); got != want {
232 t.Errorf("wrong level value: got %#v, want %#v", got, want)
233 }
234 }
00 package log
11
2 import "errors"
2 import (
3 "github.com/go-kit/log"
4 )
35
46 // Logger is the fundamental interface for all log operations. Log creates a
57 // log event from keyvals, a variadic sequence of alternating keys and values.
68 // Implementations must be safe for concurrent use by multiple goroutines. In
79 // particular, any implementation of Logger that appends to keyvals or
810 // modifies or retains any of its elements must make a copy first.
9 type Logger interface {
10 Log(keyvals ...interface{}) error
11 }
11 type Logger = log.Logger
1212
1313 // ErrMissingValue is appended to keyvals slices with odd length to substitute
1414 // the missing value.
15 var ErrMissingValue = errors.New("(MISSING)")
15 var ErrMissingValue = log.ErrMissingValue
1616
1717 // With returns a new contextual logger with keyvals prepended to those passed
18 // to calls to Log. If logger is also a contextual logger created by With or
19 // WithPrefix, keyvals is appended to the existing context.
18 // to calls to Log. If logger is also a contextual logger created by With,
19 // WithPrefix, or WithSuffix, keyvals is appended to the existing context.
2020 //
2121 // The returned Logger replaces all value elements (odd indexes) containing a
2222 // Valuer with their generated value for each call to its Log method.
2323 func With(logger Logger, keyvals ...interface{}) Logger {
24 if len(keyvals) == 0 {
25 return logger
26 }
27 l := newContext(logger)
28 kvs := append(l.keyvals, keyvals...)
29 if len(kvs)%2 != 0 {
30 kvs = append(kvs, ErrMissingValue)
31 }
32 return &context{
33 logger: l.logger,
34 // Limiting the capacity of the stored keyvals ensures that a new
35 // backing array is created if the slice must grow in Log or With.
36 // Using the extra capacity without copying risks a data race that
37 // would violate the Logger interface contract.
38 keyvals: kvs[:len(kvs):len(kvs)],
39 hasValuer: l.hasValuer || containsValuer(keyvals),
40 }
24 return log.With(logger, keyvals...)
4125 }
4226
4327 // WithPrefix returns a new contextual logger with keyvals prepended to those
4428 // passed to calls to Log. If logger is also a contextual logger created by
45 // With or WithPrefix, keyvals is prepended to the existing context.
29 // With, WithPrefix, or WithSuffix, keyvals is prepended to the existing context.
4630 //
4731 // The returned Logger replaces all value elements (odd indexes) containing a
4832 // Valuer with their generated value for each call to its Log method.
4933 func WithPrefix(logger Logger, keyvals ...interface{}) Logger {
50 if len(keyvals) == 0 {
51 return logger
52 }
53 l := newContext(logger)
54 // Limiting the capacity of the stored keyvals ensures that a new
55 // backing array is created if the slice must grow in Log or With.
56 // Using the extra capacity without copying risks a data race that
57 // would violate the Logger interface contract.
58 n := len(l.keyvals) + len(keyvals)
59 if len(keyvals)%2 != 0 {
60 n++
61 }
62 kvs := make([]interface{}, 0, n)
63 kvs = append(kvs, keyvals...)
64 if len(kvs)%2 != 0 {
65 kvs = append(kvs, ErrMissingValue)
66 }
67 kvs = append(kvs, l.keyvals...)
68 return &context{
69 logger: l.logger,
70 keyvals: kvs,
71 hasValuer: l.hasValuer || containsValuer(keyvals),
72 }
34 return log.WithPrefix(logger, keyvals...)
7335 }
7436
75 // context is the Logger implementation returned by With and WithPrefix. It
76 // wraps a Logger and holds keyvals that it includes in all log events. Its
77 // Log method calls bindValues to generate values for each Valuer in the
78 // context keyvals.
37 // WithSuffix returns a new contextual logger with keyvals appended to those
38 // passed to calls to Log. If logger is also a contextual logger created by
39 // With, WithPrefix, or WithSuffix, keyvals is appended to the existing context.
7940 //
80 // A context must always have the same number of stack frames between calls to
81 // its Log method and the eventual binding of Valuers to their value. This
82 // requirement comes from the functional requirement to allow a context to
83 // resolve application call site information for a Caller stored in the
84 // context. To do this we must be able to predict the number of logging
85 // functions on the stack when bindValues is called.
86 //
87 // Two implementation details provide the needed stack depth consistency.
88 //
89 // 1. newContext avoids introducing an additional layer when asked to
90 // wrap another context.
91 // 2. With and WithPrefix avoid introducing an additional layer by
92 // returning a newly constructed context with a merged keyvals rather
93 // than simply wrapping the existing context.
94 type context struct {
95 logger Logger
96 keyvals []interface{}
97 hasValuer bool
98 }
99
100 func newContext(logger Logger) *context {
101 if c, ok := logger.(*context); ok {
102 return c
103 }
104 return &context{logger: logger}
105 }
106
107 // Log replaces all value elements (odd indexes) containing a Valuer in the
108 // stored context with their generated value, appends keyvals, and passes the
109 // result to the wrapped Logger.
110 func (l *context) Log(keyvals ...interface{}) error {
111 kvs := append(l.keyvals, keyvals...)
112 if len(kvs)%2 != 0 {
113 kvs = append(kvs, ErrMissingValue)
114 }
115 if l.hasValuer {
116 // If no keyvals were appended above then we must copy l.keyvals so
117 // that future log events will reevaluate the stored Valuers.
118 if len(keyvals) == 0 {
119 kvs = append([]interface{}{}, l.keyvals...)
120 }
121 bindValues(kvs[:len(l.keyvals)])
122 }
123 return l.logger.Log(kvs...)
41 // The returned Logger replaces all value elements (odd indexes) containing a
42 // Valuer with their generated value for each call to its Log method.
43 func WithSuffix(logger Logger, keyvals ...interface{}) Logger {
44 return log.WithSuffix(logger, keyvals...)
12445 }
12546
12647 // LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If
12748 // f is a function with the appropriate signature, LoggerFunc(f) is a Logger
12849 // object that calls f.
129 type LoggerFunc func(...interface{}) error
130
131 // Log implements Logger by calling f(keyvals...).
132 func (f LoggerFunc) Log(keyvals ...interface{}) error {
133 return f(keyvals...)
134 }
50 type LoggerFunc = log.LoggerFunc
+0
-191
log/log_test.go less more
0 package log_test
1
2 import (
3 "bytes"
4 "fmt"
5 "sync"
6 "testing"
7
8 "github.com/go-kit/kit/log"
9 "github.com/go-stack/stack"
10 )
11
12 func TestContext(t *testing.T) {
13 t.Parallel()
14 buf := &bytes.Buffer{}
15 logger := log.NewLogfmtLogger(buf)
16
17 kvs := []interface{}{"a", 123}
18 lc := log.With(logger, kvs...)
19 kvs[1] = 0 // With should copy its key values
20
21 lc = log.With(lc, "b", "c") // With should stack
22 if err := lc.Log("msg", "message"); err != nil {
23 t.Fatal(err)
24 }
25 if want, have := "a=123 b=c msg=message\n", buf.String(); want != have {
26 t.Errorf("\nwant: %shave: %s", want, have)
27 }
28
29 buf.Reset()
30 lc = log.WithPrefix(lc, "p", "first")
31 if err := lc.Log("msg", "message"); err != nil {
32 t.Fatal(err)
33 }
34 if want, have := "p=first a=123 b=c msg=message\n", buf.String(); want != have {
35 t.Errorf("\nwant: %shave: %s", want, have)
36 }
37 }
38
39 func TestContextMissingValue(t *testing.T) {
40 t.Parallel()
41 var output []interface{}
42 logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error {
43 output = keyvals
44 return nil
45 }))
46
47 log.WithPrefix(log.With(logger, "k1"), "k0").Log("k2")
48 if want, have := 6, len(output); want != have {
49 t.Errorf("want len(output) == %v, have %v", want, have)
50 }
51 for i := 1; i < 6; i += 2 {
52 if want, have := log.ErrMissingValue, output[i]; want != have {
53 t.Errorf("want output[%d] == %#v, have %#v", i, want, have)
54 }
55 }
56 }
57
58 // Test that context.Log has a consistent function stack depth when binding
59 // Valuers, regardless of how many times With has been called.
60 func TestContextStackDepth(t *testing.T) {
61 t.Parallel()
62 fn := fmt.Sprintf("%n", stack.Caller(0))
63
64 var output []interface{}
65
66 logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error {
67 output = keyvals
68 return nil
69 }))
70
71 stackValuer := log.Valuer(func() interface{} {
72 for i, c := range stack.Trace() {
73 if fmt.Sprintf("%n", c) == fn {
74 return i
75 }
76 }
77 t.Fatal("Test function not found in stack trace.")
78 return nil
79 })
80
81 logger = log.With(logger, "stack", stackValuer)
82
83 // Call through interface to get baseline.
84 logger.Log("k", "v")
85 want := output[1].(int)
86
87 for len(output) < 10 {
88 logger.Log("k", "v")
89 if have := output[1]; have != want {
90 t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want)
91 }
92
93 wrapped := log.With(logger)
94 wrapped.Log("k", "v")
95 if have := output[1]; have != want {
96 t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want)
97 }
98
99 logger = log.With(logger, "k", "v")
100 }
101 }
102
103 // Test that With returns a Logger safe for concurrent use. This test
104 // validates that the stored logging context does not get corrupted when
105 // multiple clients concurrently log additional keyvals.
106 //
107 // This test must be run with go test -cpu 2 (or more) to achieve its goal.
108 func TestWithConcurrent(t *testing.T) {
109 // Create some buckets to count how many events each goroutine logs.
110 const goroutines = 8
111 counts := [goroutines]int{}
112
113 // This logger extracts a goroutine id from the last value field and
114 // increments the referenced bucket.
115 logger := log.LoggerFunc(func(kv ...interface{}) error {
116 goroutine := kv[len(kv)-1].(int)
117 counts[goroutine]++
118 return nil
119 })
120
121 // With must be careful about handling slices that can grow without
122 // copying the underlying array, so give it a challenge.
123 l := log.With(logger, make([]interface{}, 0, 2)...)
124
125 // Start logging concurrently. Each goroutine logs its id so the logger
126 // can bucket the event counts.
127 var wg sync.WaitGroup
128 wg.Add(goroutines)
129 const n = 10000
130 for i := 0; i < goroutines; i++ {
131 go func(idx int) {
132 defer wg.Done()
133 for j := 0; j < n; j++ {
134 l.Log("goroutineIdx", idx)
135 }
136 }(i)
137 }
138 wg.Wait()
139
140 for bucket, have := range counts {
141 if want := n; want != have {
142 t.Errorf("bucket %d: want %d, have %d", bucket, want, have) // note Errorf
143 }
144 }
145 }
146
147 func BenchmarkDiscard(b *testing.B) {
148 logger := log.NewNopLogger()
149 b.ReportAllocs()
150 b.ResetTimer()
151 for i := 0; i < b.N; i++ {
152 logger.Log("k", "v")
153 }
154 }
155
156 func BenchmarkOneWith(b *testing.B) {
157 logger := log.NewNopLogger()
158 lc := log.With(logger, "k", "v")
159 b.ReportAllocs()
160 b.ResetTimer()
161 for i := 0; i < b.N; i++ {
162 lc.Log("k", "v")
163 }
164 }
165
166 func BenchmarkTwoWith(b *testing.B) {
167 logger := log.NewNopLogger()
168 lc := log.With(logger, "k", "v")
169 for i := 1; i < 2; i++ {
170 lc = log.With(lc, "k", "v")
171 }
172 b.ReportAllocs()
173 b.ResetTimer()
174 for i := 0; i < b.N; i++ {
175 lc.Log("k", "v")
176 }
177 }
178
179 func BenchmarkTenWith(b *testing.B) {
180 logger := log.NewNopLogger()
181 lc := log.With(logger, "k", "v")
182 for i := 1; i < 10; i++ {
183 lc = log.With(lc, "k", "v")
184 }
185 b.ReportAllocs()
186 b.ResetTimer()
187 for i := 0; i < b.N; i++ {
188 lc.Log("k", "v")
189 }
190 }
00 package log
11
22 import (
3 "bytes"
43 "io"
5 "sync"
64
7 "github.com/go-logfmt/logfmt"
5 "github.com/go-kit/log"
86 )
9
10 type logfmtEncoder struct {
11 *logfmt.Encoder
12 buf bytes.Buffer
13 }
14
15 func (l *logfmtEncoder) Reset() {
16 l.Encoder.Reset()
17 l.buf.Reset()
18 }
19
20 var logfmtEncoderPool = sync.Pool{
21 New: func() interface{} {
22 var enc logfmtEncoder
23 enc.Encoder = logfmt.NewEncoder(&enc.buf)
24 return &enc
25 },
26 }
27
28 type logfmtLogger struct {
29 w io.Writer
30 }
317
328 // NewLogfmtLogger returns a logger that encodes keyvals to the Writer in
339 // logfmt format. Each log event produces no more than one call to w.Write.
3410 // The passed Writer must be safe for concurrent use by multiple goroutines if
3511 // the returned Logger will be used concurrently.
3612 func NewLogfmtLogger(w io.Writer) Logger {
37 return &logfmtLogger{w}
13 return log.NewLogfmtLogger(w)
3814 }
39
40 func (l logfmtLogger) Log(keyvals ...interface{}) error {
41 enc := logfmtEncoderPool.Get().(*logfmtEncoder)
42 enc.Reset()
43 defer logfmtEncoderPool.Put(enc)
44
45 if err := enc.EncodeKeyvals(keyvals...); err != nil {
46 return err
47 }
48
49 // Add newline to the end of the buffer
50 if err := enc.EndRecord(); err != nil {
51 return err
52 }
53
54 // The Logger interface requires implementations to be safe for concurrent
55 // use by multiple goroutines. For this implementation that means making
56 // only one call to l.w.Write() for each call to Log.
57 if _, err := l.w.Write(enc.buf.Bytes()); err != nil {
58 return err
59 }
60 return nil
61 }
+0
-57
log/logfmt_logger_test.go less more
0 package log_test
1
2 import (
3 "bytes"
4 "errors"
5 "io/ioutil"
6 "testing"
7
8 "github.com/go-kit/kit/log"
9 "github.com/go-logfmt/logfmt"
10 )
11
12 func TestLogfmtLogger(t *testing.T) {
13 t.Parallel()
14 buf := &bytes.Buffer{}
15 logger := log.NewLogfmtLogger(buf)
16
17 if err := logger.Log("hello", "world"); err != nil {
18 t.Fatal(err)
19 }
20 if want, have := "hello=world\n", buf.String(); want != have {
21 t.Errorf("want %#v, have %#v", want, have)
22 }
23
24 buf.Reset()
25 if err := logger.Log("a", 1, "err", errors.New("error")); err != nil {
26 t.Fatal(err)
27 }
28 if want, have := "a=1 err=error\n", buf.String(); want != have {
29 t.Errorf("want %#v, have %#v", want, have)
30 }
31
32 buf.Reset()
33 if err := logger.Log("std_map", map[int]int{1: 2}, "my_map", mymap{0: 0}); err != nil {
34 t.Fatal(err)
35 }
36 if want, have := "std_map=\""+logfmt.ErrUnsupportedValueType.Error()+"\" my_map=special_behavior\n", buf.String(); want != have {
37 t.Errorf("want %#v, have %#v", want, have)
38 }
39 }
40
41 func BenchmarkLogfmtLoggerSimple(b *testing.B) {
42 benchmarkRunner(b, log.NewLogfmtLogger(ioutil.Discard), baseMessage)
43 }
44
45 func BenchmarkLogfmtLoggerContextual(b *testing.B) {
46 benchmarkRunner(b, log.NewLogfmtLogger(ioutil.Discard), withMessage)
47 }
48
49 func TestLogfmtLoggerConcurrency(t *testing.T) {
50 t.Parallel()
51 testConcurrency(t, log.NewLogfmtLogger(ioutil.Discard), 10000)
52 }
53
54 type mymap map[int]int
55
56 func (m mymap) String() string { return "special_behavior" }
0 // Package logrus provides an adapter to the
1 // go-kit log.Logger interface.
2 package logrus
3
4 import (
5 "errors"
6 "fmt"
7
8 "github.com/go-kit/log"
9 "github.com/sirupsen/logrus"
10 )
11
12 type Logger struct {
13 field logrus.FieldLogger
14 level logrus.Level
15 }
16
17 type Option func(*Logger)
18
19 var errMissingValue = errors.New("(MISSING)")
20
21 // NewLogger returns a Go kit log.Logger that sends log events to a logrus.Logger.
22 func NewLogger(logger logrus.FieldLogger, options ...Option) log.Logger {
23 l := &Logger{
24 field: logger,
25 level: logrus.InfoLevel,
26 }
27
28 for _, optFunc := range options {
29 optFunc(l)
30 }
31
32 return l
33 }
34
35 // WithLevel configures a logrus logger to log at level for all events.
36 func WithLevel(level logrus.Level) Option {
37 return func(c *Logger) {
38 c.level = level
39 }
40 }
41
42 func (l Logger) Log(keyvals ...interface{}) error {
43 fields := logrus.Fields{}
44 for i := 0; i < len(keyvals); i += 2 {
45 if i+1 < len(keyvals) {
46 fields[fmt.Sprint(keyvals[i])] = keyvals[i+1]
47 } else {
48 fields[fmt.Sprint(keyvals[i])] = errMissingValue
49 }
50 }
51
52 switch l.level {
53 case logrus.InfoLevel:
54 l.field.WithFields(fields).Info()
55 case logrus.ErrorLevel:
56 l.field.WithFields(fields).Error()
57 case logrus.DebugLevel:
58 l.field.WithFields(fields).Debug()
59 case logrus.WarnLevel:
60 l.field.WithFields(fields).Warn()
61 case logrus.TraceLevel:
62 l.field.WithFields(fields).Trace()
63 default:
64 l.field.WithFields(fields).Print()
65 }
66
67 return nil
68 }
0 package logrus_test
1
2 import (
3 "bytes"
4 "encoding/json"
5 "errors"
6 "strings"
7 "testing"
8
9 log "github.com/go-kit/kit/log/logrus"
10 "github.com/sirupsen/logrus"
11 )
12
13 func TestLogrusLogger(t *testing.T) {
14 t.Parallel()
15 buf := &bytes.Buffer{}
16 logrusLogger := logrus.New()
17 logrusLogger.Out = buf
18 logrusLogger.Formatter = &logrus.TextFormatter{TimestampFormat: "02-01-2006 15:04:05", FullTimestamp: true}
19 logger := log.NewLogger(logrusLogger)
20
21 if err := logger.Log("hello", "world"); err != nil {
22 t.Fatal(err)
23 }
24 if want, have := "hello=world\n", strings.Split(buf.String(), " ")[3]; want != have {
25 t.Errorf("want %#v, have %#v", want, have)
26 }
27
28 buf.Reset()
29 if err := logger.Log("a", 1, "err", errors.New("error")); err != nil {
30 t.Fatal(err)
31 }
32 if want, have := "a=1 err=error", strings.TrimSpace(strings.SplitAfterN(buf.String(), " ", 4)[3]); want != have {
33 t.Errorf("want %#v, have %#v", want, have)
34 }
35
36 buf.Reset()
37 if err := logger.Log("a", 1, "b"); err != nil {
38 t.Fatal(err)
39 }
40 if want, have := "a=1 b=\"(MISSING)\"", strings.TrimSpace(strings.SplitAfterN(buf.String(), " ", 4)[3]); want != have {
41 t.Errorf("want %#v, have %#v", want, have)
42 }
43
44 buf.Reset()
45 if err := logger.Log("my_map", mymap{0: 0}); err != nil {
46 t.Fatal(err)
47 }
48 if want, have := "my_map=special_behavior", strings.TrimSpace(strings.Split(buf.String(), " ")[3]); want != have {
49 t.Errorf("want %#v, have %#v", want, have)
50 }
51 }
52
53 type mymap map[int]int
54
55 func (m mymap) String() string { return "special_behavior" }
56
57 func TestWithLevel(t *testing.T) {
58 tests := []struct {
59 name string
60 level logrus.Level
61 expectedLevel logrus.Level
62 }{
63 {
64 name: "Test Debug level",
65 level: logrus.DebugLevel,
66 expectedLevel: logrus.DebugLevel,
67 },
68 {
69 name: "Test Error level",
70 level: logrus.ErrorLevel,
71 expectedLevel: logrus.ErrorLevel,
72 },
73 {
74 name: "Test Warn level",
75 level: logrus.WarnLevel,
76 expectedLevel: logrus.WarnLevel,
77 },
78 {
79 name: "Test Info level",
80 level: logrus.InfoLevel,
81 expectedLevel: logrus.InfoLevel,
82 },
83 {
84 name: "Test Trace level",
85 level: logrus.TraceLevel,
86 expectedLevel: logrus.TraceLevel,
87 },
88 {
89 name: "Test not existing level",
90 level: 999,
91 expectedLevel: logrus.InfoLevel,
92 },
93 }
94 for _, tt := range tests {
95 buf := &bytes.Buffer{}
96 logrusLogger := logrus.New()
97 logrusLogger.Out = buf
98 logrusLogger.Level = tt.level
99 logrusLogger.Formatter = &logrus.JSONFormatter{}
100 logger := log.NewLogger(logrusLogger, log.WithLevel(tt.level))
101
102 t.Run(tt.name, func(t *testing.T) {
103 if err := logger.Log(); err != nil {
104 t.Fatal(err)
105 }
106
107 l := map[string]interface{}{}
108 if err := json.Unmarshal(buf.Bytes(), &l); err != nil {
109 t.Fatal(err)
110 }
111
112 if v, ok := l["level"].(string); !ok || v != tt.expectedLevel.String() {
113 t.Fatalf("Logging levels doesn't match. Expected: %s, got: %s", tt.level, v)
114 }
115
116 })
117 }
118 }
00 package log
11
2 type nopLogger struct{}
2 import "github.com/go-kit/log"
33
44 // NewNopLogger returns a logger that doesn't do anything.
5 func NewNopLogger() Logger { return nopLogger{} }
6
7 func (nopLogger) Log(...interface{}) error { return nil }
5 func NewNopLogger() Logger {
6 return log.NewNopLogger()
7 }
+0
-26
log/nop_logger_test.go less more
0 package log_test
1
2 import (
3 "testing"
4
5 "github.com/go-kit/kit/log"
6 )
7
8 func TestNopLogger(t *testing.T) {
9 t.Parallel()
10 logger := log.NewNopLogger()
11 if err := logger.Log("abc", 123); err != nil {
12 t.Error(err)
13 }
14 if err := log.With(logger, "def", "ghi").Log(); err != nil {
15 t.Error(err)
16 }
17 }
18
19 func BenchmarkNopLoggerSimple(b *testing.B) {
20 benchmarkRunner(b, log.NewNopLogger(), baseMessage)
21 }
22
23 func BenchmarkNopLoggerContextual(b *testing.B) {
24 benchmarkRunner(b, log.NewNopLogger(), withMessage)
25 }
11
22 import (
33 "io"
4 "log"
5 "regexp"
6 "strings"
4
5 "github.com/go-kit/log"
76 )
87
98 // StdlibWriter implements io.Writer by invoking the stdlib log.Print. It's
1211 //
1312 // If you have any choice in the matter, you shouldn't use this. Prefer to
1413 // redirect the stdlib log to the Go kit logger via NewStdlibAdapter.
15 type StdlibWriter struct{}
16
17 // Write implements io.Writer.
18 func (w StdlibWriter) Write(p []byte) (int, error) {
19 log.Print(strings.TrimSpace(string(p)))
20 return len(p), nil
21 }
14 type StdlibWriter = log.StdlibWriter
2215
2316 // StdlibAdapter wraps a Logger and allows it to be passed to the stdlib
2417 // logger's SetOutput. It will extract date/timestamps, filenames, and
2518 // messages, and place them under relevant keys.
26 type StdlibAdapter struct {
27 Logger
28 timestampKey string
29 fileKey string
30 messageKey string
31 }
19 type StdlibAdapter = log.StdlibAdapter
3220
3321 // StdlibAdapterOption sets a parameter for the StdlibAdapter.
34 type StdlibAdapterOption func(*StdlibAdapter)
22 type StdlibAdapterOption = log.StdlibAdapterOption
3523
3624 // TimestampKey sets the key for the timestamp field. By default, it's "ts".
3725 func TimestampKey(key string) StdlibAdapterOption {
38 return func(a *StdlibAdapter) { a.timestampKey = key }
26 return log.TimestampKey(key)
3927 }
4028
4129 // FileKey sets the key for the file and line field. By default, it's "caller".
4230 func FileKey(key string) StdlibAdapterOption {
43 return func(a *StdlibAdapter) { a.fileKey = key }
31 return log.FileKey(key)
4432 }
4533
4634 // MessageKey sets the key for the actual log message. By default, it's "msg".
4735 func MessageKey(key string) StdlibAdapterOption {
48 return func(a *StdlibAdapter) { a.messageKey = key }
36 return log.MessageKey(key)
37 }
38
39 // Prefix configures the adapter to parse a prefix from stdlib log events. If
40 // you provide a non-empty prefix to the stdlib logger, then your should provide
41 // that same prefix to the adapter via this option.
42 //
43 // By default, the prefix isn't included in the msg key. Set joinPrefixToMsg to
44 // true if you want to include the parsed prefix in the msg.
45 func Prefix(prefix string, joinPrefixToMsg bool) StdlibAdapterOption {
46 return log.Prefix(prefix, joinPrefixToMsg)
4947 }
5048
5149 // NewStdlibAdapter returns a new StdlibAdapter wrapper around the passed
5250 // logger. It's designed to be passed to log.SetOutput.
5351 func NewStdlibAdapter(logger Logger, options ...StdlibAdapterOption) io.Writer {
54 a := StdlibAdapter{
55 Logger: logger,
56 timestampKey: "ts",
57 fileKey: "caller",
58 messageKey: "msg",
59 }
60 for _, option := range options {
61 option(&a)
62 }
63 return a
52 return log.NewStdlibAdapter(logger, options...)
6453 }
65
66 func (a StdlibAdapter) Write(p []byte) (int, error) {
67 result := subexps(p)
68 keyvals := []interface{}{}
69 var timestamp string
70 if date, ok := result["date"]; ok && date != "" {
71 timestamp = date
72 }
73 if time, ok := result["time"]; ok && time != "" {
74 if timestamp != "" {
75 timestamp += " "
76 }
77 timestamp += time
78 }
79 if timestamp != "" {
80 keyvals = append(keyvals, a.timestampKey, timestamp)
81 }
82 if file, ok := result["file"]; ok && file != "" {
83 keyvals = append(keyvals, a.fileKey, file)
84 }
85 if msg, ok := result["msg"]; ok {
86 keyvals = append(keyvals, a.messageKey, msg)
87 }
88 if err := a.Logger.Log(keyvals...); err != nil {
89 return 0, err
90 }
91 return len(p), nil
92 }
93
94 const (
95 logRegexpDate = `(?P<date>[0-9]{4}/[0-9]{2}/[0-9]{2})?[ ]?`
96 logRegexpTime = `(?P<time>[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?)?[ ]?`
97 logRegexpFile = `(?P<file>.+?:[0-9]+)?`
98 logRegexpMsg = `(: )?(?P<msg>.*)`
99 )
100
101 var (
102 logRegexp = regexp.MustCompile(logRegexpDate + logRegexpTime + logRegexpFile + logRegexpMsg)
103 )
104
105 func subexps(line []byte) map[string]string {
106 m := logRegexp.FindSubmatch(line)
107 if len(m) < len(logRegexp.SubexpNames()) {
108 return map[string]string{}
109 }
110 result := map[string]string{}
111 for i, name := range logRegexp.SubexpNames() {
112 result[name] = string(m[i])
113 }
114 return result
115 }
+0
-205
log/stdlib_test.go less more
0 package log
1
2 import (
3 "bytes"
4 "fmt"
5 "log"
6 "testing"
7 "time"
8 )
9
10 func TestStdlibWriter(t *testing.T) {
11 buf := &bytes.Buffer{}
12 log.SetOutput(buf)
13 log.SetFlags(log.LstdFlags)
14 logger := NewLogfmtLogger(StdlibWriter{})
15 logger.Log("key", "val")
16 timestamp := time.Now().Format("2006/01/02 15:04:05")
17 if want, have := timestamp+" key=val\n", buf.String(); want != have {
18 t.Errorf("want %q, have %q", want, have)
19 }
20 }
21
22 func TestStdlibAdapterUsage(t *testing.T) {
23 buf := &bytes.Buffer{}
24 logger := NewLogfmtLogger(buf)
25 writer := NewStdlibAdapter(logger)
26 stdlog := log.New(writer, "", 0)
27
28 now := time.Now()
29 date := now.Format("2006/01/02")
30 time := now.Format("15:04:05")
31
32 for flag, want := range map[int]string{
33 0: "msg=hello\n",
34 log.Ldate: "ts=" + date + " msg=hello\n",
35 log.Ltime: "ts=" + time + " msg=hello\n",
36 log.Ldate | log.Ltime: "ts=\"" + date + " " + time + "\" msg=hello\n",
37 log.Lshortfile: "caller=stdlib_test.go:44 msg=hello\n",
38 log.Lshortfile | log.Ldate: "ts=" + date + " caller=stdlib_test.go:44 msg=hello\n",
39 log.Lshortfile | log.Ldate | log.Ltime: "ts=\"" + date + " " + time + "\" caller=stdlib_test.go:44 msg=hello\n",
40 } {
41 buf.Reset()
42 stdlog.SetFlags(flag)
43 stdlog.Print("hello")
44 if have := buf.String(); want != have {
45 t.Errorf("flag=%d: want %#v, have %#v", flag, want, have)
46 }
47 }
48 }
49
50 func TestStdLibAdapterExtraction(t *testing.T) {
51 buf := &bytes.Buffer{}
52 logger := NewLogfmtLogger(buf)
53 writer := NewStdlibAdapter(logger)
54 for input, want := range map[string]string{
55 "hello": "msg=hello\n",
56 "2009/01/23: hello": "ts=2009/01/23 msg=hello\n",
57 "2009/01/23 01:23:23: hello": "ts=\"2009/01/23 01:23:23\" msg=hello\n",
58 "01:23:23: hello": "ts=01:23:23 msg=hello\n",
59 "2009/01/23 01:23:23.123123: hello": "ts=\"2009/01/23 01:23:23.123123\" msg=hello\n",
60 "2009/01/23 01:23:23.123123 /a/b/c/d.go:23: hello": "ts=\"2009/01/23 01:23:23.123123\" caller=/a/b/c/d.go:23 msg=hello\n",
61 "01:23:23.123123 /a/b/c/d.go:23: hello": "ts=01:23:23.123123 caller=/a/b/c/d.go:23 msg=hello\n",
62 "2009/01/23 01:23:23 /a/b/c/d.go:23: hello": "ts=\"2009/01/23 01:23:23\" caller=/a/b/c/d.go:23 msg=hello\n",
63 "2009/01/23 /a/b/c/d.go:23: hello": "ts=2009/01/23 caller=/a/b/c/d.go:23 msg=hello\n",
64 "/a/b/c/d.go:23: hello": "caller=/a/b/c/d.go:23 msg=hello\n",
65 } {
66 buf.Reset()
67 fmt.Fprint(writer, input)
68 if have := buf.String(); want != have {
69 t.Errorf("%q: want %#v, have %#v", input, want, have)
70 }
71 }
72 }
73
74 func TestStdlibAdapterSubexps(t *testing.T) {
75 for input, wantMap := range map[string]map[string]string{
76 "hello world": {
77 "date": "",
78 "time": "",
79 "file": "",
80 "msg": "hello world",
81 },
82 "2009/01/23: hello world": {
83 "date": "2009/01/23",
84 "time": "",
85 "file": "",
86 "msg": "hello world",
87 },
88 "2009/01/23 01:23:23: hello world": {
89 "date": "2009/01/23",
90 "time": "01:23:23",
91 "file": "",
92 "msg": "hello world",
93 },
94 "01:23:23: hello world": {
95 "date": "",
96 "time": "01:23:23",
97 "file": "",
98 "msg": "hello world",
99 },
100 "2009/01/23 01:23:23.123123: hello world": {
101 "date": "2009/01/23",
102 "time": "01:23:23.123123",
103 "file": "",
104 "msg": "hello world",
105 },
106 "2009/01/23 01:23:23.123123 /a/b/c/d.go:23: hello world": {
107 "date": "2009/01/23",
108 "time": "01:23:23.123123",
109 "file": "/a/b/c/d.go:23",
110 "msg": "hello world",
111 },
112 "01:23:23.123123 /a/b/c/d.go:23: hello world": {
113 "date": "",
114 "time": "01:23:23.123123",
115 "file": "/a/b/c/d.go:23",
116 "msg": "hello world",
117 },
118 "2009/01/23 01:23:23 /a/b/c/d.go:23: hello world": {
119 "date": "2009/01/23",
120 "time": "01:23:23",
121 "file": "/a/b/c/d.go:23",
122 "msg": "hello world",
123 },
124 "2009/01/23 /a/b/c/d.go:23: hello world": {
125 "date": "2009/01/23",
126 "time": "",
127 "file": "/a/b/c/d.go:23",
128 "msg": "hello world",
129 },
130 "/a/b/c/d.go:23: hello world": {
131 "date": "",
132 "time": "",
133 "file": "/a/b/c/d.go:23",
134 "msg": "hello world",
135 },
136 "2009/01/23 01:23:23.123123 C:/a/b/c/d.go:23: hello world": {
137 "date": "2009/01/23",
138 "time": "01:23:23.123123",
139 "file": "C:/a/b/c/d.go:23",
140 "msg": "hello world",
141 },
142 "01:23:23.123123 C:/a/b/c/d.go:23: hello world": {
143 "date": "",
144 "time": "01:23:23.123123",
145 "file": "C:/a/b/c/d.go:23",
146 "msg": "hello world",
147 },
148 "2009/01/23 01:23:23 C:/a/b/c/d.go:23: hello world": {
149 "date": "2009/01/23",
150 "time": "01:23:23",
151 "file": "C:/a/b/c/d.go:23",
152 "msg": "hello world",
153 },
154 "2009/01/23 C:/a/b/c/d.go:23: hello world": {
155 "date": "2009/01/23",
156 "time": "",
157 "file": "C:/a/b/c/d.go:23",
158 "msg": "hello world",
159 },
160 "C:/a/b/c/d.go:23: hello world": {
161 "date": "",
162 "time": "",
163 "file": "C:/a/b/c/d.go:23",
164 "msg": "hello world",
165 },
166 "2009/01/23 01:23:23.123123 C:/a/b/c/d.go:23: :.;<>_#{[]}\"\\": {
167 "date": "2009/01/23",
168 "time": "01:23:23.123123",
169 "file": "C:/a/b/c/d.go:23",
170 "msg": ":.;<>_#{[]}\"\\",
171 },
172 "01:23:23.123123 C:/a/b/c/d.go:23: :.;<>_#{[]}\"\\": {
173 "date": "",
174 "time": "01:23:23.123123",
175 "file": "C:/a/b/c/d.go:23",
176 "msg": ":.;<>_#{[]}\"\\",
177 },
178 "2009/01/23 01:23:23 C:/a/b/c/d.go:23: :.;<>_#{[]}\"\\": {
179 "date": "2009/01/23",
180 "time": "01:23:23",
181 "file": "C:/a/b/c/d.go:23",
182 "msg": ":.;<>_#{[]}\"\\",
183 },
184 "2009/01/23 C:/a/b/c/d.go:23: :.;<>_#{[]}\"\\": {
185 "date": "2009/01/23",
186 "time": "",
187 "file": "C:/a/b/c/d.go:23",
188 "msg": ":.;<>_#{[]}\"\\",
189 },
190 "C:/a/b/c/d.go:23: :.;<>_#{[]}\"\\": {
191 "date": "",
192 "time": "",
193 "file": "C:/a/b/c/d.go:23",
194 "msg": ":.;<>_#{[]}\"\\",
195 },
196 } {
197 haveMap := subexps([]byte(input))
198 for key, want := range wantMap {
199 if have := haveMap[key]; want != have {
200 t.Errorf("%q: %q: want %q, have %q", input, key, want, have)
201 }
202 }
203 }
204 }
11
22 import (
33 "io"
4 "sync"
5 "sync/atomic"
4
5 "github.com/go-kit/log"
66 )
77
88 // SwapLogger wraps another logger that may be safely replaced while other
1111 //
1212 // SwapLogger serves well as a package global logger that can be changed by
1313 // importers.
14 type SwapLogger struct {
15 logger atomic.Value
16 }
17
18 type loggerStruct struct {
19 Logger
20 }
21
22 // Log implements the Logger interface by forwarding keyvals to the currently
23 // wrapped logger. It does not log anything if the wrapped logger is nil.
24 func (l *SwapLogger) Log(keyvals ...interface{}) error {
25 s, ok := l.logger.Load().(loggerStruct)
26 if !ok || s.Logger == nil {
27 return nil
28 }
29 return s.Log(keyvals...)
30 }
31
32 // Swap replaces the currently wrapped logger with logger. Swap may be called
33 // concurrently with calls to Log from other goroutines.
34 func (l *SwapLogger) Swap(logger Logger) {
35 l.logger.Store(loggerStruct{logger})
36 }
14 type SwapLogger = log.SwapLogger
3715
3816 // NewSyncWriter returns a new writer that is safe for concurrent use by
3917 // multiple goroutines. Writes to the returned writer are passed on to w. If
4624 // Fd() uintptr
4725 // }
4826 func NewSyncWriter(w io.Writer) io.Writer {
49 switch w := w.(type) {
50 case fdWriter:
51 return &fdSyncWriter{fdWriter: w}
52 default:
53 return &syncWriter{Writer: w}
54 }
55 }
56
57 // syncWriter synchronizes concurrent writes to an io.Writer.
58 type syncWriter struct {
59 sync.Mutex
60 io.Writer
61 }
62
63 // Write writes p to the underlying io.Writer. If another write is already in
64 // progress, the calling goroutine blocks until the syncWriter is available.
65 func (w *syncWriter) Write(p []byte) (n int, err error) {
66 w.Lock()
67 n, err = w.Writer.Write(p)
68 w.Unlock()
69 return n, err
70 }
71
72 // fdWriter is an io.Writer that also has an Fd method. The most common
73 // example of an fdWriter is an *os.File.
74 type fdWriter interface {
75 io.Writer
76 Fd() uintptr
77 }
78
79 // fdSyncWriter synchronizes concurrent writes to an fdWriter.
80 type fdSyncWriter struct {
81 sync.Mutex
82 fdWriter
83 }
84
85 // Write writes p to the underlying io.Writer. If another write is already in
86 // progress, the calling goroutine blocks until the fdSyncWriter is available.
87 func (w *fdSyncWriter) Write(p []byte) (n int, err error) {
88 w.Lock()
89 n, err = w.fdWriter.Write(p)
90 w.Unlock()
91 return n, err
92 }
93
94 // syncLogger provides concurrent safe logging for another Logger.
95 type syncLogger struct {
96 mu sync.Mutex
97 logger Logger
27 return log.NewSyncWriter(w)
9828 }
9929
10030 // NewSyncLogger returns a logger that synchronizes concurrent use of the
10232 // only one goroutine will be allowed to log to the wrapped logger at a time.
10333 // The other goroutines will block until the logger is available.
10434 func NewSyncLogger(logger Logger) Logger {
105 return &syncLogger{logger: logger}
35 return log.NewSyncLogger(logger)
10636 }
107
108 // Log logs keyvals to the underlying Logger. If another log is already in
109 // progress, the calling goroutine blocks until the syncLogger is available.
110 func (l *syncLogger) Log(keyvals ...interface{}) error {
111 l.mu.Lock()
112 err := l.logger.Log(keyvals...)
113 l.mu.Unlock()
114 return err
115 }
+0
-83
log/sync_test.go less more
0 package log_test
1
2 import (
3 "bytes"
4 "io"
5 "os"
6 "testing"
7
8 "github.com/go-kit/kit/log"
9 )
10
11 func TestSwapLogger(t *testing.T) {
12 t.Parallel()
13 var logger log.SwapLogger
14
15 // Zero value does not panic or error.
16 err := logger.Log("k", "v")
17 if got, want := err, error(nil); got != want {
18 t.Errorf("got %v, want %v", got, want)
19 }
20
21 buf := &bytes.Buffer{}
22 json := log.NewJSONLogger(buf)
23 logger.Swap(json)
24
25 if err := logger.Log("k", "v"); err != nil {
26 t.Error(err)
27 }
28 if got, want := buf.String(), `{"k":"v"}`+"\n"; got != want {
29 t.Errorf("got %v, want %v", got, want)
30 }
31
32 buf.Reset()
33 prefix := log.NewLogfmtLogger(buf)
34 logger.Swap(prefix)
35
36 if err := logger.Log("k", "v"); err != nil {
37 t.Error(err)
38 }
39 if got, want := buf.String(), "k=v\n"; got != want {
40 t.Errorf("got %v, want %v", got, want)
41 }
42
43 buf.Reset()
44 logger.Swap(nil)
45
46 if err := logger.Log("k", "v"); err != nil {
47 t.Error(err)
48 }
49 if got, want := buf.String(), ""; got != want {
50 t.Errorf("got %v, want %v", got, want)
51 }
52 }
53
54 func TestSwapLoggerConcurrency(t *testing.T) {
55 t.Parallel()
56 testConcurrency(t, &log.SwapLogger{}, 10000)
57 }
58
59 func TestSyncLoggerConcurrency(t *testing.T) {
60 var w io.Writer
61 w = &bytes.Buffer{}
62 logger := log.NewLogfmtLogger(w)
63 logger = log.NewSyncLogger(logger)
64 testConcurrency(t, logger, 10000)
65 }
66
67 func TestSyncWriterConcurrency(t *testing.T) {
68 var w io.Writer
69 w = &bytes.Buffer{}
70 w = log.NewSyncWriter(w)
71 testConcurrency(t, log.NewLogfmtLogger(w), 10000)
72 }
73
74 func TestSyncWriterFd(t *testing.T) {
75 _, ok := log.NewSyncWriter(os.Stdout).(interface {
76 Fd() uintptr
77 })
78
79 if !ok {
80 t.Error("NewSyncWriter does not pass through Fd method")
81 }
82 }
0 // +build !windows
1 // +build !plan9
2 // +build !nacl
3
4 package syslog_test
5
6 import (
7 "fmt"
8
9 gosyslog "log/syslog"
10
11 "github.com/go-kit/kit/log"
12 "github.com/go-kit/kit/log/level"
13 "github.com/go-kit/kit/log/syslog"
14 )
15
16 func ExampleNewSyslogLogger_defaultPrioritySelector() {
17 // Normal syslog writer
18 w, err := gosyslog.New(gosyslog.LOG_INFO, "experiment")
19 if err != nil {
20 fmt.Println(err)
21 return
22 }
23
24 // syslog logger with logfmt formatting
25 logger := syslog.NewSyslogLogger(w, log.NewLogfmtLogger)
26 logger.Log("msg", "info because of default")
27 logger.Log(level.Key(), level.DebugValue(), "msg", "debug because of explicit level")
28 }
0 //go:build !windows && !plan9 && !nacl
1 // +build !windows,!plan9,!nacl
2
3 // Deprecated: Use github.com/go-kit/log/syslog instead.
4 package syslog
5
6 import (
7 "io"
8
9 "github.com/go-kit/log"
10 "github.com/go-kit/log/syslog"
11 )
12
13 // SyslogWriter is an interface wrapping stdlib syslog Writer.
14 type SyslogWriter = syslog.SyslogWriter
15
16 // NewSyslogLogger returns a new Logger which writes to syslog in syslog format.
17 // The body of the log message is the formatted output from the Logger returned
18 // by newLogger.
19 func NewSyslogLogger(w SyslogWriter, newLogger func(io.Writer) log.Logger, options ...Option) log.Logger {
20 return syslog.NewSyslogLogger(w, newLogger, options...)
21 }
22
23 // Option sets a parameter for syslog loggers.
24 type Option = syslog.Option
25
26 // PrioritySelector inspects the list of keyvals and selects a syslog priority.
27 type PrioritySelector = syslog.PrioritySelector
28
29 // PrioritySelectorOption sets priority selector function to choose syslog
30 // priority.
31 func PrioritySelectorOption(selector PrioritySelector) Option {
32 return syslog.PrioritySelectorOption(selector)
33 }
+0
-21
log/term/LICENSE less more
0 The MIT License (MIT)
1
2 Copyright (c) 2014 Simon Eskildsen
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
00 package term
11
22 import (
3 "bytes"
4 "fmt"
53 "io"
6 "sync"
74
8 "github.com/go-kit/kit/log"
5 "github.com/go-kit/log"
6 "github.com/go-kit/log/term"
97 )
108
119 // Color represents an ANSI color. The zero value is Default.
12 type Color uint8
10 type Color = term.Color
1311
1412 // ANSI colors.
1513 const (
16 Default = Color(iota)
14 Default = term.Default
1715
18 Black
19 DarkRed
20 DarkGreen
21 Brown
22 DarkBlue
23 DarkMagenta
24 DarkCyan
25 Gray
16 Black = term.Black
17 DarkRed = term.DarkRed
18 DarkGreen = term.DarkGreen
19 Brown = term.Brown
20 DarkBlue = term.DarkBlue
21 DarkMagenta = term.DarkMagenta
22 DarkCyan = term.DarkCyan
23 Gray = term.Gray
2624
27 DarkGray
28 Red
29 Green
30 Yellow
31 Blue
32 Magenta
33 Cyan
34 White
35
36 numColors
25 DarkGray = term.DarkGray
26 Red = term.Red
27 Green = term.Green
28 Yellow = term.Yellow
29 Blue = term.Blue
30 Magenta = term.Magenta
31 Cyan = term.Cyan
32 White = term.White
3733 )
3834
39 // For more on ANSI escape codes see
40 // https://en.wikipedia.org/wiki/ANSI_escape_code. See in particular
41 // https://en.wikipedia.org/wiki/ANSI_escape_code#Colors.
42
43 var (
44 resetColorBytes = []byte("\x1b[39;49;22m")
45 fgColorBytes [][]byte
46 bgColorBytes [][]byte
47 )
48
49 func init() {
50 // Default
51 fgColorBytes = append(fgColorBytes, []byte("\x1b[39m"))
52 bgColorBytes = append(bgColorBytes, []byte("\x1b[49m"))
53
54 // dark colors
55 for color := Black; color < DarkGray; color++ {
56 fgColorBytes = append(fgColorBytes, []byte(fmt.Sprintf("\x1b[%dm", 30+color-Black)))
57 bgColorBytes = append(bgColorBytes, []byte(fmt.Sprintf("\x1b[%dm", 40+color-Black)))
58 }
59
60 // bright colors
61 for color := DarkGray; color < numColors; color++ {
62 fgColorBytes = append(fgColorBytes, []byte(fmt.Sprintf("\x1b[%d;1m", 30+color-DarkGray)))
63 bgColorBytes = append(bgColorBytes, []byte(fmt.Sprintf("\x1b[%d;1m", 40+color-DarkGray)))
64 }
65 }
66
6735 // FgBgColor represents a foreground and background color.
68 type FgBgColor struct {
69 Fg, Bg Color
70 }
71
72 func (c FgBgColor) isZero() bool {
73 return c.Fg == Default && c.Bg == Default
74 }
36 type FgBgColor = term.FgBgColor
7537
7638 // NewColorLogger returns a Logger which writes colored logs to w. ANSI color
7739 // codes for the colors returned by color are added to the formatted output
7840 // from the Logger returned by newLogger and the combined result written to w.
7941 func NewColorLogger(w io.Writer, newLogger func(io.Writer) log.Logger, color func(keyvals ...interface{}) FgBgColor) log.Logger {
80 if color == nil {
81 panic("color func nil")
82 }
83 return &colorLogger{
84 w: w,
85 newLogger: newLogger,
86 color: color,
87 bufPool: sync.Pool{New: func() interface{} { return &loggerBuf{} }},
88 noColorLogger: newLogger(w),
89 }
42 return term.NewColorLogger(w, newLogger, color)
9043 }
91
92 type colorLogger struct {
93 w io.Writer
94 newLogger func(io.Writer) log.Logger
95 color func(keyvals ...interface{}) FgBgColor
96 bufPool sync.Pool
97 noColorLogger log.Logger
98 }
99
100 func (l *colorLogger) Log(keyvals ...interface{}) error {
101 color := l.color(keyvals...)
102 if color.isZero() {
103 return l.noColorLogger.Log(keyvals...)
104 }
105
106 lb := l.getLoggerBuf()
107 defer l.putLoggerBuf(lb)
108 if color.Fg != Default {
109 lb.buf.Write(fgColorBytes[color.Fg])
110 }
111 if color.Bg != Default {
112 lb.buf.Write(bgColorBytes[color.Bg])
113 }
114 err := lb.logger.Log(keyvals...)
115 if err != nil {
116 return err
117 }
118 if color.Fg != Default || color.Bg != Default {
119 lb.buf.Write(resetColorBytes)
120 }
121 _, err = io.Copy(l.w, lb.buf)
122 return err
123 }
124
125 type loggerBuf struct {
126 buf *bytes.Buffer
127 logger log.Logger
128 }
129
130 func (l *colorLogger) getLoggerBuf() *loggerBuf {
131 lb := l.bufPool.Get().(*loggerBuf)
132 if lb.buf == nil {
133 lb.buf = &bytes.Buffer{}
134 lb.logger = l.newLogger(lb.buf)
135 } else {
136 lb.buf.Reset()
137 }
138 return lb
139 }
140
141 func (l *colorLogger) putLoggerBuf(cb *loggerBuf) {
142 l.bufPool.Put(cb)
143 }
+0
-88
log/term/colorlogger_test.go less more
0 package term_test
1
2 import (
3 "bytes"
4 "io"
5 "io/ioutil"
6 "strconv"
7 "sync"
8 "testing"
9
10 "github.com/go-kit/kit/log"
11 "github.com/go-kit/kit/log/term"
12 )
13
14 func TestColorLogger(t *testing.T) {
15 var buf bytes.Buffer
16 logger := newColorLogger(&buf)
17
18 if err := logger.Log("hello", "world"); err != nil {
19 t.Fatal(err)
20 }
21 if want, have := "hello=world\n", buf.String(); want != have {
22 t.Errorf("\nwant %#v\nhave %#v", want, have)
23 }
24
25 buf.Reset()
26 if err := logger.Log("a", 1); err != nil {
27 t.Fatal(err)
28 }
29 if want, have := "\x1b[32;1m\x1b[47;1ma=1\n\x1b[39;49;22m", buf.String(); want != have {
30 t.Errorf("\nwant %#v\nhave %#v", want, have)
31 }
32 }
33
34 func newColorLogger(w io.Writer) log.Logger {
35 return term.NewColorLogger(w, log.NewLogfmtLogger,
36 func(keyvals ...interface{}) term.FgBgColor {
37 if keyvals[0] == "a" {
38 return term.FgBgColor{Fg: term.Green, Bg: term.White}
39 }
40 return term.FgBgColor{}
41 })
42 }
43
44 func BenchmarkColorLoggerSimple(b *testing.B) {
45 benchmarkRunner(b, newColorLogger(ioutil.Discard), baseMessage)
46 }
47
48 func BenchmarkColorLoggerContextual(b *testing.B) {
49 benchmarkRunner(b, newColorLogger(ioutil.Discard), withMessage)
50 }
51
52 func TestColorLoggerConcurrency(t *testing.T) {
53 testConcurrency(t, newColorLogger(ioutil.Discard))
54 }
55
56 // copied from log/benchmark_test.go
57 func benchmarkRunner(b *testing.B, logger log.Logger, f func(log.Logger)) {
58 lc := log.With(logger, "common_key", "common_value")
59 b.ReportAllocs()
60 b.ResetTimer()
61 for i := 0; i < b.N; i++ {
62 f(lc)
63 }
64 }
65
66 var (
67 baseMessage = func(logger log.Logger) { logger.Log("foo_key", "foo_value") }
68 withMessage = func(logger log.Logger) { log.With(logger, "a", "b").Log("c", "d") }
69 )
70
71 // copied from log/concurrency_test.go
72 func testConcurrency(t *testing.T, logger log.Logger) {
73 for _, n := range []int{10, 100, 500} {
74 wg := sync.WaitGroup{}
75 wg.Add(n)
76 for i := 0; i < n; i++ {
77 go func() { spam(logger); wg.Done() }()
78 }
79 wg.Wait()
80 }
81 }
82
83 func spam(logger log.Logger) {
84 for i := 0; i < 100; i++ {
85 logger.Log("a", strconv.FormatInt(int64(i), 10))
86 }
87 }
0 package term
1
2 import (
3 "io"
4
5 "github.com/go-kit/log/term"
6 )
7
8 // NewColorWriter returns an io.Writer that writes to w and provides cross
9 // platform support for ANSI color codes. If w is not a terminal it is
10 // returned unmodified.
11 func NewColorWriter(w io.Writer) io.Writer {
12 return term.NewColorWriter(w)
13 }
+0
-12
log/term/colorwriter_others.go less more
0 // +build !windows
1
2 package term
3
4 import "io"
5
6 // NewColorWriter returns an io.Writer that writes to w and provides cross
7 // platform support for ANSI color codes. If w is not a terminal it is
8 // returned unmodified.
9 func NewColorWriter(w io.Writer) io.Writer {
10 return w
11 }
+0
-190
log/term/colorwriter_windows.go less more
0 // The code in this file is adapted from github.com/mattn/go-colorable.
1
2 // +build windows
3
4 package term
5
6 import (
7 "bytes"
8 "fmt"
9 "io"
10 "strconv"
11 "strings"
12 "syscall"
13 "unsafe"
14 )
15
16 type colorWriter struct {
17 out io.Writer
18 handle syscall.Handle
19 lastbuf bytes.Buffer
20 oldattr word
21 }
22
23 // NewColorWriter returns an io.Writer that writes to w and provides cross
24 // platform support for ANSI color codes. If w is not a terminal it is
25 // returned unmodified.
26 func NewColorWriter(w io.Writer) io.Writer {
27 if !IsConsole(w) {
28 return w
29 }
30
31 var csbi consoleScreenBufferInfo
32 handle := syscall.Handle(w.(fder).Fd())
33 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
34
35 return &colorWriter{
36 out: w,
37 handle: handle,
38 oldattr: csbi.attributes,
39 }
40 }
41
42 func (w *colorWriter) Write(data []byte) (n int, err error) {
43 var csbi consoleScreenBufferInfo
44 procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
45
46 er := bytes.NewBuffer(data)
47 loop:
48 for {
49 r1, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
50 if r1 == 0 {
51 break loop
52 }
53
54 c1, _, err := er.ReadRune()
55 if err != nil {
56 break loop
57 }
58 if c1 != 0x1b {
59 fmt.Fprint(w.out, string(c1))
60 continue
61 }
62 c2, _, err := er.ReadRune()
63 if err != nil {
64 w.lastbuf.WriteRune(c1)
65 break loop
66 }
67 if c2 != 0x5b {
68 w.lastbuf.WriteRune(c1)
69 w.lastbuf.WriteRune(c2)
70 continue
71 }
72
73 var buf bytes.Buffer
74 var m rune
75 for {
76 c, _, err := er.ReadRune()
77 if err != nil {
78 w.lastbuf.WriteRune(c1)
79 w.lastbuf.WriteRune(c2)
80 w.lastbuf.Write(buf.Bytes())
81 break loop
82 }
83 if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
84 m = c
85 break
86 }
87 buf.Write([]byte(string(c)))
88 }
89
90 switch m {
91 case 'm':
92 attr := csbi.attributes
93 cs := buf.String()
94 if cs == "" {
95 procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr))
96 continue
97 }
98 token := strings.Split(cs, ";")
99 intensityMode := word(0)
100 for _, ns := range token {
101 if n, err = strconv.Atoi(ns); err == nil {
102 switch {
103 case n == 0:
104 attr = w.oldattr
105 case n == 1:
106 attr |= intensityMode
107 case 30 <= n && n <= 37:
108 attr = (attr & backgroundMask)
109 if (n-30)&1 != 0 {
110 attr |= foregroundRed
111 }
112 if (n-30)&2 != 0 {
113 attr |= foregroundGreen
114 }
115 if (n-30)&4 != 0 {
116 attr |= foregroundBlue
117 }
118 intensityMode = foregroundIntensity
119 case n == 39: // reset foreground color
120 attr &= backgroundMask
121 attr |= w.oldattr & foregroundMask
122 case 40 <= n && n <= 47:
123 attr = (attr & foregroundMask)
124 if (n-40)&1 != 0 {
125 attr |= backgroundRed
126 }
127 if (n-40)&2 != 0 {
128 attr |= backgroundGreen
129 }
130 if (n-40)&4 != 0 {
131 attr |= backgroundBlue
132 }
133 intensityMode = backgroundIntensity
134 case n == 49: // reset background color
135 attr &= foregroundMask
136 attr |= w.oldattr & backgroundMask
137 }
138 procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr))
139 }
140 }
141 }
142 }
143 return len(data) - w.lastbuf.Len(), nil
144 }
145
146 var (
147 procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
148 procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
149 )
150
151 const (
152 foregroundBlue = 0x1
153 foregroundGreen = 0x2
154 foregroundRed = 0x4
155 foregroundIntensity = 0x8
156 foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity)
157 backgroundBlue = 0x10
158 backgroundGreen = 0x20
159 backgroundRed = 0x40
160 backgroundIntensity = 0x80
161 backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
162 )
163
164 type (
165 wchar uint16
166 short int16
167 dword uint32
168 word uint16
169 )
170
171 type coord struct {
172 x short
173 y short
174 }
175
176 type smallRect struct {
177 left short
178 top short
179 right short
180 bottom short
181 }
182
183 type consoleScreenBufferInfo struct {
184 size coord
185 cursorPosition coord
186 attributes word
187 window smallRect
188 maximumWindowSize coord
189 }
00 // Package term provides tools for logging to a terminal.
1 //
2 // Deprecated: Use github.com/go-kit/log/term instead.
13 package term
24
35 import (
46 "io"
57
6 "github.com/go-kit/kit/log"
8 "github.com/go-kit/log"
9 "github.com/go-kit/log/term"
710 )
811
912 // NewLogger returns a Logger that takes advantage of terminal features if
1013 // possible. Log events are formatted by the Logger returned by newLogger. If
1114 // w is a terminal each log event is colored according to the color function.
1215 func NewLogger(w io.Writer, newLogger func(io.Writer) log.Logger, color func(keyvals ...interface{}) FgBgColor) log.Logger {
13 if !IsTerminal(w) {
14 return newLogger(w)
15 }
16 return NewColorLogger(NewColorWriter(w), newLogger, color)
16 return term.NewLogger(w, newLogger, color)
1717 }
1818
19 type fder interface {
20 Fd() uintptr
19 // IsTerminal returns true if w writes to a terminal.
20 func IsTerminal(w io.Writer) bool {
21 return term.IsTerminal(w)
2122 }
+0
-15
log/term/terminal_appengine.go less more
0 // Based on ssh/terminal:
1 // Copyright 2013 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // +build appengine
6
7 package term
8
9 import "io"
10
11 // IsTerminal always returns false on AppEngine.
12 func IsTerminal(w io.Writer) bool {
13 return false
14 }
+0
-10
log/term/terminal_darwin.go less more
0 // Based on ssh/terminal:
1 // Copyright 2013 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package term
6
7 import "syscall"
8
9 const ioctlReadTermios = syscall.TIOCGETA
+0
-7
log/term/terminal_freebsd.go less more
0 package term
1
2 import (
3 "syscall"
4 )
5
6 const ioctlReadTermios = syscall.TIOCGETA
+0
-12
log/term/terminal_linux.go less more
0 // Based on ssh/terminal:
1 // Copyright 2013 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // +build !appengine
6
7 package term
8
9 import "syscall"
10
11 const ioctlReadTermios = syscall.TCGETS
+0
-25
log/term/terminal_notwindows.go less more
0 // Based on ssh/terminal:
1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // +build linux,!appengine darwin freebsd openbsd
6
7 package term
8
9 import (
10 "io"
11 "syscall"
12 "unsafe"
13 )
14
15 // IsTerminal returns true if w writes to a terminal.
16 func IsTerminal(w io.Writer) bool {
17 fw, ok := w.(fder)
18 if !ok {
19 return false
20 }
21 var termios syscall.Termios
22 _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fw.Fd(), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
23 return err == 0
24 }
+0
-5
log/term/terminal_openbsd.go less more
0 package term
1
2 import "syscall"
3
4 const ioctlReadTermios = syscall.TIOCGETA
+0
-102
log/term/terminal_windows.go less more
0 // Based on ssh/terminal:
1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // +build windows
6
7 package term
8
9 import (
10 "encoding/binary"
11 "io"
12 "regexp"
13 "syscall"
14 "unsafe"
15 )
16
17 var kernel32 = syscall.NewLazyDLL("kernel32.dll")
18
19 var (
20 procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx")
21 msysPipeNameRegex = regexp.MustCompile(`\\(cygwin|msys)-\w+-pty\d?-(to|from)-master`)
22 )
23
24 const (
25 fileNameInfo = 0x02
26 )
27
28 // IsTerminal returns true if w writes to a terminal.
29 func IsTerminal(w io.Writer) bool {
30 return IsConsole(w) || IsMSYSTerminal(w)
31 }
32
33 // IsConsole returns true if w writes to a Windows console.
34 func IsConsole(w io.Writer) bool {
35 var handle syscall.Handle
36
37 if fw, ok := w.(fder); ok {
38 handle = syscall.Handle(fw.Fd())
39 } else {
40 // The writer has no file-descriptor and so can't be a terminal.
41 return false
42 }
43
44 var st uint32
45 err := syscall.GetConsoleMode(handle, &st)
46
47 // If the handle is attached to a terminal, GetConsoleMode returns a
48 // non-zero value containing the console mode flags. We don't care about
49 // the specifics of flags, just that it is not zero.
50 return (err == nil && st != 0)
51 }
52
53 // IsMSYSTerminal returns true if w writes to a MSYS/MSYS2 terminal.
54 func IsMSYSTerminal(w io.Writer) bool {
55 var handle syscall.Handle
56
57 if fw, ok := w.(fder); ok {
58 handle = syscall.Handle(fw.Fd())
59 } else {
60 // The writer has no file-descriptor and so can't be a terminal.
61 return false
62 }
63
64 // MSYS(2) terminal reports as a pipe for STDIN/STDOUT/STDERR. If it isn't
65 // a pipe, it can't be a MSYS(2) terminal.
66 filetype, err := syscall.GetFileType(handle)
67
68 if filetype != syscall.FILE_TYPE_PIPE || err != nil {
69 return false
70 }
71
72 // MSYS2/Cygwin terminal's name looks like: \msys-dd50a72ab4668b33-pty2-to-master
73 data := make([]byte, 256, 256)
74
75 r, _, e := syscall.Syscall6(
76 procGetFileInformationByHandleEx.Addr(),
77 4,
78 uintptr(handle),
79 uintptr(fileNameInfo),
80 uintptr(unsafe.Pointer(&data[0])),
81 uintptr(len(data)),
82 0,
83 0,
84 )
85
86 if r != 0 && e == 0 {
87 // The first 4 bytes of the buffer are the size of the UTF16 name, in bytes.
88 unameLen := binary.LittleEndian.Uint32(data[:4]) / 2
89 uname := make([]uint16, unameLen, unameLen)
90
91 for i := uint32(0); i < unameLen; i++ {
92 uname[i] = binary.LittleEndian.Uint16(data[i*2+4 : i*2+2+4])
93 }
94
95 name := syscall.UTF16ToString(uname)
96
97 return msysPipeNameRegex.MatchString(name)
98 }
99
100 return false
101 }
+0
-71
log/term/terminal_windows_test.go less more
0 package term
1
2 import (
3 "fmt"
4 "syscall"
5 "testing"
6 )
7
8 // +build windows
9
10 type myWriter struct {
11 fd uintptr
12 }
13
14 func (w *myWriter) Write(p []byte) (int, error) {
15 return 0, fmt.Errorf("not implemented")
16 }
17
18 func (w *myWriter) Fd() uintptr {
19 return w.fd
20 }
21
22 var procGetStdHandle = kernel32.NewProc("GetStdHandle")
23
24 const stdOutputHandle = ^uintptr(0) - 11 + 1
25
26 func getConsoleHandle() syscall.Handle {
27 ptr, err := syscall.UTF16PtrFromString("CONOUT$")
28
29 if err != nil {
30 panic(err)
31 }
32
33 handle, err := syscall.CreateFile(ptr, syscall.GENERIC_READ|syscall.GENERIC_WRITE, syscall.FILE_SHARE_READ, nil, syscall.OPEN_EXISTING, 0, 0)
34
35 if err != nil {
36 panic(err)
37 }
38
39 return handle
40 }
41
42 func TestIsTerminal(t *testing.T) {
43 // This is necessary because depending on whether `go test` is called with
44 // the `-v` option, stdout will or will not be bound, changing the behavior
45 // of the test. So we refer to it directly to avoid flakyness.
46 handle := getConsoleHandle()
47
48 writer := &myWriter{
49 fd: uintptr(handle),
50 }
51
52 if !IsTerminal(writer) {
53 t.Errorf("output is supposed to be a terminal")
54 }
55 }
56
57 func TestIsConsole(t *testing.T) {
58 // This is necessary because depending on whether `go test` is called with
59 // the `-v` option, stdout will or will not be bound, changing the behavior
60 // of the test. So we refer to it directly to avoid flakyness.
61 handle := getConsoleHandle()
62
63 writer := &myWriter{
64 fd: uintptr(handle),
65 }
66
67 if !IsConsole(writer) {
68 t.Errorf("output is supposed to be a console")
69 }
70 }
22 import (
33 "time"
44
5 "github.com/go-stack/stack"
5 "github.com/go-kit/log"
66 )
77
8 // A Valuer generates a log value. When passed to With or WithPrefix in a
9 // value element (odd indexes), it represents a dynamic value which is re-
10 // evaluated with each log event.
11 type Valuer func() interface{}
12
13 // bindValues replaces all value elements (odd indexes) containing a Valuer
14 // with their generated value.
15 func bindValues(keyvals []interface{}) {
16 for i := 1; i < len(keyvals); i += 2 {
17 if v, ok := keyvals[i].(Valuer); ok {
18 keyvals[i] = v()
19 }
20 }
21 }
22
23 // containsValuer returns true if any of the value elements (odd indexes)
24 // contain a Valuer.
25 func containsValuer(keyvals []interface{}) bool {
26 for i := 1; i < len(keyvals); i += 2 {
27 if _, ok := keyvals[i].(Valuer); ok {
28 return true
29 }
30 }
31 return false
32 }
8 // A Valuer generates a log value. When passed to With, WithPrefix, or
9 // WithSuffix in a value element (odd indexes), it represents a dynamic
10 // value which is re-evaluated with each log event.
11 type Valuer = log.Valuer
3312
3413 // Timestamp returns a timestamp Valuer. It invokes the t function to get the
3514 // time; unless you are doing something tricky, pass time.Now.
3716 // Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which
3817 // are TimestampFormats that use the RFC3339Nano format.
3918 func Timestamp(t func() time.Time) Valuer {
40 return func() interface{} { return t() }
19 return log.Timestamp(t)
4120 }
4221
4322 // TimestampFormat returns a timestamp Valuer with a custom time format. It
4827 // Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which
4928 // are TimestampFormats that use the RFC3339Nano format.
5029 func TimestampFormat(t func() time.Time, layout string) Valuer {
51 return func() interface{} {
52 return timeFormat{
53 time: t(),
54 layout: layout,
55 }
56 }
57 }
58
59 // A timeFormat represents an instant in time and a layout used when
60 // marshaling to a text format.
61 type timeFormat struct {
62 time time.Time
63 layout string
64 }
65
66 func (tf timeFormat) String() string {
67 return tf.time.Format(tf.layout)
68 }
69
70 // MarshalText implements encoding.TextMarshaller.
71 func (tf timeFormat) MarshalText() (text []byte, err error) {
72 // The following code adapted from the standard library time.Time.Format
73 // method. Using the same undocumented magic constant to extend the size
74 // of the buffer as seen there.
75 b := make([]byte, 0, len(tf.layout)+10)
76 b = tf.time.AppendFormat(b, tf.layout)
77 return b, nil
30 return log.TimestampFormat(t, layout)
7831 }
7932
8033 // Caller returns a Valuer that returns a file and line from a specified depth
8134 // in the callstack. Users will probably want to use DefaultCaller.
8235 func Caller(depth int) Valuer {
83 return func() interface{} { return stack.Caller(depth) }
36 return log.Caller(depth)
8437 }
8538
8639 var (
8740 // DefaultTimestamp is a Valuer that returns the current wallclock time,
8841 // respecting time zones, when bound.
89 DefaultTimestamp = TimestampFormat(time.Now, time.RFC3339Nano)
42 DefaultTimestamp = log.DefaultTimestamp
9043
9144 // DefaultTimestampUTC is a Valuer that returns the current time in UTC
9245 // when bound.
93 DefaultTimestampUTC = TimestampFormat(
94 func() time.Time { return time.Now().UTC() },
95 time.RFC3339Nano,
96 )
46 DefaultTimestampUTC = log.DefaultTimestampUTC
9747
9848 // DefaultCaller is a Valuer that returns the file and line where the Log
9949 // method was invoked. It can only be used with log.With.
100 DefaultCaller = Caller(3)
50 DefaultCaller = log.DefaultCaller
10151 )
+0
-150
log/value_test.go less more
0 package log_test
1
2 import (
3 "encoding"
4 "fmt"
5 "reflect"
6 "testing"
7 "time"
8
9 "github.com/go-kit/kit/log"
10 )
11
12 func TestValueBinding(t *testing.T) {
13 t.Parallel()
14 var output []interface{}
15
16 logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error {
17 output = keyvals
18 return nil
19 }))
20
21 start := time.Date(2015, time.April, 25, 0, 0, 0, 0, time.UTC)
22 now := start
23 mocktime := func() time.Time {
24 now = now.Add(time.Second)
25 return now
26 }
27
28 lc := log.With(logger, "ts", log.Timestamp(mocktime), "caller", log.DefaultCaller)
29
30 lc.Log("foo", "bar")
31 timestamp, ok := output[1].(time.Time)
32 if !ok {
33 t.Fatalf("want time.Time, have %T", output[1])
34 }
35 if want, have := start.Add(time.Second), timestamp; want != have {
36 t.Errorf("output[1]: want %v, have %v", want, have)
37 }
38 if want, have := "value_test.go:31", fmt.Sprint(output[3]); want != have {
39 t.Errorf("output[3]: want %s, have %s", want, have)
40 }
41
42 // A second attempt to confirm the bindings are truly dynamic.
43 lc.Log("foo", "bar")
44 timestamp, ok = output[1].(time.Time)
45 if !ok {
46 t.Fatalf("want time.Time, have %T", output[1])
47 }
48 if want, have := start.Add(2*time.Second), timestamp; want != have {
49 t.Errorf("output[1]: want %v, have %v", want, have)
50 }
51 if want, have := "value_test.go:44", fmt.Sprint(output[3]); want != have {
52 t.Errorf("output[3]: want %s, have %s", want, have)
53 }
54 }
55
56 func TestValueBinding_loggingZeroKeyvals(t *testing.T) {
57 t.Parallel()
58 var output []interface{}
59
60 logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error {
61 output = keyvals
62 return nil
63 }))
64
65 start := time.Date(2015, time.April, 25, 0, 0, 0, 0, time.UTC)
66 now := start
67 mocktime := func() time.Time {
68 now = now.Add(time.Second)
69 return now
70 }
71
72 logger = log.With(logger, "ts", log.Timestamp(mocktime))
73
74 logger.Log()
75 timestamp, ok := output[1].(time.Time)
76 if !ok {
77 t.Fatalf("want time.Time, have %T", output[1])
78 }
79 if want, have := start.Add(time.Second), timestamp; want != have {
80 t.Errorf("output[1]: want %v, have %v", want, have)
81 }
82
83 // A second attempt to confirm the bindings are truly dynamic.
84 logger.Log()
85 timestamp, ok = output[1].(time.Time)
86 if !ok {
87 t.Fatalf("want time.Time, have %T", output[1])
88 }
89 if want, have := start.Add(2*time.Second), timestamp; want != have {
90 t.Errorf("output[1]: want %v, have %v", want, have)
91 }
92 }
93
94 func TestTimestampFormat(t *testing.T) {
95 t.Parallel()
96
97 start := time.Date(2015, time.April, 25, 0, 0, 0, 0, time.UTC)
98 now := start
99 mocktime := func() time.Time {
100 now = now.Add(time.Second)
101 return now
102 }
103
104 tv := log.TimestampFormat(mocktime, time.RFC822)
105
106 if want, have := now.Add(time.Second).Format(time.RFC822), fmt.Sprint(tv()); want != have {
107 t.Errorf("wrong time format: want %v, have %v", want, have)
108 }
109
110 if want, have := now.Add(2*time.Second).Format(time.RFC822), fmt.Sprint(tv()); want != have {
111 t.Errorf("wrong time format: want %v, have %v", want, have)
112 }
113
114 mustMarshal := func(v interface{}) []byte {
115 b, err := v.(encoding.TextMarshaler).MarshalText()
116 if err != nil {
117 t.Fatal("error marshaling text:", err)
118 }
119 return b
120 }
121
122 if want, have := now.Add(3*time.Second).AppendFormat(nil, time.RFC822), mustMarshal(tv()); !reflect.DeepEqual(want, have) {
123 t.Errorf("wrong time format: want %s, have %s", want, have)
124 }
125
126 if want, have := now.Add(4*time.Second).AppendFormat(nil, time.RFC822), mustMarshal(tv()); !reflect.DeepEqual(want, have) {
127 t.Errorf("wrong time format: want %s, have %s", want, have)
128 }
129 }
130
131 func BenchmarkValueBindingTimestamp(b *testing.B) {
132 logger := log.NewNopLogger()
133 lc := log.With(logger, "ts", log.DefaultTimestamp)
134 b.ReportAllocs()
135 b.ResetTimer()
136 for i := 0; i < b.N; i++ {
137 lc.Log("k", "v")
138 }
139 }
140
141 func BenchmarkValueBindingCaller(b *testing.B) {
142 logger := log.NewNopLogger()
143 lc := log.With(logger, "caller", log.DefaultCaller)
144 b.ReportAllocs()
145 b.ResetTimer()
146 for i := 0; i < b.N; i++ {
147 lc.Log("k", "v")
148 }
149 }
0 package zap
1
2 import (
3 "github.com/go-kit/log"
4 "go.uber.org/zap"
5 "go.uber.org/zap/zapcore"
6 )
7
8 type zapSugarLogger func(msg string, keysAndValues ...interface{})
9
10 func (l zapSugarLogger) Log(kv ...interface{}) error {
11 l("", kv...)
12 return nil
13 }
14
15 // NewZapSugarLogger returns a Go kit log.Logger that sends
16 // log events to a zap.Logger.
17 func NewZapSugarLogger(logger *zap.Logger, level zapcore.Level) log.Logger {
18 sugarLogger := logger.WithOptions(zap.AddCallerSkip(2)).Sugar()
19 var sugar zapSugarLogger
20 switch level {
21 case zapcore.DebugLevel:
22 sugar = sugarLogger.Debugw
23 case zapcore.InfoLevel:
24 sugar = sugarLogger.Infow
25 case zapcore.WarnLevel:
26 sugar = sugarLogger.Warnw
27 case zapcore.ErrorLevel:
28 sugar = sugarLogger.Errorw
29 case zapcore.DPanicLevel:
30 sugar = sugarLogger.DPanicw
31 case zapcore.PanicLevel:
32 sugar = sugarLogger.Panicw
33 case zapcore.FatalLevel:
34 sugar = sugarLogger.Fatalw
35 default:
36 sugar = sugarLogger.Infow
37 }
38 return sugar
39 }
0 package zap_test
1
2 import (
3 "encoding/json"
4 kitzap "github.com/go-kit/kit/log/zap"
5 "go.uber.org/zap"
6 "go.uber.org/zap/zapcore"
7 "strings"
8 "testing"
9 )
10
11 func TestZapSugarLogger(t *testing.T) {
12 // logger config
13 encoderConfig := zap.NewDevelopmentEncoderConfig()
14 encoder := zapcore.NewJSONEncoder(encoderConfig)
15 levelKey := encoderConfig.LevelKey
16 // basic test cases
17 type testCase struct {
18 level zapcore.Level
19 kvs []interface{}
20 want map[string]string
21 }
22 testCases := []testCase{
23 {level: zapcore.DebugLevel, kvs: []interface{}{"key1", "value1"},
24 want: map[string]string{levelKey: "DEBUG", "key1": "value1"}},
25
26 {level: zapcore.InfoLevel, kvs: []interface{}{"key2", "value2"},
27 want: map[string]string{levelKey: "INFO", "key2": "value2"}},
28
29 {level: zapcore.WarnLevel, kvs: []interface{}{"key3", "value3"},
30 want: map[string]string{levelKey: "WARN", "key3": "value3"}},
31
32 {level: zapcore.ErrorLevel, kvs: []interface{}{"key4", "value4"},
33 want: map[string]string{levelKey: "ERROR", "key4": "value4"}},
34
35 {level: zapcore.DPanicLevel, kvs: []interface{}{"key5", "value5"},
36 want: map[string]string{levelKey: "DPANIC", "key5": "value5"}},
37
38 {level: zapcore.PanicLevel, kvs: []interface{}{"key6", "value6"},
39 want: map[string]string{levelKey: "PANIC", "key6": "value6"}},
40 }
41 // test
42 for _, testCase := range testCases {
43 t.Run(testCase.level.String(), func(t *testing.T) {
44 // make logger
45 writer := &tbWriter{tb: t}
46 logger := zap.New(
47 zapcore.NewCore(encoder, zapcore.AddSync(writer), zap.DebugLevel),
48 zap.Development())
49 // check panic
50 shouldPanic := testCase.level >= zapcore.DPanicLevel
51 kitLogger := kitzap.NewZapSugarLogger(logger, testCase.level)
52 defer func() {
53 isPanic := recover() != nil
54 if shouldPanic != isPanic {
55 t.Errorf("test level %v should panic(%v), but %v", testCase.level, shouldPanic, isPanic)
56 }
57 // check log kvs
58 logMap := make(map[string]string)
59 err := json.Unmarshal([]byte(writer.sb.String()), &logMap)
60 if err != nil {
61 t.Errorf("unmarshal error: %v", err)
62 } else {
63 for k, v := range testCase.want {
64 vv, ok := logMap[k]
65 if !ok || v != vv {
66 t.Error("error log")
67 }
68 }
69 }
70 }()
71 kitLogger.Log(testCase.kvs...)
72 })
73 }
74 }
75
76 type tbWriter struct {
77 tb testing.TB
78 sb strings.Builder
79 }
80
81 func (w *tbWriter) Write(b []byte) (n int, err error) {
82 w.tb.Logf(string(b))
83 w.sb.Write(b)
84 return len(b), nil
85 }
6767
6868 ```go
6969 import (
70 "context"
7071 "net"
7172 "os"
7273 "runtime"
8081 statsd := statsd.New("foo_svc.", log.NewNopLogger())
8182 report := time.NewTicker(5 * time.Second)
8283 defer report.Stop()
83 go statsd.SendLoop(report.C, "tcp", "statsd.internal:8125")
84 go statsd.SendLoop(context.Background(), report.C, "tcp", "statsd.internal:8125")
8485 goroutines := statsd.NewGauge("goroutine_count")
8586 go exportGoroutines(goroutines)
8687 // ...
00 package cloudwatch
11
22 import (
3 "context"
34 "fmt"
45 "os"
6 "strconv"
57 "sync"
68 "time"
79
911 "github.com/aws/aws-sdk-go/service/cloudwatch"
1012 "github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface"
1113
12 "github.com/go-kit/kit/log"
1314 "github.com/go-kit/kit/metrics"
1415 "github.com/go-kit/kit/metrics/generic"
1516 "github.com/go-kit/kit/metrics/internal/lv"
16 "strconv"
17 "github.com/go-kit/log"
1718 )
1819
1920 const (
2021 maxConcurrentRequests = 20
22 maxValuesInABatch = 150
2123 )
22
23 type Percentiles []struct {
24 s string
25 f float64
26 }
2724
2825 // CloudWatch receives metrics observations and forwards them to CloudWatch.
2926 // Create a CloudWatch object, use it to create metrics, and pass those metrics as
4340 numConcurrentRequests int
4441 }
4542
46 type option func(*CloudWatch)
47
48 func (s *CloudWatch) apply(opt option) {
49 if opt != nil {
50 opt(s)
51 }
52 }
53
54 func WithLogger(logger log.Logger) option {
43 // Option is a function adapter to change config of the CloudWatch struct
44 type Option func(*CloudWatch)
45
46 // WithLogger sets the Logger that will receive error messages generated
47 // during the WriteLoop. By default, fmt logger is used.
48 func WithLogger(logger log.Logger) Option {
5549 return func(c *CloudWatch) {
5650 c.logger = logger
5751 }
6155 // existing/default values.
6256 // Reason is that Cloudwatch makes you pay per metric, so you can save half the money
6357 // by only using 2 metrics instead of the default 4.
64 func WithPercentiles(percentiles ...float64) option {
58 func WithPercentiles(percentiles ...float64) Option {
6559 return func(c *CloudWatch) {
6660 c.percentiles = make([]float64, 0, len(percentiles))
6761 for _, p := range percentiles {
7367 }
7468 }
7569
76 func WithConcurrentRequests(n int) option {
70 // WithConcurrentRequests sets the upper limit on how many
71 // cloudwatch.PutMetricDataRequest may be under way at any
72 // given time. If n is greater than 20, 20 is used. By default,
73 // the max is set at 10 concurrent requests.
74 func WithConcurrentRequests(n int) Option {
7775 return func(c *CloudWatch) {
7876 if n > maxConcurrentRequests {
7977 n = maxConcurrentRequests
8684 // Namespace is applied to all created metrics and maps to the CloudWatch namespace.
8785 // Callers must ensure that regular calls to Send are performed, either
8886 // manually or with one of the helper methods.
89 func New(namespace string, svc cloudwatchiface.CloudWatchAPI, options ...option) *CloudWatch {
87 func New(namespace string, svc cloudwatchiface.CloudWatchAPI, options ...Option) *CloudWatch {
9088 cw := &CloudWatch{
9189 sem: nil, // set below
9290 namespace: namespace,
9997 percentiles: []float64{0.50, 0.90, 0.95, 0.99},
10098 }
10199
102 for _, optFunc := range options {
103 optFunc(cw)
100 for _, opt := range options {
101 opt(cw)
104102 }
105103
106104 cw.sem = make(chan struct{}, cw.numConcurrentRequests)
135133 }
136134
137135 // WriteLoop is a helper method that invokes Send every time the passed
138 // channel fires. This method blocks until the channel is closed, so clients
136 // channel fires. This method blocks until ctx is canceled, so clients
139137 // probably want to run it in its own goroutine. For typical usage, create a
140138 // time.Ticker and pass its C channel to this method.
141 func (cw *CloudWatch) WriteLoop(c <-chan time.Time) {
142 for range c {
143 if err := cw.Send(); err != nil {
144 cw.logger.Log("during", "Send", "err", err)
139 func (cw *CloudWatch) WriteLoop(ctx context.Context, c <-chan time.Time) {
140 for {
141 select {
142 case <-c:
143 if err := cw.Send(); err != nil {
144 cw.logger.Log("during", "Send", "err", err)
145 }
146 case <-ctx.Done():
147 return
145148 }
146149 }
147150 }
167170 })
168171
169172 cw.gauges.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool {
170 value := last(values)
171 datums = append(datums, &cloudwatch.MetricDatum{
173 if len(values) == 0 {
174 return true
175 }
176
177 datum := &cloudwatch.MetricDatum{
172178 MetricName: aws.String(name),
173179 Dimensions: makeDimensions(lvs...),
174 Value: aws.Float64(value),
175180 Timestamp: aws.Time(now),
176 })
181 }
182
183 // CloudWatch Put Metrics API (https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html)
184 // expects batch of unique values including the array of corresponding counts
185 valuesCounter := make(map[float64]int)
186 for _, v := range values {
187 valuesCounter[v]++
188 }
189
190 for value, count := range valuesCounter {
191 if len(datum.Values) == maxValuesInABatch {
192 break
193 }
194 datum.Values = append(datum.Values, aws.Float64(value))
195 datum.Counts = append(datum.Counts, aws.Float64(float64(count)))
196 }
197
198 datums = append(datums, datum)
177199 return true
178200 })
179201
228250 }
229251 var firstErr error
230252 for i := 0; i < cap(errors); i++ {
231 if err := <-errors; err != nil && firstErr != nil {
253 if err := <-errors; err != nil && firstErr == nil {
232254 firstErr = err
233255 }
234256 }
242264 v += f
243265 }
244266 return v
245 }
246
247 func last(a []float64) float64 {
248 return a[len(a)-1]
249267 }
250268
251269 func min(a, b int) int {
99 "github.com/aws/aws-sdk-go/service/cloudwatch"
1010 "github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface"
1111
12 "github.com/go-kit/kit/log"
1312 "github.com/go-kit/kit/metrics"
1413 "github.com/go-kit/kit/metrics/teststat"
14 "github.com/go-kit/log"
1515 )
16
17 const metricNameToGenerateError = "metric_name_used_to_throw_an_error"
18
19 var errTest = errors.New("test error")
1620
1721 type mockCloudWatch struct {
1822 cloudwatchiface.CloudWatchAPI
1923 mtx sync.RWMutex
20 valuesReceived map[string]float64
24 valuesReceived map[string][]float64
2125 dimensionsReceived map[string][]*cloudwatch.Dimension
2226 }
2327
2428 func newMockCloudWatch() *mockCloudWatch {
2529 return &mockCloudWatch{
26 valuesReceived: map[string]float64{},
30 valuesReceived: map[string][]float64{},
2731 dimensionsReceived: map[string][]*cloudwatch.Dimension{},
2832 }
2933 }
3236 mcw.mtx.Lock()
3337 defer mcw.mtx.Unlock()
3438 for _, datum := range input.MetricData {
35 mcw.valuesReceived[*datum.MetricName] = *datum.Value
39 if *datum.MetricName == metricNameToGenerateError {
40 return nil, errTest
41 }
42
43 if len(datum.Values) > 0 {
44 for _, v := range datum.Values {
45 mcw.valuesReceived[*datum.MetricName] = append(mcw.valuesReceived[*datum.MetricName], *v)
46 }
47 } else {
48 mcw.valuesReceived[*datum.MetricName] = append(mcw.valuesReceived[*datum.MetricName], *datum.Value)
49 }
3650 mcw.dimensionsReceived[*datum.MetricName] = datum.Dimensions
3751 }
3852 return nil, nil
6276 }
6377 }
6478 }
65 return fmt.Errorf("Could not find dimension with name %s and value %s", name, value)
79 return fmt.Errorf("could not find dimension with name %s and value %s", name, value)
6680 }
6781
6882 return nil
7589 cw := New(namespace, svc, WithLogger(log.NewNopLogger()))
7690 counter := cw.NewCounter(name).With(label, value)
7791 valuef := func() float64 {
78 err := cw.Send()
79 if err != nil {
80 t.Fatal(err)
81 }
82 svc.mtx.RLock()
83 defer svc.mtx.RUnlock()
84 return svc.valuesReceived[name]
92 if err := cw.Send(); err != nil {
93 t.Fatal(err)
94 }
95 svc.mtx.RLock()
96 defer svc.mtx.RUnlock()
97 value := svc.valuesReceived[name][len(svc.valuesReceived[name])-1]
98 delete(svc.valuesReceived, name)
99
100 return value
85101 }
86102 if err := teststat.TestCounter(counter, valuef); err != nil {
87103 t.Fatal(err)
116132 wants = append(wants, teststat.FillCounter(counters[name]))
117133 }
118134
119 err := cw.Send()
120 if err != nil {
135 if err := cw.Send(); err != nil {
121136 t.Fatal(err)
122137 }
123138
124139 for i, name := range names {
125 if svc.valuesReceived[name] != wants[i] {
140 if l := len(svc.valuesReceived[name]); l == 0 && wants[i] == 0 {
141 continue
142 } else if l != 1 {
143 t.Fatalf("one value expected, got %d", l)
144 }
145
146 if svc.valuesReceived[name][0] != wants[i] {
126147 t.Fatalf("want %f, have %f", wants[i], svc.valuesReceived[name])
127148 }
128149 if err := svc.testDimensions(name, labels[i], values[i]); err != nil {
137158 svc := newMockCloudWatch()
138159 cw := New(namespace, svc, WithLogger(log.NewNopLogger()))
139160 gauge := cw.NewGauge(name).With(label, value)
140 valuef := func() float64 {
141 err := cw.Send()
142 if err != nil {
143 t.Fatal(err)
144 }
145 svc.mtx.RLock()
146 defer svc.mtx.RUnlock()
147 return svc.valuesReceived[name]
148 }
161 valuef := func() []float64 {
162 if err := cw.Send(); err != nil {
163 t.Fatal(err)
164 }
165 svc.mtx.RLock()
166 defer svc.mtx.RUnlock()
167 res := svc.valuesReceived[name]
168 delete(svc.valuesReceived, name)
169 return res
170 }
171
149172 if err := teststat.TestGauge(gauge, valuef); err != nil {
150173 t.Fatal(err)
151174 }
169192 if err != nil {
170193 t.Fatal(err)
171194 }
172 svc.mtx.RLock()
173 defer svc.mtx.RUnlock()
174 p50 = svc.valuesReceived[n50]
175 p90 = svc.valuesReceived[n90]
176 p95 = svc.valuesReceived[n95]
177 p99 = svc.valuesReceived[n99]
195
196 svc.mtx.RLock()
197 defer svc.mtx.RUnlock()
198 if len(svc.valuesReceived[n50]) > 0 {
199 p50 = svc.valuesReceived[n50][0]
200 delete(svc.valuesReceived, n50)
201 }
202
203 if len(svc.valuesReceived[n90]) > 0 {
204 p90 = svc.valuesReceived[n90][0]
205 delete(svc.valuesReceived, n90)
206 }
207
208 if len(svc.valuesReceived[n95]) > 0 {
209 p95 = svc.valuesReceived[n95][0]
210 delete(svc.valuesReceived, n95)
211 }
212
213 if len(svc.valuesReceived[n99]) > 0 {
214 p99 = svc.valuesReceived[n99][0]
215 delete(svc.valuesReceived, n99)
216 }
178217 return
179218 }
180219 if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil {
206245 }
207246 svc.mtx.RLock()
208247 defer svc.mtx.RUnlock()
209 p50 = svc.valuesReceived[n50]
210 p90 = svc.valuesReceived[n90]
248 if len(svc.valuesReceived[n50]) > 0 {
249 p50 = svc.valuesReceived[n50][0]
250 delete(svc.valuesReceived, n50)
251 }
252 if len(svc.valuesReceived[n90]) > 0 {
253 p90 = svc.valuesReceived[n90][0]
254 delete(svc.valuesReceived, n90)
255 }
211256
212257 // our teststat.TestHistogram wants us to give p95 and p99,
213258 // but with custom percentiles we don't have those.
241286 t.Fatal(err)
242287 }
243288 }
289
290 func TestErrorLog(t *testing.T) {
291 namespace := "abc"
292 svc := newMockCloudWatch()
293 cw := New(namespace, svc, WithLogger(log.NewNopLogger()))
294 cw.NewGauge(metricNameToGenerateError).Set(123)
295 if err := cw.Send(); err != errTest {
296 t.Fatal("Expected error, but didn't get one")
297 }
298 }
0 // Package cloudwatch2 emits all data as a StatisticsSet (rather than
1 // a singular Value) to CloudWatch via the aws-sdk-go-v2 SDK.
2 package cloudwatch2
3
4 import (
5 "context"
6 "math"
7 "sync"
8 "time"
9
10 "github.com/aws/aws-sdk-go-v2/aws"
11 "github.com/aws/aws-sdk-go-v2/service/cloudwatch"
12 "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types"
13 "golang.org/x/sync/errgroup"
14
15 "github.com/go-kit/kit/metrics"
16 "github.com/go-kit/kit/metrics/internal/convert"
17 "github.com/go-kit/kit/metrics/internal/lv"
18 "github.com/go-kit/log"
19 )
20
21 const (
22 maxConcurrentRequests = 20
23 )
24
25 // CloudWatchAPI is an interface that defines the set of Amazon CloudWatch API operations required by CloudWatch.
26 type CloudWatchAPI interface {
27 PutMetricData(ctx context.Context, params *cloudwatch.PutMetricDataInput, optFns ...func(*cloudwatch.Options)) (*cloudwatch.PutMetricDataOutput, error)
28 }
29
30 // CloudWatch receives metrics observations and forwards them to CloudWatch.
31 // Create a CloudWatch object, use it to create metrics, and pass those metrics as
32 // dependencies to the components that will use them.
33 //
34 // To regularly report metrics to CloudWatch, use the WriteLoop helper method.
35 type CloudWatch struct {
36 mtx sync.RWMutex
37 sem chan struct{}
38 namespace string
39 svc CloudWatchAPI
40 counters *lv.Space
41 logger log.Logger
42 numConcurrentRequests int
43 }
44
45 // Option is a function adapter to change config of the CloudWatch struct
46 type Option func(*CloudWatch)
47
48 // WithLogger sets the Logger that will receive error messages generated
49 // during the WriteLoop. By default, no logger is used.
50 func WithLogger(logger log.Logger) Option {
51 return func(cw *CloudWatch) {
52 cw.logger = logger
53 }
54 }
55
56 // WithConcurrentRequests sets the upper limit on how many
57 // cloudwatch.PutMetricDataRequest may be under way at any
58 // given time. If n is greater than 20, 20 is used. By default,
59 // the max is set at 10 concurrent requests.
60 func WithConcurrentRequests(n int) Option {
61 return func(cw *CloudWatch) {
62 if n > maxConcurrentRequests {
63 n = maxConcurrentRequests
64 }
65 cw.numConcurrentRequests = n
66 }
67 }
68
69 // New returns a CloudWatch object that may be used to create metrics.
70 // Namespace is applied to all created metrics and maps to the CloudWatch namespace.
71 // Callers must ensure that regular calls to Send are performed, either
72 // manually or with one of the helper methods.
73 func New(namespace string, svc CloudWatchAPI, options ...Option) *CloudWatch {
74 cw := &CloudWatch{
75 namespace: namespace,
76 svc: svc,
77 counters: lv.NewSpace(),
78 numConcurrentRequests: 10,
79 logger: log.NewNopLogger(),
80 }
81
82 for _, optFunc := range options {
83 optFunc(cw)
84 }
85
86 cw.sem = make(chan struct{}, cw.numConcurrentRequests)
87
88 return cw
89 }
90
91 // NewCounter returns a counter. Observations are aggregated and emitted once
92 // per write invocation.
93 func (cw *CloudWatch) NewCounter(name string) metrics.Counter {
94 return &Counter{
95 name: name,
96 obs: cw.counters.Observe,
97 }
98 }
99
100 // NewGauge returns an gauge. Under the covers, there is no distinctions
101 // in CloudWatch for how Counters/Histograms/Gauges are reported, so this
102 // just wraps a cloudwatch2.Counter.
103 func (cw *CloudWatch) NewGauge(name string) metrics.Gauge {
104 return convert.NewCounterAsGauge(cw.NewCounter(name))
105 }
106
107 // NewHistogram returns a histogram. Under the covers, there is no distinctions
108 // in CloudWatch for how Counters/Histograms/Gauges are reported, so this
109 // just wraps a cloudwatch2.Counter.
110 func (cw *CloudWatch) NewHistogram(name string) metrics.Histogram {
111 return convert.NewCounterAsHistogram(cw.NewCounter(name))
112 }
113
114 // WriteLoop is a helper method that invokes Send every time the passed
115 // channel fires. This method blocks until ctx is canceled, so clients
116 // probably want to run it in its own goroutine. For typical usage, create a
117 // time.Ticker and pass its C channel to this method.
118 func (cw *CloudWatch) WriteLoop(ctx context.Context, c <-chan time.Time) {
119 for {
120 select {
121 case <-c:
122 if err := cw.Send(); err != nil {
123 cw.logger.Log("during", "Send", "err", err)
124 }
125 case <-ctx.Done():
126 return
127 }
128 }
129 }
130
131 // Send will fire an API request to CloudWatch with the latest stats for
132 // all metrics. It is preferred that the WriteLoop method is used.
133 func (cw *CloudWatch) Send() error {
134 cw.mtx.RLock()
135 defer cw.mtx.RUnlock()
136 now := time.Now()
137
138 var datums []types.MetricDatum
139
140 cw.counters.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool {
141 datums = append(datums, types.MetricDatum{
142 MetricName: aws.String(name),
143 Dimensions: makeDimensions(lvs...),
144 StatisticValues: stats(values),
145 Timestamp: aws.Time(now),
146 })
147 return true
148 })
149
150 var batches [][]types.MetricDatum
151 for len(datums) > 0 {
152 var batch []types.MetricDatum
153 lim := len(datums)
154 if lim > maxConcurrentRequests {
155 lim = maxConcurrentRequests
156 }
157 batch, datums = datums[:lim], datums[lim:]
158 batches = append(batches, batch)
159 }
160
161 var g errgroup.Group
162 for _, batch := range batches {
163 batch := batch
164 g.Go(func() error {
165 cw.sem <- struct{}{}
166 defer func() {
167 <-cw.sem
168 }()
169 _, err := cw.svc.PutMetricData(context.TODO(), &cloudwatch.PutMetricDataInput{
170 Namespace: aws.String(cw.namespace),
171 MetricData: batch,
172 })
173 return err
174 })
175 }
176 return g.Wait()
177 }
178
179 var zero = float64(0.0)
180
181 // Just build this once to reduce construction costs whenever
182 // someone does a Send with no aggregated values.
183 var zeros = types.StatisticSet{
184 Maximum: &zero,
185 Minimum: &zero,
186 Sum: &zero,
187 SampleCount: &zero,
188 }
189
190 func stats(a []float64) *types.StatisticSet {
191 count := float64(len(a))
192 if count == 0 {
193 return &zeros
194 }
195
196 var sum float64
197 var min = math.MaxFloat64
198 var max = math.MaxFloat64 * -1
199 for _, f := range a {
200 sum += f
201 if f < min {
202 min = f
203 }
204 if f > max {
205 max = f
206 }
207 }
208
209 return &types.StatisticSet{
210 Maximum: &max,
211 Minimum: &min,
212 Sum: &sum,
213 SampleCount: &count,
214 }
215 }
216
217 func makeDimensions(labelValues ...string) []types.Dimension {
218 dimensions := make([]types.Dimension, len(labelValues)/2)
219 for i, j := 0, 0; i < len(labelValues); i, j = i+2, j+1 {
220 dimensions[j] = types.Dimension{
221 Name: aws.String(labelValues[i]),
222 Value: aws.String(labelValues[i+1]),
223 }
224 }
225 return dimensions
226 }
227
228 type observeFunc func(name string, lvs lv.LabelValues, value float64)
229
230 // Counter is a counter. Observations are forwarded to a node
231 // object, and aggregated per timeseries.
232 type Counter struct {
233 name string
234 lvs lv.LabelValues
235 obs observeFunc
236 }
237
238 // With implements metrics.Counter.
239 func (c *Counter) With(labelValues ...string) metrics.Counter {
240 return &Counter{
241 name: c.name,
242 lvs: c.lvs.With(labelValues...),
243 obs: c.obs,
244 }
245 }
246
247 // Add implements metrics.Counter.
248 func (c *Counter) Add(delta float64) {
249 c.obs(c.name, c.lvs, delta)
250 }
0 package cloudwatch2
1
2 import (
3 "context"
4 "strings"
5 "testing"
6
7 "github.com/aws/aws-sdk-go-v2/service/cloudwatch"
8 "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types"
9 )
10
11 func TestStats(t *testing.T) {
12 testCases := []struct {
13 name string
14 vals []float64
15 xMin float64
16 xMax float64
17 xSum float64
18 xCt float64
19 }{
20 {
21 "empty",
22 []float64{},
23 0.0,
24 0.0,
25 0.0,
26 0.0,
27 },
28 {
29 "single",
30 []float64{3.1416},
31 3.1416,
32 3.1416,
33 3.1416,
34 1.0,
35 },
36 {
37 "double",
38 []float64{1.0, 9.0},
39 1.0,
40 9.0,
41 10.0,
42 2.0,
43 },
44 {
45 "multiple",
46 []float64{5.0, 1.0, 9.0, 5.0},
47 1.0,
48 9.0,
49 20.0,
50 4.0,
51 },
52 }
53
54 for _, tc := range testCases {
55 t.Run(tc.name, func(t *testing.T) {
56 s := stats(tc.vals)
57 if tc.xMin != *s.Minimum {
58 t.Errorf("expected [%f]: %f\n", tc.xMin, *s.Minimum)
59 }
60 if tc.xMax != *s.Maximum {
61 t.Errorf("expected [%f]: %f\n", tc.xMax, *s.Maximum)
62 }
63 if tc.xSum != *s.Sum {
64 t.Errorf("expected [%f]: %f\n", tc.xSum, *s.Sum)
65 }
66 if tc.xCt != *s.SampleCount {
67 t.Errorf("expected [%f]: %f\n", tc.xCt, *s.SampleCount)
68 }
69 })
70 }
71 }
72
73 type mockCloudWatch struct {
74 CloudWatchAPI
75 latestName string
76 latestData []types.MetricDatum
77 }
78
79 func (mcw *mockCloudWatch) PutMetricData(ctx context.Context, params *cloudwatch.PutMetricDataInput, optFns ...func(*cloudwatch.Options)) (*cloudwatch.PutMetricDataOutput, error) {
80 mcw.latestName = *params.Namespace
81 mcw.latestData = params.MetricData
82
83 return nil, nil
84 }
85
86 func TestSend(t *testing.T) {
87 ns := "example-namespace"
88 svc := &mockCloudWatch{}
89 cw := New(ns, svc)
90
91 c := cw.NewCounter("c").With("charlie", "cat")
92 h := cw.NewHistogram("h").With("hotel", "horse")
93 g := cw.NewGauge("g").With("golf", "giraffe")
94
95 c.Add(4.0)
96 c.Add(5.0)
97 c.Add(6.0)
98 h.Observe(3.0)
99 h.Observe(5.0)
100 h.Observe(7.0)
101 g.Set(2.0)
102 g.Set(5.0)
103 g.Set(8.0)
104
105 err := cw.Send()
106 if err != nil {
107 t.Fatalf("unexpected: %v\n", err)
108 }
109
110 if ns != svc.latestName {
111 t.Errorf("expected namespace %q; not %q\n", ns, svc.latestName)
112 }
113
114 if len(svc.latestData) != 3 {
115 t.Errorf("expected 3 datums: %v\n", svc.latestData)
116 }
117 for _, datum := range svc.latestData {
118 initial := *datum.MetricName
119 if len(datum.Dimensions) != 1 {
120 t.Errorf("expected 1 dimension: %v\n", datum)
121 }
122 if !strings.HasPrefix(*datum.Dimensions[0].Name, initial) {
123 t.Errorf("expected %q in Name of %v\n", initial, datum.Dimensions)
124 }
125 if !strings.HasPrefix(*datum.Dimensions[0].Value, initial) {
126 t.Errorf("expected %q in Value of %v\n", initial, datum.Dimensions)
127 }
128 if datum.StatisticValues == nil {
129 t.Errorf("expected StatisticValues in %v\n", datum)
130 }
131 if *datum.StatisticValues.Sum != 15.0 {
132 t.Errorf("expected 15.0 for Sum in %v\n", datum)
133 }
134 if *datum.StatisticValues.SampleCount != 3.0 {
135 t.Errorf("expected 3.0 for SampleCount in %v\n", datum)
136 }
137 }
138 }
metrics/debug.test less more
Binary diff not shown
1010 package dogstatsd
1111
1212 import (
13 "context"
1314 "fmt"
1415 "io"
16 "math/rand"
1517 "strings"
1618 "sync"
1719 "sync/atomic"
1820 "time"
1921
20 "github.com/go-kit/kit/log"
2122 "github.com/go-kit/kit/metrics"
2223 "github.com/go-kit/kit/metrics/generic"
2324 "github.com/go-kit/kit/metrics/internal/lv"
2425 "github.com/go-kit/kit/metrics/internal/ratemap"
2526 "github.com/go-kit/kit/util/conn"
27 "github.com/go-kit/log"
2628 )
2729
2830 // Dogstatsd receives metrics observations and forwards them to a DogStatsD
7173 d.rates.Set(name, sampleRate)
7274 return &Counter{
7375 name: name,
74 obs: d.counters.Observe,
76 obs: sampleObservations(d.counters.Observe, sampleRate),
7577 }
7678 }
7779
9395 d.rates.Set(name, sampleRate)
9496 return &Timing{
9597 name: name,
96 obs: d.timings.Observe,
98 obs: sampleObservations(d.timings.Observe, sampleRate),
9799 }
98100 }
99101
103105 d.rates.Set(name, sampleRate)
104106 return &Histogram{
105107 name: name,
106 obs: d.histograms.Observe,
108 obs: sampleObservations(d.histograms.Observe, sampleRate),
107109 }
108110 }
109111
110112 // WriteLoop is a helper method that invokes WriteTo to the passed writer every
111 // time the passed channel fires. This method blocks until the channel is
112 // closed, so clients probably want to run it in its own goroutine. For typical
113 // time the passed channel fires. This method blocks until ctx is canceled,
114 // so clients probably want to run it in its own goroutine. For typical
113115 // usage, create a time.Ticker and pass its C channel to this method.
114 func (d *Dogstatsd) WriteLoop(c <-chan time.Time, w io.Writer) {
115 for range c {
116 if _, err := d.WriteTo(w); err != nil {
117 d.logger.Log("during", "WriteTo", "err", err)
116 func (d *Dogstatsd) WriteLoop(ctx context.Context, c <-chan time.Time, w io.Writer) {
117 for {
118 select {
119 case <-c:
120 if _, err := d.WriteTo(w); err != nil {
121 d.logger.Log("during", "WriteTo", "err", err)
122 }
123 case <-ctx.Done():
124 return
118125 }
119126 }
120127 }
121128
122129 // SendLoop is a helper method that wraps WriteLoop, passing a managed
123130 // connection to the network and address. Like WriteLoop, this method blocks
124 // until the channel is closed, so clients probably want to start it in its own
131 // until ctx is canceled, so clients probably want to start it in its own
125132 // goroutine. For typical usage, create a time.Ticker and pass its C channel to
126133 // this method.
127 func (d *Dogstatsd) SendLoop(c <-chan time.Time, network, address string) {
128 d.WriteLoop(c, conn.NewDefaultManager(network, address, d.logger))
134 func (d *Dogstatsd) SendLoop(ctx context.Context, c <-chan time.Time, network, address string) {
135 d.WriteLoop(ctx, c, conn.NewDefaultManager(network, address, d.logger))
129136 }
130137
131138 // WriteTo flushes the buffered content of the metrics to the writer, in
201208 return v
202209 }
203210
204 func last(a []float64) float64 {
205 return a[len(a)-1]
206 }
207
208211 func sampling(r float64) string {
209212 var sv string
210213 if r < 1.0 {
231234 }
232235
233236 type observeFunc func(name string, lvs lv.LabelValues, value float64)
237
238 // sampleObservations returns a modified observeFunc that samples observations.
239 func sampleObservations(obs observeFunc, sampleRate float64) observeFunc {
240 if sampleRate >= 1 {
241 return obs
242 }
243 return func(name string, lvs lv.LabelValues, value float64) {
244 if rand.Float64() > sampleRate {
245 return
246 }
247 obs(name, lvs, value)
248 }
249 }
234250
235251 // Counter is a DogStatsD counter. Observations are forwarded to a Dogstatsd
236252 // object, and aggregated (summed) per timeseries.
22 import (
33 "testing"
44
5 "github.com/go-kit/kit/log"
65 "github.com/go-kit/kit/metrics/teststat"
6 "github.com/go-kit/log"
77 )
88
99 func TestCounter(t *testing.T) {
1616
1717 func TestGauge(t *testing.T) {
1818 gauge := NewGauge("expvar_gauge").With("label values", "not supported").(*Gauge)
19 value := func() float64 { f, _ := strconv.ParseFloat(gauge.f.String(), 64); return f }
19 value := func() []float64 { f, _ := strconv.ParseFloat(gauge.f.String(), 64); return []float64{f} }
2020 if err := teststat.TestGauge(gauge, value); err != nil {
2121 t.Fatal(err)
2222 }
1717
1818 // Counter is an in-memory implementation of a Counter.
1919 type Counter struct {
20 bits uint64 // bits has to be the first word in order to be 64-aligned on 32-bit
2021 Name string
2122 lvs lv.LabelValues
22 bits uint64
2323 }
2424
2525 // NewCounter returns a new, usable Counter.
8080
8181 // Gauge is an in-memory implementation of a Gauge.
8282 type Gauge struct {
83 bits uint64 // bits has to be the first word in order to be 64-aligned on 32-bit
8384 Name string
8485 lvs lv.LabelValues
85 bits uint64
8686 }
8787
8888 // NewGauge returns a new, usable Gauge.
181181 func (h *Histogram) Print(w io.Writer) {
182182 h.h.RLock()
183183 defer h.h.RUnlock()
184 fmt.Fprintf(w, h.h.String())
184 fmt.Fprint(w, h.h.String())
185185 }
186186
187187 // safeHistogram exists as gohistogram.Histogram is not goroutine-safe.
44 // generic to use its Histogram in the Quantiles helper function.
55
66 import (
7 "go/ast"
8 "go/importer"
9 "go/parser"
10 "go/token"
11 "go/types"
12 "io/ioutil"
713 "math"
814 "math/rand"
915 "sync"
1925 if want, have := name, counter.Name; want != have {
2026 t.Errorf("Name: want %q, have %q", want, have)
2127 }
22 value := func() float64 { return counter.Value() }
28 value := counter.Value
2329 if err := teststat.TestCounter(counter, value); err != nil {
2430 t.Fatal(err)
2531 }
4450 if want, have := name, gauge.Name; want != have {
4551 t.Errorf("Name: want %q, have %q", want, have)
4652 }
47 value := func() float64 { return gauge.Value() }
53 value := func() []float64 { return []float64{gauge.Value()} }
4854 if err := teststat.TestGauge(gauge, value); err != nil {
4955 t.Fatal(err)
5056 }
106112 t.Errorf("want %f, have %f", want, have)
107113 }
108114 }
115
116 // Naive atomic alignment test.
117 // The problem is related to the use of `atomic.*` and not directly to a structure.
118 // But currently works for Counter and Gauge.
119 // To have a more solid test, this test should be removed and the other tests should be run on a 32-bit arch.
120 func TestAtomicAlignment(t *testing.T) {
121 content, err := ioutil.ReadFile("./generic.go")
122 if err != nil {
123 t.Fatal(err)
124 }
125
126 fset := token.NewFileSet()
127
128 file, err := parser.ParseFile(fset, "generic.go", content, parser.ParseComments)
129 if err != nil {
130 t.Fatal(err)
131 }
132
133 conf := types.Config{Importer: importer.ForCompiler(fset, "source", nil)}
134
135 pkg, err := conf.Check(".", fset, []*ast.File{file}, nil)
136 if err != nil {
137 t.Fatal(err)
138 }
139
140 // uses ARM as reference for 32-bit arch
141 sizes := types.SizesFor("gc", "arm")
142
143 names := []string{"Counter", "Gauge"}
144
145 for _, name := range names {
146 t.Run(name, func(t *testing.T) {
147 checkAtomicAlignment(t, sizes, pkg.Scope().Lookup(name), pkg)
148 })
149 }
150 }
151
152 func checkAtomicAlignment(t *testing.T, sizes types.Sizes, obj types.Object, pkg *types.Package) {
153 t.Helper()
154
155 st := obj.Type().Underlying().(*types.Struct)
156
157 posToCheck := make(map[int]types.Type)
158
159 var vars []*types.Var
160 for i := 0; i < st.NumFields(); i++ {
161 field := st.Field(i)
162
163 if v, ok := field.Type().(*types.Basic); ok {
164 switch v.Kind() {
165 case types.Uint64, types.Float64, types.Int64:
166 posToCheck[i] = v
167 }
168 }
169
170 vars = append(vars, types.NewVar(field.Pos(), pkg, field.Name(), field.Type()))
171 }
172
173 offsets := sizes.Offsetsof(vars)
174 for i, offset := range offsets {
175 if _, ok := posToCheck[i]; !ok {
176 continue
177 }
178
179 if offset%8 != 0 {
180 t.Errorf("misalignment detected in %s for the type %s, offset %d", obj.Name(), posToCheck[i], offset)
181 }
182 }
183 }
77 package graphite
88
99 import (
10 "context"
1011 "fmt"
1112 "io"
1213 "sync"
1314 "time"
1415
15 "github.com/go-kit/kit/log"
1616 "github.com/go-kit/kit/metrics"
1717 "github.com/go-kit/kit/metrics/generic"
1818 "github.com/go-kit/kit/util/conn"
19 "github.com/go-kit/log"
1920 )
2021
2122 // Graphite receives metrics observations and forwards them to a Graphite server.
8283 }
8384
8485 // WriteLoop is a helper method that invokes WriteTo to the passed writer every
85 // time the passed channel fires. This method blocks until the channel is
86 // closed, so clients probably want to run it in its own goroutine. For typical
86 // time the passed channel fires. This method blocks until ctx is canceled,
87 // so clients probably want to run it in its own goroutine. For typical
8788 // usage, create a time.Ticker and pass its C channel to this method.
88 func (g *Graphite) WriteLoop(c <-chan time.Time, w io.Writer) {
89 for range c {
90 if _, err := g.WriteTo(w); err != nil {
91 g.logger.Log("during", "WriteTo", "err", err)
89 func (g *Graphite) WriteLoop(ctx context.Context, c <-chan time.Time, w io.Writer) {
90 for {
91 select {
92 case <-c:
93 if _, err := g.WriteTo(w); err != nil {
94 g.logger.Log("during", "WriteTo", "err", err)
95 }
96 case <-ctx.Done():
97 return
9298 }
9399 }
94100 }
95101
96102 // SendLoop is a helper method that wraps WriteLoop, passing a managed
97103 // connection to the network and address. Like WriteLoop, this method blocks
98 // until the channel is closed, so clients probably want to start it in its own
104 // until ctx is canceled, so clients probably want to start it in its own
99105 // goroutine. For typical usage, create a time.Ticker and pass its C channel to
100106 // this method.
101 func (g *Graphite) SendLoop(c <-chan time.Time, network, address string) {
102 g.WriteLoop(c, conn.NewDefaultManager(network, address, g.logger))
107 func (g *Graphite) SendLoop(ctx context.Context, c <-chan time.Time, network, address string) {
108 g.WriteLoop(ctx, c, conn.NewDefaultManager(network, address, g.logger))
103109 }
104110
105111 // WriteTo flushes the buffered content of the metrics to the writer, in
55 "strconv"
66 "testing"
77
8 "github.com/go-kit/kit/log"
98 "github.com/go-kit/kit/metrics/teststat"
9 "github.com/go-kit/log"
1010 )
1111
1212 func TestCounter(t *testing.T) {
33 "fmt"
44 "regexp"
55
6 influxdb "github.com/influxdata/influxdb/client/v2"
6 influxdb "github.com/influxdata/influxdb1-client/v2"
77
8 "github.com/go-kit/kit/log"
8 "github.com/go-kit/log"
99 )
1010
1111 func ExampleCounter() {
9999 re := regexp.MustCompile(pattern)
100100 match := re.FindStringSubmatch(msg)
101101 if len(match) != 2 {
102 return fmt.Errorf("Pattern not found! {%s} [%s]: %v\n", pattern, msg, match)
102 return fmt.Errorf("pattern not found! {%s} [%s]: %v\n", pattern, msg, match)
103103 }
104104 fmt.Println(match[1])
105105 }
33 package influx
44
55 import (
6 "context"
67 "time"
78
8 influxdb "github.com/influxdata/influxdb/client/v2"
9
10 "github.com/go-kit/kit/log"
9 influxdb "github.com/influxdata/influxdb1-client/v2"
10
1111 "github.com/go-kit/kit/metrics"
1212 "github.com/go-kit/kit/metrics/generic"
1313 "github.com/go-kit/kit/metrics/internal/lv"
14 "github.com/go-kit/log"
1415 )
1516
1617 // Influx is a store for metrics that will be emitted to an Influx database.
8788 // time the passed channel fires. This method blocks until the channel is
8889 // closed, so clients probably want to run it in its own goroutine. For typical
8990 // usage, create a time.Ticker and pass its C channel to this method.
90 func (in *Influx) WriteLoop(c <-chan time.Time, w BatchPointsWriter) {
91 for range c {
92 if err := in.WriteTo(w); err != nil {
93 in.logger.Log("during", "WriteTo", "err", err)
91 func (in *Influx) WriteLoop(ctx context.Context, c <-chan time.Time, w BatchPointsWriter) {
92 for {
93 select {
94 case <-c:
95 if err := in.WriteTo(w); err != nil {
96 in.logger.Log("during", "WriteTo", "err", err)
97 }
98 case <-ctx.Done():
99 return
94100 }
95101 }
96102 }
77 "strings"
88 "testing"
99
10 influxdb "github.com/influxdata/influxdb/client/v2"
10 influxdb "github.com/influxdata/influxdb1-client/v2"
1111
12 "github.com/go-kit/kit/log"
1312 "github.com/go-kit/kit/metrics/teststat"
13 "github.com/go-kit/log"
1414 )
1515
1616 func TestCounter(t *testing.T) {
3333 in := New(map[string]string{"foo": "alpha"}, influxdb.BatchPointsConfig{}, log.NewNopLogger())
3434 re := regexp.MustCompile(`influx_gauge,foo=alpha value=([0-9\.]+) [0-9]+`)
3535 gauge := in.NewGauge("influx_gauge")
36 value := func() float64 {
36 value := func() []float64 {
3737 client := &bufWriter{}
3838 in.WriteTo(client)
3939 match := re.FindStringSubmatch(client.buf.String())
4040 f, _ := strconv.ParseFloat(match[1], 64)
41 return f
41 return []float64{f}
4242 }
4343 if err := teststat.TestGauge(gauge, value); err != nil {
4444 t.Fatal(err)
0 // Package influxstatsd provides support for InfluxData's StatsD Telegraf plugin. It's very
1 // similar to StatsD, but supports arbitrary tags per-metric, which map to Go
2 // kit's label values. So, while label values are no-ops in StatsD, they are
3 // supported here. For more details, see the article at
4 // https://www.influxdata.com/blog/getting-started-with-sending-statsd-metrics-to-telegraf-influxdb/
5 //
6 // This package batches observations and emits them on some schedule to the
7 // remote server. This is useful even if you connect to your service
8 // over UDP. Emitting one network packet per observation can quickly overwhelm
9 // even the fastest internal network.
10 package influxstatsd
11
12 import (
13 "context"
14 "fmt"
15 "io"
16 "strings"
17 "sync"
18 "sync/atomic"
19 "time"
20
21 "github.com/go-kit/kit/metrics"
22 "github.com/go-kit/kit/metrics/generic"
23 "github.com/go-kit/kit/metrics/internal/lv"
24 "github.com/go-kit/kit/metrics/internal/ratemap"
25 "github.com/go-kit/kit/util/conn"
26 "github.com/go-kit/log"
27 )
28
29 // Influxstatsd receives metrics observations and forwards them to a server.
30 // Create a Influxstatsd object, use it to create metrics, and pass those
31 // metrics as dependencies to the components that will use them.
32 //
33 // All metrics are buffered until WriteTo is called. Counters and gauges are
34 // aggregated into a single observation per timeseries per write. Timings and
35 // histograms are buffered but not aggregated.
36 //
37 // To regularly report metrics to an io.Writer, use the WriteLoop helper method.
38 // To send to a InfluxStatsD server, use the SendLoop helper method.
39 type Influxstatsd struct {
40 mtx sync.RWMutex
41 prefix string
42 rates *ratemap.RateMap
43 counters *lv.Space
44 gauges map[string]*gaugeNode
45 timings *lv.Space
46 histograms *lv.Space
47 logger log.Logger
48 lvs lv.LabelValues
49 }
50
51 // New returns a Influxstatsd object that may be used to create metrics. Prefix is
52 // applied to all created metrics. Callers must ensure that regular calls to
53 // WriteTo are performed, either manually or with one of the helper methods.
54 func New(prefix string, logger log.Logger, lvs ...string) *Influxstatsd {
55 if len(lvs)%2 != 0 {
56 panic("odd number of LabelValues; programmer error!")
57 }
58 return &Influxstatsd{
59 prefix: prefix,
60 rates: ratemap.New(),
61 counters: lv.NewSpace(),
62 gauges: map[string]*gaugeNode{}, // https://github.com/go-kit/kit/pull/588
63 timings: lv.NewSpace(),
64 histograms: lv.NewSpace(),
65 logger: logger,
66 lvs: lvs,
67 }
68 }
69
70 // NewCounter returns a counter, sending observations to this Influxstatsd object.
71 func (d *Influxstatsd) NewCounter(name string, sampleRate float64) *Counter {
72 d.rates.Set(name, sampleRate)
73 return &Counter{
74 name: name,
75 obs: d.counters.Observe,
76 }
77 }
78
79 // NewGauge returns a gauge, sending observations to this Influxstatsd object.
80 func (d *Influxstatsd) NewGauge(name string) *Gauge {
81 d.mtx.Lock()
82 n, ok := d.gauges[name]
83 if !ok {
84 n = &gaugeNode{gauge: &Gauge{g: generic.NewGauge(name), influx: d}}
85 d.gauges[name] = n
86 }
87 d.mtx.Unlock()
88 return n.gauge
89 }
90
91 // NewTiming returns a histogram whose observations are interpreted as
92 // millisecond durations, and are forwarded to this Influxstatsd object.
93 func (d *Influxstatsd) NewTiming(name string, sampleRate float64) *Timing {
94 d.rates.Set(name, sampleRate)
95 return &Timing{
96 name: name,
97 obs: d.timings.Observe,
98 }
99 }
100
101 // NewHistogram returns a histogram whose observations are of an unspecified
102 // unit, and are forwarded to this Influxstatsd object.
103 func (d *Influxstatsd) NewHistogram(name string, sampleRate float64) *Histogram {
104 d.rates.Set(name, sampleRate)
105 return &Histogram{
106 name: name,
107 obs: d.histograms.Observe,
108 }
109 }
110
111 // WriteLoop is a helper method that invokes WriteTo to the passed writer every
112 // time the passed channel fires. This method blocks until ctx is canceled,
113 // so clients probably want to run it in its own goroutine. For typical
114 // usage, create a time.Ticker and pass its C channel to this method.
115 func (d *Influxstatsd) WriteLoop(ctx context.Context, c <-chan time.Time, w io.Writer) {
116 for {
117 select {
118 case <-c:
119 if _, err := d.WriteTo(w); err != nil {
120 d.logger.Log("during", "WriteTo", "err", err)
121 }
122 case <-ctx.Done():
123 return
124 }
125 }
126 }
127
128 // SendLoop is a helper method that wraps WriteLoop, passing a managed
129 // connection to the network and address. Like WriteLoop, this method blocks
130 // until ctx is canceled, so clients probably want to start it in its own
131 // goroutine. For typical usage, create a time.Ticker and pass its C channel to
132 // this method.
133 func (d *Influxstatsd) SendLoop(ctx context.Context, c <-chan time.Time, network, address string) {
134 d.WriteLoop(ctx, c, conn.NewDefaultManager(network, address, d.logger))
135 }
136
137 // WriteTo flushes the buffered content of the metrics to the writer, in
138 // InfluxStatsD format. WriteTo abides best-effort semantics, so observations are
139 // lost if there is a problem with the write. Clients should be sure to call
140 // WriteTo regularly, ideally through the WriteLoop or SendLoop helper methods.
141 func (d *Influxstatsd) WriteTo(w io.Writer) (count int64, err error) {
142 var n int
143
144 d.counters.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool {
145 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)))
146 if err != nil {
147 return false
148 }
149 count += int64(n)
150 return true
151 })
152 if err != nil {
153 return count, err
154 }
155
156 d.mtx.RLock()
157 for _, root := range d.gauges {
158 root.walk(func(name string, lvs lv.LabelValues, value float64) bool {
159 n, err = fmt.Fprintf(w, "%s%s%s:%f|g\n", d.prefix, name, d.tagValues(lvs), value)
160 if err != nil {
161 return false
162 }
163 count += int64(n)
164 return true
165 })
166 }
167 d.mtx.RUnlock()
168
169 d.timings.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool {
170 sampleRate := d.rates.Get(name)
171 for _, value := range values {
172 n, err = fmt.Fprintf(w, "%s%s%s:%f|ms%s\n", d.prefix, name, d.tagValues(lvs), value, sampling(sampleRate))
173 if err != nil {
174 return false
175 }
176 count += int64(n)
177 }
178 return true
179 })
180 if err != nil {
181 return count, err
182 }
183
184 d.histograms.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool {
185 sampleRate := d.rates.Get(name)
186 for _, value := range values {
187 n, err = fmt.Fprintf(w, "%s%s%s:%f|h%s\n", d.prefix, name, d.tagValues(lvs), value, sampling(sampleRate))
188 if err != nil {
189 return false
190 }
191 count += int64(n)
192 }
193 return true
194 })
195 if err != nil {
196 return count, err
197 }
198
199 return count, err
200 }
201
202 func sum(a []float64) float64 {
203 var v float64
204 for _, f := range a {
205 v += f
206 }
207 return v
208 }
209
210 func sampling(r float64) string {
211 var sv string
212 if r < 1.0 {
213 sv = fmt.Sprintf("|@%f", r)
214 }
215 return sv
216 }
217
218 func (d *Influxstatsd) tagValues(labelValues []string) string {
219 if len(labelValues) == 0 && len(d.lvs) == 0 {
220 return ""
221 }
222 if len(labelValues)%2 != 0 {
223 panic("tagValues received a labelValues with an odd number of strings")
224 }
225 pairs := make([]string, 0, (len(d.lvs)+len(labelValues))/2)
226 for i := 0; i < len(d.lvs); i += 2 {
227 pairs = append(pairs, d.lvs[i]+"="+d.lvs[i+1])
228 }
229 for i := 0; i < len(labelValues); i += 2 {
230 pairs = append(pairs, labelValues[i]+"="+labelValues[i+1])
231 }
232 return "," + strings.Join(pairs, ",")
233 }
234
235 type observeFunc func(name string, lvs lv.LabelValues, value float64)
236
237 // Counter is a InfluxStatsD counter. Observations are forwarded to a Influxstatsd
238 // object, and aggregated (summed) per timeseries.
239 type Counter struct {
240 name string
241 lvs lv.LabelValues
242 obs observeFunc
243 }
244
245 // With implements metrics.Counter.
246 func (c *Counter) With(labelValues ...string) metrics.Counter {
247 return &Counter{
248 name: c.name,
249 lvs: c.lvs.With(labelValues...),
250 obs: c.obs,
251 }
252 }
253
254 // Add implements metrics.Counter.
255 func (c *Counter) Add(delta float64) {
256 c.obs(c.name, c.lvs, delta)
257 }
258
259 // Gauge is a InfluxStatsD gauge. Observations are forwarded to a Influxstatsd
260 // object, and aggregated (the last observation selected) per timeseries.
261 type Gauge struct {
262 g *generic.Gauge
263 influx *Influxstatsd
264 set int32
265 }
266
267 // With implements metrics.Gauge.
268 func (g *Gauge) With(labelValues ...string) metrics.Gauge {
269 g.influx.mtx.RLock()
270 node := g.influx.gauges[g.g.Name]
271 g.influx.mtx.RUnlock()
272
273 ga := &Gauge{g: g.g.With(labelValues...).(*generic.Gauge), influx: g.influx}
274 return node.addGauge(ga, ga.g.LabelValues())
275 }
276
277 // Set implements metrics.Gauge.
278 func (g *Gauge) Set(value float64) {
279 g.g.Set(value)
280 g.touch()
281 }
282
283 // Add implements metrics.Gauge.
284 func (g *Gauge) Add(delta float64) {
285 g.g.Add(delta)
286 g.touch()
287 }
288
289 // Timing is a InfluxStatsD timing, or metrics.Histogram. Observations are
290 // forwarded to a Influxstatsd object, and collected (but not aggregated) per
291 // timeseries.
292 type Timing struct {
293 name string
294 lvs lv.LabelValues
295 obs observeFunc
296 }
297
298 // With implements metrics.Timing.
299 func (t *Timing) With(labelValues ...string) metrics.Histogram {
300 return &Timing{
301 name: t.name,
302 lvs: t.lvs.With(labelValues...),
303 obs: t.obs,
304 }
305 }
306
307 // Observe implements metrics.Histogram. Value is interpreted as milliseconds.
308 func (t *Timing) Observe(value float64) {
309 t.obs(t.name, t.lvs, value)
310 }
311
312 // Histogram is a InfluxStatsD histrogram. Observations are forwarded to a
313 // Influxstatsd object, and collected (but not aggregated) per timeseries.
314 type Histogram struct {
315 name string
316 lvs lv.LabelValues
317 obs observeFunc
318 }
319
320 // With implements metrics.Histogram.
321 func (h *Histogram) With(labelValues ...string) metrics.Histogram {
322 return &Histogram{
323 name: h.name,
324 lvs: h.lvs.With(labelValues...),
325 obs: h.obs,
326 }
327 }
328
329 // Observe implements metrics.Histogram.
330 func (h *Histogram) Observe(value float64) {
331 h.obs(h.name, h.lvs, value)
332 }
333
334 type pair struct{ label, value string }
335
336 type gaugeNode struct {
337 mtx sync.RWMutex
338 gauge *Gauge
339 children map[pair]*gaugeNode
340 }
341
342 func (n *gaugeNode) addGauge(g *Gauge, lvs lv.LabelValues) *Gauge {
343 n.mtx.Lock()
344 defer n.mtx.Unlock()
345 if len(lvs) == 0 {
346 if n.gauge == nil {
347 n.gauge = g
348 }
349 return n.gauge
350 }
351 if len(lvs) < 2 {
352 panic("too few LabelValues; programmer error!")
353 }
354 head, tail := pair{lvs[0], lvs[1]}, lvs[2:]
355 if n.children == nil {
356 n.children = map[pair]*gaugeNode{}
357 }
358 child, ok := n.children[head]
359 if !ok {
360 child = &gaugeNode{}
361 n.children[head] = child
362 }
363 return child.addGauge(g, tail)
364 }
365
366 func (n *gaugeNode) walk(fn func(string, lv.LabelValues, float64) bool) bool {
367 n.mtx.RLock()
368 defer n.mtx.RUnlock()
369 if n.gauge != nil {
370 value, ok := n.gauge.read()
371 if ok && !fn(n.gauge.g.Name, n.gauge.g.LabelValues(), value) {
372 return false
373 }
374 }
375 for _, child := range n.children {
376 if !child.walk(fn) {
377 return false
378 }
379 }
380 return true
381 }
382
383 func (g *Gauge) touch() {
384 atomic.StoreInt32(&(g.set), 1)
385 }
386
387 func (g *Gauge) read() (float64, bool) {
388 set := atomic.SwapInt32(&(g.set), 0)
389 return g.g.Value(), set != 0
390 }
0 package influxstatsd
1
2 import (
3 "testing"
4
5 "github.com/go-kit/kit/metrics/teststat"
6 "github.com/go-kit/log"
7 )
8
9 func TestCounter(t *testing.T) {
10 prefix, name := "abc.", "def"
11 label, value := "label", "value"
12 regex := `^` + prefix + name + "," + label + `=` + value + `:([0-9\.]+)\|c$`
13 d := New(prefix, log.NewNopLogger())
14 counter := d.NewCounter(name, 1.0).With(label, value)
15 valuef := teststat.SumLines(d, regex)
16 if err := teststat.TestCounter(counter, valuef); err != nil {
17 t.Fatal(err)
18 }
19 }
20
21 func TestCounterSampled(t *testing.T) {
22 // This will involve multiplying the observed sum by the inverse of the
23 // sample rate and checking against the expected value within some
24 // tolerance.
25 t.Skip("TODO")
26 }
27
28 func TestGauge(t *testing.T) {
29 prefix, name := "ghi.", "jkl"
30 label, value := "xyz", "abc"
31 regex := `^` + prefix + name + `,hostname=foohost,` + label + `=` + value + `:([0-9\.]+)\|g$`
32 d := New(prefix, log.NewNopLogger(), "hostname", "foohost")
33 gauge := d.NewGauge(name).With(label, value)
34 valuef := teststat.LastLine(d, regex)
35 if err := teststat.TestGauge(gauge, valuef); err != nil {
36 t.Fatal(err)
37 }
38 }
39
40 // InfluxStatsD histograms just emit all observations. So, we collect them into
41 // a generic histogram, and run the statistics test on that.
42
43 func TestHistogram(t *testing.T) {
44 prefix, name := "influxstatsd.", "histogram_test"
45 label, value := "abc", "def"
46 regex := `^` + prefix + name + "," + label + `=` + value + `:([0-9\.]+)\|h$`
47 d := New(prefix, log.NewNopLogger())
48 histogram := d.NewHistogram(name, 1.0).With(label, value)
49 quantiles := teststat.Quantiles(d, regex, 50) // no |@0.X
50 if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil {
51 t.Fatal(err)
52 }
53 }
54
55 func TestHistogramSampled(t *testing.T) {
56 prefix, name := "influxstatsd.", "sampled_histogram_test"
57 label, value := "foo", "bar"
58 regex := `^` + prefix + name + "," + label + `=` + value + `:([0-9\.]+)\|h\|@0\.01[0]*$`
59 d := New(prefix, log.NewNopLogger())
60 histogram := d.NewHistogram(name, 0.01).With(label, value)
61 quantiles := teststat.Quantiles(d, regex, 50)
62 if err := teststat.TestHistogram(histogram, quantiles, 0.02); err != nil {
63 t.Fatal(err)
64 }
65 }
66
67 func TestTiming(t *testing.T) {
68 prefix, name := "influxstatsd.", "timing_test"
69 label, value := "wiggle", "bottom"
70 regex := `^` + prefix + name + "," + label + `=` + value + `:([0-9\.]+)\|ms$`
71 d := New(prefix, log.NewNopLogger())
72 histogram := d.NewTiming(name, 1.0).With(label, value)
73 quantiles := teststat.Quantiles(d, regex, 50) // no |@0.X
74 if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil {
75 t.Fatal(err)
76 }
77 }
78
79 func TestTimingSampled(t *testing.T) {
80 prefix, name := "influxstatsd.", "sampled_timing_test"
81 label, value := "internal", "external"
82 regex := `^` + prefix + name + "," + label + `=` + value + `:([0-9\.]+)\|ms\|@0.03[0]*$`
83 d := New(prefix, log.NewNopLogger())
84 histogram := d.NewTiming(name, 0.03).With(label, value)
85 quantiles := teststat.Quantiles(d, regex, 50)
86 if err := teststat.TestHistogram(histogram, quantiles, 0.02); err != nil {
87 t.Fatal(err)
88 }
89 }
0 // Package convert provides a way to use Counters, Histograms, or Gauges
1 // as one of the other types
2 package convert
3
4 import "github.com/go-kit/kit/metrics"
5
6 type counterHistogram struct {
7 c metrics.Counter
8 }
9
10 // NewCounterAsHistogram returns a Histogram that actually writes the
11 // value on an underlying Counter
12 func NewCounterAsHistogram(c metrics.Counter) metrics.Histogram {
13 return counterHistogram{c}
14 }
15
16 // With implements Histogram.
17 func (ch counterHistogram) With(labelValues ...string) metrics.Histogram {
18 return counterHistogram{ch.c.With(labelValues...)}
19 }
20
21 // Observe implements histogram.
22 func (ch counterHistogram) Observe(value float64) {
23 ch.c.Add(value)
24 }
25
26 type histogramCounter struct {
27 h metrics.Histogram
28 }
29
30 // NewHistogramAsCounter returns a Counter that actually writes the
31 // value on an underlying Histogram
32 func NewHistogramAsCounter(h metrics.Histogram) metrics.Counter {
33 return histogramCounter{h}
34 }
35
36 // With implements Counter.
37 func (hc histogramCounter) With(labelValues ...string) metrics.Counter {
38 return histogramCounter{hc.h.With(labelValues...)}
39 }
40
41 // Add implements Counter.
42 func (hc histogramCounter) Add(delta float64) {
43 hc.h.Observe(delta)
44 }
45
46 type counterGauge struct {
47 c metrics.Counter
48 }
49
50 // NewCounterAsGauge returns a Gauge that actually writes the
51 // value on an underlying Counter
52 func NewCounterAsGauge(c metrics.Counter) metrics.Gauge {
53 return counterGauge{c}
54 }
55
56 // With implements Gauge.
57 func (cg counterGauge) With(labelValues ...string) metrics.Gauge {
58 return counterGauge{cg.c.With(labelValues...)}
59 }
60
61 // Set implements Gauge.
62 func (cg counterGauge) Set(value float64) {
63 cg.c.Add(value)
64 }
65
66 // Add implements metrics.Gauge.
67 func (cg counterGauge) Add(delta float64) {
68 cg.c.Add(delta)
69 }
70
71 type gaugeCounter struct {
72 g metrics.Gauge
73 }
74
75 // NewGaugeAsCounter returns a Counter that actually writes the
76 // value on an underlying Gauge
77 func NewGaugeAsCounter(g metrics.Gauge) metrics.Counter {
78 return gaugeCounter{g}
79 }
80
81 // With implements Counter.
82 func (gc gaugeCounter) With(labelValues ...string) metrics.Counter {
83 return gaugeCounter{gc.g.With(labelValues...)}
84 }
85
86 // Add implements Counter.
87 func (gc gaugeCounter) Add(delta float64) {
88 gc.g.Set(delta)
89 }
90
91 type histogramGauge struct {
92 h metrics.Histogram
93 }
94
95 // NewHistogramAsGauge returns a Gauge that actually writes the
96 // value on an underlying Histogram
97 func NewHistogramAsGauge(h metrics.Histogram) metrics.Gauge {
98 return histogramGauge{h}
99 }
100
101 // With implements Gauge.
102 func (hg histogramGauge) With(labelValues ...string) metrics.Gauge {
103 return histogramGauge{hg.h.With(labelValues...)}
104 }
105
106 // Set implements Gauge.
107 func (hg histogramGauge) Set(value float64) {
108 hg.h.Observe(value)
109 }
110
111 // Add implements metrics.Gauge.
112 func (hg histogramGauge) Add(delta float64) {
113 hg.h.Observe(delta)
114 }
115
116 type gaugeHistogram struct {
117 g metrics.Gauge
118 }
119
120 // NewGaugeAsHistogram returns a Histogram that actually writes the
121 // value on an underlying Gauge
122 func NewGaugeAsHistogram(g metrics.Gauge) metrics.Histogram {
123 return gaugeHistogram{g}
124 }
125
126 // With implements Histogram.
127 func (gh gaugeHistogram) With(labelValues ...string) metrics.Histogram {
128 return gaugeHistogram{gh.g.With(labelValues...)}
129 }
130
131 // Observe implements histogram.
132 func (gh gaugeHistogram) Observe(value float64) {
133 gh.g.Set(value)
134 }
0 package convert
1
2 import (
3 "testing"
4
5 "github.com/go-kit/kit/metrics/generic"
6 "github.com/go-kit/kit/metrics/teststat"
7 )
8
9 func TestCounterHistogramConversion(t *testing.T) {
10 name := "my_counter"
11 c := generic.NewCounter(name)
12 h := NewCounterAsHistogram(c)
13 top := NewHistogramAsCounter(h).With("label", "counter").(histogramCounter)
14 mid := top.h.(counterHistogram)
15 low := mid.c.(*generic.Counter)
16 if want, have := name, low.Name; want != have {
17 t.Errorf("Name: want %q, have %q", want, have)
18 }
19 if err := teststat.TestCounter(top, low.Value); err != nil {
20 t.Fatal(err)
21 }
22 }
23
24 func TestCounterGaugeConversion(t *testing.T) {
25 name := "my_counter"
26 c := generic.NewCounter(name)
27 g := NewCounterAsGauge(c)
28 top := NewGaugeAsCounter(g).With("label", "counter").(gaugeCounter)
29 mid := top.g.(counterGauge)
30 low := mid.c.(*generic.Counter)
31 if want, have := name, low.Name; want != have {
32 t.Errorf("Name: want %q, have %q", want, have)
33 }
34 if err := teststat.TestCounter(top, low.Value); err != nil {
35 t.Fatal(err)
36 }
37 }
38
39 func TestHistogramGaugeConversion(t *testing.T) {
40 name := "my_histogram"
41 h := generic.NewHistogram(name, 50)
42 g := NewHistogramAsGauge(h)
43 top := NewGaugeAsHistogram(g).With("label", "histogram").(gaugeHistogram)
44 mid := top.g.(histogramGauge)
45 low := mid.h.(*generic.Histogram)
46 if want, have := name, low.Name; want != have {
47 t.Errorf("Name: want %q, have %q", want, have)
48 }
49 quantiles := func() (float64, float64, float64, float64) {
50 return low.Quantile(0.50), low.Quantile(0.90), low.Quantile(0.95), low.Quantile(0.99)
51 }
52 if err := teststat.TestHistogram(top, quantiles, 0.01); err != nil {
53 t.Fatal(err)
54 }
55 }
7878 func (n *node) observe(lvs LabelValues, value float64) {
7979 n.mtx.Lock()
8080 defer n.mtx.Unlock()
81 if len(lvs) == 0 {
81 if len(lvs) <= 0 {
8282 n.observations = append(n.observations, value)
8383 return
8484 }
100100 func (n *node) add(lvs LabelValues, delta float64) {
101101 n.mtx.Lock()
102102 defer n.mtx.Unlock()
103 if len(lvs) == 0 {
103 if len(lvs) <= 0 {
104104 var value float64
105105 if len(n.observations) > 0 {
106106 value = last(n.observations) + delta
3939
4040 have := map[string]float64{}
4141 s.Walk(func(name string, lvs LabelValues, obs []float64) bool {
42 //t.Logf("%s %v => %v", name, lvs, obs)
4342 have[name+" ["+strings.Join(lvs, "")+"]"] += sum(obs)
4443 return true
4544 })
00 package pcp
11
22 import (
3 "github.com/performancecopilot/speed"
3 "github.com/performancecopilot/speed/v4"
44
55 "github.com/go-kit/kit/metrics"
66 )
22 import (
33 "testing"
44
5 "github.com/performancecopilot/speed"
5 "github.com/performancecopilot/speed/v4"
66
77 "github.com/go-kit/kit/metrics/teststat"
88 )
3939
4040 gauge = gauge.With("label values", "not supported").(*Gauge)
4141
42 value := func() float64 { f := gauge.g.Val(); return f }
42 value := func() []float64 { f := gauge.g.Val(); return []float64{f} }
4343 if err := teststat.TestGauge(gauge, value); err != nil {
4444 t.Fatal(err)
4545 }
5050 lvs lv.LabelValues
5151 }
5252
53 // NewGaugeFrom construts and registers a Prometheus GaugeVec,
53 // NewGaugeFrom constructs and registers a Prometheus GaugeVec,
5454 // and returns a usable Gauge object.
5555 func NewGaugeFrom(opts prometheus.GaugeOpts, labelNames []string) *Gauge {
5656 gv := prometheus.NewGaugeVec(opts, labelNames)
1111 "strings"
1212 "testing"
1313
14 "github.com/go-kit/kit/metrics/teststat"
1415 stdprometheus "github.com/prometheus/client_golang/prometheus"
15
16 "github.com/go-kit/kit/metrics/teststat"
16 "github.com/prometheus/client_golang/prometheus/promhttp"
1717 )
1818
1919 func TestCounter(t *testing.T) {
20 s := httptest.NewServer(stdprometheus.UninstrumentedHandler())
20 s := httptest.NewServer(promhttp.HandlerFor(stdprometheus.DefaultGatherer, promhttp.HandlerOpts{}))
2121 defer s.Close()
2222
2323 scrape := func() string {
4848 }
4949
5050 func TestGauge(t *testing.T) {
51 s := httptest.NewServer(stdprometheus.UninstrumentedHandler())
51 s := httptest.NewServer(promhttp.HandlerFor(stdprometheus.DefaultGatherer, promhttp.HandlerOpts{}))
5252 defer s.Close()
5353
5454 scrape := func() string {
6767 Help: "This is a different help string.",
6868 }, []string{"foo"}).With("foo", "bar")
6969
70 value := func() float64 {
70 value := func() []float64 {
7171 matches := re.FindStringSubmatch(scrape())
7272 f, _ := strconv.ParseFloat(matches[1], 64)
73 return f
73 return []float64{f}
7474 }
7575
7676 if err := teststat.TestGauge(gauge, value); err != nil {
7979 }
8080
8181 func TestSummary(t *testing.T) {
82 s := httptest.NewServer(stdprometheus.UninstrumentedHandler())
82 s := httptest.NewServer(promhttp.HandlerFor(stdprometheus.DefaultGatherer, promhttp.HandlerOpts{}))
8383 defer s.Close()
8484
8585 scrape := func() string {
9494 re99 := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{a="a",b="b",quantile="0.99"} ([0-9\.]+)`)
9595
9696 summary := NewSummaryFrom(stdprometheus.SummaryOpts{
97 Namespace: namespace,
98 Subsystem: subsystem,
99 Name: name,
100 Help: "This is the help string for the summary.",
97 Namespace: namespace,
98 Subsystem: subsystem,
99 Name: name,
100 Help: "This is the help string for the summary.",
101 Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
101102 }, []string{"a", "b"}).With("b", "b").With("a", "a")
102103
103104 quantiles := func() (float64, float64, float64, float64) {
123124 // limit. That is, the count monotonically increases over the buckets. This
124125 // requires a different strategy to test.
125126
126 s := httptest.NewServer(stdprometheus.UninstrumentedHandler())
127 s := httptest.NewServer(promhttp.HandlerFor(stdprometheus.DefaultGatherer, promhttp.HandlerOpts{}))
127128 defer s.Close()
128129
129130 scrape := func() string {
169170 }
170171
171172 bucket, _ := strconv.ParseInt(match[1], 10, 64)
172 have, _ := strconv.ParseInt(match[2], 10, 64)
173 have, _ := strconv.ParseFloat(match[2], 64)
173174
174175 want := teststat.ExpectedObservationsLessThan(bucket)
175176 if match[1] == "+Inf" {
182183 // with my Expected calculation, or in Prometheus.
183184 tolerance := 0.25
184185 if delta := math.Abs(float64(want) - float64(have)); (delta / float64(want)) > tolerance {
185 t.Errorf("Bucket %d: want %d, have %d (%.1f%%)", bucket, want, have, (100.0 * delta / float64(want)))
186 t.Errorf("Bucket %d: want %d, have %d (%.1f%%)", bucket, want, int(have), (100.0 * delta / float64(want)))
186187 }
187188 }
188189 }
197198 if !ok {
198199 t.Fatalf("expected error, got %s", reflect.TypeOf(x))
199200 }
200 if want, have := "inconsistent label cardinality", err.Error(); want != have {
201 if want, have := "inconsistent label cardinality", err.Error(); !strings.HasPrefix(have, want) {
201202 t.Fatalf("want %q, have %q", want, have)
202203 }
203204 }()
4545 }, []string{})
4646 }
4747
48 // NewGauge implements Provider via prometheus.NewSummaryFrom, i.e. the summary
48 // NewHistogram implements Provider via prometheus.NewSummaryFrom, i.e. the summary
4949 // is registered. The metric's namespace and subsystem are taken from the
5050 // Provider. Help is set to the name of the metric, and no const label names are
5151 // set. Buckets are ignored.
1313 // case "statsd":
1414 // s := statsd.New(...)
1515 // t := time.NewTicker(5*time.Second)
16 // go s.SendLoop(t.C, "tcp", "statsd.local:8125")
16 // go s.SendLoop(ctx, t.C, "tcp", "statsd.local:8125")
1717 // latency = s.NewHistogram(...)
1818 // requests = s.NewCounter(...)
1919 // default:
88 package statsd
99
1010 import (
11 "context"
1112 "fmt"
1213 "io"
1314 "time"
1415
15 "github.com/go-kit/kit/log"
1616 "github.com/go-kit/kit/metrics"
1717 "github.com/go-kit/kit/metrics/internal/lv"
1818 "github.com/go-kit/kit/metrics/internal/ratemap"
1919 "github.com/go-kit/kit/util/conn"
20 "github.com/go-kit/log"
2021 )
2122
2223 // Statsd receives metrics observations and forwards them to a StatsD server.
8889 }
8990
9091 // WriteLoop is a helper method that invokes WriteTo to the passed writer every
91 // time the passed channel fires. This method blocks until the channel is
92 // closed, so clients probably want to run it in its own goroutine. For typical
92 // time the passed channel fires. This method blocks until ctx is canceled,
93 // so clients probably want to run it in its own goroutine. For typical
9394 // usage, create a time.Ticker and pass its C channel to this method.
94 func (s *Statsd) WriteLoop(c <-chan time.Time, w io.Writer) {
95 for range c {
96 if _, err := s.WriteTo(w); err != nil {
97 s.logger.Log("during", "WriteTo", "err", err)
95 func (s *Statsd) WriteLoop(ctx context.Context, c <-chan time.Time, w io.Writer) {
96 for {
97 select {
98 case <-c:
99 if _, err := s.WriteTo(w); err != nil {
100 s.logger.Log("during", "WriteTo", "err", err)
101 }
102 case <-ctx.Done():
103 return
98104 }
99105 }
100106 }
101107
102108 // SendLoop is a helper method that wraps WriteLoop, passing a managed
103109 // connection to the network and address. Like WriteLoop, this method blocks
104 // until the channel is closed, so clients probably want to start it in its own
110 // until ctx is canceled, so clients probably want to start it in its own
105111 // goroutine. For typical usage, create a time.Ticker and pass its C channel to
106112 // this method.
107 func (s *Statsd) SendLoop(c <-chan time.Time, network, address string) {
108 s.WriteLoop(c, conn.NewDefaultManager(network, address, s.logger))
113 func (s *Statsd) SendLoop(ctx context.Context, c <-chan time.Time, network, address string) {
114 s.WriteLoop(ctx, c, conn.NewDefaultManager(network, address, s.logger))
109115 }
110116
111117 // WriteTo flushes the buffered content of the metrics to the writer, in
22 import (
33 "testing"
44
5 "github.com/go-kit/kit/log"
65 "github.com/go-kit/kit/metrics/teststat"
6 "github.com/go-kit/log"
77 )
88
99 func TestCounter(t *testing.T) {
2222 // LastLine expects a regex whose first capture group can be parsed as a
2323 // float64. It will dump the WriterTo and parse each line, expecting to find a
2424 // match. It returns the final captured float.
25 func LastLine(w io.WriterTo, regex string) func() float64 {
26 return func() float64 {
25 func LastLine(w io.WriterTo, regex string) func() []float64 {
26 return func() []float64 {
2727 _, final := stats(w, regex, nil)
28 return final
28 return []float64{final}
2929 }
3030 }
3131
4646 re := regexp.MustCompile(regex)
4747 buf := &bytes.Buffer{}
4848 w.WriteTo(buf)
49 //fmt.Fprintf(os.Stderr, "%s\n", buf.String())
5049 s := bufio.NewScanner(buf)
5150 for s.Scan() {
5251 match := re.FindStringSubmatch(s.Text())
6363 z = math.Sqrt(-math.Log((1.0 - y) / 2.0))
6464 x = (((c[3]*z+c[2])*z+c[1])*z + c[0]) / ((d[1]*z+d[0])*z + 1.0)
6565 }
66 x = x - (math.Erf(x)-y)/(2.0/math.SqrtPi*math.Exp(-x*x))
67 x = x - (math.Erf(x)-y)/(2.0/math.SqrtPi*math.Exp(-x*x))
66 x -= (math.Erf(x) - y) / (2.0 / math.SqrtPi * math.Exp(-x*x))
67 x -= (math.Erf(x) - y) / (2.0 / math.SqrtPi * math.Exp(-x*x))
6868 }
6969
7070 return x
55 "fmt"
66 "math"
77 "math/rand"
8 "reflect"
9 "sort"
810 "strings"
911
1012 "github.com/go-kit/kit/metrics"
3739
3840 // TestGauge puts some values through the gauge, and then calls the value func
3941 // to check that the gauge has the correct final value.
40 func TestGauge(gauge metrics.Gauge, value func() float64) error {
42 func TestGauge(gauge metrics.Gauge, value func() []float64) error {
4143 a := rand.Perm(100)
4244 n := rand.Intn(len(a))
4345
44 var want float64
46 var want []float64
4547 for i := 0; i < n; i++ {
4648 f := float64(a[i])
4749 gauge.Set(f)
48 want = f
50 want = append(want, f)
4951 }
5052
5153 for i := 0; i < n; i++ {
5254 f := float64(a[i])
5355 gauge.Add(f)
54 want += f
56 want = append(want, want[len(want)-1]+f)
5557 }
5658
57 if have := value(); want != have {
58 return fmt.Errorf("want %f, have %f", want, have)
59 have := value()
60
61 switch len(have) {
62 case 0:
63 return fmt.Errorf("got 0 values")
64 case 1: // provider doesn't support multi value
65 if have[0] != want[len(want)-1] {
66 return fmt.Errorf("want %f, have %f", want, have)
67 }
68 default: // provider support multi value gauges
69 sort.Float64s(want)
70 sort.Float64s(have)
71 if !reflect.DeepEqual(want, have) {
72 return fmt.Errorf("want %f, have %f", want, have)
73 }
5974 }
6075
6176 return nil
22 import (
33 "context"
44 "errors"
5 "time"
6
7 "github.com/juju/ratelimit"
85
96 "github.com/go-kit/kit/endpoint"
107 )
129 // ErrLimited is returned in the request path when the rate limiter is
1310 // triggered and the request is rejected.
1411 var ErrLimited = errors.New("rate limit exceeded")
15
16 // NewTokenBucketLimiter returns an endpoint.Middleware that acts as a rate
17 // limiter based on a token-bucket algorithm. Requests that would exceed the
18 // maximum request rate are simply rejected with an error.
19 func NewTokenBucketLimiter(tb *ratelimit.Bucket) endpoint.Middleware {
20 return NewErroringLimiter(NewAllower(tb))
21 }
22
23 // NewTokenBucketThrottler returns an endpoint.Middleware that acts as a
24 // request throttler based on a token-bucket algorithm. Requests that would
25 // exceed the maximum request rate are delayed.
26 // The parameterized function "_" is kept for backwards-compatiblity of
27 // the API, but it is no longer used for anything. You may pass it nil.
28 func NewTokenBucketThrottler(tb *ratelimit.Bucket, _ func(time.Duration)) endpoint.Middleware {
29 return NewDelayingLimiter(NewWaiter(tb))
30 }
3112
3213 // Allower dictates whether or not a request is acceptable to run.
3314 // The Limiter from "golang.org/x/time/rate" already implements this interface,
8061 return f()
8162 }
8263
83 // NewAllower turns an existing ratelimit.Bucket into an API-compatible form
84 func NewAllower(tb *ratelimit.Bucket) Allower {
85 return AllowerFunc(func() bool {
86 return (tb.TakeAvailable(1) != 0)
87 })
88 }
89
9064 // WaiterFunc is an adapter that lets a function operate as if
9165 // it implements Waiter
9266 type WaiterFunc func(ctx context.Context) error
9569 func (f WaiterFunc) Wait(ctx context.Context) error {
9670 return f(ctx)
9771 }
98
99 // NewWaiter turns an existing ratelimit.Bucket into an API-compatible form
100 func NewWaiter(tb *ratelimit.Bucket) Waiter {
101 return WaiterFunc(func(ctx context.Context) error {
102 dur := tb.Take(1)
103 select {
104 case <-ctx.Done():
105 return ctx.Err()
106 case <-time.After(dur):
107 // happy path
108 }
109 return nil
110 })
111 }
55 "testing"
66 "time"
77
8 jujuratelimit "github.com/juju/ratelimit"
98 "golang.org/x/time/rate"
109
1110 "github.com/go-kit/kit/endpoint"
1312 )
1413
1514 var nopEndpoint = func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }
16
17 func TestTokenBucketLimiter(t *testing.T) {
18 tb := jujuratelimit.NewBucket(time.Minute, 1)
19 testSuccessThenFailure(
20 t,
21 ratelimit.NewTokenBucketLimiter(tb)(nopEndpoint),
22 ratelimit.ErrLimited.Error())
23 }
24
25 func TestTokenBucketThrottler(t *testing.T) {
26 tb := jujuratelimit.NewBucket(time.Minute, 1)
27 testSuccessThenFailure(
28 t,
29 ratelimit.NewTokenBucketThrottler(tb, nil)(nopEndpoint),
30 "context deadline exceeded")
31 }
3215
3316 func TestXRateErroring(t *testing.T) {
3417 limit := rate.NewLimiter(rate.Every(time.Minute), 1)
44 "testing"
55
66 "github.com/go-kit/kit/endpoint"
7 "github.com/go-kit/kit/log"
7 "github.com/go-kit/log"
88 )
99
1010 func BenchmarkEndpoints(b *testing.B) {
8989 results = append(results, entry)
9090 }
9191
92 return results, &stdconsul.QueryMeta{}, nil
92 return results, &stdconsul.QueryMeta{LastIndex: opts.WaitIndex}, nil
9393 }
9494
9595 func (c *testClient) Register(r *stdconsul.AgentServiceRegistration) error {
00 package consul
11
22 import (
3 "errors"
34 "fmt"
4 "io"
5 "time"
56
67 consul "github.com/hashicorp/consul/api"
78
8 "github.com/go-kit/kit/log"
99 "github.com/go-kit/kit/sd"
1010 "github.com/go-kit/kit/sd/internal/instance"
11 "github.com/go-kit/kit/util/conn"
12 "github.com/go-kit/log"
1113 )
1214
1315 const defaultIndex = 0
16
17 // errStopped notifies the loop to quit. aka stopped via quitc
18 var errStopped = errors.New("quit and closed consul instancer")
1419
1520 // Instancer yields instances for a service in Consul.
1621 type Instancer struct {
5863 var (
5964 instances []string
6065 err error
66 d time.Duration = 10 * time.Millisecond
67 index uint64
6168 )
6269 for {
63 instances, lastIndex, err = s.getInstances(lastIndex, s.quitc)
70 instances, index, err = s.getInstances(lastIndex, s.quitc)
6471 switch {
65 case err == io.EOF:
72 case errors.Is(err, errStopped):
6673 return // stopped via quitc
6774 case err != nil:
6875 s.logger.Log("err", err)
76 time.Sleep(d)
77 d = conn.Exponential(d)
6978 s.cache.Update(sd.Event{Err: err})
79 case index == defaultIndex:
80 s.logger.Log("err", "index is not sane")
81 time.Sleep(d)
82 d = conn.Exponential(d)
83 case index < lastIndex:
84 s.logger.Log("err", "index is less than previous; resetting to default")
85 lastIndex = defaultIndex
86 time.Sleep(d)
87 d = conn.Exponential(d)
7088 default:
89 lastIndex = index
7190 s.cache.Update(sd.Event{Instances: instances})
91 d = 10 * time.Millisecond
7292 }
7393 }
7494 }
118138 case res := <-resc:
119139 return res.instances, res.index, nil
120140 case <-interruptc:
121 return nil, 0, io.EOF
141 return nil, 0, errStopped
122142 }
123143 }
124144
11
22 import (
33 "context"
4 "fmt"
5 "io"
46 "testing"
7 "time"
58
69 consul "github.com/hashicorp/consul/api"
710
8 "github.com/go-kit/kit/log"
911 "github.com/go-kit/kit/sd"
12 "github.com/go-kit/log"
1013 )
1114
1215 var _ sd.Instancer = (*Instancer)(nil) // API check
130133 t.Errorf("want %q, have %q", want, have)
131134 }
132135 }
136
137 type eofTestClient struct {
138 client *testClient
139 eofSig chan bool
140 called chan struct{}
141 }
142
143 func neweofTestClient(client *testClient, sig chan bool, called chan struct{}) Client {
144 return &eofTestClient{client: client, eofSig: sig, called: called}
145 }
146
147 func (c *eofTestClient) Register(r *consul.AgentServiceRegistration) error {
148 return c.client.Register(r)
149 }
150
151 func (c *eofTestClient) Deregister(r *consul.AgentServiceRegistration) error {
152 return c.client.Deregister(r)
153 }
154
155 func (c *eofTestClient) Service(service, tag string, passingOnly bool, queryOpts *consul.QueryOptions) ([]*consul.ServiceEntry, *consul.QueryMeta, error) {
156 c.called <- struct{}{}
157 shouldEOF := <-c.eofSig
158 if shouldEOF {
159 return nil, &consul.QueryMeta{}, io.EOF
160 }
161 return c.client.Service(service, tag, passingOnly, queryOpts)
162 }
163
164 func TestInstancerWithEOF(t *testing.T) {
165 var (
166 sig = make(chan bool, 1)
167 called = make(chan struct{}, 1)
168 logger = log.NewNopLogger()
169 client = neweofTestClient(newTestClient(consulState), sig, called)
170 )
171
172 sig <- false
173 s := NewInstancer(client, logger, "search", []string{"api"}, true)
174 defer s.Stop()
175
176 select {
177 case <-called:
178 case <-time.Tick(time.Millisecond * 500):
179 t.Error("failed, to receive call")
180 }
181
182 state := s.cache.State()
183 if want, have := 2, len(state.Instances); want != have {
184 t.Errorf("want %d, have %d", want, have)
185 }
186
187 // some error occurred resulting in io.EOF
188 sig <- true
189
190 // Service Called Once
191 select {
192 case <-called:
193 case <-time.Tick(time.Millisecond * 500):
194 t.Error("failed, to receive call in time")
195 }
196
197 sig <- false
198
199 // loop should continue
200 select {
201 case <-called:
202 case <-time.Tick(time.Millisecond * 500):
203 t.Error("failed, to receive call in time")
204 }
205 }
206
207 type badIndexTestClient struct {
208 client *testClient
209 called chan struct{}
210 }
211
212 func newBadIndexTestClient(client *testClient, called chan struct{}) Client {
213 return &badIndexTestClient{client: client, called: called}
214 }
215
216 func (c *badIndexTestClient) Register(r *consul.AgentServiceRegistration) error {
217 return c.client.Register(r)
218 }
219
220 func (c *badIndexTestClient) Deregister(r *consul.AgentServiceRegistration) error {
221 return c.client.Deregister(r)
222 }
223
224 func (c *badIndexTestClient) Service(service, tag string, passingOnly bool, queryOpts *consul.QueryOptions) ([]*consul.ServiceEntry, *consul.QueryMeta, error) {
225 switch {
226 case queryOpts.WaitIndex == 0:
227 queryOpts.WaitIndex = 100
228 case queryOpts.WaitIndex == 100:
229 queryOpts.WaitIndex = 99
230 default:
231 }
232 c.called <- struct{}{}
233 return c.client.Service(service, tag, passingOnly, queryOpts)
234 }
235
236 func TestInstancerWithInvalidIndex(t *testing.T) {
237 var (
238 called = make(chan struct{}, 1)
239 logger = log.NewNopLogger()
240 client = newBadIndexTestClient(newTestClient(consulState), called)
241 )
242
243 s := NewInstancer(client, logger, "search", []string{"api"}, true)
244 defer s.Stop()
245
246 select {
247 case <-called:
248 case <-time.Tick(time.Millisecond * 500):
249 t.Error("failed, to receive call")
250 }
251
252 state := s.cache.State()
253 if want, have := 2, len(state.Instances); want != have {
254 t.Errorf("want %d, have %d", want, have)
255 }
256
257 // loop should continue
258 select {
259 case <-called:
260 case <-time.Tick(time.Millisecond * 500):
261 t.Error("failed, to receive call in time")
262 }
263 }
264
265 type indexTestClient struct {
266 client *testClient
267 index uint64
268 errs chan error
269 }
270
271 func newIndexTestClient(c *testClient, errs chan error) *indexTestClient {
272 return &indexTestClient{
273 client: c,
274 index: 0,
275 errs: errs,
276 }
277 }
278
279 func (i *indexTestClient) Register(r *consul.AgentServiceRegistration) error {
280 return i.client.Register(r)
281 }
282
283 func (i *indexTestClient) Deregister(r *consul.AgentServiceRegistration) error {
284 return i.client.Deregister(r)
285 }
286
287 func (i *indexTestClient) Service(service, tag string, passingOnly bool, queryOpts *consul.QueryOptions) ([]*consul.ServiceEntry, *consul.QueryMeta, error) {
288
289 // Assumes this is the first call Service, loop hasn't begun running yet
290 if i.index == 0 && queryOpts.WaitIndex == 0 {
291 i.index = 100
292 entries, meta, err := i.client.Service(service, tag, passingOnly, queryOpts)
293 meta.LastIndex = i.index
294 return entries, meta, err
295 }
296
297 if queryOpts.WaitIndex < i.index {
298 i.errs <- fmt.Errorf("wait index %d is less than or equal to previous value", queryOpts.WaitIndex)
299 }
300
301 entries, meta, err := i.client.Service(service, tag, passingOnly, queryOpts)
302 i.index++
303 meta.LastIndex = i.index
304 return entries, meta, err
305 }
306
307 func TestInstancerLoopIndex(t *testing.T) {
308
309 var (
310 errs = make(chan error, 1)
311 logger = log.NewNopLogger()
312 client = newIndexTestClient(newTestClient(consulState), errs)
313 )
314
315 go func() {
316 for err := range errs {
317 t.Error(err)
318 t.FailNow()
319 }
320 }()
321
322 instancer := NewInstancer(client, logger, "search", []string{"api"}, true)
323 defer instancer.Stop()
324
325 time.Sleep(2 * time.Second)
326 }
0 //go:build integration
01 // +build integration
12
23 package consul
89 "time"
910
1011 "github.com/go-kit/kit/endpoint"
11 "github.com/go-kit/kit/log"
1212 "github.com/go-kit/kit/sd"
13 "github.com/go-kit/log"
1314 stdconsul "github.com/hashicorp/consul/api"
1415 )
1516
1617 func TestIntegration(t *testing.T) {
1718 consulAddr := os.Getenv("CONSUL_ADDR")
1819 if consulAddr == "" {
19 t.Fatal("CONSUL_ADDR is not set")
20 t.Skip("CONSUL_ADDR not set; skipping integration test")
2021 }
2122 stdClient, err := stdconsul.NewClient(&stdconsul.Config{
2223 Address: consulAddr,
44
55 stdconsul "github.com/hashicorp/consul/api"
66
7 "github.com/go-kit/kit/log"
7 "github.com/go-kit/log"
88 )
99
1010 // Registrar registers service instance liveness information to Consul.
44
55 stdconsul "github.com/hashicorp/consul/api"
66
7 "github.com/go-kit/kit/log"
7 "github.com/go-kit/log"
88 )
99
1010 func TestRegistrar(t *testing.T) {
00 package dnssrv
11
22 import (
3 "errors"
34 "fmt"
45 "net"
56 "time"
67
7 "github.com/go-kit/kit/log"
88 "github.com/go-kit/kit/sd"
99 "github.com/go-kit/kit/sd/internal/instance"
10 "github.com/go-kit/log"
1011 )
12
13 // ErrPortZero is returned by the resolve machinery
14 // when a DNS resolver returns an SRV record with its
15 // port set to zero.
16 var ErrPortZero = errors.New("resolver returned SRV record with port 0")
1117
1218 // Instancer yields instances from the named DNS SRV record. The name is
1319 // resolved on a fixed schedule. Priorities and weights are ignored.
5662 }
5763
5864 // Stop terminates the Instancer.
59 func (p *Instancer) Stop() {
60 close(p.quit)
65 func (in *Instancer) Stop() {
66 close(in.quit)
6167 }
6268
63 func (p *Instancer) loop(t *time.Ticker, lookup Lookup) {
69 func (in *Instancer) loop(t *time.Ticker, lookup Lookup) {
6470 defer t.Stop()
6571 for {
6672 select {
6773 case <-t.C:
68 instances, err := p.resolve(lookup)
74 instances, err := in.resolve(lookup)
6975 if err != nil {
70 p.logger.Log("name", p.name, "err", err)
71 p.cache.Update(sd.Event{Err: err})
76 in.logger.Log("name", in.name, "err", err)
77 in.cache.Update(sd.Event{Err: err})
7278 continue // don't replace potentially-good with bad
7379 }
74 p.cache.Update(sd.Event{Instances: instances})
80 in.cache.Update(sd.Event{Instances: instances})
7581
76 case <-p.quit:
82 case <-in.quit:
7783 return
7884 }
7985 }
8086 }
8187
82 func (p *Instancer) resolve(lookup Lookup) ([]string, error) {
83 _, addrs, err := lookup("", "", p.name)
88 func (in *Instancer) resolve(lookup Lookup) ([]string, error) {
89 _, addrs, err := lookup("", "", in.name)
8490 if err != nil {
8591 return nil, err
8692 }
8793 instances := make([]string, len(addrs))
8894 for i, addr := range addrs {
95 if addr.Port == 0 {
96 return nil, ErrPortZero
97 }
8998 instances[i] = net.JoinHostPort(addr.Target, fmt.Sprint(addr.Port))
9099 }
91100 return instances, nil
92101 }
93102
94103 // Register implements Instancer.
95 func (s *Instancer) Register(ch chan<- sd.Event) {
96 s.cache.Register(ch)
104 func (in *Instancer) Register(ch chan<- sd.Event) {
105 in.cache.Register(ch)
97106 }
98107
99108 // Deregister implements Instancer.
100 func (s *Instancer) Deregister(ch chan<- sd.Event) {
101 s.cache.Deregister(ch)
109 func (in *Instancer) Deregister(ch chan<- sd.Event) {
110 in.cache.Deregister(ch)
102111 }
55 "testing"
66 "time"
77
8 "github.com/go-kit/kit/log"
98 "github.com/go-kit/kit/sd"
9 "github.com/go-kit/log"
1010 )
1111
1212 var _ sd.Instancer = (*Instancer)(nil) // API check
6767 }
6868 }
6969
70 func TestIssue892(t *testing.T) {
71 ticker := time.NewTicker(time.Second)
72 ticker.Stop()
73 tickc := make(chan time.Time)
74 ticker.C = tickc
75
76 records := []*net.SRV{
77 {Target: "1.0.0.1", Port: 80},
78 {Target: "1.0.0.2", Port: 0},
79 {Target: "1.0.0.3", Port: 80},
80 }
81
82 lookup := func(service, proto, name string) (string, []*net.SRV, error) {
83 return "cname", records, nil
84 }
85
86 instancer := NewInstancerDetailed("name", ticker, lookup, log.NewNopLogger())
87 defer instancer.Stop()
88
89 tickc <- time.Now()
90 time.Sleep(100 * time.Millisecond)
91
92 if want, have := ErrPortZero, instancer.cache.State().Err; want != have {
93 t.Fatalf("want %v, have %v", want, have)
94 }
95 }
96
7097 type nopCloser struct{}
7198
7299 func (nopCloser) Close() error { return nil }
66 "time"
77
88 "github.com/go-kit/kit/endpoint"
9 "github.com/go-kit/kit/log"
9 "github.com/go-kit/log"
1010 )
1111
1212 // endpointCache collects the most recent set of instances from a service discovery
66 "time"
77
88 "github.com/go-kit/kit/endpoint"
9 "github.com/go-kit/kit/log"
9 "github.com/go-kit/log"
1010 )
1111
1212 func TestEndpointCache(t *testing.T) {
33 "time"
44
55 "github.com/go-kit/kit/endpoint"
6 "github.com/go-kit/kit/log"
6 "github.com/go-kit/log"
77 )
88
99 // Endpointer listens to a service discovery system and yields a set of
55 "time"
66
77 "github.com/go-kit/kit/endpoint"
8 "github.com/go-kit/kit/log"
98 "github.com/go-kit/kit/sd"
109 "github.com/go-kit/kit/sd/internal/instance"
10 "github.com/go-kit/log"
1111 )
1212
1313 func TestDefaultEndpointer(t *testing.T) {
99 "net/http"
1010 "time"
1111
12 etcd "github.com/coreos/etcd/client"
12 etcd "go.etcd.io/etcd/client/v2"
1313 )
1414
1515 var (
00 package etcd
11
22 import (
3 "context"
34 "errors"
45 "reflect"
56 "testing"
67 "time"
78
8 "golang.org/x/net/context"
9
10 etcd "github.com/coreos/etcd/client"
9 etcd "go.etcd.io/etcd/client/v2"
1110 )
1211
1312 func TestNewClient(t *testing.T) {
129128 // When an event occurs it just return nil response and error.
130129 // When an error occur it return a non nil error.
131130 func (fw *fakeWatcher) Next(context.Context) (*etcd.Response, error) {
132 for {
133 select {
134 case <-fw.event:
135 return nil, nil
136 case <-fw.err:
137 return nil, errors.New("error from underlying etcd watcher")
138 default:
139 }
131 select {
132 case <-fw.event:
133 return nil, nil
134 case <-fw.err:
135 return nil, errors.New("error from underlying etcd watcher")
136
140137 }
141138 }
142139
55 "time"
66
77 "github.com/go-kit/kit/endpoint"
8 "github.com/go-kit/kit/log"
98 "github.com/go-kit/kit/sd"
109 "github.com/go-kit/kit/sd/lb"
10 "github.com/go-kit/log"
1111 )
1212
1313 func Example() {
00 package etcd
11
22 import (
3 "github.com/go-kit/kit/log"
43 "github.com/go-kit/kit/sd"
54 "github.com/go-kit/kit/sd/internal/instance"
5 "github.com/go-kit/log"
66 )
77
88 // Instancer yields instances stored in a certain etcd keyspace. Any kind of
33 "errors"
44 "testing"
55
6 stdetcd "github.com/coreos/etcd/client"
6 stdetcd "go.etcd.io/etcd/client/v2"
77
8 "github.com/go-kit/kit/log"
98 "github.com/go-kit/kit/sd"
9 "github.com/go-kit/log"
1010 )
1111
1212 var _ sd.Instancer = (*Instancer)(nil) // API check
0 // +build integration
0 //go:build flaky_integration
1 // +build flaky_integration
12
23 package etcd
34
910 "time"
1011
1112 "github.com/go-kit/kit/endpoint"
12 "github.com/go-kit/kit/log"
1313 "github.com/go-kit/kit/sd"
14 "github.com/go-kit/log"
1415 )
1516
1617 // Package sd/etcd provides a wrapper around the etcd key/value store. This
33 "sync"
44 "time"
55
6 etcd "github.com/coreos/etcd/client"
6 etcd "go.etcd.io/etcd/client/v2"
77
8 "github.com/go-kit/kit/log"
8 "github.com/go-kit/log"
99 )
1010
1111 const minHeartBeatTime = 500 * time.Millisecond
44 "errors"
55 "testing"
66
7 "github.com/go-kit/kit/log"
7 "github.com/go-kit/log"
88 )
99
1010 // testClient is a basic implementation of Client
0 package etcdv3
1
2 import (
3 "context"
4 "crypto/tls"
5 "errors"
6 "time"
7
8 "go.etcd.io/etcd/client/pkg/v3/transport"
9 clientv3 "go.etcd.io/etcd/client/v3"
10 "google.golang.org/grpc"
11 )
12
13 var (
14 // ErrNoKey indicates a client method needs a key but receives none.
15 ErrNoKey = errors.New("no key provided")
16
17 // ErrNoValue indicates a client method needs a value but receives none.
18 ErrNoValue = errors.New("no value provided")
19 )
20
21 // Client is a wrapper around the etcd client.
22 type Client interface {
23 // GetEntries queries the given prefix in etcd and returns a slice
24 // containing the values of all keys found, recursively, underneath that
25 // prefix.
26 GetEntries(prefix string) ([]string, error)
27
28 // WatchPrefix watches the given prefix in etcd for changes. When a change
29 // is detected, it will signal on the passed channel. Clients are expected
30 // to call GetEntries to update themselves with the latest set of complete
31 // values. WatchPrefix will always send an initial sentinel value on the
32 // channel after establishing the watch, to ensure that clients always
33 // receive the latest set of values. WatchPrefix will block until the
34 // context passed to the NewClient constructor is terminated.
35 WatchPrefix(prefix string, ch chan struct{})
36
37 // Register a service with etcd.
38 Register(s Service) error
39
40 // Deregister a service with etcd.
41 Deregister(s Service) error
42
43 // LeaseID returns the lease id created for this service instance
44 LeaseID() int64
45 }
46
47 type client struct {
48 cli *clientv3.Client
49 ctx context.Context
50
51 kv clientv3.KV
52
53 // Watcher interface instance, used to leverage Watcher.Close()
54 watcher clientv3.Watcher
55 // watcher context
56 wctx context.Context
57 // watcher cancel func
58 wcf context.CancelFunc
59
60 // leaseID will be 0 (clientv3.NoLease) if a lease was not created
61 leaseID clientv3.LeaseID
62
63 hbch <-chan *clientv3.LeaseKeepAliveResponse
64 // Lease interface instance, used to leverage Lease.Close()
65 leaser clientv3.Lease
66 }
67
68 // ClientOptions defines options for the etcd client. All values are optional.
69 // If any duration is not specified, a default of 3 seconds will be used.
70 type ClientOptions struct {
71 Cert string
72 Key string
73 CACert string
74 DialTimeout time.Duration
75 DialKeepAlive time.Duration
76
77 // DialOptions is a list of dial options for the gRPC client (e.g., for interceptors).
78 // For example, pass grpc.WithBlock() to block until the underlying connection is up.
79 // Without this, Dial returns immediately and connecting the server happens in background.
80 DialOptions []grpc.DialOption
81
82 Username string
83 Password string
84 }
85
86 // NewClient returns Client with a connection to the named machines. It will
87 // return an error if a connection to the cluster cannot be made.
88 func NewClient(ctx context.Context, machines []string, options ClientOptions) (Client, error) {
89 if options.DialTimeout == 0 {
90 options.DialTimeout = 3 * time.Second
91 }
92 if options.DialKeepAlive == 0 {
93 options.DialKeepAlive = 3 * time.Second
94 }
95
96 var err error
97 var tlscfg *tls.Config
98
99 if options.Cert != "" && options.Key != "" {
100 tlsInfo := transport.TLSInfo{
101 CertFile: options.Cert,
102 KeyFile: options.Key,
103 TrustedCAFile: options.CACert,
104 }
105 tlscfg, err = tlsInfo.ClientConfig()
106 if err != nil {
107 return nil, err
108 }
109 }
110
111 cli, err := clientv3.New(clientv3.Config{
112 Context: ctx,
113 Endpoints: machines,
114 DialTimeout: options.DialTimeout,
115 DialKeepAliveTime: options.DialKeepAlive,
116 DialOptions: options.DialOptions,
117 TLS: tlscfg,
118 Username: options.Username,
119 Password: options.Password,
120 })
121 if err != nil {
122 return nil, err
123 }
124
125 return &client{
126 cli: cli,
127 ctx: ctx,
128 kv: clientv3.NewKV(cli),
129 }, nil
130 }
131
132 func (c *client) LeaseID() int64 { return int64(c.leaseID) }
133
134 // GetEntries implements the etcd Client interface.
135 func (c *client) GetEntries(key string) ([]string, error) {
136 resp, err := c.kv.Get(c.ctx, key, clientv3.WithPrefix())
137 if err != nil {
138 return nil, err
139 }
140
141 entries := make([]string, len(resp.Kvs))
142 for i, kv := range resp.Kvs {
143 entries[i] = string(kv.Value)
144 }
145
146 return entries, nil
147 }
148
149 // WatchPrefix implements the etcd Client interface.
150 func (c *client) WatchPrefix(prefix string, ch chan struct{}) {
151 c.wctx, c.wcf = context.WithCancel(c.ctx)
152 c.watcher = clientv3.NewWatcher(c.cli)
153
154 wch := c.watcher.Watch(c.wctx, prefix, clientv3.WithPrefix(), clientv3.WithRev(0))
155 ch <- struct{}{}
156 for wr := range wch {
157 if wr.Canceled {
158 return
159 }
160 ch <- struct{}{}
161 }
162 }
163
164 func (c *client) Register(s Service) error {
165 var err error
166
167 if s.Key == "" {
168 return ErrNoKey
169 }
170 if s.Value == "" {
171 return ErrNoValue
172 }
173
174 if c.leaser != nil {
175 c.leaser.Close()
176 }
177 c.leaser = clientv3.NewLease(c.cli)
178
179 if c.watcher != nil {
180 c.watcher.Close()
181 }
182 c.watcher = clientv3.NewWatcher(c.cli)
183 if c.kv == nil {
184 c.kv = clientv3.NewKV(c.cli)
185 }
186
187 if s.TTL == nil {
188 s.TTL = NewTTLOption(time.Second*3, time.Second*10)
189 }
190
191 grantResp, err := c.leaser.Grant(c.ctx, int64(s.TTL.ttl.Seconds()))
192 if err != nil {
193 return err
194 }
195 c.leaseID = grantResp.ID
196
197 _, err = c.kv.Put(
198 c.ctx,
199 s.Key,
200 s.Value,
201 clientv3.WithLease(c.leaseID),
202 )
203 if err != nil {
204 return err
205 }
206
207 // this will keep the key alive 'forever' or until we revoke it or
208 // the context is canceled
209 c.hbch, err = c.leaser.KeepAlive(c.ctx, c.leaseID)
210 if err != nil {
211 return err
212 }
213
214 // discard the keepalive response, make etcd library not to complain
215 // fix bug #799
216 go func() {
217 for {
218 select {
219 case r := <-c.hbch:
220 // avoid dead loop when channel was closed
221 if r == nil {
222 return
223 }
224 case <-c.ctx.Done():
225 return
226 }
227 }
228 }()
229
230 return nil
231 }
232
233 func (c *client) Deregister(s Service) error {
234 defer c.close()
235
236 if s.Key == "" {
237 return ErrNoKey
238 }
239 if _, err := c.cli.Delete(c.ctx, s.Key, clientv3.WithIgnoreLease()); err != nil {
240 return err
241 }
242
243 return nil
244 }
245
246 // close will close any open clients and call
247 // the watcher cancel func
248 func (c *client) close() {
249 if c.leaser != nil {
250 c.leaser.Close()
251 }
252 if c.watcher != nil {
253 c.watcher.Close()
254 }
255 if c.wcf != nil {
256 c.wcf()
257 }
258 }
0 package etcdv3
1
2 import (
3 "context"
4 "testing"
5 "time"
6
7 "google.golang.org/grpc"
8 )
9
10 const (
11 // irrelevantEndpoint is an address which does not exists.
12 irrelevantEndpoint = "http://irrelevant:12345"
13 )
14
15 func TestNewClient(t *testing.T) {
16 client, err := NewClient(
17 context.Background(),
18 []string{irrelevantEndpoint},
19 ClientOptions{
20 DialTimeout: 3 * time.Second,
21 DialKeepAlive: 3 * time.Second,
22 },
23 )
24 if err != nil {
25 t.Fatalf("unexpected error creating client: %v", err)
26 }
27 if client == nil {
28 t.Fatal("expected new Client, got nil")
29 }
30 }
31
32 func TestClientOptions(t *testing.T) {
33 client, err := NewClient(
34 context.Background(),
35 []string{},
36 ClientOptions{
37 Cert: "",
38 Key: "",
39 CACert: "",
40 DialTimeout: 3 * time.Second,
41 DialKeepAlive: 3 * time.Second,
42 },
43 )
44 if err == nil {
45 t.Errorf("expected error: %v", err)
46 }
47 if client != nil {
48 t.Fatalf("expected client to be nil on failure")
49 }
50
51 _, err = NewClient(
52 context.Background(),
53 []string{irrelevantEndpoint},
54 ClientOptions{
55 Cert: "does-not-exist.crt",
56 Key: "does-not-exist.key",
57 CACert: "does-not-exist.CACert",
58 DialTimeout: 3 * time.Second,
59 DialKeepAlive: 3 * time.Second,
60 },
61 )
62 if err == nil {
63 t.Errorf("expected error: %v", err)
64 }
65
66 client, err = NewClient(
67 context.Background(),
68 []string{irrelevantEndpoint},
69 ClientOptions{
70 DialOptions: []grpc.DialOption{grpc.WithBlock()},
71 },
72 )
73 if err == nil {
74 t.Errorf("expected connection should fail")
75 }
76 if client != nil {
77 t.Errorf("expected client to be nil on failure")
78 }
79 }
0 // Package etcdv3 provides an Instancer and Registrar implementation for etcd v3. If
1 // you use etcd v3 as your service discovery system, this package will help you
2 // implement the registration and client-side load balancing patterns.
3 package etcdv3
0 package etcdv3
1
2 import (
3 "context"
4 "io"
5 "time"
6
7 "github.com/go-kit/kit/endpoint"
8 "github.com/go-kit/kit/sd"
9 "github.com/go-kit/kit/sd/lb"
10 "github.com/go-kit/log"
11 "google.golang.org/grpc"
12 )
13
14 func Example() {
15 // Let's say this is a service that means to register itself.
16 // First, we will set up some context.
17 var (
18 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
19 prefix = "/services/foosvc/" // known at compile time
20 instance = "1.2.3.4:8080" // taken from runtime or platform, somehow
21 key = prefix + instance // should be globally unique
22 value = "http://" + instance // based on our transport
23 ctx = context.Background()
24 )
25
26 options := ClientOptions{
27 // Path to trusted ca file
28 CACert: "",
29
30 // Path to certificate
31 Cert: "",
32
33 // Path to private key
34 Key: "",
35
36 // Username if required
37 Username: "",
38
39 // Password if required
40 Password: "",
41
42 // If DialTimeout is 0, it defaults to 3s
43 DialTimeout: time.Second * 3,
44
45 // If DialKeepAlive is 0, it defaults to 3s
46 DialKeepAlive: time.Second * 3,
47
48 // If passing `grpc.WithBlock`, dial connection will block until success.
49 DialOptions: []grpc.DialOption{grpc.WithBlock()},
50 }
51
52 // Build the client.
53 client, err := NewClient(ctx, []string{etcdServer}, options)
54 if err != nil {
55 panic(err)
56 }
57
58 // Build the registrar.
59 registrar := NewRegistrar(client, Service{
60 Key: key,
61 Value: value,
62 }, log.NewNopLogger())
63
64 // Register our instance.
65 registrar.Register()
66
67 // At the end of our service lifecycle, for example at the end of func main,
68 // we should make sure to deregister ourselves. This is important! Don't
69 // accidentally skip this step by invoking a log.Fatal or os.Exit in the
70 // interim, which bypasses the defer stack.
71 defer registrar.Deregister()
72
73 // It's likely that we'll also want to connect to other services and call
74 // their methods. We can build an Instancer to listen for changes from etcd,
75 // create Endpointer, wrap it with a load-balancer to pick a single
76 // endpoint, and finally wrap it with a retry strategy to get something that
77 // can be used as an endpoint directly.
78 barPrefix := "/services/barsvc"
79 logger := log.NewNopLogger()
80 instancer, err := NewInstancer(client, barPrefix, logger)
81 if err != nil {
82 panic(err)
83 }
84 endpointer := sd.NewEndpointer(instancer, barFactory, logger)
85 balancer := lb.NewRoundRobin(endpointer)
86 retry := lb.Retry(3, 3*time.Second, balancer)
87
88 // And now retry can be used like any other endpoint.
89 req := struct{}{}
90 if _, err = retry(ctx, req); err != nil {
91 panic(err)
92 }
93 }
94
95 func barFactory(string) (endpoint.Endpoint, io.Closer, error) { return endpoint.Nop, nil, nil }
0 package etcdv3
1
2 import (
3 "github.com/go-kit/kit/sd"
4 "github.com/go-kit/kit/sd/internal/instance"
5 "github.com/go-kit/log"
6 )
7
8 // Instancer yields instances stored in a certain etcd keyspace. Any kind of
9 // change in that keyspace is watched and will update the Instancer's Instancers.
10 type Instancer struct {
11 cache *instance.Cache
12 client Client
13 prefix string
14 logger log.Logger
15 quitc chan struct{}
16 }
17
18 // NewInstancer returns an etcd instancer. It will start watching the given
19 // prefix for changes, and update the subscribers.
20 func NewInstancer(c Client, prefix string, logger log.Logger) (*Instancer, error) {
21 s := &Instancer{
22 client: c,
23 prefix: prefix,
24 cache: instance.NewCache(),
25 logger: logger,
26 quitc: make(chan struct{}),
27 }
28
29 instances, err := s.client.GetEntries(s.prefix)
30 if err == nil {
31 logger.Log("prefix", s.prefix, "instances", len(instances))
32 } else {
33 logger.Log("prefix", s.prefix, "err", err)
34 }
35 s.cache.Update(sd.Event{Instances: instances, Err: err})
36
37 go s.loop()
38 return s, nil
39 }
40
41 func (s *Instancer) loop() {
42 ch := make(chan struct{})
43 go s.client.WatchPrefix(s.prefix, ch)
44
45 for {
46 select {
47 case <-ch:
48 instances, err := s.client.GetEntries(s.prefix)
49 if err != nil {
50 s.logger.Log("msg", "failed to retrieve entries", "err", err)
51 s.cache.Update(sd.Event{Err: err})
52 continue
53 }
54 s.cache.Update(sd.Event{Instances: instances})
55
56 case <-s.quitc:
57 return
58 }
59 }
60 }
61
62 // Stop terminates the Instancer.
63 func (s *Instancer) Stop() {
64 close(s.quitc)
65 }
66
67 // Register implements Instancer.
68 func (s *Instancer) Register(ch chan<- sd.Event) {
69 s.cache.Register(ch)
70 }
71
72 // Deregister implements Instancer.
73 func (s *Instancer) Deregister(ch chan<- sd.Event) {
74 s.cache.Deregister(ch)
75 }
0 package etcdv3
1
2 import (
3 "errors"
4 "testing"
5
6 "github.com/go-kit/kit/sd"
7 "github.com/go-kit/log"
8 )
9
10 var _ sd.Instancer = (*Instancer)(nil) // API check
11
12 type testKV struct {
13 Key []byte
14 Value []byte
15 }
16
17 type testResponse struct {
18 Kvs []testKV
19 }
20
21 var (
22 fakeResponse = testResponse{
23 Kvs: []testKV{
24 {
25 Key: []byte("/foo/1"),
26 Value: []byte("1:1"),
27 },
28 {
29 Key: []byte("/foo/2"),
30 Value: []byte("2:2"),
31 },
32 },
33 }
34 )
35
36 var _ sd.Instancer = &Instancer{} // API check
37
38 func TestInstancer(t *testing.T) {
39 client := &fakeClient{
40 responses: map[string]testResponse{"/foo": fakeResponse},
41 }
42
43 s, err := NewInstancer(client, "/foo", log.NewNopLogger())
44 if err != nil {
45 t.Fatal(err)
46 }
47 defer s.Stop()
48
49 if state := s.cache.State(); state.Err != nil {
50 t.Fatal(state.Err)
51 }
52 }
53
54 type fakeClient struct {
55 responses map[string]testResponse
56 }
57
58 func (c *fakeClient) GetEntries(prefix string) ([]string, error) {
59 response, ok := c.responses[prefix]
60 if !ok {
61 return nil, errors.New("key not exist")
62 }
63
64 entries := make([]string, len(response.Kvs))
65 for i, node := range response.Kvs {
66 entries[i] = string(node.Value)
67 }
68 return entries, nil
69 }
70
71 func (c *fakeClient) WatchPrefix(prefix string, ch chan struct{}) {
72 }
73
74 func (c *fakeClient) LeaseID() int64 {
75 return 0
76 }
77
78 func (c *fakeClient) Register(Service) error {
79 return nil
80 }
81 func (c *fakeClient) Deregister(Service) error {
82 return nil
83 }
0 //go:build flaky_integration
1 // +build flaky_integration
2
3 package etcdv3
4
5 import (
6 "context"
7 "io"
8 "os"
9 "testing"
10 "time"
11
12 "github.com/go-kit/kit/endpoint"
13 "github.com/go-kit/kit/sd"
14 "github.com/go-kit/log"
15 )
16
17 func runIntegration(settings integrationSettings, client Client, service Service, t *testing.T) {
18 // Verify test data is initially empty.
19 entries, err := client.GetEntries(settings.key)
20 if err != nil {
21 t.Fatalf("GetEntries(%q): expected no error, got one: %v", settings.key, err)
22 }
23 if len(entries) > 0 {
24 t.Fatalf("GetEntries(%q): expected no instance entries, got %d", settings.key, len(entries))
25 }
26 t.Logf("GetEntries(%q): %v (OK)", settings.key, entries)
27
28 // Instantiate a new Registrar, passing in test data.
29 registrar := NewRegistrar(
30 client,
31 service,
32 log.With(log.NewLogfmtLogger(os.Stderr), "component", "registrar"),
33 )
34
35 // Register our instance.
36 registrar.Register()
37 t.Log("Registered")
38
39 // Retrieve entries from etcd manually.
40 entries, err = client.GetEntries(settings.key)
41 if err != nil {
42 t.Fatalf("client.GetEntries(%q): %v", settings.key, err)
43 }
44 if want, have := 1, len(entries); want != have {
45 t.Fatalf("client.GetEntries(%q): want %d, have %d", settings.key, want, have)
46 }
47 if want, have := settings.value, entries[0]; want != have {
48 t.Fatalf("want %q, have %q", want, have)
49 }
50
51 instancer, err := NewInstancer(
52 client,
53 settings.prefix,
54 log.With(log.NewLogfmtLogger(os.Stderr), "component", "instancer"),
55 )
56 if err != nil {
57 t.Fatalf("NewInstancer: %v", err)
58 }
59 t.Log("Constructed Instancer OK")
60 defer instancer.Stop()
61
62 endpointer := sd.NewEndpointer(
63 instancer,
64 func(string) (endpoint.Endpoint, io.Closer, error) { return endpoint.Nop, nil, nil },
65 log.With(log.NewLogfmtLogger(os.Stderr), "component", "instancer"),
66 )
67 t.Log("Constructed Endpointer OK")
68 defer endpointer.Close()
69
70 if !within(time.Second, func() bool {
71 endpoints, err := endpointer.Endpoints()
72 return err == nil && len(endpoints) == 1
73 }) {
74 t.Fatal("Endpointer didn't see Register in time")
75 }
76 t.Log("Endpointer saw Register OK")
77
78 // Deregister first instance of test data.
79 registrar.Deregister()
80 t.Log("Deregistered")
81
82 // Check it was deregistered.
83 if !within(time.Second, func() bool {
84 endpoints, err := endpointer.Endpoints()
85 t.Logf("Checking Deregister: len(endpoints) = %d, err = %v", len(endpoints), err)
86 return err == nil && len(endpoints) == 0
87 }) {
88 t.Fatalf("Endpointer didn't see Deregister in time")
89 }
90
91 // Verify test data no longer exists in etcd.
92 entries, err = client.GetEntries(settings.key)
93 if err != nil {
94 t.Fatalf("GetEntries(%q): expected no error, got one: %v", settings.key, err)
95 }
96 if len(entries) > 0 {
97 t.Fatalf("GetEntries(%q): expected no entries, got %v", settings.key, entries)
98 }
99 t.Logf("GetEntries(%q): %v (OK)", settings.key, entries)
100 }
101
102 type integrationSettings struct {
103 addr string
104 prefix string
105 instance string
106 key string
107 value string
108 }
109
110 func testIntegrationSettings(t *testing.T) integrationSettings {
111 var settings integrationSettings
112
113 settings.addr = os.Getenv("ETCD_ADDR")
114 if settings.addr == "" {
115 t.Skip("ETCD_ADDR not set; skipping integration test")
116 }
117
118 settings.prefix = "/services/foosvc/" // known at compile time
119 settings.instance = "1.2.3.4:8080" // taken from runtime or platform, somehow
120 settings.key = settings.prefix + settings.instance
121 settings.value = "http://" + settings.instance // based on our transport
122
123 return settings
124 }
125
126 // Package sd/etcd provides a wrapper around the etcd key/value store. This
127 // example assumes the user has an instance of etcd installed and running
128 // locally on port 2379.
129 func TestIntegration(t *testing.T) {
130 settings := testIntegrationSettings(t)
131 client, err := NewClient(context.Background(), []string{settings.addr}, ClientOptions{
132 DialTimeout: 2 * time.Second,
133 DialKeepAlive: 2 * time.Second,
134 })
135 if err != nil {
136 t.Fatalf("NewClient(%q): %v", settings.addr, err)
137 }
138
139 service := Service{
140 Key: settings.key,
141 Value: settings.value,
142 }
143
144 runIntegration(settings, client, service, t)
145 }
146
147 func TestIntegrationTTL(t *testing.T) {
148 settings := testIntegrationSettings(t)
149 client, err := NewClient(context.Background(), []string{settings.addr}, ClientOptions{
150 DialTimeout: 2 * time.Second,
151 DialKeepAlive: 2 * time.Second,
152 })
153 if err != nil {
154 t.Fatalf("NewClient(%q): %v", settings.addr, err)
155 }
156
157 service := Service{
158 Key: settings.key,
159 Value: settings.value,
160 TTL: NewTTLOption(time.Second*3, time.Second*10),
161 }
162 defer client.Deregister(service)
163
164 runIntegration(settings, client, service, t)
165 }
166
167 func TestIntegrationRegistrarOnly(t *testing.T) {
168 settings := testIntegrationSettings(t)
169 client, err := NewClient(context.Background(), []string{settings.addr}, ClientOptions{
170 DialTimeout: 2 * time.Second,
171 DialKeepAlive: 2 * time.Second,
172 })
173 if err != nil {
174 t.Fatalf("NewClient(%q): %v", settings.addr, err)
175 }
176
177 service := Service{
178 Key: settings.key,
179 Value: settings.value,
180 TTL: NewTTLOption(time.Second*3, time.Second*10),
181 }
182 defer client.Deregister(service)
183
184 // Verify test data is initially empty.
185 entries, err := client.GetEntries(settings.key)
186 if err != nil {
187 t.Fatalf("GetEntries(%q): expected no error, got one: %v", settings.key, err)
188 }
189 if len(entries) > 0 {
190 t.Fatalf("GetEntries(%q): expected no instance entries, got %d", settings.key, len(entries))
191 }
192 t.Logf("GetEntries(%q): %v (OK)", settings.key, entries)
193
194 // Instantiate a new Registrar, passing in test data.
195 registrar := NewRegistrar(
196 client,
197 service,
198 log.With(log.NewLogfmtLogger(os.Stderr), "component", "registrar"),
199 )
200
201 // Register our instance.
202 registrar.Register()
203 t.Log("Registered")
204
205 // Deregister our instance. (so we test registrar only scenario)
206 registrar.Deregister()
207 t.Log("Deregistered")
208
209 }
210
211 func within(d time.Duration, f func() bool) bool {
212 deadline := time.Now().Add(d)
213 for time.Now().Before(deadline) {
214 if f() {
215 return true
216 }
217 time.Sleep(d / 10)
218 }
219 return false
220 }
0 package etcdv3
1
2 import (
3 "sync"
4 "time"
5
6 "github.com/go-kit/log"
7 )
8
9 const minHeartBeatTime = 500 * time.Millisecond
10
11 // Registrar registers service instance liveness information to etcd.
12 type Registrar struct {
13 client Client
14 service Service
15 logger log.Logger
16
17 quitmtx sync.Mutex
18 quit chan struct{}
19 }
20
21 // Service holds the instance identifying data you want to publish to etcd. Key
22 // must be unique, and value is the string returned to subscribers, typically
23 // called the "instance" string in other parts of package sd.
24 type Service struct {
25 Key string // unique key, e.g. "/service/foobar/1.2.3.4:8080"
26 Value string // returned to subscribers, e.g. "http://1.2.3.4:8080"
27 TTL *TTLOption
28 }
29
30 // TTLOption allow setting a key with a TTL. This option will be used by a loop
31 // goroutine which regularly refreshes the lease of the key.
32 type TTLOption struct {
33 heartbeat time.Duration // e.g. time.Second * 3
34 ttl time.Duration // e.g. time.Second * 10
35 }
36
37 // NewTTLOption returns a TTLOption that contains proper TTL settings. Heartbeat
38 // is used to refresh the lease of the key periodically; its value should be at
39 // least 500ms. TTL defines the lease of the key; its value should be
40 // significantly greater than heartbeat.
41 //
42 // Good default values might be 3s heartbeat, 10s TTL.
43 func NewTTLOption(heartbeat, ttl time.Duration) *TTLOption {
44 if heartbeat <= minHeartBeatTime {
45 heartbeat = minHeartBeatTime
46 }
47 if ttl <= heartbeat {
48 ttl = 3 * heartbeat
49 }
50 return &TTLOption{
51 heartbeat: heartbeat,
52 ttl: ttl,
53 }
54 }
55
56 // NewRegistrar returns a etcd Registrar acting on the provided catalog
57 // registration (service).
58 func NewRegistrar(client Client, service Service, logger log.Logger) *Registrar {
59 return &Registrar{
60 client: client,
61 service: service,
62 logger: log.With(logger, "key", service.Key, "value", service.Value),
63 }
64 }
65
66 // Register implements the sd.Registrar interface. Call it when you want your
67 // service to be registered in etcd, typically at startup.
68 func (r *Registrar) Register() {
69 if err := r.client.Register(r.service); err != nil {
70 r.logger.Log("err", err)
71 return
72 }
73 if r.service.TTL != nil {
74 r.logger.Log("action", "register", "lease", r.client.LeaseID())
75 } else {
76 r.logger.Log("action", "register")
77 }
78 }
79
80 // Deregister implements the sd.Registrar interface. Call it when you want your
81 // service to be deregistered from etcd, typically just prior to shutdown.
82 func (r *Registrar) Deregister() {
83 if err := r.client.Deregister(r.service); err != nil {
84 r.logger.Log("err", err)
85 } else {
86 r.logger.Log("action", "deregister")
87 }
88
89 r.quitmtx.Lock()
90 defer r.quitmtx.Unlock()
91 if r.quit != nil {
92 close(r.quit)
93 r.quit = nil
94 }
95 }
0 package etcdv3
1
2 import (
3 "bytes"
4 "errors"
5 "testing"
6
7 "github.com/go-kit/log"
8 )
9
10 // testClient is a basic implementation of Client
11 type testClient struct {
12 registerRes error // value returned when Register or Deregister is called
13 }
14
15 func (tc *testClient) GetEntries(prefix string) ([]string, error) {
16 return nil, nil
17 }
18
19 func (tc *testClient) WatchPrefix(prefix string, ch chan struct{}) {
20 }
21
22 func (tc *testClient) Register(s Service) error {
23 return tc.registerRes
24 }
25
26 func (tc *testClient) Deregister(s Service) error {
27 return tc.registerRes
28 }
29
30 func (tc *testClient) LeaseID() int64 {
31 return 0
32 }
33
34 // default service used to build registrar in our tests
35 var testService = Service{
36 Key: "testKey",
37 Value: "testValue",
38 TTL: nil,
39 }
40
41 // NewRegistar should return a registar with a logger using the service key and value
42 func TestNewRegistar(t *testing.T) {
43 c := Client(&testClient{nil})
44 buf := &bytes.Buffer{}
45 logger := log.NewLogfmtLogger(buf)
46 r := NewRegistrar(
47 c,
48 testService,
49 logger,
50 )
51
52 if err := r.logger.Log("msg", "message"); err != nil {
53 t.Fatal(err)
54 }
55 if want, have := "key=testKey value=testValue msg=message\n", buf.String(); want != have {
56 t.Errorf("\nwant: %shave: %s", want, have)
57 }
58 }
59
60 func TestRegister(t *testing.T) {
61 // Register log the error returned by the client or log the successful registration action
62 // table of test cases for method Register
63 var registerTestTable = []struct {
64 registerRes error // value returned by the client on calls to Register
65 log string // expected log by the registrar
66
67 }{
68 // test case: an error is returned by the client
69 {errors.New("regError"), "key=testKey value=testValue err=regError\n"},
70 // test case: registration successful
71 {nil, "key=testKey value=testValue action=register\n"},
72 }
73
74 for _, tc := range registerTestTable {
75 c := Client(&testClient{tc.registerRes})
76 buf := &bytes.Buffer{}
77 logger := log.NewLogfmtLogger(buf)
78 r := NewRegistrar(
79 c,
80 testService,
81 logger,
82 )
83 r.Register()
84 if want, have := tc.log, buf.String(); want != have {
85 t.Fatalf("want %v, have %v", want, have)
86 }
87 }
88 }
89
90 func TestDeregister(t *testing.T) {
91 // Deregister log the error returned by the client or log the successful deregistration action
92 // table of test cases for method Deregister
93 var deregisterTestTable = []struct {
94 deregisterRes error // value returned by the client on calls to Deregister
95 log string // expected log by the registrar
96 }{
97 // test case: an error is returned by the client
98 {errors.New("deregError"), "key=testKey value=testValue err=deregError\n"},
99 // test case: deregistration successful
100 {nil, "key=testKey value=testValue action=deregister\n"},
101 }
102
103 for _, tc := range deregisterTestTable {
104 c := Client(&testClient{tc.deregisterRes})
105 buf := &bytes.Buffer{}
106 logger := log.NewLogfmtLogger(buf)
107 r := NewRegistrar(
108 c,
109 testService,
110 logger,
111 )
112 r.Deregister()
113 if want, have := tc.log, buf.String(); want != have {
114 t.Fatalf("want %v, have %v", want, have)
115 }
116 }
117 }
44
55 "github.com/hudl/fargo"
66
7 "github.com/go-kit/kit/log"
87 "github.com/go-kit/kit/sd"
98 "github.com/go-kit/kit/sd/internal/instance"
9 "github.com/go-kit/log"
1010 )
1111
1212 // Instancer yields instances stored in the Eureka registry for the given app.
0 //go:build integration
01 // +build integration
12
23 package eureka
89
910 "github.com/hudl/fargo"
1011
11 "github.com/go-kit/kit/log"
12 "github.com/go-kit/log"
1213 )
1314
1415 // Package sd/eureka provides a wrapper around the Netflix Eureka service
77
88 "github.com/hudl/fargo"
99
10 "github.com/go-kit/kit/log"
1110 "github.com/go-kit/kit/sd"
11 "github.com/go-kit/log"
1212 )
1313
1414 // Matches official Netflix Java client default.
66 "sync"
77 "time"
88
9 "github.com/go-kit/kit/log"
9 "github.com/go-kit/log"
1010 "github.com/hudl/fargo"
1111 )
1212
117117 }
118118 instances := c.instancesForApplication(name)
119119 if len(instances) == 0 {
120 return nil, fmt.Errorf("Application not found for name=%s", name)
120 return nil, fmt.Errorf("application not found for name=%s", name)
121121 }
122122 return &fargo.Application{Name: name, Instances: instances}, nil
123123 }
4040 // State returns the current state of discovery (instances or error) as sd.Event
4141 func (c *Cache) State() sd.Event {
4242 c.mtx.RLock()
43 defer c.mtx.RUnlock()
44 return c.state
43 event := c.state
44 c.mtx.RUnlock()
45 eventCopy := copyEvent(event)
46 return eventCopy
4547 }
4648
4749 // Stop implements Instancer. Since the cache is just a plain-old store of data,
5355 c.mtx.Lock()
5456 defer c.mtx.Unlock()
5557 c.reg.register(ch)
58 event := c.state
59 eventCopy := copyEvent(event)
5660 // always push the current state to new channels
57 ch <- c.state
61 ch <- eventCopy
5862 }
5963
6064 // Deregister implements Instancer.
6973
7074 func (r registry) broadcast(event sd.Event) {
7175 for c := range r {
72 c <- event
76 eventCopy := copyEvent(event)
77 c <- eventCopy
7378 }
7479 }
7580
8085 func (r registry) deregister(c chan<- sd.Event) {
8186 delete(r, c)
8287 }
88
89 // copyEvent does a deep copy on sd.Event
90 func copyEvent(e sd.Event) sd.Event {
91 // observers all need their own copy of event
92 // because they can directly modify event.Instances
93 // for example, by calling sort.Strings
94 if e.Instances == nil {
95 return e
96 }
97 instances := make([]string, len(e.Instances))
98 copy(instances, e.Instances)
99 e.Instances = instances
100 return e
101 }
00 package instance
11
22 import (
3 "context"
4 "fmt"
5 "io"
36 "reflect"
47 "testing"
58 "time"
69
10 "github.com/go-kit/kit/endpoint"
711 "github.com/go-kit/kit/sd"
12 "github.com/go-kit/log"
813 )
914
1015 var _ sd.Instancer = (*Cache)(nil) // API check
7479 // if deregister didn't work, broadcast would panic on closed channels
7580 reg.broadcast(sd.Event{Instances: []string{"x", "y"}})
7681 }
82
83 // This test is meant to be run with the race detector enabled: -race.
84 // It ensures that every registered observer receives a copy
85 // of sd.Event.Instances because observers can directly modify the field.
86 // For example, endpointCache calls sort.Strings() on sd.Event.Instances.
87 func TestDataRace(t *testing.T) {
88 instances := make([]string, 0)
89 // the number of iterations here maters because we need sort.Strings to
90 // perform a Swap in doPivot -> medianOfThree to cause a data race.
91 for i := 1; i < 1000; i++ {
92 instances = append(instances, fmt.Sprintf("%v", i))
93 }
94 e1 := sd.Event{Instances: instances}
95 cache := NewCache()
96 cache.Update(e1)
97 nullEndpoint := func(_ context.Context, _ interface{}) (interface{}, error) {
98 return nil, nil
99 }
100 nullFactory := func(instance string) (endpoint.Endpoint, io.Closer, error) {
101 return nullEndpoint, nil, nil
102 }
103 logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error {
104 return nil
105 }))
106
107 sd.NewEndpointer(cache, nullFactory, logger)
108 sd.NewEndpointer(cache, nullFactory, logger)
109 }
55 "strings"
66 "time"
77
8 "github.com/samuel/go-zookeeper/zk"
9
10 "github.com/go-kit/kit/log"
8 "github.com/go-zookeeper/zk"
9
10 "github.com/go-kit/log"
1111 )
1212
1313 // DefaultACL is the default ACL to use for creating znodes.
237237 if err := c.CreateParentNodes(path); err != nil {
238238 return err
239239 }
240 if path[len(path)-1] != '/' {
241 path += "/"
242 }
240243 node, err := c.CreateProtectedEphemeralSequential(path, s.Data, c.acl)
241244 if err != nil {
242245 return err
44 "testing"
55 "time"
66
7 stdzk "github.com/samuel/go-zookeeper/zk"
7 stdzk "github.com/go-zookeeper/zk"
88
9 "github.com/go-kit/kit/log"
9 "github.com/go-kit/log"
1010 )
1111
1212 func TestNewClient(t *testing.T) {
6262 if want, have := sessionTimeout, clientImpl.sessionTimeout; want != have {
6363 t.Errorf("want %d, have %d", want, have)
6464 }
65 if want, have := payload, clientImpl.rootNodePayload; bytes.Compare(want[0], have[0]) != 0 || bytes.Compare(want[1], have[1]) != 0 {
65 if want, have := payload, clientImpl.rootNodePayload; !bytes.Equal(want[0], have[0]) || !bytes.Equal(want[1], have[1]) {
6666 t.Errorf("want %s, have %s", want, have)
6767 }
6868
00 package zk
11
22 import (
3 "github.com/samuel/go-zookeeper/zk"
3 "github.com/go-zookeeper/zk"
44
5 "github.com/go-kit/kit/log"
65 "github.com/go-kit/kit/sd"
76 "github.com/go-kit/kit/sd/internal/instance"
7 "github.com/go-kit/log"
88 )
99
1010 // Instancer yield instances stored in a certain ZooKeeper path. Any kind of
33
44 import (
55 "bytes"
6 "log"
76 "os"
87 "testing"
98 "time"
109
11 stdzk "github.com/samuel/go-zookeeper/zk"
10 stdzk "github.com/go-zookeeper/zk"
1211 )
1312
1413 var (
1716
1817 func TestMain(m *testing.M) {
1918 zkAddr := os.Getenv("ZK_ADDR")
20 if zkAddr == "" {
21 log.Fatal("ZK_ADDR is not set")
22 }
23 host = []string{zkAddr}
19 if zkAddr != "" {
20 host = []string{zkAddr}
21 }
22 m.Run()
2423 }
2524
2625 func TestCreateParentNodesOnServer(t *testing.T) {
26 if len(host) == 0 {
27 t.Skip("ZK_ADDR not set; skipping integration test")
28 }
2729 payload := [][]byte{[]byte("Payload"), []byte("Test")}
2830 c1, err := NewClient(host, logger, Payload(payload))
2931 if err != nil {
6668 }
6769
6870 func TestCreateBadParentNodesOnServer(t *testing.T) {
71 if len(host) == 0 {
72 t.Skip("ZK_ADDR not set; skipping integration test")
73 }
6974 c, _ := NewClient(host, logger)
7075 defer c.Stop()
7176
7782 }
7883
7984 func TestCredentials1(t *testing.T) {
85 if len(host) == 0 {
86 t.Skip("ZK_ADDR not set; skipping integration test")
87 }
8088 acl := stdzk.DigestACL(stdzk.PermAll, "user", "secret")
8189 c, _ := NewClient(host, logger, ACL(acl), Credentials("user", "secret"))
8290 defer c.Stop()
8997 }
9098
9199 func TestCredentials2(t *testing.T) {
100 if len(host) == 0 {
101 t.Skip("ZK_ADDR not set; skipping integration test")
102 }
92103 acl := stdzk.DigestACL(stdzk.PermAll, "user", "secret")
93104 c, _ := NewClient(host, logger, ACL(acl))
94105 defer c.Stop()
101112 }
102113
103114 func TestConnection(t *testing.T) {
115 if len(host) == 0 {
116 t.Skip("ZK_ADDR not set; skipping integration test")
117 }
104118 c, _ := NewClient(host, logger)
105119 c.Stop()
106120
112126 }
113127
114128 func TestGetEntriesOnServer(t *testing.T) {
129 if len(host) == 0 {
130 t.Skip("ZK_ADDR not set; skipping integration test")
131 }
115132 var instancePayload = "10.0.3.204:8002"
116133
117134 c1, err := NewClient(host, logger)
157174 }
158175
159176 func TestGetEntriesPayloadOnServer(t *testing.T) {
177 t.Skip("FLAKY")
178
179 if len(host) == 0 {
180 t.Skip("ZK_ADDR not set; skipping integration test")
181 }
182
160183 c, err := NewClient(host, logger)
161184 if err != nil {
162185 t.Fatalf("Connect returned error: %v", err)
163186 }
187
164188 _, eventc, err := c.GetEntries(path)
165189 if err != nil {
166190 t.Fatal(err)
171195 Name: "instance3",
172196 Data: []byte("just some payload"),
173197 }
198
174199 registrar := NewRegistrar(c, instance3, logger)
175200 registrar.Register()
201
176202 select {
177203 case event := <-eventc:
178204 if want, have := stdzk.EventNodeChildrenChanged.String(), event.Type.String(); want != have {
179205 t.Errorf("want %s, have %s", want, have)
180206 }
181 case <-time.After(100 * time.Millisecond):
207 case <-time.After(10 * time.Second):
182208 t.Errorf("expected incoming watch event, timeout occurred")
183209 }
184210
22 import (
33 "fmt"
44
5 "github.com/samuel/go-zookeeper/zk"
5 "github.com/go-zookeeper/zk"
66
7 "github.com/go-kit/kit/log"
7 "github.com/go-kit/log"
88 )
99
1010 // wrapLogger wraps a Go kit logger so we can use it as the logging service for
00 package zk
11
2 import "github.com/go-kit/kit/log"
2 import "github.com/go-kit/log"
33
44 // Registrar registers service instance liveness information to ZooKeeper.
55 type Registrar struct {
00 package zk
11
22 import (
3 "context"
43 "errors"
54 "fmt"
65 "io"
76 "sync"
87 "time"
98
10 "github.com/samuel/go-zookeeper/zk"
9 "github.com/go-zookeeper/zk"
1110
1211 "github.com/go-kit/kit/endpoint"
13 "github.com/go-kit/kit/log"
1412 "github.com/go-kit/kit/sd"
13 "github.com/go-kit/log"
1514 )
1615
1716 var (
1817 path = "/gokit.test/service.name"
19 e = func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }
2018 logger = log.NewNopLogger()
2119 )
2220
00 # package tracing
11
2 `package tracing` provides [Dapper][]-style request tracing to services.
2 `package tracing` provides [Dapper]-style request tracing to services.
33
44 ## Rationale
55
99 benefit from request tracing; sufficiently large infrastructures will require
1010 it.
1111
12 ## Zipkin
13
14 [Zipkin] is one of the most used OSS distributed tracing platforms available
15 with support for many different languages and frameworks. Go kit provides
16 bindings to the native Go tracing implementation [zipkin-go]. If using Zipkin
17 with Go kit in a polyglot microservices environment, this is the preferred
18 binding to use. Instrumentation exists for `kit/transport/http` and
19 `kit/transport/grpc`. The bindings are highlighted in the [addsvc] example. For
20 more information regarding Zipkin feel free to visit [Zipkin's Gitter].
21
22 ## OpenCensus
23
24 Go kit supports transport and endpoint middlewares for the [OpenCensus]
25 instrumentation library. OpenCensus provides a cross language consistent data
26 model and instrumentation libraries for tracing and metrics. From this data
27 model it allows exports to various tracing and metrics backends including but
28 not limited to Zipkin, Prometheus, Stackdriver Trace & Monitoring, Jaeger,
29 AWS X-Ray and Datadog. Go kit uses the [opencensus-go] implementation to power
30 its middlewares.
31
1232 ## OpenTracing
1333
14 Go kit builds on top of the [OpenTracing] API and uses the [opentracing-go]
15 package to provide tracing middlewares for its servers and clients. Currently
16 `kit/transport/http` and `kit/transport/grpc` transports are supported.
34 Go kit supports the [OpenTracing] API and uses the [opentracing-go] package to
35 provide tracing middlewares for its servers and clients. Currently OpenTracing
36 instrumentation exists for `kit/transport/http` and `kit/transport/grpc`.
1737
18 Since [OpenTracing] is an upcoming standard API, Go kit should support a
19 multitude of tracing backends. If a Tracer implementation in Go for your
20 back-end exists, it should work out of the box. The following tracing back-ends
21 are known to work with Go kit through the OpenTracing interface and are
22 highlighted in the [addsvc] example.
38 Since [OpenTracing] is an effort to provide a generic API, Go kit should support
39 a multitude of tracing backends. If a Tracer implementation or OpenTracing
40 bridge in Go for your back-end exists, it should work out of the box.
2341
42 Please note that the "world view" of existing tracing systems do differ.
43 OpenTracing can not guarantee you that tracing alignment is perfect in a
44 microservice environment especially one which is not exclusively OpenTracing
45 enabled or switching from one tracing backend to another truly entails just a
46 change in configuration.
47
48 The following tracing back-ends are known to work with Go kit through the
49 OpenTracing interface and are highlighted in the [addsvc] example.
50
51 ### AppDash
52
53 [Appdash] support is available straight from their system repository in the
54 [appdash/opentracing] directory.
2455
2556 ### LightStep
2657
2758 [LightStep] support is available through their standard Go package
2859 [lightstep-tracer-go].
2960
30 ### AppDash
31
32 [Appdash] support is available straight from their system repository in the
33 [appdash/opentracing] directory.
34
3561 ### Zipkin
3662
37 [Zipkin] support is now available from the [zipkin-go-opentracing] package which
38 can be found at the [Open Zipkin GitHub] page. This means our old custom
39 `tracing/zipkin` package is now deprecated. In the `kit/tracing/zipkin`
40 directory you can still find the `docker-compose` script to bootstrap a Zipkin
41 development environment and a [README] detailing how to transition from the
42 old package to the new.
63 [Zipkin] support is available through the [zipkin-go-opentracing] package.
64
65 ## OpenTelemetry
66
67 [OpenTelemetry] came to life as a result of merging [OpenCensus] and [OpenTracing].
68 Go kit instrumentation can be found in [opentelemetry-go-contrib]
69 which is a central repository of instrumentation libraries.
4370
4471 [Dapper]: http://research.google.com/pubs/pub36356.html
45 [addsvc]:https://github.com/go-kit/kit/tree/master/examples/addsvc
72 [addsvc]: https://github.com/go-kit/examples/tree/master/addsvc
4673 [README]: https://github.com/go-kit/kit/blob/master/tracing/zipkin/README.md
4774
4875 [OpenTracing]: http://opentracing.io
5077
5178 [Zipkin]: http://zipkin.io/
5279 [Open Zipkin GitHub]: https://github.com/openzipkin
53 [zipkin-go-opentracing]: https://github.com/openzipkin/zipkin-go-opentracing
80 [zipkin-go-opentracing]: https://github.com/openzipkin-contrib/zipkin-go-opentracing
81 [zipkin-go]: https://github.com/openzipkin/zipkin-go
82 [Zipkin's Gitter]: https://gitter.im/openzipkin/zipkin
5483
5584 [Appdash]: https://github.com/sourcegraph/appdash
5685 [appdash/opentracing]: https://github.com/sourcegraph/appdash/tree/master/opentracing
5786
5887 [LightStep]: http://lightstep.com/
5988 [lightstep-tracer-go]: https://github.com/lightstep/lightstep-tracer-go
89
90 [OpenCensus]: https://opencensus.io/
91 [opencensus-go]: https://github.com/census-instrumentation/opencensus-go
92
93 [OpenTelemetry]: https://opentelemetry.io/
94 [opentelemetry-go-contrib]: https://github.com/open-telemetry/opentelemetry-go-contrib
22 // As your infrastructure grows, it becomes important to be able to trace a
33 // request, as it travels through multiple services and back to the user.
44 // Package tracing provides endpoints and transport helpers and middlewares to
5 // capture and emit request-scoped information. We use the excellent OpenTracing
6 // project to bind to concrete tracing systems.
5 // capture and emit request-scoped information.
76 package tracing
0 // Package opencensus provides Go kit integration to the OpenCensus project.
1 // OpenCensus is a single distribution of libraries for metrics and distributed
2 // tracing with minimal overhead that allows you to export data to multiple
3 // backends. The Go kit OpenCencus package as provided here contains middlewares
4 // for tracing.
5 package opencensus
0 package opencensus
1
2 import (
3 "context"
4 "strconv"
5
6 "go.opencensus.io/trace"
7
8 "github.com/go-kit/kit/endpoint"
9 "github.com/go-kit/kit/sd/lb"
10 )
11
12 // TraceEndpointDefaultName is the default endpoint span name to use.
13 const TraceEndpointDefaultName = "gokit/endpoint"
14
15 // TraceEndpoint returns an Endpoint middleware, tracing a Go kit endpoint.
16 // This endpoint tracer should be used in combination with a Go kit Transport
17 // tracing middleware, generic OpenCensus transport middleware or custom before
18 // and after transport functions as service propagation of SpanContext is not
19 // provided in this middleware.
20 func TraceEndpoint(name string, options ...EndpointOption) endpoint.Middleware {
21 if name == "" {
22 name = TraceEndpointDefaultName
23 }
24
25 cfg := &EndpointOptions{}
26
27 for _, o := range options {
28 o(cfg)
29 }
30
31 return func(next endpoint.Endpoint) endpoint.Endpoint {
32 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
33 if cfg.GetName != nil {
34 if newName := cfg.GetName(ctx, name); newName != "" {
35 name = newName
36 }
37 }
38
39 ctx, span := trace.StartSpan(ctx, name)
40 if len(cfg.Attributes) > 0 {
41 span.AddAttributes(cfg.Attributes...)
42 }
43 defer span.End()
44
45 if cfg.GetAttributes != nil {
46 if attrs := cfg.GetAttributes(ctx); len(attrs) > 0 {
47 span.AddAttributes(attrs...)
48 }
49 }
50
51 defer func() {
52 if err != nil {
53 if lberr, ok := err.(lb.RetryError); ok {
54 // handle errors originating from lb.Retry
55 attrs := make([]trace.Attribute, 0, len(lberr.RawErrors))
56 for idx, rawErr := range lberr.RawErrors {
57 attrs = append(attrs, trace.StringAttribute(
58 "gokit.retry.error."+strconv.Itoa(idx+1), rawErr.Error(),
59 ))
60 }
61 span.AddAttributes(attrs...)
62 span.SetStatus(trace.Status{
63 Code: trace.StatusCodeUnknown,
64 Message: lberr.Final.Error(),
65 })
66 return
67 }
68 // generic error
69 span.SetStatus(trace.Status{
70 Code: trace.StatusCodeUnknown,
71 Message: err.Error(),
72 })
73 return
74 }
75
76 // test for business error
77 if res, ok := response.(endpoint.Failer); ok && res.Failed() != nil {
78 span.AddAttributes(
79 trace.StringAttribute("gokit.business.error", res.Failed().Error()),
80 )
81 if cfg.IgnoreBusinessError {
82 span.SetStatus(trace.Status{Code: trace.StatusCodeOK})
83 return
84 }
85 // treating business error as real error in span.
86 span.SetStatus(trace.Status{
87 Code: trace.StatusCodeUnknown,
88 Message: res.Failed().Error(),
89 })
90 return
91 }
92
93 // no errors identified
94 span.SetStatus(trace.Status{Code: trace.StatusCodeOK})
95 }()
96 response, err = next(ctx, request)
97 return
98 }
99 }
100 }
0 package opencensus
1
2 import (
3 "context"
4
5 "go.opencensus.io/trace"
6 )
7
8 // EndpointOptions holds the options for tracing an endpoint
9 type EndpointOptions struct {
10 // IgnoreBusinessError if set to true will not treat a business error
11 // identified through the endpoint.Failer interface as a span error.
12 IgnoreBusinessError bool
13
14 // Attributes holds the default attributes which will be set on span
15 // creation by our Endpoint middleware.
16 Attributes []trace.Attribute
17
18 // GetName is an optional function that can set the span name based on the existing name
19 // for the endpoint and information in the context.
20 //
21 // If the function is nil, or the returned name is empty, the existing name for the endpoint is used.
22 GetName func(ctx context.Context, name string) string
23
24 // GetAttributes is an optional function that can extract trace attributes
25 // from the context and add them to the span.
26 GetAttributes func(ctx context.Context) []trace.Attribute
27 }
28
29 // EndpointOption allows for functional options to our OpenCensus endpoint
30 // tracing middleware.
31 type EndpointOption func(*EndpointOptions)
32
33 // WithEndpointConfig sets all configuration options at once by use of the
34 // EndpointOptions struct.
35 func WithEndpointConfig(options EndpointOptions) EndpointOption {
36 return func(o *EndpointOptions) {
37 *o = options
38 }
39 }
40
41 // WithEndpointAttributes sets the default attributes for the spans created by
42 // the Endpoint tracer.
43 func WithEndpointAttributes(attrs ...trace.Attribute) EndpointOption {
44 return func(o *EndpointOptions) {
45 o.Attributes = attrs
46 }
47 }
48
49 // WithIgnoreBusinessError if set to true will not treat a business error
50 // identified through the endpoint.Failer interface as a span error.
51 func WithIgnoreBusinessError(val bool) EndpointOption {
52 return func(o *EndpointOptions) {
53 o.IgnoreBusinessError = val
54 }
55 }
56
57 // WithSpanName extracts additional attributes from the request context.
58 func WithSpanName(fn func(ctx context.Context, name string) string) EndpointOption {
59 return func(o *EndpointOptions) {
60 o.GetName = fn
61 }
62 }
63
64 // WithSpanAttributes extracts additional attributes from the request context.
65 func WithSpanAttributes(fn func(ctx context.Context) []trace.Attribute) EndpointOption {
66 return func(o *EndpointOptions) {
67 o.GetAttributes = fn
68 }
69 }
0 package opencensus_test
1
2 import (
3 "context"
4 "errors"
5 "testing"
6 "time"
7
8 "go.opencensus.io/trace"
9
10 "github.com/go-kit/kit/endpoint"
11 "github.com/go-kit/kit/sd"
12 "github.com/go-kit/kit/sd/lb"
13 "github.com/go-kit/kit/tracing/opencensus"
14 )
15
16 const (
17 span1 = ""
18 span2 = "SPAN-2"
19 span3 = "SPAN-3"
20 span4 = "SPAN-4"
21 span5 = "SPAN-5"
22 span6 = "SPAN-6"
23 )
24
25 var (
26 err1 = errors.New("some error")
27 err2 = errors.New("other error")
28 err3 = errors.New("some business error")
29 err4 = errors.New("other business error")
30 )
31
32 // compile time assertion
33 var _ endpoint.Failer = failedResponse{}
34
35 type failedResponse struct {
36 err error
37 }
38
39 func (r failedResponse) Failed() error { return r.err }
40
41 func passEndpoint(_ context.Context, req interface{}) (interface{}, error) {
42 if err, _ := req.(error); err != nil {
43 return nil, err
44 }
45 return req, nil
46 }
47
48 func TestTraceEndpoint(t *testing.T) {
49 ctx := context.Background()
50
51 e := &recordingExporter{}
52 trace.RegisterExporter(e)
53 trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
54
55 // span 1
56 span1Attrs := []trace.Attribute{
57 trace.StringAttribute("string", "value"),
58 trace.Int64Attribute("int64", 42),
59 }
60 mw := opencensus.TraceEndpoint(
61 span1, opencensus.WithEndpointAttributes(span1Attrs...),
62 )
63 mw(endpoint.Nop)(ctx, nil)
64
65 // span 2
66 opts := opencensus.EndpointOptions{}
67 mw = opencensus.TraceEndpoint(span2, opencensus.WithEndpointConfig(opts))
68 mw(passEndpoint)(ctx, err1)
69
70 // span3
71 mw = opencensus.TraceEndpoint(span3)
72 ep := lb.Retry(5, 1*time.Second, lb.NewRoundRobin(sd.FixedEndpointer{passEndpoint}))
73 mw(ep)(ctx, err2)
74
75 // span4
76 mw = opencensus.TraceEndpoint(span4)
77 mw(passEndpoint)(ctx, failedResponse{err: err3})
78
79 // span5
80 mw = opencensus.TraceEndpoint(span5, opencensus.WithIgnoreBusinessError(true))
81 mw(passEndpoint)(ctx, failedResponse{err: err4})
82
83 // span6
84 span6Attrs := []trace.Attribute{
85 trace.StringAttribute("string", "value"),
86 trace.Int64Attribute("int64", 42),
87 }
88 mw = opencensus.TraceEndpoint(
89 "",
90 opencensus.WithSpanName(func(ctx context.Context, name string) string {
91 return span6
92 }),
93 opencensus.WithSpanAttributes(func(ctx context.Context) []trace.Attribute {
94 return span6Attrs
95 }),
96 )
97 mw(endpoint.Nop)(ctx, nil)
98
99 // check span count
100 spans := e.Flush()
101 if want, have := 6, len(spans); want != have {
102 t.Fatalf("incorrected number of spans, wanted %d, got %d", want, have)
103 }
104
105 // test span 1
106 span := spans[0]
107 if want, have := int32(trace.StatusCodeOK), span.Code; want != have {
108 t.Errorf("incorrect status code, wanted %d, got %d", want, have)
109 }
110
111 if want, have := opencensus.TraceEndpointDefaultName, span.Name; want != have {
112 t.Errorf("incorrect span name, wanted %q, got %q", want, have)
113 }
114
115 if want, have := 2, len(span.Attributes); want != have {
116 t.Fatalf("incorrect attribute count, wanted %d, got %d", want, have)
117 }
118
119 // test span 2
120 span = spans[1]
121 if want, have := int32(trace.StatusCodeUnknown), span.Code; want != have {
122 t.Errorf("incorrect status code, wanted %d, got %d", want, have)
123 }
124
125 if want, have := span2, span.Name; want != have {
126 t.Errorf("incorrect span name, wanted %q, got %q", want, have)
127 }
128
129 if want, have := 0, len(span.Attributes); want != have {
130 t.Fatalf("incorrect attribute count, wanted %d, got %d", want, have)
131 }
132
133 // test span 3
134 span = spans[2]
135 if want, have := int32(trace.StatusCodeUnknown), span.Code; want != have {
136 t.Errorf("incorrect status code, wanted %d, got %d", want, have)
137 }
138
139 if want, have := span3, span.Name; want != have {
140 t.Errorf("incorrect span name, wanted %q, got %q", want, have)
141 }
142
143 if want, have := 5, len(span.Attributes); want != have {
144 t.Fatalf("incorrect attribute count, wanted %d, got %d", want, have)
145 }
146
147 // test span 4
148 span = spans[3]
149 if want, have := int32(trace.StatusCodeUnknown), span.Code; want != have {
150 t.Errorf("incorrect status code, wanted %d, got %d", want, have)
151 }
152
153 if want, have := span4, span.Name; want != have {
154 t.Errorf("incorrect span name, wanted %q, got %q", want, have)
155 }
156
157 if want, have := 1, len(span.Attributes); want != have {
158 t.Fatalf("incorrect attribute count, wanted %d, got %d", want, have)
159 }
160
161 // test span 5
162 span = spans[4]
163 if want, have := int32(trace.StatusCodeOK), span.Code; want != have {
164 t.Errorf("incorrect status code, wanted %d, got %d", want, have)
165 }
166
167 if want, have := span5, span.Name; want != have {
168 t.Errorf("incorrect span name, wanted %q, got %q", want, have)
169 }
170
171 if want, have := 1, len(span.Attributes); want != have {
172 t.Fatalf("incorrect attribute count, wanted %d, got %d", want, have)
173 }
174
175 // test span 6
176 span = spans[5]
177 if want, have := span6, span.Name; want != have {
178 t.Errorf("incorrect span name, wanted %q, got %q", want, have)
179 }
180
181 if want, have := 2, len(span.Attributes); want != have {
182 t.Fatalf("incorrect attribute count, wanted %d, got %d", want, have)
183 }
184 }
0 package opencensus
1
2 import (
3 "context"
4
5 "go.opencensus.io/trace"
6 "go.opencensus.io/trace/propagation"
7 "google.golang.org/grpc/codes"
8 "google.golang.org/grpc/metadata"
9 "google.golang.org/grpc/status"
10
11 kitgrpc "github.com/go-kit/kit/transport/grpc"
12 )
13
14 const propagationKey = "grpc-trace-bin"
15
16 // GRPCClientTrace enables OpenCensus tracing of a Go kit gRPC transport client.
17 func GRPCClientTrace(options ...TracerOption) kitgrpc.ClientOption {
18 cfg := TracerOptions{}
19
20 for _, option := range options {
21 option(&cfg)
22 }
23
24 clientBefore := kitgrpc.ClientBefore(
25 func(ctx context.Context, md *metadata.MD) context.Context {
26 var name string
27
28 if cfg.Name != "" {
29 name = cfg.Name
30 } else {
31 name = ctx.Value(kitgrpc.ContextKeyRequestMethod).(string)
32 }
33
34 ctx, span := trace.StartSpan(
35 ctx,
36 name,
37 trace.WithSampler(cfg.Sampler),
38 trace.WithSpanKind(trace.SpanKindClient),
39 )
40
41 if !cfg.Public {
42 traceContextBinary := string(propagation.Binary(span.SpanContext()))
43 (*md)[propagationKey] = append((*md)[propagationKey], traceContextBinary)
44 }
45
46 return ctx
47 },
48 )
49
50 clientFinalizer := kitgrpc.ClientFinalizer(
51 func(ctx context.Context, err error) {
52 if span := trace.FromContext(ctx); span != nil {
53 if s, ok := status.FromError(err); ok {
54 span.SetStatus(trace.Status{Code: int32(s.Code()), Message: s.Message()})
55 } else {
56 span.SetStatus(trace.Status{Code: int32(codes.Unknown), Message: err.Error()})
57 }
58 span.End()
59 }
60 },
61 )
62
63 return func(c *kitgrpc.Client) {
64 clientBefore(c)
65 clientFinalizer(c)
66 }
67 }
68
69 // GRPCServerTrace enables OpenCensus tracing of a Go kit gRPC transport server.
70 func GRPCServerTrace(options ...TracerOption) kitgrpc.ServerOption {
71 cfg := TracerOptions{}
72
73 for _, option := range options {
74 option(&cfg)
75 }
76
77 serverBefore := kitgrpc.ServerBefore(
78 func(ctx context.Context, md metadata.MD) context.Context {
79 var name string
80
81 if cfg.Name != "" {
82 name = cfg.Name
83 } else {
84 name, _ = ctx.Value(kitgrpc.ContextKeyRequestMethod).(string)
85 if name == "" {
86 // we can't find the gRPC method. probably the
87 // unaryInterceptor was not wired up.
88 name = "unknown grpc method"
89 }
90 }
91
92 var (
93 parentContext trace.SpanContext
94 traceContext = md[propagationKey]
95 ok bool
96 )
97
98 if len(traceContext) > 0 {
99 traceContextBinary := []byte(traceContext[0])
100 parentContext, ok = propagation.FromBinary(traceContextBinary)
101 if ok && !cfg.Public {
102 ctx, _ = trace.StartSpanWithRemoteParent(
103 ctx,
104 name,
105 parentContext,
106 trace.WithSpanKind(trace.SpanKindServer),
107 trace.WithSampler(cfg.Sampler),
108 )
109 return ctx
110 }
111 }
112 ctx, span := trace.StartSpan(
113 ctx,
114 name,
115 trace.WithSpanKind(trace.SpanKindServer),
116 trace.WithSampler(cfg.Sampler),
117 )
118 if ok {
119 span.AddLink(
120 trace.Link{
121 TraceID: parentContext.TraceID,
122 SpanID: parentContext.SpanID,
123 Type: trace.LinkTypeChild,
124 },
125 )
126 }
127 return ctx
128 },
129 )
130
131 serverFinalizer := kitgrpc.ServerFinalizer(
132 func(ctx context.Context, err error) {
133 if span := trace.FromContext(ctx); span != nil {
134 if s, ok := status.FromError(err); ok {
135 span.SetStatus(trace.Status{Code: int32(s.Code()), Message: s.Message()})
136 } else {
137 span.SetStatus(trace.Status{Code: int32(codes.Internal), Message: err.Error()})
138 }
139 span.End()
140 }
141 },
142 )
143
144 return func(s *kitgrpc.Server) {
145 serverBefore(s)
146 serverFinalizer(s)
147 }
148 }
0 package opencensus_test
1
2 import (
3 "context"
4 "errors"
5 "testing"
6
7 "go.opencensus.io/trace"
8 "go.opencensus.io/trace/propagation"
9 "google.golang.org/grpc"
10 "google.golang.org/grpc/codes"
11 "google.golang.org/grpc/metadata"
12
13 "github.com/go-kit/kit/endpoint"
14 ockit "github.com/go-kit/kit/tracing/opencensus"
15 grpctransport "github.com/go-kit/kit/transport/grpc"
16 )
17
18 type dummy struct{}
19
20 const traceContextKey = "grpc-trace-bin"
21
22 func unaryInterceptor(
23 ctx context.Context, method string, req, reply interface{},
24 cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption,
25 ) error {
26 return nil
27 }
28
29 func TestGRPCClientTrace(t *testing.T) {
30 rec := &recordingExporter{}
31
32 trace.RegisterExporter(rec)
33
34 cc, err := grpc.Dial(
35 "",
36 grpc.WithUnaryInterceptor(unaryInterceptor),
37 grpc.WithInsecure(),
38 )
39 if err != nil {
40 t.Fatalf("unable to create gRPC dialer: %s", err.Error())
41 }
42
43 traces := []struct {
44 name string
45 err error
46 }{
47 {"", nil},
48 {"CustomName", nil},
49 {"", errors.New("dummy-error")},
50 }
51
52 for _, tr := range traces {
53 clientTracer := ockit.GRPCClientTrace(
54 ockit.WithName(tr.name),
55 ockit.WithSampler(trace.AlwaysSample()),
56 )
57
58 ep := grpctransport.NewClient(
59 cc,
60 "dummyService",
61 "dummyMethod",
62 func(context.Context, interface{}) (interface{}, error) {
63 return nil, nil
64 },
65 func(context.Context, interface{}) (interface{}, error) {
66 return nil, tr.err
67 },
68 dummy{},
69 clientTracer,
70 ).Endpoint()
71
72 ctx, parentSpan := trace.StartSpan(context.Background(), "test")
73
74 _, err = ep(ctx, nil)
75 if want, have := tr.err, err; want != have {
76 t.Fatalf("unexpected error, want %s, have %s", tr.err.Error(), err.Error())
77 }
78
79 spans := rec.Flush()
80 if want, have := 1, len(spans); want != have {
81 t.Fatalf("incorrect number of spans, want %d, have %d", want, have)
82 }
83 span := spans[0]
84 if want, have := parentSpan.SpanContext().SpanID, span.ParentSpanID; want != have {
85 t.Errorf("incorrect parent ID, want %s, have %s", want, have)
86 }
87
88 if want, have := tr.name, span.Name; want != have && want != "" {
89 t.Errorf("incorrect span name, want %s, have %s", want, have)
90 }
91
92 if want, have := "/dummyService/dummyMethod", span.Name; want != have && tr.name == "" {
93 t.Errorf("incorrect span name, want %s, have %s", want, have)
94 }
95
96 code := trace.StatusCodeOK
97 if tr.err != nil {
98 code = trace.StatusCodeUnknown
99
100 if want, have := err.Error(), span.Status.Message; want != have {
101 t.Errorf("incorrect span status msg, want %s, have %s", want, have)
102 }
103 }
104
105 if want, have := int32(code), span.Status.Code; want != have {
106 t.Errorf("incorrect span status code, want %d, have %d", want, have)
107 }
108 }
109 }
110
111 func TestGRPCServerTrace(t *testing.T) {
112 rec := &recordingExporter{}
113
114 trace.RegisterExporter(rec)
115
116 traces := []struct {
117 useParent bool
118 name string
119 err error
120 }{
121 {false, "", nil},
122 {true, "", nil},
123 {true, "CustomName", nil},
124 {true, "", errors.New("dummy-error")},
125 }
126
127 for _, tr := range traces {
128 var (
129 ctx = context.Background()
130 parentSpan *trace.Span
131 )
132
133 server := grpctransport.NewServer(
134 endpoint.Nop,
135 func(context.Context, interface{}) (interface{}, error) {
136 return nil, nil
137 },
138 func(context.Context, interface{}) (interface{}, error) {
139 return nil, tr.err
140 },
141 ockit.GRPCServerTrace(
142 ockit.WithName(tr.name),
143 ockit.WithSampler(trace.AlwaysSample()),
144 ),
145 )
146
147 if tr.useParent {
148 _, parentSpan = trace.StartSpan(context.Background(), "test")
149 traceContextBinary := propagation.Binary(parentSpan.SpanContext())
150
151 md := metadata.MD{}
152 md.Set(traceContextKey, string(traceContextBinary))
153 ctx = metadata.NewIncomingContext(ctx, md)
154 }
155
156 server.ServeGRPC(ctx, nil)
157
158 spans := rec.Flush()
159
160 if want, have := 1, len(spans); want != have {
161 t.Fatalf("incorrect number of spans, want %d, have %d", want, have)
162 }
163
164 if tr.useParent {
165 if want, have := parentSpan.SpanContext().TraceID, spans[0].TraceID; want != have {
166 t.Errorf("incorrect trace ID, want %s, have %s", want, have)
167 }
168
169 if want, have := parentSpan.SpanContext().SpanID, spans[0].ParentSpanID; want != have {
170 t.Errorf("incorrect span ID, want %s, have %s", want, have)
171 }
172 }
173
174 if want, have := tr.name, spans[0].Name; want != have && want != "" {
175 t.Errorf("incorrect span name, want %s, have %s", want, have)
176 }
177
178 if tr.err != nil {
179 if want, have := int32(codes.Internal), spans[0].Status.Code; want != have {
180 t.Errorf("incorrect span status code, want %d, have %d", want, have)
181 }
182
183 if want, have := tr.err.Error(), spans[0].Status.Message; want != have {
184 t.Errorf("incorrect span status message, want %s, have %s", want, have)
185 }
186 }
187 }
188 }
0 package opencensus
1
2 import (
3 "context"
4 "net/http"
5
6 "go.opencensus.io/plugin/ochttp"
7 "go.opencensus.io/plugin/ochttp/propagation/b3"
8 "go.opencensus.io/trace"
9
10 kithttp "github.com/go-kit/kit/transport/http"
11 )
12
13 // HTTPClientTrace enables OpenCensus tracing of a Go kit HTTP transport client.
14 func HTTPClientTrace(options ...TracerOption) kithttp.ClientOption {
15 cfg := TracerOptions{}
16
17 for _, option := range options {
18 option(&cfg)
19 }
20
21 if !cfg.Public && cfg.HTTPPropagate == nil {
22 cfg.HTTPPropagate = &b3.HTTPFormat{}
23 }
24
25 clientBefore := kithttp.ClientBefore(
26 func(ctx context.Context, req *http.Request) context.Context {
27 var name string
28
29 if cfg.Name != "" {
30 name = cfg.Name
31 } else {
32 // OpenCensus states Path being default naming for a client span
33 name = req.Method + " " + req.URL.Path
34 }
35
36 ctx, span := trace.StartSpan(
37 ctx,
38 name,
39 trace.WithSampler(cfg.Sampler),
40 trace.WithSpanKind(trace.SpanKindClient),
41 )
42
43 span.AddAttributes(
44 trace.StringAttribute(ochttp.HostAttribute, req.URL.Host),
45 trace.StringAttribute(ochttp.MethodAttribute, req.Method),
46 trace.StringAttribute(ochttp.PathAttribute, req.URL.Path),
47 trace.StringAttribute(ochttp.UserAgentAttribute, req.UserAgent()),
48 )
49
50 if !cfg.Public {
51 cfg.HTTPPropagate.SpanContextToRequest(span.SpanContext(), req)
52 }
53
54 return ctx
55 },
56 )
57
58 clientAfter := kithttp.ClientAfter(
59 func(ctx context.Context, res *http.Response) context.Context {
60 if span := trace.FromContext(ctx); span != nil {
61 span.SetStatus(ochttp.TraceStatus(res.StatusCode, http.StatusText(res.StatusCode)))
62 span.AddAttributes(
63 trace.Int64Attribute(ochttp.StatusCodeAttribute, int64(res.StatusCode)),
64 )
65 }
66 return ctx
67 },
68 )
69
70 clientFinalizer := kithttp.ClientFinalizer(
71 func(ctx context.Context, err error) {
72 if span := trace.FromContext(ctx); span != nil {
73 if err != nil {
74 span.SetStatus(trace.Status{
75 Code: trace.StatusCodeUnknown,
76 Message: err.Error(),
77 })
78 }
79 span.End()
80 }
81 },
82 )
83
84 return func(c *kithttp.Client) {
85 clientBefore(c)
86 clientAfter(c)
87 clientFinalizer(c)
88 }
89 }
90
91 // HTTPServerTrace enables OpenCensus tracing of a Go kit HTTP transport server.
92 func HTTPServerTrace(options ...TracerOption) kithttp.ServerOption {
93 cfg := TracerOptions{}
94
95 for _, option := range options {
96 option(&cfg)
97 }
98
99 if !cfg.Public && cfg.HTTPPropagate == nil {
100 cfg.HTTPPropagate = &b3.HTTPFormat{}
101 }
102
103 serverBefore := kithttp.ServerBefore(
104 func(ctx context.Context, req *http.Request) context.Context {
105 var (
106 spanContext trace.SpanContext
107 span *trace.Span
108 name string
109 ok bool
110 )
111
112 if cfg.Name != "" {
113 name = cfg.Name
114 } else {
115 name = req.Method + " " + req.URL.Path
116 }
117
118 spanContext, ok = cfg.HTTPPropagate.SpanContextFromRequest(req)
119 if ok && !cfg.Public {
120 ctx, span = trace.StartSpanWithRemoteParent(
121 ctx,
122 name,
123 spanContext,
124 trace.WithSpanKind(trace.SpanKindServer),
125 trace.WithSampler(cfg.Sampler),
126 )
127 } else {
128 ctx, span = trace.StartSpan(
129 ctx,
130 name,
131 trace.WithSpanKind(trace.SpanKindServer),
132 trace.WithSampler(cfg.Sampler),
133 )
134 if ok {
135 span.AddLink(trace.Link{
136 TraceID: spanContext.TraceID,
137 SpanID: spanContext.SpanID,
138 Type: trace.LinkTypeChild,
139 Attributes: nil,
140 })
141 }
142 }
143
144 span.AddAttributes(
145 trace.StringAttribute(ochttp.MethodAttribute, req.Method),
146 trace.StringAttribute(ochttp.PathAttribute, req.URL.Path),
147 )
148
149 return ctx
150 },
151 )
152
153 serverFinalizer := kithttp.ServerFinalizer(
154 func(ctx context.Context, code int, r *http.Request) {
155 if span := trace.FromContext(ctx); span != nil {
156 span.SetStatus(ochttp.TraceStatus(code, http.StatusText(code)))
157
158 if rs, ok := ctx.Value(kithttp.ContextKeyResponseSize).(int64); ok {
159 span.AddAttributes(
160 trace.Int64Attribute("http.response_size", rs),
161 )
162 }
163
164 span.End()
165 }
166 },
167 )
168
169 return func(s *kithttp.Server) {
170 serverBefore(s)
171 serverFinalizer(s)
172 }
173 }
0 package opencensus_test
1
2 import (
3 "context"
4 "errors"
5 "net/http"
6 "net/http/httptest"
7 "net/url"
8 "testing"
9
10 "go.opencensus.io/plugin/ochttp"
11 "go.opencensus.io/plugin/ochttp/propagation/b3"
12 "go.opencensus.io/plugin/ochttp/propagation/tracecontext"
13 "go.opencensus.io/trace"
14 "go.opencensus.io/trace/propagation"
15
16 "github.com/go-kit/kit/endpoint"
17 ockit "github.com/go-kit/kit/tracing/opencensus"
18 kithttp "github.com/go-kit/kit/transport/http"
19 )
20
21 func TestHTTPClientTrace(t *testing.T) {
22 var (
23 err error
24 rec = &recordingExporter{}
25 rURL, _ = url.Parse("https://httpbin.org/get")
26 )
27
28 trace.RegisterExporter(rec)
29
30 traces := []struct {
31 name string
32 err error
33 }{
34 {"", nil},
35 {"CustomName", nil},
36 {"", errors.New("dummy-error")},
37 }
38
39 for _, tr := range traces {
40 clientTracer := ockit.HTTPClientTrace(
41 ockit.WithName(tr.name),
42 ockit.WithSampler(trace.AlwaysSample()),
43 )
44 ep := kithttp.NewClient(
45 "GET",
46 rURL,
47 func(ctx context.Context, r *http.Request, i interface{}) error {
48 return nil
49 },
50 func(ctx context.Context, r *http.Response) (response interface{}, err error) {
51 return nil, tr.err
52 },
53 clientTracer,
54 ).Endpoint()
55
56 ctx, parentSpan := trace.StartSpan(context.Background(), "test")
57
58 _, err = ep(ctx, nil)
59 if want, have := tr.err, err; want != have {
60 t.Fatalf("unexpected error, want %s, have %s", tr.err.Error(), err.Error())
61 }
62
63 spans := rec.Flush()
64 if want, have := 1, len(spans); want != have {
65 t.Fatalf("incorrect number of spans, want %d, have %d", want, have)
66 }
67
68 span := spans[0]
69 if want, have := parentSpan.SpanContext().SpanID, span.ParentSpanID; want != have {
70 t.Errorf("incorrect parent ID, want %s, have %s", want, have)
71 }
72
73 if want, have := tr.name, span.Name; want != have && want != "" {
74 t.Errorf("incorrect span name, want %s, have %s", want, have)
75 }
76
77 if want, have := "GET /get", span.Name; want != have && tr.name == "" {
78 t.Errorf("incorrect span name, want %s, have %s", want, have)
79 }
80
81 code := trace.StatusCodeOK
82 if tr.err != nil {
83 code = trace.StatusCodeUnknown
84
85 if want, have := err.Error(), span.Status.Message; want != have {
86 t.Errorf("incorrect span status msg, want %s, have %s", want, have)
87 }
88 }
89
90 if want, have := int32(code), span.Status.Code; want != have {
91 t.Errorf("incorrect span status code, want %d, have %d", want, have)
92 }
93 }
94 }
95
96 func TestHTTPServerTrace(t *testing.T) {
97 rec := &recordingExporter{}
98
99 trace.RegisterExporter(rec)
100
101 traces := []struct {
102 useParent bool
103 name string
104 err error
105 propagation propagation.HTTPFormat
106 }{
107 {false, "", nil, nil},
108 {true, "", nil, nil},
109 {true, "CustomName", nil, &b3.HTTPFormat{}},
110 {true, "", errors.New("dummy-error"), &tracecontext.HTTPFormat{}},
111 }
112
113 for _, tr := range traces {
114 var client http.Client
115
116 handler := kithttp.NewServer(
117 endpoint.Nop,
118 func(context.Context, *http.Request) (interface{}, error) { return nil, nil },
119 func(context.Context, http.ResponseWriter, interface{}) error { return errors.New("dummy") },
120 ockit.HTTPServerTrace(
121 ockit.WithName(tr.name),
122 ockit.WithSampler(trace.AlwaysSample()),
123 ockit.WithHTTPPropagation(tr.propagation),
124 ),
125 )
126
127 server := httptest.NewServer(handler)
128 defer server.Close()
129
130 const httpMethod = "GET"
131
132 req, err := http.NewRequest(httpMethod, server.URL, nil)
133 if err != nil {
134 t.Fatalf("unable to create HTTP request: %s", err.Error())
135 }
136
137 if tr.useParent {
138 client = http.Client{
139 Transport: &ochttp.Transport{
140 StartOptions: trace.StartOptions{
141 Sampler: trace.AlwaysSample(),
142 },
143 Propagation: tr.propagation,
144 },
145 }
146 }
147
148 resp, err := client.Do(req.WithContext(context.Background()))
149 if err != nil {
150 t.Fatalf("unable to send HTTP request: %s", err.Error())
151 }
152 resp.Body.Close()
153
154 spans := rec.Flush()
155
156 expectedSpans := 1
157 if tr.useParent {
158 expectedSpans++
159 }
160
161 if want, have := expectedSpans, len(spans); want != have {
162 t.Fatalf("incorrect number of spans, want %d, have %d", want, have)
163 }
164
165 if tr.useParent {
166 if want, have := spans[1].TraceID, spans[0].TraceID; want != have {
167 t.Errorf("incorrect trace ID, want %s, have %s", want, have)
168 }
169
170 if want, have := spans[1].SpanID, spans[0].ParentSpanID; want != have {
171 t.Errorf("incorrect span ID, want %s, have %s", want, have)
172 }
173 }
174
175 if want, have := tr.name, spans[0].Name; want != have && want != "" {
176 t.Errorf("incorrect span name, want %s, have %s", want, have)
177 }
178
179 if want, have := "GET /", spans[0].Name; want != have && tr.name == "" {
180 t.Errorf("incorrect span name, want %s, have %s", want, have)
181 }
182 }
183 }
0 package opencensus
1
2 import (
3 "context"
4 "net/http"
5
6 "go.opencensus.io/plugin/ochttp"
7 "go.opencensus.io/plugin/ochttp/propagation/b3"
8 "go.opencensus.io/trace"
9
10 kithttp "github.com/go-kit/kit/transport/http"
11 jsonrpc "github.com/go-kit/kit/transport/http/jsonrpc"
12 )
13
14 // JSONRPCClientTrace enables OpenCensus tracing of a Go kit JSONRPC transport client.
15 func JSONRPCClientTrace(options ...TracerOption) jsonrpc.ClientOption {
16 cfg := TracerOptions{}
17
18 for _, option := range options {
19 option(&cfg)
20 }
21
22 if !cfg.Public && cfg.HTTPPropagate == nil {
23 cfg.HTTPPropagate = &b3.HTTPFormat{}
24 }
25
26 clientBefore := jsonrpc.ClientBefore(
27 func(ctx context.Context, req *http.Request) context.Context {
28 var name string
29
30 if cfg.Name != "" {
31 name = cfg.Name
32 } else {
33 // OpenCensus states Path being default naming for a client span
34 name = ctx.Value(jsonrpc.ContextKeyRequestMethod).(string)
35 }
36
37 ctx, span := trace.StartSpan(
38 ctx,
39 name,
40 trace.WithSampler(cfg.Sampler),
41 trace.WithSpanKind(trace.SpanKindClient),
42 )
43
44 span.AddAttributes(
45 trace.StringAttribute(ochttp.HostAttribute, req.URL.Host),
46 trace.StringAttribute(ochttp.MethodAttribute, req.Method),
47 trace.StringAttribute(ochttp.PathAttribute, req.URL.Path),
48 trace.StringAttribute(ochttp.UserAgentAttribute, req.UserAgent()),
49 )
50
51 if !cfg.Public {
52 cfg.HTTPPropagate.SpanContextToRequest(span.SpanContext(), req)
53 }
54
55 return ctx
56 },
57 )
58
59 clientAfter := jsonrpc.ClientAfter(
60 func(ctx context.Context, res *http.Response) context.Context {
61 if span := trace.FromContext(ctx); span != nil {
62 span.SetStatus(ochttp.TraceStatus(res.StatusCode, http.StatusText(res.StatusCode)))
63 span.AddAttributes(
64 trace.Int64Attribute(ochttp.StatusCodeAttribute, int64(res.StatusCode)),
65 )
66 }
67 return ctx
68 },
69 )
70
71 clientFinalizer := jsonrpc.ClientFinalizer(
72 func(ctx context.Context, err error) {
73 if span := trace.FromContext(ctx); span != nil {
74 if err != nil {
75 span.SetStatus(trace.Status{
76 Code: trace.StatusCodeUnknown,
77 Message: err.Error(),
78 })
79 }
80 span.End()
81 }
82 },
83 )
84
85 return func(c *jsonrpc.Client) {
86 clientBefore(c)
87 clientAfter(c)
88 clientFinalizer(c)
89 }
90 }
91
92 // JSONRPCServerTrace enables OpenCensus tracing of a Go kit JSONRPC transport server.
93 func JSONRPCServerTrace(options ...TracerOption) jsonrpc.ServerOption {
94 cfg := TracerOptions{}
95
96 for _, option := range options {
97 option(&cfg)
98 }
99
100 if !cfg.Public && cfg.HTTPPropagate == nil {
101 cfg.HTTPPropagate = &b3.HTTPFormat{}
102 }
103
104 serverBeforeCodec := jsonrpc.ServerBeforeCodec(
105 func(ctx context.Context, httpReq *http.Request, req jsonrpc.Request) context.Context {
106 var (
107 spanContext trace.SpanContext
108 span *trace.Span
109 name string
110 ok bool
111 )
112
113 if cfg.Name != "" {
114 name = cfg.Name
115 } else {
116 name = ctx.Value(jsonrpc.ContextKeyRequestMethod).(string)
117 if name == "" {
118 // we can't find the rpc method. probably the
119 // unaryInterceptor was not wired up.
120 name = "unknown jsonrpc method"
121 }
122 }
123
124 spanContext, ok = cfg.HTTPPropagate.SpanContextFromRequest(httpReq)
125 if ok && !cfg.Public {
126 ctx, span = trace.StartSpanWithRemoteParent(
127 ctx,
128 name,
129 spanContext,
130 trace.WithSpanKind(trace.SpanKindServer),
131 trace.WithSampler(cfg.Sampler),
132 )
133 } else {
134 ctx, span = trace.StartSpan(
135 ctx,
136 name,
137 trace.WithSpanKind(trace.SpanKindServer),
138 trace.WithSampler(cfg.Sampler),
139 )
140 if ok {
141 span.AddLink(trace.Link{
142 TraceID: spanContext.TraceID,
143 SpanID: spanContext.SpanID,
144 Type: trace.LinkTypeChild,
145 Attributes: nil,
146 })
147 }
148 }
149
150 span.AddAttributes(
151 trace.StringAttribute(ochttp.MethodAttribute, httpReq.Method),
152 trace.StringAttribute(ochttp.PathAttribute, httpReq.URL.Path),
153 )
154
155 return ctx
156 },
157 )
158
159 serverFinalizer := jsonrpc.ServerFinalizer(
160 func(ctx context.Context, code int, r *http.Request) {
161 if span := trace.FromContext(ctx); span != nil {
162 span.SetStatus(ochttp.TraceStatus(code, http.StatusText(code)))
163
164 if rs, ok := ctx.Value(kithttp.ContextKeyResponseSize).(int64); ok {
165 span.AddAttributes(
166 trace.Int64Attribute("http.response_size", rs),
167 )
168 }
169
170 span.End()
171 }
172 },
173 )
174
175 return func(s *jsonrpc.Server) {
176 serverBeforeCodec(s)
177 serverFinalizer(s)
178 }
179 }
0 package opencensus_test
1
2 import (
3 "bytes"
4 "context"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "net/http"
9 "net/http/httptest"
10 "net/url"
11 "testing"
12
13 "go.opencensus.io/plugin/ochttp"
14 "go.opencensus.io/plugin/ochttp/propagation/b3"
15 "go.opencensus.io/plugin/ochttp/propagation/tracecontext"
16 "go.opencensus.io/trace"
17 "go.opencensus.io/trace/propagation"
18
19 "github.com/go-kit/kit/endpoint"
20 ockit "github.com/go-kit/kit/tracing/opencensus"
21 jsonrpc "github.com/go-kit/kit/transport/http/jsonrpc"
22 )
23
24 func TestJSONRPCClientTrace(t *testing.T) {
25 t.Skip("FLAKY")
26
27 var (
28 err error
29 rec = &recordingExporter{}
30 rURL, _ = url.Parse("https://httpbin.org/anything")
31 endpointName = "DummyEndpoint"
32 )
33
34 trace.RegisterExporter(rec)
35
36 traces := []struct {
37 name string
38 err error
39 }{
40 {"", nil},
41 {"CustomName", nil},
42 {"", errors.New("dummy-error")},
43 }
44
45 for _, tr := range traces {
46 clientTracer := ockit.JSONRPCClientTrace(
47 ockit.WithName(tr.name),
48 ockit.WithSampler(trace.AlwaysSample()),
49 )
50 ep := jsonrpc.NewClient(
51 rURL,
52 endpointName,
53 jsonrpc.ClientRequestEncoder(func(ctx context.Context, i interface{}) (json.RawMessage, error) {
54 return json.RawMessage(`{}`), nil
55 }),
56 jsonrpc.ClientResponseDecoder(func(ctx context.Context, r jsonrpc.Response) (response interface{}, err error) {
57 return nil, tr.err
58 }),
59 clientTracer,
60 ).Endpoint()
61
62 ctx, parentSpan := trace.StartSpan(context.Background(), "test")
63
64 _, err = ep(ctx, nil)
65 if want, have := tr.err, err; want != have {
66 t.Fatalf("unexpected error, want %v, have %v", tr.err, err)
67 }
68
69 spans := rec.Flush()
70 if want, have := 1, len(spans); want != have {
71 t.Fatalf("incorrect number of spans, want %d, have %d", want, have)
72 }
73
74 span := spans[0]
75 if want, have := parentSpan.SpanContext().SpanID, span.ParentSpanID; want != have {
76 t.Errorf("incorrect parent ID, want %s, have %s", want, have)
77 }
78
79 if want, have := tr.name, span.Name; want != have && want != "" {
80 t.Errorf("incorrect span name, want %s, have %s", want, have)
81 }
82
83 if want, have := endpointName, span.Name; want != have && tr.name == "" {
84 t.Errorf("incorrect span name, want %s, have %s", want, have)
85 }
86
87 code := trace.StatusCodeOK
88 if tr.err != nil {
89 code = trace.StatusCodeUnknown
90
91 if want, have := err.Error(), span.Status.Message; want != have {
92 t.Errorf("incorrect span status msg, want %s, have %s", want, have)
93 }
94 }
95
96 if want, have := int32(code), span.Status.Code; want != have {
97 t.Errorf("incorrect span status code, want %d, have %d", want, have)
98 }
99 }
100 }
101
102 func TestJSONRPCServerTrace(t *testing.T) {
103 var (
104 endpointName = "DummyEndpoint"
105 rec = &recordingExporter{}
106 )
107
108 trace.RegisterExporter(rec)
109
110 traces := []struct {
111 useParent bool
112 name string
113 err error
114 propagation propagation.HTTPFormat
115 }{
116 {false, "", nil, nil},
117 {true, "", nil, nil},
118 {true, "CustomName", nil, &b3.HTTPFormat{}},
119 {true, "", errors.New("dummy-error"), &tracecontext.HTTPFormat{}},
120 }
121
122 for _, tr := range traces {
123 var client http.Client
124
125 handler := jsonrpc.NewServer(
126 jsonrpc.EndpointCodecMap{
127 endpointName: jsonrpc.EndpointCodec{
128 Endpoint: endpoint.Nop,
129 Decode: func(context.Context, json.RawMessage) (interface{}, error) { return nil, nil },
130 Encode: func(context.Context, interface{}) (json.RawMessage, error) { return nil, tr.err },
131 },
132 },
133 ockit.JSONRPCServerTrace(
134 ockit.WithName(tr.name),
135 ockit.WithSampler(trace.AlwaysSample()),
136 ockit.WithHTTPPropagation(tr.propagation),
137 ),
138 )
139
140 server := httptest.NewServer(handler)
141 defer server.Close()
142
143 jsonStr := []byte(fmt.Sprintf(`{"method":"%s"}`, endpointName))
144 req, err := http.NewRequest("POST", server.URL, bytes.NewBuffer(jsonStr))
145 if err != nil {
146 t.Fatalf("unable to create JSONRPC request: %v", err)
147 }
148
149 if tr.useParent {
150 client = http.Client{
151 Transport: &ochttp.Transport{
152 StartOptions: trace.StartOptions{
153 Sampler: trace.AlwaysSample(),
154 },
155 Propagation: tr.propagation,
156 },
157 }
158 }
159
160 resp, err := client.Do(req.WithContext(context.Background()))
161 if err != nil {
162 t.Fatalf("unable to send JSONRPC request: %v", err)
163 }
164 resp.Body.Close()
165
166 spans := rec.Flush()
167
168 expectedSpans := 1
169 if tr.useParent {
170 expectedSpans++
171 }
172
173 if want, have := expectedSpans, len(spans); want != have {
174 t.Fatalf("incorrect number of spans, want %d, have %d", want, have)
175 }
176
177 if tr.useParent {
178 if want, have := spans[1].TraceID, spans[0].TraceID; want != have {
179 t.Errorf("incorrect trace ID, want %s, have %s", want, have)
180 }
181
182 if want, have := spans[1].SpanID, spans[0].ParentSpanID; want != have {
183 t.Errorf("incorrect span ID, want %s, have %s", want, have)
184 }
185 }
186
187 if want, have := tr.name, spans[0].Name; want != have && want != "" {
188 t.Errorf("incorrect span name, want %s, have %s", want, have)
189 }
190
191 if want, have := endpointName, spans[0].Name; want != have && tr.name == "" {
192 t.Errorf("incorrect span name, want %s, have %s", want, have)
193 }
194 }
195 }
0 package opencensus_test
1
2 import (
3 "sync"
4
5 "go.opencensus.io/trace"
6 )
7
8 type recordingExporter struct {
9 mu sync.Mutex
10 data []*trace.SpanData
11 }
12
13 func (e *recordingExporter) ExportSpan(d *trace.SpanData) {
14 e.mu.Lock()
15 defer e.mu.Unlock()
16
17 e.data = append(e.data, d)
18 }
19
20 func (e *recordingExporter) Flush() (data []*trace.SpanData) {
21 e.mu.Lock()
22 defer e.mu.Unlock()
23
24 data = e.data
25 e.data = nil
26 return
27 }
0 package opencensus
1
2 import (
3 "go.opencensus.io/plugin/ochttp/propagation/b3"
4 "go.opencensus.io/trace"
5 "go.opencensus.io/trace/propagation"
6 )
7
8 // defaultHTTPPropagate holds OpenCensus' default HTTP propagation format which
9 // currently is Zipkin's B3.
10 var defaultHTTPPropagate propagation.HTTPFormat = &b3.HTTPFormat{}
11
12 // TracerOption allows for functional options to our OpenCensus tracing
13 // middleware.
14 type TracerOption func(o *TracerOptions)
15
16 // WithTracerConfig sets all configuration options at once.
17 func WithTracerConfig(options TracerOptions) TracerOption {
18 return func(o *TracerOptions) {
19 *o = options
20 }
21 }
22
23 // WithSampler sets the sampler to use by our OpenCensus Tracer.
24 func WithSampler(sampler trace.Sampler) TracerOption {
25 return func(o *TracerOptions) {
26 o.Sampler = sampler
27 }
28 }
29
30 // WithName sets the name for an instrumented transport endpoint. If name is omitted
31 // at tracing middleware creation, the method of the transport or transport rpc
32 // name is used.
33 func WithName(name string) TracerOption {
34 return func(o *TracerOptions) {
35 o.Name = name
36 }
37 }
38
39 // IsPublic should be set to true for publicly accessible servers and for
40 // clients that should not propagate their current trace metadata.
41 // On the server side a new trace will always be started regardless of any
42 // trace metadata being found in the incoming request. If any trace metadata
43 // is found, it will be added as a linked trace instead.
44 func IsPublic(isPublic bool) TracerOption {
45 return func(o *TracerOptions) {
46 o.Public = isPublic
47 }
48 }
49
50 // WithHTTPPropagation sets the propagation handlers for the HTTP transport
51 // middlewares. If used on a non HTTP transport this is a noop.
52 func WithHTTPPropagation(p propagation.HTTPFormat) TracerOption {
53 return func(o *TracerOptions) {
54 if p == nil {
55 // reset to default OC HTTP format
56 o.HTTPPropagate = defaultHTTPPropagate
57 return
58 }
59 o.HTTPPropagate = p
60 }
61 }
62
63 // TracerOptions holds configuration for our tracing middlewares
64 type TracerOptions struct {
65 Sampler trace.Sampler
66 Name string
67 Public bool
68 HTTPPropagate propagation.HTTPFormat
69 }
11
22 import (
33 "context"
4 "strconv"
45
56 "github.com/opentracing/opentracing-go"
67 otext "github.com/opentracing/opentracing-go/ext"
8 otlog "github.com/opentracing/opentracing-go/log"
79
810 "github.com/go-kit/kit/endpoint"
11 "github.com/go-kit/kit/sd/lb"
912 )
1013
11 // TraceServer returns a Middleware that wraps the `next` Endpoint in an
14 // TraceEndpoint returns a Middleware that wraps the `next` Endpoint in an
1215 // OpenTracing Span called `operationName`.
1316 //
14 // If `ctx` already has a Span, it is re-used and the operation name is
15 // overwritten. If `ctx` does not yet have a Span, one is created here.
16 func TraceServer(tracer opentracing.Tracer, operationName string) endpoint.Middleware {
17 // If `ctx` already has a Span, child span is created from it.
18 // If `ctx` doesn't yet have a Span, the new one is created.
19 func TraceEndpoint(tracer opentracing.Tracer, operationName string, opts ...EndpointOption) endpoint.Middleware {
20 cfg := &EndpointOptions{
21 Tags: make(opentracing.Tags),
22 }
23
24 for _, opt := range opts {
25 opt(cfg)
26 }
27
1728 return func(next endpoint.Endpoint) endpoint.Endpoint {
18 return func(ctx context.Context, request interface{}) (interface{}, error) {
19 serverSpan := opentracing.SpanFromContext(ctx)
20 if serverSpan == nil {
21 // All we can do is create a new root span.
22 serverSpan = tracer.StartSpan(operationName)
29 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
30 if cfg.GetOperationName != nil {
31 if newOperationName := cfg.GetOperationName(ctx, operationName); newOperationName != "" {
32 operationName = newOperationName
33 }
34 }
35
36 var span opentracing.Span
37 if parentSpan := opentracing.SpanFromContext(ctx); parentSpan != nil {
38 span = tracer.StartSpan(
39 operationName,
40 opentracing.ChildOf(parentSpan.Context()),
41 )
2342 } else {
24 serverSpan.SetOperationName(operationName)
43 span = tracer.StartSpan(operationName)
2544 }
26 defer serverSpan.Finish()
27 otext.SpanKindRPCServer.Set(serverSpan)
28 ctx = opentracing.ContextWithSpan(ctx, serverSpan)
45 defer span.Finish()
46
47 applyTags(span, cfg.Tags)
48 if cfg.GetTags != nil {
49 extraTags := cfg.GetTags(ctx)
50 applyTags(span, extraTags)
51 }
52
53 ctx = opentracing.ContextWithSpan(ctx, span)
54
55 defer func() {
56 if err != nil {
57 if lbErr, ok := err.(lb.RetryError); ok {
58 // handle errors originating from lb.Retry
59 fields := make([]otlog.Field, 0, len(lbErr.RawErrors))
60 for idx, rawErr := range lbErr.RawErrors {
61 fields = append(fields, otlog.String(
62 "gokit.retry.error."+strconv.Itoa(idx+1), rawErr.Error(),
63 ))
64 }
65
66 otext.LogError(span, lbErr, fields...)
67
68 return
69 }
70
71 // generic error
72 otext.LogError(span, err)
73
74 return
75 }
76
77 // test for business error
78 if res, ok := response.(endpoint.Failer); ok && res.Failed() != nil {
79 span.LogFields(
80 otlog.String("gokit.business.error", res.Failed().Error()),
81 )
82
83 if cfg.IgnoreBusinessError {
84 return
85 }
86
87 // treating business error as real error in span.
88 otext.LogError(span, res.Failed())
89
90 return
91 }
92 }()
93
2994 return next(ctx, request)
3095 }
3196 }
3297 }
3398
99 // TraceServer returns a Middleware that wraps the `next` Endpoint in an
100 // OpenTracing Span called `operationName` with server span.kind tag..
101 func TraceServer(tracer opentracing.Tracer, operationName string, opts ...EndpointOption) endpoint.Middleware {
102 opts = append(opts, WithTags(map[string]interface{}{
103 otext.SpanKindRPCServer.Key: otext.SpanKindRPCServer.Value,
104 }))
105
106 return TraceEndpoint(tracer, operationName, opts...)
107 }
108
34109 // TraceClient returns a Middleware that wraps the `next` Endpoint in an
35 // OpenTracing Span called `operationName`.
36 func TraceClient(tracer opentracing.Tracer, operationName string) endpoint.Middleware {
37 return func(next endpoint.Endpoint) endpoint.Endpoint {
38 return func(ctx context.Context, request interface{}) (interface{}, error) {
39 var clientSpan opentracing.Span
40 if parentSpan := opentracing.SpanFromContext(ctx); parentSpan != nil {
41 clientSpan = tracer.StartSpan(
42 operationName,
43 opentracing.ChildOf(parentSpan.Context()),
44 )
45 } else {
46 clientSpan = tracer.StartSpan(operationName)
47 }
48 defer clientSpan.Finish()
49 otext.SpanKindRPCClient.Set(clientSpan)
50 ctx = opentracing.ContextWithSpan(ctx, clientSpan)
51 return next(ctx, request)
52 }
110 // OpenTracing Span called `operationName` with client span.kind tag.
111 func TraceClient(tracer opentracing.Tracer, operationName string, opts ...EndpointOption) endpoint.Middleware {
112 opts = append(opts, WithTags(map[string]interface{}{
113 otext.SpanKindRPCClient.Key: otext.SpanKindRPCClient.Value,
114 }))
115
116 return TraceEndpoint(tracer, operationName, opts...)
117 }
118
119 func applyTags(span opentracing.Span, tags opentracing.Tags) {
120 for key, value := range tags {
121 span.SetTag(key, value)
53122 }
54123 }
0 package opentracing
1
2 import (
3 "context"
4
5 "github.com/opentracing/opentracing-go"
6 )
7
8 // EndpointOptions holds the options for tracing an endpoint
9 type EndpointOptions struct {
10 // IgnoreBusinessError if set to true will not treat a business error
11 // identified through the endpoint.Failer interface as a span error.
12 IgnoreBusinessError bool
13
14 // GetOperationName is an optional function that can set the span operation name based on the existing one
15 // for the endpoint and information in the context.
16 //
17 // If the function is nil, or the returned name is empty, the existing name for the endpoint is used.
18 GetOperationName func(ctx context.Context, name string) string
19
20 // Tags holds the default tags which will be set on span
21 // creation by our Endpoint middleware.
22 Tags opentracing.Tags
23
24 // GetTags is an optional function that can extract tags
25 // from the context and add them to the span.
26 GetTags func(ctx context.Context) opentracing.Tags
27 }
28
29 // EndpointOption allows for functional options to endpoint tracing middleware.
30 type EndpointOption func(*EndpointOptions)
31
32 // WithOptions sets all configuration options at once by use of the EndpointOptions struct.
33 func WithOptions(options EndpointOptions) EndpointOption {
34 return func(o *EndpointOptions) {
35 *o = options
36 }
37 }
38
39 // WithIgnoreBusinessError if set to true will not treat a business error
40 // identified through the endpoint.Failer interface as a span error.
41 func WithIgnoreBusinessError(ignoreBusinessError bool) EndpointOption {
42 return func(o *EndpointOptions) {
43 o.IgnoreBusinessError = ignoreBusinessError
44 }
45 }
46
47 // WithOperationNameFunc allows to set function that can set the span operation name based on the existing one
48 // for the endpoint and information in the context.
49 func WithOperationNameFunc(getOperationName func(ctx context.Context, name string) string) EndpointOption {
50 return func(o *EndpointOptions) {
51 o.GetOperationName = getOperationName
52 }
53 }
54
55 // WithTags adds default tags for the spans created by the Endpoint tracer.
56 func WithTags(tags opentracing.Tags) EndpointOption {
57 return func(o *EndpointOptions) {
58 if o.Tags == nil {
59 o.Tags = make(opentracing.Tags)
60 }
61
62 for key, value := range tags {
63 o.Tags[key] = value
64 }
65 }
66 }
67
68 // WithTagsFunc set the func to extracts additional tags from the context.
69 func WithTagsFunc(getTags func(ctx context.Context) opentracing.Tags) EndpointOption {
70 return func(o *EndpointOptions) {
71 o.GetTags = getTags
72 }
73 }
11
22 import (
33 "context"
4 "errors"
5 "fmt"
6 "reflect"
47 "testing"
8 "time"
59
610 "github.com/opentracing/opentracing-go"
11 otext "github.com/opentracing/opentracing-go/ext"
12 otlog "github.com/opentracing/opentracing-go/log"
713 "github.com/opentracing/opentracing-go/mocktracer"
814
915 "github.com/go-kit/kit/endpoint"
16 "github.com/go-kit/kit/sd"
17 "github.com/go-kit/kit/sd/lb"
1018 kitot "github.com/go-kit/kit/tracing/opentracing"
1119 )
1220
13 func TestTraceServer(t *testing.T) {
21 const (
22 span1 = "SPAN-1"
23 span2 = "SPAN-2"
24 span3 = "SPAN-3"
25 span4 = "SPAN-4"
26 span5 = "SPAN-5"
27 span6 = "SPAN-6"
28 span7 = "SPAN-7"
29 span8 = "SPAN-8"
30 )
31
32 var (
33 err1 = errors.New("some error")
34 err2 = errors.New("some business error")
35 err3 = errors.New("other business error")
36 )
37
38 // compile time assertion
39 var _ endpoint.Failer = failedResponse{}
40
41 type failedResponse struct {
42 err error
43 }
44
45 func (r failedResponse) Failed() error {
46 return r.err
47 }
48
49 func TestTraceEndpoint(t *testing.T) {
1450 tracer := mocktracer.New()
1551
16 // Initialize the ctx with a nameless Span.
17 contextSpan := tracer.StartSpan("").(*mocktracer.MockSpan)
18 ctx := opentracing.ContextWithSpan(context.Background(), contextSpan)
19
20 tracedEndpoint := kitot.TraceServer(tracer, "testOp")(endpoint.Nop)
52 // Initialize the ctx with a parent Span.
53 parentSpan := tracer.StartSpan("parent").(*mocktracer.MockSpan)
54 defer parentSpan.Finish()
55 ctx := opentracing.ContextWithSpan(context.Background(), parentSpan)
56
57 tracedEndpoint := kitot.TraceEndpoint(tracer, "testOp")(endpoint.Nop)
2158 if _, err := tracedEndpoint(ctx, struct{}{}); err != nil {
2259 t.Fatal(err)
2360 }
2461
62 // tracedEndpoint created a new Span.
2563 finishedSpans := tracer.FinishedSpans()
2664 if want, have := 1, len(finishedSpans); want != have {
2765 t.Fatalf("Want %v span(s), found %v", want, have)
2866 }
2967
30 // Test that the op name is updated
3168 endpointSpan := finishedSpans[0]
3269 if want, have := "testOp", endpointSpan.OperationName; want != have {
3370 t.Fatalf("Want %q, have %q", want, have)
3471 }
35 contextContext := contextSpan.Context().(mocktracer.MockSpanContext)
36 endpointContext := endpointSpan.Context().(mocktracer.MockSpanContext)
37 // ...and that the ID is unmodified.
38 if want, have := contextContext.SpanID, endpointContext.SpanID; want != have {
39 t.Errorf("Want SpanID %q, have %q", want, have)
40 }
41 }
42
43 func TestTraceServerNoContextSpan(t *testing.T) {
72
73 parentContext := parentSpan.Context().(mocktracer.MockSpanContext)
74 endpointContext := parentSpan.Context().(mocktracer.MockSpanContext)
75
76 // ... and that the parent ID is set appropriately.
77 if want, have := parentContext.SpanID, endpointContext.SpanID; want != have {
78 t.Errorf("Want ParentID %q, have %q", want, have)
79 }
80 }
81
82 func TestTraceEndpointNoContextSpan(t *testing.T) {
83 tracer := mocktracer.New()
84
85 // Empty/background context.
86 tracedEndpoint := kitot.TraceEndpoint(tracer, "testOp")(endpoint.Nop)
87 if _, err := tracedEndpoint(context.Background(), struct{}{}); err != nil {
88 t.Fatal(err)
89 }
90
91 // tracedEndpoint created a new Span.
92 finishedSpans := tracer.FinishedSpans()
93 if want, have := 1, len(finishedSpans); want != have {
94 t.Fatalf("Want %v span(s), found %v", want, have)
95 }
96
97 endpointSpan := finishedSpans[0]
98
99 if want, have := "testOp", endpointSpan.OperationName; want != have {
100 t.Fatalf("Want %q, have %q", want, have)
101 }
102 }
103
104 func TestTraceEndpointWithOptions(t *testing.T) {
105 tracer := mocktracer.New()
106
107 // span 1 without options
108 mw := kitot.TraceEndpoint(tracer, span1)
109 tracedEndpoint := mw(endpoint.Nop)
110 _, _ = tracedEndpoint(context.Background(), struct{}{})
111
112 // span 2 with options
113 mw = kitot.TraceEndpoint(
114 tracer,
115 span2,
116 kitot.WithOptions(kitot.EndpointOptions{}),
117 )
118 tracedEndpoint = mw(func(context.Context, interface{}) (interface{}, error) {
119 return nil, err1
120 })
121 _, _ = tracedEndpoint(context.Background(), struct{}{})
122
123 // span 3 with lb error
124 mw = kitot.TraceEndpoint(
125 tracer,
126 span3,
127 kitot.WithOptions(kitot.EndpointOptions{}),
128 )
129 tracedEndpoint = mw(
130 lb.Retry(
131 5,
132 1*time.Second,
133 lb.NewRoundRobin(
134 sd.FixedEndpointer{
135 func(context.Context, interface{}) (interface{}, error) {
136 return nil, err1
137 },
138 },
139 ),
140 ),
141 )
142 _, _ = tracedEndpoint(context.Background(), struct{}{})
143
144 // span 4 with disabled IgnoreBusinessError option
145 mw = kitot.TraceEndpoint(
146 tracer,
147 span4,
148 kitot.WithIgnoreBusinessError(false),
149 )
150 tracedEndpoint = mw(func(context.Context, interface{}) (interface{}, error) {
151 return failedResponse{
152 err: err2,
153 }, nil
154 })
155 _, _ = tracedEndpoint(context.Background(), struct{}{})
156
157 // span 5 with enabled IgnoreBusinessError option
158 mw = kitot.TraceEndpoint(tracer, span5, kitot.WithIgnoreBusinessError(true))
159 tracedEndpoint = mw(func(context.Context, interface{}) (interface{}, error) {
160 return failedResponse{
161 err: err3,
162 }, nil
163 })
164 _, _ = tracedEndpoint(context.Background(), struct{}{})
165
166 // span 6 with OperationNameFunc option
167 mw = kitot.TraceEndpoint(
168 tracer,
169 span6,
170 kitot.WithOperationNameFunc(func(ctx context.Context, name string) string {
171 return fmt.Sprintf("%s-%s", "new", name)
172 }),
173 )
174 tracedEndpoint = mw(endpoint.Nop)
175 _, _ = tracedEndpoint(context.Background(), struct{}{})
176
177 // span 7 with Tags options
178 mw = kitot.TraceEndpoint(
179 tracer,
180 span7,
181 kitot.WithTags(map[string]interface{}{
182 "tag1": "tag1",
183 "tag2": "tag2",
184 }),
185 kitot.WithTags(map[string]interface{}{
186 "tag3": "tag3",
187 }),
188 )
189 tracedEndpoint = mw(endpoint.Nop)
190 _, _ = tracedEndpoint(context.Background(), struct{}{})
191
192 // span 8 with TagsFunc options
193 mw = kitot.TraceEndpoint(
194 tracer,
195 span8,
196 kitot.WithTags(map[string]interface{}{
197 "tag1": "tag1",
198 "tag2": "tag2",
199 }),
200 kitot.WithTags(map[string]interface{}{
201 "tag3": "tag3",
202 }),
203 kitot.WithTagsFunc(func(ctx context.Context) opentracing.Tags {
204 return map[string]interface{}{
205 "tag4": "tag4",
206 }
207 }),
208 )
209 tracedEndpoint = mw(endpoint.Nop)
210 _, _ = tracedEndpoint(context.Background(), struct{}{})
211
212 finishedSpans := tracer.FinishedSpans()
213 if want, have := 8, len(finishedSpans); want != have {
214 t.Fatalf("Want %v span(s), found %v", want, have)
215 }
216
217 // test span 1
218 span := finishedSpans[0]
219
220 if want, have := span1, span.OperationName; want != have {
221 t.Fatalf("Want %q, have %q", want, have)
222 }
223
224 // test span 2
225 span = finishedSpans[1]
226
227 if want, have := span2, span.OperationName; want != have {
228 t.Fatalf("Want %q, have %q", want, have)
229 }
230
231 if want, have := true, span.Tag("error"); want != have {
232 t.Fatalf("Want %v, have %v", want, have)
233 }
234
235 // test span 3
236 span = finishedSpans[2]
237
238 if want, have := span3, span.OperationName; want != have {
239 t.Fatalf("Want %q, have %q", want, have)
240 }
241
242 if want, have := true, span.Tag("error"); want != have {
243 t.Fatalf("Want %v, have %v", want, have)
244 }
245
246 if want, have := 1, len(span.Logs()); want != have {
247 t.Fatalf("incorrect logs count, wanted %d, got %d", want, have)
248 }
249
250 if want, have := []otlog.Field{
251 otlog.String("event", "error"),
252 otlog.String("error.object", "some error (previously: some error; some error; some error; some error)"),
253 otlog.String("gokit.retry.error.1", "some error"),
254 otlog.String("gokit.retry.error.2", "some error"),
255 otlog.String("gokit.retry.error.3", "some error"),
256 otlog.String("gokit.retry.error.4", "some error"),
257 otlog.String("gokit.retry.error.5", "some error"),
258 }, span.Logs()[0].Fields; reflect.DeepEqual(want, have) {
259 t.Fatalf("Want %q, have %q", want, have)
260 }
261
262 // test span 4
263 span = finishedSpans[3]
264
265 if want, have := span4, span.OperationName; want != have {
266 t.Fatalf("Want %q, have %q", want, have)
267 }
268
269 if want, have := true, span.Tag("error"); want != have {
270 t.Fatalf("Want %v, have %v", want, have)
271 }
272
273 if want, have := 2, len(span.Logs()); want != have {
274 t.Fatalf("incorrect logs count, wanted %d, got %d", want, have)
275 }
276
277 if want, have := []otlog.Field{
278 otlog.String("gokit.business.error", "some business error"),
279 }, span.Logs()[0].Fields; reflect.DeepEqual(want, have) {
280 t.Fatalf("Want %q, have %q", want, have)
281 }
282
283 if want, have := []otlog.Field{
284 otlog.String("event", "error"),
285 otlog.String("error.object", "some business error"),
286 }, span.Logs()[1].Fields; reflect.DeepEqual(want, have) {
287 t.Fatalf("Want %q, have %q", want, have)
288 }
289
290 // test span 5
291 span = finishedSpans[4]
292
293 if want, have := span5, span.OperationName; want != have {
294 t.Fatalf("Want %q, have %q", want, have)
295 }
296
297 if want, have := (interface{})(nil), span.Tag("error"); want != have {
298 t.Fatalf("Want %q, have %q", want, have)
299 }
300
301 if want, have := 1, len(span.Logs()); want != have {
302 t.Fatalf("incorrect logs count, wanted %d, got %d", want, have)
303 }
304
305 if want, have := []otlog.Field{
306 otlog.String("gokit.business.error", "some business error"),
307 }, span.Logs()[0].Fields; reflect.DeepEqual(want, have) {
308 t.Fatalf("Want %q, have %q", want, have)
309 }
310
311 // test span 6
312 span = finishedSpans[5]
313
314 if want, have := fmt.Sprintf("%s-%s", "new", span6), span.OperationName; want != have {
315 t.Fatalf("Want %q, have %q", want, have)
316 }
317
318 // test span 7
319 span = finishedSpans[6]
320
321 if want, have := span7, span.OperationName; want != have {
322 t.Fatalf("Want %q, have %q", want, have)
323 }
324
325 if want, have := map[string]interface{}{
326 "tag1": "tag1",
327 "tag2": "tag2",
328 "tag3": "tag3",
329 }, span.Tags(); fmt.Sprint(want) != fmt.Sprint(have) {
330 t.Fatalf("Want %q, have %q", want, have)
331 }
332
333 // test span 8
334 span = finishedSpans[7]
335
336 if want, have := span8, span.OperationName; want != have {
337 t.Fatalf("Want %q, have %q", want, have)
338 }
339
340 if want, have := map[string]interface{}{
341 "tag1": "tag1",
342 "tag2": "tag2",
343 "tag3": "tag3",
344 "tag4": "tag4",
345 }, span.Tags(); fmt.Sprint(want) != fmt.Sprint(have) {
346 t.Fatalf("Want %q, have %q", want, have)
347 }
348 }
349
350 func TestTraceServer(t *testing.T) {
44351 tracer := mocktracer.New()
45352
46353 // Empty/background context.
55362 t.Fatalf("Want %v span(s), found %v", want, have)
56363 }
57364
58 endpointSpan := finishedSpans[0]
59 if want, have := "testOp", endpointSpan.OperationName; want != have {
365 span := finishedSpans[0]
366
367 if want, have := "testOp", span.OperationName; want != have {
368 t.Fatalf("Want %q, have %q", want, have)
369 }
370
371 if want, have := map[string]interface{}{
372 otext.SpanKindRPCServer.Key: otext.SpanKindRPCServer.Value,
373 }, span.Tags(); fmt.Sprint(want) != fmt.Sprint(have) {
60374 t.Fatalf("Want %q, have %q", want, have)
61375 }
62376 }
64378 func TestTraceClient(t *testing.T) {
65379 tracer := mocktracer.New()
66380
67 // Initialize the ctx with a parent Span.
68 parentSpan := tracer.StartSpan("parent").(*mocktracer.MockSpan)
69 defer parentSpan.Finish()
70 ctx := opentracing.ContextWithSpan(context.Background(), parentSpan)
71
381 // Empty/background context.
72382 tracedEndpoint := kitot.TraceClient(tracer, "testOp")(endpoint.Nop)
73 if _, err := tracedEndpoint(ctx, struct{}{}); err != nil {
383 if _, err := tracedEndpoint(context.Background(), struct{}{}); err != nil {
74384 t.Fatal(err)
75385 }
76386
80390 t.Fatalf("Want %v span(s), found %v", want, have)
81391 }
82392
83 endpointSpan := finishedSpans[0]
84 if want, have := "testOp", endpointSpan.OperationName; want != have {
85 t.Fatalf("Want %q, have %q", want, have)
86 }
87
88 parentContext := parentSpan.Context().(mocktracer.MockSpanContext)
89 endpointContext := parentSpan.Context().(mocktracer.MockSpanContext)
90
91 // ... and that the parent ID is set appropriately.
92 if want, have := parentContext.SpanID, endpointContext.SpanID; want != have {
93 t.Errorf("Want ParentID %q, have %q", want, have)
94 }
95 }
96
97 func TestTraceClientNoContextSpan(t *testing.T) {
98 tracer := mocktracer.New()
99
100 // Empty/background context.
101 tracedEndpoint := kitot.TraceClient(tracer, "testOp")(endpoint.Nop)
102 if _, err := tracedEndpoint(context.Background(), struct{}{}); err != nil {
103 t.Fatal(err)
104 }
105
106 // tracedEndpoint created a new Span.
107 finishedSpans := tracer.FinishedSpans()
108 if want, have := 1, len(finishedSpans); want != have {
109 t.Fatalf("Want %v span(s), found %v", want, have)
110 }
111
112 endpointSpan := finishedSpans[0]
113 if want, have := "testOp", endpointSpan.OperationName; want != have {
114 t.Fatalf("Want %q, have %q", want, have)
115 }
116 }
393 span := finishedSpans[0]
394
395 if want, have := "testOp", span.OperationName; want != have {
396 t.Fatalf("Want %q, have %q", want, have)
397 }
398
399 if want, have := map[string]interface{}{
400 otext.SpanKindRPCClient.Key: otext.SpanKindRPCClient.Value,
401 }, span.Tags(); fmt.Sprint(want) != fmt.Sprint(have) {
402 t.Fatalf("Want %q, have %q", want, have)
403 }
404 }
88 "github.com/opentracing/opentracing-go/ext"
99 "google.golang.org/grpc/metadata"
1010
11 "github.com/go-kit/kit/log"
11 "github.com/go-kit/log"
1212 )
1313
1414 // ContextToGRPC returns a grpc RequestFunc that injects an OpenTracing Span
5252 func (w metadataReaderWriter) Set(key, val string) {
5353 key = strings.ToLower(key)
5454 if strings.HasSuffix(key, "-bin") {
55 val = string(base64.StdEncoding.EncodeToString([]byte(val)))
55 val = base64.StdEncoding.EncodeToString([]byte(val))
5656 }
5757 (*w.MD)[key] = append((*w.MD)[key], val)
5858 }
77 "github.com/opentracing/opentracing-go/mocktracer"
88 "google.golang.org/grpc/metadata"
99
10 "github.com/go-kit/kit/log"
1110 kitot "github.com/go-kit/kit/tracing/opentracing"
11 "github.com/go-kit/log"
1212 )
1313
1414 func TestTraceGRPCRequestRoundtrip(t *testing.T) {
55 "net/http"
66 "strconv"
77
8 "github.com/opentracing/opentracing-go"
8 opentracing "github.com/opentracing/opentracing-go"
99 "github.com/opentracing/opentracing-go/ext"
1010
11 "github.com/go-kit/kit/log"
1211 kithttp "github.com/go-kit/kit/transport/http"
12 "github.com/go-kit/log"
1313 )
1414
1515 // ContextToHTTP returns an http RequestFunc that injects an OpenTracing Span
2525 host, portString, err := net.SplitHostPort(req.URL.Host)
2626 if err == nil {
2727 ext.PeerHostname.Set(span, host)
28 if port, err := strconv.Atoi(portString); err != nil {
28 if port, err := strconv.Atoi(portString); err == nil {
2929 ext.PeerPort.Set(span, uint16(port))
3030 }
3131 } else {
3535 // There's nothing we can do with any errors here.
3636 if err = tracer.Inject(
3737 span.Context(),
38 opentracing.TextMap,
38 opentracing.HTTPHeaders,
3939 opentracing.HTTPHeadersCarrier(req.Header),
4040 ); err != nil {
4141 logger.Log("err", err)
5555 // Try to join to a trace propagated in `req`.
5656 var span opentracing.Span
5757 wireContext, err := tracer.Extract(
58 opentracing.TextMap,
58 opentracing.HTTPHeaders,
5959 opentracing.HTTPHeadersCarrier(req.Header),
6060 )
6161 if err != nil && err != opentracing.ErrSpanContextNotFound {
55 "reflect"
66 "testing"
77
8 "github.com/opentracing/opentracing-go"
8 opentracing "github.com/opentracing/opentracing-go"
99 "github.com/opentracing/opentracing-go/ext"
1010 "github.com/opentracing/opentracing-go/mocktracer"
1111
12 "github.com/go-kit/kit/log"
1312 kitot "github.com/go-kit/kit/tracing/opentracing"
13 "github.com/go-kit/log"
1414 )
1515
1616 func TestTraceHTTPRequestRoundtrip(t *testing.T) {
8888 parentSpan := tracer.StartSpan("to_extract").(*mocktracer.MockSpan)
8989 defer parentSpan.Finish()
9090 req, _ := http.NewRequest("GET", "http://test.biz/path", nil)
91 tracer.Inject(parentSpan.Context(), opentracing.TextMap, opentracing.HTTPHeadersCarrier(req.Header))
91 tracer.Inject(parentSpan.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
9292
9393 ctx := kitot.HTTPToContext(tracer, "op", log.NewNopLogger())(context.Background(), req)
9494 opentracing.SpanFromContext(ctx).Finish()
44 Great efforts have been made to make [Zipkin] easier to test, develop and
55 experiment against. [Zipkin] can now be run from a single Docker container or by
66 running its self-contained executable jar without extensive configuration. In
7 its default configuration you will run Zipkin with a HTTP collector, In memory
7 its default configuration you will run [Zipkin] with a HTTP collector, In memory
88 Span storage backend and web UI on port 9411.
99
1010 Example:
1414
1515 [zipkin]: http://zipkin.io
1616
17 Instrumenting your services with Zipkin distributed tracing using the default
18 configuration is now possible with the latest release of [zipkin-go-opentracing]
19 as it includes an HTTP transport for sending spans to the [Zipkin] HTTP
20 Collector.
21
2217 ## Middleware Usage
2318
24 Follow the [addsvc] example to check out how to wire the Zipkin Middleware. The
25 changes should be relatively minor.
19 Follow the [addsvc] example to check out how to wire the [Zipkin] Middleware.
20 The changes should be relatively minor.
2621
27 The [zipkin-go-opentracing] package has support for HTTP, Kafka and Scribe
28 collectors as well as using Go Kit's [Log] package for logging.
22 The [zipkin-go] package has Reporters to send Spans to the [Zipkin] HTTP and
23 Kafka Collectors.
2924
30 ### Configuring for the Zipkin HTTP Collector
25 ### Configuring the Zipkin HTTP Reporter
3126
32 To select the transport for the HTTP Collector, you configure the `Recorder`
33 with the appropriate collector like this:
27 To use the HTTP Reporter with a [Zipkin] instance running on localhost you
28 bootstrap [zipkin-go] like this:
3429
3530 ```go
3631 var (
37 debugMode = false
3832 serviceName = "MyService"
3933 serviceHostPort = "localhost:8000"
40 zipkinHTTPEndpoint = "localhost:9411"
34 zipkinHTTPEndpoint = "http://localhost:9411/api/v2/spans"
4135 )
42 collector, err = zipkin.NewHTTPCollector(zipkinHTTPEndpoint)
43 if err != nil {
44 // handle error
45 }
46 tracer, err = zipkin.NewTracer(
47 zipkin.NewRecorder(collector, debugMode, serviceHostPort, serviceName),
36
37 // create an instance of the HTTP Reporter.
38 reporter := zipkin.NewReporter(zipkinHTTPEndpoint)
39
40 // create our tracer's local endpoint (how the service is identified in Zipkin).
41 localEndpoint, err := zipkin.NewEndpoint(serviceName, serviceHostPort)
42
43 // create our tracer instance.
44 tracer, err = zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(localEndpoint))
4845 ...
49 )
46
5047 ```
5148
52 ### Span per Node vs. Span per RPC
53 By default Zipkin V1 considers either side of an RPC to have the same identity
54 and differs in that respect from many other tracing systems which consider the
55 caller to be the parent and the receiver to be the child. The OpenTracing
56 specification does not dictate one model over the other, but the Zipkin team is
57 looking into these [single-host-spans] to potentially bring Zipkin more in-line
58 with the other tracing systems.
59
60 [single-host-spans]: https://github.com/openzipkin/zipkin/issues/963
61
62 In case of a `span per node` the receiver will create a child span from the
63 propagated parent span like this:
64
65 ```
66 Span per Node propagation and identities
67
68 CALLER: RECEIVER:
69 ---------------------------------
70 traceId -> traceId
71 spanId (new)
72 spanId -> parentSpanId
73 parentSpanId
74 ```
75
76 **Note:** most tracing implementations supporting the `span per node` model
77 therefore do not propagate their `parentSpanID` as its not needed.
78
79 A typical Zipkin implementation will use the `span per RPC` model and recreate
80 the span identity from the caller on the receiver's end and then annotates its
81 values on top of it. Propagation will happen like this:
82
83 ```
84 Span per RPC propagation and identities
85
86 CALLER: RECEIVER:
87 ---------------------------------
88 traceId -> traceId
89 spanId -> spanId
90 parentSpanId -> parentSpanId
91 ```
92
93 The [zipkin-go-opentracing] implementation allows you to choose which model you
94 wish to use. Make sure you select the same model consistently for all your
95 services that are required to communicate with each other or you will have trace
96 propagation issues. If using non OpenTracing / legacy instrumentation, it's
97 probably best to use the `span per RPC call` model.
98
99 To adhere to the more common tracing philosophy of `span per node`, the Tracer
100 defaults to `span per node`. To set the `span per RPC call` mode start your
101 tracer like this:
102
103 ```go
104 tracer, err = zipkin.NewTracer(
105 zipkin.NewRecorder(...),
106 zipkin.ClientServerSameSpan(true),
107 )
108 ```
109
110 [zipkin-go-opentracing]: https://github.com/openzipkin/zipkin-go-opentracing
111 [addsvc]:https://github.com/go-kit/kit/tree/master/examples/addsvc
49 [zipkin-go]: https://github.com/openzipkin/zipkin-go
50 [addsvc]: https://github.com/go-kit/examples/tree/master/addsvc
11251 [Log]: https://github.com/go-kit/kit/tree/master/log
11352
11453 ### Tracing Resources
11554
116 In our legacy implementation we had the `NewChildSpan` method to allow
117 annotation of resources such as databases, caches and other services that do not
118 have server side tracing support. Since OpenTracing has no specific method of
119 dealing with these items explicitely that is compatible with Zipkin's `SA`
120 annotation, the [zipkin-go-opentracing] has implemented support using the
121 OpenTracing Tags system. Here is an example of how one would be able to record
122 a resource span compatible with standard OpenTracing and triggering an `SA`
123 annotation in [zipkin-go-opentracing]:
124
55 Here is an example of how you could trace resources and work with local spans.
12556 ```go
126 // you need to import the ext package for the Tag helper functions
12757 import (
128 "github.com/opentracing/opentracing-go"
129 "github.com/opentracing/opentracing-go/ext"
58 zipkin "github.com/openzipkin/zipkin-go"
13059 )
13160
13261 func (svc *Service) GetMeSomeExamples(ctx context.Context, ...) ([]Examples, error) {
133 // Example of annotating a database query:
134 var (
135 serviceName = "MySQL"
136 serviceHost = "mysql.example.com"
137 servicePort = uint16(3306)
138 queryLabel = "GetExamplesByParam"
139 query = "select * from example where param = 'value'"
140 )
62 // Example of annotating a database query:
63 var (
64 spanContext model.SpanContext
65 serviceName = "MySQL"
66 serviceHost = "mysql.example.com:3306"
67 queryLabel = "GetExamplesByParam"
68 query = "select * from example where param = :value"
69 )
14170
142 // retrieve the parent span, if not found create a new trace
143 parentSpan := opentracing.SpanFromContext(ctx)
144 if parentSpan == nil {
145 parentSpan = opentracing.StartSpan(queryLabel)
146 defer parentSpan.Finish()
147 }
71 // retrieve the parent span from context to use as parent if available.
72 if parentSpan := zipkin.SpanFromContext(ctx); parentSpan != nil {
73 spanContext = parentSpan.Context()
74 }
14875
149 // create a new span to record the resource interaction
150 span := opentracing.StartChildSpan(parentSpan, queryLabel)
76 // create the remote Zipkin endpoint
77 ep, _ := zipkin.NewEndpoint(serviceName, serviceHost)
15178
152 // span.kind "resource" triggers SA annotation
153 ext.SpanKind.Set(span, "resource")
79 // create a new span to record the resource interaction
80 span := zipkin.StartSpan(
81 queryLabel,
82 zipkin.Parent(parentSpan.Context()),
83 zipkin.WithRemoteEndpoint(ep),
84 )
15485
155 // this will label the span's service & hostPort (called Endpoint in Zipkin)
156 ext.PeerService.Set(span, serviceName)
157 ext.PeerHostname.Set(span, serviceHost)
158 ext.PeerPort.Set(span, servicePort)
159
160 // a Tag is the equivalent of a Zipkin Binary Annotation (key:value pair)
86 // add interesting key/value pair to our span
16187 span.SetTag("query", query)
16288
163 // a LogEvent is the equivalent of a Zipkin Annotation (timestamped)
164 span.LogEvent("query:start")
89 // add interesting timed event to our span
90 span.Annotate(time.Now(), "query:start")
16591
16692 // do the actual query...
16793
16894 // let's annotate the end...
169 span.LogEvent("query:end")
95 span.Annotate(time.Now(), "query:end")
17096
17197 // we're done with this span.
17298 span.Finish()
0 // Package zipkin provides Go kit integration to the OpenZipkin project through
1 // the use of zipkin-go, the official OpenZipkin tracer implementation for Go.
2 // OpenZipkin is the most used open source distributed tracing ecosystem with
3 // many different libraries and interoperability options.
4 package zipkin
0 package zipkin
1
2 import (
3 "context"
4
5 "github.com/openzipkin/zipkin-go"
6 "github.com/openzipkin/zipkin-go/model"
7
8 "github.com/go-kit/kit/endpoint"
9 )
10
11 // TraceEndpoint returns an Endpoint middleware, tracing a Go kit endpoint.
12 // This endpoint tracer should be used in combination with a Go kit Transport
13 // tracing middleware or custom before and after transport functions as
14 // propagation of SpanContext is not provided in this middleware.
15 func TraceEndpoint(tracer *zipkin.Tracer, name string) endpoint.Middleware {
16 return func(next endpoint.Endpoint) endpoint.Endpoint {
17 return func(ctx context.Context, request interface{}) (interface{}, error) {
18 var sc model.SpanContext
19 if parentSpan := zipkin.SpanFromContext(ctx); parentSpan != nil {
20 sc = parentSpan.Context()
21 }
22 sp := tracer.StartSpan(name, zipkin.Parent(sc))
23 defer sp.Finish()
24
25 ctx = zipkin.NewContext(ctx, sp)
26 return next(ctx, request)
27 }
28 }
29 }
0 package zipkin_test
1
2 import (
3 "context"
4 "testing"
5
6 "github.com/openzipkin/zipkin-go"
7 "github.com/openzipkin/zipkin-go/reporter/recorder"
8
9 "github.com/go-kit/kit/endpoint"
10 zipkinkit "github.com/go-kit/kit/tracing/zipkin"
11 )
12
13 const spanName = "test"
14
15 func TestTraceEndpoint(t *testing.T) {
16 rec := recorder.NewReporter()
17 tr, _ := zipkin.NewTracer(rec)
18 mw := zipkinkit.TraceEndpoint(tr, spanName)
19 mw(endpoint.Nop)(context.Background(), nil)
20
21 spans := rec.Flush()
22
23 if want, have := 1, len(spans); want != have {
24 t.Fatalf("incorrect number of spans, wanted %d, got %d", want, have)
25 }
26
27 if want, have := spanName, spans[0].Name; want != have {
28 t.Fatalf("incorrect span name, wanted %s, got %s", want, have)
29 }
30 }
0 package zipkin
1
2 import (
3 "context"
4 "strconv"
5
6 zipkin "github.com/openzipkin/zipkin-go"
7 "github.com/openzipkin/zipkin-go/model"
8 "github.com/openzipkin/zipkin-go/propagation/b3"
9 "google.golang.org/grpc/metadata"
10 "google.golang.org/grpc/status"
11
12 kitgrpc "github.com/go-kit/kit/transport/grpc"
13 "github.com/go-kit/log"
14 )
15
16 // GRPCClientTrace enables native Zipkin tracing of a Go kit gRPC transport
17 // Client.
18 //
19 // Go kit creates gRPC transport clients per remote endpoint. This middleware
20 // can be set-up individually by adding the endpoint name for each of the Go kit
21 // transport clients using the Name() TracerOption.
22 // If wanting to use the gRPC FullMethod (/service/method) as Span name you can
23 // create a global client tracer omitting the Name() TracerOption, which you can
24 // then feed to each Go kit gRPC transport client.
25 // If instrumenting a client to an external (not on your platform) service, you
26 // will probably want to disallow propagation of SpanContext using the
27 // AllowPropagation TracerOption and setting it to false.
28 func GRPCClientTrace(tracer *zipkin.Tracer, options ...TracerOption) kitgrpc.ClientOption {
29 config := tracerOptions{
30 tags: make(map[string]string),
31 name: "",
32 logger: log.NewNopLogger(),
33 propagate: true,
34 }
35
36 for _, option := range options {
37 option(&config)
38 }
39
40 clientBefore := kitgrpc.ClientBefore(
41 func(ctx context.Context, md *metadata.MD) context.Context {
42 var (
43 spanContext model.SpanContext
44 name string
45 )
46
47 if config.name != "" {
48 name = config.name
49 } else {
50 name = ctx.Value(kitgrpc.ContextKeyRequestMethod).(string)
51 }
52
53 if parent := zipkin.SpanFromContext(ctx); parent != nil {
54 spanContext = parent.Context()
55 }
56
57 span := tracer.StartSpan(
58 name,
59 zipkin.Kind(model.Client),
60 zipkin.Tags(config.tags),
61 zipkin.Parent(spanContext),
62 zipkin.FlushOnFinish(false),
63 )
64
65 if config.propagate {
66 if err := b3.InjectGRPC(md)(span.Context()); err != nil {
67 config.logger.Log("err", err)
68 }
69 }
70
71 return zipkin.NewContext(ctx, span)
72 },
73 )
74
75 clientAfter := kitgrpc.ClientAfter(
76 func(ctx context.Context, _ metadata.MD, _ metadata.MD) context.Context {
77 if span := zipkin.SpanFromContext(ctx); span != nil {
78 span.Finish()
79 }
80
81 return ctx
82 },
83 )
84
85 clientFinalizer := kitgrpc.ClientFinalizer(
86 func(ctx context.Context, err error) {
87 if span := zipkin.SpanFromContext(ctx); span != nil {
88 if err != nil {
89 zipkin.TagError.Set(span, err.Error())
90 }
91 // calling span.Finish() a second time is a noop, if we didn't get to
92 // ClientAfter we can at least time the early bail out by calling it
93 // here.
94 span.Finish()
95 // send span to the Reporter
96 span.Flush()
97 }
98 },
99 )
100
101 return func(c *kitgrpc.Client) {
102 clientBefore(c)
103 clientAfter(c)
104 clientFinalizer(c)
105 }
106
107 }
108
109 // GRPCServerTrace enables native Zipkin tracing of a Go kit gRPC transport
110 // Server.
111 //
112 // Go kit creates gRPC transport servers per gRPC method. This middleware can be
113 // set-up individually by adding the method name for each of the Go kit method
114 // servers using the Name() TracerOption.
115 // If wanting to use the gRPC FullMethod (/service/method) as Span name you can
116 // create a global server tracer omitting the Name() TracerOption, which you can
117 // then feed to each Go kit method server. For this to work you will need to
118 // wire the Go kit gRPC Interceptor too.
119 // If instrumenting a service to external (not on your platform) clients, you
120 // will probably want to disallow propagation of a client SpanContext using
121 // the AllowPropagation TracerOption and setting it to false.
122 func GRPCServerTrace(tracer *zipkin.Tracer, options ...TracerOption) kitgrpc.ServerOption {
123 config := tracerOptions{
124 tags: make(map[string]string),
125 name: "",
126 logger: log.NewNopLogger(),
127 propagate: true,
128 }
129
130 for _, option := range options {
131 option(&config)
132 }
133
134 serverBefore := kitgrpc.ServerBefore(
135 func(ctx context.Context, md metadata.MD) context.Context {
136 var (
137 spanContext model.SpanContext
138 name string
139 tags = make(map[string]string)
140 )
141
142 rpcMethod, ok := ctx.Value(kitgrpc.ContextKeyRequestMethod).(string)
143 if !ok {
144 config.logger.Log("err", "unable to retrieve method name: missing gRPC interceptor hook")
145 } else {
146 tags["grpc.method"] = rpcMethod
147 }
148
149 if config.name != "" {
150 name = config.name
151 } else {
152 name = rpcMethod
153 }
154
155 if config.propagate {
156 spanContext = tracer.Extract(b3.ExtractGRPC(&md))
157 if spanContext.Err != nil {
158 config.logger.Log("err", spanContext.Err)
159 }
160 }
161
162 span := tracer.StartSpan(
163 name,
164 zipkin.Kind(model.Server),
165 zipkin.Tags(config.tags),
166 zipkin.Tags(tags),
167 zipkin.Parent(spanContext),
168 zipkin.FlushOnFinish(false),
169 )
170
171 return zipkin.NewContext(ctx, span)
172 },
173 )
174
175 serverAfter := kitgrpc.ServerAfter(
176 func(ctx context.Context, _ *metadata.MD, _ *metadata.MD) context.Context {
177 if span := zipkin.SpanFromContext(ctx); span != nil {
178 span.Finish()
179 }
180
181 return ctx
182 },
183 )
184
185 serverFinalizer := kitgrpc.ServerFinalizer(
186 func(ctx context.Context, err error) {
187 if span := zipkin.SpanFromContext(ctx); span != nil {
188 if err != nil {
189 if status, ok := status.FromError(err); ok {
190 statusCode := strconv.FormatUint(uint64(status.Code()), 10)
191 zipkin.TagGRPCStatusCode.Set(span, statusCode)
192 zipkin.TagError.Set(span, status.Message())
193 } else {
194 zipkin.TagError.Set(span, err.Error())
195 }
196 }
197
198 // calling span.Finish() a second time is a noop, if we didn't get to
199 // ServerAfter we can at least time the early bail out by calling it
200 // here.
201 span.Finish()
202 // send span to the Reporter
203 span.Flush()
204 }
205 },
206 )
207
208 return func(s *kitgrpc.Server) {
209 serverBefore(s)
210 serverAfter(s)
211 serverFinalizer(s)
212 }
213 }
0 package zipkin_test
1
2 import (
3 "context"
4 "testing"
5
6 zipkin "github.com/openzipkin/zipkin-go"
7 "github.com/openzipkin/zipkin-go/propagation/b3"
8 "github.com/openzipkin/zipkin-go/reporter/recorder"
9 "google.golang.org/grpc"
10 "google.golang.org/grpc/metadata"
11
12 "github.com/go-kit/kit/endpoint"
13 kitzipkin "github.com/go-kit/kit/tracing/zipkin"
14 grpctransport "github.com/go-kit/kit/transport/grpc"
15 )
16
17 type dummy struct{}
18
19 func unaryInterceptor(
20 ctx context.Context, method string, req, reply interface{},
21 cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption,
22 ) error {
23 return nil
24 }
25
26 func TestGRPCClientTrace(t *testing.T) {
27 rec := recorder.NewReporter()
28 defer rec.Close()
29
30 tr, _ := zipkin.NewTracer(rec)
31
32 clientTracer := kitzipkin.GRPCClientTrace(tr)
33
34 cc, err := grpc.Dial(
35 "",
36 grpc.WithUnaryInterceptor(unaryInterceptor),
37 grpc.WithInsecure(),
38 )
39 if err != nil {
40 t.Fatalf("unable to create gRPC dialer: %s", err.Error())
41 }
42
43 ep := grpctransport.NewClient(
44 cc,
45 "dummyService",
46 "dummyMethod",
47 func(context.Context, interface{}) (interface{}, error) { return nil, nil },
48 func(context.Context, interface{}) (interface{}, error) { return nil, nil },
49 dummy{},
50 clientTracer,
51 ).Endpoint()
52
53 parentSpan := tr.StartSpan("test")
54 ctx := zipkin.NewContext(context.Background(), parentSpan)
55
56 if _, err = ep(ctx, nil); err != nil {
57 t.Errorf("unexpected error: %s", err.Error())
58 }
59
60 spans := rec.Flush()
61 if want, have := 1, len(spans); want != have {
62 t.Fatalf("incorrect number of spans, want %d, have %d", want, have)
63 }
64
65 if spans[0].SpanContext.ParentID == nil {
66 t.Fatalf("incorrect parent ID, want %s have nil", parentSpan.Context().ID)
67 }
68
69 if want, have := parentSpan.Context().ID, *spans[0].SpanContext.ParentID; want != have {
70 t.Fatalf("incorrect parent ID, want %s, have %s", want, have)
71 }
72 }
73
74 func TestGRPCServerTrace(t *testing.T) {
75 rec := recorder.NewReporter()
76 defer rec.Close()
77
78 tr, _ := zipkin.NewTracer(rec)
79
80 serverTracer := kitzipkin.GRPCServerTrace(tr)
81
82 server := grpctransport.NewServer(
83 endpoint.Nop,
84 func(context.Context, interface{}) (interface{}, error) { return nil, nil },
85 func(context.Context, interface{}) (interface{}, error) { return nil, nil },
86 serverTracer,
87 )
88
89 md := metadata.MD{}
90 parentSpan := tr.StartSpan("test")
91
92 b3.InjectGRPC(&md)(parentSpan.Context())
93
94 ctx := metadata.NewIncomingContext(context.Background(), md)
95 server.ServeGRPC(ctx, nil)
96
97 spans := rec.Flush()
98
99 if want, have := 1, len(spans); want != have {
100 t.Fatalf("incorrect number of spans, want %d, have %d", want, have)
101 }
102
103 if want, have := parentSpan.Context().TraceID, spans[0].SpanContext.TraceID; want != have {
104 t.Errorf("incorrect TraceID, want %+v, have %+v", want, have)
105 }
106
107 if want, have := parentSpan.Context().ID, spans[0].SpanContext.ID; want != have {
108 t.Errorf("incorrect span ID, want %d, have %d", want, have)
109 }
110 }
0 package zipkin
1
2 import (
3 "context"
4 "net/http"
5 "strconv"
6
7 zipkin "github.com/openzipkin/zipkin-go"
8 "github.com/openzipkin/zipkin-go/model"
9 "github.com/openzipkin/zipkin-go/propagation/b3"
10
11 kithttp "github.com/go-kit/kit/transport/http"
12 "github.com/go-kit/log"
13 )
14
15 // HTTPClientTrace enables native Zipkin tracing of a Go kit HTTP transport
16 // Client.
17 //
18 // Go kit creates HTTP transport clients per remote endpoint. This middleware
19 // can be set-up individually by adding the endpoint name for each of the Go kit
20 // transport clients using the Name() TracerOption.
21 // If wanting to use the HTTP Method (Get, Post, Put, etc.) as Span name you can
22 // create a global client tracer omitting the Name() TracerOption, which you can
23 // then feed to each Go kit transport client.
24 // If instrumenting a client to an external (not on your platform) service, you
25 // will probably want to disallow propagation of SpanContext using the
26 // AllowPropagation TracerOption and setting it to false.
27 func HTTPClientTrace(tracer *zipkin.Tracer, options ...TracerOption) kithttp.ClientOption {
28 config := tracerOptions{
29 tags: make(map[string]string),
30 name: "",
31 logger: log.NewNopLogger(),
32 propagate: true,
33 }
34
35 for _, option := range options {
36 option(&config)
37 }
38
39 clientBefore := kithttp.ClientBefore(
40 func(ctx context.Context, req *http.Request) context.Context {
41 var (
42 spanContext model.SpanContext
43 name string
44 )
45
46 if config.name != "" {
47 name = config.name
48 } else {
49 name = req.Method
50 }
51
52 if parent := zipkin.SpanFromContext(ctx); parent != nil {
53 spanContext = parent.Context()
54 }
55
56 tags := map[string]string{
57 string(zipkin.TagHTTPMethod): req.Method,
58 string(zipkin.TagHTTPUrl): req.URL.String(),
59 }
60
61 span := tracer.StartSpan(
62 name,
63 zipkin.Kind(model.Client),
64 zipkin.Tags(config.tags),
65 zipkin.Tags(tags),
66 zipkin.Parent(spanContext),
67 zipkin.FlushOnFinish(false),
68 )
69
70 if config.propagate {
71 if err := b3.InjectHTTP(req)(span.Context()); err != nil {
72 config.logger.Log("err", err)
73 }
74 }
75
76 return zipkin.NewContext(ctx, span)
77 },
78 )
79
80 clientAfter := kithttp.ClientAfter(
81 func(ctx context.Context, res *http.Response) context.Context {
82 if span := zipkin.SpanFromContext(ctx); span != nil {
83 zipkin.TagHTTPResponseSize.Set(span, strconv.FormatInt(res.ContentLength, 10))
84 zipkin.TagHTTPStatusCode.Set(span, strconv.Itoa(res.StatusCode))
85 if res.StatusCode > 399 {
86 zipkin.TagError.Set(span, strconv.Itoa(res.StatusCode))
87 }
88 span.Finish()
89 }
90
91 return ctx
92 },
93 )
94
95 clientFinalizer := kithttp.ClientFinalizer(
96 func(ctx context.Context, err error) {
97 if span := zipkin.SpanFromContext(ctx); span != nil {
98 if err != nil {
99 zipkin.TagError.Set(span, err.Error())
100 }
101 // calling span.Finish() a second time is a noop, if we didn't get to
102 // ClientAfter we can at least time the early bail out by calling it
103 // here.
104 span.Finish()
105 // send span to the Reporter
106 span.Flush()
107 }
108 },
109 )
110
111 return func(c *kithttp.Client) {
112 clientBefore(c)
113 clientAfter(c)
114 clientFinalizer(c)
115 }
116 }
117
118 // HTTPServerTrace enables native Zipkin tracing of a Go kit HTTP transport
119 // Server.
120 //
121 // Go kit creates HTTP transport servers per HTTP endpoint. This middleware can
122 // be set-up individually by adding the method name for each of the Go kit
123 // method servers using the Name() TracerOption.
124 // If wanting to use the HTTP method (Get, Post, Put, etc.) as Span name you can
125 // create a global server tracer omitting the Name() TracerOption, which you can
126 // then feed to each Go kit method server.
127 //
128 // If instrumenting a service to external (not on your platform) clients, you
129 // will probably want to disallow propagation of a client SpanContext using
130 // the AllowPropagation TracerOption and setting it to false.
131 func HTTPServerTrace(tracer *zipkin.Tracer, options ...TracerOption) kithttp.ServerOption {
132 config := tracerOptions{
133 tags: make(map[string]string),
134 name: "",
135 logger: log.NewNopLogger(),
136 propagate: true,
137 }
138
139 for _, option := range options {
140 option(&config)
141 }
142
143 serverBefore := kithttp.ServerBefore(
144 func(ctx context.Context, req *http.Request) context.Context {
145 var (
146 spanContext model.SpanContext
147 name string
148 )
149
150 if config.name != "" {
151 name = config.name
152 } else {
153 name = req.Method
154 }
155
156 if config.propagate {
157 spanContext = tracer.Extract(b3.ExtractHTTP(req))
158
159 if spanContext.Sampled == nil && config.requestSampler != nil {
160 sample := config.requestSampler(req)
161 spanContext.Sampled = &sample
162 }
163
164 if spanContext.Err != nil {
165 config.logger.Log("err", spanContext.Err)
166 }
167 }
168
169 tags := map[string]string{
170 string(zipkin.TagHTTPMethod): req.Method,
171 string(zipkin.TagHTTPPath): req.URL.Path,
172 }
173
174 span := tracer.StartSpan(
175 name,
176 zipkin.Kind(model.Server),
177 zipkin.Tags(config.tags),
178 zipkin.Tags(tags),
179 zipkin.Parent(spanContext),
180 zipkin.FlushOnFinish(false),
181 )
182
183 return zipkin.NewContext(ctx, span)
184 },
185 )
186
187 serverAfter := kithttp.ServerAfter(
188 func(ctx context.Context, _ http.ResponseWriter) context.Context {
189 if span := zipkin.SpanFromContext(ctx); span != nil {
190 span.Finish()
191 }
192
193 return ctx
194 },
195 )
196
197 serverFinalizer := kithttp.ServerFinalizer(
198 func(ctx context.Context, code int, r *http.Request) {
199 if span := zipkin.SpanFromContext(ctx); span != nil {
200 zipkin.TagHTTPStatusCode.Set(span, strconv.Itoa(code))
201 if code > 399 {
202 // set http status as error tag (if already set, this is a noop)
203 zipkin.TagError.Set(span, http.StatusText(code))
204 }
205 if rs, ok := ctx.Value(kithttp.ContextKeyResponseSize).(int64); ok {
206 zipkin.TagHTTPResponseSize.Set(span, strconv.FormatInt(rs, 10))
207 }
208
209 // calling span.Finish() a second time is a noop, if we didn't get to
210 // ServerAfter we can at least time the early bail out by calling it
211 // here.
212 span.Finish()
213 // send span to the Reporter
214 span.Flush()
215 }
216 },
217 )
218
219 return func(s *kithttp.Server) {
220 serverBefore(s)
221 serverAfter(s)
222 serverFinalizer(s)
223 }
224 }
0 package zipkin_test
1
2 import (
3 "context"
4 "errors"
5 "fmt"
6 "net/http"
7 "net/http/httptest"
8 "net/url"
9 "reflect"
10 "testing"
11
12 zipkin "github.com/openzipkin/zipkin-go"
13 "github.com/openzipkin/zipkin-go/model"
14 "github.com/openzipkin/zipkin-go/propagation/b3"
15 "github.com/openzipkin/zipkin-go/reporter/recorder"
16
17 "github.com/go-kit/kit/endpoint"
18 zipkinkit "github.com/go-kit/kit/tracing/zipkin"
19 kithttp "github.com/go-kit/kit/transport/http"
20 )
21
22 const (
23 testName = "test"
24 testBody = "test_body"
25 testTagKey = "test_key"
26 testTagValue = "test_value"
27 )
28
29 func TestHTTPClientTracePropagatesParentSpan(t *testing.T) {
30 rec := recorder.NewReporter()
31 defer rec.Close()
32
33 tr, _ := zipkin.NewTracer(rec)
34
35 rURL, _ := url.Parse("https://httpbin.org/get")
36
37 clientTracer := zipkinkit.HTTPClientTrace(tr)
38 ep := kithttp.NewClient(
39 "GET",
40 rURL,
41 func(ctx context.Context, r *http.Request, i interface{}) error {
42 return nil
43 },
44 func(ctx context.Context, r *http.Response) (response interface{}, err error) {
45 return nil, nil
46 },
47 clientTracer,
48 ).Endpoint()
49
50 parentSpan := tr.StartSpan("test")
51
52 ctx := zipkin.NewContext(context.Background(), parentSpan)
53
54 _, err := ep(ctx, nil)
55 if err != nil {
56 t.Fatalf("unexpected error: %s", err.Error())
57 }
58
59 spans := rec.Flush()
60 if want, have := 1, len(spans); want != have {
61 t.Fatalf("incorrect number of spans, want %d, have %d", want, have)
62 }
63
64 span := spans[0]
65 if span.SpanContext.ParentID == nil {
66 t.Fatalf("incorrect parent ID, want %s have nil", parentSpan.Context().ID)
67 }
68
69 if want, have := parentSpan.Context().ID, *span.SpanContext.ParentID; want != have {
70 t.Fatalf("incorrect parent ID, want %s, have %s", want, have)
71 }
72 }
73
74 func TestHTTPClientTraceAddsExpectedTags(t *testing.T) {
75 dataProvider := []struct {
76 ResponseStatusCode int
77 ErrorTagValue string
78 }{
79 {http.StatusOK, ""},
80 {http.StatusForbidden, fmt.Sprint(http.StatusForbidden)},
81 }
82
83 for _, data := range dataProvider {
84 testHTTPClientTraceCase(t, data.ResponseStatusCode, data.ErrorTagValue)
85 }
86 }
87
88 func testHTTPClientTraceCase(t *testing.T, responseStatusCode int, errTagValue string) {
89 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
90 w.WriteHeader(responseStatusCode)
91 w.Write([]byte(testBody))
92 }))
93 defer ts.Close()
94
95 rec := recorder.NewReporter()
96 defer rec.Close()
97
98 tr, err := zipkin.NewTracer(rec)
99 if err != nil {
100 t.Errorf("Unwanted error: %s", err.Error())
101 }
102
103 rMethod := "GET"
104 rURL, _ := url.Parse(ts.URL)
105
106 clientTracer := zipkinkit.HTTPClientTrace(
107 tr,
108 zipkinkit.Name(testName),
109 zipkinkit.Tags(map[string]string{testTagKey: testTagValue}),
110 )
111
112 ep := kithttp.NewClient(
113 rMethod,
114 rURL,
115 func(ctx context.Context, r *http.Request, i interface{}) error {
116 return nil
117 },
118 func(ctx context.Context, r *http.Response) (response interface{}, err error) {
119 return nil, nil
120 },
121 clientTracer,
122 ).Endpoint()
123
124 _, err = ep(context.Background(), nil)
125 if err != nil {
126 t.Fatalf("unwanted error: %s", err.Error())
127 }
128
129 spans := rec.Flush()
130 if want, have := 1, len(spans); want != have {
131 t.Fatalf("incorrect number of spans, wanted %d, got %d", want, have)
132 }
133
134 span := spans[0]
135 if span.SpanContext.ParentID != nil {
136 t.Fatalf("incorrect parentID, wanted nil, got %s", span.SpanContext.ParentID)
137 }
138
139 if want, have := testName, span.Name; want != have {
140 t.Fatalf("incorrect span name, wanted %s, got %s", want, have)
141 }
142
143 if want, have := model.Client, span.Kind; want != have {
144 t.Fatalf("incorrect span kind, wanted %s, got %s", want, have)
145 }
146
147 tags := map[string]string{
148 testTagKey: testTagValue,
149 string(zipkin.TagHTTPStatusCode): fmt.Sprint(responseStatusCode),
150 string(zipkin.TagHTTPMethod): rMethod,
151 string(zipkin.TagHTTPUrl): rURL.String(),
152 string(zipkin.TagHTTPResponseSize): fmt.Sprint(len(testBody)),
153 }
154
155 if errTagValue != "" {
156 tags[string(zipkin.TagError)] = fmt.Sprint(errTagValue)
157 }
158
159 if !reflect.DeepEqual(span.Tags, tags) {
160 t.Fatalf("invalid tags set, wanted %+v, got %+v", tags, span.Tags)
161 }
162 }
163
164 func TestHTTPServerTrace(t *testing.T) {
165 rec := recorder.NewReporter()
166 defer rec.Close()
167
168 // explicitly show we use the default of RPC shared spans in Zipkin as it
169 // is idiomatic for Zipkin to share span identifiers between client and
170 // server side.
171 tr, _ := zipkin.NewTracer(rec, zipkin.WithSharedSpans(true))
172
173 handler := kithttp.NewServer(
174 endpoint.Nop,
175 func(context.Context, *http.Request) (interface{}, error) { return nil, nil },
176 func(context.Context, http.ResponseWriter, interface{}) error { return errors.New("dummy") },
177 zipkinkit.HTTPServerTrace(tr),
178 )
179
180 server := httptest.NewServer(handler)
181 defer server.Close()
182
183 const httpMethod = "GET"
184
185 req, err := http.NewRequest(httpMethod, server.URL, nil)
186 if err != nil {
187 t.Fatalf("unable to create HTTP request: %s", err.Error())
188 }
189
190 parentSpan := tr.StartSpan("Dummy")
191
192 b3.InjectHTTP(req)(parentSpan.Context())
193
194 client := http.Client{}
195 resp, err := client.Do(req)
196 if err != nil {
197 t.Fatalf("unable to send HTTP request: %s", err.Error())
198 }
199 resp.Body.Close()
200
201 spans := rec.Flush()
202 if want, have := 1, len(spans); want != have {
203 t.Fatalf("incorrect number of spans, want %d, have %d", want, have)
204 }
205
206 if want, have := parentSpan.Context().TraceID, spans[0].SpanContext.TraceID; want != have {
207 t.Errorf("incorrect TraceID, want %+v, have %+v", want, have)
208 }
209
210 if want, have := parentSpan.Context().ID, spans[0].SpanContext.ID; want != have {
211 t.Errorf("incorrect span ID, want %d, have %d", want, have)
212 }
213
214 if want, have := httpMethod, spans[0].Name; want != have {
215 t.Errorf("incorrect span name, want %s, have %s", want, have)
216 }
217
218 if want, have := http.StatusText(500), spans[0].Tags["error"]; want != have {
219 t.Fatalf("incorrect error tag, want %s, have %s", want, have)
220 }
221 }
222
223 func TestHTTPServerTraceIsRequestBasedSampled(t *testing.T) {
224 rec := recorder.NewReporter()
225 defer rec.Close()
226
227 const httpMethod = "DELETE"
228
229 tr, _ := zipkin.NewTracer(rec)
230
231 handler := kithttp.NewServer(
232 endpoint.Nop,
233 func(context.Context, *http.Request) (interface{}, error) { return nil, nil },
234 func(context.Context, http.ResponseWriter, interface{}) error { return nil },
235 zipkinkit.HTTPServerTrace(tr, zipkinkit.RequestSampler(func(r *http.Request) bool {
236 return r.Method == httpMethod
237 })),
238 )
239
240 server := httptest.NewServer(handler)
241 defer server.Close()
242
243 req, err := http.NewRequest(httpMethod, server.URL, nil)
244 if err != nil {
245 t.Fatalf("unable to create HTTP request: %s", err.Error())
246 }
247
248 client := http.Client{}
249 resp, err := client.Do(req)
250 if err != nil {
251 t.Fatalf("unable to send HTTP request: %s", err.Error())
252 }
253 resp.Body.Close()
254
255 spans := rec.Flush()
256 if want, have := 1, len(spans); want != have {
257 t.Fatalf("incorrect number of spans, want %d, have %d", want, have)
258 }
259 }
0 package zipkin
1
2 import (
3 "net/http"
4
5 "github.com/go-kit/log"
6 )
7
8 // TracerOption allows for functional options to our Zipkin tracing middleware.
9 type TracerOption func(o *tracerOptions)
10
11 // Name sets the name for an instrumented transport endpoint. If name is omitted
12 // at tracing middleware creation, the method of the transport or transport rpc
13 // name is used.
14 func Name(name string) TracerOption {
15 return func(o *tracerOptions) {
16 o.name = name
17 }
18 }
19
20 // Tags adds default tags to our Zipkin transport spans.
21 func Tags(tags map[string]string) TracerOption {
22 return func(o *tracerOptions) {
23 for k, v := range tags {
24 o.tags[k] = v
25 }
26 }
27 }
28
29 // Logger adds a Go kit logger to our Zipkin Middleware to log SpanContext
30 // extract / inject errors if they occur. Default is Noop.
31 func Logger(logger log.Logger) TracerOption {
32 return func(o *tracerOptions) {
33 if logger != nil {
34 o.logger = logger
35 }
36 }
37 }
38
39 // AllowPropagation instructs the tracer to allow or deny propagation of the
40 // span context between this instrumented client or service and its peers. If
41 // the instrumented client connects to services outside its own platform or if
42 // the instrumented service receives requests from untrusted clients it is
43 // strongly advised to disallow propagation. Propagation between services inside
44 // your own platform benefit from propagation. Default for both TraceClient and
45 // TraceServer is to allow propagation.
46 func AllowPropagation(propagate bool) TracerOption {
47 return func(o *tracerOptions) {
48 o.propagate = propagate
49 }
50 }
51
52 // RequestSampler allows one to set the sampling decision based on the details
53 // found in the http.Request.
54 func RequestSampler(sampleFunc func(r *http.Request) bool) TracerOption {
55 return func(o *tracerOptions) {
56 o.requestSampler = sampleFunc
57 }
58 }
59
60 type tracerOptions struct {
61 tags map[string]string
62 name string
63 logger log.Logger
64 propagate bool
65 requestSampler func(r *http.Request) bool
66 }
0 // Package amqp implements an AMQP transport.
1 package amqp
0 package amqp
1
2 import (
3 "context"
4
5 amqp "github.com/rabbitmq/amqp091-go"
6 )
7
8 // DecodeRequestFunc extracts a user-domain request object from
9 // an AMQP Delivery object. It is designed to be used in AMQP Subscribers.
10 type DecodeRequestFunc func(context.Context, *amqp.Delivery) (request interface{}, err error)
11
12 // EncodeRequestFunc encodes the passed request object into
13 // an AMQP Publishing object. It is designed to be used in AMQP Publishers.
14 type EncodeRequestFunc func(context.Context, *amqp.Publishing, interface{}) error
15
16 // EncodeResponseFunc encodes the passed response object to
17 // an AMQP Publishing object. It is designed to be used in AMQP Subscribers.
18 type EncodeResponseFunc func(context.Context, *amqp.Publishing, interface{}) error
19
20 // DecodeResponseFunc extracts a user-domain response object from
21 // an AMQP Delivery object. It is designed to be used in AMQP Publishers.
22 type DecodeResponseFunc func(context.Context, *amqp.Delivery) (response interface{}, err error)
0 package amqp
1
2 import (
3 "context"
4 "time"
5
6 "github.com/go-kit/kit/endpoint"
7 amqp "github.com/rabbitmq/amqp091-go"
8 )
9
10 // The golang AMQP implementation requires the []byte representation of
11 // correlation id strings to have a maximum length of 255 bytes.
12 const maxCorrelationIdLength = 255
13
14 // Publisher wraps an AMQP channel and queue, and provides a method that
15 // implements endpoint.Endpoint.
16 type Publisher struct {
17 ch Channel
18 q *amqp.Queue
19 enc EncodeRequestFunc
20 dec DecodeResponseFunc
21 before []RequestFunc
22 after []PublisherResponseFunc
23 deliverer Deliverer
24 timeout time.Duration
25 }
26
27 // NewPublisher constructs a usable Publisher for a single remote method.
28 func NewPublisher(
29 ch Channel,
30 q *amqp.Queue,
31 enc EncodeRequestFunc,
32 dec DecodeResponseFunc,
33 options ...PublisherOption,
34 ) *Publisher {
35 p := &Publisher{
36 ch: ch,
37 q: q,
38 enc: enc,
39 dec: dec,
40 deliverer: DefaultDeliverer,
41 timeout: 10 * time.Second,
42 }
43 for _, option := range options {
44 option(p)
45 }
46 return p
47 }
48
49 // PublisherOption sets an optional parameter for clients.
50 type PublisherOption func(*Publisher)
51
52 // PublisherBefore sets the RequestFuncs that are applied to the outgoing AMQP
53 // request before it's invoked.
54 func PublisherBefore(before ...RequestFunc) PublisherOption {
55 return func(p *Publisher) { p.before = append(p.before, before...) }
56 }
57
58 // PublisherAfter sets the ClientResponseFuncs applied to the incoming AMQP
59 // request prior to it being decoded. This is useful for obtaining anything off
60 // of the response and adding onto the context prior to decoding.
61 func PublisherAfter(after ...PublisherResponseFunc) PublisherOption {
62 return func(p *Publisher) { p.after = append(p.after, after...) }
63 }
64
65 // PublisherDeliverer sets the deliverer function that the Publisher invokes.
66 func PublisherDeliverer(deliverer Deliverer) PublisherOption {
67 return func(p *Publisher) { p.deliverer = deliverer }
68 }
69
70 // PublisherTimeout sets the available timeout for an AMQP request.
71 func PublisherTimeout(timeout time.Duration) PublisherOption {
72 return func(p *Publisher) { p.timeout = timeout }
73 }
74
75 // Endpoint returns a usable endpoint that invokes the remote endpoint.
76 func (p Publisher) Endpoint() endpoint.Endpoint {
77 return func(ctx context.Context, request interface{}) (interface{}, error) {
78 ctx, cancel := context.WithTimeout(ctx, p.timeout)
79 defer cancel()
80
81 pub := amqp.Publishing{
82 ReplyTo: p.q.Name,
83 CorrelationId: randomString(randInt(5, maxCorrelationIdLength)),
84 }
85
86 if err := p.enc(ctx, &pub, request); err != nil {
87 return nil, err
88 }
89
90 for _, f := range p.before {
91 // Affect only amqp.Publishing
92 ctx = f(ctx, &pub, nil)
93 }
94
95 deliv, err := p.deliverer(ctx, p, &pub)
96 if err != nil {
97 return nil, err
98 }
99
100 for _, f := range p.after {
101 ctx = f(ctx, deliv)
102 }
103 response, err := p.dec(ctx, deliv)
104 if err != nil {
105 return nil, err
106 }
107
108 return response, nil
109 }
110 }
111
112 // Deliverer is invoked by the Publisher to publish the specified Publishing, and to
113 // retrieve the appropriate response Delivery object.
114 type Deliverer func(
115 context.Context,
116 Publisher,
117 *amqp.Publishing,
118 ) (*amqp.Delivery, error)
119
120 // DefaultDeliverer is a deliverer that publishes the specified Publishing
121 // and returns the first Delivery object with the matching correlationId.
122 // If the context times out while waiting for a reply, an error will be returned.
123 func DefaultDeliverer(
124 ctx context.Context,
125 p Publisher,
126 pub *amqp.Publishing,
127 ) (*amqp.Delivery, error) {
128 err := p.ch.Publish(
129 getPublishExchange(ctx),
130 getPublishKey(ctx),
131 false, //mandatory
132 false, //immediate
133 *pub,
134 )
135 if err != nil {
136 return nil, err
137 }
138 autoAck := getConsumeAutoAck(ctx)
139
140 msg, err := p.ch.Consume(
141 p.q.Name,
142 "", //consumer
143 autoAck,
144 false, //exclusive
145 false, //noLocal
146 false, //noWait
147 getConsumeArgs(ctx),
148 )
149 if err != nil {
150 return nil, err
151 }
152
153 for {
154 select {
155 case d := <-msg:
156 if d.CorrelationId == pub.CorrelationId {
157 if !autoAck {
158 d.Ack(false) //multiple
159 }
160 return &d, nil
161 }
162
163 case <-ctx.Done():
164 return nil, ctx.Err()
165 }
166 }
167
168 }
169
170 // SendAndForgetDeliverer delivers the supplied publishing and
171 // returns a nil response.
172 // When using this deliverer please ensure that the supplied DecodeResponseFunc and
173 // PublisherResponseFunc are able to handle nil-type responses.
174 func SendAndForgetDeliverer(
175 ctx context.Context,
176 p Publisher,
177 pub *amqp.Publishing,
178 ) (*amqp.Delivery, error) {
179 err := p.ch.Publish(
180 getPublishExchange(ctx),
181 getPublishKey(ctx),
182 false, //mandatory
183 false, //immediate
184 *pub,
185 )
186 return nil, err
187 }
0 package amqp_test
1
2 import (
3 "context"
4 "encoding/json"
5 "errors"
6 "testing"
7 "time"
8
9 amqptransport "github.com/go-kit/kit/transport/amqp"
10 amqp "github.com/rabbitmq/amqp091-go"
11 )
12
13 var (
14 defaultContentType = ""
15 defaultContentEncoding = ""
16 )
17
18 // TestBadEncode tests if encode errors are handled properly.
19 func TestBadEncode(t *testing.T) {
20 ch := &mockChannel{f: nullFunc}
21 q := &amqp.Queue{Name: "some queue"}
22 pub := amqptransport.NewPublisher(
23 ch,
24 q,
25 func(context.Context, *amqp.Publishing, interface{}) error { return errors.New("err!") },
26 func(context.Context, *amqp.Delivery) (response interface{}, err error) { return struct{}{}, nil },
27 )
28 errChan := make(chan error, 1)
29 var err error
30 go func() {
31 _, err := pub.Endpoint()(context.Background(), struct{}{})
32 errChan <- err
33
34 }()
35 select {
36 case err = <-errChan:
37 break
38
39 case <-time.After(100 * time.Millisecond):
40 t.Fatal("Timed out waiting for result")
41 }
42 if err == nil {
43 t.Error("expected error")
44 }
45 if want, have := "err!", err.Error(); want != have {
46 t.Errorf("want %s, have %s", want, have)
47 }
48 }
49
50 // TestBadDecode tests if decode errors are handled properly.
51 func TestBadDecode(t *testing.T) {
52 cid := "correlation"
53 ch := &mockChannel{
54 f: nullFunc,
55 c: make(chan amqp.Publishing, 1),
56 deliveries: []amqp.Delivery{
57 amqp.Delivery{
58 CorrelationId: cid,
59 },
60 },
61 }
62 q := &amqp.Queue{Name: "some queue"}
63
64 pub := amqptransport.NewPublisher(
65 ch,
66 q,
67 func(context.Context, *amqp.Publishing, interface{}) error { return nil },
68 func(context.Context, *amqp.Delivery) (response interface{}, err error) {
69 return struct{}{}, errors.New("err!")
70 },
71 amqptransport.PublisherBefore(
72 amqptransport.SetCorrelationID(cid),
73 ),
74 )
75
76 var err error
77 errChan := make(chan error, 1)
78 go func() {
79 _, err := pub.Endpoint()(context.Background(), struct{}{})
80 errChan <- err
81
82 }()
83
84 select {
85 case err = <-errChan:
86 break
87
88 case <-time.After(100 * time.Millisecond):
89 t.Fatal("Timed out waiting for result")
90 }
91
92 if err == nil {
93 t.Error("expected error")
94 }
95 if want, have := "err!", err.Error(); want != have {
96 t.Errorf("want %s, have %s", want, have)
97 }
98 }
99
100 // TestPublisherTimeout ensures that the publisher timeout mechanism works.
101 func TestPublisherTimeout(t *testing.T) {
102 ch := &mockChannel{
103 f: nullFunc,
104 c: make(chan amqp.Publishing, 1),
105 deliveries: []amqp.Delivery{}, // no reply from mock subscriber
106 }
107 q := &amqp.Queue{Name: "some queue"}
108
109 pub := amqptransport.NewPublisher(
110 ch,
111 q,
112 func(context.Context, *amqp.Publishing, interface{}) error { return nil },
113 func(context.Context, *amqp.Delivery) (response interface{}, err error) {
114 return struct{}{}, nil
115 },
116 amqptransport.PublisherTimeout(50*time.Millisecond),
117 )
118
119 var err error
120 errChan := make(chan error, 1)
121 go func() {
122 _, err := pub.Endpoint()(context.Background(), struct{}{})
123 errChan <- err
124
125 }()
126
127 select {
128 case err = <-errChan:
129 break
130
131 case <-time.After(100 * time.Millisecond):
132 t.Fatal("timed out waiting for result")
133 }
134
135 if err == nil {
136 t.Error("expected error")
137 }
138 if want, have := context.DeadlineExceeded.Error(), err.Error(); want != have {
139 t.Errorf("want %s, have %s", want, have)
140 }
141 }
142
143 func TestSuccessfulPublisher(t *testing.T) {
144 cid := "correlation"
145 mockReq := testReq{437}
146 mockRes := testRes{
147 Squadron: mockReq.Squadron,
148 Name: names[mockReq.Squadron],
149 }
150 b, err := json.Marshal(mockRes)
151 if err != nil {
152 t.Fatal(err)
153 }
154 reqChan := make(chan amqp.Publishing, 1)
155 ch := &mockChannel{
156 f: nullFunc,
157 c: reqChan,
158 deliveries: []amqp.Delivery{
159 amqp.Delivery{
160 CorrelationId: cid,
161 Body: b,
162 },
163 },
164 }
165 q := &amqp.Queue{Name: "some queue"}
166
167 pub := amqptransport.NewPublisher(
168 ch,
169 q,
170 testReqEncoder,
171 testResDeliveryDecoder,
172 amqptransport.PublisherBefore(
173 amqptransport.SetCorrelationID(cid),
174 ),
175 )
176 var publishing amqp.Publishing
177 var res testRes
178 var ok bool
179 resChan := make(chan interface{}, 1)
180 errChan := make(chan error, 1)
181 go func() {
182 res, err := pub.Endpoint()(context.Background(), mockReq)
183 if err != nil {
184 errChan <- err
185 } else {
186 resChan <- res
187 }
188 }()
189
190 select {
191 case publishing = <-reqChan:
192 break
193
194 case <-time.After(100 * time.Millisecond):
195 t.Fatal("timed out waiting for request")
196 }
197 if want, have := defaultContentType, publishing.ContentType; want != have {
198 t.Errorf("want %s, have %s", want, have)
199 }
200 if want, have := defaultContentEncoding, publishing.ContentEncoding; want != have {
201 t.Errorf("want %s, have %s", want, have)
202 }
203
204 select {
205 case response := <-resChan:
206 res, ok = response.(testRes)
207 if !ok {
208 t.Error("failed to assert endpoint response type")
209 }
210 break
211
212 case err = <-errChan:
213 break
214
215 case <-time.After(100 * time.Millisecond):
216 t.Fatal("timed out waiting for result")
217 }
218
219 if err != nil {
220 t.Fatal(err)
221 }
222 if want, have := mockRes.Name, res.Name; want != have {
223 t.Errorf("want %s, have %s", want, have)
224 }
225 }
226
227 // TestSendAndForgetPublisher tests that the SendAndForgetDeliverer is working
228 func TestSendAndForgetPublisher(t *testing.T) {
229 ch := &mockChannel{
230 f: nullFunc,
231 c: make(chan amqp.Publishing, 1),
232 deliveries: []amqp.Delivery{}, // no reply from mock subscriber
233 }
234 q := &amqp.Queue{Name: "some queue"}
235
236 pub := amqptransport.NewPublisher(
237 ch,
238 q,
239 func(context.Context, *amqp.Publishing, interface{}) error { return nil },
240 func(context.Context, *amqp.Delivery) (response interface{}, err error) {
241 return struct{}{}, nil
242 },
243 amqptransport.PublisherDeliverer(amqptransport.SendAndForgetDeliverer),
244 amqptransport.PublisherTimeout(50*time.Millisecond),
245 )
246
247 var err error
248 errChan := make(chan error, 1)
249 finishChan := make(chan bool, 1)
250 go func() {
251 _, err := pub.Endpoint()(context.Background(), struct{}{})
252 if err != nil {
253 errChan <- err
254 } else {
255 finishChan <- true
256 }
257
258 }()
259
260 select {
261 case <-finishChan:
262 break
263 case err = <-errChan:
264 t.Errorf("unexpected error %s", err)
265 case <-time.After(100 * time.Millisecond):
266 t.Fatal("timed out waiting for result")
267 }
268
269 }
0 package amqp
1
2 import (
3 "context"
4 "time"
5
6 amqp "github.com/rabbitmq/amqp091-go"
7 )
8
9 // RequestFunc may take information from a publisher request and put it into a
10 // request context. In Subscribers, RequestFuncs are executed prior to invoking
11 // the endpoint.
12 type RequestFunc func(context.Context, *amqp.Publishing, *amqp.Delivery) context.Context
13
14 // SubscriberResponseFunc may take information from a request context and use it to
15 // manipulate a Publisher. SubscriberResponseFuncs are only executed in
16 // subscribers, after invoking the endpoint but prior to publishing a reply.
17 type SubscriberResponseFunc func(context.Context,
18 *amqp.Delivery,
19 Channel,
20 *amqp.Publishing,
21 ) context.Context
22
23 // PublisherResponseFunc may take information from an AMQP request and make the
24 // response available for consumption. PublisherResponseFunc are only executed
25 // in publishers, after a request has been made, but prior to it being decoded.
26 type PublisherResponseFunc func(context.Context, *amqp.Delivery) context.Context
27
28 // SetPublishExchange returns a RequestFunc that sets the Exchange field
29 // of an AMQP Publish call.
30 func SetPublishExchange(publishExchange string) RequestFunc {
31 return func(ctx context.Context, pub *amqp.Publishing, _ *amqp.Delivery) context.Context {
32 return context.WithValue(ctx, ContextKeyExchange, publishExchange)
33 }
34 }
35
36 // SetPublishKey returns a RequestFunc that sets the Key field
37 // of an AMQP Publish call.
38 func SetPublishKey(publishKey string) RequestFunc {
39 return func(ctx context.Context, pub *amqp.Publishing, _ *amqp.Delivery) context.Context {
40 return context.WithValue(ctx, ContextKeyPublishKey, publishKey)
41 }
42 }
43
44 // SetPublishDeliveryMode sets the delivery mode of a Publishing.
45 // Please refer to AMQP delivery mode constants in the AMQP package.
46 func SetPublishDeliveryMode(dmode uint8) RequestFunc {
47 return func(ctx context.Context, pub *amqp.Publishing, _ *amqp.Delivery) context.Context {
48 pub.DeliveryMode = dmode
49 return ctx
50 }
51 }
52
53 // SetNackSleepDuration returns a RequestFunc that sets the amount of time
54 // to sleep in the event of a Nack.
55 // This has to be used in conjunction with an error encoder that Nack and sleeps.
56 // One example is the SingleNackRequeueErrorEncoder.
57 // It is designed to be used by Subscribers.
58 func SetNackSleepDuration(duration time.Duration) RequestFunc {
59 return func(ctx context.Context, pub *amqp.Publishing, _ *amqp.Delivery) context.Context {
60 return context.WithValue(ctx, ContextKeyNackSleepDuration, duration)
61 }
62 }
63
64 // SetConsumeAutoAck returns a RequestFunc that sets whether or not to autoAck
65 // messages when consuming.
66 // When set to false, the publisher will Ack the first message it receives with
67 // a matching correlationId.
68 // It is designed to be used by Publishers.
69 func SetConsumeAutoAck(autoAck bool) RequestFunc {
70 return func(ctx context.Context, pub *amqp.Publishing, _ *amqp.Delivery) context.Context {
71 return context.WithValue(ctx, ContextKeyAutoAck, autoAck)
72 }
73 }
74
75 // SetConsumeArgs returns a RequestFunc that set the arguments for amqp Consume
76 // function.
77 // It is designed to be used by Publishers.
78 func SetConsumeArgs(args amqp.Table) RequestFunc {
79 return func(ctx context.Context, pub *amqp.Publishing, _ *amqp.Delivery) context.Context {
80 return context.WithValue(ctx, ContextKeyConsumeArgs, args)
81 }
82 }
83
84 // SetContentType returns a RequestFunc that sets the ContentType field of
85 // an AMQP Publishing.
86 func SetContentType(contentType string) RequestFunc {
87 return func(ctx context.Context, pub *amqp.Publishing, _ *amqp.Delivery) context.Context {
88 pub.ContentType = contentType
89 return ctx
90 }
91 }
92
93 // SetContentEncoding returns a RequestFunc that sets the ContentEncoding field
94 // of an AMQP Publishing.
95 func SetContentEncoding(contentEncoding string) RequestFunc {
96 return func(ctx context.Context, pub *amqp.Publishing, _ *amqp.Delivery) context.Context {
97 pub.ContentEncoding = contentEncoding
98 return ctx
99 }
100 }
101
102 // SetCorrelationID returns a RequestFunc that sets the CorrelationId field
103 // of an AMQP Publishing.
104 func SetCorrelationID(cid string) RequestFunc {
105 return func(ctx context.Context, pub *amqp.Publishing, _ *amqp.Delivery) context.Context {
106 pub.CorrelationId = cid
107 return ctx
108 }
109 }
110
111 // SetAckAfterEndpoint returns a SubscriberResponseFunc that prompts the service
112 // to Ack the Delivery object after successfully evaluating the endpoint,
113 // and before it encodes the response.
114 // It is designed to be used by Subscribers.
115 func SetAckAfterEndpoint(multiple bool) SubscriberResponseFunc {
116 return func(ctx context.Context,
117 deliv *amqp.Delivery,
118 ch Channel,
119 pub *amqp.Publishing,
120 ) context.Context {
121 deliv.Ack(multiple)
122 return ctx
123 }
124 }
125
126 func getPublishExchange(ctx context.Context) string {
127 if exchange := ctx.Value(ContextKeyExchange); exchange != nil {
128 return exchange.(string)
129 }
130 return ""
131 }
132
133 func getPublishKey(ctx context.Context) string {
134 if publishKey := ctx.Value(ContextKeyPublishKey); publishKey != nil {
135 return publishKey.(string)
136 }
137 return ""
138 }
139
140 func getNackSleepDuration(ctx context.Context) time.Duration {
141 if duration := ctx.Value(ContextKeyNackSleepDuration); duration != nil {
142 return duration.(time.Duration)
143 }
144 return 0
145 }
146
147 func getConsumeAutoAck(ctx context.Context) bool {
148 if autoAck := ctx.Value(ContextKeyAutoAck); autoAck != nil {
149 return autoAck.(bool)
150 }
151 return false
152 }
153
154 func getConsumeArgs(ctx context.Context) amqp.Table {
155 if args := ctx.Value(ContextKeyConsumeArgs); args != nil {
156 return args.(amqp.Table)
157 }
158 return nil
159 }
160
161 type contextKey int
162
163 const (
164 // ContextKeyExchange is the value of the reply Exchange in
165 // amqp.Publish.
166 ContextKeyExchange contextKey = iota
167 // ContextKeyPublishKey is the value of the ReplyTo field in
168 // amqp.Publish.
169 ContextKeyPublishKey
170 // ContextKeyNackSleepDuration is the duration to sleep for if the
171 // service Nack and requeues a message.
172 // This is to prevent sporadic send-resending of message
173 // when a message is constantly Nack'd and requeued.
174 ContextKeyNackSleepDuration
175 // ContextKeyAutoAck is the value of autoAck field when calling
176 // amqp.Channel.Consume.
177 ContextKeyAutoAck
178 // ContextKeyConsumeArgs is the value of consumeArgs field when calling
179 // amqp.Channel.Consume.
180 ContextKeyConsumeArgs
181 )
0 package amqp
1
2 import (
3 "context"
4 "encoding/json"
5 "time"
6
7 "github.com/go-kit/kit/endpoint"
8 "github.com/go-kit/kit/transport"
9 "github.com/go-kit/log"
10 amqp "github.com/rabbitmq/amqp091-go"
11 )
12
13 // Subscriber wraps an endpoint and provides a handler for AMQP Delivery messages.
14 type Subscriber struct {
15 e endpoint.Endpoint
16 dec DecodeRequestFunc
17 enc EncodeResponseFunc
18 before []RequestFunc
19 after []SubscriberResponseFunc
20 responsePublisher ResponsePublisher
21 errorEncoder ErrorEncoder
22 errorHandler transport.ErrorHandler
23 }
24
25 // NewSubscriber constructs a new subscriber, which provides a handler
26 // for AMQP Delivery messages.
27 func NewSubscriber(
28 e endpoint.Endpoint,
29 dec DecodeRequestFunc,
30 enc EncodeResponseFunc,
31 options ...SubscriberOption,
32 ) *Subscriber {
33 s := &Subscriber{
34 e: e,
35 dec: dec,
36 enc: enc,
37 responsePublisher: DefaultResponsePublisher,
38 errorEncoder: DefaultErrorEncoder,
39 errorHandler: transport.NewLogErrorHandler(log.NewNopLogger()),
40 }
41 for _, option := range options {
42 option(s)
43 }
44 return s
45 }
46
47 // SubscriberOption sets an optional parameter for subscribers.
48 type SubscriberOption func(*Subscriber)
49
50 // SubscriberBefore functions are executed on the publisher delivery object
51 // before the request is decoded.
52 func SubscriberBefore(before ...RequestFunc) SubscriberOption {
53 return func(s *Subscriber) { s.before = append(s.before, before...) }
54 }
55
56 // SubscriberAfter functions are executed on the subscriber reply after the
57 // endpoint is invoked, but before anything is published to the reply.
58 func SubscriberAfter(after ...SubscriberResponseFunc) SubscriberOption {
59 return func(s *Subscriber) { s.after = append(s.after, after...) }
60 }
61
62 // SubscriberResponsePublisher is used by the subscriber to deliver response
63 // objects to the original sender.
64 // By default, the DefaultResponsePublisher is used.
65 func SubscriberResponsePublisher(rp ResponsePublisher) SubscriberOption {
66 return func(s *Subscriber) { s.responsePublisher = rp }
67 }
68
69 // SubscriberErrorEncoder is used to encode errors to the subscriber reply
70 // whenever they're encountered in the processing of a request. Clients can
71 // use this to provide custom error formatting. By default,
72 // errors will be published with the DefaultErrorEncoder.
73 func SubscriberErrorEncoder(ee ErrorEncoder) SubscriberOption {
74 return func(s *Subscriber) { s.errorEncoder = ee }
75 }
76
77 // SubscriberErrorLogger is used to log non-terminal errors. By default, no errors
78 // are logged. This is intended as a diagnostic measure. Finer-grained control
79 // of error handling, including logging in more detail, should be performed in a
80 // custom SubscriberErrorEncoder which has access to the context.
81 // Deprecated: Use SubscriberErrorHandler instead.
82 func SubscriberErrorLogger(logger log.Logger) SubscriberOption {
83 return func(s *Subscriber) { s.errorHandler = transport.NewLogErrorHandler(logger) }
84 }
85
86 // SubscriberErrorHandler is used to handle non-terminal errors. By default, non-terminal errors
87 // are ignored. This is intended as a diagnostic measure. Finer-grained control
88 // of error handling, including logging in more detail, should be performed in a
89 // custom SubscriberErrorEncoder which has access to the context.
90 func SubscriberErrorHandler(errorHandler transport.ErrorHandler) SubscriberOption {
91 return func(s *Subscriber) { s.errorHandler = errorHandler }
92 }
93
94 // ServeDelivery handles AMQP Delivery messages
95 // It is strongly recommended to use *amqp.Channel as the
96 // Channel interface implementation.
97 func (s Subscriber) ServeDelivery(ch Channel) func(deliv *amqp.Delivery) {
98 return func(deliv *amqp.Delivery) {
99 ctx, cancel := context.WithCancel(context.Background())
100 defer cancel()
101
102 pub := amqp.Publishing{}
103
104 for _, f := range s.before {
105 ctx = f(ctx, &pub, deliv)
106 }
107
108 request, err := s.dec(ctx, deliv)
109 if err != nil {
110 s.errorHandler.Handle(ctx, err)
111 s.errorEncoder(ctx, err, deliv, ch, &pub)
112 return
113 }
114
115 response, err := s.e(ctx, request)
116 if err != nil {
117 s.errorHandler.Handle(ctx, err)
118 s.errorEncoder(ctx, err, deliv, ch, &pub)
119 return
120 }
121
122 for _, f := range s.after {
123 ctx = f(ctx, deliv, ch, &pub)
124 }
125
126 if err := s.enc(ctx, &pub, response); err != nil {
127 s.errorHandler.Handle(ctx, err)
128 s.errorEncoder(ctx, err, deliv, ch, &pub)
129 return
130 }
131
132 if err := s.responsePublisher(ctx, deliv, ch, &pub); err != nil {
133 s.errorHandler.Handle(ctx, err)
134 s.errorEncoder(ctx, err, deliv, ch, &pub)
135 return
136 }
137 }
138
139 }
140
141 // EncodeJSONResponse marshals the response as JSON as part of the
142 // payload of the AMQP Publishing object.
143 func EncodeJSONResponse(
144 ctx context.Context,
145 pub *amqp.Publishing,
146 response interface{},
147 ) error {
148 b, err := json.Marshal(response)
149 if err != nil {
150 return err
151 }
152 pub.Body = b
153 return nil
154 }
155
156 // EncodeNopResponse is a response function that does nothing.
157 func EncodeNopResponse(
158 ctx context.Context,
159 pub *amqp.Publishing,
160 response interface{},
161 ) error {
162 return nil
163 }
164
165 // ResponsePublisher functions are executed by the subscriber to
166 // publish response object to the original sender.
167 // Please note that the word "publisher" does not refer
168 // to the publisher of pub/sub.
169 // Rather, publisher is merely a function that publishes, or sends responses.
170 type ResponsePublisher func(
171 context.Context,
172 *amqp.Delivery,
173 Channel,
174 *amqp.Publishing,
175 ) error
176
177 // DefaultResponsePublisher extracts the reply exchange and reply key
178 // from the request, and sends the response object to that destination.
179 func DefaultResponsePublisher(
180 ctx context.Context,
181 deliv *amqp.Delivery,
182 ch Channel,
183 pub *amqp.Publishing,
184 ) error {
185 if pub.CorrelationId == "" {
186 pub.CorrelationId = deliv.CorrelationId
187 }
188
189 replyExchange := getPublishExchange(ctx)
190 replyTo := getPublishKey(ctx)
191 if replyTo == "" {
192 replyTo = deliv.ReplyTo
193 }
194
195 return ch.Publish(
196 replyExchange,
197 replyTo,
198 false, // mandatory
199 false, // immediate
200 *pub,
201 )
202 }
203
204 // NopResponsePublisher does not deliver a response to the original sender.
205 // This response publisher is used when the user wants the subscriber to
206 // receive and forget.
207 func NopResponsePublisher(
208 ctx context.Context,
209 deliv *amqp.Delivery,
210 ch Channel,
211 pub *amqp.Publishing,
212 ) error {
213 return nil
214 }
215
216 // ErrorEncoder is responsible for encoding an error to the subscriber reply.
217 // Users are encouraged to use custom ErrorEncoders to encode errors to
218 // their replies, and will likely want to pass and check for their own error
219 // types.
220 type ErrorEncoder func(ctx context.Context,
221 err error, deliv *amqp.Delivery, ch Channel, pub *amqp.Publishing)
222
223 // DefaultErrorEncoder simply ignores the message. It does not reply
224 // nor Ack/Nack the message.
225 func DefaultErrorEncoder(ctx context.Context,
226 err error, deliv *amqp.Delivery, ch Channel, pub *amqp.Publishing) {
227 }
228
229 // SingleNackRequeueErrorEncoder issues a Nack to the delivery with multiple flag set as false
230 // and requeue flag set as true. It does not reply the message.
231 func SingleNackRequeueErrorEncoder(ctx context.Context,
232 err error, deliv *amqp.Delivery, ch Channel, pub *amqp.Publishing) {
233 deliv.Nack(
234 false, //multiple
235 true, //requeue
236 )
237 duration := getNackSleepDuration(ctx)
238 time.Sleep(duration)
239 }
240
241 // ReplyErrorEncoder serializes the error message as a DefaultErrorResponse
242 // JSON and sends the message to the ReplyTo address.
243 func ReplyErrorEncoder(
244 ctx context.Context,
245 err error,
246 deliv *amqp.Delivery,
247 ch Channel,
248 pub *amqp.Publishing,
249 ) {
250
251 if pub.CorrelationId == "" {
252 pub.CorrelationId = deliv.CorrelationId
253 }
254
255 replyExchange := getPublishExchange(ctx)
256 replyTo := getPublishKey(ctx)
257 if replyTo == "" {
258 replyTo = deliv.ReplyTo
259 }
260
261 response := DefaultErrorResponse{err.Error()}
262
263 b, err := json.Marshal(response)
264 if err != nil {
265 return
266 }
267 pub.Body = b
268
269 ch.Publish(
270 replyExchange,
271 replyTo,
272 false, // mandatory
273 false, // immediate
274 *pub,
275 )
276 }
277
278 // ReplyAndAckErrorEncoder serializes the error message as a DefaultErrorResponse
279 // JSON and sends the message to the ReplyTo address then Acks the original
280 // message.
281 func ReplyAndAckErrorEncoder(ctx context.Context, err error, deliv *amqp.Delivery, ch Channel, pub *amqp.Publishing) {
282 ReplyErrorEncoder(ctx, err, deliv, ch, pub)
283 deliv.Ack(false)
284 }
285
286 // DefaultErrorResponse is the default structure of responses in the event
287 // of an error.
288 type DefaultErrorResponse struct {
289 Error string `json:"err"`
290 }
291
292 // Channel is a channel interface to make testing possible.
293 // It is highly recommended to use *amqp.Channel as the interface implementation.
294 type Channel interface {
295 Publish(exchange, key string, mandatory, immediate bool, msg amqp.Publishing) error
296 Consume(queue, consumer string, autoAck, exclusive, noLocal, noWail bool, args amqp.Table) (<-chan amqp.Delivery, error)
297 }
0 package amqp_test
1
2 import (
3 "context"
4 "encoding/json"
5 "errors"
6 "testing"
7 "time"
8
9 amqptransport "github.com/go-kit/kit/transport/amqp"
10 amqp "github.com/rabbitmq/amqp091-go"
11 )
12
13 var (
14 errTypeAssertion = errors.New("type assertion error")
15 )
16
17 // mockChannel is a mock of *amqp.Channel.
18 type mockChannel struct {
19 f func(exchange, key string, mandatory, immediate bool)
20 c chan<- amqp.Publishing
21 deliveries []amqp.Delivery
22 }
23
24 // Publish runs a test function f and sends resultant message to a channel.
25 func (ch *mockChannel) Publish(exchange, key string, mandatory, immediate bool, msg amqp.Publishing) error {
26 ch.f(exchange, key, mandatory, immediate)
27 ch.c <- msg
28 return nil
29 }
30
31 var nullFunc = func(exchange, key string, mandatory, immediate bool) {
32 }
33
34 func (ch *mockChannel) Consume(queue, consumer string, autoAck, exclusive, noLocal, noWail bool, args amqp.Table) (<-chan amqp.Delivery, error) {
35 c := make(chan amqp.Delivery, len(ch.deliveries))
36 for _, d := range ch.deliveries {
37 c <- d
38 }
39 return c, nil
40 }
41
42 // TestSubscriberBadDecode checks if decoder errors are handled properly.
43 func TestSubscriberBadDecode(t *testing.T) {
44 sub := amqptransport.NewSubscriber(
45 func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil },
46 func(context.Context, *amqp.Delivery) (interface{}, error) { return nil, errors.New("err!") },
47 func(context.Context, *amqp.Publishing, interface{}) error {
48 return nil
49 },
50 amqptransport.SubscriberErrorEncoder(amqptransport.ReplyErrorEncoder),
51 )
52
53 outputChan := make(chan amqp.Publishing, 1)
54 ch := &mockChannel{f: nullFunc, c: outputChan}
55 sub.ServeDelivery(ch)(&amqp.Delivery{})
56
57 var msg amqp.Publishing
58 select {
59 case msg = <-outputChan:
60 break
61
62 case <-time.After(100 * time.Millisecond):
63 t.Fatal("Timed out waiting for publishing")
64 }
65 res, err := decodeSubscriberError(msg)
66 if err != nil {
67 t.Fatal(err)
68 }
69 if want, have := "err!", res.Error; want != have {
70 t.Errorf("want %s, have %s", want, have)
71 }
72 }
73
74 // TestSubscriberBadEndpoint checks if endpoint errors are handled properly.
75 func TestSubscriberBadEndpoint(t *testing.T) {
76 sub := amqptransport.NewSubscriber(
77 func(context.Context, interface{}) (interface{}, error) { return nil, errors.New("err!") },
78 func(context.Context, *amqp.Delivery) (interface{}, error) { return struct{}{}, nil },
79 func(context.Context, *amqp.Publishing, interface{}) error {
80 return nil
81 },
82 amqptransport.SubscriberErrorEncoder(amqptransport.ReplyErrorEncoder),
83 )
84
85 outputChan := make(chan amqp.Publishing, 1)
86 ch := &mockChannel{f: nullFunc, c: outputChan}
87 sub.ServeDelivery(ch)(&amqp.Delivery{})
88
89 var msg amqp.Publishing
90
91 select {
92 case msg = <-outputChan:
93 break
94
95 case <-time.After(100 * time.Millisecond):
96 t.Fatal("Timed out waiting for publishing")
97 }
98
99 res, err := decodeSubscriberError(msg)
100 if err != nil {
101 t.Fatal(err)
102 }
103 if want, have := "err!", res.Error; want != have {
104 t.Errorf("want %s, have %s", want, have)
105 }
106 }
107
108 // TestSubscriberBadEncoder checks if encoder errors are handled properly.
109 func TestSubscriberBadEncoder(t *testing.T) {
110 sub := amqptransport.NewSubscriber(
111 func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil },
112 func(context.Context, *amqp.Delivery) (interface{}, error) { return struct{}{}, nil },
113 func(context.Context, *amqp.Publishing, interface{}) error {
114 return errors.New("err!")
115 },
116 amqptransport.SubscriberErrorEncoder(amqptransport.ReplyErrorEncoder),
117 )
118
119 outputChan := make(chan amqp.Publishing, 1)
120 ch := &mockChannel{f: nullFunc, c: outputChan}
121 sub.ServeDelivery(ch)(&amqp.Delivery{})
122
123 var msg amqp.Publishing
124
125 select {
126 case msg = <-outputChan:
127 break
128
129 case <-time.After(100 * time.Millisecond):
130 t.Fatal("Timed out waiting for publishing")
131 }
132
133 res, err := decodeSubscriberError(msg)
134 if err != nil {
135 t.Fatal(err)
136 }
137 if want, have := "err!", res.Error; want != have {
138 t.Errorf("want %s, have %s", want, have)
139 }
140 }
141
142 // TestSubscriberSuccess checks if CorrelationId and ReplyTo are set properly
143 // and if the payload is encoded properly.
144 func TestSubscriberSuccess(t *testing.T) {
145 cid := "correlation"
146 replyTo := "sender"
147 obj := testReq{
148 Squadron: 436,
149 }
150 b, err := json.Marshal(obj)
151 if err != nil {
152 t.Fatal(err)
153 }
154
155 sub := amqptransport.NewSubscriber(
156 testEndpoint,
157 testReqDecoder,
158 amqptransport.EncodeJSONResponse,
159 amqptransport.SubscriberErrorEncoder(amqptransport.ReplyErrorEncoder),
160 )
161
162 checkReplyToFunc := func(exchange, key string, mandatory, immediate bool) {
163 if want, have := replyTo, key; want != have {
164 t.Errorf("want %s, have %s", want, have)
165 }
166 }
167
168 outputChan := make(chan amqp.Publishing, 1)
169 ch := &mockChannel{f: checkReplyToFunc, c: outputChan}
170 sub.ServeDelivery(ch)(&amqp.Delivery{
171 CorrelationId: cid,
172 ReplyTo: replyTo,
173 Body: b,
174 })
175
176 var msg amqp.Publishing
177
178 select {
179 case msg = <-outputChan:
180 break
181
182 case <-time.After(100 * time.Millisecond):
183 t.Fatal("Timed out waiting for publishing")
184 }
185
186 if want, have := cid, msg.CorrelationId; want != have {
187 t.Errorf("want %s, have %s", want, have)
188 }
189
190 // check if error is not thrown
191 errRes, err := decodeSubscriberError(msg)
192 if err != nil {
193 t.Fatal(err)
194 }
195 if errRes.Error != "" {
196 t.Error("Received error from subscriber", errRes.Error)
197 return
198 }
199
200 // check obj vals
201 response, err := testResDecoder(msg.Body)
202 if err != nil {
203 t.Fatal(err)
204 }
205 res, ok := response.(testRes)
206 if !ok {
207 t.Error(errTypeAssertion)
208 }
209
210 if want, have := obj.Squadron, res.Squadron; want != have {
211 t.Errorf("want %d, have %d", want, have)
212 }
213 if want, have := names[obj.Squadron], res.Name; want != have {
214 t.Errorf("want %s, have %s", want, have)
215 }
216 }
217
218 // TestNopResponseSubscriber checks if setting responsePublisher to
219 // NopResponsePublisher works properly by disabling response.
220 func TestNopResponseSubscriber(t *testing.T) {
221 cid := "correlation"
222 replyTo := "sender"
223 obj := testReq{
224 Squadron: 436,
225 }
226 b, err := json.Marshal(obj)
227 if err != nil {
228 t.Fatal(err)
229 }
230
231 sub := amqptransport.NewSubscriber(
232 testEndpoint,
233 testReqDecoder,
234 amqptransport.EncodeJSONResponse,
235 amqptransport.SubscriberResponsePublisher(amqptransport.NopResponsePublisher),
236 amqptransport.SubscriberErrorEncoder(amqptransport.ReplyErrorEncoder),
237 )
238
239 checkReplyToFunc := func(exchange, key string, mandatory, immediate bool) {}
240
241 outputChan := make(chan amqp.Publishing, 1)
242 ch := &mockChannel{f: checkReplyToFunc, c: outputChan}
243 sub.ServeDelivery(ch)(&amqp.Delivery{
244 CorrelationId: cid,
245 ReplyTo: replyTo,
246 Body: b,
247 })
248
249 select {
250 case <-outputChan:
251 t.Fatal("Subscriber with NopResponsePublisher replied.")
252 case <-time.After(100 * time.Millisecond):
253 break
254 }
255 }
256
257 // TestSubscriberMultipleBefore checks if options to set exchange, key, deliveryMode
258 // are working.
259 func TestSubscriberMultipleBefore(t *testing.T) {
260 exchange := "some exchange"
261 key := "some key"
262 deliveryMode := uint8(127)
263 contentType := "some content type"
264 contentEncoding := "some content encoding"
265 sub := amqptransport.NewSubscriber(
266 func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil },
267 func(context.Context, *amqp.Delivery) (interface{}, error) { return struct{}{}, nil },
268 amqptransport.EncodeJSONResponse,
269 amqptransport.SubscriberErrorEncoder(amqptransport.ReplyErrorEncoder),
270 amqptransport.SubscriberBefore(
271 amqptransport.SetPublishExchange(exchange),
272 amqptransport.SetPublishKey(key),
273 amqptransport.SetPublishDeliveryMode(deliveryMode),
274 amqptransport.SetContentType(contentType),
275 amqptransport.SetContentEncoding(contentEncoding),
276 ),
277 )
278 checkReplyToFunc := func(exch, k string, mandatory, immediate bool) {
279 if want, have := exchange, exch; want != have {
280 t.Errorf("want %s, have %s", want, have)
281 }
282 if want, have := key, k; want != have {
283 t.Errorf("want %s, have %s", want, have)
284 }
285 }
286
287 outputChan := make(chan amqp.Publishing, 1)
288 ch := &mockChannel{f: checkReplyToFunc, c: outputChan}
289 sub.ServeDelivery(ch)(&amqp.Delivery{})
290
291 var msg amqp.Publishing
292
293 select {
294 case msg = <-outputChan:
295 break
296
297 case <-time.After(100 * time.Millisecond):
298 t.Fatal("Timed out waiting for publishing")
299 }
300
301 // check if error is not thrown
302 errRes, err := decodeSubscriberError(msg)
303 if err != nil {
304 t.Fatal(err)
305 }
306 if errRes.Error != "" {
307 t.Error("Received error from subscriber", errRes.Error)
308 return
309 }
310
311 if want, have := contentType, msg.ContentType; want != have {
312 t.Errorf("want %s, have %s", want, have)
313 }
314
315 if want, have := contentEncoding, msg.ContentEncoding; want != have {
316 t.Errorf("want %s, have %s", want, have)
317 }
318
319 if want, have := deliveryMode, msg.DeliveryMode; want != have {
320 t.Errorf("want %d, have %d", want, have)
321 }
322 }
323
324 // TestDefaultContentMetaData checks that default ContentType and Content-Encoding
325 // is not set as mentioned by AMQP specification.
326 func TestDefaultContentMetaData(t *testing.T) {
327 defaultContentType := ""
328 defaultContentEncoding := ""
329 sub := amqptransport.NewSubscriber(
330 func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil },
331 func(context.Context, *amqp.Delivery) (interface{}, error) { return struct{}{}, nil },
332 amqptransport.EncodeJSONResponse,
333 amqptransport.SubscriberErrorEncoder(amqptransport.ReplyErrorEncoder),
334 )
335 checkReplyToFunc := func(exch, k string, mandatory, immediate bool) {}
336 outputChan := make(chan amqp.Publishing, 1)
337 ch := &mockChannel{f: checkReplyToFunc, c: outputChan}
338 sub.ServeDelivery(ch)(&amqp.Delivery{})
339
340 var msg amqp.Publishing
341
342 select {
343 case msg = <-outputChan:
344 break
345
346 case <-time.After(100 * time.Millisecond):
347 t.Fatal("Timed out waiting for publishing")
348 }
349
350 // check if error is not thrown
351 errRes, err := decodeSubscriberError(msg)
352 if err != nil {
353 t.Fatal(err)
354 }
355 if errRes.Error != "" {
356 t.Error("Received error from subscriber", errRes.Error)
357 return
358 }
359
360 if want, have := defaultContentType, msg.ContentType; want != have {
361 t.Errorf("want %s, have %s", want, have)
362 }
363 if want, have := defaultContentEncoding, msg.ContentEncoding; want != have {
364 t.Errorf("want %s, have %s", want, have)
365 }
366 }
367
368 func decodeSubscriberError(pub amqp.Publishing) (amqptransport.DefaultErrorResponse, error) {
369 var res amqptransport.DefaultErrorResponse
370 err := json.Unmarshal(pub.Body, &res)
371 return res, err
372 }
373
374 type testReq struct {
375 Squadron int `json:"s"`
376 }
377 type testRes struct {
378 Squadron int `json:"s"`
379 Name string `json:"n"`
380 }
381
382 func testEndpoint(_ context.Context, request interface{}) (interface{}, error) {
383 req, ok := request.(testReq)
384 if !ok {
385 return nil, errTypeAssertion
386 }
387 name, prs := names[req.Squadron]
388 if !prs {
389 return nil, errors.New("unknown squadron name")
390 }
391 res := testRes{
392 Squadron: req.Squadron,
393 Name: name,
394 }
395 return res, nil
396 }
397
398 func testReqDecoder(_ context.Context, d *amqp.Delivery) (interface{}, error) {
399 var obj testReq
400 err := json.Unmarshal(d.Body, &obj)
401 return obj, err
402 }
403
404 func testReqEncoder(_ context.Context, p *amqp.Publishing, request interface{}) error {
405 req, ok := request.(testReq)
406 if !ok {
407 return errors.New("type assertion failure")
408 }
409 b, err := json.Marshal(req)
410 if err != nil {
411 return err
412 }
413 p.Body = b
414 return nil
415 }
416
417 func testResDeliveryDecoder(_ context.Context, d *amqp.Delivery) (interface{}, error) {
418 return testResDecoder(d.Body)
419 }
420
421 func testResDecoder(b []byte) (interface{}, error) {
422 var obj testRes
423 err := json.Unmarshal(b, &obj)
424 return obj, err
425 }
426
427 var names = map[int]string{
428 424: "tiger",
429 426: "thunderbird",
430 429: "bison",
431 436: "tusker",
432 437: "husky",
433 }
0 package amqp
1
2 import (
3 "math/rand"
4 )
5
6 func randomString(l int) string {
7 bytes := make([]byte, l)
8 for i := 0; i < l; i++ {
9 bytes[i] = byte(randInt(65, 90))
10 }
11 return string(bytes)
12 }
13
14 func randInt(min int, max int) int {
15 return min + rand.Intn(max-min)
16 }
0 // Package awslambda provides an AWS Lambda transport layer.
1 package awslambda
0 package awslambda
1
2 import (
3 "context"
4 )
5
6 // DecodeRequestFunc extracts a user-domain request object from an
7 // AWS Lambda payload.
8 type DecodeRequestFunc func(context.Context, []byte) (interface{}, error)
9
10 // EncodeResponseFunc encodes the passed response object into []byte,
11 // ready to be sent as AWS Lambda response.
12 type EncodeResponseFunc func(context.Context, interface{}) ([]byte, error)
13
14 // ErrorEncoder is responsible for encoding an error.
15 type ErrorEncoder func(ctx context.Context, err error) ([]byte, error)
0 package awslambda
1
2 import (
3 "context"
4
5 "github.com/go-kit/kit/endpoint"
6 "github.com/go-kit/kit/transport"
7 "github.com/go-kit/log"
8 )
9
10 // Handler wraps an endpoint.
11 type Handler struct {
12 e endpoint.Endpoint
13 dec DecodeRequestFunc
14 enc EncodeResponseFunc
15 before []HandlerRequestFunc
16 after []HandlerResponseFunc
17 errorEncoder ErrorEncoder
18 finalizer []HandlerFinalizerFunc
19 errorHandler transport.ErrorHandler
20 }
21
22 // NewHandler constructs a new handler, which implements
23 // the AWS lambda.Handler interface.
24 func NewHandler(
25 e endpoint.Endpoint,
26 dec DecodeRequestFunc,
27 enc EncodeResponseFunc,
28 options ...HandlerOption,
29 ) *Handler {
30 h := &Handler{
31 e: e,
32 dec: dec,
33 enc: enc,
34 errorEncoder: DefaultErrorEncoder,
35 errorHandler: transport.NewLogErrorHandler(log.NewNopLogger()),
36 }
37 for _, option := range options {
38 option(h)
39 }
40 return h
41 }
42
43 // HandlerOption sets an optional parameter for handlers.
44 type HandlerOption func(*Handler)
45
46 // HandlerBefore functions are executed on the payload byte,
47 // before the request is decoded.
48 func HandlerBefore(before ...HandlerRequestFunc) HandlerOption {
49 return func(h *Handler) { h.before = append(h.before, before...) }
50 }
51
52 // HandlerAfter functions are only executed after invoking the endpoint
53 // but prior to returning a response.
54 func HandlerAfter(after ...HandlerResponseFunc) HandlerOption {
55 return func(h *Handler) { h.after = append(h.after, after...) }
56 }
57
58 // HandlerErrorLogger is used to log non-terminal errors.
59 // By default, no errors are logged.
60 // Deprecated: Use HandlerErrorHandler instead.
61 func HandlerErrorLogger(logger log.Logger) HandlerOption {
62 return func(h *Handler) { h.errorHandler = transport.NewLogErrorHandler(logger) }
63 }
64
65 // HandlerErrorHandler is used to handle non-terminal errors.
66 // By default, non-terminal errors are ignored.
67 func HandlerErrorHandler(errorHandler transport.ErrorHandler) HandlerOption {
68 return func(h *Handler) { h.errorHandler = errorHandler }
69 }
70
71 // HandlerErrorEncoder is used to encode errors.
72 func HandlerErrorEncoder(ee ErrorEncoder) HandlerOption {
73 return func(h *Handler) { h.errorEncoder = ee }
74 }
75
76 // HandlerFinalizer sets finalizer which are called at the end of
77 // request. By default no finalizer is registered.
78 func HandlerFinalizer(f ...HandlerFinalizerFunc) HandlerOption {
79 return func(h *Handler) { h.finalizer = append(h.finalizer, f...) }
80 }
81
82 // DefaultErrorEncoder defines the default behavior of encoding an error response,
83 // where it returns nil, and the error itself.
84 func DefaultErrorEncoder(ctx context.Context, err error) ([]byte, error) {
85 return nil, err
86 }
87
88 // Invoke represents implementation of the AWS lambda.Handler interface.
89 func (h *Handler) Invoke(
90 ctx context.Context,
91 payload []byte,
92 ) (resp []byte, err error) {
93 if len(h.finalizer) > 0 {
94 defer func() {
95 for _, f := range h.finalizer {
96 f(ctx, resp, err)
97 }
98 }()
99 }
100
101 for _, f := range h.before {
102 ctx = f(ctx, payload)
103 }
104
105 request, err := h.dec(ctx, payload)
106 if err != nil {
107 h.errorHandler.Handle(ctx, err)
108 return h.errorEncoder(ctx, err)
109 }
110
111 response, err := h.e(ctx, request)
112 if err != nil {
113 h.errorHandler.Handle(ctx, err)
114 return h.errorEncoder(ctx, err)
115 }
116
117 for _, f := range h.after {
118 ctx = f(ctx, response)
119 }
120
121 if resp, err = h.enc(ctx, response); err != nil {
122 h.errorHandler.Handle(ctx, err)
123 return h.errorEncoder(ctx, err)
124 }
125
126 return resp, err
127 }
0 package awslambda
1
2 import (
3 "context"
4 "encoding/json"
5 "fmt"
6 "testing"
7
8 "github.com/go-kit/kit/endpoint"
9 "github.com/go-kit/kit/transport"
10 "github.com/go-kit/log"
11 )
12
13 type key int
14
15 const (
16 KeyBeforeOne key = iota
17 KeyBeforeTwo key = iota
18 KeyAfterOne key = iota
19 KeyEncMode key = iota
20 )
21
22 // Created based on github.com/aws/aws-lambda-go@v1.13.3/events.APIGatewayProxyRequest for the purposes of the tests below.
23 type apiGatewayProxyRequest struct {
24 Body string `json:"body"`
25 }
26
27 // Created based on github.com/aws/aws-lambda-go@v1.13.3/events.APIGatewayProxyResponse for the purposes of the tests below.
28 type apiGatewayProxyResponse struct {
29 StatusCode int `json:"statusCode"`
30 Body string `json:"body"`
31 }
32
33 func TestDefaultErrorEncoder(t *testing.T) {
34 ctx := context.Background()
35 rootErr := fmt.Errorf("root")
36 b, err := DefaultErrorEncoder(ctx, rootErr)
37 if b != nil {
38 t.Fatalf("DefaultErrorEncoder should return nil as []byte")
39 }
40 if err != rootErr {
41 t.Fatalf("DefaultErrorEncoder expects return back the given error.")
42 }
43 }
44
45 func TestInvokeHappyPath(t *testing.T) {
46 svc := serviceTest01{}
47
48 helloHandler := NewHandler(
49 makeTest01HelloEndpoint(svc),
50 decodeHelloRequestWithTwoBefores,
51 encodeResponse,
52 HandlerErrorHandler(transport.NewLogErrorHandler(log.NewNopLogger())),
53 HandlerBefore(func(
54 ctx context.Context,
55 payload []byte,
56 ) context.Context {
57 ctx = context.WithValue(ctx, KeyBeforeOne, "bef1")
58 return ctx
59 }),
60 HandlerBefore(func(
61 ctx context.Context,
62 payload []byte,
63 ) context.Context {
64 ctx = context.WithValue(ctx, KeyBeforeTwo, "bef2")
65 return ctx
66 }),
67 HandlerAfter(func(
68 ctx context.Context,
69 response interface{},
70 ) context.Context {
71 ctx = context.WithValue(ctx, KeyAfterOne, "af1")
72 return ctx
73 }),
74 HandlerAfter(func(
75 ctx context.Context,
76 response interface{},
77 ) context.Context {
78 if _, ok := ctx.Value(KeyAfterOne).(string); !ok {
79 t.Fatalf("Value was not set properly during multi HandlerAfter")
80 }
81 return ctx
82 }),
83 HandlerFinalizer(func(
84 _ context.Context,
85 resp []byte,
86 _ error,
87 ) {
88 apigwResp := apiGatewayProxyResponse{}
89 err := json.Unmarshal(resp, &apigwResp)
90 if err != nil {
91 t.Fatalf("Should have no error, but got: %+v", err)
92 }
93
94 response := helloResponse{}
95 err = json.Unmarshal([]byte(apigwResp.Body), &response)
96 if err != nil {
97 t.Fatalf("Should have no error, but got: %+v", err)
98 }
99
100 expectedGreeting := "hello john doe bef1 bef2"
101 if response.Greeting != expectedGreeting {
102 t.Fatalf(
103 "Expect: %s, Actual: %s", expectedGreeting, response.Greeting)
104 }
105 }),
106 )
107
108 ctx := context.Background()
109 req, _ := json.Marshal(apiGatewayProxyRequest{
110 Body: `{"name":"john doe"}`,
111 })
112 resp, err := helloHandler.Invoke(ctx, req)
113
114 if err != nil {
115 t.Fatalf("Should have no error, but got: %+v", err)
116 }
117
118 apigwResp := apiGatewayProxyResponse{}
119 err = json.Unmarshal(resp, &apigwResp)
120 if err != nil {
121 t.Fatalf("Should have no error, but got: %+v", err)
122 }
123
124 response := helloResponse{}
125 err = json.Unmarshal([]byte(apigwResp.Body), &response)
126 if err != nil {
127 t.Fatalf("Should have no error, but got: %+v", err)
128 }
129
130 expectedGreeting := "hello john doe bef1 bef2"
131 if response.Greeting != expectedGreeting {
132 t.Fatalf(
133 "Expect: %s, Actual: %s", expectedGreeting, response.Greeting)
134 }
135 }
136
137 func TestInvokeFailDecode(t *testing.T) {
138 svc := serviceTest01{}
139
140 helloHandler := NewHandler(
141 makeTest01HelloEndpoint(svc),
142 decodeHelloRequestWithTwoBefores,
143 encodeResponse,
144 HandlerErrorEncoder(func(
145 ctx context.Context,
146 err error,
147 ) ([]byte, error) {
148 apigwResp := apiGatewayProxyResponse{}
149 apigwResp.Body = `{"error":"yes"}`
150 apigwResp.StatusCode = 500
151 resp, err := json.Marshal(apigwResp)
152 return resp, err
153 }),
154 )
155
156 ctx := context.Background()
157 req, _ := json.Marshal(apiGatewayProxyRequest{
158 Body: `{"name":"john doe"}`,
159 })
160 resp, err := helloHandler.Invoke(ctx, req)
161
162 if err != nil {
163 t.Fatalf("Should have no error, but got: %+v", err)
164 }
165
166 apigwResp := apiGatewayProxyResponse{}
167 json.Unmarshal(resp, &apigwResp)
168 if apigwResp.StatusCode != 500 {
169 t.Fatalf("Expect status code of 500, instead of %d", apigwResp.StatusCode)
170 }
171 }
172
173 func TestInvokeFailEndpoint(t *testing.T) {
174 svc := serviceTest01{}
175
176 helloHandler := NewHandler(
177 makeTest01FailEndpoint(svc),
178 decodeHelloRequestWithTwoBefores,
179 encodeResponse,
180 HandlerBefore(func(
181 ctx context.Context,
182 payload []byte,
183 ) context.Context {
184 ctx = context.WithValue(ctx, KeyBeforeOne, "bef1")
185 return ctx
186 }),
187 HandlerBefore(func(
188 ctx context.Context,
189 payload []byte,
190 ) context.Context {
191 ctx = context.WithValue(ctx, KeyBeforeTwo, "bef2")
192 return ctx
193 }),
194 HandlerErrorEncoder(func(
195 ctx context.Context,
196 err error,
197 ) ([]byte, error) {
198 apigwResp := apiGatewayProxyResponse{}
199 apigwResp.Body = `{"error":"yes"}`
200 apigwResp.StatusCode = 500
201 resp, err := json.Marshal(apigwResp)
202 return resp, err
203 }),
204 )
205
206 ctx := context.Background()
207 req, _ := json.Marshal(apiGatewayProxyRequest{
208 Body: `{"name":"john doe"}`,
209 })
210 resp, err := helloHandler.Invoke(ctx, req)
211
212 if err != nil {
213 t.Fatalf("Should have no error, but got: %+v", err)
214 }
215
216 apigwResp := apiGatewayProxyResponse{}
217 json.Unmarshal(resp, &apigwResp)
218 if apigwResp.StatusCode != 500 {
219 t.Fatalf("Expect status code of 500, instead of %d", apigwResp.StatusCode)
220 }
221 }
222
223 func TestInvokeFailEncode(t *testing.T) {
224 svc := serviceTest01{}
225
226 helloHandler := NewHandler(
227 makeTest01HelloEndpoint(svc),
228 decodeHelloRequestWithTwoBefores,
229 encodeResponse,
230 HandlerBefore(func(
231 ctx context.Context,
232 payload []byte,
233 ) context.Context {
234 ctx = context.WithValue(ctx, KeyBeforeOne, "bef1")
235 return ctx
236 }),
237 HandlerBefore(func(
238 ctx context.Context,
239 payload []byte,
240 ) context.Context {
241 ctx = context.WithValue(ctx, KeyBeforeTwo, "bef2")
242 return ctx
243 }),
244 HandlerAfter(func(
245 ctx context.Context,
246 response interface{},
247 ) context.Context {
248 ctx = context.WithValue(ctx, KeyEncMode, "fail_encode")
249 return ctx
250 }),
251 HandlerErrorEncoder(func(
252 ctx context.Context,
253 err error,
254 ) ([]byte, error) {
255 // convert error into proper APIGateway response.
256 apigwResp := apiGatewayProxyResponse{}
257 apigwResp.Body = `{"error":"yes"}`
258 apigwResp.StatusCode = 500
259 resp, err := json.Marshal(apigwResp)
260 return resp, err
261 }),
262 )
263
264 ctx := context.Background()
265 req, _ := json.Marshal(apiGatewayProxyRequest{
266 Body: `{"name":"john doe"}`,
267 })
268 resp, err := helloHandler.Invoke(ctx, req)
269
270 if err != nil {
271 t.Fatalf("Should have no error, but got: %+v", err)
272 }
273
274 apigwResp := apiGatewayProxyResponse{}
275 json.Unmarshal(resp, &apigwResp)
276 if apigwResp.StatusCode != 500 {
277 t.Fatalf("Expect status code of 500, instead of %d", apigwResp.StatusCode)
278 }
279 }
280
281 func decodeHelloRequestWithTwoBefores(
282 ctx context.Context, req []byte,
283 ) (interface{}, error) {
284 apigwReq := apiGatewayProxyRequest{}
285 err := json.Unmarshal([]byte(req), &apigwReq)
286 if err != nil {
287 return apigwReq, err
288 }
289
290 request := helloRequest{}
291 err = json.Unmarshal([]byte(apigwReq.Body), &request)
292 if err != nil {
293 return request, err
294 }
295
296 valOne, ok := ctx.Value(KeyBeforeOne).(string)
297 if !ok {
298 return request, fmt.Errorf(
299 "Value was not set properly when multiple HandlerBefores are used")
300 }
301
302 valTwo, ok := ctx.Value(KeyBeforeTwo).(string)
303 if !ok {
304 return request, fmt.Errorf(
305 "Value was not set properly when multiple HandlerBefores are used")
306 }
307
308 request.Name += " " + valOne + " " + valTwo
309 return request, err
310 }
311
312 func encodeResponse(
313 ctx context.Context, response interface{},
314 ) ([]byte, error) {
315 apigwResp := apiGatewayProxyResponse{}
316
317 mode, ok := ctx.Value(KeyEncMode).(string)
318 if ok && mode == "fail_encode" {
319 return nil, fmt.Errorf("fail encoding")
320 }
321
322 respByte, err := json.Marshal(response)
323 if err != nil {
324 return nil, err
325 }
326
327 apigwResp.Body = string(respByte)
328 apigwResp.StatusCode = 200
329
330 resp, err := json.Marshal(apigwResp)
331 return resp, err
332 }
333
334 type helloRequest struct {
335 Name string `json:"name"`
336 }
337
338 type helloResponse struct {
339 Greeting string `json:"greeting"`
340 }
341
342 func makeTest01HelloEndpoint(svc serviceTest01) endpoint.Endpoint {
343 return func(_ context.Context, request interface{}) (interface{}, error) {
344 req := request.(helloRequest)
345 greeting := svc.hello(req.Name)
346 return helloResponse{greeting}, nil
347 }
348 }
349
350 func makeTest01FailEndpoint(_ serviceTest01) endpoint.Endpoint {
351 return func(_ context.Context, request interface{}) (interface{}, error) {
352 return nil, fmt.Errorf("test error endpoint")
353 }
354 }
355
356 type serviceTest01 struct{}
357
358 func (ts *serviceTest01) hello(name string) string {
359 return fmt.Sprintf("hello %s", name)
360 }
0 package awslambda
1
2 import (
3 "context"
4 )
5
6 // HandlerRequestFunc may take information from the received
7 // payload and use it to place items in the request scoped context.
8 // HandlerRequestFuncs are executed prior to invoking the endpoint and
9 // decoding of the payload.
10 type HandlerRequestFunc func(ctx context.Context, payload []byte) context.Context
11
12 // HandlerResponseFunc may take information from a request context
13 // and use it to manipulate the response before it's marshaled.
14 // HandlerResponseFunc are executed after invoking the endpoint
15 // but prior to returning a response.
16 type HandlerResponseFunc func(ctx context.Context, response interface{}) context.Context
17
18 // HandlerFinalizerFunc is executed at the end of Invoke.
19 // This can be used for logging purposes.
20 type HandlerFinalizerFunc func(ctx context.Context, resp []byte, err error)
0 // Package transport contains bindings to concrete transports.
0 // Package transport contains helpers applicable to all supported transports.
11 package transport
0 package transport
1
2 import (
3 "context"
4
5 "github.com/go-kit/log"
6 )
7
8 // ErrorHandler receives a transport error to be processed for diagnostic purposes.
9 // Usually this means logging the error.
10 type ErrorHandler interface {
11 Handle(ctx context.Context, err error)
12 }
13
14 // LogErrorHandler is a transport error handler implementation which logs an error.
15 type LogErrorHandler struct {
16 logger log.Logger
17 }
18
19 func NewLogErrorHandler(logger log.Logger) *LogErrorHandler {
20 return &LogErrorHandler{
21 logger: logger,
22 }
23 }
24
25 func (h *LogErrorHandler) Handle(ctx context.Context, err error) {
26 h.logger.Log("err", err)
27 }
28
29 // The ErrorHandlerFunc type is an adapter to allow the use of
30 // ordinary function as ErrorHandler. If f is a function
31 // with the appropriate signature, ErrorHandlerFunc(f) is a
32 // ErrorHandler that calls f.
33 type ErrorHandlerFunc func(ctx context.Context, err error)
34
35 // Handle calls f(ctx, err).
36 func (f ErrorHandlerFunc) Handle(ctx context.Context, err error) {
37 f(ctx, err)
38 }
0 package transport_test
1
2 import (
3 "context"
4 "errors"
5 "testing"
6
7 "github.com/go-kit/kit/transport"
8 "github.com/go-kit/log"
9 )
10
11 func TestLogErrorHandler(t *testing.T) {
12 var output []interface{}
13
14 logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error {
15 output = append(output, keyvals...)
16 return nil
17 }))
18
19 errorHandler := transport.NewLogErrorHandler(logger)
20
21 err := errors.New("error")
22
23 errorHandler.Handle(context.Background(), err)
24
25 if output[1] != err {
26 t.Errorf("expected an error log event: have %v, want %v", output[1], err)
27 }
28 }
1212 First, define your service using protobuf3. This is explained
1313 [in gRPC documentation](http://www.grpc.io/docs/#defining-a-service).
1414 See
15 [add.proto](https://github.com/go-kit/kit/blob/ec8b02591ee873433565a1ae9d317353412d1d27/examples/addsvc/pb/add.proto)
15 [addsvc.proto](https://github.com/go-kit/examples/blob/master/addsvc/pb/addsvc.proto)
1616 for an example. Make sure the proto definition matches your service's go-kit
1717 (interface) definition.
1818
4242 Finally, write a tiny binding from your service definition to the gRPC
4343 definition. It's a simple conversion from one domain to another.
4444 See
45 [grpc_binding.go](https://github.com/go-kit/kit/blob/ec8b02591ee873433565a1ae9d317353412d1d27/examples/addsvc/grpc_binding.go)
45 [grpc.go](https://github.com/go-kit/examples/blob/master/addsvc/pkg/addtransport/grpc.go)
4646 for an example.
4747
4848 That's it!
4949 The gRPC binding can be bound to a listener and serve normal gRPC requests.
5050 And within your service, you can use standard go-kit components and idioms.
51 See [addsvc](https://github.com/go-kit/kit/tree/master/examples/addsvc) for
51 See [addsvc](https://github.com/go-kit/examples/tree/master/addsvc/) for
5252 a complete working example with gRPC support. And remember: go-kit services
5353 can support multiple transports simultaneously.
00 package pb
11
2 //go:generate protoc test.proto --go_out=plugins=grpc:.
2 //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
0 // Code generated by protoc-gen-go.
0 // Code generated by protoc-gen-go. DO NOT EDIT.
1 // versions:
2 // protoc-gen-go v1.26.0
3 // protoc v3.16.0
14 // source: test.proto
2 // DO NOT EDIT!
3
4 /*
5 Package pb is a generated protocol buffer package.
6
7 It is generated from these files:
8 test.proto
9
10 It has these top-level messages:
11 TestRequest
12 TestResponse
13 */
5
146 package pb
157
16 import proto "github.com/golang/protobuf/proto"
17 import fmt "fmt"
18 import math "math"
19
208 import (
21 context "golang.org/x/net/context"
22 grpc "google.golang.org/grpc"
9 protoreflect "google.golang.org/protobuf/reflect/protoreflect"
10 protoimpl "google.golang.org/protobuf/runtime/protoimpl"
11 reflect "reflect"
12 sync "sync"
2313 )
2414
25 // Reference imports to suppress errors if they are not otherwise used.
26 var _ = proto.Marshal
27 var _ = fmt.Errorf
28 var _ = math.Inf
29
30 // This is a compile-time assertion to ensure that this generated file
31 // is compatible with the proto package it is being compiled against.
32 // A compilation error at this line likely means your copy of the
33 // proto package needs to be updated.
34 const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
15 const (
16 // Verify that this generated code is sufficiently up-to-date.
17 _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
18 // Verify that runtime/protoimpl is sufficiently up-to-date.
19 _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
20 )
3521
3622 type TestRequest struct {
37 A string `protobuf:"bytes,1,opt,name=a" json:"a,omitempty"`
38 B int64 `protobuf:"varint,2,opt,name=b" json:"b,omitempty"`
39 }
40
41 func (m *TestRequest) Reset() { *m = TestRequest{} }
42 func (m *TestRequest) String() string { return proto.CompactTextString(m) }
43 func (*TestRequest) ProtoMessage() {}
44 func (*TestRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
45
46 func (m *TestRequest) GetA() string {
47 if m != nil {
48 return m.A
23 state protoimpl.MessageState
24 sizeCache protoimpl.SizeCache
25 unknownFields protoimpl.UnknownFields
26
27 A string `protobuf:"bytes,1,opt,name=a,proto3" json:"a,omitempty"`
28 B int64 `protobuf:"varint,2,opt,name=b,proto3" json:"b,omitempty"`
29 }
30
31 func (x *TestRequest) Reset() {
32 *x = TestRequest{}
33 if protoimpl.UnsafeEnabled {
34 mi := &file_test_proto_msgTypes[0]
35 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
36 ms.StoreMessageInfo(mi)
37 }
38 }
39
40 func (x *TestRequest) String() string {
41 return protoimpl.X.MessageStringOf(x)
42 }
43
44 func (*TestRequest) ProtoMessage() {}
45
46 func (x *TestRequest) ProtoReflect() protoreflect.Message {
47 mi := &file_test_proto_msgTypes[0]
48 if protoimpl.UnsafeEnabled && x != nil {
49 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
50 if ms.LoadMessageInfo() == nil {
51 ms.StoreMessageInfo(mi)
52 }
53 return ms
54 }
55 return mi.MessageOf(x)
56 }
57
58 // Deprecated: Use TestRequest.ProtoReflect.Descriptor instead.
59 func (*TestRequest) Descriptor() ([]byte, []int) {
60 return file_test_proto_rawDescGZIP(), []int{0}
61 }
62
63 func (x *TestRequest) GetA() string {
64 if x != nil {
65 return x.A
4966 }
5067 return ""
5168 }
5269
53 func (m *TestRequest) GetB() int64 {
54 if m != nil {
55 return m.B
70 func (x *TestRequest) GetB() int64 {
71 if x != nil {
72 return x.B
5673 }
5774 return 0
5875 }
5976
6077 type TestResponse struct {
61 V string `protobuf:"bytes,1,opt,name=v" json:"v,omitempty"`
62 }
63
64 func (m *TestResponse) Reset() { *m = TestResponse{} }
65 func (m *TestResponse) String() string { return proto.CompactTextString(m) }
66 func (*TestResponse) ProtoMessage() {}
67 func (*TestResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
68
69 func (m *TestResponse) GetV() string {
70 if m != nil {
71 return m.V
78 state protoimpl.MessageState
79 sizeCache protoimpl.SizeCache
80 unknownFields protoimpl.UnknownFields
81
82 V string `protobuf:"bytes,1,opt,name=v,proto3" json:"v,omitempty"`
83 }
84
85 func (x *TestResponse) Reset() {
86 *x = TestResponse{}
87 if protoimpl.UnsafeEnabled {
88 mi := &file_test_proto_msgTypes[1]
89 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
90 ms.StoreMessageInfo(mi)
91 }
92 }
93
94 func (x *TestResponse) String() string {
95 return protoimpl.X.MessageStringOf(x)
96 }
97
98 func (*TestResponse) ProtoMessage() {}
99
100 func (x *TestResponse) ProtoReflect() protoreflect.Message {
101 mi := &file_test_proto_msgTypes[1]
102 if protoimpl.UnsafeEnabled && x != nil {
103 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
104 if ms.LoadMessageInfo() == nil {
105 ms.StoreMessageInfo(mi)
106 }
107 return ms
108 }
109 return mi.MessageOf(x)
110 }
111
112 // Deprecated: Use TestResponse.ProtoReflect.Descriptor instead.
113 func (*TestResponse) Descriptor() ([]byte, []int) {
114 return file_test_proto_rawDescGZIP(), []int{1}
115 }
116
117 func (x *TestResponse) GetV() string {
118 if x != nil {
119 return x.V
72120 }
73121 return ""
74122 }
75123
76 func init() {
77 proto.RegisterType((*TestRequest)(nil), "pb.TestRequest")
78 proto.RegisterType((*TestResponse)(nil), "pb.TestResponse")
79 }
80
81 // Reference imports to suppress errors if they are not otherwise used.
82 var _ context.Context
83 var _ grpc.ClientConn
84
85 // This is a compile-time assertion to ensure that this generated file
86 // is compatible with the grpc package it is being compiled against.
87 const _ = grpc.SupportPackageIsVersion4
88
89 // Client API for Test service
90
91 type TestClient interface {
92 Test(ctx context.Context, in *TestRequest, opts ...grpc.CallOption) (*TestResponse, error)
93 }
94
95 type testClient struct {
96 cc *grpc.ClientConn
97 }
98
99 func NewTestClient(cc *grpc.ClientConn) TestClient {
100 return &testClient{cc}
101 }
102
103 func (c *testClient) Test(ctx context.Context, in *TestRequest, opts ...grpc.CallOption) (*TestResponse, error) {
104 out := new(TestResponse)
105 err := grpc.Invoke(ctx, "/pb.Test/Test", in, out, c.cc, opts...)
106 if err != nil {
107 return nil, err
108 }
109 return out, nil
110 }
111
112 // Server API for Test service
113
114 type TestServer interface {
115 Test(context.Context, *TestRequest) (*TestResponse, error)
116 }
117
118 func RegisterTestServer(s *grpc.Server, srv TestServer) {
119 s.RegisterService(&_Test_serviceDesc, srv)
120 }
121
122 func _Test_Test_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
123 in := new(TestRequest)
124 if err := dec(in); err != nil {
125 return nil, err
126 }
127 if interceptor == nil {
128 return srv.(TestServer).Test(ctx, in)
129 }
130 info := &grpc.UnaryServerInfo{
131 Server: srv,
132 FullMethod: "/pb.Test/Test",
133 }
134 handler := func(ctx context.Context, req interface{}) (interface{}, error) {
135 return srv.(TestServer).Test(ctx, req.(*TestRequest))
136 }
137 return interceptor(ctx, in, info, handler)
138 }
139
140 var _Test_serviceDesc = grpc.ServiceDesc{
141 ServiceName: "pb.Test",
142 HandlerType: (*TestServer)(nil),
143 Methods: []grpc.MethodDesc{
144 {
145 MethodName: "Test",
146 Handler: _Test_Test_Handler,
124 var File_test_proto protoreflect.FileDescriptor
125
126 var file_test_proto_rawDesc = []byte{
127 0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x70, 0x62,
128 0x22, 0x29, 0x0a, 0x0b, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
129 0x0c, 0x0a, 0x01, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x61, 0x12, 0x0c, 0x0a,
130 0x01, 0x62, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x01, 0x62, 0x22, 0x1c, 0x0a, 0x0c, 0x54,
131 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0c, 0x0a, 0x01, 0x76,
132 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x01, 0x76, 0x32, 0x33, 0x0a, 0x04, 0x54, 0x65, 0x73,
133 0x74, 0x12, 0x2b, 0x0a, 0x04, 0x54, 0x65, 0x73, 0x74, 0x12, 0x0f, 0x2e, 0x70, 0x62, 0x2e, 0x54,
134 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x70, 0x62, 0x2e,
135 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x62, 0x06,
136 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
137 }
138
139 var (
140 file_test_proto_rawDescOnce sync.Once
141 file_test_proto_rawDescData = file_test_proto_rawDesc
142 )
143
144 func file_test_proto_rawDescGZIP() []byte {
145 file_test_proto_rawDescOnce.Do(func() {
146 file_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData)
147 })
148 return file_test_proto_rawDescData
149 }
150
151 var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
152 var file_test_proto_goTypes = []interface{}{
153 (*TestRequest)(nil), // 0: pb.TestRequest
154 (*TestResponse)(nil), // 1: pb.TestResponse
155 }
156 var file_test_proto_depIdxs = []int32{
157 0, // 0: pb.Test.Test:input_type -> pb.TestRequest
158 1, // 1: pb.Test.Test:output_type -> pb.TestResponse
159 1, // [1:2] is the sub-list for method output_type
160 0, // [0:1] is the sub-list for method input_type
161 0, // [0:0] is the sub-list for extension type_name
162 0, // [0:0] is the sub-list for extension extendee
163 0, // [0:0] is the sub-list for field type_name
164 }
165
166 func init() { file_test_proto_init() }
167 func file_test_proto_init() {
168 if File_test_proto != nil {
169 return
170 }
171 if !protoimpl.UnsafeEnabled {
172 file_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
173 switch v := v.(*TestRequest); i {
174 case 0:
175 return &v.state
176 case 1:
177 return &v.sizeCache
178 case 2:
179 return &v.unknownFields
180 default:
181 return nil
182 }
183 }
184 file_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
185 switch v := v.(*TestResponse); i {
186 case 0:
187 return &v.state
188 case 1:
189 return &v.sizeCache
190 case 2:
191 return &v.unknownFields
192 default:
193 return nil
194 }
195 }
196 }
197 type x struct{}
198 out := protoimpl.TypeBuilder{
199 File: protoimpl.DescBuilder{
200 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
201 RawDescriptor: file_test_proto_rawDesc,
202 NumEnums: 0,
203 NumMessages: 2,
204 NumExtensions: 0,
205 NumServices: 1,
147206 },
148 },
149 Streams: []grpc.StreamDesc{},
150 Metadata: "test.proto",
151 }
152
153 func init() { proto.RegisterFile("test.proto", fileDescriptor0) }
154
155 var fileDescriptor0 = []byte{
156 // 129 bytes of a gzipped FileDescriptorProto
157 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x49, 0x2d, 0x2e,
158 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2a, 0x48, 0x52, 0xd2, 0xe4, 0xe2, 0x0e, 0x49,
159 0x2d, 0x2e, 0x09, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0xe2, 0xe1, 0x62, 0x4c, 0x94, 0x60,
160 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x62, 0x4c, 0x04, 0xf1, 0x92, 0x24, 0x98, 0x14, 0x18, 0x35, 0x98,
161 0x83, 0x18, 0x93, 0x94, 0x64, 0xb8, 0x78, 0x20, 0x4a, 0x8b, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x41,
162 0xb2, 0x65, 0x30, 0xb5, 0x65, 0x46, 0xc6, 0x5c, 0x2c, 0x20, 0x59, 0x21, 0x6d, 0x28, 0xcd, 0xaf,
163 0x57, 0x90, 0xa4, 0x87, 0x64, 0xb4, 0x94, 0x00, 0x42, 0x00, 0x62, 0x80, 0x12, 0x43, 0x12, 0x1b,
164 0xd8, 0x21, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x49, 0xfc, 0xd8, 0xf1, 0x96, 0x00, 0x00,
165 0x00,
166 }
207 GoTypes: file_test_proto_goTypes,
208 DependencyIndexes: file_test_proto_depIdxs,
209 MessageInfos: file_test_proto_msgTypes,
210 }.Build()
211 File_test_proto = out.File
212 file_test_proto_rawDesc = nil
213 file_test_proto_goTypes = nil
214 file_test_proto_depIdxs = nil
215 }
0 // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
1
2 package pb
3
4 import (
5 context "context"
6 grpc "google.golang.org/grpc"
7 codes "google.golang.org/grpc/codes"
8 status "google.golang.org/grpc/status"
9 )
10
11 // This is a compile-time assertion to ensure that this generated file
12 // is compatible with the grpc package it is being compiled against.
13 // Requires gRPC-Go v1.32.0 or later.
14 const _ = grpc.SupportPackageIsVersion7
15
16 // TestClient is the client API for Test service.
17 //
18 // 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.
19 type TestClient interface {
20 Test(ctx context.Context, in *TestRequest, opts ...grpc.CallOption) (*TestResponse, error)
21 }
22
23 type testClient struct {
24 cc grpc.ClientConnInterface
25 }
26
27 func NewTestClient(cc grpc.ClientConnInterface) TestClient {
28 return &testClient{cc}
29 }
30
31 func (c *testClient) Test(ctx context.Context, in *TestRequest, opts ...grpc.CallOption) (*TestResponse, error) {
32 out := new(TestResponse)
33 err := c.cc.Invoke(ctx, "/pb.Test/Test", in, out, opts...)
34 if err != nil {
35 return nil, err
36 }
37 return out, nil
38 }
39
40 // TestServer is the server API for Test service.
41 // All implementations must embed UnimplementedTestServer
42 // for forward compatibility
43 type TestServer interface {
44 Test(context.Context, *TestRequest) (*TestResponse, error)
45 mustEmbedUnimplementedTestServer()
46 }
47
48 // UnimplementedTestServer must be embedded to have forward compatible implementations.
49 type UnimplementedTestServer struct {
50 }
51
52 func (UnimplementedTestServer) Test(context.Context, *TestRequest) (*TestResponse, error) {
53 return nil, status.Errorf(codes.Unimplemented, "method Test not implemented")
54 }
55 func (UnimplementedTestServer) mustEmbedUnimplementedTestServer() {}
56
57 // UnsafeTestServer may be embedded to opt out of forward compatibility for this service.
58 // Use of this interface is not recommended, as added methods to TestServer will
59 // result in compilation errors.
60 type UnsafeTestServer interface {
61 mustEmbedUnimplementedTestServer()
62 }
63
64 func RegisterTestServer(s grpc.ServiceRegistrar, srv TestServer) {
65 s.RegisterService(&Test_ServiceDesc, srv)
66 }
67
68 func _Test_Test_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
69 in := new(TestRequest)
70 if err := dec(in); err != nil {
71 return nil, err
72 }
73 if interceptor == nil {
74 return srv.(TestServer).Test(ctx, in)
75 }
76 info := &grpc.UnaryServerInfo{
77 Server: srv,
78 FullMethod: "/pb.Test/Test",
79 }
80 handler := func(ctx context.Context, req interface{}) (interface{}, error) {
81 return srv.(TestServer).Test(ctx, req.(*TestRequest))
82 }
83 return interceptor(ctx, in, info, handler)
84 }
85
86 // Test_ServiceDesc is the grpc.ServiceDesc for Test service.
87 // It's only intended for direct use with grpc.RegisterService,
88 // and not to be introspected or modified (even as a copy)
89 var Test_ServiceDesc = grpc.ServiceDesc{
90 ServiceName: "pb.Test",
91 HandlerType: (*TestServer)(nil),
92 Methods: []grpc.MethodDesc{
93 {
94 MethodName: "Test",
95 Handler: _Test_Test_Handler,
96 },
97 },
98 Streams: []grpc.StreamDesc{},
99 Metadata: "test.proto",
100 }
22 import (
33 "context"
44 "fmt"
5
6 oldcontext "golang.org/x/net/context"
75
86 "github.com/go-kit/kit/endpoint"
97 grpctransport "github.com/go-kit/kit/transport/grpc"
3230 }
3331
3432 type serverBinding struct {
33 pb.UnimplementedTestServer
34
3535 test grpctransport.Handler
3636 }
3737
38 func (b *serverBinding) Test(ctx oldcontext.Context, req *pb.TestRequest) (*pb.TestResponse, error) {
38 func (b *serverBinding) Test(ctx context.Context, req *pb.TestRequest) (*pb.TestResponse, error) {
3939 _, response, err := b.test.ServeGRPC(ctx, req)
4040 if err != nil {
4141 return nil, err
2121 grpcReply reflect.Type
2222 before []ClientRequestFunc
2323 after []ClientResponseFunc
24 finalizer []ClientFinalizerFunc
2425 }
2526
2627 // NewClient constructs a usable Client for a single remote endpoint.
7475 return func(c *Client) { c.after = append(c.after, after...) }
7576 }
7677
78 // ClientFinalizer is executed at the end of every gRPC request.
79 // By default, no finalizer is registered.
80 func ClientFinalizer(f ...ClientFinalizerFunc) ClientOption {
81 return func(s *Client) { s.finalizer = append(s.finalizer, f...) }
82 }
83
7784 // Endpoint returns a usable endpoint that will invoke the gRPC specified by the
7885 // client.
7986 func (c Client) Endpoint() endpoint.Endpoint {
80 return func(ctx context.Context, request interface{}) (interface{}, error) {
87 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
8188 ctx, cancel := context.WithCancel(ctx)
8289 defer cancel()
90
91 if c.finalizer != nil {
92 defer func() {
93 for _, f := range c.finalizer {
94 f(ctx, err)
95 }
96 }()
97 }
98
99 ctx = context.WithValue(ctx, ContextKeyRequestMethod, c.method)
83100
84101 req, err := c.enc(ctx, request)
85102 if err != nil {
94111
95112 var header, trailer metadata.MD
96113 grpcReply := reflect.New(c.grpcReply).Interface()
97 if err = grpc.Invoke(
98 ctx, c.method, req, grpcReply, c.client,
99 grpc.Header(&header), grpc.Trailer(&trailer),
114 if err = c.client.Invoke(
115 ctx, c.method, req, grpcReply, grpc.Header(&header),
116 grpc.Trailer(&trailer),
100117 ); err != nil {
101118 return nil, err
102119 }
105122 ctx = f(ctx, header, trailer)
106123 }
107124
108 response, err := c.dec(ctx, grpcReply)
125 response, err = c.dec(ctx, grpcReply)
109126 if err != nil {
110127 return nil, err
111128 }
112129 return response, nil
113130 }
114131 }
132
133 // ClientFinalizerFunc can be used to perform work at the end of a client gRPC
134 // request, after the response is returned. The principal
135 // intended use is for error logging. Additional response parameters are
136 // provided in the context under keys with the ContextKeyResponse prefix.
137 // Note: err may be nil. There maybe also no additional response parameters depending on
138 // when an error occurs.
139 type ClientFinalizerFunc func(ctx context.Context, err error)
6868 func EncodeKeyValue(key, val string) (string, string) {
6969 key = strings.ToLower(key)
7070 if strings.HasSuffix(key, binHdrSuffix) {
71 v := base64.StdEncoding.EncodeToString([]byte(val))
72 val = string(v)
71 val = base64.StdEncoding.EncodeToString([]byte(val))
7372 }
7473 return key, val
7574 }
75
76 type contextKey int
77
78 const (
79 ContextKeyRequestMethod contextKey = iota
80 )
00 package grpc
11
22 import (
3 oldcontext "golang.org/x/net/context"
3 "context"
4
45 "google.golang.org/grpc"
56 "google.golang.org/grpc/metadata"
67
78 "github.com/go-kit/kit/endpoint"
8 "github.com/go-kit/kit/log"
9 "github.com/go-kit/kit/transport"
10 "github.com/go-kit/log"
911 )
1012
1113 // Handler which should be called from the gRPC binding of the service
1214 // implementation. The incoming request parameter, and returned response
1315 // parameter, are both gRPC types, not user-domain.
1416 type Handler interface {
15 ServeGRPC(ctx oldcontext.Context, request interface{}) (oldcontext.Context, interface{}, error)
17 ServeGRPC(ctx context.Context, request interface{}) (context.Context, interface{}, error)
1618 }
1719
1820 // Server wraps an endpoint and implements grpc.Handler.
1921 type Server struct {
20 e endpoint.Endpoint
21 dec DecodeRequestFunc
22 enc EncodeResponseFunc
23 before []ServerRequestFunc
24 after []ServerResponseFunc
25 logger log.Logger
22 e endpoint.Endpoint
23 dec DecodeRequestFunc
24 enc EncodeResponseFunc
25 before []ServerRequestFunc
26 after []ServerResponseFunc
27 finalizer []ServerFinalizerFunc
28 errorHandler transport.ErrorHandler
2629 }
2730
2831 // NewServer constructs a new server, which implements wraps the provided
3740 options ...ServerOption,
3841 ) *Server {
3942 s := &Server{
40 e: e,
41 dec: dec,
42 enc: enc,
43 logger: log.NewNopLogger(),
43 e: e,
44 dec: dec,
45 enc: enc,
46 errorHandler: transport.NewLogErrorHandler(log.NewNopLogger()),
4447 }
4548 for _, option := range options {
4649 option(s)
5154 // ServerOption sets an optional parameter for servers.
5255 type ServerOption func(*Server)
5356
54 // ServerBefore functions are executed on the HTTP request object before the
57 // ServerBefore functions are executed on the gRPC request object before the
5558 // request is decoded.
5659 func ServerBefore(before ...ServerRequestFunc) ServerOption {
5760 return func(s *Server) { s.before = append(s.before, before...) }
5861 }
5962
60 // ServerAfter functions are executed on the HTTP response writer after the
63 // ServerAfter functions are executed on the gRPC response writer after the
6164 // endpoint is invoked, but before anything is written to the client.
6265 func ServerAfter(after ...ServerResponseFunc) ServerOption {
6366 return func(s *Server) { s.after = append(s.after, after...) }
6568
6669 // ServerErrorLogger is used to log non-terminal errors. By default, no errors
6770 // are logged.
71 // Deprecated: Use ServerErrorHandler instead.
6872 func ServerErrorLogger(logger log.Logger) ServerOption {
69 return func(s *Server) { s.logger = logger }
73 return func(s *Server) { s.errorHandler = transport.NewLogErrorHandler(logger) }
74 }
75
76 // ServerErrorHandler is used to handle non-terminal errors. By default, non-terminal errors
77 // are ignored.
78 func ServerErrorHandler(errorHandler transport.ErrorHandler) ServerOption {
79 return func(s *Server) { s.errorHandler = errorHandler }
80 }
81
82 // ServerFinalizer is executed at the end of every gRPC request.
83 // By default, no finalizer is registered.
84 func ServerFinalizer(f ...ServerFinalizerFunc) ServerOption {
85 return func(s *Server) { s.finalizer = append(s.finalizer, f...) }
7086 }
7187
7288 // ServeGRPC implements the Handler interface.
73 func (s Server) ServeGRPC(ctx oldcontext.Context, req interface{}) (oldcontext.Context, interface{}, error) {
89 func (s Server) ServeGRPC(ctx context.Context, req interface{}) (retctx context.Context, resp interface{}, err error) {
7490 // Retrieve gRPC metadata.
7591 md, ok := metadata.FromIncomingContext(ctx)
7692 if !ok {
7793 md = metadata.MD{}
7894 }
7995
96 if len(s.finalizer) > 0 {
97 defer func() {
98 for _, f := range s.finalizer {
99 f(ctx, err)
100 }
101 }()
102 }
103
80104 for _, f := range s.before {
81105 ctx = f(ctx, md)
82106 }
83107
84 request, err := s.dec(ctx, req)
108 var (
109 request interface{}
110 response interface{}
111 grpcResp interface{}
112 )
113
114 request, err = s.dec(ctx, req)
85115 if err != nil {
86 s.logger.Log("err", err)
116 s.errorHandler.Handle(ctx, err)
87117 return ctx, nil, err
88118 }
89119
90 response, err := s.e(ctx, request)
120 response, err = s.e(ctx, request)
91121 if err != nil {
92 s.logger.Log("err", err)
122 s.errorHandler.Handle(ctx, err)
93123 return ctx, nil, err
94124 }
95125
98128 ctx = f(ctx, &mdHeader, &mdTrailer)
99129 }
100130
101 grpcResp, err := s.enc(ctx, response)
131 grpcResp, err = s.enc(ctx, response)
102132 if err != nil {
103 s.logger.Log("err", err)
133 s.errorHandler.Handle(ctx, err)
104134 return ctx, nil, err
105135 }
106136
107137 if len(mdHeader) > 0 {
108138 if err = grpc.SendHeader(ctx, mdHeader); err != nil {
109 s.logger.Log("err", err)
139 s.errorHandler.Handle(ctx, err)
110140 return ctx, nil, err
111141 }
112142 }
113143
114144 if len(mdTrailer) > 0 {
115145 if err = grpc.SetTrailer(ctx, mdTrailer); err != nil {
116 s.logger.Log("err", err)
146 s.errorHandler.Handle(ctx, err)
117147 return ctx, nil, err
118148 }
119149 }
120150
121151 return ctx, grpcResp, nil
122152 }
153
154 // ServerFinalizerFunc can be used to perform work at the end of an gRPC
155 // request, after the response has been written to the client.
156 type ServerFinalizerFunc func(ctx context.Context, err error)
157
158 // Interceptor is a grpc UnaryInterceptor that injects the method name into
159 // context so it can be consumed by Go kit gRPC middlewares. The Interceptor
160 // typically is added at creation time of the grpc-go server.
161 // Like this: `grpc.NewServer(grpc.UnaryInterceptor(kitgrpc.Interceptor))`
162 func Interceptor(
163 ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler,
164 ) (resp interface{}, err error) {
165 ctx = context.WithValue(ctx, ContextKeyRequestMethod, info.FullMethod)
166 return handler(ctx, req)
167 }
44 "context"
55 "encoding/json"
66 "encoding/xml"
7 "io"
78 "io/ioutil"
89 "net/http"
910 "net/url"
1112 "github.com/go-kit/kit/endpoint"
1213 )
1314
15 // HTTPClient is an interface that models *http.Client.
16 type HTTPClient interface {
17 Do(req *http.Request) (*http.Response, error)
18 }
19
1420 // Client wraps a URL and provides a method that implements endpoint.Endpoint.
1521 type Client struct {
16 client *http.Client
17 method string
18 tgt *url.URL
19 enc EncodeRequestFunc
22 client HTTPClient
23 req CreateRequestFunc
2024 dec DecodeResponseFunc
2125 before []RequestFunc
2226 after []ClientResponseFunc
23 finalizer ClientFinalizerFunc
27 finalizer []ClientFinalizerFunc
2428 bufferedStream bool
2529 }
2630
2731 // NewClient constructs a usable Client for a single remote method.
28 func NewClient(
29 method string,
30 tgt *url.URL,
31 enc EncodeRequestFunc,
32 dec DecodeResponseFunc,
33 options ...ClientOption,
34 ) *Client {
32 func NewClient(method string, tgt *url.URL, enc EncodeRequestFunc, dec DecodeResponseFunc, options ...ClientOption) *Client {
33 return NewExplicitClient(makeCreateRequestFunc(method, tgt, enc), dec, options...)
34 }
35
36 // NewExplicitClient is like NewClient but uses a CreateRequestFunc instead of a
37 // method, target URL, and EncodeRequestFunc, which allows for more control over
38 // the outgoing HTTP request.
39 func NewExplicitClient(req CreateRequestFunc, dec DecodeResponseFunc, options ...ClientOption) *Client {
3540 c := &Client{
36 client: http.DefaultClient,
37 method: method,
38 tgt: tgt,
39 enc: enc,
40 dec: dec,
41 before: []RequestFunc{},
42 after: []ClientResponseFunc{},
43 bufferedStream: false,
41 client: http.DefaultClient,
42 req: req,
43 dec: dec,
4444 }
4545 for _, option := range options {
4646 option(c)
5353
5454 // SetClient sets the underlying HTTP client used for requests.
5555 // By default, http.DefaultClient is used.
56 func SetClient(client *http.Client) ClientOption {
56 func SetClient(client HTTPClient) ClientOption {
5757 return func(c *Client) { c.client = client }
5858 }
5959
60 // ClientBefore sets the RequestFuncs that are applied to the outgoing HTTP
60 // ClientBefore adds one or more RequestFuncs to be applied to the outgoing HTTP
6161 // request before it's invoked.
6262 func ClientBefore(before ...RequestFunc) ClientOption {
6363 return func(c *Client) { c.before = append(c.before, before...) }
6464 }
6565
66 // ClientAfter sets the ClientResponseFuncs applied to the incoming HTTP
67 // request prior to it being decoded. This is useful for obtaining anything off
68 // of the response and adding onto the context prior to decoding.
66 // ClientAfter adds one or more ClientResponseFuncs, which are applied to the
67 // incoming HTTP response prior to it being decoded. This is useful for
68 // obtaining anything off of the response and adding it into the context prior
69 // to decoding.
6970 func ClientAfter(after ...ClientResponseFunc) ClientOption {
7071 return func(c *Client) { c.after = append(c.after, after...) }
7172 }
7273
73 // ClientFinalizer is executed at the end of every HTTP request.
74 // By default, no finalizer is registered.
75 func ClientFinalizer(f ClientFinalizerFunc) ClientOption {
76 return func(s *Client) { s.finalizer = f }
77 }
78
79 // BufferedStream sets whether the Response.Body is left open, allowing it
74 // ClientFinalizer adds one or more ClientFinalizerFuncs to be executed at the
75 // end of every HTTP request. Finalizers are executed in the order in which they
76 // were added. By default, no finalizer is registered.
77 func ClientFinalizer(f ...ClientFinalizerFunc) ClientOption {
78 return func(s *Client) { s.finalizer = append(s.finalizer, f...) }
79 }
80
81 // BufferedStream sets whether the HTTP response body is left open, allowing it
8082 // to be read from later. Useful for transporting a file as a buffered stream.
83 // That body has to be drained and closed to properly end the request.
8184 func BufferedStream(buffered bool) ClientOption {
8285 return func(c *Client) { c.bufferedStream = buffered }
8386 }
8487
85 // Endpoint returns a usable endpoint that invokes the remote endpoint.
88 // Endpoint returns a usable Go kit endpoint that calls the remote HTTP endpoint.
8689 func (c Client) Endpoint() endpoint.Endpoint {
8790 return func(ctx context.Context, request interface{}) (interface{}, error) {
8891 ctx, cancel := context.WithCancel(ctx)
89 defer cancel()
9092
9193 var (
9294 resp *http.Response
98100 ctx = context.WithValue(ctx, ContextKeyResponseHeaders, resp.Header)
99101 ctx = context.WithValue(ctx, ContextKeyResponseSize, resp.ContentLength)
100102 }
101 c.finalizer(ctx, err)
103 for _, f := range c.finalizer {
104 f(ctx, err)
105 }
102106 }()
103107 }
104108
105 req, err := http.NewRequest(c.method, c.tgt.String(), nil)
106 if err != nil {
107 return nil, err
108 }
109
110 if err = c.enc(ctx, req, request); err != nil {
109 req, err := c.req(ctx, request)
110 if err != nil {
111 cancel()
111112 return nil, err
112113 }
113114
116117 }
117118
118119 resp, err = c.client.Do(req.WithContext(ctx))
119
120 if err != nil {
121 return nil, err
122 }
123
124 if !c.bufferedStream {
120 if err != nil {
121 cancel()
122 return nil, err
123 }
124
125 // If the caller asked for a buffered stream, we don't cancel the
126 // context when the endpoint returns. Instead, we should call the
127 // cancel func when closing the response body.
128 if c.bufferedStream {
129 resp.Body = bodyWithCancel{ReadCloser: resp.Body, cancel: cancel}
130 } else {
125131 defer resp.Body.Close()
132 defer cancel()
126133 }
127134
128135 for _, f := range c.after {
136143
137144 return response, nil
138145 }
146 }
147
148 // bodyWithCancel is a wrapper for an io.ReadCloser with also a
149 // cancel function which is called when the Close is used
150 type bodyWithCancel struct {
151 io.ReadCloser
152
153 cancel context.CancelFunc
154 }
155
156 func (bwc bodyWithCancel) Close() error {
157 bwc.ReadCloser.Close()
158 bwc.cancel()
159 return nil
139160 }
140161
141162 // ClientFinalizerFunc can be used to perform work at the end of a client HTTP
142163 // request, after the response is returned. The principal
143164 // intended use is for error logging. Additional response parameters are
144165 // provided in the context under keys with the ContextKeyResponse prefix.
145 // Note: err may be nil. There maybe also no additional response parameters depending on
146 // when an error occurs.
166 // Note: err may be nil. There maybe also no additional response parameters
167 // depending on when an error occurs.
147168 type ClientFinalizerFunc func(ctx context.Context, err error)
148169
149170 // EncodeJSONRequest is an EncodeRequestFunc that serializes the request as a
176197 r.Body = ioutil.NopCloser(&b)
177198 return xml.NewEncoder(&b).Encode(request)
178199 }
200
201 //
202 //
203 //
204
205 func makeCreateRequestFunc(method string, target *url.URL, enc EncodeRequestFunc) CreateRequestFunc {
206 return func(ctx context.Context, request interface{}) (*http.Request, error) {
207 req, err := http.NewRequest(method, target.String(), nil)
208 if err != nil {
209 return nil, err
210 }
211
212 if err = enc(ctx, req, request); err != nil {
213 return nil, err
214 }
215
216 return req, nil
217 }
218 }
00 package http_test
11
22 import (
3 "bytes"
34 "context"
5 "fmt"
46 "io"
57 "io/ioutil"
68 "net/http"
79 "net/http/httptest"
810 "net/url"
11 "strings"
912 "testing"
1013 "time"
1114
9699 }
97100
98101 func TestHTTPClientBufferedStream(t *testing.T) {
102 // bodysize has a size big enought to make the resopnse.Body not an instant read
103 // so if the response is cancelled it wount be all readed and the test would fail
104 // The 6000 has not a particular meaning, it big enough to fulfill the usecase.
105 const bodysize = 6000
99106 var (
100 testbody = "testbody"
107 testbody = string(make([]byte, bodysize))
101108 encode = func(context.Context, *http.Request, interface{}) error { return nil }
102109 decode = func(_ context.Context, r *http.Response) (interface{}, error) {
103110 return TestResponse{r.Body, ""}, nil
127134 if !ok {
128135 t.Fatal("response should be TestResponse")
129136 }
137 defer response.Body.Close()
138 // Faking work
139 time.Sleep(time.Second * 1)
130140
131141 // Check that response body was NOT closed
132142 b := make([]byte, len(testbody))
251261 }
252262 }
253263
264 func TestSetClient(t *testing.T) {
265 var (
266 encode = func(context.Context, *http.Request, interface{}) error { return nil }
267 decode = func(_ context.Context, r *http.Response) (interface{}, error) {
268 t, err := ioutil.ReadAll(r.Body)
269 if err != nil {
270 return nil, err
271 }
272 return string(t), nil
273 }
274 )
275
276 testHttpClient := httpClientFunc(func(req *http.Request) (*http.Response, error) {
277 return &http.Response{
278 StatusCode: http.StatusOK,
279 Request: req,
280 Body: ioutil.NopCloser(bytes.NewBufferString("hello, world!")),
281 }, nil
282 })
283
284 client := httptransport.NewClient(
285 "GET",
286 &url.URL{},
287 encode,
288 decode,
289 httptransport.SetClient(testHttpClient),
290 ).Endpoint()
291
292 resp, err := client(context.Background(), nil)
293 if err != nil {
294 t.Fatal(err)
295 }
296 if r, ok := resp.(string); !ok || r != "hello, world!" {
297 t.Fatal("Expected response to be 'hello, world!' string")
298 }
299 }
300
301 func TestNewExplicitClient(t *testing.T) {
302 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
303 fmt.Fprintf(w, "%d", r.ContentLength)
304 }))
305 defer srv.Close()
306
307 req := func(ctx context.Context, request interface{}) (*http.Request, error) {
308 req, _ := http.NewRequest("POST", srv.URL, strings.NewReader(request.(string)))
309 return req, nil
310 }
311
312 dec := func(_ context.Context, resp *http.Response) (response interface{}, err error) {
313 buf, err := ioutil.ReadAll(resp.Body)
314 resp.Body.Close()
315 return string(buf), err
316 }
317
318 client := httptransport.NewExplicitClient(req, dec)
319
320 request := "hello world"
321 response, err := client.Endpoint()(context.Background(), request)
322 if err != nil {
323 t.Fatal(err)
324 }
325
326 if want, have := "11", response.(string); want != have {
327 t.Fatalf("want %q, have %q", want, have)
328 }
329 }
330
254331 func mustParse(s string) *url.URL {
255332 u, err := url.Parse(s)
256333 if err != nil {
264341 }
265342
266343 func (e enhancedRequest) Headers() http.Header { return http.Header{"X-Edward": []string{"Snowden"}} }
344
345 type httpClientFunc func(req *http.Request) (*http.Response, error)
346
347 func (f httpClientFunc) Do(req *http.Request) (*http.Response, error) {
348 return f(req)
349 }
77 // DecodeRequestFunc extracts a user-domain request object from an HTTP
88 // request object. It's designed to be used in HTTP servers, for server-side
99 // endpoints. One straightforward DecodeRequestFunc could be something that
10 // JSON decodes from the request body to the concrete response type.
10 // JSON decodes from the request body to the concrete request type.
1111 type DecodeRequestFunc func(context.Context, *http.Request) (request interface{}, err error)
1212
1313 // EncodeRequestFunc encodes the passed request object into the HTTP request
1414 // object. It's designed to be used in HTTP clients, for client-side
15 // endpoints. One straightforward EncodeRequestFunc could something that JSON
15 // endpoints. One straightforward EncodeRequestFunc could be something that JSON
1616 // encodes the object directly to the request body.
1717 type EncodeRequestFunc func(context.Context, *http.Request, interface{}) error
18
19 // CreateRequestFunc creates an outgoing HTTP request based on the passed
20 // request object. It's designed to be used in HTTP clients, for client-side
21 // endpoints. It's a more powerful version of EncodeRequestFunc, and can be used
22 // if more fine-grained control of the HTTP request is required.
23 type CreateRequestFunc func(context.Context, interface{}) (*http.Request, error)
1824
1925 // EncodeResponseFunc encodes the passed response object to the HTTP response
2026 // writer. It's designed to be used in HTTP servers, for server-side
0 package http
1
2 import (
3 "io"
4 "net/http"
5 )
6
7 type interceptingWriter struct {
8 http.ResponseWriter
9 code int
10 written int64
11 }
12
13 // WriteHeader may not be explicitly called, so care must be taken to
14 // initialize w.code to its default value of http.StatusOK.
15 func (w *interceptingWriter) WriteHeader(code int) {
16 w.code = code
17 w.ResponseWriter.WriteHeader(code)
18 }
19
20 func (w *interceptingWriter) Write(p []byte) (int, error) {
21 n, err := w.ResponseWriter.Write(p)
22 w.written += int64(n)
23 return n, err
24 }
25
26 // reimplementInterfaces returns a wrapped version of the embedded ResponseWriter
27 // and selectively implements the same combination of additional interfaces as
28 // the wrapped one. The interfaces it may implement are: http.Hijacker,
29 // http.CloseNotifier, http.Pusher, http.Flusher and io.ReaderFrom. The standard
30 // library is known to assert the existence of these interfaces and behaves
31 // differently. This implementation is derived from
32 // https://github.com/felixge/httpsnoop.
33 func (w *interceptingWriter) reimplementInterfaces() http.ResponseWriter {
34 var (
35 hj, i0 = w.ResponseWriter.(http.Hijacker)
36 cn, i1 = w.ResponseWriter.(http.CloseNotifier)
37 pu, i2 = w.ResponseWriter.(http.Pusher)
38 fl, i3 = w.ResponseWriter.(http.Flusher)
39 rf, i4 = w.ResponseWriter.(io.ReaderFrom)
40 )
41
42 switch {
43 case !i0 && !i1 && !i2 && !i3 && !i4:
44 return struct {
45 http.ResponseWriter
46 }{w}
47 case !i0 && !i1 && !i2 && !i3 && i4:
48 return struct {
49 http.ResponseWriter
50 io.ReaderFrom
51 }{w, rf}
52 case !i0 && !i1 && !i2 && i3 && !i4:
53 return struct {
54 http.ResponseWriter
55 http.Flusher
56 }{w, fl}
57 case !i0 && !i1 && !i2 && i3 && i4:
58 return struct {
59 http.ResponseWriter
60 http.Flusher
61 io.ReaderFrom
62 }{w, fl, rf}
63 case !i0 && !i1 && i2 && !i3 && !i4:
64 return struct {
65 http.ResponseWriter
66 http.Pusher
67 }{w, pu}
68 case !i0 && !i1 && i2 && !i3 && i4:
69 return struct {
70 http.ResponseWriter
71 http.Pusher
72 io.ReaderFrom
73 }{w, pu, rf}
74 case !i0 && !i1 && i2 && i3 && !i4:
75 return struct {
76 http.ResponseWriter
77 http.Pusher
78 http.Flusher
79 }{w, pu, fl}
80 case !i0 && !i1 && i2 && i3 && i4:
81 return struct {
82 http.ResponseWriter
83 http.Pusher
84 http.Flusher
85 io.ReaderFrom
86 }{w, pu, fl, rf}
87 case !i0 && i1 && !i2 && !i3 && !i4:
88 return struct {
89 http.ResponseWriter
90 http.CloseNotifier
91 }{w, cn}
92 case !i0 && i1 && !i2 && !i3 && i4:
93 return struct {
94 http.ResponseWriter
95 http.CloseNotifier
96 io.ReaderFrom
97 }{w, cn, rf}
98 case !i0 && i1 && !i2 && i3 && !i4:
99 return struct {
100 http.ResponseWriter
101 http.CloseNotifier
102 http.Flusher
103 }{w, cn, fl}
104 case !i0 && i1 && !i2 && i3 && i4:
105 return struct {
106 http.ResponseWriter
107 http.CloseNotifier
108 http.Flusher
109 io.ReaderFrom
110 }{w, cn, fl, rf}
111 case !i0 && i1 && i2 && !i3 && !i4:
112 return struct {
113 http.ResponseWriter
114 http.CloseNotifier
115 http.Pusher
116 }{w, cn, pu}
117 case !i0 && i1 && i2 && !i3 && i4:
118 return struct {
119 http.ResponseWriter
120 http.CloseNotifier
121 http.Pusher
122 io.ReaderFrom
123 }{w, cn, pu, rf}
124 case !i0 && i1 && i2 && i3 && !i4:
125 return struct {
126 http.ResponseWriter
127 http.CloseNotifier
128 http.Pusher
129 http.Flusher
130 }{w, cn, pu, fl}
131 case !i0 && i1 && i2 && i3 && i4:
132 return struct {
133 http.ResponseWriter
134 http.CloseNotifier
135 http.Pusher
136 http.Flusher
137 io.ReaderFrom
138 }{w, cn, pu, fl, rf}
139 case i0 && !i1 && !i2 && !i3 && !i4:
140 return struct {
141 http.ResponseWriter
142 http.Hijacker
143 }{w, hj}
144 case i0 && !i1 && !i2 && !i3 && i4:
145 return struct {
146 http.ResponseWriter
147 http.Hijacker
148 io.ReaderFrom
149 }{w, hj, rf}
150 case i0 && !i1 && !i2 && i3 && !i4:
151 return struct {
152 http.ResponseWriter
153 http.Hijacker
154 http.Flusher
155 }{w, hj, fl}
156 case i0 && !i1 && !i2 && i3 && i4:
157 return struct {
158 http.ResponseWriter
159 http.Hijacker
160 http.Flusher
161 io.ReaderFrom
162 }{w, hj, fl, rf}
163 case i0 && !i1 && i2 && !i3 && !i4:
164 return struct {
165 http.ResponseWriter
166 http.Hijacker
167 http.Pusher
168 }{w, hj, pu}
169 case i0 && !i1 && i2 && !i3 && i4:
170 return struct {
171 http.ResponseWriter
172 http.Hijacker
173 http.Pusher
174 io.ReaderFrom
175 }{w, hj, pu, rf}
176 case i0 && !i1 && i2 && i3 && !i4:
177 return struct {
178 http.ResponseWriter
179 http.Hijacker
180 http.Pusher
181 http.Flusher
182 }{w, hj, pu, fl}
183 case i0 && !i1 && i2 && i3 && i4:
184 return struct {
185 http.ResponseWriter
186 http.Hijacker
187 http.Pusher
188 http.Flusher
189 io.ReaderFrom
190 }{w, hj, pu, fl, rf}
191 case i0 && i1 && !i2 && !i3 && !i4:
192 return struct {
193 http.ResponseWriter
194 http.Hijacker
195 http.CloseNotifier
196 }{w, hj, cn}
197 case i0 && i1 && !i2 && !i3 && i4:
198 return struct {
199 http.ResponseWriter
200 http.Hijacker
201 http.CloseNotifier
202 io.ReaderFrom
203 }{w, hj, cn, rf}
204 case i0 && i1 && !i2 && i3 && !i4:
205 return struct {
206 http.ResponseWriter
207 http.Hijacker
208 http.CloseNotifier
209 http.Flusher
210 }{w, hj, cn, fl}
211 case i0 && i1 && !i2 && i3 && i4:
212 return struct {
213 http.ResponseWriter
214 http.Hijacker
215 http.CloseNotifier
216 http.Flusher
217 io.ReaderFrom
218 }{w, hj, cn, fl, rf}
219 case i0 && i1 && i2 && !i3 && !i4:
220 return struct {
221 http.ResponseWriter
222 http.Hijacker
223 http.CloseNotifier
224 http.Pusher
225 }{w, hj, cn, pu}
226 case i0 && i1 && i2 && !i3 && i4:
227 return struct {
228 http.ResponseWriter
229 http.Hijacker
230 http.CloseNotifier
231 http.Pusher
232 io.ReaderFrom
233 }{w, hj, cn, pu, rf}
234 case i0 && i1 && i2 && i3 && !i4:
235 return struct {
236 http.ResponseWriter
237 http.Hijacker
238 http.CloseNotifier
239 http.Pusher
240 http.Flusher
241 }{w, hj, cn, pu, fl}
242 case i0 && i1 && i2 && i3 && i4:
243 return struct {
244 http.ResponseWriter
245 http.Hijacker
246 http.CloseNotifier
247 http.Pusher
248 http.Flusher
249 io.ReaderFrom
250 }{w, hj, cn, pu, fl, rf}
251 default:
252 return struct {
253 http.ResponseWriter
254 }{w}
255 }
256 }
0 package http
1
2 import (
3 "bufio"
4 "io"
5 "net"
6 "net/http"
7 "testing"
8 )
9
10 type versatileWriter struct {
11 http.ResponseWriter
12 closeNotifyCalled bool
13 hijackCalled bool
14 readFromCalled bool
15 pushCalled bool
16 flushCalled bool
17 }
18
19 func (v *versatileWriter) Flush() { v.flushCalled = true }
20 func (v *versatileWriter) Push(target string, opts *http.PushOptions) error {
21 v.pushCalled = true
22 return nil
23 }
24 func (v *versatileWriter) ReadFrom(r io.Reader) (n int64, err error) {
25 v.readFromCalled = true
26 return 0, nil
27 }
28 func (v *versatileWriter) CloseNotify() <-chan bool { v.closeNotifyCalled = true; return nil }
29 func (v *versatileWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
30 v.hijackCalled = true
31 return nil, nil, nil
32 }
33
34 func TestInterceptingWriter_passthroughs(t *testing.T) {
35 w := &versatileWriter{}
36 iw := (&interceptingWriter{ResponseWriter: w}).reimplementInterfaces()
37 iw.(http.Flusher).Flush()
38 iw.(http.Pusher).Push("", nil)
39 iw.(http.CloseNotifier).CloseNotify()
40 iw.(http.Hijacker).Hijack()
41 iw.(io.ReaderFrom).ReadFrom(nil)
42
43 if !w.flushCalled {
44 t.Error("Flush not called")
45 }
46 if !w.pushCalled {
47 t.Error("Push not called")
48 }
49 if !w.closeNotifyCalled {
50 t.Error("CloseNotify not called")
51 }
52 if !w.hijackCalled {
53 t.Error("Hijack not called")
54 }
55 if !w.readFromCalled {
56 t.Error("ReadFrom not called")
57 }
58 }
59
60 // TestInterceptingWriter_reimplementInterfaces is also derived from
61 // https://github.com/felixge/httpsnoop, like interceptingWriter.
62 func TestInterceptingWriter_reimplementInterfaces(t *testing.T) {
63 // combination 1/32
64 {
65 t.Log("http.ResponseWriter")
66 inner := struct {
67 http.ResponseWriter
68 }{}
69 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
70 if _, ok := w.(http.ResponseWriter); ok != true {
71 t.Error("unexpected interface")
72 }
73 if _, ok := w.(http.Flusher); ok != false {
74 t.Error("unexpected interface")
75 }
76 if _, ok := w.(http.CloseNotifier); ok != false {
77 t.Error("unexpected interface")
78 }
79 if _, ok := w.(http.Hijacker); ok != false {
80 t.Error("unexpected interface")
81 }
82 if _, ok := w.(io.ReaderFrom); ok != false {
83 t.Error("unexpected interface")
84 }
85 if _, ok := w.(http.Pusher); ok != false {
86 t.Error("unexpected interface")
87 }
88
89 }
90
91 // combination 2/32
92 {
93 t.Log("http.ResponseWriter, http.Pusher")
94 inner := struct {
95 http.ResponseWriter
96 http.Pusher
97 }{}
98 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
99 if _, ok := w.(http.ResponseWriter); ok != true {
100 t.Error("unexpected interface")
101 }
102 if _, ok := w.(http.Flusher); ok != false {
103 t.Error("unexpected interface")
104 }
105 if _, ok := w.(http.CloseNotifier); ok != false {
106 t.Error("unexpected interface")
107 }
108 if _, ok := w.(http.Hijacker); ok != false {
109 t.Error("unexpected interface")
110 }
111 if _, ok := w.(io.ReaderFrom); ok != false {
112 t.Error("unexpected interface")
113 }
114 if _, ok := w.(http.Pusher); ok != true {
115 t.Error("unexpected interface")
116 }
117
118 }
119
120 // combination 3/32
121 {
122 t.Log("http.ResponseWriter, io.ReaderFrom")
123 inner := struct {
124 http.ResponseWriter
125 io.ReaderFrom
126 }{}
127 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
128 if _, ok := w.(http.ResponseWriter); ok != true {
129 t.Error("unexpected interface")
130 }
131 if _, ok := w.(http.Flusher); ok != false {
132 t.Error("unexpected interface")
133 }
134 if _, ok := w.(http.CloseNotifier); ok != false {
135 t.Error("unexpected interface")
136 }
137 if _, ok := w.(http.Hijacker); ok != false {
138 t.Error("unexpected interface")
139 }
140 if _, ok := w.(io.ReaderFrom); ok != true {
141 t.Error("unexpected interface")
142 }
143 if _, ok := w.(http.Pusher); ok != false {
144 t.Error("unexpected interface")
145 }
146
147 }
148
149 // combination 4/32
150 {
151 t.Log("http.ResponseWriter, io.ReaderFrom, http.Pusher")
152 inner := struct {
153 http.ResponseWriter
154 io.ReaderFrom
155 http.Pusher
156 }{}
157 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
158 if _, ok := w.(http.ResponseWriter); ok != true {
159 t.Error("unexpected interface")
160 }
161 if _, ok := w.(http.Flusher); ok != false {
162 t.Error("unexpected interface")
163 }
164 if _, ok := w.(http.CloseNotifier); ok != false {
165 t.Error("unexpected interface")
166 }
167 if _, ok := w.(http.Hijacker); ok != false {
168 t.Error("unexpected interface")
169 }
170 if _, ok := w.(io.ReaderFrom); ok != true {
171 t.Error("unexpected interface")
172 }
173 if _, ok := w.(http.Pusher); ok != true {
174 t.Error("unexpected interface")
175 }
176
177 }
178
179 // combination 5/32
180 {
181 t.Log("http.ResponseWriter, http.Hijacker")
182 inner := struct {
183 http.ResponseWriter
184 http.Hijacker
185 }{}
186 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
187 if _, ok := w.(http.ResponseWriter); ok != true {
188 t.Error("unexpected interface")
189 }
190 if _, ok := w.(http.Flusher); ok != false {
191 t.Error("unexpected interface")
192 }
193 if _, ok := w.(http.CloseNotifier); ok != false {
194 t.Error("unexpected interface")
195 }
196 if _, ok := w.(http.Hijacker); ok != true {
197 t.Error("unexpected interface")
198 }
199 if _, ok := w.(io.ReaderFrom); ok != false {
200 t.Error("unexpected interface")
201 }
202 if _, ok := w.(http.Pusher); ok != false {
203 t.Error("unexpected interface")
204 }
205
206 }
207
208 // combination 6/32
209 {
210 t.Log("http.ResponseWriter, http.Hijacker, http.Pusher")
211 inner := struct {
212 http.ResponseWriter
213 http.Hijacker
214 http.Pusher
215 }{}
216 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
217 if _, ok := w.(http.ResponseWriter); ok != true {
218 t.Error("unexpected interface")
219 }
220 if _, ok := w.(http.Flusher); ok != false {
221 t.Error("unexpected interface")
222 }
223 if _, ok := w.(http.CloseNotifier); ok != false {
224 t.Error("unexpected interface")
225 }
226 if _, ok := w.(http.Hijacker); ok != true {
227 t.Error("unexpected interface")
228 }
229 if _, ok := w.(io.ReaderFrom); ok != false {
230 t.Error("unexpected interface")
231 }
232 if _, ok := w.(http.Pusher); ok != true {
233 t.Error("unexpected interface")
234 }
235
236 }
237
238 // combination 7/32
239 {
240 t.Log("http.ResponseWriter, http.Hijacker, io.ReaderFrom")
241 inner := struct {
242 http.ResponseWriter
243 http.Hijacker
244 io.ReaderFrom
245 }{}
246 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
247 if _, ok := w.(http.ResponseWriter); ok != true {
248 t.Error("unexpected interface")
249 }
250 if _, ok := w.(http.Flusher); ok != false {
251 t.Error("unexpected interface")
252 }
253 if _, ok := w.(http.CloseNotifier); ok != false {
254 t.Error("unexpected interface")
255 }
256 if _, ok := w.(http.Hijacker); ok != true {
257 t.Error("unexpected interface")
258 }
259 if _, ok := w.(io.ReaderFrom); ok != true {
260 t.Error("unexpected interface")
261 }
262 if _, ok := w.(http.Pusher); ok != false {
263 t.Error("unexpected interface")
264 }
265
266 }
267
268 // combination 8/32
269 {
270 t.Log("http.ResponseWriter, http.Hijacker, io.ReaderFrom, http.Pusher")
271 inner := struct {
272 http.ResponseWriter
273 http.Hijacker
274 io.ReaderFrom
275 http.Pusher
276 }{}
277 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
278 if _, ok := w.(http.ResponseWriter); ok != true {
279 t.Error("unexpected interface")
280 }
281 if _, ok := w.(http.Flusher); ok != false {
282 t.Error("unexpected interface")
283 }
284 if _, ok := w.(http.CloseNotifier); ok != false {
285 t.Error("unexpected interface")
286 }
287 if _, ok := w.(http.Hijacker); ok != true {
288 t.Error("unexpected interface")
289 }
290 if _, ok := w.(io.ReaderFrom); ok != true {
291 t.Error("unexpected interface")
292 }
293 if _, ok := w.(http.Pusher); ok != true {
294 t.Error("unexpected interface")
295 }
296
297 }
298
299 // combination 9/32
300 {
301 t.Log("http.ResponseWriter, http.CloseNotifier")
302 inner := struct {
303 http.ResponseWriter
304 http.CloseNotifier
305 }{}
306 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
307 if _, ok := w.(http.ResponseWriter); ok != true {
308 t.Error("unexpected interface")
309 }
310 if _, ok := w.(http.Flusher); ok != false {
311 t.Error("unexpected interface")
312 }
313 if _, ok := w.(http.CloseNotifier); ok != true {
314 t.Error("unexpected interface")
315 }
316 if _, ok := w.(http.Hijacker); ok != false {
317 t.Error("unexpected interface")
318 }
319 if _, ok := w.(io.ReaderFrom); ok != false {
320 t.Error("unexpected interface")
321 }
322 if _, ok := w.(http.Pusher); ok != false {
323 t.Error("unexpected interface")
324 }
325
326 }
327
328 // combination 10/32
329 {
330 t.Log("http.ResponseWriter, http.CloseNotifier, http.Pusher")
331 inner := struct {
332 http.ResponseWriter
333 http.CloseNotifier
334 http.Pusher
335 }{}
336 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
337 if _, ok := w.(http.ResponseWriter); ok != true {
338 t.Error("unexpected interface")
339 }
340 if _, ok := w.(http.Flusher); ok != false {
341 t.Error("unexpected interface")
342 }
343 if _, ok := w.(http.CloseNotifier); ok != true {
344 t.Error("unexpected interface")
345 }
346 if _, ok := w.(http.Hijacker); ok != false {
347 t.Error("unexpected interface")
348 }
349 if _, ok := w.(io.ReaderFrom); ok != false {
350 t.Error("unexpected interface")
351 }
352 if _, ok := w.(http.Pusher); ok != true {
353 t.Error("unexpected interface")
354 }
355
356 }
357
358 // combination 11/32
359 {
360 t.Log("http.ResponseWriter, http.CloseNotifier, io.ReaderFrom")
361 inner := struct {
362 http.ResponseWriter
363 http.CloseNotifier
364 io.ReaderFrom
365 }{}
366 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
367 if _, ok := w.(http.ResponseWriter); ok != true {
368 t.Error("unexpected interface")
369 }
370 if _, ok := w.(http.Flusher); ok != false {
371 t.Error("unexpected interface")
372 }
373 if _, ok := w.(http.CloseNotifier); ok != true {
374 t.Error("unexpected interface")
375 }
376 if _, ok := w.(http.Hijacker); ok != false {
377 t.Error("unexpected interface")
378 }
379 if _, ok := w.(io.ReaderFrom); ok != true {
380 t.Error("unexpected interface")
381 }
382 if _, ok := w.(http.Pusher); ok != false {
383 t.Error("unexpected interface")
384 }
385
386 }
387
388 // combination 12/32
389 {
390 t.Log("http.ResponseWriter, http.CloseNotifier, io.ReaderFrom, http.Pusher")
391 inner := struct {
392 http.ResponseWriter
393 http.CloseNotifier
394 io.ReaderFrom
395 http.Pusher
396 }{}
397 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
398 if _, ok := w.(http.ResponseWriter); ok != true {
399 t.Error("unexpected interface")
400 }
401 if _, ok := w.(http.Flusher); ok != false {
402 t.Error("unexpected interface")
403 }
404 if _, ok := w.(http.CloseNotifier); ok != true {
405 t.Error("unexpected interface")
406 }
407 if _, ok := w.(http.Hijacker); ok != false {
408 t.Error("unexpected interface")
409 }
410 if _, ok := w.(io.ReaderFrom); ok != true {
411 t.Error("unexpected interface")
412 }
413 if _, ok := w.(http.Pusher); ok != true {
414 t.Error("unexpected interface")
415 }
416
417 }
418
419 // combination 13/32
420 {
421 t.Log("http.ResponseWriter, http.CloseNotifier, http.Hijacker")
422 inner := struct {
423 http.ResponseWriter
424 http.CloseNotifier
425 http.Hijacker
426 }{}
427 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
428 if _, ok := w.(http.ResponseWriter); ok != true {
429 t.Error("unexpected interface")
430 }
431 if _, ok := w.(http.Flusher); ok != false {
432 t.Error("unexpected interface")
433 }
434 if _, ok := w.(http.CloseNotifier); ok != true {
435 t.Error("unexpected interface")
436 }
437 if _, ok := w.(http.Hijacker); ok != true {
438 t.Error("unexpected interface")
439 }
440 if _, ok := w.(io.ReaderFrom); ok != false {
441 t.Error("unexpected interface")
442 }
443 if _, ok := w.(http.Pusher); ok != false {
444 t.Error("unexpected interface")
445 }
446
447 }
448
449 // combination 14/32
450 {
451 t.Log("http.ResponseWriter, http.CloseNotifier, http.Hijacker, http.Pusher")
452 inner := struct {
453 http.ResponseWriter
454 http.CloseNotifier
455 http.Hijacker
456 http.Pusher
457 }{}
458 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
459 if _, ok := w.(http.ResponseWriter); ok != true {
460 t.Error("unexpected interface")
461 }
462 if _, ok := w.(http.Flusher); ok != false {
463 t.Error("unexpected interface")
464 }
465 if _, ok := w.(http.CloseNotifier); ok != true {
466 t.Error("unexpected interface")
467 }
468 if _, ok := w.(http.Hijacker); ok != true {
469 t.Error("unexpected interface")
470 }
471 if _, ok := w.(io.ReaderFrom); ok != false {
472 t.Error("unexpected interface")
473 }
474 if _, ok := w.(http.Pusher); ok != true {
475 t.Error("unexpected interface")
476 }
477
478 }
479
480 // combination 15/32
481 {
482 t.Log("http.ResponseWriter, http.CloseNotifier, http.Hijacker, io.ReaderFrom")
483 inner := struct {
484 http.ResponseWriter
485 http.CloseNotifier
486 http.Hijacker
487 io.ReaderFrom
488 }{}
489 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
490 if _, ok := w.(http.ResponseWriter); ok != true {
491 t.Error("unexpected interface")
492 }
493 if _, ok := w.(http.Flusher); ok != false {
494 t.Error("unexpected interface")
495 }
496 if _, ok := w.(http.CloseNotifier); ok != true {
497 t.Error("unexpected interface")
498 }
499 if _, ok := w.(http.Hijacker); ok != true {
500 t.Error("unexpected interface")
501 }
502 if _, ok := w.(io.ReaderFrom); ok != true {
503 t.Error("unexpected interface")
504 }
505 if _, ok := w.(http.Pusher); ok != false {
506 t.Error("unexpected interface")
507 }
508
509 }
510
511 // combination 16/32
512 {
513 t.Log("http.ResponseWriter, http.CloseNotifier, http.Hijacker, io.ReaderFrom, http.Pusher")
514 inner := struct {
515 http.ResponseWriter
516 http.CloseNotifier
517 http.Hijacker
518 io.ReaderFrom
519 http.Pusher
520 }{}
521 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
522 if _, ok := w.(http.ResponseWriter); ok != true {
523 t.Error("unexpected interface")
524 }
525 if _, ok := w.(http.Flusher); ok != false {
526 t.Error("unexpected interface")
527 }
528 if _, ok := w.(http.CloseNotifier); ok != true {
529 t.Error("unexpected interface")
530 }
531 if _, ok := w.(http.Hijacker); ok != true {
532 t.Error("unexpected interface")
533 }
534 if _, ok := w.(io.ReaderFrom); ok != true {
535 t.Error("unexpected interface")
536 }
537 if _, ok := w.(http.Pusher); ok != true {
538 t.Error("unexpected interface")
539 }
540
541 }
542
543 // combination 17/32
544 {
545 t.Log("http.ResponseWriter, http.Flusher")
546 inner := struct {
547 http.ResponseWriter
548 http.Flusher
549 }{}
550 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
551 if _, ok := w.(http.ResponseWriter); ok != true {
552 t.Error("unexpected interface")
553 }
554 if _, ok := w.(http.Flusher); ok != true {
555 t.Error("unexpected interface")
556 }
557 if _, ok := w.(http.CloseNotifier); ok != false {
558 t.Error("unexpected interface")
559 }
560 if _, ok := w.(http.Hijacker); ok != false {
561 t.Error("unexpected interface")
562 }
563 if _, ok := w.(io.ReaderFrom); ok != false {
564 t.Error("unexpected interface")
565 }
566 if _, ok := w.(http.Pusher); ok != false {
567 t.Error("unexpected interface")
568 }
569
570 }
571
572 // combination 18/32
573 {
574 t.Log("http.ResponseWriter, http.Flusher, http.Pusher")
575 inner := struct {
576 http.ResponseWriter
577 http.Flusher
578 http.Pusher
579 }{}
580 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
581 if _, ok := w.(http.ResponseWriter); ok != true {
582 t.Error("unexpected interface")
583 }
584 if _, ok := w.(http.Flusher); ok != true {
585 t.Error("unexpected interface")
586 }
587 if _, ok := w.(http.CloseNotifier); ok != false {
588 t.Error("unexpected interface")
589 }
590 if _, ok := w.(http.Hijacker); ok != false {
591 t.Error("unexpected interface")
592 }
593 if _, ok := w.(io.ReaderFrom); ok != false {
594 t.Error("unexpected interface")
595 }
596 if _, ok := w.(http.Pusher); ok != true {
597 t.Error("unexpected interface")
598 }
599
600 }
601
602 // combination 19/32
603 {
604 t.Log("http.ResponseWriter, http.Flusher, io.ReaderFrom")
605 inner := struct {
606 http.ResponseWriter
607 http.Flusher
608 io.ReaderFrom
609 }{}
610 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
611 if _, ok := w.(http.ResponseWriter); ok != true {
612 t.Error("unexpected interface")
613 }
614 if _, ok := w.(http.Flusher); ok != true {
615 t.Error("unexpected interface")
616 }
617 if _, ok := w.(http.CloseNotifier); ok != false {
618 t.Error("unexpected interface")
619 }
620 if _, ok := w.(http.Hijacker); ok != false {
621 t.Error("unexpected interface")
622 }
623 if _, ok := w.(io.ReaderFrom); ok != true {
624 t.Error("unexpected interface")
625 }
626 if _, ok := w.(http.Pusher); ok != false {
627 t.Error("unexpected interface")
628 }
629
630 }
631
632 // combination 20/32
633 {
634 t.Log("http.ResponseWriter, http.Flusher, io.ReaderFrom, http.Pusher")
635 inner := struct {
636 http.ResponseWriter
637 http.Flusher
638 io.ReaderFrom
639 http.Pusher
640 }{}
641 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
642 if _, ok := w.(http.ResponseWriter); ok != true {
643 t.Error("unexpected interface")
644 }
645 if _, ok := w.(http.Flusher); ok != true {
646 t.Error("unexpected interface")
647 }
648 if _, ok := w.(http.CloseNotifier); ok != false {
649 t.Error("unexpected interface")
650 }
651 if _, ok := w.(http.Hijacker); ok != false {
652 t.Error("unexpected interface")
653 }
654 if _, ok := w.(io.ReaderFrom); ok != true {
655 t.Error("unexpected interface")
656 }
657 if _, ok := w.(http.Pusher); ok != true {
658 t.Error("unexpected interface")
659 }
660
661 }
662
663 // combination 21/32
664 {
665 t.Log("http.ResponseWriter, http.Flusher, http.Hijacker")
666 inner := struct {
667 http.ResponseWriter
668 http.Flusher
669 http.Hijacker
670 }{}
671 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
672 if _, ok := w.(http.ResponseWriter); ok != true {
673 t.Error("unexpected interface")
674 }
675 if _, ok := w.(http.Flusher); ok != true {
676 t.Error("unexpected interface")
677 }
678 if _, ok := w.(http.CloseNotifier); ok != false {
679 t.Error("unexpected interface")
680 }
681 if _, ok := w.(http.Hijacker); ok != true {
682 t.Error("unexpected interface")
683 }
684 if _, ok := w.(io.ReaderFrom); ok != false {
685 t.Error("unexpected interface")
686 }
687 if _, ok := w.(http.Pusher); ok != false {
688 t.Error("unexpected interface")
689 }
690
691 }
692
693 // combination 22/32
694 {
695 t.Log("http.ResponseWriter, http.Flusher, http.Hijacker, http.Pusher")
696 inner := struct {
697 http.ResponseWriter
698 http.Flusher
699 http.Hijacker
700 http.Pusher
701 }{}
702 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
703 if _, ok := w.(http.ResponseWriter); ok != true {
704 t.Error("unexpected interface")
705 }
706 if _, ok := w.(http.Flusher); ok != true {
707 t.Error("unexpected interface")
708 }
709 if _, ok := w.(http.CloseNotifier); ok != false {
710 t.Error("unexpected interface")
711 }
712 if _, ok := w.(http.Hijacker); ok != true {
713 t.Error("unexpected interface")
714 }
715 if _, ok := w.(io.ReaderFrom); ok != false {
716 t.Error("unexpected interface")
717 }
718 if _, ok := w.(http.Pusher); ok != true {
719 t.Error("unexpected interface")
720 }
721
722 }
723
724 // combination 23/32
725 {
726 t.Log("http.ResponseWriter, http.Flusher, http.Hijacker, io.ReaderFrom")
727 inner := struct {
728 http.ResponseWriter
729 http.Flusher
730 http.Hijacker
731 io.ReaderFrom
732 }{}
733 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
734 if _, ok := w.(http.ResponseWriter); ok != true {
735 t.Error("unexpected interface")
736 }
737 if _, ok := w.(http.Flusher); ok != true {
738 t.Error("unexpected interface")
739 }
740 if _, ok := w.(http.CloseNotifier); ok != false {
741 t.Error("unexpected interface")
742 }
743 if _, ok := w.(http.Hijacker); ok != true {
744 t.Error("unexpected interface")
745 }
746 if _, ok := w.(io.ReaderFrom); ok != true {
747 t.Error("unexpected interface")
748 }
749 if _, ok := w.(http.Pusher); ok != false {
750 t.Error("unexpected interface")
751 }
752
753 }
754
755 // combination 24/32
756 {
757 t.Log("http.ResponseWriter, http.Flusher, http.Hijacker, io.ReaderFrom, http.Pusher")
758 inner := struct {
759 http.ResponseWriter
760 http.Flusher
761 http.Hijacker
762 io.ReaderFrom
763 http.Pusher
764 }{}
765 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
766 if _, ok := w.(http.ResponseWriter); ok != true {
767 t.Error("unexpected interface")
768 }
769 if _, ok := w.(http.Flusher); ok != true {
770 t.Error("unexpected interface")
771 }
772 if _, ok := w.(http.CloseNotifier); ok != false {
773 t.Error("unexpected interface")
774 }
775 if _, ok := w.(http.Hijacker); ok != true {
776 t.Error("unexpected interface")
777 }
778 if _, ok := w.(io.ReaderFrom); ok != true {
779 t.Error("unexpected interface")
780 }
781 if _, ok := w.(http.Pusher); ok != true {
782 t.Error("unexpected interface")
783 }
784
785 }
786
787 // combination 25/32
788 {
789 t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier")
790 inner := struct {
791 http.ResponseWriter
792 http.Flusher
793 http.CloseNotifier
794 }{}
795 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
796 if _, ok := w.(http.ResponseWriter); ok != true {
797 t.Error("unexpected interface")
798 }
799 if _, ok := w.(http.Flusher); ok != true {
800 t.Error("unexpected interface")
801 }
802 if _, ok := w.(http.CloseNotifier); ok != true {
803 t.Error("unexpected interface")
804 }
805 if _, ok := w.(http.Hijacker); ok != false {
806 t.Error("unexpected interface")
807 }
808 if _, ok := w.(io.ReaderFrom); ok != false {
809 t.Error("unexpected interface")
810 }
811 if _, ok := w.(http.Pusher); ok != false {
812 t.Error("unexpected interface")
813 }
814
815 }
816
817 // combination 26/32
818 {
819 t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, http.Pusher")
820 inner := struct {
821 http.ResponseWriter
822 http.Flusher
823 http.CloseNotifier
824 http.Pusher
825 }{}
826 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
827 if _, ok := w.(http.ResponseWriter); ok != true {
828 t.Error("unexpected interface")
829 }
830 if _, ok := w.(http.Flusher); ok != true {
831 t.Error("unexpected interface")
832 }
833 if _, ok := w.(http.CloseNotifier); ok != true {
834 t.Error("unexpected interface")
835 }
836 if _, ok := w.(http.Hijacker); ok != false {
837 t.Error("unexpected interface")
838 }
839 if _, ok := w.(io.ReaderFrom); ok != false {
840 t.Error("unexpected interface")
841 }
842 if _, ok := w.(http.Pusher); ok != true {
843 t.Error("unexpected interface")
844 }
845
846 }
847
848 // combination 27/32
849 {
850 t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, io.ReaderFrom")
851 inner := struct {
852 http.ResponseWriter
853 http.Flusher
854 http.CloseNotifier
855 io.ReaderFrom
856 }{}
857 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
858 if _, ok := w.(http.ResponseWriter); ok != true {
859 t.Error("unexpected interface")
860 }
861 if _, ok := w.(http.Flusher); ok != true {
862 t.Error("unexpected interface")
863 }
864 if _, ok := w.(http.CloseNotifier); ok != true {
865 t.Error("unexpected interface")
866 }
867 if _, ok := w.(http.Hijacker); ok != false {
868 t.Error("unexpected interface")
869 }
870 if _, ok := w.(io.ReaderFrom); ok != true {
871 t.Error("unexpected interface")
872 }
873 if _, ok := w.(http.Pusher); ok != false {
874 t.Error("unexpected interface")
875 }
876
877 }
878
879 // combination 28/32
880 {
881 t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, io.ReaderFrom, http.Pusher")
882 inner := struct {
883 http.ResponseWriter
884 http.Flusher
885 http.CloseNotifier
886 io.ReaderFrom
887 http.Pusher
888 }{}
889 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
890 if _, ok := w.(http.ResponseWriter); ok != true {
891 t.Error("unexpected interface")
892 }
893 if _, ok := w.(http.Flusher); ok != true {
894 t.Error("unexpected interface")
895 }
896 if _, ok := w.(http.CloseNotifier); ok != true {
897 t.Error("unexpected interface")
898 }
899 if _, ok := w.(http.Hijacker); ok != false {
900 t.Error("unexpected interface")
901 }
902 if _, ok := w.(io.ReaderFrom); ok != true {
903 t.Error("unexpected interface")
904 }
905 if _, ok := w.(http.Pusher); ok != true {
906 t.Error("unexpected interface")
907 }
908
909 }
910
911 // combination 29/32
912 {
913 t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, http.Hijacker")
914 inner := struct {
915 http.ResponseWriter
916 http.Flusher
917 http.CloseNotifier
918 http.Hijacker
919 }{}
920 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
921 if _, ok := w.(http.ResponseWriter); ok != true {
922 t.Error("unexpected interface")
923 }
924 if _, ok := w.(http.Flusher); ok != true {
925 t.Error("unexpected interface")
926 }
927 if _, ok := w.(http.CloseNotifier); ok != true {
928 t.Error("unexpected interface")
929 }
930 if _, ok := w.(http.Hijacker); ok != true {
931 t.Error("unexpected interface")
932 }
933 if _, ok := w.(io.ReaderFrom); ok != false {
934 t.Error("unexpected interface")
935 }
936 if _, ok := w.(http.Pusher); ok != false {
937 t.Error("unexpected interface")
938 }
939
940 }
941
942 // combination 30/32
943 {
944 t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, http.Hijacker, http.Pusher")
945 inner := struct {
946 http.ResponseWriter
947 http.Flusher
948 http.CloseNotifier
949 http.Hijacker
950 http.Pusher
951 }{}
952 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
953 if _, ok := w.(http.ResponseWriter); ok != true {
954 t.Error("unexpected interface")
955 }
956 if _, ok := w.(http.Flusher); ok != true {
957 t.Error("unexpected interface")
958 }
959 if _, ok := w.(http.CloseNotifier); ok != true {
960 t.Error("unexpected interface")
961 }
962 if _, ok := w.(http.Hijacker); ok != true {
963 t.Error("unexpected interface")
964 }
965 if _, ok := w.(io.ReaderFrom); ok != false {
966 t.Error("unexpected interface")
967 }
968 if _, ok := w.(http.Pusher); ok != true {
969 t.Error("unexpected interface")
970 }
971
972 }
973
974 // combination 31/32
975 {
976 t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, http.Hijacker, io.ReaderFrom")
977 inner := struct {
978 http.ResponseWriter
979 http.Flusher
980 http.CloseNotifier
981 http.Hijacker
982 io.ReaderFrom
983 }{}
984 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
985 if _, ok := w.(http.ResponseWriter); ok != true {
986 t.Error("unexpected interface")
987 }
988 if _, ok := w.(http.Flusher); ok != true {
989 t.Error("unexpected interface")
990 }
991 if _, ok := w.(http.CloseNotifier); ok != true {
992 t.Error("unexpected interface")
993 }
994 if _, ok := w.(http.Hijacker); ok != true {
995 t.Error("unexpected interface")
996 }
997 if _, ok := w.(io.ReaderFrom); ok != true {
998 t.Error("unexpected interface")
999 }
1000 if _, ok := w.(http.Pusher); ok != false {
1001 t.Error("unexpected interface")
1002 }
1003
1004 }
1005
1006 // combination 32/32
1007 {
1008 t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, http.Hijacker, io.ReaderFrom, http.Pusher")
1009 inner := struct {
1010 http.ResponseWriter
1011 http.Flusher
1012 http.CloseNotifier
1013 http.Hijacker
1014 io.ReaderFrom
1015 http.Pusher
1016 }{}
1017 w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces()
1018 if _, ok := w.(http.ResponseWriter); ok != true {
1019 t.Error("unexpected interface")
1020 }
1021 if _, ok := w.(http.Flusher); ok != true {
1022 t.Error("unexpected interface")
1023 }
1024 if _, ok := w.(http.CloseNotifier); ok != true {
1025 t.Error("unexpected interface")
1026 }
1027 if _, ok := w.(http.Hijacker); ok != true {
1028 t.Error("unexpected interface")
1029 }
1030 if _, ok := w.(io.ReaderFrom); ok != true {
1031 t.Error("unexpected interface")
1032 }
1033 if _, ok := w.(http.Pusher); ok != true {
1034 t.Error("unexpected interface")
1035 }
1036
1037 }
1038 }
0 # JSON RPC
1
2 [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.
3
4 ## Using JSON RPC with Go-Kit
5 Using JSON RPC and go-kit together is quite simple.
6
7 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.
8
9 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.
10
11 ## Example — Add Service
12 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:
13
14 {
15 "id": 123,
16 "jsonrpc": "2.0",
17 "method": "sum",
18 "params": {
19 "A": 2,
20 "B": 2
21 }
22 }
23
24 ### `EndpointCodecMap`
25 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`.
26
27 jsonrpc.EndpointCodecMap{
28 "sum": jsonrpc.EndpointCodec{
29 Endpoint: sumEndpoint,
30 Decode: decodeSumRequest,
31 Encode: encodeSumResponse,
32 },
33 }
34
35 ### Decoder
36 type DecodeRequestFunc func(context.Context, json.RawMessage) (request interface{}, err error)
37
38 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:
39
40 type SumRequest struct {
41 A, B int
42 }
43
44 So here's our decoder:
45
46 func decodeSumRequest(ctx context.Context, msg json.RawMessage) (interface{}, error) {
47 var req SumRequest
48 err := json.Unmarshal(msg, &req)
49 if err != nil {
50 return nil, err
51 }
52 return req, nil
53 }
54
55 So our `SumRequest` will now be passed to the endpoint. Once the endpoint has done its work, we hand over to the…
56
57 ### Encoder
58 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:
59
60 func encodeSumResponse(ctx context.Context, result interface{}) (json.RawMessage, error) {
61 sum, ok := result.(int)
62 if !ok {
63 return nil, errors.New("result is not an int")
64 }
65 b, err := json.Marshal(sum)
66 if err != nil {
67 return nil, err
68 }
69 return b, nil
70 }
71
72 ### Server
73 Now that we have an EndpointCodec with decoder, endpoint, and encoder, we can wire up the server:
74
75 handler := jsonrpc.NewServer(jsonrpc.EndpointCodecMap{
76 "sum": jsonrpc.EndpointCodec{
77 Endpoint: sumEndpoint,
78 Decode: decodeSumRequest,
79 Encode: encodeSumResponse,
80 },
81 })
82 http.Handle("/rpc", handler)
83 http.ListenAndServe(":80", nil)
84
85 With all of this done, our example request above should result in a response like this:
86
87 {
88 "jsonrpc": "2.0",
89 "result": 4
90 }
0 package jsonrpc
1
2 import (
3 "bytes"
4 "context"
5 "encoding/json"
6 "io/ioutil"
7 "net/http"
8 "net/url"
9 "sync/atomic"
10
11 "github.com/go-kit/kit/endpoint"
12 httptransport "github.com/go-kit/kit/transport/http"
13 )
14
15 // Client wraps a JSON RPC method and provides a method that implements endpoint.Endpoint.
16 type Client struct {
17 client httptransport.HTTPClient
18
19 // JSON RPC endpoint URL
20 tgt *url.URL
21
22 // JSON RPC method name.
23 method string
24
25 enc EncodeRequestFunc
26 dec DecodeResponseFunc
27 before []httptransport.RequestFunc
28 after []httptransport.ClientResponseFunc
29 finalizer httptransport.ClientFinalizerFunc
30 requestID RequestIDGenerator
31 bufferedStream bool
32 }
33
34 type clientRequest struct {
35 JSONRPC string `json:"jsonrpc"`
36 Method string `json:"method"`
37 Params json.RawMessage `json:"params"`
38 ID interface{} `json:"id"`
39 }
40
41 // NewClient constructs a usable Client for a single remote method.
42 func NewClient(
43 tgt *url.URL,
44 method string,
45 options ...ClientOption,
46 ) *Client {
47 c := &Client{
48 client: http.DefaultClient,
49 method: method,
50 tgt: tgt,
51 enc: DefaultRequestEncoder,
52 dec: DefaultResponseDecoder,
53 before: []httptransport.RequestFunc{},
54 after: []httptransport.ClientResponseFunc{},
55 requestID: NewAutoIncrementID(0),
56 bufferedStream: false,
57 }
58 for _, option := range options {
59 option(c)
60 }
61 return c
62 }
63
64 // DefaultRequestEncoder marshals the given request to JSON.
65 func DefaultRequestEncoder(_ context.Context, req interface{}) (json.RawMessage, error) {
66 return json.Marshal(req)
67 }
68
69 // DefaultResponseDecoder unmarshals the result to interface{}, or returns an
70 // error, if found.
71 func DefaultResponseDecoder(_ context.Context, res Response) (interface{}, error) {
72 if res.Error != nil {
73 return nil, *res.Error
74 }
75 var result interface{}
76 err := json.Unmarshal(res.Result, &result)
77 if err != nil {
78 return nil, err
79 }
80 return result, nil
81 }
82
83 // ClientOption sets an optional parameter for clients.
84 type ClientOption func(*Client)
85
86 // SetClient sets the underlying HTTP client used for requests.
87 // By default, http.DefaultClient is used.
88 func SetClient(client httptransport.HTTPClient) ClientOption {
89 return func(c *Client) { c.client = client }
90 }
91
92 // ClientBefore sets the RequestFuncs that are applied to the outgoing HTTP
93 // request before it's invoked.
94 func ClientBefore(before ...httptransport.RequestFunc) ClientOption {
95 return func(c *Client) { c.before = append(c.before, before...) }
96 }
97
98 // ClientAfter sets the ClientResponseFuncs applied to the server's HTTP
99 // response prior to it being decoded. This is useful for obtaining anything
100 // from the response and adding onto the context prior to decoding.
101 func ClientAfter(after ...httptransport.ClientResponseFunc) ClientOption {
102 return func(c *Client) { c.after = append(c.after, after...) }
103 }
104
105 // ClientFinalizer is executed at the end of every HTTP request.
106 // By default, no finalizer is registered.
107 func ClientFinalizer(f httptransport.ClientFinalizerFunc) ClientOption {
108 return func(c *Client) { c.finalizer = f }
109 }
110
111 // ClientRequestEncoder sets the func used to encode the request params to JSON.
112 // If not set, DefaultRequestEncoder is used.
113 func ClientRequestEncoder(enc EncodeRequestFunc) ClientOption {
114 return func(c *Client) { c.enc = enc }
115 }
116
117 // ClientResponseDecoder sets the func used to decode the response params from
118 // JSON. If not set, DefaultResponseDecoder is used.
119 func ClientResponseDecoder(dec DecodeResponseFunc) ClientOption {
120 return func(c *Client) { c.dec = dec }
121 }
122
123 // RequestIDGenerator returns an ID for the request.
124 type RequestIDGenerator interface {
125 Generate() interface{}
126 }
127
128 // ClientRequestIDGenerator is executed before each request to generate an ID
129 // for the request.
130 // By default, AutoIncrementRequestID is used.
131 func ClientRequestIDGenerator(g RequestIDGenerator) ClientOption {
132 return func(c *Client) { c.requestID = g }
133 }
134
135 // BufferedStream sets whether the Response.Body is left open, allowing it
136 // to be read from later. Useful for transporting a file as a buffered stream.
137 func BufferedStream(buffered bool) ClientOption {
138 return func(c *Client) { c.bufferedStream = buffered }
139 }
140
141 // Endpoint returns a usable endpoint that invokes the remote endpoint.
142 func (c Client) Endpoint() endpoint.Endpoint {
143 return func(ctx context.Context, request interface{}) (interface{}, error) {
144 ctx, cancel := context.WithCancel(ctx)
145 defer cancel()
146
147 var (
148 resp *http.Response
149 err error
150 )
151 if c.finalizer != nil {
152 defer func() {
153 if resp != nil {
154 ctx = context.WithValue(ctx, httptransport.ContextKeyResponseHeaders, resp.Header)
155 ctx = context.WithValue(ctx, httptransport.ContextKeyResponseSize, resp.ContentLength)
156 }
157 c.finalizer(ctx, err)
158 }()
159 }
160
161 ctx = context.WithValue(ctx, ContextKeyRequestMethod, c.method)
162
163 var params json.RawMessage
164 if params, err = c.enc(ctx, request); err != nil {
165 return nil, err
166 }
167 rpcReq := clientRequest{
168 JSONRPC: Version,
169 Method: c.method,
170 Params: params,
171 ID: c.requestID.Generate(),
172 }
173
174 req, err := http.NewRequest("POST", c.tgt.String(), nil)
175 if err != nil {
176 return nil, err
177 }
178
179 req.Header.Set("Content-Type", "application/json; charset=utf-8")
180 var b bytes.Buffer
181 req.Body = ioutil.NopCloser(&b)
182 err = json.NewEncoder(&b).Encode(rpcReq)
183 if err != nil {
184 return nil, err
185 }
186
187 for _, f := range c.before {
188 ctx = f(ctx, req)
189 }
190
191 resp, err = c.client.Do(req.WithContext(ctx))
192 if err != nil {
193 return nil, err
194 }
195
196 if !c.bufferedStream {
197 defer resp.Body.Close()
198 }
199
200 for _, f := range c.after {
201 ctx = f(ctx, resp)
202 }
203
204 // Decode the body into an object
205 var rpcRes Response
206 err = json.NewDecoder(resp.Body).Decode(&rpcRes)
207 if err != nil {
208 return nil, err
209 }
210
211 response, err := c.dec(ctx, rpcRes)
212 if err != nil {
213 return nil, err
214 }
215
216 return response, nil
217 }
218 }
219
220 // ClientFinalizerFunc can be used to perform work at the end of a client HTTP
221 // request, after the response is returned. The principal
222 // intended use is for error logging. Additional response parameters are
223 // provided in the context under keys with the ContextKeyResponse prefix.
224 // Note: err may be nil. There maybe also no additional response parameters
225 // depending on when an error occurs.
226 type ClientFinalizerFunc func(ctx context.Context, err error)
227
228 // autoIncrementID is a RequestIDGenerator that generates
229 // auto-incrementing integer IDs.
230 type autoIncrementID struct {
231 v *uint64
232 }
233
234 // NewAutoIncrementID returns an auto-incrementing request ID generator,
235 // initialised with the given value.
236 func NewAutoIncrementID(init uint64) RequestIDGenerator {
237 // Offset by one so that the first generated value = init.
238 v := init - 1
239 return &autoIncrementID{v: &v}
240 }
241
242 // Generate satisfies RequestIDGenerator
243 func (i *autoIncrementID) Generate() interface{} {
244 id := atomic.AddUint64(i.v, 1)
245 return id
246 }
0 package jsonrpc_test
1
2 import (
3 "context"
4 "encoding/json"
5 "io"
6 "io/ioutil"
7 "net/http"
8 "net/http/httptest"
9 "net/url"
10 "testing"
11
12 "github.com/go-kit/kit/transport/http/jsonrpc"
13 )
14
15 type TestResponse struct {
16 Body io.ReadCloser
17 String string
18 }
19
20 type testServerResponseOptions struct {
21 Body string
22 Status int
23 }
24
25 func httptestServer(t *testing.T) *httptest.Server {
26 return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
27 defer r.Body.Close()
28
29 var testReq jsonrpc.Request
30 if err := json.NewDecoder(r.Body).Decode(&testReq); err != nil {
31 t.Fatal(err)
32 }
33
34 var options testServerResponseOptions
35 if err := json.Unmarshal(testReq.Params, &options); err != nil {
36 t.Fatal(err)
37 }
38
39 if options.Status == 0 {
40 options.Status = http.StatusOK
41 }
42
43 w.WriteHeader(options.Status)
44 w.Write([]byte(options.Body))
45 }))
46 }
47
48 func TestBeforeAfterFuncs(t *testing.T) {
49 t.Parallel()
50
51 var tests = []struct {
52 name string
53 status int
54 body string
55 }{
56 {
57 name: "empty body",
58 body: "",
59 },
60 {
61 name: "empty body 500",
62 body: "",
63 status: 500,
64 },
65
66 {
67 name: "empty json body",
68 body: "{}",
69 },
70 {
71 name: "error",
72 body: `{"jsonrpc":"2.0","error":{"code":32603,"message":"Bad thing happened."}}`,
73 },
74 }
75
76 server := httptestServer(t)
77 defer server.Close()
78
79 testUrl, err := url.Parse(server.URL)
80 if err != nil {
81 t.Fatal(err)
82 }
83
84 for _, tt := range tests {
85 t.Run(tt.name, func(t *testing.T) {
86 beforeCalled := false
87 afterCalled := false
88 finalizerCalled := false
89
90 sut := jsonrpc.NewClient(
91 testUrl,
92 "dummy",
93 jsonrpc.ClientBefore(func(ctx context.Context, req *http.Request) context.Context {
94 beforeCalled = true
95 return ctx
96 }),
97 jsonrpc.ClientAfter(func(ctx context.Context, resp *http.Response) context.Context {
98 afterCalled = true
99 return ctx
100 }),
101 jsonrpc.ClientFinalizer(func(ctx context.Context, err error) {
102 finalizerCalled = true
103 }),
104 )
105
106 sut.Endpoint()(context.TODO(), testServerResponseOptions{Body: tt.body, Status: tt.status})
107 if !beforeCalled {
108 t.Fatal("Expected client before func to be called. Wasn't.")
109 }
110 if !afterCalled {
111 t.Fatal("Expected client after func to be called. Wasn't.")
112 }
113 if !finalizerCalled {
114 t.Fatal("Expected client finalizer func to be called. Wasn't.")
115 }
116
117 })
118
119 }
120
121 }
122
123 type staticIDGenerator int
124
125 func (g staticIDGenerator) Generate() interface{} { return g }
126
127 func TestClientHappyPath(t *testing.T) {
128 t.Parallel()
129
130 var (
131 afterCalledKey = "AC"
132 beforeHeaderKey = "BF"
133 beforeHeaderValue = "beforeFuncWozEre"
134 testbody = `{"jsonrpc":"2.0", "result":5}`
135 requestBody []byte
136 beforeFunc = func(ctx context.Context, r *http.Request) context.Context {
137 r.Header.Add(beforeHeaderKey, beforeHeaderValue)
138 return ctx
139 }
140 encode = func(ctx context.Context, req interface{}) (json.RawMessage, error) {
141 return json.Marshal(req)
142 }
143 afterFunc = func(ctx context.Context, r *http.Response) context.Context {
144 return context.WithValue(ctx, afterCalledKey, true)
145 }
146 finalizerCalled = false
147 fin = func(ctx context.Context, err error) {
148 finalizerCalled = true
149 }
150 decode = func(ctx context.Context, res jsonrpc.Response) (interface{}, error) {
151 if ac := ctx.Value(afterCalledKey); ac == nil {
152 t.Fatal("after not called")
153 }
154 var result int
155 err := json.Unmarshal(res.Result, &result)
156 if err != nil {
157 return nil, err
158 }
159 return result, nil
160 }
161
162 wantID = 666
163 gen = staticIDGenerator(wantID)
164 )
165
166 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
167 if r.Header.Get(beforeHeaderKey) != beforeHeaderValue {
168 t.Fatal("Header not set by before func.")
169 }
170
171 b, err := ioutil.ReadAll(r.Body)
172 if err != nil && err != io.EOF {
173 t.Fatal(err)
174 }
175 requestBody = b
176
177 w.WriteHeader(http.StatusOK)
178 w.Write([]byte(testbody))
179 }))
180 defer server.Close()
181
182 sut := jsonrpc.NewClient(
183 mustParse(server.URL),
184 "add",
185 jsonrpc.ClientRequestEncoder(encode),
186 jsonrpc.ClientResponseDecoder(decode),
187 jsonrpc.ClientBefore(beforeFunc),
188 jsonrpc.ClientAfter(afterFunc),
189 jsonrpc.ClientRequestIDGenerator(gen),
190 jsonrpc.ClientFinalizer(fin),
191 jsonrpc.SetClient(http.DefaultClient),
192 jsonrpc.BufferedStream(false),
193 )
194
195 type addRequest struct {
196 A int
197 B int
198 }
199
200 in := addRequest{2, 2}
201
202 result, err := sut.Endpoint()(context.Background(), in)
203 if err != nil {
204 t.Fatal(err)
205 }
206 ri, ok := result.(int)
207 if !ok {
208 t.Fatalf("result is not int: (%T)%+v", result, result)
209 }
210 if ri != 5 {
211 t.Fatalf("want=5, got=%d", ri)
212 }
213
214 var requestAtServer jsonrpc.Request
215 err = json.Unmarshal(requestBody, &requestAtServer)
216 if err != nil {
217 t.Fatal(err)
218 }
219 if id, _ := requestAtServer.ID.Int(); id != wantID {
220 t.Fatalf("Request ID at server: want=%d, got=%d", wantID, id)
221 }
222 if requestAtServer.JSONRPC != jsonrpc.Version {
223 t.Fatalf("JSON-RPC version at server: want=%s, got=%s", jsonrpc.Version, requestAtServer.JSONRPC)
224 }
225
226 var paramsAtServer addRequest
227 err = json.Unmarshal(requestAtServer.Params, &paramsAtServer)
228 if err != nil {
229 t.Fatal(err)
230 }
231
232 if paramsAtServer != in {
233 t.Fatalf("want=%+v, got=%+v", in, paramsAtServer)
234 }
235
236 if !finalizerCalled {
237 t.Fatal("Expected finalizer to be called. Wasn't.")
238 }
239 }
240
241 func TestCanUseDefaults(t *testing.T) {
242 t.Parallel()
243
244 var (
245 testbody = `{"jsonrpc":"2.0", "result":"boogaloo"}`
246 requestBody []byte
247 )
248
249 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
250 b, err := ioutil.ReadAll(r.Body)
251 if err != nil && err != io.EOF {
252 t.Fatal(err)
253 }
254 requestBody = b
255
256 w.WriteHeader(http.StatusOK)
257 w.Write([]byte(testbody))
258 }))
259 defer server.Close()
260
261 sut := jsonrpc.NewClient(
262 mustParse(server.URL),
263 "add",
264 )
265
266 type addRequest struct {
267 A int
268 B int
269 }
270
271 in := addRequest{2, 2}
272
273 result, err := sut.Endpoint()(context.Background(), in)
274 if err != nil {
275 t.Fatal(err)
276 }
277 rs, ok := result.(string)
278 if !ok {
279 t.Fatalf("result is not string: (%T)%+v", result, result)
280 }
281 if rs != "boogaloo" {
282 t.Fatalf("want=boogaloo, got=%s", rs)
283 }
284
285 var requestAtServer jsonrpc.Request
286 err = json.Unmarshal(requestBody, &requestAtServer)
287 if err != nil {
288 t.Fatal(err)
289 }
290 var paramsAtServer addRequest
291 err = json.Unmarshal(requestAtServer.Params, &paramsAtServer)
292 if err != nil {
293 t.Fatal(err)
294 }
295
296 if paramsAtServer != in {
297 t.Fatalf("want=%+v, got=%+v", in, paramsAtServer)
298 }
299 }
300
301 func TestClientCanHandleJSONRPCError(t *testing.T) {
302 t.Parallel()
303
304 var testbody = `{
305 "jsonrpc": "2.0",
306 "error": {
307 "code": -32603,
308 "message": "Bad thing happened."
309 }
310 }`
311 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
312 w.WriteHeader(http.StatusOK)
313 w.Write([]byte(testbody))
314 }))
315 defer server.Close()
316
317 sut := jsonrpc.NewClient(mustParse(server.URL), "add")
318
319 _, err := sut.Endpoint()(context.Background(), 5)
320 if err == nil {
321 t.Fatal("Expected error, got none.")
322 }
323
324 {
325 want := "Bad thing happened."
326 got := err.Error()
327 if got != want {
328 t.Fatalf("error message: want=%s, got=%s", want, got)
329 }
330 }
331
332 type errorCoder interface {
333 ErrorCode() int
334 }
335 ec, ok := err.(errorCoder)
336 if !ok {
337 t.Fatal("Error is not errorCoder")
338 }
339
340 {
341 want := -32603
342 got := ec.ErrorCode()
343 if got != want {
344 t.Fatalf("error code: want=%d, got=%d", want, got)
345 }
346 }
347 }
348
349 func TestDefaultAutoIncrementer(t *testing.T) {
350 t.Parallel()
351
352 sut := jsonrpc.NewAutoIncrementID(0)
353 var want uint64
354 for ; want < 100; want++ {
355 got := sut.Generate()
356 if got != want {
357 t.Fatalf("want=%d, got=%d", want, got)
358 }
359 }
360 }
361
362 func mustParse(s string) *url.URL {
363 u, err := url.Parse(s)
364 if err != nil {
365 panic(err)
366 }
367 return u
368 }
0 // Package jsonrpc provides a JSON RPC (v2.0) binding for endpoints.
1 // See http://www.jsonrpc.org/specification
2 package jsonrpc
0 package jsonrpc
1
2 import (
3 "encoding/json"
4
5 "github.com/go-kit/kit/endpoint"
6
7 "context"
8 )
9
10 // Server-Side Codec
11
12 // EndpointCodec defines a server Endpoint and its associated codecs
13 type EndpointCodec struct {
14 Endpoint endpoint.Endpoint
15 Decode DecodeRequestFunc
16 Encode EncodeResponseFunc
17 }
18
19 // EndpointCodecMap maps the Request.Method to the proper EndpointCodec
20 type EndpointCodecMap map[string]EndpointCodec
21
22 // DecodeRequestFunc extracts a user-domain request object from raw JSON
23 // It's designed to be used in JSON RPC servers, for server-side endpoints.
24 // One straightforward DecodeRequestFunc could be something that unmarshals
25 // JSON from the request body to the concrete request type.
26 type DecodeRequestFunc func(context.Context, json.RawMessage) (request interface{}, err error)
27
28 // EncodeResponseFunc encodes the passed response object to a JSON RPC result.
29 // It's designed to be used in HTTP servers, for server-side endpoints.
30 // One straightforward EncodeResponseFunc could be something that JSON encodes
31 // the object directly.
32 type EncodeResponseFunc func(context.Context, interface{}) (response json.RawMessage, err error)
33
34 // Client-Side Codec
35
36 // EncodeRequestFunc encodes the given request object to raw JSON.
37 // It's designed to be used in JSON RPC clients, for client-side
38 // endpoints. One straightforward EncodeResponseFunc could be something that
39 // JSON encodes the object directly.
40 type EncodeRequestFunc func(context.Context, interface{}) (request json.RawMessage, err error)
41
42 // DecodeResponseFunc extracts a user-domain response object from an JSON RPC
43 // response object. It's designed to be used in JSON RPC clients, for
44 // client-side endpoints. It is the responsibility of this function to decide
45 // whether any error present in the JSON RPC response should be surfaced to the
46 // client endpoint.
47 type DecodeResponseFunc func(context.Context, Response) (response interface{}, err error)
0 package jsonrpc
1
2 // Error defines a JSON RPC error that can be returned
3 // in a Response from the spec
4 // http://www.jsonrpc.org/specification#error_object
5 type Error struct {
6 Code int `json:"code"`
7 Message string `json:"message"`
8 Data interface{} `json:"data,omitempty"`
9 }
10
11 // Error implements error.
12 func (e Error) Error() string {
13 if e.Message != "" {
14 return e.Message
15 }
16 return errorMessage[e.Code]
17 }
18
19 // ErrorCode returns the JSON RPC error code associated with the error.
20 func (e Error) ErrorCode() int {
21 return e.Code
22 }
23
24 const (
25 // ParseError defines invalid JSON was received by the server.
26 // An error occurred on the server while parsing the JSON text.
27 ParseError int = -32700
28
29 // InvalidRequestError defines the JSON sent is not a valid Request object.
30 InvalidRequestError int = -32600
31
32 // MethodNotFoundError defines the method does not exist / is not available.
33 MethodNotFoundError int = -32601
34
35 // InvalidParamsError defines invalid method parameter(s).
36 InvalidParamsError int = -32602
37
38 // InternalError defines a server error
39 InternalError int = -32603
40 )
41
42 var errorMessage = map[int]string{
43 ParseError: "An error occurred on the server while parsing the JSON text.",
44 InvalidRequestError: "The JSON sent is not a valid Request object.",
45 MethodNotFoundError: "The method does not exist / is not available.",
46 InvalidParamsError: "Invalid method parameter(s).",
47 InternalError: "Internal JSON-RPC error.",
48 }
49
50 // ErrorMessage returns a message for the JSON RPC error code. It returns the empty
51 // string if the code is unknown.
52 func ErrorMessage(code int) string {
53 return errorMessage[code]
54 }
55
56 type parseError string
57
58 func (e parseError) Error() string {
59 return string(e)
60 }
61 func (e parseError) ErrorCode() int {
62 return ParseError
63 }
64
65 type invalidRequestError string
66
67 func (e invalidRequestError) Error() string {
68 return string(e)
69 }
70 func (e invalidRequestError) ErrorCode() int {
71 return InvalidRequestError
72 }
73
74 type methodNotFoundError string
75
76 func (e methodNotFoundError) Error() string {
77 return string(e)
78 }
79 func (e methodNotFoundError) ErrorCode() int {
80 return MethodNotFoundError
81 }
82
83 type invalidParamsError string
84
85 func (e invalidParamsError) Error() string {
86 return string(e)
87 }
88 func (e invalidParamsError) ErrorCode() int {
89 return InvalidParamsError
90 }
91
92 type internalError string
93
94 func (e internalError) Error() string {
95 return string(e)
96 }
97 func (e internalError) ErrorCode() int {
98 return InternalError
99 }
0 package jsonrpc
1
2 import "testing"
3
4 func TestError(t *testing.T) {
5 wantCode := ParseError
6 sut := Error{
7 Code: wantCode,
8 }
9
10 gotCode := sut.ErrorCode()
11 if gotCode != wantCode {
12 t.Fatalf("want=%d, got=%d", gotCode, wantCode)
13 }
14
15 if sut.Error() == "" {
16 t.Fatal("Empty error string.")
17 }
18
19 want := "override"
20 sut.Message = want
21 got := sut.Error()
22 if sut.Error() != want {
23 t.Fatalf("overridden error message: want=%s, got=%s", want, got)
24 }
25
26 }
27 func TestErrorsSatisfyError(t *testing.T) {
28 errs := []interface{}{
29 parseError("parseError"),
30 invalidRequestError("invalidRequestError"),
31 methodNotFoundError("methodNotFoundError"),
32 invalidParamsError("invalidParamsError"),
33 internalError("internalError"),
34 }
35 for _, e := range errs {
36 err, ok := e.(error)
37 if !ok {
38 t.Fatalf("Couldn't assert %s as error.", e)
39 }
40 errString := err.Error()
41 if errString == "" {
42 t.Fatal("Empty error string")
43 }
44
45 ec, ok := e.(ErrorCoder)
46 if !ok {
47 t.Fatalf("Couldn't assert %s as ErrorCoder.", e)
48 }
49 if ErrorMessage(ec.ErrorCode()) == "" {
50 t.Fatalf("Error type %s returned code of %d, which does not map to error string", e, ec.ErrorCode())
51 }
52 }
53 }
0 package jsonrpc
1
2 import (
3 "context"
4 "encoding/json"
5 "net/http"
6 )
7
8 // Request defines a JSON RPC request from the spec
9 // http://www.jsonrpc.org/specification#request_object
10 type Request struct {
11 JSONRPC string `json:"jsonrpc"`
12 Method string `json:"method"`
13 Params json.RawMessage `json:"params"`
14 ID *RequestID `json:"id"`
15 }
16
17 // RequestID defines a request ID that can be string, number, or null.
18 // An identifier established by the Client that MUST contain a String,
19 // Number, or NULL value if included.
20 // If it is not included it is assumed to be a notification.
21 // The value SHOULD normally not be Null and
22 // Numbers SHOULD NOT contain fractional parts.
23 type RequestID struct {
24 intValue int
25 intError error
26 floatValue float32
27 floatError error
28 stringValue string
29 stringError error
30 }
31
32 // RequestFunc may take information from decoded json body and place in
33 // request context. In Servers, RequestFuncs are executed after json is parsed
34 // but prior to invoking the codec
35 type RequestFunc func(context.Context, *http.Request, Request) context.Context
36
37 // UnmarshalJSON satisfies json.Unmarshaler
38 func (id *RequestID) UnmarshalJSON(b []byte) error {
39 id.intError = json.Unmarshal(b, &id.intValue)
40 id.floatError = json.Unmarshal(b, &id.floatValue)
41 id.stringError = json.Unmarshal(b, &id.stringValue)
42
43 return nil
44 }
45
46 func (id *RequestID) MarshalJSON() ([]byte, error) {
47 if id.intError == nil {
48 return json.Marshal(id.intValue)
49 } else if id.floatError == nil {
50 return json.Marshal(id.floatValue)
51 } else {
52 return json.Marshal(id.stringValue)
53 }
54 }
55
56 // Int returns the ID as an integer value.
57 // An error is returned if the ID can't be treated as an int.
58 func (id *RequestID) Int() (int, error) {
59 return id.intValue, id.intError
60 }
61
62 // Float32 returns the ID as a float value.
63 // An error is returned if the ID can't be treated as an float.
64 func (id *RequestID) Float32() (float32, error) {
65 return id.floatValue, id.floatError
66 }
67
68 // String returns the ID as a string value.
69 // An error is returned if the ID can't be treated as an string.
70 func (id *RequestID) String() (string, error) {
71 return id.stringValue, id.stringError
72 }
73
74 // Response defines a JSON RPC response from the spec
75 // http://www.jsonrpc.org/specification#response_object
76 type Response struct {
77 JSONRPC string `json:"jsonrpc"`
78 Result json.RawMessage `json:"result,omitempty"`
79 Error *Error `json:"error,omitempty"`
80 ID *RequestID `json:"id"`
81 }
82
83 const (
84 // Version defines the version of the JSON RPC implementation
85 Version string = "2.0"
86
87 // ContentType defines the content type to be served.
88 ContentType string = "application/json; charset=utf-8"
89 )
90
91 type contextKey int
92
93 const (
94 ContextKeyRequestMethod contextKey = iota
95 )
0 package jsonrpc_test
1
2 import (
3 "encoding/json"
4 "fmt"
5 "testing"
6
7 "github.com/go-kit/kit/transport/http/jsonrpc"
8 )
9
10 func TestCanUnMarshalID(t *testing.T) {
11 cases := []struct {
12 JSON string
13 expType string
14 expValue interface{}
15 }{
16 {`12345`, "int", 12345},
17 {`12345.6`, "float", 12345.6},
18 {`"stringaling"`, "string", "stringaling"},
19 }
20
21 for _, c := range cases {
22 r := jsonrpc.Request{}
23 JSON := fmt.Sprintf(`{"id":%s}`, c.JSON)
24
25 var foo interface{}
26 _ = json.Unmarshal([]byte(JSON), &foo)
27
28 err := json.Unmarshal([]byte(JSON), &r)
29 if err != nil {
30 t.Fatalf("Unexpected error unmarshaling JSON into request: %s\n", err)
31 }
32 id := r.ID
33
34 switch c.expType {
35 case "int":
36 want := c.expValue.(int)
37 got, err := id.Int()
38 if err != nil {
39 t.Fatal(err)
40 }
41 if got != want {
42 t.Fatalf("'%s' Int(): want %d, got %d.", c.JSON, want, got)
43 }
44
45 // Allow an int ID to be interpreted as a float.
46 wantf := float32(c.expValue.(int))
47 gotf, err := id.Float32()
48 if err != nil {
49 t.Fatal(err)
50 }
51 if gotf != wantf {
52 t.Fatalf("'%s' Int value as Float32(): want %f, got %f.", c.JSON, wantf, gotf)
53 }
54
55 _, err = id.String()
56 if err == nil {
57 t.Fatal("Expected String() to error for int value. Didn't.")
58 }
59 case "string":
60 want := c.expValue.(string)
61 got, err := id.String()
62 if err != nil {
63 t.Fatal(err)
64 }
65 if got != want {
66 t.Fatalf("'%s' String(): want %s, got %s.", c.JSON, want, got)
67 }
68
69 _, err = id.Int()
70 if err == nil {
71 t.Fatal("Expected Int() to error for string value. Didn't.")
72 }
73 _, err = id.Float32()
74 if err == nil {
75 t.Fatal("Expected Float32() to error for string value. Didn't.")
76 }
77 case "float32":
78 want := c.expValue.(float32)
79 got, err := id.Float32()
80 if err != nil {
81 t.Fatal(err)
82 }
83 if got != want {
84 t.Fatalf("'%s' Float32(): want %f, got %f.", c.JSON, want, got)
85 }
86
87 _, err = id.String()
88 if err == nil {
89 t.Fatal("Expected String() to error for float value. Didn't.")
90 }
91 _, err = id.Int()
92 if err == nil {
93 t.Fatal("Expected Int() to error for float value. Didn't.")
94 }
95 }
96 }
97 }
98
99 func TestCanUnmarshalNullID(t *testing.T) {
100 r := jsonrpc.Request{}
101 JSON := `{"id":null}`
102 err := json.Unmarshal([]byte(JSON), &r)
103 if err != nil {
104 t.Fatalf("Unexpected error unmarshaling JSON into request: %s\n", err)
105 }
106
107 if r.ID != nil {
108 t.Fatalf("Expected ID to be nil, got %+v.\n", r.ID)
109 }
110 }
111
112 func TestCanMarshalID(t *testing.T) {
113 cases := []struct {
114 JSON string
115 expType string
116 expValue interface{}
117 }{
118 {`12345`, "int", 12345},
119 {`12345.6`, "float", 12345.6},
120 {`"stringaling"`, "string", "stringaling"},
121 {`null`, "null", nil},
122 }
123
124 for _, c := range cases {
125 req := jsonrpc.Request{}
126 JSON := fmt.Sprintf(`{"jsonrpc":"2.0","id":%s}`, c.JSON)
127 json.Unmarshal([]byte(JSON), &req)
128 resp := jsonrpc.Response{ID: req.ID, JSONRPC: req.JSONRPC}
129
130 want := JSON
131 bol, _ := json.Marshal(resp)
132 got := string(bol)
133 if got != want {
134 t.Fatalf("'%s': want %s, got %s.", c.expType, want, got)
135 }
136 }
137 }
0 package jsonrpc
1
2 import (
3 "context"
4 "encoding/json"
5 "fmt"
6 "io"
7 "net/http"
8
9 httptransport "github.com/go-kit/kit/transport/http"
10 "github.com/go-kit/log"
11 )
12
13 type requestIDKeyType struct{}
14
15 var requestIDKey requestIDKeyType
16
17 // Server wraps an endpoint and implements http.Handler.
18 type Server struct {
19 ecm EndpointCodecMap
20 before []httptransport.RequestFunc
21 beforeCodec []RequestFunc
22 after []httptransport.ServerResponseFunc
23 errorEncoder httptransport.ErrorEncoder
24 finalizer httptransport.ServerFinalizerFunc
25 logger log.Logger
26 }
27
28 // NewServer constructs a new server, which implements http.Server.
29 func NewServer(
30 ecm EndpointCodecMap,
31 options ...ServerOption,
32 ) *Server {
33 s := &Server{
34 ecm: ecm,
35 errorEncoder: DefaultErrorEncoder,
36 logger: log.NewNopLogger(),
37 }
38 for _, option := range options {
39 option(s)
40 }
41 return s
42 }
43
44 // ServerOption sets an optional parameter for servers.
45 type ServerOption func(*Server)
46
47 // ServerBefore functions are executed on the HTTP request object before the
48 // request is decoded.
49 func ServerBefore(before ...httptransport.RequestFunc) ServerOption {
50 return func(s *Server) { s.before = append(s.before, before...) }
51 }
52
53 // ServerBeforeCodec functions are executed after the JSON request body has been
54 // decoded, but before the method's decoder is called. This provides an opportunity
55 // for middleware to inspect the contents of the rpc request before being passed
56 // to the codec.
57 func ServerBeforeCodec(beforeCodec ...RequestFunc) ServerOption {
58 return func(s *Server) { s.beforeCodec = append(s.beforeCodec, beforeCodec...) }
59 }
60
61 // ServerAfter functions are executed on the HTTP response writer after the
62 // endpoint is invoked, but before anything is written to the client.
63 func ServerAfter(after ...httptransport.ServerResponseFunc) ServerOption {
64 return func(s *Server) { s.after = append(s.after, after...) }
65 }
66
67 // ServerErrorEncoder is used to encode errors to the http.ResponseWriter
68 // whenever they're encountered in the processing of a request. Clients can
69 // use this to provide custom error formatting and response codes. By default,
70 // errors will be written with the DefaultErrorEncoder.
71 func ServerErrorEncoder(ee httptransport.ErrorEncoder) ServerOption {
72 return func(s *Server) { s.errorEncoder = ee }
73 }
74
75 // ServerErrorLogger is used to log non-terminal errors. By default, no errors
76 // are logged. This is intended as a diagnostic measure. Finer-grained control
77 // of error handling, including logging in more detail, should be performed in a
78 // custom ServerErrorEncoder or ServerFinalizer, both of which have access to
79 // the context.
80 func ServerErrorLogger(logger log.Logger) ServerOption {
81 return func(s *Server) { s.logger = logger }
82 }
83
84 // ServerFinalizer is executed at the end of every HTTP request.
85 // By default, no finalizer is registered.
86 func ServerFinalizer(f httptransport.ServerFinalizerFunc) ServerOption {
87 return func(s *Server) { s.finalizer = f }
88 }
89
90 // ServeHTTP implements http.Handler.
91 func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
92 if r.Method != http.MethodPost {
93 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
94 w.WriteHeader(http.StatusMethodNotAllowed)
95 _, _ = io.WriteString(w, "405 must POST\n")
96 return
97 }
98 ctx := r.Context()
99
100 if s.finalizer != nil {
101 iw := &interceptingWriter{w, http.StatusOK}
102 defer func() { s.finalizer(ctx, iw.code, r) }()
103 w = iw
104 }
105
106 for _, f := range s.before {
107 ctx = f(ctx, r)
108 }
109
110 // Decode the body into an object
111 var req Request
112 err := json.NewDecoder(r.Body).Decode(&req)
113 if err != nil {
114 rpcerr := parseError("JSON could not be decoded: " + err.Error())
115 s.logger.Log("err", rpcerr)
116 s.errorEncoder(ctx, rpcerr, w)
117 return
118 }
119
120 ctx = context.WithValue(ctx, requestIDKey, req.ID)
121 ctx = context.WithValue(ctx, ContextKeyRequestMethod, req.Method)
122
123 for _, f := range s.beforeCodec {
124 ctx = f(ctx, r, req)
125 }
126
127 // Get the endpoint and codecs from the map using the method
128 // defined in the JSON object
129 ecm, ok := s.ecm[req.Method]
130 if !ok {
131 err := methodNotFoundError(fmt.Sprintf("Method %s was not found.", req.Method))
132 s.logger.Log("err", err)
133 s.errorEncoder(ctx, err, w)
134 return
135 }
136
137 // Decode the JSON "params"
138 reqParams, err := ecm.Decode(ctx, req.Params)
139 if err != nil {
140 s.logger.Log("err", err)
141 s.errorEncoder(ctx, err, w)
142 return
143 }
144
145 // Call the Endpoint with the params
146 response, err := ecm.Endpoint(ctx, reqParams)
147 if err != nil {
148 s.logger.Log("err", err)
149 s.errorEncoder(ctx, err, w)
150 return
151 }
152
153 for _, f := range s.after {
154 ctx = f(ctx, w)
155 }
156
157 res := Response{
158 ID: req.ID,
159 JSONRPC: Version,
160 }
161
162 // Encode the response from the Endpoint
163 resParams, err := ecm.Encode(ctx, response)
164 if err != nil {
165 s.logger.Log("err", err)
166 s.errorEncoder(ctx, err, w)
167 return
168 }
169
170 res.Result = resParams
171
172 w.Header().Set("Content-Type", ContentType)
173 _ = json.NewEncoder(w).Encode(res)
174 }
175
176 // DefaultErrorEncoder writes the error to the ResponseWriter,
177 // as a json-rpc error response, with an InternalError status code.
178 // The Error() string of the error will be used as the response error message.
179 // If the error implements ErrorCoder, the provided code will be set on the
180 // response error.
181 // If the error implements Headerer, the given headers will be set.
182 func DefaultErrorEncoder(ctx context.Context, err error, w http.ResponseWriter) {
183 w.Header().Set("Content-Type", ContentType)
184 if headerer, ok := err.(httptransport.Headerer); ok {
185 for k := range headerer.Headers() {
186 w.Header().Set(k, headerer.Headers().Get(k))
187 }
188 }
189
190 e := Error{
191 Code: InternalError,
192 Message: err.Error(),
193 }
194 if sc, ok := err.(ErrorCoder); ok {
195 e.Code = sc.ErrorCode()
196 }
197
198 w.WriteHeader(http.StatusOK)
199
200 var requestID *RequestID
201 if v := ctx.Value(requestIDKey); v != nil {
202 requestID = v.(*RequestID)
203 }
204 _ = json.NewEncoder(w).Encode(Response{
205 ID: requestID,
206 JSONRPC: Version,
207 Error: &e,
208 })
209 }
210
211 // ErrorCoder is checked by DefaultErrorEncoder. If an error value implements
212 // ErrorCoder, the integer result of ErrorCode() will be used as the JSONRPC
213 // error code when encoding the error.
214 //
215 // By default, InternalError (-32603) is used.
216 type ErrorCoder interface {
217 ErrorCode() int
218 }
219
220 // interceptingWriter intercepts calls to WriteHeader, so that a finalizer
221 // can be given the correct status code.
222 type interceptingWriter struct {
223 http.ResponseWriter
224 code int
225 }
226
227 // WriteHeader may not be explicitly called, so care must be taken to
228 // initialize w.code to its default value of http.StatusOK.
229 func (w *interceptingWriter) WriteHeader(code int) {
230 w.code = code
231 w.ResponseWriter.WriteHeader(code)
232 }
0 package jsonrpc_test
1
2 import (
3 "context"
4 "encoding/json"
5 "errors"
6 "io"
7 "io/ioutil"
8 "net/http"
9 "net/http/httptest"
10 "strings"
11 "testing"
12 "time"
13
14 "github.com/go-kit/kit/endpoint"
15 "github.com/go-kit/kit/transport/http/jsonrpc"
16 )
17
18 func addBody() io.Reader {
19 return body(`{"jsonrpc": "2.0", "method": "add", "params": [3, 2], "id": 1}`)
20 }
21
22 func body(in string) io.Reader {
23 return strings.NewReader(in)
24 }
25
26 func unmarshalResponse(body []byte) (resp jsonrpc.Response, err error) {
27 err = json.Unmarshal(body, &resp)
28 return
29 }
30
31 func expectErrorCode(t *testing.T, want int, body []byte) {
32 t.Helper()
33
34 r, err := unmarshalResponse(body)
35 if err != nil {
36 t.Fatalf("Can't decode response: %v (%s)", err, body)
37 }
38 if r.Error == nil {
39 t.Fatalf("Expected error on response. Got none: %s", body)
40 }
41 if have := r.Error.Code; want != have {
42 t.Fatalf("Unexpected error code. Want %d, have %d: %s", want, have, body)
43 }
44 }
45
46 func expectValidRequestID(t *testing.T, want int, body []byte) {
47 t.Helper()
48
49 r, err := unmarshalResponse(body)
50 if err != nil {
51 t.Fatalf("Can't decode response: %v (%s)", err, body)
52 }
53 have, err := r.ID.Int()
54 if err != nil {
55 t.Fatalf("Can't get requestID in response. err=%s, body=%s", err, body)
56 }
57 if want != have {
58 t.Fatalf("Request ID: want %d, have %d (%s)", want, have, body)
59 }
60 }
61
62 func expectNilRequestID(t *testing.T, body []byte) {
63 t.Helper()
64
65 r, err := unmarshalResponse(body)
66 if err != nil {
67 t.Fatalf("Can't decode response: %v (%s)", err, body)
68 }
69 if r.ID != nil {
70 t.Fatalf("Request ID: want nil, have %v", r.ID)
71 }
72 }
73
74 func nopDecoder(context.Context, json.RawMessage) (interface{}, error) { return struct{}{}, nil }
75 func nopEncoder(context.Context, interface{}) (json.RawMessage, error) { return []byte("[]"), nil }
76
77 type mockLogger struct {
78 Called bool
79 LastArgs []interface{}
80 }
81
82 func (l *mockLogger) Log(keyvals ...interface{}) error {
83 l.Called = true
84 l.LastArgs = append(l.LastArgs, keyvals)
85 return nil
86 }
87
88 func TestServerBadDecode(t *testing.T) {
89 ecm := jsonrpc.EndpointCodecMap{
90 "add": jsonrpc.EndpointCodec{
91 Endpoint: endpoint.Nop,
92 Decode: func(context.Context, json.RawMessage) (interface{}, error) { return struct{}{}, errors.New("oof") },
93 Encode: nopEncoder,
94 },
95 }
96 logger := mockLogger{}
97 handler := jsonrpc.NewServer(ecm, jsonrpc.ServerErrorLogger(&logger))
98 server := httptest.NewServer(handler)
99 defer server.Close()
100 resp, _ := http.Post(server.URL, "application/json", addBody())
101 buf, _ := ioutil.ReadAll(resp.Body)
102 if want, have := http.StatusOK, resp.StatusCode; want != have {
103 t.Errorf("want %d, have %d: %s", want, have, buf)
104 }
105 expectErrorCode(t, jsonrpc.InternalError, buf)
106 if !logger.Called {
107 t.Fatal("Expected logger to be called with error. Wasn't.")
108 }
109 }
110
111 func TestServerBadEndpoint(t *testing.T) {
112 ecm := jsonrpc.EndpointCodecMap{
113 "add": jsonrpc.EndpointCodec{
114 Endpoint: func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errors.New("oof") },
115 Decode: nopDecoder,
116 Encode: nopEncoder,
117 },
118 }
119 handler := jsonrpc.NewServer(ecm)
120 server := httptest.NewServer(handler)
121 defer server.Close()
122 resp, _ := http.Post(server.URL, "application/json", addBody())
123 if want, have := http.StatusOK, resp.StatusCode; want != have {
124 t.Errorf("want %d, have %d", want, have)
125 }
126 buf, _ := ioutil.ReadAll(resp.Body)
127 expectErrorCode(t, jsonrpc.InternalError, buf)
128 expectValidRequestID(t, 1, buf)
129 }
130
131 func TestServerBadEncode(t *testing.T) {
132 ecm := jsonrpc.EndpointCodecMap{
133 "add": jsonrpc.EndpointCodec{
134 Endpoint: endpoint.Nop,
135 Decode: nopDecoder,
136 Encode: func(context.Context, interface{}) (json.RawMessage, error) { return []byte{}, errors.New("oof") },
137 },
138 }
139 handler := jsonrpc.NewServer(ecm)
140 server := httptest.NewServer(handler)
141 defer server.Close()
142 resp, _ := http.Post(server.URL, "application/json", addBody())
143 if want, have := http.StatusOK, resp.StatusCode; want != have {
144 t.Errorf("want %d, have %d", want, have)
145 }
146 buf, _ := ioutil.ReadAll(resp.Body)
147 expectErrorCode(t, jsonrpc.InternalError, buf)
148 expectValidRequestID(t, 1, buf)
149 }
150
151 func TestServerErrorEncoder(t *testing.T) {
152 errTeapot := errors.New("teapot")
153 code := func(err error) int {
154 if errors.Is(err, errTeapot) {
155 return http.StatusTeapot
156 }
157 return http.StatusInternalServerError
158 }
159 ecm := jsonrpc.EndpointCodecMap{
160 "add": jsonrpc.EndpointCodec{
161 Endpoint: func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errTeapot },
162 Decode: nopDecoder,
163 Encode: nopEncoder,
164 },
165 }
166 handler := jsonrpc.NewServer(
167 ecm,
168 jsonrpc.ServerErrorEncoder(func(_ context.Context, err error, w http.ResponseWriter) { w.WriteHeader(code(err)) }),
169 )
170 server := httptest.NewServer(handler)
171 defer server.Close()
172 resp, _ := http.Post(server.URL, "application/json", addBody())
173 if want, have := http.StatusTeapot, resp.StatusCode; want != have {
174 t.Errorf("want %d, have %d", want, have)
175 }
176 }
177
178 func TestCanRejectNonPostRequest(t *testing.T) {
179 ecm := jsonrpc.EndpointCodecMap{}
180 handler := jsonrpc.NewServer(ecm)
181 server := httptest.NewServer(handler)
182 defer server.Close()
183 resp, _ := http.Get(server.URL)
184 if want, have := http.StatusMethodNotAllowed, resp.StatusCode; want != have {
185 t.Errorf("want %d, have %d", want, have)
186 }
187 }
188
189 func TestCanRejectInvalidJSON(t *testing.T) {
190 ecm := jsonrpc.EndpointCodecMap{}
191 handler := jsonrpc.NewServer(ecm)
192 server := httptest.NewServer(handler)
193 defer server.Close()
194 resp, _ := http.Post(server.URL, "application/json", body("clearlynotjson"))
195 if want, have := http.StatusOK, resp.StatusCode; want != have {
196 t.Errorf("want %d, have %d", want, have)
197 }
198 buf, _ := ioutil.ReadAll(resp.Body)
199 expectErrorCode(t, jsonrpc.ParseError, buf)
200 expectNilRequestID(t, buf)
201 }
202
203 func TestServerUnregisteredMethod(t *testing.T) {
204 ecm := jsonrpc.EndpointCodecMap{}
205 handler := jsonrpc.NewServer(ecm)
206 server := httptest.NewServer(handler)
207 defer server.Close()
208 resp, _ := http.Post(server.URL, "application/json", addBody())
209 if want, have := http.StatusOK, resp.StatusCode; want != have {
210 t.Errorf("want %d, have %d", want, have)
211 }
212 buf, _ := ioutil.ReadAll(resp.Body)
213 expectErrorCode(t, jsonrpc.MethodNotFoundError, buf)
214 }
215
216 func TestServerHappyPath(t *testing.T) {
217 step, response := testServer(t)
218 step()
219 resp := <-response
220 defer resp.Body.Close() // nolint
221 buf, _ := ioutil.ReadAll(resp.Body)
222 if want, have := http.StatusOK, resp.StatusCode; want != have {
223 t.Errorf("want %d, have %d (%s)", want, have, buf)
224 }
225 r, err := unmarshalResponse(buf)
226 if err != nil {
227 t.Fatalf("Can't decode response. err=%s, body=%s", err, buf)
228 }
229 if r.JSONRPC != jsonrpc.Version {
230 t.Fatalf("JSONRPC Version: want=%s, got=%s", jsonrpc.Version, r.JSONRPC)
231 }
232 if r.Error != nil {
233 t.Fatalf("Unxpected error on response: %s", buf)
234 }
235 }
236
237 func TestMultipleServerBeforeCodec(t *testing.T) {
238 var done = make(chan struct{})
239 ecm := jsonrpc.EndpointCodecMap{
240 "add": jsonrpc.EndpointCodec{
241 Endpoint: endpoint.Nop,
242 Decode: nopDecoder,
243 Encode: nopEncoder,
244 },
245 }
246 handler := jsonrpc.NewServer(
247 ecm,
248 jsonrpc.ServerBeforeCodec(func(ctx context.Context, r *http.Request, req jsonrpc.Request) context.Context {
249 ctx = context.WithValue(ctx, "one", 1)
250
251 return ctx
252 }),
253 jsonrpc.ServerBeforeCodec(func(ctx context.Context, r *http.Request, req jsonrpc.Request) context.Context {
254 if _, ok := ctx.Value("one").(int); !ok {
255 t.Error("Value was not set properly when multiple ServerBeforeCodecs are used")
256 }
257
258 close(done)
259 return ctx
260 }),
261 )
262 server := httptest.NewServer(handler)
263 defer server.Close()
264 http.Post(server.URL, "application/json", addBody()) // nolint
265
266 select {
267 case <-done:
268 case <-time.After(time.Second):
269 t.Fatal("timeout waiting for finalizer")
270 }
271 }
272
273 func TestMultipleServerBefore(t *testing.T) {
274 var done = make(chan struct{})
275 ecm := jsonrpc.EndpointCodecMap{
276 "add": jsonrpc.EndpointCodec{
277 Endpoint: endpoint.Nop,
278 Decode: nopDecoder,
279 Encode: nopEncoder,
280 },
281 }
282 handler := jsonrpc.NewServer(
283 ecm,
284 jsonrpc.ServerBefore(func(ctx context.Context, r *http.Request) context.Context {
285 ctx = context.WithValue(ctx, "one", 1)
286
287 return ctx
288 }),
289 jsonrpc.ServerBefore(func(ctx context.Context, r *http.Request) context.Context {
290 if _, ok := ctx.Value("one").(int); !ok {
291 t.Error("Value was not set properly when multiple ServerBefores are used")
292 }
293
294 close(done)
295 return ctx
296 }),
297 )
298 server := httptest.NewServer(handler)
299 defer server.Close()
300 http.Post(server.URL, "application/json", addBody()) // nolint
301
302 select {
303 case <-done:
304 case <-time.After(time.Second):
305 t.Fatal("timeout waiting for finalizer")
306 }
307 }
308
309 func TestMultipleServerAfter(t *testing.T) {
310 var done = make(chan struct{})
311 ecm := jsonrpc.EndpointCodecMap{
312 "add": jsonrpc.EndpointCodec{
313 Endpoint: endpoint.Nop,
314 Decode: nopDecoder,
315 Encode: nopEncoder,
316 },
317 }
318 handler := jsonrpc.NewServer(
319 ecm,
320 jsonrpc.ServerAfter(func(ctx context.Context, w http.ResponseWriter) context.Context {
321 ctx = context.WithValue(ctx, "one", 1)
322
323 return ctx
324 }),
325 jsonrpc.ServerAfter(func(ctx context.Context, w http.ResponseWriter) context.Context {
326 if _, ok := ctx.Value("one").(int); !ok {
327 t.Error("Value was not set properly when multiple ServerAfters are used")
328 }
329
330 close(done)
331 return ctx
332 }),
333 )
334 server := httptest.NewServer(handler)
335 defer server.Close()
336 http.Post(server.URL, "application/json", addBody()) // nolint
337
338 select {
339 case <-done:
340 case <-time.After(time.Second):
341 t.Fatal("timeout waiting for finalizer")
342 }
343 }
344
345 func TestCanFinalize(t *testing.T) {
346 var done = make(chan struct{})
347 var finalizerCalled bool
348 ecm := jsonrpc.EndpointCodecMap{
349 "add": jsonrpc.EndpointCodec{
350 Endpoint: endpoint.Nop,
351 Decode: nopDecoder,
352 Encode: nopEncoder,
353 },
354 }
355 handler := jsonrpc.NewServer(
356 ecm,
357 jsonrpc.ServerFinalizer(func(ctx context.Context, code int, req *http.Request) {
358 finalizerCalled = true
359 close(done)
360 }),
361 )
362 server := httptest.NewServer(handler)
363 defer server.Close()
364 http.Post(server.URL, "application/json", addBody()) // nolint
365
366 select {
367 case <-done:
368 case <-time.After(time.Second):
369 t.Fatal("timeout waiting for finalizer")
370 }
371
372 if !finalizerCalled {
373 t.Fatal("Finalizer was not called.")
374 }
375 }
376
377 func testServer(t *testing.T) (step func(), resp <-chan *http.Response) {
378 var (
379 stepch = make(chan bool)
380 endpoint = func(ctx context.Context, request interface{}) (response interface{}, err error) {
381 <-stepch
382 return struct{}{}, nil
383 }
384 response = make(chan *http.Response)
385 ecm = jsonrpc.EndpointCodecMap{
386 "add": jsonrpc.EndpointCodec{
387 Endpoint: endpoint,
388 Decode: nopDecoder,
389 Encode: nopEncoder,
390 },
391 }
392 handler = jsonrpc.NewServer(ecm)
393 )
394 go func() {
395 server := httptest.NewServer(handler)
396 defer server.Close()
397 rb := strings.NewReader(`{"jsonrpc": "2.0", "method": "add", "params": [3, 2], "id": 1}`)
398 resp, err := http.Post(server.URL, "application/json", rb)
399 if err != nil {
400 t.Error(err)
401 return
402 }
403 response <- resp
404 }()
405 return func() { stepch <- true }, response
406 }
0 package proto
1
2 import (
3 "bytes"
4 "context"
5 "errors"
6 "io/ioutil"
7 "net/http"
8
9 httptransport "github.com/go-kit/kit/transport/http"
10 "google.golang.org/protobuf/proto"
11 )
12
13 // EncodeProtoRequest is an EncodeRequestFunc that serializes the request as Protobuf.
14 // If the request implements Headerer, the provided headers will be applied
15 // to the request. If the given request does not implement proto.Message, an error will
16 // be returned.
17 func EncodeProtoRequest(_ context.Context, r *http.Request, preq interface{}) error {
18 r.Header.Set("Content-Type", "application/x-protobuf")
19 if headerer, ok := preq.(httptransport.Headerer); ok {
20 for k := range headerer.Headers() {
21 r.Header.Set(k, headerer.Headers().Get(k))
22 }
23 }
24 req, ok := preq.(proto.Message)
25 if !ok {
26 return errors.New("response does not implement proto.Message")
27 }
28
29 b, err := proto.Marshal(req)
30 if err != nil {
31 return err
32 }
33 r.ContentLength = int64(len(b))
34 r.Body = ioutil.NopCloser(bytes.NewReader(b))
35 return nil
36 }
0 package proto
1
2 //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
3 //go:generate mv proto_test.pb.go proto_pb_test.go
0 // Code generated by protoc-gen-go. DO NOT EDIT.
1 // versions:
2 // protoc-gen-go v1.26.0
3 // protoc v3.16.0
4 // source: proto_test.proto
5
6 package proto
7
8 import (
9 protoreflect "google.golang.org/protobuf/reflect/protoreflect"
10 protoimpl "google.golang.org/protobuf/runtime/protoimpl"
11 reflect "reflect"
12 sync "sync"
13 )
14
15 const (
16 // Verify that this generated code is sufficiently up-to-date.
17 _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
18 // Verify that runtime/protoimpl is sufficiently up-to-date.
19 _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
20 )
21
22 type Cat struct {
23 state protoimpl.MessageState
24 sizeCache protoimpl.SizeCache
25 unknownFields protoimpl.UnknownFields
26
27 Age int32 `protobuf:"varint,1,opt,name=Age,proto3" json:"Age,omitempty"`
28 Breed string `protobuf:"bytes,2,opt,name=Breed,proto3" json:"Breed,omitempty"`
29 Name string `protobuf:"bytes,3,opt,name=Name,proto3" json:"Name,omitempty"`
30 }
31
32 func (x *Cat) Reset() {
33 *x = Cat{}
34 if protoimpl.UnsafeEnabled {
35 mi := &file_proto_test_proto_msgTypes[0]
36 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
37 ms.StoreMessageInfo(mi)
38 }
39 }
40
41 func (x *Cat) String() string {
42 return protoimpl.X.MessageStringOf(x)
43 }
44
45 func (*Cat) ProtoMessage() {}
46
47 func (x *Cat) ProtoReflect() protoreflect.Message {
48 mi := &file_proto_test_proto_msgTypes[0]
49 if protoimpl.UnsafeEnabled && x != nil {
50 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
51 if ms.LoadMessageInfo() == nil {
52 ms.StoreMessageInfo(mi)
53 }
54 return ms
55 }
56 return mi.MessageOf(x)
57 }
58
59 // Deprecated: Use Cat.ProtoReflect.Descriptor instead.
60 func (*Cat) Descriptor() ([]byte, []int) {
61 return file_proto_test_proto_rawDescGZIP(), []int{0}
62 }
63
64 func (x *Cat) GetAge() int32 {
65 if x != nil {
66 return x.Age
67 }
68 return 0
69 }
70
71 func (x *Cat) GetBreed() string {
72 if x != nil {
73 return x.Breed
74 }
75 return ""
76 }
77
78 func (x *Cat) GetName() string {
79 if x != nil {
80 return x.Name
81 }
82 return ""
83 }
84
85 var File_proto_test_proto protoreflect.FileDescriptor
86
87 var file_proto_test_proto_rawDesc = []byte{
88 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f,
89 0x74, 0x6f, 0x22, 0x41, 0x0a, 0x03, 0x43, 0x61, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x41, 0x67, 0x65,
90 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x41, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x42,
91 0x72, 0x65, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x42, 0x72, 0x65, 0x65,
92 0x64, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
93 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
94 }
95
96 var (
97 file_proto_test_proto_rawDescOnce sync.Once
98 file_proto_test_proto_rawDescData = file_proto_test_proto_rawDesc
99 )
100
101 func file_proto_test_proto_rawDescGZIP() []byte {
102 file_proto_test_proto_rawDescOnce.Do(func() {
103 file_proto_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_test_proto_rawDescData)
104 })
105 return file_proto_test_proto_rawDescData
106 }
107
108 var file_proto_test_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
109 var file_proto_test_proto_goTypes = []interface{}{
110 (*Cat)(nil), // 0: Cat
111 }
112 var file_proto_test_proto_depIdxs = []int32{
113 0, // [0:0] is the sub-list for method output_type
114 0, // [0:0] is the sub-list for method input_type
115 0, // [0:0] is the sub-list for extension type_name
116 0, // [0:0] is the sub-list for extension extendee
117 0, // [0:0] is the sub-list for field type_name
118 }
119
120 func init() { file_proto_test_proto_init() }
121 func file_proto_test_proto_init() {
122 if File_proto_test_proto != nil {
123 return
124 }
125 if !protoimpl.UnsafeEnabled {
126 file_proto_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
127 switch v := v.(*Cat); i {
128 case 0:
129 return &v.state
130 case 1:
131 return &v.sizeCache
132 case 2:
133 return &v.unknownFields
134 default:
135 return nil
136 }
137 }
138 }
139 type x struct{}
140 out := protoimpl.TypeBuilder{
141 File: protoimpl.DescBuilder{
142 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
143 RawDescriptor: file_proto_test_proto_rawDesc,
144 NumEnums: 0,
145 NumMessages: 1,
146 NumExtensions: 0,
147 NumServices: 0,
148 },
149 GoTypes: file_proto_test_proto_goTypes,
150 DependencyIndexes: file_proto_test_proto_depIdxs,
151 MessageInfos: file_proto_test_proto_msgTypes,
152 }.Build()
153 File_proto_test_proto = out.File
154 file_proto_test_proto_rawDesc = nil
155 file_proto_test_proto_goTypes = nil
156 file_proto_test_proto_depIdxs = nil
157 }
0 package proto
1
2 import (
3 "context"
4 "io/ioutil"
5 "net/http"
6 "net/http/httptest"
7 "testing"
8
9 "google.golang.org/protobuf/proto"
10 )
11
12 func TestEncodeProtoRequest(t *testing.T) {
13 cat := &Cat{Name: "Ziggy", Age: 13, Breed: "Lumpy"}
14
15 r := httptest.NewRequest(http.MethodGet, "/cat", nil)
16
17 err := EncodeProtoRequest(context.TODO(), r, cat)
18 if err != nil {
19 t.Errorf("expected no encoding errors but got: %s", err)
20 return
21 }
22
23 const xproto = "application/x-protobuf"
24 if typ := r.Header.Get("Content-Type"); typ != xproto {
25 t.Errorf("expected content type of %q, got %q", xproto, typ)
26 return
27 }
28
29 bod, err := ioutil.ReadAll(r.Body)
30 if err != nil {
31 t.Errorf("expected no read errors but got: %s", err)
32 return
33 }
34 defer r.Body.Close()
35
36 var got Cat
37 err = proto.Unmarshal(bod, &got)
38 if err != nil {
39 t.Errorf("expected no proto errors but got: %s", err)
40 return
41 }
42
43 if !proto.Equal(&got, cat) {
44 t.Errorf("expected cats to be equal but got:\n\n%#v\n\nwant:\n\n%#v", got, cat)
45 return
46 }
47 }
48
49 func TestEncodeProtoResponse(t *testing.T) {
50 cat := &Cat{Name: "Ziggy", Age: 13, Breed: "Lumpy"}
51
52 wr := httptest.NewRecorder()
53
54 err := EncodeProtoResponse(context.TODO(), wr, cat)
55 if err != nil {
56 t.Errorf("expected no encoding errors but got: %s", err)
57 return
58 }
59
60 w := wr.Result()
61
62 const xproto = "application/x-protobuf"
63 if typ := w.Header.Get("Content-Type"); typ != xproto {
64 t.Errorf("expected content type of %q, got %q", xproto, typ)
65 return
66 }
67
68 if w.StatusCode != http.StatusTeapot {
69 t.Errorf("expected status code of %d, got %d", http.StatusTeapot, w.StatusCode)
70 return
71 }
72
73 bod, err := ioutil.ReadAll(w.Body)
74 if err != nil {
75 t.Errorf("expected no read errors but got: %s", err)
76 return
77 }
78 defer w.Body.Close()
79
80 var got Cat
81 err = proto.Unmarshal(bod, &got)
82 if err != nil {
83 t.Errorf("expected no proto errors but got: %s", err)
84 return
85 }
86
87 if !proto.Equal(&got, cat) {
88 t.Errorf("expected cats to be equal but got:\n\n%#v\n\nwant:\n\n%#v", got, cat)
89 return
90 }
91 }
92
93 func (c *Cat) StatusCode() int {
94 return http.StatusTeapot
95 }
0 syntax = "proto3";
1
2 message Cat {
3 int32 Age = 1;
4 string Breed = 2;
5 string Name = 3;
6 }
0 package proto
1
2 import (
3 "context"
4 "errors"
5 "net/http"
6
7 httptransport "github.com/go-kit/kit/transport/http"
8 "google.golang.org/protobuf/proto"
9 )
10
11 // EncodeProtoResponse is an EncodeResponseFunc that serializes the response as Protobuf.
12 // Many Proto-over-HTTP services can use it as a sensible default. If the response
13 // implements Headerer, the provided headers will be applied to the response. If the
14 // response implements StatusCoder, the provided StatusCode will be used instead of 200.
15 func EncodeProtoResponse(ctx context.Context, w http.ResponseWriter, pres interface{}) error {
16 res, ok := pres.(proto.Message)
17 if !ok {
18 return errors.New("response does not implement proto.Message")
19 }
20 w.Header().Set("Content-Type", "application/x-protobuf")
21 if headerer, ok := w.(httptransport.Headerer); ok {
22 for k := range headerer.Headers() {
23 w.Header().Set(k, headerer.Headers().Get(k))
24 }
25 }
26 code := http.StatusOK
27 if sc, ok := pres.(httptransport.StatusCoder); ok {
28 code = sc.StatusCode()
29 }
30 w.WriteHeader(code)
31 if code == http.StatusNoContent {
32 return nil
33 }
34 if res == nil {
35 return nil
36 }
37 b, err := proto.Marshal(res)
38 if err != nil {
39 return err
40 }
41 _, err = w.Write(b)
42 if err != nil {
43 return err
44 }
45 return nil
46 }
55 "net/http"
66
77 "github.com/go-kit/kit/endpoint"
8 "github.com/go-kit/kit/log"
8 "github.com/go-kit/kit/transport"
9 "github.com/go-kit/log"
910 )
1011
1112 // Server wraps an endpoint and implements http.Handler.
1617 before []RequestFunc
1718 after []ServerResponseFunc
1819 errorEncoder ErrorEncoder
19 finalizer ServerFinalizerFunc
20 logger log.Logger
20 finalizer []ServerFinalizerFunc
21 errorHandler transport.ErrorHandler
2122 }
2223
2324 // NewServer constructs a new server, which implements http.Handler and wraps
3334 dec: dec,
3435 enc: enc,
3536 errorEncoder: DefaultErrorEncoder,
36 logger: log.NewNopLogger(),
37 errorHandler: transport.NewLogErrorHandler(log.NewNopLogger()),
3738 }
3839 for _, option := range options {
3940 option(s)
6970 // of error handling, including logging in more detail, should be performed in a
7071 // custom ServerErrorEncoder or ServerFinalizer, both of which have access to
7172 // the context.
73 // Deprecated: Use ServerErrorHandler instead.
7274 func ServerErrorLogger(logger log.Logger) ServerOption {
73 return func(s *Server) { s.logger = logger }
75 return func(s *Server) { s.errorHandler = transport.NewLogErrorHandler(logger) }
76 }
77
78 // ServerErrorHandler is used to handle non-terminal errors. By default, non-terminal errors
79 // are ignored. This is intended as a diagnostic measure. Finer-grained control
80 // of error handling, including logging in more detail, should be performed in a
81 // custom ServerErrorEncoder or ServerFinalizer, both of which have access to
82 // the context.
83 func ServerErrorHandler(errorHandler transport.ErrorHandler) ServerOption {
84 return func(s *Server) { s.errorHandler = errorHandler }
7485 }
7586
7687 // ServerFinalizer is executed at the end of every HTTP request.
7788 // By default, no finalizer is registered.
78 func ServerFinalizer(f ServerFinalizerFunc) ServerOption {
79 return func(s *Server) { s.finalizer = f }
89 func ServerFinalizer(f ...ServerFinalizerFunc) ServerOption {
90 return func(s *Server) { s.finalizer = append(s.finalizer, f...) }
8091 }
8192
8293 // ServeHTTP implements http.Handler.
8394 func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
8495 ctx := r.Context()
8596
86 if s.finalizer != nil {
97 if len(s.finalizer) > 0 {
8798 iw := &interceptingWriter{w, http.StatusOK, 0}
8899 defer func() {
89100 ctx = context.WithValue(ctx, ContextKeyResponseHeaders, iw.Header())
90101 ctx = context.WithValue(ctx, ContextKeyResponseSize, iw.written)
91 s.finalizer(ctx, iw.code, r)
102 for _, f := range s.finalizer {
103 f(ctx, iw.code, r)
104 }
92105 }()
93 w = iw
106 w = iw.reimplementInterfaces()
94107 }
95108
96109 for _, f := range s.before {
99112
100113 request, err := s.dec(ctx, r)
101114 if err != nil {
102 s.logger.Log("err", err)
115 s.errorHandler.Handle(ctx, err)
103116 s.errorEncoder(ctx, err, w)
104117 return
105118 }
106119
107120 response, err := s.e(ctx, request)
108121 if err != nil {
109 s.logger.Log("err", err)
122 s.errorHandler.Handle(ctx, err)
110123 s.errorEncoder(ctx, err, w)
111124 return
112125 }
116129 }
117130
118131 if err := s.enc(ctx, w, response); err != nil {
119 s.logger.Log("err", err)
132 s.errorHandler.Handle(ctx, err)
120133 s.errorEncoder(ctx, err, w)
121134 return
122135 }
134147 // provided in the function signature, additional response parameters are
135148 // provided in the context under keys with the ContextKeyResponse prefix.
136149 type ServerFinalizerFunc func(ctx context.Context, code int, r *http.Request)
150
151 // NopRequestDecoder is a DecodeRequestFunc that can be used for requests that do not
152 // need to be decoded, and simply returns nil, nil.
153 func NopRequestDecoder(ctx context.Context, r *http.Request) (interface{}, error) {
154 return nil, nil
155 }
137156
138157 // EncodeJSONResponse is a EncodeResponseFunc that serializes the response as a
139158 // JSON object to the ResponseWriter. Many JSON-over-HTTP services can use it as
143162 func EncodeJSONResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
144163 w.Header().Set("Content-Type", "application/json; charset=utf-8")
145164 if headerer, ok := response.(Headerer); ok {
146 for k := range headerer.Headers() {
147 w.Header().Set(k, headerer.Headers().Get(k))
165 for k, values := range headerer.Headers() {
166 for _, v := range values {
167 w.Header().Add(k, v)
168 }
148169 }
149170 }
150171 code := http.StatusOK
174195 }
175196 w.Header().Set("Content-Type", contentType)
176197 if headerer, ok := err.(Headerer); ok {
177 for k := range headerer.Headers() {
178 w.Header().Set(k, headerer.Headers().Get(k))
198 for k, values := range headerer.Headers() {
199 for _, v := range values {
200 w.Header().Add(k, v)
201 }
179202 }
180203 }
181204 code := http.StatusInternalServerError
199222 type Headerer interface {
200223 Headers() http.Header
201224 }
202
203 type interceptingWriter struct {
204 http.ResponseWriter
205 code int
206 written int64
207 }
208
209 // WriteHeader may not be explicitly called, so care must be taken to
210 // initialize w.code to its default value of http.StatusOK.
211 func (w *interceptingWriter) WriteHeader(code int) {
212 w.code = code
213 w.ResponseWriter.WriteHeader(code)
214 }
215
216 func (w *interceptingWriter) Write(p []byte) (int, error) {
217 n, err := w.ResponseWriter.Write(p)
218 w.written += int64(n)
219 return n, err
220 }
5858 func TestServerErrorEncoder(t *testing.T) {
5959 errTeapot := errors.New("teapot")
6060 code := func(err error) int {
61 if err == errTeapot {
61 if errors.Is(err, errTeapot) {
6262 return http.StatusTeapot
6363 }
6464 return http.StatusInternalServerError
260260 }
261261 }
262262
263 type multiHeaderResponse struct{}
264
265 func (_ multiHeaderResponse) Headers() http.Header {
266 return http.Header{"Vary": []string{"Origin", "User-Agent"}}
267 }
268
269 func TestAddMultipleHeaders(t *testing.T) {
270 handler := httptransport.NewServer(
271 func(context.Context, interface{}) (interface{}, error) { return multiHeaderResponse{}, nil },
272 func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil },
273 httptransport.EncodeJSONResponse,
274 )
275
276 server := httptest.NewServer(handler)
277 defer server.Close()
278
279 resp, err := http.Get(server.URL)
280 if err != nil {
281 t.Fatal(err)
282 }
283 expect := map[string]map[string]struct{}{"Vary": map[string]struct{}{"Origin": struct{}{}, "User-Agent": struct{}{}}}
284 for k, vls := range resp.Header {
285 for _, v := range vls {
286 delete((expect[k]), v)
287 }
288 if len(expect[k]) != 0 {
289 t.Errorf("Header: unexpected header %s: %v", k, expect[k])
290 }
291 }
292 }
293
294 type multiHeaderResponseError struct {
295 multiHeaderResponse
296 msg string
297 }
298
299 func (m multiHeaderResponseError) Error() string {
300 return m.msg
301 }
302
303 func TestAddMultipleHeadersErrorEncoder(t *testing.T) {
304 errStr := "oh no"
305 handler := httptransport.NewServer(
306 func(context.Context, interface{}) (interface{}, error) {
307 return nil, multiHeaderResponseError{msg: errStr}
308 },
309 func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil },
310 httptransport.EncodeJSONResponse,
311 )
312
313 server := httptest.NewServer(handler)
314 defer server.Close()
315
316 resp, err := http.Get(server.URL)
317 if err != nil {
318 t.Fatal(err)
319 }
320 expect := map[string]map[string]struct{}{"Vary": map[string]struct{}{"Origin": struct{}{}, "User-Agent": struct{}{}}}
321 for k, vls := range resp.Header {
322 for _, v := range vls {
323 delete((expect[k]), v)
324 }
325 if len(expect[k]) != 0 {
326 t.Errorf("Header: unexpected header %s: %v", k, expect[k])
327 }
328 }
329 if b, _ := ioutil.ReadAll(resp.Body); errStr != string(b) {
330 t.Errorf("ErrorEncoder: got: %q, expected: %q", b, errStr)
331 }
332 }
333
263334 type noContentResponse struct{}
264335
265336 func (e noContentResponse) StatusCode() int { return http.StatusNoContent }
318389 buf, _ := ioutil.ReadAll(resp.Body)
319390 if want, have := `{"err":"enhanced"}`, strings.TrimSpace(string(buf)); want != have {
320391 t.Errorf("Body: want %s, have %s", want, have)
392 }
393 }
394
395 func TestNoOpRequestDecoder(t *testing.T) {
396 resw := httptest.NewRecorder()
397 req, err := http.NewRequest(http.MethodGet, "/", nil)
398 if err != nil {
399 t.Error("Failed to create request")
400 }
401 handler := httptransport.NewServer(
402 func(ctx context.Context, request interface{}) (interface{}, error) {
403 if request != nil {
404 t.Error("Expected nil request in endpoint when using NopRequestDecoder")
405 }
406 return nil, nil
407 },
408 httptransport.NopRequestDecoder,
409 httptransport.EncodeJSONResponse,
410 )
411 handler.ServeHTTP(resw, req)
412 if resw.Code != http.StatusOK {
413 t.Errorf("Expected status code %d but got %d", http.StatusOK, resw.Code)
321414 }
322415 }
323416
0 // Package nats provides a NATS transport.
1 package nats
0 package nats
1
2 import (
3 "context"
4
5 "github.com/nats-io/nats.go"
6 )
7
8 // DecodeRequestFunc extracts a user-domain request object from a publisher
9 // request object. It's designed to be used in NATS subscribers, for subscriber-side
10 // endpoints. One straightforward DecodeRequestFunc could be something that
11 // JSON decodes from the request body to the concrete response type.
12 type DecodeRequestFunc func(context.Context, *nats.Msg) (request interface{}, err error)
13
14 // EncodeRequestFunc encodes the passed request object into the NATS request
15 // object. It's designed to be used in NATS publishers, for publisher-side
16 // endpoints. One straightforward EncodeRequestFunc could something that JSON
17 // encodes the object directly to the request payload.
18 type EncodeRequestFunc func(context.Context, *nats.Msg, interface{}) error
19
20 // EncodeResponseFunc encodes the passed response object to the subscriber reply.
21 // It's designed to be used in NATS subscribers, for subscriber-side
22 // endpoints. One straightforward EncodeResponseFunc could be something that
23 // JSON encodes the object directly to the response body.
24 type EncodeResponseFunc func(context.Context, string, *nats.Conn, interface{}) error
25
26 // DecodeResponseFunc extracts a user-domain response object from an NATS
27 // response object. It's designed to be used in NATS publisher, for publisher-side
28 // endpoints. One straightforward DecodeResponseFunc could be something that
29 // JSON decodes from the response payload to the concrete response type.
30 type DecodeResponseFunc func(context.Context, *nats.Msg) (response interface{}, err error)
31
0 package nats
1
2 import (
3 "context"
4 "encoding/json"
5 "github.com/go-kit/kit/endpoint"
6 "github.com/nats-io/nats.go"
7 "time"
8 )
9
10 // Publisher wraps a URL and provides a method that implements endpoint.Endpoint.
11 type Publisher struct {
12 publisher *nats.Conn
13 subject string
14 enc EncodeRequestFunc
15 dec DecodeResponseFunc
16 before []RequestFunc
17 after []PublisherResponseFunc
18 timeout time.Duration
19 }
20
21 // NewPublisher constructs a usable Publisher for a single remote method.
22 func NewPublisher(
23 publisher *nats.Conn,
24 subject string,
25 enc EncodeRequestFunc,
26 dec DecodeResponseFunc,
27 options ...PublisherOption,
28 ) *Publisher {
29 p := &Publisher{
30 publisher: publisher,
31 subject: subject,
32 enc: enc,
33 dec: dec,
34 timeout: 10 * time.Second,
35 }
36 for _, option := range options {
37 option(p)
38 }
39 return p
40 }
41
42 // PublisherOption sets an optional parameter for clients.
43 type PublisherOption func(*Publisher)
44
45 // PublisherBefore sets the RequestFuncs that are applied to the outgoing NATS
46 // request before it's invoked.
47 func PublisherBefore(before ...RequestFunc) PublisherOption {
48 return func(p *Publisher) { p.before = append(p.before, before...) }
49 }
50
51 // PublisherAfter sets the ClientResponseFuncs applied to the incoming NATS
52 // request prior to it being decoded. This is useful for obtaining anything off
53 // of the response and adding onto the context prior to decoding.
54 func PublisherAfter(after ...PublisherResponseFunc) PublisherOption {
55 return func(p *Publisher) { p.after = append(p.after, after...) }
56 }
57
58 // PublisherTimeout sets the available timeout for NATS request.
59 func PublisherTimeout(timeout time.Duration) PublisherOption {
60 return func(p *Publisher) { p.timeout = timeout }
61 }
62
63 // Endpoint returns a usable endpoint that invokes the remote endpoint.
64 func (p Publisher) Endpoint() endpoint.Endpoint {
65 return func(ctx context.Context, request interface{}) (interface{}, error) {
66 ctx, cancel := context.WithTimeout(ctx, p.timeout)
67 defer cancel()
68
69 msg := nats.Msg{Subject: p.subject}
70
71 if err := p.enc(ctx, &msg, request); err != nil {
72 return nil, err
73 }
74
75 for _, f := range p.before {
76 ctx = f(ctx, &msg)
77 }
78
79 resp, err := p.publisher.RequestWithContext(ctx, msg.Subject, msg.Data)
80 if err != nil {
81 return nil, err
82 }
83
84 for _, f := range p.after {
85 ctx = f(ctx, resp)
86 }
87
88 response, err := p.dec(ctx, resp)
89 if err != nil {
90 return nil, err
91 }
92
93 return response, nil
94 }
95 }
96
97 // EncodeJSONRequest is an EncodeRequestFunc that serializes the request as a
98 // JSON object to the Data of the Msg. Many JSON-over-NATS services can use it as
99 // a sensible default.
100 func EncodeJSONRequest(_ context.Context, msg *nats.Msg, request interface{}) error {
101 b, err := json.Marshal(request)
102 if err != nil {
103 return err
104 }
105
106 msg.Data = b
107
108 return nil
109 }
0 package nats_test
1
2 import (
3 "context"
4 "strings"
5 "testing"
6 "time"
7
8 natstransport "github.com/go-kit/kit/transport/nats"
9 "github.com/nats-io/nats.go"
10 )
11
12 func TestPublisher(t *testing.T) {
13 var (
14 testdata = "testdata"
15 encode = func(context.Context, *nats.Msg, interface{}) error { return nil }
16 decode = func(_ context.Context, msg *nats.Msg) (interface{}, error) {
17 return TestResponse{string(msg.Data), ""}, nil
18 }
19 )
20
21 s, c := newNATSConn(t)
22 defer func() { s.Shutdown(); s.WaitForShutdown() }()
23 defer c.Close()
24
25 sub, err := c.QueueSubscribe("natstransport.test", "natstransport", func(msg *nats.Msg) {
26 if err := c.Publish(msg.Reply, []byte(testdata)); err != nil {
27 t.Fatal(err)
28 }
29 })
30 if err != nil {
31 t.Fatal(err)
32 }
33 defer sub.Unsubscribe()
34
35 publisher := natstransport.NewPublisher(
36 c,
37 "natstransport.test",
38 encode,
39 decode,
40 )
41
42 res, err := publisher.Endpoint()(context.Background(), struct{}{})
43 if err != nil {
44 t.Fatal(err)
45 }
46
47 response, ok := res.(TestResponse)
48 if !ok {
49 t.Fatal("response should be TestResponse")
50 }
51 if want, have := testdata, response.String; want != have {
52 t.Errorf("want %q, have %q", want, have)
53 }
54
55 }
56
57 func TestPublisherBefore(t *testing.T) {
58 var (
59 testdata = "testdata"
60 encode = func(context.Context, *nats.Msg, interface{}) error { return nil }
61 decode = func(_ context.Context, msg *nats.Msg) (interface{}, error) {
62 return TestResponse{string(msg.Data), ""}, nil
63 }
64 )
65
66 s, c := newNATSConn(t)
67 defer func() { s.Shutdown(); s.WaitForShutdown() }()
68 defer c.Close()
69
70 sub, err := c.QueueSubscribe("natstransport.test", "natstransport", func(msg *nats.Msg) {
71 if err := c.Publish(msg.Reply, msg.Data); err != nil {
72 t.Fatal(err)
73 }
74 })
75 if err != nil {
76 t.Fatal(err)
77 }
78 defer sub.Unsubscribe()
79
80 publisher := natstransport.NewPublisher(
81 c,
82 "natstransport.test",
83 encode,
84 decode,
85 natstransport.PublisherBefore(func(ctx context.Context, msg *nats.Msg) context.Context {
86 msg.Data = []byte(strings.ToUpper(string(testdata)))
87 return ctx
88 }),
89 )
90
91 res, err := publisher.Endpoint()(context.Background(), struct{}{})
92 if err != nil {
93 t.Fatal(err)
94 }
95
96 response, ok := res.(TestResponse)
97 if !ok {
98 t.Fatal("response should be TestResponse")
99 }
100 if want, have := strings.ToUpper(testdata), response.String; want != have {
101 t.Errorf("want %q, have %q", want, have)
102 }
103
104 }
105
106 func TestPublisherAfter(t *testing.T) {
107 var (
108 testdata = "testdata"
109 encode = func(context.Context, *nats.Msg, interface{}) error { return nil }
110 decode = func(_ context.Context, msg *nats.Msg) (interface{}, error) {
111 return TestResponse{string(msg.Data), ""}, nil
112 }
113 )
114
115 s, c := newNATSConn(t)
116 defer func() { s.Shutdown(); s.WaitForShutdown() }()
117 defer c.Close()
118
119 sub, err := c.QueueSubscribe("natstransport.test", "natstransport", func(msg *nats.Msg) {
120 if err := c.Publish(msg.Reply, []byte(testdata)); err != nil {
121 t.Fatal(err)
122 }
123 })
124 if err != nil {
125 t.Fatal(err)
126 }
127 defer sub.Unsubscribe()
128
129 publisher := natstransport.NewPublisher(
130 c,
131 "natstransport.test",
132 encode,
133 decode,
134 natstransport.PublisherAfter(func(ctx context.Context, msg *nats.Msg) context.Context {
135 msg.Data = []byte(strings.ToUpper(string(msg.Data)))
136 return ctx
137 }),
138 )
139
140 res, err := publisher.Endpoint()(context.Background(), struct{}{})
141 if err != nil {
142 t.Fatal(err)
143 }
144
145 response, ok := res.(TestResponse)
146 if !ok {
147 t.Fatal("response should be TestResponse")
148 }
149 if want, have := strings.ToUpper(testdata), response.String; want != have {
150 t.Errorf("want %q, have %q", want, have)
151 }
152
153 }
154
155 func TestPublisherTimeout(t *testing.T) {
156 var (
157 encode = func(context.Context, *nats.Msg, interface{}) error { return nil }
158 decode = func(_ context.Context, msg *nats.Msg) (interface{}, error) {
159 return TestResponse{string(msg.Data), ""}, nil
160 }
161 )
162
163 s, c := newNATSConn(t)
164 defer func() { s.Shutdown(); s.WaitForShutdown() }()
165 defer c.Close()
166
167 ch := make(chan struct{})
168 defer close(ch)
169
170 sub, err := c.QueueSubscribe("natstransport.test", "natstransport", func(msg *nats.Msg) {
171 <-ch
172 })
173 if err != nil {
174 t.Fatal(err)
175 }
176 defer sub.Unsubscribe()
177
178 publisher := natstransport.NewPublisher(
179 c,
180 "natstransport.test",
181 encode,
182 decode,
183 natstransport.PublisherTimeout(time.Second),
184 )
185
186 _, err = publisher.Endpoint()(context.Background(), struct{}{})
187 if err != context.DeadlineExceeded {
188 t.Errorf("want %s, have %s", context.DeadlineExceeded, err)
189 }
190 }
191
192 func TestPublisherCancellation(t *testing.T) {
193 var (
194 testdata = "testdata"
195 encode = func(context.Context, *nats.Msg, interface{}) error { return nil }
196 decode = func(_ context.Context, msg *nats.Msg) (interface{}, error) {
197 return TestResponse{string(msg.Data), ""}, nil
198 }
199 )
200
201 s, c := newNATSConn(t)
202 defer func() { s.Shutdown(); s.WaitForShutdown() }()
203 defer c.Close()
204
205 sub, err := c.QueueSubscribe("natstransport.test", "natstransport", func(msg *nats.Msg) {
206 if err := c.Publish(msg.Reply, []byte(testdata)); err != nil {
207 t.Fatal(err)
208 }
209 })
210 if err != nil {
211 t.Fatal(err)
212 }
213 defer sub.Unsubscribe()
214
215 publisher := natstransport.NewPublisher(
216 c,
217 "natstransport.test",
218 encode,
219 decode,
220 )
221
222 ctx, cancel := context.WithCancel(context.Background())
223 cancel()
224
225 _, err = publisher.Endpoint()(ctx, struct{}{})
226 if err != context.Canceled {
227 t.Errorf("want %s, have %s", context.Canceled, err)
228 }
229 }
230
231 func TestEncodeJSONRequest(t *testing.T) {
232 var data string
233
234 s, c := newNATSConn(t)
235 defer func() { s.Shutdown(); s.WaitForShutdown() }()
236 defer c.Close()
237
238 sub, err := c.QueueSubscribe("natstransport.test", "natstransport", func(msg *nats.Msg) {
239 data = string(msg.Data)
240
241 if err := c.Publish(msg.Reply, []byte("")); err != nil {
242 t.Fatal(err)
243 }
244 })
245 if err != nil {
246 t.Fatal(err)
247 }
248 defer sub.Unsubscribe()
249
250 publisher := natstransport.NewPublisher(
251 c,
252 "natstransport.test",
253 natstransport.EncodeJSONRequest,
254 func(context.Context, *nats.Msg) (interface{}, error) { return nil, nil },
255 ).Endpoint()
256
257 for _, test := range []struct {
258 value interface{}
259 body string
260 }{
261 {nil, "null"},
262 {12, "12"},
263 {1.2, "1.2"},
264 {true, "true"},
265 {"test", "\"test\""},
266 {struct {
267 Foo string `json:"foo"`
268 }{"foo"}, "{\"foo\":\"foo\"}"},
269 } {
270 if _, err := publisher(context.Background(), test.value); err != nil {
271 t.Fatal(err)
272 continue
273 }
274
275 if data != test.body {
276 t.Errorf("%v: actual %#v, expected %#v", test.value, data, test.body)
277 }
278 }
279
280 }
0 package nats
1
2 import (
3 "context"
4
5 "github.com/nats-io/nats.go"
6 )
7
8 // RequestFunc may take information from a publisher request and put it into a
9 // request context. In Subscribers, RequestFuncs are executed prior to invoking the
10 // endpoint.
11 type RequestFunc func(context.Context, *nats.Msg) context.Context
12
13 // SubscriberResponseFunc may take information from a request context and use it to
14 // manipulate a Publisher. SubscriberResponseFuncs are only executed in
15 // subscribers, after invoking the endpoint but prior to publishing a reply.
16 type SubscriberResponseFunc func(context.Context, *nats.Conn) context.Context
17
18 // PublisherResponseFunc may take information from an NATS request and make the
19 // response available for consumption. ClientResponseFuncs are only executed in
20 // clients, after a request has been made, but prior to it being decoded.
21 type PublisherResponseFunc func(context.Context, *nats.Msg) context.Context
0 package nats
1
2 import (
3 "context"
4 "encoding/json"
5
6 "github.com/go-kit/kit/endpoint"
7 "github.com/go-kit/kit/transport"
8 "github.com/go-kit/log"
9
10 "github.com/nats-io/nats.go"
11 )
12
13 // Subscriber wraps an endpoint and provides nats.MsgHandler.
14 type Subscriber struct {
15 e endpoint.Endpoint
16 dec DecodeRequestFunc
17 enc EncodeResponseFunc
18 before []RequestFunc
19 after []SubscriberResponseFunc
20 errorEncoder ErrorEncoder
21 finalizer []SubscriberFinalizerFunc
22 errorHandler transport.ErrorHandler
23 }
24
25 // NewSubscriber constructs a new subscriber, which provides nats.MsgHandler and wraps
26 // the provided endpoint.
27 func NewSubscriber(
28 e endpoint.Endpoint,
29 dec DecodeRequestFunc,
30 enc EncodeResponseFunc,
31 options ...SubscriberOption,
32 ) *Subscriber {
33 s := &Subscriber{
34 e: e,
35 dec: dec,
36 enc: enc,
37 errorEncoder: DefaultErrorEncoder,
38 errorHandler: transport.NewLogErrorHandler(log.NewNopLogger()),
39 }
40 for _, option := range options {
41 option(s)
42 }
43 return s
44 }
45
46 // SubscriberOption sets an optional parameter for subscribers.
47 type SubscriberOption func(*Subscriber)
48
49 // SubscriberBefore functions are executed on the publisher request object before the
50 // request is decoded.
51 func SubscriberBefore(before ...RequestFunc) SubscriberOption {
52 return func(s *Subscriber) { s.before = append(s.before, before...) }
53 }
54
55 // SubscriberAfter functions are executed on the subscriber reply after the
56 // endpoint is invoked, but before anything is published to the reply.
57 func SubscriberAfter(after ...SubscriberResponseFunc) SubscriberOption {
58 return func(s *Subscriber) { s.after = append(s.after, after...) }
59 }
60
61 // SubscriberErrorEncoder is used to encode errors to the subscriber reply
62 // whenever they're encountered in the processing of a request. Clients can
63 // use this to provide custom error formatting. By default,
64 // errors will be published with the DefaultErrorEncoder.
65 func SubscriberErrorEncoder(ee ErrorEncoder) SubscriberOption {
66 return func(s *Subscriber) { s.errorEncoder = ee }
67 }
68
69 // SubscriberErrorLogger is used to log non-terminal errors. By default, no errors
70 // are logged. This is intended as a diagnostic measure. Finer-grained control
71 // of error handling, including logging in more detail, should be performed in a
72 // custom SubscriberErrorEncoder which has access to the context.
73 // Deprecated: Use SubscriberErrorHandler instead.
74 func SubscriberErrorLogger(logger log.Logger) SubscriberOption {
75 return func(s *Subscriber) { s.errorHandler = transport.NewLogErrorHandler(logger) }
76 }
77
78 // SubscriberErrorHandler is used to handle non-terminal errors. By default, non-terminal errors
79 // are ignored. This is intended as a diagnostic measure. Finer-grained control
80 // of error handling, including logging in more detail, should be performed in a
81 // custom SubscriberErrorEncoder which has access to the context.
82 func SubscriberErrorHandler(errorHandler transport.ErrorHandler) SubscriberOption {
83 return func(s *Subscriber) { s.errorHandler = errorHandler }
84 }
85
86 // SubscriberFinalizer is executed at the end of every request from a publisher through NATS.
87 // By default, no finalizer is registered.
88 func SubscriberFinalizer(f ...SubscriberFinalizerFunc) SubscriberOption {
89 return func(s *Subscriber) { s.finalizer = f }
90 }
91
92 // ServeMsg provides nats.MsgHandler.
93 func (s Subscriber) ServeMsg(nc *nats.Conn) func(msg *nats.Msg) {
94 return func(msg *nats.Msg) {
95 ctx, cancel := context.WithCancel(context.Background())
96 defer cancel()
97
98 if len(s.finalizer) > 0 {
99 defer func() {
100 for _, f := range s.finalizer {
101 f(ctx, msg)
102 }
103 }()
104 }
105
106 for _, f := range s.before {
107 ctx = f(ctx, msg)
108 }
109
110 request, err := s.dec(ctx, msg)
111 if err != nil {
112 s.errorHandler.Handle(ctx, err)
113 if msg.Reply == "" {
114 return
115 }
116 s.errorEncoder(ctx, err, msg.Reply, nc)
117 return
118 }
119
120 response, err := s.e(ctx, request)
121 if err != nil {
122 s.errorHandler.Handle(ctx, err)
123 if msg.Reply == "" {
124 return
125 }
126 s.errorEncoder(ctx, err, msg.Reply, nc)
127 return
128 }
129
130 for _, f := range s.after {
131 ctx = f(ctx, nc)
132 }
133
134 if msg.Reply == "" {
135 return
136 }
137
138 if err := s.enc(ctx, msg.Reply, nc, response); err != nil {
139 s.errorHandler.Handle(ctx, err)
140 s.errorEncoder(ctx, err, msg.Reply, nc)
141 return
142 }
143 }
144 }
145
146 // ErrorEncoder is responsible for encoding an error to the subscriber reply.
147 // Users are encouraged to use custom ErrorEncoders to encode errors to
148 // their replies, and will likely want to pass and check for their own error
149 // types.
150 type ErrorEncoder func(ctx context.Context, err error, reply string, nc *nats.Conn)
151
152 // SubscriberFinalizerFunc can be used to perform work at the end of an request
153 // from a publisher, after the response has been written to the publisher. The principal
154 // intended use is for request logging.
155 type SubscriberFinalizerFunc func(ctx context.Context, msg *nats.Msg)
156
157 // NopRequestDecoder is a DecodeRequestFunc that can be used for requests that do not
158 // need to be decoded, and simply returns nil, nil.
159 func NopRequestDecoder(_ context.Context, _ *nats.Msg) (interface{}, error) {
160 return nil, nil
161 }
162
163 // EncodeJSONResponse is a EncodeResponseFunc that serializes the response as a
164 // JSON object to the subscriber reply. Many JSON-over services can use it as
165 // a sensible default.
166 func EncodeJSONResponse(_ context.Context, reply string, nc *nats.Conn, response interface{}) error {
167 b, err := json.Marshal(response)
168 if err != nil {
169 return err
170 }
171
172 return nc.Publish(reply, b)
173 }
174
175 // DefaultErrorEncoder writes the error to the subscriber reply.
176 func DefaultErrorEncoder(_ context.Context, err error, reply string, nc *nats.Conn) {
177 logger := log.NewNopLogger()
178
179 type Response struct {
180 Error string `json:"err"`
181 }
182
183 var response Response
184
185 response.Error = err.Error()
186
187 b, err := json.Marshal(response)
188 if err != nil {
189 logger.Log("err", err)
190 return
191 }
192
193 if err := nc.Publish(reply, b); err != nil {
194 logger.Log("err", err)
195 }
196 }
0 package nats_test
1
2 import (
3 "context"
4 "encoding/json"
5 "errors"
6 "strings"
7 "sync"
8 "testing"
9 "time"
10
11 "github.com/nats-io/nats-server/v2/server"
12 "github.com/nats-io/nats.go"
13
14 "github.com/go-kit/kit/endpoint"
15 natstransport "github.com/go-kit/kit/transport/nats"
16 )
17
18 type TestResponse struct {
19 String string `json:"str"`
20 Error string `json:"err"`
21 }
22
23 func newNATSConn(t *testing.T) (*server.Server, *nats.Conn) {
24 s, err := server.NewServer(&server.Options{
25 Host: "localhost",
26 Port: 0,
27 })
28 if err != nil {
29 t.Fatal(err)
30 }
31
32 go s.Start()
33
34 for i := 0; i < 5 && !s.Running(); i++ {
35 t.Logf("Running %v", s.Running())
36 time.Sleep(time.Second)
37 }
38 if !s.Running() {
39 s.Shutdown()
40 s.WaitForShutdown()
41 t.Fatal("not yet running")
42 }
43
44 if ok := s.ReadyForConnections(5 * time.Second); !ok {
45 t.Fatal("not ready for connections")
46 }
47
48 c, err := nats.Connect("nats://"+s.Addr().String(), nats.Name(t.Name()))
49 if err != nil {
50 t.Fatalf("failed to connect to NATS server: %s", err)
51 }
52
53 return s, c
54 }
55
56 func TestSubscriberBadDecode(t *testing.T) {
57 s, c := newNATSConn(t)
58 defer func() { s.Shutdown(); s.WaitForShutdown() }()
59 defer c.Close()
60
61 handler := natstransport.NewSubscriber(
62 func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil },
63 func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, errors.New("dang") },
64 func(context.Context, string, *nats.Conn, interface{}) error { return nil },
65 )
66
67 resp := testRequest(t, c, handler)
68
69 if want, have := "dang", resp.Error; want != have {
70 t.Errorf("want %s, have %s", want, have)
71 }
72
73 }
74
75 func TestSubscriberBadEndpoint(t *testing.T) {
76 s, c := newNATSConn(t)
77 defer func() { s.Shutdown(); s.WaitForShutdown() }()
78 defer c.Close()
79
80 handler := natstransport.NewSubscriber(
81 func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errors.New("dang") },
82 func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, nil },
83 func(context.Context, string, *nats.Conn, interface{}) error { return nil },
84 )
85
86 resp := testRequest(t, c, handler)
87
88 if want, have := "dang", resp.Error; want != have {
89 t.Errorf("want %s, have %s", want, have)
90 }
91 }
92
93 func TestSubscriberBadEncode(t *testing.T) {
94 s, c := newNATSConn(t)
95 defer func() { s.Shutdown(); s.WaitForShutdown() }()
96 defer c.Close()
97
98 handler := natstransport.NewSubscriber(
99 func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil },
100 func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, nil },
101 func(context.Context, string, *nats.Conn, interface{}) error { return errors.New("dang") },
102 )
103
104 resp := testRequest(t, c, handler)
105
106 if want, have := "dang", resp.Error; want != have {
107 t.Errorf("want %s, have %s", want, have)
108 }
109 }
110
111 func TestSubscriberErrorEncoder(t *testing.T) {
112 s, c := newNATSConn(t)
113 defer func() { s.Shutdown(); s.WaitForShutdown() }()
114 defer c.Close()
115
116 errTeapot := errors.New("teapot")
117 code := func(err error) error {
118 if errors.Is(err, errTeapot) {
119 return err
120 }
121 return errors.New("dang")
122 }
123 handler := natstransport.NewSubscriber(
124 func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errTeapot },
125 func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, nil },
126 func(context.Context, string, *nats.Conn, interface{}) error { return nil },
127 natstransport.SubscriberErrorEncoder(func(_ context.Context, err error, reply string, nc *nats.Conn) {
128 var r TestResponse
129 r.Error = code(err).Error()
130
131 b, err := json.Marshal(r)
132 if err != nil {
133 t.Fatal(err)
134 }
135
136 if err := c.Publish(reply, b); err != nil {
137 t.Fatal(err)
138 }
139 }),
140 )
141
142 resp := testRequest(t, c, handler)
143
144 if want, have := errTeapot.Error(), resp.Error; want != have {
145 t.Errorf("want %s, have %s", want, have)
146 }
147 }
148
149 func TestSubscriberHappySubject(t *testing.T) {
150 step, response := testSubscriber(t)
151 step()
152 r := <-response
153
154 var resp TestResponse
155 err := json.Unmarshal(r.Data, &resp)
156 if err != nil {
157 t.Fatal(err)
158 }
159
160 if want, have := "", resp.Error; want != have {
161 t.Errorf("want %s, have %s (%s)", want, have, r.Data)
162 }
163 }
164
165 func TestMultipleSubscriberBefore(t *testing.T) {
166 s, c := newNATSConn(t)
167 defer func() { s.Shutdown(); s.WaitForShutdown() }()
168 defer c.Close()
169
170 var (
171 response = struct{ Body string }{"go eat a fly ugly\n"}
172 wg sync.WaitGroup
173 done = make(chan struct{})
174 )
175 handler := natstransport.NewSubscriber(
176 endpoint.Nop,
177 func(context.Context, *nats.Msg) (interface{}, error) {
178 return struct{}{}, nil
179 },
180 func(_ context.Context, reply string, nc *nats.Conn, _ interface{}) error {
181 b, err := json.Marshal(response)
182 if err != nil {
183 return err
184 }
185
186 return c.Publish(reply, b)
187 },
188 natstransport.SubscriberBefore(func(ctx context.Context, _ *nats.Msg) context.Context {
189 ctx = context.WithValue(ctx, "one", 1)
190
191 return ctx
192 }),
193 natstransport.SubscriberBefore(func(ctx context.Context, _ *nats.Msg) context.Context {
194 if _, ok := ctx.Value("one").(int); !ok {
195 t.Error("Value was not set properly when multiple ServerBefores are used")
196 }
197
198 close(done)
199 return ctx
200 }),
201 )
202
203 sub, err := c.QueueSubscribe("natstransport.test", "natstransport", handler.ServeMsg(c))
204 if err != nil {
205 t.Fatal(err)
206 }
207 defer sub.Unsubscribe()
208
209 wg.Add(1)
210 go func() {
211 defer wg.Done()
212 _, err := c.Request("natstransport.test", []byte("test data"), 2*time.Second)
213 if err != nil {
214 t.Fatal(err)
215 }
216 }()
217
218 select {
219 case <-done:
220 case <-time.After(time.Second):
221 t.Fatal("timeout waiting for finalizer")
222 }
223
224 wg.Wait()
225 }
226
227 func TestMultipleSubscriberAfter(t *testing.T) {
228 s, c := newNATSConn(t)
229 defer func() { s.Shutdown(); s.WaitForShutdown() }()
230 defer c.Close()
231
232 var (
233 response = struct{ Body string }{"go eat a fly ugly\n"}
234 wg sync.WaitGroup
235 done = make(chan struct{})
236 )
237 handler := natstransport.NewSubscriber(
238 endpoint.Nop,
239 func(context.Context, *nats.Msg) (interface{}, error) {
240 return struct{}{}, nil
241 },
242 func(_ context.Context, reply string, nc *nats.Conn, _ interface{}) error {
243 b, err := json.Marshal(response)
244 if err != nil {
245 return err
246 }
247 return c.Publish(reply, b)
248 },
249 natstransport.SubscriberAfter(func(ctx context.Context, nc *nats.Conn) context.Context {
250 return context.WithValue(ctx, "one", 1)
251 }),
252 natstransport.SubscriberAfter(func(ctx context.Context, nc *nats.Conn) context.Context {
253 if _, ok := ctx.Value("one").(int); !ok {
254 t.Error("Value was not set properly when multiple ServerAfters are used")
255 }
256 close(done)
257 return ctx
258 }),
259 )
260
261 sub, err := c.QueueSubscribe("natstransport.test", "natstransport", handler.ServeMsg(c))
262 if err != nil {
263 t.Fatal(err)
264 }
265 defer sub.Unsubscribe()
266
267 wg.Add(1)
268 go func() {
269 defer wg.Done()
270 _, err := c.Request("natstransport.test", []byte("test data"), 2*time.Second)
271 if err != nil {
272 t.Fatal(err)
273 }
274 }()
275
276 select {
277 case <-done:
278 case <-time.After(time.Second):
279 t.Fatal("timeout waiting for finalizer")
280 }
281
282 wg.Wait()
283 }
284
285 func TestSubscriberFinalizerFunc(t *testing.T) {
286 s, c := newNATSConn(t)
287 defer func() { s.Shutdown(); s.WaitForShutdown() }()
288 defer c.Close()
289
290 var (
291 response = struct{ Body string }{"go eat a fly ugly\n"}
292 wg sync.WaitGroup
293 done = make(chan struct{})
294 )
295 handler := natstransport.NewSubscriber(
296 endpoint.Nop,
297 func(context.Context, *nats.Msg) (interface{}, error) {
298 return struct{}{}, nil
299 },
300 func(_ context.Context, reply string, nc *nats.Conn, _ interface{}) error {
301 b, err := json.Marshal(response)
302 if err != nil {
303 return err
304 }
305
306 return c.Publish(reply, b)
307 },
308 natstransport.SubscriberFinalizer(func(ctx context.Context, _ *nats.Msg) {
309 close(done)
310 }),
311 )
312
313 sub, err := c.QueueSubscribe("natstransport.test", "natstransport", handler.ServeMsg(c))
314 if err != nil {
315 t.Fatal(err)
316 }
317 defer sub.Unsubscribe()
318
319 wg.Add(1)
320 go func() {
321 defer wg.Done()
322 _, err := c.Request("natstransport.test", []byte("test data"), 2*time.Second)
323 if err != nil {
324 t.Fatal(err)
325 }
326 }()
327
328 select {
329 case <-done:
330 case <-time.After(time.Second):
331 t.Fatal("timeout waiting for finalizer")
332 }
333
334 wg.Wait()
335 }
336
337 func TestEncodeJSONResponse(t *testing.T) {
338 s, c := newNATSConn(t)
339 defer func() { s.Shutdown(); s.WaitForShutdown() }()
340 defer c.Close()
341
342 handler := natstransport.NewSubscriber(
343 func(context.Context, interface{}) (interface{}, error) {
344 return struct {
345 Foo string `json:"foo"`
346 }{"bar"}, nil
347 },
348 func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, nil },
349 natstransport.EncodeJSONResponse,
350 )
351
352 sub, err := c.QueueSubscribe("natstransport.test", "natstransport", handler.ServeMsg(c))
353 if err != nil {
354 t.Fatal(err)
355 }
356 defer sub.Unsubscribe()
357
358 r, err := c.Request("natstransport.test", []byte("test data"), 2*time.Second)
359 if err != nil {
360 t.Fatal(err)
361 }
362
363 if want, have := `{"foo":"bar"}`, strings.TrimSpace(string(r.Data)); want != have {
364 t.Errorf("Body: want %s, have %s", want, have)
365 }
366 }
367
368 type responseError struct {
369 msg string
370 }
371
372 func (m responseError) Error() string {
373 return m.msg
374 }
375
376 func TestErrorEncoder(t *testing.T) {
377 s, c := newNATSConn(t)
378 defer func() { s.Shutdown(); s.WaitForShutdown() }()
379 defer c.Close()
380
381 errResp := struct {
382 Error string `json:"err"`
383 }{"oh no"}
384 handler := natstransport.NewSubscriber(
385 func(context.Context, interface{}) (interface{}, error) {
386 return nil, responseError{msg: errResp.Error}
387 },
388 func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, nil },
389 natstransport.EncodeJSONResponse,
390 )
391
392 sub, err := c.QueueSubscribe("natstransport.test", "natstransport", handler.ServeMsg(c))
393 if err != nil {
394 t.Fatal(err)
395 }
396 defer sub.Unsubscribe()
397
398 r, err := c.Request("natstransport.test", []byte("test data"), 2*time.Second)
399 if err != nil {
400 t.Fatal(err)
401 }
402
403 b, err := json.Marshal(errResp)
404 if err != nil {
405 t.Fatal(err)
406 }
407 if string(b) != string(r.Data) {
408 t.Errorf("ErrorEncoder: got: %q, expected: %q", r.Data, b)
409 }
410 }
411
412 type noContentResponse struct{}
413
414 func TestEncodeNoContent(t *testing.T) {
415 s, c := newNATSConn(t)
416 defer func() { s.Shutdown(); s.WaitForShutdown() }()
417 defer c.Close()
418
419 handler := natstransport.NewSubscriber(
420 func(context.Context, interface{}) (interface{}, error) { return noContentResponse{}, nil },
421 func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, nil },
422 natstransport.EncodeJSONResponse,
423 )
424
425 sub, err := c.QueueSubscribe("natstransport.test", "natstransport", handler.ServeMsg(c))
426 if err != nil {
427 t.Fatal(err)
428 }
429 defer sub.Unsubscribe()
430
431 r, err := c.Request("natstransport.test", []byte("test data"), 2*time.Second)
432 if err != nil {
433 t.Fatal(err)
434 }
435
436 if want, have := `{}`, strings.TrimSpace(string(r.Data)); want != have {
437 t.Errorf("Body: want %s, have %s", want, have)
438 }
439 }
440
441 func TestNoOpRequestDecoder(t *testing.T) {
442 s, c := newNATSConn(t)
443 defer func() { s.Shutdown(); s.WaitForShutdown() }()
444 defer c.Close()
445
446 handler := natstransport.NewSubscriber(
447 func(ctx context.Context, request interface{}) (interface{}, error) {
448 if request != nil {
449 t.Error("Expected nil request in endpoint when using NopRequestDecoder")
450 }
451 return nil, nil
452 },
453 natstransport.NopRequestDecoder,
454 natstransport.EncodeJSONResponse,
455 )
456
457 sub, err := c.QueueSubscribe("natstransport.test", "natstransport", handler.ServeMsg(c))
458 if err != nil {
459 t.Fatal(err)
460 }
461 defer sub.Unsubscribe()
462
463 r, err := c.Request("natstransport.test", []byte("test data"), 2*time.Second)
464 if err != nil {
465 t.Fatal(err)
466 }
467
468 if want, have := `null`, strings.TrimSpace(string(r.Data)); want != have {
469 t.Errorf("Body: want %s, have %s", want, have)
470 }
471 }
472
473 func testSubscriber(t *testing.T) (step func(), resp <-chan *nats.Msg) {
474 var (
475 stepch = make(chan bool)
476 endpoint = func(context.Context, interface{}) (interface{}, error) {
477 <-stepch
478 return struct{}{}, nil
479 }
480 response = make(chan *nats.Msg)
481 handler = natstransport.NewSubscriber(
482 endpoint,
483 func(context.Context, *nats.Msg) (interface{}, error) { return struct{}{}, nil },
484 natstransport.EncodeJSONResponse,
485 natstransport.SubscriberBefore(func(ctx context.Context, msg *nats.Msg) context.Context { return ctx }),
486 natstransport.SubscriberAfter(func(ctx context.Context, nc *nats.Conn) context.Context { return ctx }),
487 )
488 )
489
490 go func() {
491 s, c := newNATSConn(t)
492 defer func() { s.Shutdown(); s.WaitForShutdown() }()
493 defer c.Close()
494
495 sub, err := c.QueueSubscribe("natstransport.test", "natstransport", handler.ServeMsg(c))
496 if err != nil {
497 t.Fatal(err)
498 }
499 defer sub.Unsubscribe()
500
501 r, err := c.Request("natstransport.test", []byte("test data"), 2*time.Second)
502 if err != nil {
503 t.Fatal(err)
504 }
505
506 response <- r
507 }()
508
509 return func() { stepch <- true }, response
510 }
511
512 func testRequest(t *testing.T, c *nats.Conn, handler *natstransport.Subscriber) TestResponse {
513 sub, err := c.QueueSubscribe("natstransport.test", "natstransport", handler.ServeMsg(c))
514 if err != nil {
515 t.Fatal(err)
516 }
517 defer sub.Unsubscribe()
518
519 r, err := c.Request("natstransport.test", []byte("test data"), 2*time.Second)
520 if err != nil {
521 t.Fatal(err)
522 }
523
524 var resp TestResponse
525 err = json.Unmarshal(r.Data, &resp)
526 if err != nil {
527 t.Fatal(err)
528 }
529
530 return resp
531 }
99 That's it!
1010 The net/rpc binding can be registered to a name, and bound to an HTTP handler, the same as any other net/rpc endpoint.
1111 And within your service, you can use standard Go kit components and idioms.
12 See [addsvc](https://github.com/go-kit/kit/tree/master/examples/addsvc) for a complete working example with net/rpc support.
12 See [addsvc](https://github.com/go-kit/examples/tree/master/addsvc) for a complete working example with net/rpc support.
1313 And remember: Go kit services can support multiple transports simultaneously.
55
66 First, define your service in the Thrift IDL.
77 The [Thrift IDL documentation](https://thrift.apache.org/docs/idl) provides more details.
8 See [add.thrift](https://github.com/go-kit/kit/blob/ec8b02591ee873433565a1ae9d317353412d1d27/examples/addsvc/_thrift/add.thrift) for an example.
8 See [addsvc.thrift](https://github.com/go-kit/examples/blob/master/addsvc/thrift/addsvc.thrift) for an example.
99 Make sure the Thrift definition matches your service's Go kit (interface) definition.
1010
1111 Next, [download Thrift](https://thrift.apache.org/download) and [install the compiler](https://thrift.apache.org/docs/install/).
2121
2222 Finally, write a tiny binding from your service definition to the Thrift definition.
2323 It's a straightforward conversion from one domain to the other.
24 See [thrift_binding.go](https://github.com/go-kit/kit/blob/ec8b02591ee873433565a1ae9d317353412d1d27/examples/addsvc/thrift_binding.go) for an example.
24 See [thrift.go](https://github.com/go-kit/examples/blob/master/addsvc/pkg/addtransport/thrift.go) for an example.
2525
2626 That's it!
2727 The Thrift binding can be bound to a listener and serve normal Thrift requests.
2828 And within your service, you can use standard Go kit components and idioms.
2929 Unfortunately, setting up a Thrift listener is rather laborious and nonidiomatic in Go.
30 Fortunately, [addsvc](https://github.com/go-kit/kit/tree/master/examples/addsvc) is a complete working example with Thrift support.
30 Fortunately, [addsvc](https://github.com/go-kit/examples/tree/master/addsvc) is a complete working example with Thrift support.
3131 And remember: Go kit services can support multiple transports simultaneously.
+0
-28
update_deps.bash less more
0 #!/usr/bin/env bash
1
2 # This script updates each non-stdlib, non-Go-kit dependency to its most recent
3 # commit. It can be invoked to aid in debugging after a dependency-related
4 # failure on continuous integration.
5
6 function deps {
7 go list -f '{{join .Deps "\n"}}' ./...
8 }
9
10 function not_stdlib {
11 xargs go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}'
12 }
13
14 function not_gokit {
15 grep -v 'go-kit/kit'
16 }
17
18 function go_get_update {
19 while read d
20 do
21 echo $d
22 go get -u $d || echo "failed, trying again with master" && cd $GOPATH/src/$d && git checkout master && go get -u $d
23 done
24 }
25
26 deps | not_stdlib | not_gokit | go_get_update
27
11
22 import (
33 "errors"
4 "math/rand"
45 "net"
56 "time"
67
7 "github.com/go-kit/kit/log"
8 "github.com/go-kit/log"
89 )
910
1011 // Dialer imitates net.Dial. Dialer is assumed to yield connections that are
102103 case conn = <-connc:
103104 if conn == nil {
104105 // didn't work
105 backoff = exponential(backoff) // wait longer
106 backoff = Exponential(backoff) // wait longer
106107 reconnectc = m.after(backoff) // try again
107108 } else {
108109 // worked!
115116 case err := <-m.putc:
116117 if err != nil && conn != nil {
117118 m.logger.Log("err", err)
119 conn.Close()
118120 conn = nil // connection is bad
119121 reconnectc = m.after(time.Nanosecond) // trigger immediately
120122 }
131133 return conn
132134 }
133135
134 func exponential(d time.Duration) time.Duration {
136 // Exponential takes a duration and returns another one that is twice as long, +/- 50%. It is
137 // used to provide backoff for operations that may fail and should avoid thundering herds.
138 // See https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ for rationale
139 func Exponential(d time.Duration) time.Duration {
135140 d *= 2
141 jitter := rand.Float64() + 0.5
142 d = time.Duration(int64(float64(d.Nanoseconds()) * jitter))
136143 if d > time.Minute {
137144 d = time.Minute
138145 }
139146 return d
147
140148 }
141149
142150 // ErrConnectionUnavailable is returned by the Manager's Write method when the
66 "testing"
77 "time"
88
9 "github.com/go-kit/kit/log"
9 "github.com/go-kit/log"
1010 )
1111
1212 func TestManager(t *testing.T) {