Switch from glide to govendor

This commit is contained in:
Manfred Touron
2017-12-19 13:55:52 +01:00
parent ccffd8bfe2
commit 230480afd1
1871 changed files with 302 additions and 801202 deletions

View File

@@ -1,45 +0,0 @@
examples/addsvc/addsvc
examples/addsvc/client/client
examples/apigateway/apigateway
examples/profilesvc/profilesvc
examples/stringsvc1/stringsvc1
examples/stringsvc2/stringsvc2
examples/stringsvc3/stringsvc3
gover.coverprofile
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
_old*
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
# https://github.com/github/gitignore/blob/master/Global/Vim.gitignore
# swap
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
# session
Session.vim
# temporary
.netrwhist
*~
# auto-generated tag files
tags

View File

@@ -1,9 +0,0 @@
language: go
script: go test -race -v ./...
go:
- 1.5.4
- 1.6.3
- 1.7.1
- tip

View File

@@ -1,106 +0,0 @@
package jwt
import (
"testing"
jwt "github.com/dgrijalva/jwt-go"
"golang.org/x/net/context"
)
var (
kid = "kid"
key = []byte("test_signing_key")
method = jwt.SigningMethodHS256
invalidMethod = jwt.SigningMethodRS256
claims = Claims{"user": "go-kit"}
// Signed tokens generated at https://jwt.io/
signedKey = "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtpZCIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiZ28ta2l0In0.14M2VmYyApdSlV_LZ88ajjwuaLeIFplB8JpyNy0A19E"
invalidKey = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.e30.vKVCKto-Wn6rgz3vBdaZaCBGfCBDTXOENSo_X2Gq7qA"
)
func TestSigner(t *testing.T) {
e := func(ctx context.Context, i interface{}) (interface{}, error) { return ctx, nil }
signer := NewSigner(kid, key, method, claims)(e)
ctx, err := signer(context.Background(), struct{}{})
if err != nil {
t.Fatalf("Signer returned error: %s", err)
}
token, ok := ctx.(context.Context).Value(JWTTokenContextKey).(string)
if !ok {
t.Fatal("Token did not exist in context")
}
if token != signedKey {
t.Fatalf("JWT tokens did not match: expecting %s got %s", signedKey, token)
}
}
func TestJWTParser(t *testing.T) {
e := func(ctx context.Context, i interface{}) (interface{}, error) { return ctx, nil }
keys := func(token *jwt.Token) (interface{}, error) {
return key, nil
}
parser := NewParser(keys, method)(e)
// No Token is passed into the parser
_, err := parser(context.Background(), struct{}{})
if err == nil {
t.Error("Parser should have returned an error")
}
if err != ErrTokenContextMissing {
t.Errorf("unexpected error returned, expected: %s got: %s", ErrTokenContextMissing, err)
}
// Invalid Token is passed into the parser
ctx := context.WithValue(context.Background(), JWTTokenContextKey, invalidKey)
_, err = parser(ctx, struct{}{})
if err == nil {
t.Error("Parser should have returned an error")
}
// Invalid Method is used in the parser
badParser := NewParser(keys, invalidMethod)(e)
ctx = context.WithValue(context.Background(), JWTTokenContextKey, signedKey)
_, err = badParser(ctx, struct{}{})
if err == nil {
t.Error("Parser should have returned an error")
}
if err != ErrUnexpectedSigningMethod {
t.Errorf("unexpected error returned, expected: %s got: %s", ErrUnexpectedSigningMethod, err)
}
// Invalid key is used in the parser
invalidKeys := func(token *jwt.Token) (interface{}, error) {
return []byte("bad"), nil
}
badParser = NewParser(invalidKeys, method)(e)
ctx = context.WithValue(context.Background(), JWTTokenContextKey, signedKey)
_, err = badParser(ctx, struct{}{})
if err == nil {
t.Error("Parser should have returned an error")
}
// Correct token is passed into the parser
ctx = context.WithValue(context.Background(), JWTTokenContextKey, signedKey)
ctx1, err := parser(ctx, struct{}{})
if err != nil {
t.Fatalf("Parser returned error: %s", err)
}
cl, ok := ctx1.(context.Context).Value(JWTClaimsContextKey).(Claims)
if !ok {
t.Fatal("Claims were not passed into context correctly")
}
if cl["user"] != claims["user"] {
t.Fatalf("JWT Claims.user did not match: expecting %s got %s", claims["user"], cl["user"])
}
}

View File

@@ -1,126 +0,0 @@
package jwt
import (
"fmt"
"net/http"
"testing"
"google.golang.org/grpc/metadata"
"golang.org/x/net/context"
)
func TestToHTTPContext(t *testing.T) {
reqFunc := ToHTTPContext()
// When the header doesn't exist
ctx := reqFunc(context.Background(), &http.Request{})
if ctx.Value(JWTTokenContextKey) != nil {
t.Error("Context shouldn't contain the encoded JWT")
}
// Authorization header value has invalid format
header := http.Header{}
header.Set("Authorization", "no expected auth header format value")
ctx = reqFunc(context.Background(), &http.Request{Header: header})
if ctx.Value(JWTTokenContextKey) != nil {
t.Error("Context shouldn't contain the encoded JWT")
}
// Authorization header is correct
header.Set("Authorization", generateAuthHeaderFromToken(signedKey))
ctx = reqFunc(context.Background(), &http.Request{Header: header})
token := ctx.Value(JWTTokenContextKey).(string)
if token != signedKey {
t.Errorf("Context doesn't contain the expected encoded token value; expected: %s, got: %s", signedKey, token)
}
}
func TestFromHTTPContext(t *testing.T) {
reqFunc := FromHTTPContext()
// No JWT Token is passed in the context
ctx := context.Background()
r := http.Request{}
reqFunc(ctx, &r)
token := r.Header.Get("Authorization")
if token != "" {
t.Error("authorization key should not exist in metadata")
}
// Correct JWT Token is passed in the context
ctx = context.WithValue(context.Background(), JWTTokenContextKey, signedKey)
r = http.Request{Header: http.Header{}}
reqFunc(ctx, &r)
token = r.Header.Get("Authorization")
expected := generateAuthHeaderFromToken(signedKey)
if token != expected {
t.Errorf("Authorization header does not contain the expected JWT token; expected %s, got %s", expected, token)
}
}
func TestToGRPCContext(t *testing.T) {
md := metadata.MD{}
reqFunc := ToGRPCContext()
// No Authorization header is passed
ctx := reqFunc(context.Background(), &md)
token := ctx.Value(JWTTokenContextKey)
if token != nil {
t.Error("Context should not contain a JWT Token")
}
// Invalid Authorization header is passed
md["authorization"] = []string{fmt.Sprintf("%s", signedKey)}
ctx = reqFunc(context.Background(), &md)
token = ctx.Value(JWTTokenContextKey)
if token != nil {
t.Error("Context should not contain a JWT Token")
}
// Authorization header is correct
md["authorization"] = []string{fmt.Sprintf("Bearer %s", signedKey)}
ctx = reqFunc(context.Background(), &md)
token, ok := ctx.Value(JWTTokenContextKey).(string)
if !ok {
t.Fatal("JWT Token not passed to context correctly")
}
if token != signedKey {
t.Errorf("JWT tokens did not match: expecting %s got %s", signedKey, token)
}
}
func TestFromGRPCContext(t *testing.T) {
reqFunc := FromGRPCContext()
// No JWT Token is passed in the context
ctx := context.Background()
md := metadata.MD{}
reqFunc(ctx, &md)
_, ok := md["authorization"]
if ok {
t.Error("authorization key should not exist in metadata")
}
// Correct JWT Token is passed in the context
ctx = context.WithValue(context.Background(), JWTTokenContextKey, signedKey)
md = metadata.MD{}
reqFunc(ctx, &md)
token, ok := md["authorization"]
if !ok {
t.Fatal("JWT Token not passed to metadata correctly")
}
if token[0] != generateAuthHeaderFromToken(signedKey) {
t.Errorf("JWT tokens did not match: expecting %s got %s", signedKey, token[0])
}
}

View File

@@ -1,10 +0,0 @@
// Package circuitbreaker implements the circuit breaker pattern.
//
// Circuit breakers prevent thundering herds, and improve resiliency against
// intermittent errors. Every client-side endpoint should be wrapped in a
// circuit breaker.
//
// We provide several implementations in this package, but if you're looking
// for guidance, Gobreaker is probably the best place to start. It has a
// simple and intuitive API, and is well-tested.
package circuitbreaker

View File

@@ -1,21 +0,0 @@
package circuitbreaker
import (
"github.com/sony/gobreaker"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
)
// Gobreaker returns an endpoint.Middleware that implements the circuit
// breaker pattern using the sony/gobreaker package. Only errors returned by
// the wrapped endpoint count against the circuit breaker's error count.
//
// See http://godoc.org/github.com/sony/gobreaker for more information.
func Gobreaker(cb *gobreaker.CircuitBreaker) endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
return cb.Execute(func() (interface{}, error) { return next(ctx, request) })
}
}
}

View File

@@ -1,19 +0,0 @@
package circuitbreaker_test
import (
"testing"
"github.com/sony/gobreaker"
"github.com/go-kit/kit/circuitbreaker"
)
func TestGobreaker(t *testing.T) {
var (
breaker = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{}))
primeWith = 100
shouldPass = func(n int) bool { return n <= 5 } // https://github.com/sony/gobreaker/blob/bfa846d/gobreaker.go#L76
circuitOpenError = "circuit breaker is open"
)
testFailingEndpoint(t, breaker, primeWith, shouldPass, 0, circuitOpenError)
}

View File

@@ -1,38 +0,0 @@
package circuitbreaker
import (
"time"
"github.com/streadway/handy/breaker"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
)
// HandyBreaker returns an endpoint.Middleware that implements the circuit
// breaker pattern using the streadway/handy/breaker package. Only errors
// returned by the wrapped endpoint count against the circuit breaker's error
// count.
//
// See http://godoc.org/github.com/streadway/handy/breaker for more
// information.
func HandyBreaker(cb breaker.Breaker) endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
if !cb.Allow() {
return nil, breaker.ErrCircuitOpen
}
defer func(begin time.Time) {
if err == nil {
cb.Success(time.Since(begin))
} else {
cb.Failure(time.Since(begin))
}
}(time.Now())
response, err = next(ctx, request)
return
}
}
}

View File

@@ -1,20 +0,0 @@
package circuitbreaker_test
import (
"testing"
handybreaker "github.com/streadway/handy/breaker"
"github.com/go-kit/kit/circuitbreaker"
)
func TestHandyBreaker(t *testing.T) {
var (
failureRatio = 0.05
breaker = circuitbreaker.HandyBreaker(handybreaker.NewBreaker(failureRatio))
primeWith = handybreaker.DefaultMinObservations * 10
shouldPass = func(n int) bool { return (float64(n) / float64(primeWith+n)) <= failureRatio }
openCircuitError = handybreaker.ErrCircuitOpen.Error()
)
testFailingEndpoint(t, breaker, primeWith, shouldPass, 0, openCircuitError)
}

View File

@@ -1,30 +0,0 @@
package circuitbreaker
import (
"github.com/afex/hystrix-go/hystrix"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
)
// Hystrix returns an endpoint.Middleware that implements the circuit
// breaker pattern using the afex/hystrix-go package.
//
// When using this circuit breaker, please configure your commands separately.
//
// See https://godoc.org/github.com/afex/hystrix-go/hystrix for more
// information.
func Hystrix(commandName string) endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
var resp interface{}
if err := hystrix.Do(commandName, func() (err error) {
resp, err = next(ctx, request)
return err
}, nil); err != nil {
return nil, err
}
return resp, nil
}
}
}

View File

@@ -1,40 +0,0 @@
package circuitbreaker_test
import (
"io/ioutil"
stdlog "log"
"testing"
"time"
"github.com/afex/hystrix-go/hystrix"
"github.com/go-kit/kit/circuitbreaker"
)
func TestHystrix(t *testing.T) {
stdlog.SetOutput(ioutil.Discard)
const (
commandName = "my-endpoint"
errorPercent = 5
maxConcurrent = 1000
)
hystrix.ConfigureCommand(commandName, hystrix.CommandConfig{
ErrorPercentThreshold: errorPercent,
MaxConcurrentRequests: maxConcurrent,
})
var (
breaker = circuitbreaker.Hystrix(commandName)
primeWith = hystrix.DefaultVolumeThreshold * 2
shouldPass = func(n int) bool { return (float64(n) / float64(primeWith+n)) <= (float64(errorPercent-1) / 100.0) }
openCircuitError = hystrix.ErrCircuitOpen.Error()
)
// hystrix-go uses buffered channels to receive reports on request success/failure,
// and so is basically impossible to test deterministically. We have to make sure
// the report buffer is emptied, by injecting a sleep between each invocation.
requestDelay := 5 * time.Millisecond
testFailingEndpoint(t, breaker, primeWith, shouldPass, requestDelay, openCircuitError)
}

View File

@@ -1,76 +0,0 @@
package circuitbreaker_test
import (
"errors"
"fmt"
"path/filepath"
"runtime"
"testing"
"time"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
)
func testFailingEndpoint(
t *testing.T,
breaker endpoint.Middleware,
primeWith int,
shouldPass func(int) bool,
requestDelay time.Duration,
openCircuitError string,
) {
_, file, line, _ := runtime.Caller(1)
caller := fmt.Sprintf("%s:%d", filepath.Base(file), line)
// Create a mock endpoint and wrap it with the breaker.
m := mock{}
var e endpoint.Endpoint
e = m.endpoint
e = breaker(e)
// Prime the endpoint with successful requests.
for i := 0; i < primeWith; i++ {
if _, err := e(context.Background(), struct{}{}); err != nil {
t.Fatalf("%s: during priming, got error: %v", caller, err)
}
time.Sleep(requestDelay)
}
// Switch the endpoint to start throwing errors.
m.err = errors.New("tragedy+disaster")
m.through = 0
// The first several should be allowed through and yield our error.
for i := 0; shouldPass(i); i++ {
if _, err := e(context.Background(), struct{}{}); err != m.err {
t.Fatalf("%s: want %v, have %v", caller, m.err, err)
}
time.Sleep(requestDelay)
}
through := m.through
// But the rest should be blocked by an open circuit.
for i := 0; i < 10; i++ {
if _, err := e(context.Background(), struct{}{}); err.Error() != openCircuitError {
t.Fatalf("%s: want %q, have %q", caller, openCircuitError, err.Error())
}
time.Sleep(requestDelay)
}
// Make sure none of those got through.
if want, have := through, m.through; want != have {
t.Errorf("%s: want %d, have %d", caller, want, have)
}
}
type mock struct {
through int
err error
}
func (m *mock) endpoint(context.Context, interface{}) (interface{}, error) {
m.through++
return struct{}{}, m.err
}

View File

@@ -1,50 +0,0 @@
package endpoint_test
import (
"fmt"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
)
func ExampleChain() {
e := endpoint.Chain(
annotate("first"),
annotate("second"),
annotate("third"),
)(myEndpoint)
if _, err := e(ctx, req); err != nil {
panic(err)
}
// Output:
// first pre
// second pre
// third pre
// my endpoint!
// third post
// second post
// first post
}
var (
ctx = context.Background()
req = struct{}{}
)
func annotate(s string) endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
fmt.Println(s, "pre")
defer fmt.Println(s, "post")
return next(ctx, request)
}
}
}
func myEndpoint(context.Context, interface{}) (interface{}, error) {
fmt.Println("my endpoint!")
return struct{}{}, nil
}

View File

@@ -1,5 +0,0 @@
# Examples
For more information about these examples,
including a walkthrough of the stringsvc example,
see [gokit.io/examples](https://gokit.io/examples).

View File

@@ -1,75 +0,0 @@
// Package grpc provides a gRPC client for the add service.
package grpc
import (
"time"
jujuratelimit "github.com/juju/ratelimit"
stdopentracing "github.com/opentracing/opentracing-go"
"github.com/sony/gobreaker"
"google.golang.org/grpc"
"github.com/go-kit/kit/circuitbreaker"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/examples/addsvc"
"github.com/go-kit/kit/examples/addsvc/pb"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/ratelimit"
"github.com/go-kit/kit/tracing/opentracing"
grpctransport "github.com/go-kit/kit/transport/grpc"
)
// New returns an AddService backed by a gRPC client connection. It is the
// responsibility of the caller to dial, and later close, the connection.
func New(conn *grpc.ClientConn, tracer stdopentracing.Tracer, logger log.Logger) addsvc.Service {
// We construct a single ratelimiter middleware, to limit the total outgoing
// QPS from this client to all methods on the remote instance. We also
// construct per-endpoint circuitbreaker middlewares to demonstrate how
// that's done, although they could easily be combined into a single breaker
// for the entire remote instance, too.
limiter := ratelimit.NewTokenBucketLimiter(jujuratelimit.NewBucketWithRate(100, 100))
var sumEndpoint endpoint.Endpoint
{
sumEndpoint = grpctransport.NewClient(
conn,
"Add",
"Sum",
addsvc.EncodeGRPCSumRequest,
addsvc.DecodeGRPCSumResponse,
pb.SumReply{},
grpctransport.ClientBefore(opentracing.ToGRPCRequest(tracer, logger)),
).Endpoint()
sumEndpoint = opentracing.TraceClient(tracer, "Sum")(sumEndpoint)
sumEndpoint = limiter(sumEndpoint)
sumEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{
Name: "Sum",
Timeout: 30 * time.Second,
}))(sumEndpoint)
}
var concatEndpoint endpoint.Endpoint
{
concatEndpoint = grpctransport.NewClient(
conn,
"Add",
"Concat",
addsvc.EncodeGRPCConcatRequest,
addsvc.DecodeGRPCConcatResponse,
pb.ConcatReply{},
grpctransport.ClientBefore(opentracing.ToGRPCRequest(tracer, logger)),
).Endpoint()
concatEndpoint = opentracing.TraceClient(tracer, "Concat")(concatEndpoint)
concatEndpoint = limiter(concatEndpoint)
concatEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{
Name: "Concat",
Timeout: 30 * time.Second,
}))(concatEndpoint)
}
return addsvc.Endpoints{
SumEndpoint: sumEndpoint,
ConcatEndpoint: concatEndpoint,
}
}

View File

@@ -1,86 +0,0 @@
// Package http provides an HTTP client for the add service.
package http
import (
"net/url"
"strings"
"time"
jujuratelimit "github.com/juju/ratelimit"
stdopentracing "github.com/opentracing/opentracing-go"
"github.com/sony/gobreaker"
"github.com/go-kit/kit/circuitbreaker"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/examples/addsvc"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/ratelimit"
"github.com/go-kit/kit/tracing/opentracing"
httptransport "github.com/go-kit/kit/transport/http"
)
// New returns an AddService backed by an HTTP server living at the remote
// instance. We expect instance to come from a service discovery system, so
// likely of the form "host:port".
func New(instance string, tracer stdopentracing.Tracer, logger log.Logger) (addsvc.Service, error) {
if !strings.HasPrefix(instance, "http") {
instance = "http://" + instance
}
u, err := url.Parse(instance)
if err != nil {
return nil, err
}
// We construct a single ratelimiter middleware, to limit the total outgoing
// QPS from this client to all methods on the remote instance. We also
// construct per-endpoint circuitbreaker middlewares to demonstrate how
// that's done, although they could easily be combined into a single breaker
// for the entire remote instance, too.
limiter := ratelimit.NewTokenBucketLimiter(jujuratelimit.NewBucketWithRate(100, 100))
var sumEndpoint endpoint.Endpoint
{
sumEndpoint = httptransport.NewClient(
"POST",
copyURL(u, "/sum"),
addsvc.EncodeHTTPGenericRequest,
addsvc.DecodeHTTPSumResponse,
httptransport.ClientBefore(opentracing.ToHTTPRequest(tracer, logger)),
).Endpoint()
sumEndpoint = opentracing.TraceClient(tracer, "Sum")(sumEndpoint)
sumEndpoint = limiter(sumEndpoint)
sumEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{
Name: "Sum",
Timeout: 30 * time.Second,
}))(sumEndpoint)
}
var concatEndpoint endpoint.Endpoint
{
concatEndpoint = httptransport.NewClient(
"POST",
copyURL(u, "/concat"),
addsvc.EncodeHTTPGenericRequest,
addsvc.DecodeHTTPConcatResponse,
httptransport.ClientBefore(opentracing.ToHTTPRequest(tracer, logger)),
).Endpoint()
concatEndpoint = opentracing.TraceClient(tracer, "Concat")(concatEndpoint)
concatEndpoint = limiter(concatEndpoint)
sumEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{
Name: "Concat",
Timeout: 30 * time.Second,
}))(sumEndpoint)
}
return addsvc.Endpoints{
SumEndpoint: sumEndpoint,
ConcatEndpoint: concatEndpoint,
}, nil
}
func copyURL(base *url.URL, path string) *url.URL {
next := *base
next.Path = path
return &next
}

View File

@@ -1,55 +0,0 @@
// Package thrift provides a Thrift client for the add service.
package thrift
import (
"time"
jujuratelimit "github.com/juju/ratelimit"
"github.com/sony/gobreaker"
"github.com/go-kit/kit/circuitbreaker"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/examples/addsvc"
thriftadd "github.com/go-kit/kit/examples/addsvc/thrift/gen-go/addsvc"
"github.com/go-kit/kit/ratelimit"
)
// New returns an AddService backed by a Thrift server described by the provided
// client. The caller is responsible for constructing the client, and eventually
// closing the underlying transport.
func New(client *thriftadd.AddServiceClient) addsvc.Service {
// We construct a single ratelimiter middleware, to limit the total outgoing
// QPS from this client to all methods on the remote instance. We also
// construct per-endpoint circuitbreaker middlewares to demonstrate how
// that's done, although they could easily be combined into a single breaker
// for the entire remote instance, too.
limiter := ratelimit.NewTokenBucketLimiter(jujuratelimit.NewBucketWithRate(100, 100))
// Thrift does not currently have tracer bindings, so we skip tracing.
var sumEndpoint endpoint.Endpoint
{
sumEndpoint = addsvc.MakeThriftSumEndpoint(client)
sumEndpoint = limiter(sumEndpoint)
sumEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{
Name: "Sum",
Timeout: 30 * time.Second,
}))(sumEndpoint)
}
var concatEndpoint endpoint.Endpoint
{
concatEndpoint = addsvc.MakeThriftConcatEndpoint(client)
concatEndpoint = limiter(concatEndpoint)
concatEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{
Name: "Concat",
Timeout: 30 * time.Second,
}))(concatEndpoint)
}
return addsvc.Endpoints{
SumEndpoint: sumEndpoint,
ConcatEndpoint: concatEndpoint,
}
}

View File

@@ -1,178 +0,0 @@
package main
import (
"flag"
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/apache/thrift/lib/go/thrift"
"github.com/lightstep/lightstep-tracer-go"
stdopentracing "github.com/opentracing/opentracing-go"
zipkin "github.com/openzipkin/zipkin-go-opentracing"
"golang.org/x/net/context"
"google.golang.org/grpc"
"sourcegraph.com/sourcegraph/appdash"
appdashot "sourcegraph.com/sourcegraph/appdash/opentracing"
"github.com/go-kit/kit/examples/addsvc"
grpcclient "github.com/go-kit/kit/examples/addsvc/client/grpc"
httpclient "github.com/go-kit/kit/examples/addsvc/client/http"
thriftclient "github.com/go-kit/kit/examples/addsvc/client/thrift"
thriftadd "github.com/go-kit/kit/examples/addsvc/thrift/gen-go/addsvc"
"github.com/go-kit/kit/log"
)
func main() {
// The addcli presumes no service discovery system, and expects users to
// provide the direct address of an addsvc. This presumption is reflected in
// the addcli binary and the the client packages: the -transport.addr flags
// and various client constructors both expect host:port strings. For an
// example service with a client built on top of a service discovery system,
// see profilesvc.
var (
httpAddr = flag.String("http.addr", "", "HTTP address of addsvc")
grpcAddr = flag.String("grpc.addr", "", "gRPC (HTTP) address of addsvc")
thriftAddr = flag.String("thrift.addr", "", "Thrift address of addsvc")
thriftProtocol = flag.String("thrift.protocol", "binary", "binary, compact, json, simplejson")
thriftBufferSize = flag.Int("thrift.buffer.size", 0, "0 for unbuffered")
thriftFramed = flag.Bool("thrift.framed", false, "true to enable framing")
zipkinAddr = flag.String("zipkin.addr", "", "Enable Zipkin tracing via a Kafka Collector host:port")
appdashAddr = flag.String("appdash.addr", "", "Enable Appdash tracing via an Appdash server host:port")
lightstepToken = flag.String("lightstep.token", "", "Enable LightStep tracing via a LightStep access token")
method = flag.String("method", "sum", "sum, concat")
)
flag.Parse()
if len(flag.Args()) != 2 {
fmt.Fprintf(os.Stderr, "usage: addcli [flags] <a> <b>\n")
os.Exit(1)
}
// This is a demonstration client, which supports multiple tracers.
// Your clients will probably just use one tracer.
var tracer stdopentracing.Tracer
{
if *zipkinAddr != "" {
collector, err := zipkin.NewKafkaCollector(
strings.Split(*zipkinAddr, ","),
zipkin.KafkaLogger(log.NewNopLogger()),
)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
tracer, err = zipkin.NewTracer(
zipkin.NewRecorder(collector, false, "localhost:8000", "addcli"),
)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
} else if *appdashAddr != "" {
tracer = appdashot.NewTracer(appdash.NewRemoteCollector(*appdashAddr))
} else if *lightstepToken != "" {
tracer = lightstep.NewTracer(lightstep.Options{
AccessToken: *lightstepToken,
})
defer lightstep.FlushLightStepTracer(tracer)
} else {
tracer = stdopentracing.GlobalTracer() // no-op
}
}
// This is a demonstration client, which supports multiple transports.
// Your clients will probably just define and stick with 1 transport.
var (
service addsvc.Service
err error
)
if *httpAddr != "" {
service, err = httpclient.New(*httpAddr, tracer, log.NewNopLogger())
} else if *grpcAddr != "" {
conn, err := grpc.Dial(*grpcAddr, grpc.WithInsecure(), grpc.WithTimeout(time.Second))
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v", err)
os.Exit(1)
}
defer conn.Close()
service = grpcclient.New(conn, tracer, log.NewNopLogger())
} else if *thriftAddr != "" {
// It's necessary to do all of this construction in the func main,
// because (among other reasons) we need to control the lifecycle of the
// Thrift transport, i.e. close it eventually.
var protocolFactory thrift.TProtocolFactory
switch *thriftProtocol {
case "compact":
protocolFactory = thrift.NewTCompactProtocolFactory()
case "simplejson":
protocolFactory = thrift.NewTSimpleJSONProtocolFactory()
case "json":
protocolFactory = thrift.NewTJSONProtocolFactory()
case "binary", "":
protocolFactory = thrift.NewTBinaryProtocolFactoryDefault()
default:
fmt.Fprintf(os.Stderr, "error: invalid protocol %q\n", *thriftProtocol)
os.Exit(1)
}
var transportFactory thrift.TTransportFactory
if *thriftBufferSize > 0 {
transportFactory = thrift.NewTBufferedTransportFactory(*thriftBufferSize)
} else {
transportFactory = thrift.NewTTransportFactory()
}
if *thriftFramed {
transportFactory = thrift.NewTFramedTransportFactory(transportFactory)
}
transportSocket, err := thrift.NewTSocket(*thriftAddr)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
transport := transportFactory.GetTransport(transportSocket)
if err := transport.Open(); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
defer transport.Close()
client := thriftadd.NewAddServiceClientFactory(transport, protocolFactory)
service = thriftclient.New(client)
} else {
fmt.Fprintf(os.Stderr, "error: no remote address specified\n")
os.Exit(1)
}
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
switch *method {
case "sum":
a, _ := strconv.ParseInt(flag.Args()[0], 10, 64)
b, _ := strconv.ParseInt(flag.Args()[1], 10, 64)
v, err := service.Sum(context.Background(), int(a), int(b))
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%d + %d = %d\n", a, b, v)
case "concat":
a := flag.Args()[0]
b := flag.Args()[1]
v, err := service.Concat(context.Background(), a, b)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%q + %q = %q\n", a, b, v)
default:
fmt.Fprintf(os.Stderr, "error: invalid method %q\n", method)
os.Exit(1)
}
}

View File

@@ -1,256 +0,0 @@
package main
import (
"flag"
"fmt"
"net"
"net/http"
"net/http/pprof"
"os"
"os/signal"
"strings"
"syscall"
"github.com/apache/thrift/lib/go/thrift"
lightstep "github.com/lightstep/lightstep-tracer-go"
stdopentracing "github.com/opentracing/opentracing-go"
zipkin "github.com/openzipkin/zipkin-go-opentracing"
stdprometheus "github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
"google.golang.org/grpc"
"sourcegraph.com/sourcegraph/appdash"
appdashot "sourcegraph.com/sourcegraph/appdash/opentracing"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/examples/addsvc"
"github.com/go-kit/kit/examples/addsvc/pb"
thriftadd "github.com/go-kit/kit/examples/addsvc/thrift/gen-go/addsvc"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/metrics"
"github.com/go-kit/kit/metrics/prometheus"
"github.com/go-kit/kit/tracing/opentracing"
)
func main() {
var (
debugAddr = flag.String("debug.addr", ":8080", "Debug and metrics listen address")
httpAddr = flag.String("http.addr", ":8081", "HTTP listen address")
grpcAddr = flag.String("grpc.addr", ":8082", "gRPC (HTTP) listen address")
thriftAddr = flag.String("thrift.addr", ":8083", "Thrift listen address")
thriftProtocol = flag.String("thrift.protocol", "binary", "binary, compact, json, simplejson")
thriftBufferSize = flag.Int("thrift.buffer.size", 0, "0 for unbuffered")
thriftFramed = flag.Bool("thrift.framed", false, "true to enable framing")
zipkinAddr = flag.String("zipkin.addr", "", "Enable Zipkin tracing via a Kafka server host:port")
appdashAddr = flag.String("appdash.addr", "", "Enable Appdash tracing via an Appdash server host:port")
lightstepToken = flag.String("lightstep.token", "", "Enable LightStep tracing via a LightStep access token")
)
flag.Parse()
// Logging domain.
var logger log.Logger
{
logger = log.NewLogfmtLogger(os.Stdout)
logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
logger = log.NewContext(logger).With("caller", log.DefaultCaller)
}
logger.Log("msg", "hello")
defer logger.Log("msg", "goodbye")
// Metrics domain.
var ints, chars metrics.Counter
{
// Business level metrics.
ints = prometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "addsvc",
Name: "integers_summed",
Help: "Total count of integers summed via the Sum method.",
}, []string{})
chars = prometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "addsvc",
Name: "characters_concatenated",
Help: "Total count of characters concatenated via the Concat method.",
}, []string{})
}
var duration metrics.Histogram
{
// Transport level metrics.
duration = prometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
Namespace: "addsvc",
Name: "request_duration_ns",
Help: "Request duration in nanoseconds.",
}, []string{"method", "success"})
}
// Tracing domain.
var tracer stdopentracing.Tracer
{
if *zipkinAddr != "" {
logger := log.NewContext(logger).With("tracer", "Zipkin")
logger.Log("addr", *zipkinAddr)
collector, err := zipkin.NewKafkaCollector(
strings.Split(*zipkinAddr, ","),
zipkin.KafkaLogger(logger),
)
if err != nil {
logger.Log("err", err)
os.Exit(1)
}
tracer, err = zipkin.NewTracer(
zipkin.NewRecorder(collector, false, "localhost:80", "addsvc"),
)
if err != nil {
logger.Log("err", err)
os.Exit(1)
}
} else if *appdashAddr != "" {
logger := log.NewContext(logger).With("tracer", "Appdash")
logger.Log("addr", *appdashAddr)
tracer = appdashot.NewTracer(appdash.NewRemoteCollector(*appdashAddr))
} else if *lightstepToken != "" {
logger := log.NewContext(logger).With("tracer", "LightStep")
logger.Log() // probably don't want to print out the token :)
tracer = lightstep.NewTracer(lightstep.Options{
AccessToken: *lightstepToken,
})
defer lightstep.FlushLightStepTracer(tracer)
} else {
logger := log.NewContext(logger).With("tracer", "none")
logger.Log()
tracer = stdopentracing.GlobalTracer() // no-op
}
}
// Business domain.
var service addsvc.Service
{
service = addsvc.NewBasicService()
service = addsvc.ServiceLoggingMiddleware(logger)(service)
service = addsvc.ServiceInstrumentingMiddleware(ints, chars)(service)
}
// Endpoint domain.
var sumEndpoint endpoint.Endpoint
{
sumDuration := duration.With("method", "Sum")
sumLogger := log.NewContext(logger).With("method", "Sum")
sumEndpoint = addsvc.MakeSumEndpoint(service)
sumEndpoint = opentracing.TraceServer(tracer, "Sum")(sumEndpoint)
sumEndpoint = addsvc.EndpointInstrumentingMiddleware(sumDuration)(sumEndpoint)
sumEndpoint = addsvc.EndpointLoggingMiddleware(sumLogger)(sumEndpoint)
}
var concatEndpoint endpoint.Endpoint
{
concatDuration := duration.With("method", "Concat")
concatLogger := log.NewContext(logger).With("method", "Concat")
concatEndpoint = addsvc.MakeConcatEndpoint(service)
concatEndpoint = opentracing.TraceServer(tracer, "Concat")(concatEndpoint)
concatEndpoint = addsvc.EndpointInstrumentingMiddleware(concatDuration)(concatEndpoint)
concatEndpoint = addsvc.EndpointLoggingMiddleware(concatLogger)(concatEndpoint)
}
endpoints := addsvc.Endpoints{
SumEndpoint: sumEndpoint,
ConcatEndpoint: concatEndpoint,
}
// Mechanical domain.
errc := make(chan error)
ctx := context.Background()
// Interrupt handler.
go func() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
errc <- fmt.Errorf("%s", <-c)
}()
// Debug listener.
go func() {
logger := log.NewContext(logger).With("transport", "debug")
m := http.NewServeMux()
m.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index))
m.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline))
m.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
m.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
m.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace))
m.Handle("/metrics", stdprometheus.Handler())
logger.Log("addr", *debugAddr)
errc <- http.ListenAndServe(*debugAddr, m)
}()
// HTTP transport.
go func() {
logger := log.NewContext(logger).With("transport", "HTTP")
h := addsvc.MakeHTTPHandler(ctx, endpoints, tracer, logger)
logger.Log("addr", *httpAddr)
errc <- http.ListenAndServe(*httpAddr, h)
}()
// gRPC transport.
go func() {
logger := log.NewContext(logger).With("transport", "gRPC")
ln, err := net.Listen("tcp", *grpcAddr)
if err != nil {
errc <- err
return
}
srv := addsvc.MakeGRPCServer(ctx, endpoints, tracer, logger)
s := grpc.NewServer()
pb.RegisterAddServer(s, srv)
logger.Log("addr", *grpcAddr)
errc <- s.Serve(ln)
}()
// Thrift transport.
go func() {
logger := log.NewContext(logger).With("transport", "Thrift")
var protocolFactory thrift.TProtocolFactory
switch *thriftProtocol {
case "binary":
protocolFactory = thrift.NewTBinaryProtocolFactoryDefault()
case "compact":
protocolFactory = thrift.NewTCompactProtocolFactory()
case "json":
protocolFactory = thrift.NewTJSONProtocolFactory()
case "simplejson":
protocolFactory = thrift.NewTSimpleJSONProtocolFactory()
default:
errc <- fmt.Errorf("invalid Thrift protocol %q", *thriftProtocol)
return
}
var transportFactory thrift.TTransportFactory
if *thriftBufferSize > 0 {
transportFactory = thrift.NewTBufferedTransportFactory(*thriftBufferSize)
} else {
transportFactory = thrift.NewTTransportFactory()
}
if *thriftFramed {
transportFactory = thrift.NewTFramedTransportFactory(transportFactory)
}
transport, err := thrift.NewTServerSocket(*thriftAddr)
if err != nil {
errc <- err
return
}
logger.Log("addr", *thriftAddr)
errc <- thrift.NewTSimpleServer4(
thriftadd.NewAddServiceProcessor(addsvc.MakeThriftHandler(ctx, endpoints)),
transport,
transportFactory,
protocolFactory,
).Serve()
}()
// Run!
logger.Log("exit", <-errc)
}

View File

@@ -1,5 +0,0 @@
// Package addsvc is an example microservice, useful for education. It can sum
// integers and concatenate strings. A client library is available in the client
// subdirectory. A server binary is available in cmd/addsrv. An example client
// binary is available in cmd/addcli.
package addsvc

View File

@@ -1,135 +0,0 @@
package addsvc
// This file contains methods to make individual endpoints from services,
// request and response types to serve those endpoints, as well as encoders and
// decoders for those types, for all of our supported transport serialization
// formats. It also includes endpoint middlewares.
import (
"fmt"
"time"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/metrics"
)
// Endpoints collects all of the endpoints that compose an add service. It's
// meant to be used as a helper struct, to collect all of the endpoints into a
// single parameter.
//
// In a server, it's useful for functions that need to operate on a per-endpoint
// basis. For example, you might pass an Endpoints to a function that produces
// an http.Handler, with each method (endpoint) wired up to a specific path. (It
// is probably a mistake in design to invoke the Service methods on the
// Endpoints struct in a server.)
//
// In a client, it's useful to collect individually constructed endpoints into a
// single type that implements the Service interface. For example, you might
// construct individual endpoints using transport/http.NewClient, combine them
// into an Endpoints, and return it to the caller as a Service.
type Endpoints struct {
SumEndpoint endpoint.Endpoint
ConcatEndpoint endpoint.Endpoint
}
// Sum implements Service. Primarily useful in a client.
func (e Endpoints) Sum(ctx context.Context, a, b int) (int, error) {
request := sumRequest{A: a, B: b}
response, err := e.SumEndpoint(ctx, request)
if err != nil {
return 0, err
}
return response.(sumResponse).V, response.(sumResponse).Err
}
// Concat implements Service. Primarily useful in a client.
func (e Endpoints) Concat(ctx context.Context, a, b string) (string, error) {
request := concatRequest{A: a, B: b}
response, err := e.ConcatEndpoint(ctx, request)
if err != nil {
return "", err
}
return response.(concatResponse).V, response.(concatResponse).Err
}
// MakeSumEndpoint returns an endpoint that invokes Sum on the service.
// Primarily useful in a server.
func MakeSumEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
sumReq := request.(sumRequest)
v, err := s.Sum(ctx, sumReq.A, sumReq.B)
if err == ErrIntOverflow {
return nil, err // special case; see comment on ErrIntOverflow
}
return sumResponse{
V: v,
Err: err,
}, nil
}
}
// MakeConcatEndpoint returns an endpoint that invokes Concat on the service.
// Primarily useful in a server.
func MakeConcatEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
concatReq := request.(concatRequest)
v, err := s.Concat(ctx, concatReq.A, concatReq.B)
return concatResponse{
V: v,
Err: err,
}, nil
}
}
// EndpointInstrumentingMiddleware returns an endpoint middleware that records
// the duration of each invocation to the passed histogram. The middleware adds
// a single field: "success", which is "true" if no error is returned, and
// "false" otherwise.
func EndpointInstrumentingMiddleware(duration metrics.Histogram) endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
defer func(begin time.Time) {
duration.With("success", fmt.Sprint(err == nil)).Observe(time.Since(begin).Seconds())
}(time.Now())
return next(ctx, request)
}
}
}
// EndpointLoggingMiddleware returns an endpoint middleware that logs the
// duration of each invocation, and the resulting error, if any.
func EndpointLoggingMiddleware(logger log.Logger) endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
defer func(begin time.Time) {
logger.Log("error", err, "took", time.Since(begin))
}(time.Now())
return next(ctx, request)
}
}
}
// These types are unexported because they only exist to serve the endpoint
// domain, which is totally encapsulated in this package. They are otherwise
// opaque to all callers.
type sumRequest struct{ A, B int }
type sumResponse struct {
V int
Err error
}
type concatRequest struct{ A, B string }
type concatResponse struct {
V string
Err error
}

View File

@@ -1,215 +0,0 @@
// Code generated by protoc-gen-go.
// source: addsvc.proto
// DO NOT EDIT!
/*
Package pb is a generated protocol buffer package.
It is generated from these files:
addsvc.proto
It has these top-level messages:
SumRequest
SumReply
ConcatRequest
ConcatReply
*/
package pb
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// The sum request contains two parameters.
type SumRequest struct {
A int64 `protobuf:"varint,1,opt,name=a" json:"a,omitempty"`
B int64 `protobuf:"varint,2,opt,name=b" json:"b,omitempty"`
}
func (m *SumRequest) Reset() { *m = SumRequest{} }
func (m *SumRequest) String() string { return proto.CompactTextString(m) }
func (*SumRequest) ProtoMessage() {}
func (*SumRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
// The sum response contains the result of the calculation.
type SumReply struct {
V int64 `protobuf:"varint,1,opt,name=v" json:"v,omitempty"`
Err string `protobuf:"bytes,2,opt,name=err" json:"err,omitempty"`
}
func (m *SumReply) Reset() { *m = SumReply{} }
func (m *SumReply) String() string { return proto.CompactTextString(m) }
func (*SumReply) ProtoMessage() {}
func (*SumReply) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
// The Concat request contains two parameters.
type ConcatRequest struct {
A string `protobuf:"bytes,1,opt,name=a" json:"a,omitempty"`
B string `protobuf:"bytes,2,opt,name=b" json:"b,omitempty"`
}
func (m *ConcatRequest) Reset() { *m = ConcatRequest{} }
func (m *ConcatRequest) String() string { return proto.CompactTextString(m) }
func (*ConcatRequest) ProtoMessage() {}
func (*ConcatRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
// The Concat response contains the result of the concatenation.
type ConcatReply struct {
V string `protobuf:"bytes,1,opt,name=v" json:"v,omitempty"`
Err string `protobuf:"bytes,2,opt,name=err" json:"err,omitempty"`
}
func (m *ConcatReply) Reset() { *m = ConcatReply{} }
func (m *ConcatReply) String() string { return proto.CompactTextString(m) }
func (*ConcatReply) ProtoMessage() {}
func (*ConcatReply) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
func init() {
proto.RegisterType((*SumRequest)(nil), "pb.SumRequest")
proto.RegisterType((*SumReply)(nil), "pb.SumReply")
proto.RegisterType((*ConcatRequest)(nil), "pb.ConcatRequest")
proto.RegisterType((*ConcatReply)(nil), "pb.ConcatReply")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// Client API for Add service
type AddClient interface {
// Sums two integers.
Sum(ctx context.Context, in *SumRequest, opts ...grpc.CallOption) (*SumReply, error)
// Concatenates two strings
Concat(ctx context.Context, in *ConcatRequest, opts ...grpc.CallOption) (*ConcatReply, error)
}
type addClient struct {
cc *grpc.ClientConn
}
func NewAddClient(cc *grpc.ClientConn) AddClient {
return &addClient{cc}
}
func (c *addClient) Sum(ctx context.Context, in *SumRequest, opts ...grpc.CallOption) (*SumReply, error) {
out := new(SumReply)
err := grpc.Invoke(ctx, "/pb.Add/Sum", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *addClient) Concat(ctx context.Context, in *ConcatRequest, opts ...grpc.CallOption) (*ConcatReply, error) {
out := new(ConcatReply)
err := grpc.Invoke(ctx, "/pb.Add/Concat", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Add service
type AddServer interface {
// Sums two integers.
Sum(context.Context, *SumRequest) (*SumReply, error)
// Concatenates two strings
Concat(context.Context, *ConcatRequest) (*ConcatReply, error)
}
func RegisterAddServer(s *grpc.Server, srv AddServer) {
s.RegisterService(&_Add_serviceDesc, srv)
}
func _Add_Sum_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SumRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AddServer).Sum(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/pb.Add/Sum",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AddServer).Sum(ctx, req.(*SumRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Add_Concat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ConcatRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AddServer).Concat(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/pb.Add/Concat",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AddServer).Concat(ctx, req.(*ConcatRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Add_serviceDesc = grpc.ServiceDesc{
ServiceName: "pb.Add",
HandlerType: (*AddServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Sum",
Handler: _Add_Sum_Handler,
},
{
MethodName: "Concat",
Handler: _Add_Concat_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "addsvc.proto",
}
func init() { proto.RegisterFile("addsvc.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 189 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x4c, 0x49, 0x29,
0x2e, 0x4b, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2a, 0x48, 0x52, 0xd2, 0xe0, 0xe2,
0x0a, 0x2e, 0xcd, 0x0d, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0xe2, 0xe1, 0x62, 0x4c, 0x94,
0x60, 0x54, 0x60, 0xd4, 0x60, 0x0e, 0x62, 0x4c, 0x04, 0xf1, 0x92, 0x24, 0x98, 0x20, 0xbc, 0x24,
0x25, 0x2d, 0x2e, 0x0e, 0xb0, 0xca, 0x82, 0x9c, 0x4a, 0x90, 0x4c, 0x19, 0x4c, 0x5d, 0x99, 0x90,
0x00, 0x17, 0x73, 0x6a, 0x51, 0x11, 0x58, 0x25, 0x67, 0x10, 0x88, 0xa9, 0xa4, 0xcd, 0xc5, 0xeb,
0x9c, 0x9f, 0x97, 0x9c, 0x58, 0x82, 0x61, 0x30, 0x27, 0x8a, 0xc1, 0x9c, 0x20, 0x83, 0x75, 0xb9,
0xb8, 0x61, 0x8a, 0x51, 0xcc, 0xe6, 0xc4, 0x6a, 0xb6, 0x51, 0x0c, 0x17, 0xb3, 0x63, 0x4a, 0x8a,
0x90, 0x2a, 0x17, 0x73, 0x70, 0x69, 0xae, 0x10, 0x9f, 0x5e, 0x41, 0x92, 0x1e, 0xc2, 0x07, 0x52,
0x3c, 0x70, 0x7e, 0x41, 0x4e, 0xa5, 0x12, 0x83, 0x90, 0x1e, 0x17, 0x1b, 0xc4, 0x70, 0x21, 0x41,
0x90, 0x0c, 0x8a, 0xab, 0xa4, 0xf8, 0x91, 0x85, 0xc0, 0xea, 0x93, 0xd8, 0xc0, 0x41, 0x63, 0x0c,
0x08, 0x00, 0x00, 0xff, 0xff, 0xdc, 0x37, 0x81, 0x99, 0x2a, 0x01, 0x00, 0x00,
}

View File

@@ -1,36 +0,0 @@
syntax = "proto3";
package pb;
// The Add service definition.
service Add {
// Sums two integers.
rpc Sum (SumRequest) returns (SumReply) {}
// Concatenates two strings
rpc Concat (ConcatRequest) returns (ConcatReply) {}
}
// The sum request contains two parameters.
message SumRequest {
int64 a = 1;
int64 b = 2;
}
// The sum response contains the result of the calculation.
message SumReply {
int64 v = 1;
string err = 2;
}
// The Concat request contains two parameters.
message ConcatRequest {
string a = 1;
string b = 2;
}
// The Concat response contains the result of the concatenation.
message ConcatReply {
string v = 1;
string err = 2;
}

View File

@@ -1,14 +0,0 @@
#!/usr/bin/env sh
# Install proto3 from source
# brew install autoconf automake libtool
# git clone https://github.com/google/protobuf
# ./autogen.sh ; ./configure ; make ; make install
#
# Update protoc Go bindings via
# go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
#
# See also
# https://github.com/grpc/grpc-go/tree/master/examples
protoc addsvc.proto --go_out=plugins=grpc:.

View File

@@ -1,164 +0,0 @@
package addsvc
// This file contains the Service definition, and a basic service
// implementation. It also includes service middlewares.
import (
"errors"
"time"
"golang.org/x/net/context"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/metrics"
)
// Service describes a service that adds things together.
type Service interface {
Sum(ctx context.Context, a, b int) (int, error)
Concat(ctx context.Context, a, b string) (string, error)
}
// Business-domain errors like these may be served in two ways: returned
// directly by endpoints, or bundled into the response struct. Both methods can
// be made to work, but errors returned directly by endpoints are counted by
// middlewares that check errors, like circuit breakers.
//
// If you don't want that behavior -- and you probably don't -- then it's better
// to bundle errors into the response struct.
var (
// ErrTwoZeroes is an arbitrary business rule for the Add method.
ErrTwoZeroes = errors.New("can't sum two zeroes")
// ErrIntOverflow protects the Add method. We've decided that this error
// indicates a misbehaving service and should count against e.g. circuit
// breakers. So, we return it directly in endpoints, to illustrate the
// difference. In a real service, this probably wouldn't be the case.
ErrIntOverflow = errors.New("integer overflow")
// ErrMaxSizeExceeded protects the Concat method.
ErrMaxSizeExceeded = errors.New("result exceeds maximum size")
)
// These annoying helper functions are required to translate Go error types to
// and from strings, which is the type we use in our IDLs to represent errors.
// There is special casing to treat empty strings as nil errors.
func str2err(s string) error {
if s == "" {
return nil
}
return errors.New(s)
}
func err2str(err error) string {
if err == nil {
return ""
}
return err.Error()
}
// NewBasicService returns a naïve, stateless implementation of Service.
func NewBasicService() Service {
return basicService{}
}
type basicService struct{}
const (
intMax = 1<<31 - 1
intMin = -(intMax + 1)
maxLen = 102400
)
// Sum implements Service.
func (s basicService) Sum(_ context.Context, a, b int) (int, error) {
if a == 0 && b == 0 {
return 0, ErrTwoZeroes
}
if (b > 0 && a > (intMax-b)) || (b < 0 && a < (intMin-b)) {
return 0, ErrIntOverflow
}
return a + b, nil
}
// Concat implements Service.
func (s basicService) Concat(_ context.Context, a, b string) (string, error) {
if len(a)+len(b) > maxLen {
return "", ErrMaxSizeExceeded
}
return a + b, nil
}
// Middleware describes a service (as opposed to endpoint) middleware.
type Middleware func(Service) Service
// ServiceLoggingMiddleware returns a service middleware that logs the
// parameters and result of each method invocation.
func ServiceLoggingMiddleware(logger log.Logger) Middleware {
return func(next Service) Service {
return serviceLoggingMiddleware{
logger: logger,
next: next,
}
}
}
type serviceLoggingMiddleware struct {
logger log.Logger
next Service
}
func (mw serviceLoggingMiddleware) Sum(ctx context.Context, a, b int) (v int, err error) {
defer func(begin time.Time) {
mw.logger.Log(
"method", "Sum",
"a", a, "b", b, "result", v, "error", err,
"took", time.Since(begin),
)
}(time.Now())
return mw.next.Sum(ctx, a, b)
}
func (mw serviceLoggingMiddleware) Concat(ctx context.Context, a, b string) (v string, err error) {
defer func(begin time.Time) {
mw.logger.Log(
"method", "Concat",
"a", a, "b", b, "result", v, "error", err,
"took", time.Since(begin),
)
}(time.Now())
return mw.next.Concat(ctx, a, b)
}
// ServiceInstrumentingMiddleware returns a service middleware that instruments
// the number of integers summed and characters concatenated over the lifetime of
// the service.
func ServiceInstrumentingMiddleware(ints, chars metrics.Counter) Middleware {
return func(next Service) Service {
return serviceInstrumentingMiddleware{
ints: ints,
chars: chars,
next: next,
}
}
}
type serviceInstrumentingMiddleware struct {
ints metrics.Counter
chars metrics.Counter
next Service
}
func (mw serviceInstrumentingMiddleware) Sum(ctx context.Context, a, b int) (int, error) {
v, err := mw.next.Sum(ctx, a, b)
mw.ints.Add(float64(v))
return v, err
}
func (mw serviceInstrumentingMiddleware) Concat(ctx context.Context, a, b string) (string, error) {
v, err := mw.next.Concat(ctx, a, b)
mw.chars.Add(float64(len(v)))
return v, err
}

View File

@@ -1,14 +0,0 @@
struct SumReply {
1: i64 value
2: string err
}
struct ConcatReply {
1: string value
2: string err
}
service AddService {
SumReply Sum(1: i64 a, 2: i64 b)
ConcatReply Concat(1: string a, 2: string b)
}

View File

@@ -1,5 +0,0 @@
#!/usr/bin/env sh
# See also https://thrift.apache.org/tutorial/go
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

View File

@@ -1,157 +0,0 @@
// Autogenerated by Thrift Compiler (0.9.3)
// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
package main
import (
"flag"
"fmt"
"github.com/apache/thrift/lib/go/thrift"
"github.com/go-kit/kit/examples/addsvc/thrift/gen-go/addsvc"
"math"
"net"
"net/url"
"os"
"strconv"
"strings"
)
func Usage() {
fmt.Fprintln(os.Stderr, "Usage of ", os.Args[0], " [-h host:port] [-u url] [-f[ramed]] function [arg1 [arg2...]]:")
flag.PrintDefaults()
fmt.Fprintln(os.Stderr, "\nFunctions:")
fmt.Fprintln(os.Stderr, " SumReply Sum(i64 a, i64 b)")
fmt.Fprintln(os.Stderr, " ConcatReply Concat(string a, string b)")
fmt.Fprintln(os.Stderr)
os.Exit(0)
}
func main() {
flag.Usage = Usage
var host string
var port int
var protocol string
var urlString string
var framed bool
var useHttp bool
var parsedUrl url.URL
var trans thrift.TTransport
_ = strconv.Atoi
_ = math.Abs
flag.Usage = Usage
flag.StringVar(&host, "h", "localhost", "Specify host and port")
flag.IntVar(&port, "p", 9090, "Specify port")
flag.StringVar(&protocol, "P", "binary", "Specify the protocol (binary, compact, simplejson, json)")
flag.StringVar(&urlString, "u", "", "Specify the url")
flag.BoolVar(&framed, "framed", false, "Use framed transport")
flag.BoolVar(&useHttp, "http", false, "Use http")
flag.Parse()
if len(urlString) > 0 {
parsedUrl, err := url.Parse(urlString)
if err != nil {
fmt.Fprintln(os.Stderr, "Error parsing URL: ", err)
flag.Usage()
}
host = parsedUrl.Host
useHttp = len(parsedUrl.Scheme) <= 0 || parsedUrl.Scheme == "http"
} else if useHttp {
_, err := url.Parse(fmt.Sprint("http://", host, ":", port))
if err != nil {
fmt.Fprintln(os.Stderr, "Error parsing URL: ", err)
flag.Usage()
}
}
cmd := flag.Arg(0)
var err error
if useHttp {
trans, err = thrift.NewTHttpClient(parsedUrl.String())
} else {
portStr := fmt.Sprint(port)
if strings.Contains(host, ":") {
host, portStr, err = net.SplitHostPort(host)
if err != nil {
fmt.Fprintln(os.Stderr, "error with host:", err)
os.Exit(1)
}
}
trans, err = thrift.NewTSocket(net.JoinHostPort(host, portStr))
if err != nil {
fmt.Fprintln(os.Stderr, "error resolving address:", err)
os.Exit(1)
}
if framed {
trans = thrift.NewTFramedTransport(trans)
}
}
if err != nil {
fmt.Fprintln(os.Stderr, "Error creating transport", err)
os.Exit(1)
}
defer trans.Close()
var protocolFactory thrift.TProtocolFactory
switch protocol {
case "compact":
protocolFactory = thrift.NewTCompactProtocolFactory()
break
case "simplejson":
protocolFactory = thrift.NewTSimpleJSONProtocolFactory()
break
case "json":
protocolFactory = thrift.NewTJSONProtocolFactory()
break
case "binary", "":
protocolFactory = thrift.NewTBinaryProtocolFactoryDefault()
break
default:
fmt.Fprintln(os.Stderr, "Invalid protocol specified: ", protocol)
Usage()
os.Exit(1)
}
client := addsvc.NewAddServiceClientFactory(trans, protocolFactory)
if err := trans.Open(); err != nil {
fmt.Fprintln(os.Stderr, "Error opening socket to ", host, ":", port, " ", err)
os.Exit(1)
}
switch cmd {
case "Sum":
if flag.NArg()-1 != 2 {
fmt.Fprintln(os.Stderr, "Sum requires 2 args")
flag.Usage()
}
argvalue0, err6 := (strconv.ParseInt(flag.Arg(1), 10, 64))
if err6 != nil {
Usage()
return
}
value0 := argvalue0
argvalue1, err7 := (strconv.ParseInt(flag.Arg(2), 10, 64))
if err7 != nil {
Usage()
return
}
value1 := argvalue1
fmt.Print(client.Sum(value0, value1))
fmt.Print("\n")
break
case "Concat":
if flag.NArg()-1 != 2 {
fmt.Fprintln(os.Stderr, "Concat requires 2 args")
flag.Usage()
}
argvalue0 := flag.Arg(1)
value0 := argvalue0
argvalue1 := flag.Arg(2)
value1 := argvalue1
fmt.Print(client.Concat(value0, value1))
fmt.Print("\n")
break
case "":
Usage()
break
default:
fmt.Fprintln(os.Stderr, "Invalid function ", cmd)
}
}

View File

@@ -1,807 +0,0 @@
// Autogenerated by Thrift Compiler (0.9.3)
// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
package addsvc
import (
"bytes"
"fmt"
"github.com/apache/thrift/lib/go/thrift"
)
// (needed to ensure safety because of naive import list construction.)
var _ = thrift.ZERO
var _ = fmt.Printf
var _ = bytes.Equal
type AddService interface {
// Parameters:
// - A
// - B
Sum(a int64, b int64) (r *SumReply, err error)
// Parameters:
// - A
// - B
Concat(a string, b string) (r *ConcatReply, err error)
}
type AddServiceClient struct {
Transport thrift.TTransport
ProtocolFactory thrift.TProtocolFactory
InputProtocol thrift.TProtocol
OutputProtocol thrift.TProtocol
SeqId int32
}
func NewAddServiceClientFactory(t thrift.TTransport, f thrift.TProtocolFactory) *AddServiceClient {
return &AddServiceClient{Transport: t,
ProtocolFactory: f,
InputProtocol: f.GetProtocol(t),
OutputProtocol: f.GetProtocol(t),
SeqId: 0,
}
}
func NewAddServiceClientProtocol(t thrift.TTransport, iprot thrift.TProtocol, oprot thrift.TProtocol) *AddServiceClient {
return &AddServiceClient{Transport: t,
ProtocolFactory: nil,
InputProtocol: iprot,
OutputProtocol: oprot,
SeqId: 0,
}
}
// Parameters:
// - A
// - B
func (p *AddServiceClient) Sum(a int64, b int64) (r *SumReply, err error) {
if err = p.sendSum(a, b); err != nil {
return
}
return p.recvSum()
}
func (p *AddServiceClient) sendSum(a int64, b int64) (err error) {
oprot := p.OutputProtocol
if oprot == nil {
oprot = p.ProtocolFactory.GetProtocol(p.Transport)
p.OutputProtocol = oprot
}
p.SeqId++
if err = oprot.WriteMessageBegin("Sum", thrift.CALL, p.SeqId); err != nil {
return
}
args := AddServiceSumArgs{
A: a,
B: b,
}
if err = args.Write(oprot); err != nil {
return
}
if err = oprot.WriteMessageEnd(); err != nil {
return
}
return oprot.Flush()
}
func (p *AddServiceClient) recvSum() (value *SumReply, err error) {
iprot := p.InputProtocol
if iprot == nil {
iprot = p.ProtocolFactory.GetProtocol(p.Transport)
p.InputProtocol = iprot
}
method, mTypeId, seqId, err := iprot.ReadMessageBegin()
if err != nil {
return
}
if method != "Sum" {
err = thrift.NewTApplicationException(thrift.WRONG_METHOD_NAME, "Sum failed: wrong method name")
return
}
if p.SeqId != seqId {
err = thrift.NewTApplicationException(thrift.BAD_SEQUENCE_ID, "Sum failed: out of sequence response")
return
}
if mTypeId == thrift.EXCEPTION {
error0 := thrift.NewTApplicationException(thrift.UNKNOWN_APPLICATION_EXCEPTION, "Unknown Exception")
var error1 error
error1, err = error0.Read(iprot)
if err != nil {
return
}
if err = iprot.ReadMessageEnd(); err != nil {
return
}
err = error1
return
}
if mTypeId != thrift.REPLY {
err = thrift.NewTApplicationException(thrift.INVALID_MESSAGE_TYPE_EXCEPTION, "Sum failed: invalid message type")
return
}
result := AddServiceSumResult{}
if err = result.Read(iprot); err != nil {
return
}
if err = iprot.ReadMessageEnd(); err != nil {
return
}
value = result.GetSuccess()
return
}
// Parameters:
// - A
// - B
func (p *AddServiceClient) Concat(a string, b string) (r *ConcatReply, err error) {
if err = p.sendConcat(a, b); err != nil {
return
}
return p.recvConcat()
}
func (p *AddServiceClient) sendConcat(a string, b string) (err error) {
oprot := p.OutputProtocol
if oprot == nil {
oprot = p.ProtocolFactory.GetProtocol(p.Transport)
p.OutputProtocol = oprot
}
p.SeqId++
if err = oprot.WriteMessageBegin("Concat", thrift.CALL, p.SeqId); err != nil {
return
}
args := AddServiceConcatArgs{
A: a,
B: b,
}
if err = args.Write(oprot); err != nil {
return
}
if err = oprot.WriteMessageEnd(); err != nil {
return
}
return oprot.Flush()
}
func (p *AddServiceClient) recvConcat() (value *ConcatReply, err error) {
iprot := p.InputProtocol
if iprot == nil {
iprot = p.ProtocolFactory.GetProtocol(p.Transport)
p.InputProtocol = iprot
}
method, mTypeId, seqId, err := iprot.ReadMessageBegin()
if err != nil {
return
}
if method != "Concat" {
err = thrift.NewTApplicationException(thrift.WRONG_METHOD_NAME, "Concat failed: wrong method name")
return
}
if p.SeqId != seqId {
err = thrift.NewTApplicationException(thrift.BAD_SEQUENCE_ID, "Concat failed: out of sequence response")
return
}
if mTypeId == thrift.EXCEPTION {
error2 := thrift.NewTApplicationException(thrift.UNKNOWN_APPLICATION_EXCEPTION, "Unknown Exception")
var error3 error
error3, err = error2.Read(iprot)
if err != nil {
return
}
if err = iprot.ReadMessageEnd(); err != nil {
return
}
err = error3
return
}
if mTypeId != thrift.REPLY {
err = thrift.NewTApplicationException(thrift.INVALID_MESSAGE_TYPE_EXCEPTION, "Concat failed: invalid message type")
return
}
result := AddServiceConcatResult{}
if err = result.Read(iprot); err != nil {
return
}
if err = iprot.ReadMessageEnd(); err != nil {
return
}
value = result.GetSuccess()
return
}
type AddServiceProcessor struct {
processorMap map[string]thrift.TProcessorFunction
handler AddService
}
func (p *AddServiceProcessor) AddToProcessorMap(key string, processor thrift.TProcessorFunction) {
p.processorMap[key] = processor
}
func (p *AddServiceProcessor) GetProcessorFunction(key string) (processor thrift.TProcessorFunction, ok bool) {
processor, ok = p.processorMap[key]
return processor, ok
}
func (p *AddServiceProcessor) ProcessorMap() map[string]thrift.TProcessorFunction {
return p.processorMap
}
func NewAddServiceProcessor(handler AddService) *AddServiceProcessor {
self4 := &AddServiceProcessor{handler: handler, processorMap: make(map[string]thrift.TProcessorFunction)}
self4.processorMap["Sum"] = &addServiceProcessorSum{handler: handler}
self4.processorMap["Concat"] = &addServiceProcessorConcat{handler: handler}
return self4
}
func (p *AddServiceProcessor) Process(iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) {
name, _, seqId, err := iprot.ReadMessageBegin()
if err != nil {
return false, err
}
if processor, ok := p.GetProcessorFunction(name); ok {
return processor.Process(seqId, iprot, oprot)
}
iprot.Skip(thrift.STRUCT)
iprot.ReadMessageEnd()
x5 := thrift.NewTApplicationException(thrift.UNKNOWN_METHOD, "Unknown function "+name)
oprot.WriteMessageBegin(name, thrift.EXCEPTION, seqId)
x5.Write(oprot)
oprot.WriteMessageEnd()
oprot.Flush()
return false, x5
}
type addServiceProcessorSum struct {
handler AddService
}
func (p *addServiceProcessorSum) Process(seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) {
args := AddServiceSumArgs{}
if err = args.Read(iprot); err != nil {
iprot.ReadMessageEnd()
x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error())
oprot.WriteMessageBegin("Sum", thrift.EXCEPTION, seqId)
x.Write(oprot)
oprot.WriteMessageEnd()
oprot.Flush()
return false, err
}
iprot.ReadMessageEnd()
result := AddServiceSumResult{}
var retval *SumReply
var err2 error
if retval, err2 = p.handler.Sum(args.A, args.B); err2 != nil {
x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing Sum: "+err2.Error())
oprot.WriteMessageBegin("Sum", thrift.EXCEPTION, seqId)
x.Write(oprot)
oprot.WriteMessageEnd()
oprot.Flush()
return true, err2
} else {
result.Success = retval
}
if err2 = oprot.WriteMessageBegin("Sum", thrift.REPLY, seqId); err2 != nil {
err = err2
}
if err2 = result.Write(oprot); err == nil && err2 != nil {
err = err2
}
if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil {
err = err2
}
if err2 = oprot.Flush(); err == nil && err2 != nil {
err = err2
}
if err != nil {
return
}
return true, err
}
type addServiceProcessorConcat struct {
handler AddService
}
func (p *addServiceProcessorConcat) Process(seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) {
args := AddServiceConcatArgs{}
if err = args.Read(iprot); err != nil {
iprot.ReadMessageEnd()
x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error())
oprot.WriteMessageBegin("Concat", thrift.EXCEPTION, seqId)
x.Write(oprot)
oprot.WriteMessageEnd()
oprot.Flush()
return false, err
}
iprot.ReadMessageEnd()
result := AddServiceConcatResult{}
var retval *ConcatReply
var err2 error
if retval, err2 = p.handler.Concat(args.A, args.B); err2 != nil {
x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing Concat: "+err2.Error())
oprot.WriteMessageBegin("Concat", thrift.EXCEPTION, seqId)
x.Write(oprot)
oprot.WriteMessageEnd()
oprot.Flush()
return true, err2
} else {
result.Success = retval
}
if err2 = oprot.WriteMessageBegin("Concat", thrift.REPLY, seqId); err2 != nil {
err = err2
}
if err2 = result.Write(oprot); err == nil && err2 != nil {
err = err2
}
if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil {
err = err2
}
if err2 = oprot.Flush(); err == nil && err2 != nil {
err = err2
}
if err != nil {
return
}
return true, err
}
// HELPER FUNCTIONS AND STRUCTURES
// Attributes:
// - A
// - B
type AddServiceSumArgs struct {
A int64 `thrift:"a,1" json:"a"`
B int64 `thrift:"b,2" json:"b"`
}
func NewAddServiceSumArgs() *AddServiceSumArgs {
return &AddServiceSumArgs{}
}
func (p *AddServiceSumArgs) GetA() int64 {
return p.A
}
func (p *AddServiceSumArgs) GetB() int64 {
return p.B
}
func (p *AddServiceSumArgs) Read(iprot thrift.TProtocol) error {
if _, err := iprot.ReadStructBegin(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err)
}
for {
_, fieldTypeId, fieldId, err := iprot.ReadFieldBegin()
if err != nil {
return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err)
}
if fieldTypeId == thrift.STOP {
break
}
switch fieldId {
case 1:
if err := p.readField1(iprot); err != nil {
return err
}
case 2:
if err := p.readField2(iprot); err != nil {
return err
}
default:
if err := iprot.Skip(fieldTypeId); err != nil {
return err
}
}
if err := iprot.ReadFieldEnd(); err != nil {
return err
}
}
if err := iprot.ReadStructEnd(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
}
return nil
}
func (p *AddServiceSumArgs) readField1(iprot thrift.TProtocol) error {
if v, err := iprot.ReadI64(); err != nil {
return thrift.PrependError("error reading field 1: ", err)
} else {
p.A = v
}
return nil
}
func (p *AddServiceSumArgs) readField2(iprot thrift.TProtocol) error {
if v, err := iprot.ReadI64(); err != nil {
return thrift.PrependError("error reading field 2: ", err)
} else {
p.B = v
}
return nil
}
func (p *AddServiceSumArgs) Write(oprot thrift.TProtocol) error {
if err := oprot.WriteStructBegin("Sum_args"); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
}
if err := p.writeField1(oprot); err != nil {
return err
}
if err := p.writeField2(oprot); err != nil {
return err
}
if err := oprot.WriteFieldStop(); err != nil {
return thrift.PrependError("write field stop error: ", err)
}
if err := oprot.WriteStructEnd(); err != nil {
return thrift.PrependError("write struct stop error: ", err)
}
return nil
}
func (p *AddServiceSumArgs) writeField1(oprot thrift.TProtocol) (err error) {
if err := oprot.WriteFieldBegin("a", thrift.I64, 1); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:a: ", p), err)
}
if err := oprot.WriteI64(int64(p.A)); err != nil {
return thrift.PrependError(fmt.Sprintf("%T.a (1) field write error: ", p), err)
}
if err := oprot.WriteFieldEnd(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field end error 1:a: ", p), err)
}
return err
}
func (p *AddServiceSumArgs) writeField2(oprot thrift.TProtocol) (err error) {
if err := oprot.WriteFieldBegin("b", thrift.I64, 2); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:b: ", p), err)
}
if err := oprot.WriteI64(int64(p.B)); err != nil {
return thrift.PrependError(fmt.Sprintf("%T.b (2) field write error: ", p), err)
}
if err := oprot.WriteFieldEnd(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field end error 2:b: ", p), err)
}
return err
}
func (p *AddServiceSumArgs) String() string {
if p == nil {
return "<nil>"
}
return fmt.Sprintf("AddServiceSumArgs(%+v)", *p)
}
// Attributes:
// - Success
type AddServiceSumResult struct {
Success *SumReply `thrift:"success,0" json:"success,omitempty"`
}
func NewAddServiceSumResult() *AddServiceSumResult {
return &AddServiceSumResult{}
}
var AddServiceSumResult_Success_DEFAULT *SumReply
func (p *AddServiceSumResult) GetSuccess() *SumReply {
if !p.IsSetSuccess() {
return AddServiceSumResult_Success_DEFAULT
}
return p.Success
}
func (p *AddServiceSumResult) IsSetSuccess() bool {
return p.Success != nil
}
func (p *AddServiceSumResult) Read(iprot thrift.TProtocol) error {
if _, err := iprot.ReadStructBegin(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err)
}
for {
_, fieldTypeId, fieldId, err := iprot.ReadFieldBegin()
if err != nil {
return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err)
}
if fieldTypeId == thrift.STOP {
break
}
switch fieldId {
case 0:
if err := p.readField0(iprot); err != nil {
return err
}
default:
if err := iprot.Skip(fieldTypeId); err != nil {
return err
}
}
if err := iprot.ReadFieldEnd(); err != nil {
return err
}
}
if err := iprot.ReadStructEnd(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
}
return nil
}
func (p *AddServiceSumResult) readField0(iprot thrift.TProtocol) error {
p.Success = &SumReply{}
if err := p.Success.Read(iprot); err != nil {
return thrift.PrependError(fmt.Sprintf("%T error reading struct: ", p.Success), err)
}
return nil
}
func (p *AddServiceSumResult) Write(oprot thrift.TProtocol) error {
if err := oprot.WriteStructBegin("Sum_result"); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
}
if err := p.writeField0(oprot); err != nil {
return err
}
if err := oprot.WriteFieldStop(); err != nil {
return thrift.PrependError("write field stop error: ", err)
}
if err := oprot.WriteStructEnd(); err != nil {
return thrift.PrependError("write struct stop error: ", err)
}
return nil
}
func (p *AddServiceSumResult) writeField0(oprot thrift.TProtocol) (err error) {
if p.IsSetSuccess() {
if err := oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field begin error 0:success: ", p), err)
}
if err := p.Success.Write(oprot); err != nil {
return thrift.PrependError(fmt.Sprintf("%T error writing struct: ", p.Success), err)
}
if err := oprot.WriteFieldEnd(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field end error 0:success: ", p), err)
}
}
return err
}
func (p *AddServiceSumResult) String() string {
if p == nil {
return "<nil>"
}
return fmt.Sprintf("AddServiceSumResult(%+v)", *p)
}
// Attributes:
// - A
// - B
type AddServiceConcatArgs struct {
A string `thrift:"a,1" json:"a"`
B string `thrift:"b,2" json:"b"`
}
func NewAddServiceConcatArgs() *AddServiceConcatArgs {
return &AddServiceConcatArgs{}
}
func (p *AddServiceConcatArgs) GetA() string {
return p.A
}
func (p *AddServiceConcatArgs) GetB() string {
return p.B
}
func (p *AddServiceConcatArgs) Read(iprot thrift.TProtocol) error {
if _, err := iprot.ReadStructBegin(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err)
}
for {
_, fieldTypeId, fieldId, err := iprot.ReadFieldBegin()
if err != nil {
return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err)
}
if fieldTypeId == thrift.STOP {
break
}
switch fieldId {
case 1:
if err := p.readField1(iprot); err != nil {
return err
}
case 2:
if err := p.readField2(iprot); err != nil {
return err
}
default:
if err := iprot.Skip(fieldTypeId); err != nil {
return err
}
}
if err := iprot.ReadFieldEnd(); err != nil {
return err
}
}
if err := iprot.ReadStructEnd(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
}
return nil
}
func (p *AddServiceConcatArgs) readField1(iprot thrift.TProtocol) error {
if v, err := iprot.ReadString(); err != nil {
return thrift.PrependError("error reading field 1: ", err)
} else {
p.A = v
}
return nil
}
func (p *AddServiceConcatArgs) readField2(iprot thrift.TProtocol) error {
if v, err := iprot.ReadString(); err != nil {
return thrift.PrependError("error reading field 2: ", err)
} else {
p.B = v
}
return nil
}
func (p *AddServiceConcatArgs) Write(oprot thrift.TProtocol) error {
if err := oprot.WriteStructBegin("Concat_args"); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
}
if err := p.writeField1(oprot); err != nil {
return err
}
if err := p.writeField2(oprot); err != nil {
return err
}
if err := oprot.WriteFieldStop(); err != nil {
return thrift.PrependError("write field stop error: ", err)
}
if err := oprot.WriteStructEnd(); err != nil {
return thrift.PrependError("write struct stop error: ", err)
}
return nil
}
func (p *AddServiceConcatArgs) writeField1(oprot thrift.TProtocol) (err error) {
if err := oprot.WriteFieldBegin("a", thrift.STRING, 1); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:a: ", p), err)
}
if err := oprot.WriteString(string(p.A)); err != nil {
return thrift.PrependError(fmt.Sprintf("%T.a (1) field write error: ", p), err)
}
if err := oprot.WriteFieldEnd(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field end error 1:a: ", p), err)
}
return err
}
func (p *AddServiceConcatArgs) writeField2(oprot thrift.TProtocol) (err error) {
if err := oprot.WriteFieldBegin("b", thrift.STRING, 2); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:b: ", p), err)
}
if err := oprot.WriteString(string(p.B)); err != nil {
return thrift.PrependError(fmt.Sprintf("%T.b (2) field write error: ", p), err)
}
if err := oprot.WriteFieldEnd(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field end error 2:b: ", p), err)
}
return err
}
func (p *AddServiceConcatArgs) String() string {
if p == nil {
return "<nil>"
}
return fmt.Sprintf("AddServiceConcatArgs(%+v)", *p)
}
// Attributes:
// - Success
type AddServiceConcatResult struct {
Success *ConcatReply `thrift:"success,0" json:"success,omitempty"`
}
func NewAddServiceConcatResult() *AddServiceConcatResult {
return &AddServiceConcatResult{}
}
var AddServiceConcatResult_Success_DEFAULT *ConcatReply
func (p *AddServiceConcatResult) GetSuccess() *ConcatReply {
if !p.IsSetSuccess() {
return AddServiceConcatResult_Success_DEFAULT
}
return p.Success
}
func (p *AddServiceConcatResult) IsSetSuccess() bool {
return p.Success != nil
}
func (p *AddServiceConcatResult) Read(iprot thrift.TProtocol) error {
if _, err := iprot.ReadStructBegin(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err)
}
for {
_, fieldTypeId, fieldId, err := iprot.ReadFieldBegin()
if err != nil {
return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err)
}
if fieldTypeId == thrift.STOP {
break
}
switch fieldId {
case 0:
if err := p.readField0(iprot); err != nil {
return err
}
default:
if err := iprot.Skip(fieldTypeId); err != nil {
return err
}
}
if err := iprot.ReadFieldEnd(); err != nil {
return err
}
}
if err := iprot.ReadStructEnd(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
}
return nil
}
func (p *AddServiceConcatResult) readField0(iprot thrift.TProtocol) error {
p.Success = &ConcatReply{}
if err := p.Success.Read(iprot); err != nil {
return thrift.PrependError(fmt.Sprintf("%T error reading struct: ", p.Success), err)
}
return nil
}
func (p *AddServiceConcatResult) Write(oprot thrift.TProtocol) error {
if err := oprot.WriteStructBegin("Concat_result"); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
}
if err := p.writeField0(oprot); err != nil {
return err
}
if err := oprot.WriteFieldStop(); err != nil {
return thrift.PrependError("write field stop error: ", err)
}
if err := oprot.WriteStructEnd(); err != nil {
return thrift.PrependError("write struct stop error: ", err)
}
return nil
}
func (p *AddServiceConcatResult) writeField0(oprot thrift.TProtocol) (err error) {
if p.IsSetSuccess() {
if err := oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field begin error 0:success: ", p), err)
}
if err := p.Success.Write(oprot); err != nil {
return thrift.PrependError(fmt.Sprintf("%T error writing struct: ", p.Success), err)
}
if err := oprot.WriteFieldEnd(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field end error 0:success: ", p), err)
}
}
return err
}
func (p *AddServiceConcatResult) String() string {
if p == nil {
return "<nil>"
}
return fmt.Sprintf("AddServiceConcatResult(%+v)", *p)
}

View File

@@ -1,18 +0,0 @@
// Autogenerated by Thrift Compiler (0.9.3)
// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
package addsvc
import (
"bytes"
"fmt"
"github.com/apache/thrift/lib/go/thrift"
)
// (needed to ensure safety because of naive import list construction.)
var _ = thrift.ZERO
var _ = fmt.Printf
var _ = bytes.Equal
func init() {
}

View File

@@ -1,269 +0,0 @@
// Autogenerated by Thrift Compiler (0.9.3)
// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
package addsvc
import (
"bytes"
"fmt"
"github.com/apache/thrift/lib/go/thrift"
)
// (needed to ensure safety because of naive import list construction.)
var _ = thrift.ZERO
var _ = fmt.Printf
var _ = bytes.Equal
var GoUnusedProtection__ int
// Attributes:
// - Value
// - Err
type SumReply struct {
Value int64 `thrift:"value,1" json:"value"`
Err string `thrift:"err,2" json:"err"`
}
func NewSumReply() *SumReply {
return &SumReply{}
}
func (p *SumReply) GetValue() int64 {
return p.Value
}
func (p *SumReply) GetErr() string {
return p.Err
}
func (p *SumReply) Read(iprot thrift.TProtocol) error {
if _, err := iprot.ReadStructBegin(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err)
}
for {
_, fieldTypeId, fieldId, err := iprot.ReadFieldBegin()
if err != nil {
return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err)
}
if fieldTypeId == thrift.STOP {
break
}
switch fieldId {
case 1:
if err := p.readField1(iprot); err != nil {
return err
}
case 2:
if err := p.readField2(iprot); err != nil {
return err
}
default:
if err := iprot.Skip(fieldTypeId); err != nil {
return err
}
}
if err := iprot.ReadFieldEnd(); err != nil {
return err
}
}
if err := iprot.ReadStructEnd(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
}
return nil
}
func (p *SumReply) readField1(iprot thrift.TProtocol) error {
if v, err := iprot.ReadI64(); err != nil {
return thrift.PrependError("error reading field 1: ", err)
} else {
p.Value = v
}
return nil
}
func (p *SumReply) readField2(iprot thrift.TProtocol) error {
if v, err := iprot.ReadString(); err != nil {
return thrift.PrependError("error reading field 2: ", err)
} else {
p.Err = v
}
return nil
}
func (p *SumReply) Write(oprot thrift.TProtocol) error {
if err := oprot.WriteStructBegin("SumReply"); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
}
if err := p.writeField1(oprot); err != nil {
return err
}
if err := p.writeField2(oprot); err != nil {
return err
}
if err := oprot.WriteFieldStop(); err != nil {
return thrift.PrependError("write field stop error: ", err)
}
if err := oprot.WriteStructEnd(); err != nil {
return thrift.PrependError("write struct stop error: ", err)
}
return nil
}
func (p *SumReply) writeField1(oprot thrift.TProtocol) (err error) {
if err := oprot.WriteFieldBegin("value", thrift.I64, 1); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:value: ", p), err)
}
if err := oprot.WriteI64(int64(p.Value)); err != nil {
return thrift.PrependError(fmt.Sprintf("%T.value (1) field write error: ", p), err)
}
if err := oprot.WriteFieldEnd(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field end error 1:value: ", p), err)
}
return err
}
func (p *SumReply) writeField2(oprot thrift.TProtocol) (err error) {
if err := oprot.WriteFieldBegin("err", thrift.STRING, 2); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:err: ", p), err)
}
if err := oprot.WriteString(string(p.Err)); err != nil {
return thrift.PrependError(fmt.Sprintf("%T.err (2) field write error: ", p), err)
}
if err := oprot.WriteFieldEnd(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field end error 2:err: ", p), err)
}
return err
}
func (p *SumReply) String() string {
if p == nil {
return "<nil>"
}
return fmt.Sprintf("SumReply(%+v)", *p)
}
// Attributes:
// - Value
// - Err
type ConcatReply struct {
Value string `thrift:"value,1" json:"value"`
Err string `thrift:"err,2" json:"err"`
}
func NewConcatReply() *ConcatReply {
return &ConcatReply{}
}
func (p *ConcatReply) GetValue() string {
return p.Value
}
func (p *ConcatReply) GetErr() string {
return p.Err
}
func (p *ConcatReply) Read(iprot thrift.TProtocol) error {
if _, err := iprot.ReadStructBegin(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err)
}
for {
_, fieldTypeId, fieldId, err := iprot.ReadFieldBegin()
if err != nil {
return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err)
}
if fieldTypeId == thrift.STOP {
break
}
switch fieldId {
case 1:
if err := p.readField1(iprot); err != nil {
return err
}
case 2:
if err := p.readField2(iprot); err != nil {
return err
}
default:
if err := iprot.Skip(fieldTypeId); err != nil {
return err
}
}
if err := iprot.ReadFieldEnd(); err != nil {
return err
}
}
if err := iprot.ReadStructEnd(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
}
return nil
}
func (p *ConcatReply) readField1(iprot thrift.TProtocol) error {
if v, err := iprot.ReadString(); err != nil {
return thrift.PrependError("error reading field 1: ", err)
} else {
p.Value = v
}
return nil
}
func (p *ConcatReply) readField2(iprot thrift.TProtocol) error {
if v, err := iprot.ReadString(); err != nil {
return thrift.PrependError("error reading field 2: ", err)
} else {
p.Err = v
}
return nil
}
func (p *ConcatReply) Write(oprot thrift.TProtocol) error {
if err := oprot.WriteStructBegin("ConcatReply"); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
}
if err := p.writeField1(oprot); err != nil {
return err
}
if err := p.writeField2(oprot); err != nil {
return err
}
if err := oprot.WriteFieldStop(); err != nil {
return thrift.PrependError("write field stop error: ", err)
}
if err := oprot.WriteStructEnd(); err != nil {
return thrift.PrependError("write struct stop error: ", err)
}
return nil
}
func (p *ConcatReply) writeField1(oprot thrift.TProtocol) (err error) {
if err := oprot.WriteFieldBegin("value", thrift.STRING, 1); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:value: ", p), err)
}
if err := oprot.WriteString(string(p.Value)); err != nil {
return thrift.PrependError(fmt.Sprintf("%T.value (1) field write error: ", p), err)
}
if err := oprot.WriteFieldEnd(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field end error 1:value: ", p), err)
}
return err
}
func (p *ConcatReply) writeField2(oprot thrift.TProtocol) (err error) {
if err := oprot.WriteFieldBegin("err", thrift.STRING, 2); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:err: ", p), err)
}
if err := oprot.WriteString(string(p.Err)); err != nil {
return thrift.PrependError(fmt.Sprintf("%T.err (2) field write error: ", p), err)
}
if err := oprot.WriteFieldEnd(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field end error 2:err: ", p), err)
}
return err
}
func (p *ConcatReply) String() string {
if p == nil {
return "<nil>"
}
return fmt.Sprintf("ConcatReply(%+v)", *p)
}

View File

@@ -1,118 +0,0 @@
package addsvc
// This file provides server-side bindings for the gRPC transport.
// It utilizes the transport/grpc.Server.
import (
stdopentracing "github.com/opentracing/opentracing-go"
"golang.org/x/net/context"
"github.com/go-kit/kit/examples/addsvc/pb"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/tracing/opentracing"
grpctransport "github.com/go-kit/kit/transport/grpc"
)
// MakeGRPCServer makes a set of endpoints available as a gRPC AddServer.
func MakeGRPCServer(ctx context.Context, endpoints Endpoints, tracer stdopentracing.Tracer, logger log.Logger) pb.AddServer {
options := []grpctransport.ServerOption{
grpctransport.ServerErrorLogger(logger),
}
return &grpcServer{
sum: grpctransport.NewServer(
ctx,
endpoints.SumEndpoint,
DecodeGRPCSumRequest,
EncodeGRPCSumResponse,
append(options, grpctransport.ServerBefore(opentracing.FromGRPCRequest(tracer, "Sum", logger)))...,
),
concat: grpctransport.NewServer(
ctx,
endpoints.ConcatEndpoint,
DecodeGRPCConcatRequest,
EncodeGRPCConcatResponse,
append(options, grpctransport.ServerBefore(opentracing.FromGRPCRequest(tracer, "Concat", logger)))...,
),
}
}
type grpcServer struct {
sum grpctransport.Handler
concat grpctransport.Handler
}
func (s *grpcServer) Sum(ctx context.Context, req *pb.SumRequest) (*pb.SumReply, error) {
_, rep, err := s.sum.ServeGRPC(ctx, req)
if err != nil {
return nil, err
}
return rep.(*pb.SumReply), nil
}
func (s *grpcServer) Concat(ctx context.Context, req *pb.ConcatRequest) (*pb.ConcatReply, error) {
_, rep, err := s.concat.ServeGRPC(ctx, req)
if err != nil {
return nil, err
}
return rep.(*pb.ConcatReply), nil
}
// DecodeGRPCSumRequest is a transport/grpc.DecodeRequestFunc that converts a
// gRPC sum request to a user-domain sum request. Primarily useful in a server.
func DecodeGRPCSumRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*pb.SumRequest)
return sumRequest{A: int(req.A), B: int(req.B)}, nil
}
// DecodeGRPCConcatRequest is a transport/grpc.DecodeRequestFunc that converts a
// gRPC concat request to a user-domain concat request. Primarily useful in a
// server.
func DecodeGRPCConcatRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*pb.ConcatRequest)
return concatRequest{A: req.A, B: req.B}, nil
}
// DecodeGRPCSumResponse is a transport/grpc.DecodeResponseFunc that converts a
// gRPC sum reply to a user-domain sum response. Primarily useful in a client.
func DecodeGRPCSumResponse(_ context.Context, grpcReply interface{}) (interface{}, error) {
reply := grpcReply.(*pb.SumReply)
return sumResponse{V: int(reply.V), Err: str2err(reply.Err)}, nil
}
// DecodeGRPCConcatResponse is a transport/grpc.DecodeResponseFunc that converts
// a gRPC concat reply to a user-domain concat response. Primarily useful in a
// client.
func DecodeGRPCConcatResponse(_ context.Context, grpcReply interface{}) (interface{}, error) {
reply := grpcReply.(*pb.ConcatReply)
return concatResponse{V: reply.V, Err: str2err(reply.Err)}, nil
}
// EncodeGRPCSumResponse is a transport/grpc.EncodeResponseFunc that converts a
// user-domain sum response to a gRPC sum reply. Primarily useful in a server.
func EncodeGRPCSumResponse(_ context.Context, response interface{}) (interface{}, error) {
resp := response.(sumResponse)
return &pb.SumReply{V: int64(resp.V), Err: err2str(resp.Err)}, nil
}
// EncodeGRPCConcatResponse is a transport/grpc.EncodeResponseFunc that converts
// a user-domain concat response to a gRPC concat reply. Primarily useful in a
// server.
func EncodeGRPCConcatResponse(_ context.Context, response interface{}) (interface{}, error) {
resp := response.(concatResponse)
return &pb.ConcatReply{V: resp.V, Err: err2str(resp.Err)}, nil
}
// EncodeGRPCSumRequest is a transport/grpc.EncodeRequestFunc that converts a
// user-domain sum request to a gRPC sum request. Primarily useful in a client.
func EncodeGRPCSumRequest(_ context.Context, request interface{}) (interface{}, error) {
req := request.(sumRequest)
return &pb.SumRequest{A: int64(req.A), B: int64(req.B)}, nil
}
// EncodeGRPCConcatRequest is a transport/grpc.EncodeRequestFunc that converts a
// user-domain concat request to a gRPC concat request. Primarily useful in a
// client.
func EncodeGRPCConcatRequest(_ context.Context, request interface{}) (interface{}, error) {
req := request.(concatRequest)
return &pb.ConcatRequest{A: req.A, B: req.B}, nil
}

View File

@@ -1,141 +0,0 @@
package addsvc
// This file provides server-side bindings for the HTTP transport.
// It utilizes the transport/http.Server.
import (
"bytes"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
stdopentracing "github.com/opentracing/opentracing-go"
"golang.org/x/net/context"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/tracing/opentracing"
httptransport "github.com/go-kit/kit/transport/http"
)
// MakeHTTPHandler returns a handler that makes a set of endpoints available
// on predefined paths.
func MakeHTTPHandler(ctx context.Context, endpoints Endpoints, tracer stdopentracing.Tracer, logger log.Logger) http.Handler {
options := []httptransport.ServerOption{
httptransport.ServerErrorEncoder(errorEncoder),
httptransport.ServerErrorLogger(logger),
}
m := http.NewServeMux()
m.Handle("/sum", httptransport.NewServer(
ctx,
endpoints.SumEndpoint,
DecodeHTTPSumRequest,
EncodeHTTPGenericResponse,
append(options, httptransport.ServerBefore(opentracing.FromHTTPRequest(tracer, "Sum", logger)))...,
))
m.Handle("/concat", httptransport.NewServer(
ctx,
endpoints.ConcatEndpoint,
DecodeHTTPConcatRequest,
EncodeHTTPGenericResponse,
append(options, httptransport.ServerBefore(opentracing.FromHTTPRequest(tracer, "Concat", logger)))...,
))
return m
}
func errorEncoder(_ context.Context, err error, w http.ResponseWriter) {
code := http.StatusInternalServerError
msg := err.Error()
if e, ok := err.(httptransport.Error); ok {
msg = e.Err.Error()
switch e.Domain {
case httptransport.DomainDecode:
code = http.StatusBadRequest
case httptransport.DomainDo:
switch e.Err {
case ErrTwoZeroes, ErrMaxSizeExceeded, ErrIntOverflow:
code = http.StatusBadRequest
}
}
}
w.WriteHeader(code)
json.NewEncoder(w).Encode(errorWrapper{Error: msg})
}
func errorDecoder(r *http.Response) error {
var w errorWrapper
if err := json.NewDecoder(r.Body).Decode(&w); err != nil {
return err
}
return errors.New(w.Error)
}
type errorWrapper struct {
Error string `json:"error"`
}
// DecodeHTTPSumRequest is a transport/http.DecodeRequestFunc that decodes a
// JSON-encoded sum request from the HTTP request body. Primarily useful in a
// server.
func DecodeHTTPSumRequest(_ context.Context, r *http.Request) (interface{}, error) {
var req sumRequest
err := json.NewDecoder(r.Body).Decode(&req)
return req, err
}
// DecodeHTTPConcatRequest is a transport/http.DecodeRequestFunc that decodes a
// JSON-encoded concat request from the HTTP request body. Primarily useful in a
// server.
func DecodeHTTPConcatRequest(_ context.Context, r *http.Request) (interface{}, error) {
var req concatRequest
err := json.NewDecoder(r.Body).Decode(&req)
return req, err
}
// DecodeHTTPSumResponse is a transport/http.DecodeResponseFunc that decodes a
// JSON-encoded sum response from the HTTP response body. If the response has a
// non-200 status code, we will interpret that as an error and attempt to decode
// the specific error message from the response body. Primarily useful in a
// client.
func DecodeHTTPSumResponse(_ context.Context, r *http.Response) (interface{}, error) {
if r.StatusCode != http.StatusOK {
return nil, errorDecoder(r)
}
var resp sumResponse
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}
// DecodeHTTPConcatResponse is a transport/http.DecodeResponseFunc that decodes
// a JSON-encoded concat response from the HTTP response body. If the response
// has a non-200 status code, we will interpret that as an error and attempt to
// decode the specific error message from the response body. Primarily useful in
// a client.
func DecodeHTTPConcatResponse(_ context.Context, r *http.Response) (interface{}, error) {
if r.StatusCode != http.StatusOK {
return nil, errorDecoder(r)
}
var resp concatResponse
err := json.NewDecoder(r.Body).Decode(&resp)
return resp, err
}
// EncodeHTTPGenericRequest is a transport/http.EncodeRequestFunc that
// JSON-encodes any request to the request body. Primarily useful in a client.
func EncodeHTTPGenericRequest(_ context.Context, r *http.Request, request interface{}) error {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(request); err != nil {
return err
}
r.Body = ioutil.NopCloser(&buf)
return nil
}
// EncodeHTTPGenericResponse is a transport/http.EncodeResponseFunc that encodes
// the response as JSON to the response writer. Primarily useful in a server.
func EncodeHTTPGenericResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
return json.NewEncoder(w).Encode(response)
}

View File

@@ -1,73 +0,0 @@
package addsvc
// This file provides server-side bindings for the Thrift transport.
//
// This file also provides endpoint constructors that utilize a Thrift client,
// for use in client packages, because package transport/thrift doesn't exist
// yet. See https://github.com/go-kit/kit/issues/184.
import (
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
thriftadd "github.com/go-kit/kit/examples/addsvc/thrift/gen-go/addsvc"
)
// MakeThriftHandler makes a set of endpoints available as a Thrift service.
func MakeThriftHandler(ctx context.Context, e Endpoints) thriftadd.AddService {
return &thriftServer{
ctx: ctx,
sum: e.SumEndpoint,
concat: e.ConcatEndpoint,
}
}
type thriftServer struct {
ctx context.Context
sum endpoint.Endpoint
concat endpoint.Endpoint
}
func (s *thriftServer) Sum(a int64, b int64) (*thriftadd.SumReply, error) {
request := sumRequest{A: int(a), B: int(b)}
response, err := s.sum(s.ctx, request)
if err != nil {
return nil, err
}
resp := response.(sumResponse)
return &thriftadd.SumReply{Value: int64(resp.V), Err: err2str(resp.Err)}, nil
}
func (s *thriftServer) Concat(a string, b string) (*thriftadd.ConcatReply, error) {
request := concatRequest{A: a, B: b}
response, err := s.concat(s.ctx, request)
if err != nil {
return nil, err
}
resp := response.(concatResponse)
return &thriftadd.ConcatReply{Value: resp.V, Err: err2str(resp.Err)}, nil
}
// MakeThriftSumEndpoint returns an endpoint that invokes the passed Thrift client.
// Useful only in clients, and only until a proper transport/thrift.Client exists.
func MakeThriftSumEndpoint(client *thriftadd.AddServiceClient) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(sumRequest)
reply, err := client.Sum(int64(req.A), int64(req.B))
if err == ErrIntOverflow {
return nil, err // special case; see comment on ErrIntOverflow
}
return sumResponse{V: int(reply.Value), Err: err}, nil
}
}
// MakeThriftConcatEndpoint returns an endpoint that invokes the passed Thrift
// client. Useful only in clients, and only until a proper
// transport/thrift.Client exists.
func MakeThriftConcatEndpoint(client *thriftadd.AddServiceClient) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(concatRequest)
reply, err := client.Concat(req.A, req.B)
return concatResponse{V: reply.Value, Err: err}, nil
}
}

View File

@@ -1,282 +0,0 @@
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/gorilla/mux"
"github.com/hashicorp/consul/api"
stdopentracing "github.com/opentracing/opentracing-go"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/examples/addsvc"
addsvcgrpcclient "github.com/go-kit/kit/examples/addsvc/client/grpc"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/sd"
consulsd "github.com/go-kit/kit/sd/consul"
"github.com/go-kit/kit/sd/lb"
httptransport "github.com/go-kit/kit/transport/http"
"google.golang.org/grpc"
)
func main() {
var (
httpAddr = flag.String("http.addr", ":8000", "Address for HTTP (JSON) server")
consulAddr = flag.String("consul.addr", "", "Consul agent address")
retryMax = flag.Int("retry.max", 3, "per-request retries to different instances")
retryTimeout = flag.Duration("retry.timeout", 500*time.Millisecond, "per-request timeout, including retries")
)
flag.Parse()
// Logging domain.
var logger log.Logger
{
logger = log.NewLogfmtLogger(os.Stderr)
logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
logger = log.NewContext(logger).With("caller", log.DefaultCaller)
}
// Service discovery domain. In this example we use Consul.
var client consulsd.Client
{
consulConfig := api.DefaultConfig()
if len(*consulAddr) > 0 {
consulConfig.Address = *consulAddr
}
consulClient, err := api.NewClient(consulConfig)
if err != nil {
logger.Log("err", err)
os.Exit(1)
}
client = consulsd.NewClient(consulClient)
}
// Transport domain.
tracer := stdopentracing.GlobalTracer() // no-op
ctx := context.Background()
r := mux.NewRouter()
// Now we begin installing the routes. Each route corresponds to a single
// method: sum, concat, uppercase, and count.
// addsvc routes.
{
// Each method gets constructed with a factory. Factories take an
// instance string, and return a specific endpoint. In the factory we
// dial the instance string we get from Consul, and then leverage an
// addsvc client package to construct a complete service. We can then
// leverage the addsvc.Make{Sum,Concat}Endpoint constructors to convert
// the complete service to specific endpoint.
var (
tags = []string{}
passingOnly = true
endpoints = addsvc.Endpoints{}
)
{
factory := addsvcFactory(addsvc.MakeSumEndpoint, tracer, logger)
subscriber := consulsd.NewSubscriber(client, factory, logger, "addsvc", tags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(*retryMax, *retryTimeout, balancer)
endpoints.SumEndpoint = retry
}
{
factory := addsvcFactory(addsvc.MakeConcatEndpoint, tracer, logger)
subscriber := consulsd.NewSubscriber(client, factory, logger, "addsvc", tags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(*retryMax, *retryTimeout, balancer)
endpoints.ConcatEndpoint = retry
}
// Here we leverage the fact that addsvc comes with a constructor for an
// HTTP handler, and just install it under a particular path prefix in
// our router.
r.PathPrefix("addsvc/").Handler(addsvc.MakeHTTPHandler(ctx, endpoints, tracer, logger))
}
// stringsvc routes.
{
// addsvc had lots of nice importable Go packages we could leverage.
// With stringsvc we are not so fortunate, it just has some endpoints
// that we assume will exist. So we have to write that logic here. This
// is by design, so you can see two totally different methods of
// proxying to a remote service.
var (
tags = []string{}
passingOnly = true
uppercase endpoint.Endpoint
count endpoint.Endpoint
)
{
factory := stringsvcFactory(ctx, "GET", "/uppercase")
subscriber := consulsd.NewSubscriber(client, factory, logger, "stringsvc", tags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(*retryMax, *retryTimeout, balancer)
uppercase = retry
}
{
factory := stringsvcFactory(ctx, "GET", "/count")
subscriber := consulsd.NewSubscriber(client, factory, logger, "stringsvc", tags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(*retryMax, *retryTimeout, balancer)
count = retry
}
// We can use the transport/http.Server to act as our handler, all we
// have to do provide it with the encode and decode functions for our
// stringsvc methods.
r.Handle("/stringsvc/uppercase", httptransport.NewServer(ctx, uppercase, decodeUppercaseRequest, encodeJSONResponse))
r.Handle("/stringsvc/count", httptransport.NewServer(ctx, count, decodeCountRequest, encodeJSONResponse))
}
// Interrupt handler.
errc := make(chan error)
go func() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
errc <- fmt.Errorf("%s", <-c)
}()
// HTTP transport.
go func() {
logger.Log("transport", "HTTP", "addr", *httpAddr)
errc <- http.ListenAndServe(*httpAddr, r)
}()
// Run!
logger.Log("exit", <-errc)
}
func addsvcFactory(makeEndpoint func(addsvc.Service) endpoint.Endpoint, tracer stdopentracing.Tracer, logger log.Logger) sd.Factory {
return func(instance string) (endpoint.Endpoint, io.Closer, error) {
// We could just as easily use the HTTP or Thrift client package to make
// the connection to addsvc. We've chosen gRPC arbitrarily. Note that
// the transport is an implementation detail: it doesn't leak out of
// this function. Nice!
conn, err := grpc.Dial(instance, grpc.WithInsecure())
if err != nil {
return nil, nil, err
}
service := addsvcgrpcclient.New(conn, tracer, logger)
endpoint := makeEndpoint(service)
// Notice that the addsvc gRPC client converts the connection to a
// complete addsvc, and we just throw away everything except the method
// we're interested in. A smarter factory would mux multiple methods
// over the same connection. But that would require more work to manage
// the returned io.Closer, e.g. reference counting. Since this is for
// the purposes of demonstration, we'll just keep it simple.
return endpoint, conn, nil
}
}
func stringsvcFactory(ctx context.Context, method, path string) sd.Factory {
return func(instance string) (endpoint.Endpoint, io.Closer, error) {
if !strings.HasPrefix(instance, "http") {
instance = "http://" + instance
}
tgt, err := url.Parse(instance)
if err != nil {
return nil, nil, err
}
tgt.Path = path
// Since stringsvc doesn't have any kind of package we can import, or
// any formal spec, we are forced to just assert where the endpoints
// live, and write our own code to encode and decode requests and
// responses. Ideally, if you write the service, you will want to
// provide stronger guarantees to your clients.
var (
enc httptransport.EncodeRequestFunc
dec httptransport.DecodeResponseFunc
)
switch path {
case "/uppercase":
enc, dec = encodeJSONRequest, decodeUppercaseResponse
case "/count":
enc, dec = encodeJSONRequest, decodeCountResponse
default:
return nil, nil, fmt.Errorf("unknown stringsvc path %q", path)
}
return httptransport.NewClient(method, tgt, enc, dec).Endpoint(), nil, nil
}
}
func encodeJSONRequest(_ context.Context, req *http.Request, request interface{}) error {
// Both uppercase and count requests are encoded in the same way:
// simple JSON serialization to the request body.
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(request); err != nil {
return err
}
req.Body = ioutil.NopCloser(&buf)
return nil
}
func encodeJSONResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
return json.NewEncoder(w).Encode(response)
}
// I've just copied these functions from stringsvc3/transport.go, inlining the
// struct definitions.
func decodeUppercaseResponse(ctx context.Context, resp *http.Response) (interface{}, error) {
var response struct {
V string `json:"v"`
Err string `json:"err,omitempty"`
}
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return nil, err
}
return response, nil
}
func decodeCountResponse(ctx context.Context, resp *http.Response) (interface{}, error) {
var response struct {
V int `json:"v"`
}
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return nil, err
}
return response, nil
}
func decodeUppercaseRequest(ctx context.Context, req *http.Request) (interface{}, error) {
var request struct {
S string `json:"s"`
}
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
return nil, err
}
return request, nil
}
func decodeCountRequest(ctx context.Context, req *http.Request) (interface{}, error) {
var request struct {
S string `json:"s"`
}
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
return nil, err
}
return request, nil
}

View File

@@ -1,4 +0,0 @@
# profilesvc
This example demonstrates how to use Go kit to implement a REST-y HTTP service.
It leverages the excellent [gorilla mux package](https://github.com/gorilla/mux) for routing.

View File

@@ -1,120 +0,0 @@
// Package client provides a profilesvc client based on a predefined Consul
// service name and relevant tags. Users must only provide the address of a
// Consul server.
package client
import (
"io"
"time"
consulapi "github.com/hashicorp/consul/api"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/examples/profilesvc"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/sd"
"github.com/go-kit/kit/sd/consul"
"github.com/go-kit/kit/sd/lb"
)
// New returns a service that's load-balanced over instances of profilesvc found
// in the provided Consul server. The mechanism of looking up profilesvc
// instances in Consul is hard-coded into the client.
func New(consulAddr string, logger log.Logger) (profilesvc.Service, error) {
apiclient, err := consulapi.NewClient(&consulapi.Config{
Address: consulAddr,
})
if err != nil {
return nil, err
}
// As the implementer of profilesvc, we declare and enforce these
// parameters for all of the profilesvc consumers.
var (
consulService = "profilesvc"
consulTags = []string{"prod"}
passingOnly = true
retryMax = 3
retryTimeout = 500 * time.Millisecond
)
var (
sdclient = consul.NewClient(apiclient)
endpoints profilesvc.Endpoints
)
{
factory := factoryFor(profilesvc.MakePostProfileEndpoint)
subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(retryMax, retryTimeout, balancer)
endpoints.PostProfileEndpoint = retry
}
{
factory := factoryFor(profilesvc.MakeGetProfileEndpoint)
subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(retryMax, retryTimeout, balancer)
endpoints.GetProfileEndpoint = retry
}
{
factory := factoryFor(profilesvc.MakePutProfileEndpoint)
subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(retryMax, retryTimeout, balancer)
endpoints.PutProfileEndpoint = retry
}
{
factory := factoryFor(profilesvc.MakePatchProfileEndpoint)
subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(retryMax, retryTimeout, balancer)
endpoints.PatchProfileEndpoint = retry
}
{
factory := factoryFor(profilesvc.MakeDeleteProfileEndpoint)
subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(retryMax, retryTimeout, balancer)
endpoints.DeleteProfileEndpoint = retry
}
{
factory := factoryFor(profilesvc.MakeGetAddressesEndpoint)
subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(retryMax, retryTimeout, balancer)
endpoints.GetAddressesEndpoint = retry
}
{
factory := factoryFor(profilesvc.MakeGetAddressEndpoint)
subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(retryMax, retryTimeout, balancer)
endpoints.GetAddressEndpoint = retry
}
{
factory := factoryFor(profilesvc.MakePostAddressEndpoint)
subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(retryMax, retryTimeout, balancer)
endpoints.PostAddressEndpoint = retry
}
{
factory := factoryFor(profilesvc.MakeDeleteAddressEndpoint)
subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(retryMax, retryTimeout, balancer)
endpoints.DeleteAddressEndpoint = retry
}
return endpoints, nil
}
func factoryFor(makeEndpoint func(profilesvc.Service) endpoint.Endpoint) sd.Factory {
return func(instance string) (endpoint.Endpoint, io.Closer, error) {
service, err := profilesvc.MakeClientEndpoints(instance)
if err != nil {
return nil, nil, err
}
return makeEndpoint(service), nil, nil
}
}

View File

@@ -1,59 +0,0 @@
package main
import (
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"golang.org/x/net/context"
"github.com/go-kit/kit/examples/profilesvc"
"github.com/go-kit/kit/log"
)
func main() {
var (
httpAddr = flag.String("http.addr", ":8080", "HTTP listen address")
)
flag.Parse()
var logger log.Logger
{
logger = log.NewLogfmtLogger(os.Stderr)
logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
logger = log.NewContext(logger).With("caller", log.DefaultCaller)
}
var ctx context.Context
{
ctx = context.Background()
}
var s profilesvc.Service
{
s = profilesvc.NewInmemService()
s = profilesvc.LoggingMiddleware(logger)(s)
}
var h http.Handler
{
h = profilesvc.MakeHTTPHandler(ctx, s, log.NewContext(logger).With("component", "HTTP"))
}
errs := make(chan error)
go func() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
errs <- fmt.Errorf("%s", <-c)
}()
go func() {
logger.Log("transport", "HTTP", "addr", *httpAddr)
errs <- http.ListenAndServe(*httpAddr, h)
}()
logger.Log("exit", <-errs)
}

View File

@@ -1,388 +0,0 @@
package profilesvc
import (
"net/url"
"strings"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
httptransport "github.com/go-kit/kit/transport/http"
)
// Endpoints collects all of the endpoints that compose a profile service. It's
// meant to be used as a helper struct, to collect all of the endpoints into a
// single parameter.
//
// In a server, it's useful for functions that need to operate on a per-endpoint
// basis. For example, you might pass an Endpoints to a function that produces
// an http.Handler, with each method (endpoint) wired up to a specific path. (It
// is probably a mistake in design to invoke the Service methods on the
// Endpoints struct in a server.)
//
// In a client, it's useful to collect individually constructed endpoints into a
// single type that implements the Service interface. For example, you might
// construct individual endpoints using transport/http.NewClient, combine them
// into an Endpoints, and return it to the caller as a Service.
type Endpoints struct {
PostProfileEndpoint endpoint.Endpoint
GetProfileEndpoint endpoint.Endpoint
PutProfileEndpoint endpoint.Endpoint
PatchProfileEndpoint endpoint.Endpoint
DeleteProfileEndpoint endpoint.Endpoint
GetAddressesEndpoint endpoint.Endpoint
GetAddressEndpoint endpoint.Endpoint
PostAddressEndpoint endpoint.Endpoint
DeleteAddressEndpoint endpoint.Endpoint
}
// MakeServerEndpoints returns an Endpoints struct where each endpoint invokes
// the corresponding method on the provided service. Useful in a profilesvc
// server.
func MakeServerEndpoints(s Service) Endpoints {
return Endpoints{
PostProfileEndpoint: MakePostProfileEndpoint(s),
GetProfileEndpoint: MakeGetProfileEndpoint(s),
PutProfileEndpoint: MakePutProfileEndpoint(s),
PatchProfileEndpoint: MakePatchProfileEndpoint(s),
DeleteProfileEndpoint: MakeDeleteProfileEndpoint(s),
GetAddressesEndpoint: MakeGetAddressesEndpoint(s),
GetAddressEndpoint: MakeGetAddressEndpoint(s),
PostAddressEndpoint: MakePostAddressEndpoint(s),
DeleteAddressEndpoint: MakeDeleteAddressEndpoint(s),
}
}
// MakeClientEndpoints returns an Endpoints struct where each endpoint invokes
// the corresponding method on the remote instance, via a transport/http.Client.
// Useful in a profilesvc client.
func MakeClientEndpoints(instance string) (Endpoints, error) {
if !strings.HasPrefix(instance, "http") {
instance = "http://" + instance
}
tgt, err := url.Parse(instance)
if err != nil {
return Endpoints{}, err
}
tgt.Path = ""
options := []httptransport.ClientOption{}
// Note that the request encoders need to modify the request URL, changing
// the path and method. That's fine: we simply need to provide specific
// encoders for each endpoint.
return Endpoints{
PostProfileEndpoint: httptransport.NewClient("POST", tgt, encodePostProfileRequest, decodePostProfileResponse, options...).Endpoint(),
GetProfileEndpoint: httptransport.NewClient("GET", tgt, encodeGetProfileRequest, decodeGetProfileResponse, options...).Endpoint(),
PutProfileEndpoint: httptransport.NewClient("PUT", tgt, encodePutProfileRequest, decodePutProfileResponse, options...).Endpoint(),
PatchProfileEndpoint: httptransport.NewClient("PATCH", tgt, encodePatchProfileRequest, decodePatchProfileResponse, options...).Endpoint(),
DeleteProfileEndpoint: httptransport.NewClient("DELETE", tgt, encodeDeleteProfileRequest, decodeDeleteProfileResponse, options...).Endpoint(),
GetAddressesEndpoint: httptransport.NewClient("GET", tgt, encodeGetAddressesRequest, decodeGetAddressesResponse, options...).Endpoint(),
GetAddressEndpoint: httptransport.NewClient("GET", tgt, encodeGetAddressRequest, decodeGetAddressResponse, options...).Endpoint(),
PostAddressEndpoint: httptransport.NewClient("POST", tgt, encodePostAddressRequest, decodePostAddressResponse, options...).Endpoint(),
DeleteAddressEndpoint: httptransport.NewClient("DELETE", tgt, encodeDeleteAddressRequest, decodeDeleteAddressResponse, options...).Endpoint(),
}, nil
}
// PostProfile implements Service. Primarily useful in a client.
func (e Endpoints) PostProfile(ctx context.Context, p Profile) error {
request := postProfileRequest{Profile: p}
response, err := e.PostProfileEndpoint(ctx, request)
if err != nil {
return err
}
resp := response.(postProfileResponse)
return resp.Err
}
// GetProfile implements Service. Primarily useful in a client.
func (e Endpoints) GetProfile(ctx context.Context, id string) (Profile, error) {
request := getProfileRequest{ID: id}
response, err := e.GetProfileEndpoint(ctx, request)
if err != nil {
return Profile{}, err
}
resp := response.(getProfileResponse)
return resp.Profile, resp.Err
}
// PutProfile implements Service. Primarily useful in a client.
func (e Endpoints) PutProfile(ctx context.Context, id string, p Profile) error {
request := putProfileRequest{ID: id, Profile: p}
response, err := e.PutProfileEndpoint(ctx, request)
if err != nil {
return err
}
resp := response.(putProfileResponse)
return resp.Err
}
// PatchProfile implements Service. Primarily useful in a client.
func (e Endpoints) PatchProfile(ctx context.Context, id string, p Profile) error {
request := patchProfileRequest{ID: id, Profile: p}
response, err := e.PatchProfileEndpoint(ctx, request)
if err != nil {
return err
}
resp := response.(patchProfileResponse)
return resp.Err
}
// DeleteProfile implements Service. Primarily useful in a client.
func (e Endpoints) DeleteProfile(ctx context.Context, id string) error {
request := deleteProfileRequest{ID: id}
response, err := e.DeleteProfileEndpoint(ctx, request)
if err != nil {
return err
}
resp := response.(deleteProfileResponse)
return resp.Err
}
// GetAddresses implements Service. Primarily useful in a client.
func (e Endpoints) GetAddresses(ctx context.Context, profileID string) ([]Address, error) {
request := getAddressesRequest{ProfileID: profileID}
response, err := e.GetAddressesEndpoint(ctx, request)
if err != nil {
return nil, err
}
resp := response.(getAddressesResponse)
return resp.Addresses, resp.Err
}
// GetAddress implements Service. Primarily useful in a client.
func (e Endpoints) GetAddress(ctx context.Context, profileID string, addressID string) (Address, error) {
request := getAddressRequest{ProfileID: profileID, AddressID: addressID}
response, err := e.GetAddressEndpoint(ctx, request)
if err != nil {
return Address{}, err
}
resp := response.(getAddressResponse)
return resp.Address, resp.Err
}
// PostAddress implements Service. Primarily useful in a client.
func (e Endpoints) PostAddress(ctx context.Context, profileID string, a Address) error {
request := postAddressRequest{ProfileID: profileID, Address: a}
response, err := e.PostAddressEndpoint(ctx, request)
if err != nil {
return err
}
resp := response.(postAddressResponse)
return resp.Err
}
// DeleteAddress implements Service. Primarily useful in a client.
func (e Endpoints) DeleteAddress(ctx context.Context, profileID string, addressID string) error {
request := deleteAddressRequest{ProfileID: profileID, AddressID: addressID}
response, err := e.DeleteAddressEndpoint(ctx, request)
if err != nil {
return err
}
resp := response.(deleteAddressResponse)
return resp.Err
}
// MakePostProfileEndpoint returns an endpoint via the passed service.
// Primarily useful in a server.
func MakePostProfileEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(postProfileRequest)
e := s.PostProfile(ctx, req.Profile)
return postProfileResponse{Err: e}, nil
}
}
// MakeGetProfileEndpoint returns an endpoint via the passed service.
// Primarily useful in a server.
func MakeGetProfileEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(getProfileRequest)
p, e := s.GetProfile(ctx, req.ID)
return getProfileResponse{Profile: p, Err: e}, nil
}
}
// MakePutProfileEndpoint returns an endpoint via the passed service.
// Primarily useful in a server.
func MakePutProfileEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(putProfileRequest)
e := s.PutProfile(ctx, req.ID, req.Profile)
return putProfileResponse{Err: e}, nil
}
}
// MakePatchProfileEndpoint returns an endpoint via the passed service.
// Primarily useful in a server.
func MakePatchProfileEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(patchProfileRequest)
e := s.PatchProfile(ctx, req.ID, req.Profile)
return patchProfileResponse{Err: e}, nil
}
}
// MakeDeleteProfileEndpoint returns an endpoint via the passed service.
// Primarily useful in a server.
func MakeDeleteProfileEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(deleteProfileRequest)
e := s.DeleteProfile(ctx, req.ID)
return deleteProfileResponse{Err: e}, nil
}
}
// MakeGetAddressesEndpoint returns an endpoint via the passed service.
// Primarily useful in a server.
func MakeGetAddressesEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(getAddressesRequest)
a, e := s.GetAddresses(ctx, req.ProfileID)
return getAddressesResponse{Addresses: a, Err: e}, nil
}
}
// MakeGetAddressEndpoint returns an endpoint via the passed service.
// Primarily useful in a server.
func MakeGetAddressEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(getAddressRequest)
a, e := s.GetAddress(ctx, req.ProfileID, req.AddressID)
return getAddressResponse{Address: a, Err: e}, nil
}
}
// MakePostAddressEndpoint returns an endpoint via the passed service.
// Primarily useful in a server.
func MakePostAddressEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(postAddressRequest)
e := s.PostAddress(ctx, req.ProfileID, req.Address)
return postAddressResponse{Err: e}, nil
}
}
// MakeDeleteAddressEndpoint returns an endpoint via the passed service.
// Primarily useful in a server.
func MakeDeleteAddressEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(deleteAddressRequest)
e := s.DeleteAddress(ctx, req.ProfileID, req.AddressID)
return deleteAddressResponse{Err: e}, nil
}
}
// We have two options to return errors from the business logic.
//
// We could return the error via the endpoint itself. That makes certain things
// a little bit easier, like providing non-200 HTTP responses to the client. But
// Go kit assumes that endpoint errors are (or may be treated as)
// transport-domain errors. For example, an endpoint error will count against a
// circuit breaker error count.
//
// Therefore, it's often better to return service (business logic) errors in the
// response object. This means we have to do a bit more work in the HTTP
// response encoder to detect e.g. a not-found error and provide a proper HTTP
// status code. That work is done with the errorer interface, in transport.go.
// Response types that may contain business-logic errors implement that
// interface.
type postProfileRequest struct {
Profile Profile
}
type postProfileResponse struct {
Err error `json:"err,omitempty"`
}
func (r postProfileResponse) error() error { return r.Err }
type getProfileRequest struct {
ID string
}
type getProfileResponse struct {
Profile Profile `json:"profile,omitempty"`
Err error `json:"err,omitempty"`
}
func (r getProfileResponse) error() error { return r.Err }
type putProfileRequest struct {
ID string
Profile Profile
}
type putProfileResponse struct {
Err error `json:"err,omitempty"`
}
func (r putProfileResponse) error() error { return nil }
type patchProfileRequest struct {
ID string
Profile Profile
}
type patchProfileResponse struct {
Err error `json:"err,omitempty"`
}
func (r patchProfileResponse) error() error { return r.Err }
type deleteProfileRequest struct {
ID string
}
type deleteProfileResponse struct {
Err error `json:"err,omitempty"`
}
func (r deleteProfileResponse) error() error { return r.Err }
type getAddressesRequest struct {
ProfileID string
}
type getAddressesResponse struct {
Addresses []Address `json:"addresses,omitempty"`
Err error `json:"err,omitempty"`
}
func (r getAddressesResponse) error() error { return r.Err }
type getAddressRequest struct {
ProfileID string
AddressID string
}
type getAddressResponse struct {
Address Address `json:"address,omitempty"`
Err error `json:"err,omitempty"`
}
func (r getAddressResponse) error() error { return r.Err }
type postAddressRequest struct {
ProfileID string
Address Address
}
type postAddressResponse struct {
Err error `json:"err,omitempty"`
}
func (r postAddressResponse) error() error { return r.Err }
type deleteAddressRequest struct {
ProfileID string
AddressID string
}
type deleteAddressResponse struct {
Err error `json:"err,omitempty"`
}
func (r deleteAddressResponse) error() error { return r.Err }

View File

@@ -1,89 +0,0 @@
package profilesvc
import (
"time"
"golang.org/x/net/context"
"github.com/go-kit/kit/log"
)
// Middleware describes a service (as opposed to endpoint) middleware.
type Middleware func(Service) Service
func LoggingMiddleware(logger log.Logger) Middleware {
return func(next Service) Service {
return &loggingMiddleware{
next: next,
logger: logger,
}
}
}
type loggingMiddleware struct {
next Service
logger log.Logger
}
func (mw loggingMiddleware) PostProfile(ctx context.Context, p Profile) (err error) {
defer func(begin time.Time) {
mw.logger.Log("method", "PostProfile", "id", p.ID, "took", time.Since(begin), "err", err)
}(time.Now())
return mw.next.PostProfile(ctx, p)
}
func (mw loggingMiddleware) GetProfile(ctx context.Context, id string) (p Profile, err error) {
defer func(begin time.Time) {
mw.logger.Log("method", "GetProfile", "id", id, "took", time.Since(begin), "err", err)
}(time.Now())
return mw.next.GetProfile(ctx, id)
}
func (mw loggingMiddleware) PutProfile(ctx context.Context, id string, p Profile) (err error) {
defer func(begin time.Time) {
mw.logger.Log("method", "PutProfile", "id", id, "took", time.Since(begin), "err", err)
}(time.Now())
return mw.next.PutProfile(ctx, id, p)
}
func (mw loggingMiddleware) PatchProfile(ctx context.Context, id string, p Profile) (err error) {
defer func(begin time.Time) {
mw.logger.Log("method", "PatchProfile", "id", id, "took", time.Since(begin), "err", err)
}(time.Now())
return mw.next.PatchProfile(ctx, id, p)
}
func (mw loggingMiddleware) DeleteProfile(ctx context.Context, id string) (err error) {
defer func(begin time.Time) {
mw.logger.Log("method", "DeleteProfile", "id", id, "took", time.Since(begin), "err", err)
}(time.Now())
return mw.next.DeleteProfile(ctx, id)
}
func (mw loggingMiddleware) GetAddresses(ctx context.Context, profileID string) (addresses []Address, err error) {
defer func(begin time.Time) {
mw.logger.Log("method", "GetAddresses", "profileID", profileID, "took", time.Since(begin), "err", err)
}(time.Now())
return mw.next.GetAddresses(ctx, profileID)
}
func (mw loggingMiddleware) GetAddress(ctx context.Context, profileID string, addressID string) (a Address, err error) {
defer func(begin time.Time) {
mw.logger.Log("method", "GetAddress", "profileID", profileID, "addressID", addressID, "took", time.Since(begin), "err", err)
}(time.Now())
return mw.next.GetAddress(ctx, profileID, addressID)
}
func (mw loggingMiddleware) PostAddress(ctx context.Context, profileID string, a Address) (err error) {
defer func(begin time.Time) {
mw.logger.Log("method", "PostAddress", "profileID", profileID, "took", time.Since(begin), "err", err)
}(time.Now())
return mw.next.PostAddress(ctx, profileID, a)
}
func (mw loggingMiddleware) DeleteAddress(ctx context.Context, profileID string, addressID string) (err error) {
defer func(begin time.Time) {
mw.logger.Log("method", "DeleteAddress", "profileID", profileID, "addressID", addressID, "took", time.Since(begin), "err", err)
}(time.Now())
return mw.next.DeleteAddress(ctx, profileID, addressID)
}

View File

@@ -1,186 +0,0 @@
package profilesvc
import (
"errors"
"sync"
"golang.org/x/net/context"
)
// Service is a simple CRUD interface for user profiles.
type Service interface {
PostProfile(ctx context.Context, p Profile) error
GetProfile(ctx context.Context, id string) (Profile, error)
PutProfile(ctx context.Context, id string, p Profile) error
PatchProfile(ctx context.Context, id string, p Profile) error
DeleteProfile(ctx context.Context, id string) error
GetAddresses(ctx context.Context, profileID string) ([]Address, error)
GetAddress(ctx context.Context, profileID string, addressID string) (Address, error)
PostAddress(ctx context.Context, profileID string, a Address) error
DeleteAddress(ctx context.Context, profileID string, addressID string) error
}
// Profile represents a single user profile.
// ID should be globally unique.
type Profile struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Addresses []Address `json:"addresses,omitempty"`
}
// Address is a field of a user profile.
// ID should be unique within the profile (at a minimum).
type Address struct {
ID string `json:"id"`
Location string `json:"location,omitempty"`
}
var (
ErrInconsistentIDs = errors.New("inconsistent IDs")
ErrAlreadyExists = errors.New("already exists")
ErrNotFound = errors.New("not found")
)
type inmemService struct {
mtx sync.RWMutex
m map[string]Profile
}
func NewInmemService() Service {
return &inmemService{
m: map[string]Profile{},
}
}
func (s *inmemService) PostProfile(ctx context.Context, p Profile) error {
s.mtx.Lock()
defer s.mtx.Unlock()
if _, ok := s.m[p.ID]; ok {
return ErrAlreadyExists // POST = create, don't overwrite
}
s.m[p.ID] = p
return nil
}
func (s *inmemService) GetProfile(ctx context.Context, id string) (Profile, error) {
s.mtx.RLock()
defer s.mtx.RUnlock()
p, ok := s.m[id]
if !ok {
return Profile{}, ErrNotFound
}
return p, nil
}
func (s *inmemService) PutProfile(ctx context.Context, id string, p Profile) error {
if id != p.ID {
return ErrInconsistentIDs
}
s.mtx.Lock()
defer s.mtx.Unlock()
s.m[id] = p // PUT = create or update
return nil
}
func (s *inmemService) PatchProfile(ctx context.Context, id string, p Profile) error {
if p.ID != "" && id != p.ID {
return ErrInconsistentIDs
}
s.mtx.Lock()
defer s.mtx.Unlock()
existing, ok := s.m[id]
if !ok {
return ErrNotFound // PATCH = update existing, don't create
}
// We assume that it's not possible to PATCH the ID, and that it's not
// possible to PATCH any field to its zero value. That is, the zero value
// means not specified. The way around this is to use e.g. Name *string in
// the Profile definition. But since this is just a demonstrative example,
// I'm leaving that out.
if p.Name != "" {
existing.Name = p.Name
}
if len(p.Addresses) > 0 {
existing.Addresses = p.Addresses
}
s.m[id] = existing
return nil
}
func (s *inmemService) DeleteProfile(ctx context.Context, id string) error {
s.mtx.Lock()
defer s.mtx.Unlock()
if _, ok := s.m[id]; !ok {
return ErrNotFound
}
delete(s.m, id)
return nil
}
func (s *inmemService) GetAddresses(ctx context.Context, profileID string) ([]Address, error) {
s.mtx.RLock()
defer s.mtx.RUnlock()
p, ok := s.m[profileID]
if !ok {
return []Address{}, ErrNotFound
}
return p.Addresses, nil
}
func (s *inmemService) GetAddress(ctx context.Context, profileID string, addressID string) (Address, error) {
s.mtx.RLock()
defer s.mtx.RUnlock()
p, ok := s.m[profileID]
if !ok {
return Address{}, ErrNotFound
}
for _, address := range p.Addresses {
if address.ID == addressID {
return address, nil
}
}
return Address{}, ErrNotFound
}
func (s *inmemService) PostAddress(ctx context.Context, profileID string, a Address) error {
s.mtx.Lock()
defer s.mtx.Unlock()
p, ok := s.m[profileID]
if !ok {
return ErrNotFound
}
for _, address := range p.Addresses {
if address.ID == a.ID {
return ErrAlreadyExists
}
}
p.Addresses = append(p.Addresses, a)
s.m[profileID] = p
return nil
}
func (s *inmemService) DeleteAddress(ctx context.Context, profileID string, addressID string) error {
s.mtx.Lock()
defer s.mtx.Unlock()
p, ok := s.m[profileID]
if !ok {
return ErrNotFound
}
newAddresses := make([]Address, 0, len(p.Addresses))
for _, address := range p.Addresses {
if address.ID == addressID {
continue // delete
}
newAddresses = append(newAddresses, address)
}
if len(newAddresses) == len(p.Addresses) {
return ErrNotFound
}
p.Addresses = newAddresses
s.m[profileID] = p
return nil
}

View File

@@ -1,420 +0,0 @@
package profilesvc
// The profilesvc is just over HTTP, so we just have a single transport.go.
import (
"bytes"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"
"github.com/gorilla/mux"
"golang.org/x/net/context"
"github.com/go-kit/kit/log"
httptransport "github.com/go-kit/kit/transport/http"
)
var (
// ErrBadRouting is returned when an expected path variable is missing.
// It always indicates programmer error.
ErrBadRouting = errors.New("inconsistent mapping between route and handler (programmer error)")
)
// MakeHTTPHandler mounts all of the service endpoints into an http.Handler.
// Useful in a profilesvc server.
func MakeHTTPHandler(ctx context.Context, s Service, logger log.Logger) http.Handler {
r := mux.NewRouter()
e := MakeServerEndpoints(s)
options := []httptransport.ServerOption{
httptransport.ServerErrorLogger(logger),
httptransport.ServerErrorEncoder(encodeError),
}
// POST /profiles/ adds another profile
// GET /profiles/:id retrieves the given profile by id
// PUT /profiles/:id post updated profile information about the profile
// PATCH /profiles/:id partial updated profile information
// DELETE /profiles/:id remove the given profile
// GET /profiles/:id/addresses/ retrieve addresses associated with the profile
// GET /profiles/:id/addresses/:addressID retrieve a particular profile address
// POST /profiles/:id/addresses/ add a new address
// DELETE /profiles/:id/addresses/:addressID remove an address
r.Methods("POST").Path("/profiles/").Handler(httptransport.NewServer(
ctx,
e.PostProfileEndpoint,
decodePostProfileRequest,
encodeResponse,
options...,
))
r.Methods("GET").Path("/profiles/{id}").Handler(httptransport.NewServer(
ctx,
e.GetProfileEndpoint,
decodeGetProfileRequest,
encodeResponse,
options...,
))
r.Methods("PUT").Path("/profiles/{id}").Handler(httptransport.NewServer(
ctx,
e.PutProfileEndpoint,
decodePutProfileRequest,
encodeResponse,
options...,
))
r.Methods("PATCH").Path("/profiles/{id}").Handler(httptransport.NewServer(
ctx,
e.PatchProfileEndpoint,
decodePatchProfileRequest,
encodeResponse,
options...,
))
r.Methods("DELETE").Path("/profiles/{id}").Handler(httptransport.NewServer(
ctx,
e.DeleteProfileEndpoint,
decodeDeleteProfileRequest,
encodeResponse,
options...,
))
r.Methods("GET").Path("/profiles/{id}/addresses/").Handler(httptransport.NewServer(
ctx,
e.GetAddressesEndpoint,
decodeGetAddressesRequest,
encodeResponse,
options...,
))
r.Methods("GET").Path("/profiles/{id}/addresses/{addressID}").Handler(httptransport.NewServer(
ctx,
e.GetAddressEndpoint,
decodeGetAddressRequest,
encodeResponse,
options...,
))
r.Methods("POST").Path("/profiles/{id}/addresses/").Handler(httptransport.NewServer(
ctx,
e.PostAddressEndpoint,
decodePostAddressRequest,
encodeResponse,
options...,
))
r.Methods("DELETE").Path("/profiles/{id}/addresses/{addressID}").Handler(httptransport.NewServer(
ctx,
e.DeleteAddressEndpoint,
decodeDeleteAddressRequest,
encodeResponse,
options...,
))
return r
}
func decodePostProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
var req postProfileRequest
if e := json.NewDecoder(r.Body).Decode(&req.Profile); e != nil {
return nil, e
}
return req, nil
}
func decodeGetProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, ErrBadRouting
}
return getProfileRequest{ID: id}, nil
}
func decodePutProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, ErrBadRouting
}
var profile Profile
if err := json.NewDecoder(r.Body).Decode(&profile); err != nil {
return nil, err
}
return putProfileRequest{
ID: id,
Profile: profile,
}, nil
}
func decodePatchProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, ErrBadRouting
}
var profile Profile
if err := json.NewDecoder(r.Body).Decode(&profile); err != nil {
return nil, err
}
return patchProfileRequest{
ID: id,
Profile: profile,
}, nil
}
func decodeDeleteProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, ErrBadRouting
}
return deleteProfileRequest{ID: id}, nil
}
func decodeGetAddressesRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, ErrBadRouting
}
return getAddressesRequest{ProfileID: id}, nil
}
func decodeGetAddressRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, ErrBadRouting
}
addressID, ok := vars["addressID"]
if !ok {
return nil, ErrBadRouting
}
return getAddressRequest{
ProfileID: id,
AddressID: addressID,
}, nil
}
func decodePostAddressRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, ErrBadRouting
}
var address Address
if err := json.NewDecoder(r.Body).Decode(&address); err != nil {
return nil, err
}
return postAddressRequest{
ProfileID: id,
Address: address,
}, nil
}
func decodeDeleteAddressRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, ErrBadRouting
}
addressID, ok := vars["addressID"]
if !ok {
return nil, ErrBadRouting
}
return deleteAddressRequest{
ProfileID: id,
AddressID: addressID,
}, nil
}
func encodePostProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
// r.Methods("POST").Path("/profiles/")
req.Method, req.URL.Path = "POST", "/profiles/"
return encodeRequest(ctx, req, request)
}
func encodeGetProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
// r.Methods("GET").Path("/profiles/{id}")
r := request.(getProfileRequest)
profileID := url.QueryEscape(r.ID)
req.Method, req.URL.Path = "GET", "/profiles/"+profileID
return encodeRequest(ctx, req, request)
}
func encodePutProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
// r.Methods("PUT").Path("/profiles/{id}")
r := request.(putProfileRequest)
profileID := url.QueryEscape(r.ID)
req.Method, req.URL.Path = "PUT", "/profiles/"+profileID
return encodeRequest(ctx, req, request)
}
func encodePatchProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
// r.Methods("PATCH").Path("/profiles/{id}")
r := request.(patchProfileRequest)
profileID := url.QueryEscape(r.ID)
req.Method, req.URL.Path = "PATCH", "/profiles/"+profileID
return encodeRequest(ctx, req, request)
}
func encodeDeleteProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
// r.Methods("DELETE").Path("/profiles/{id}")
r := request.(deleteProfileRequest)
profileID := url.QueryEscape(r.ID)
req.Method, req.URL.Path = "DELETE", "/profiles/"+profileID
return encodeRequest(ctx, req, request)
}
func encodeGetAddressesRequest(ctx context.Context, req *http.Request, request interface{}) error {
// r.Methods("GET").Path("/profiles/{id}/addresses/")
r := request.(getAddressesRequest)
profileID := url.QueryEscape(r.ProfileID)
req.Method, req.URL.Path = "GET", "/profiles/"+profileID+"/addresses/"
return encodeRequest(ctx, req, request)
}
func encodeGetAddressRequest(ctx context.Context, req *http.Request, request interface{}) error {
// r.Methods("GET").Path("/profiles/{id}/addresses/{addressID}")
r := request.(getAddressRequest)
profileID := url.QueryEscape(r.ProfileID)
addressID := url.QueryEscape(r.AddressID)
req.Method, req.URL.Path = "GET", "/profiles/"+profileID+"/addresses/"+addressID
return encodeRequest(ctx, req, request)
}
func encodePostAddressRequest(ctx context.Context, req *http.Request, request interface{}) error {
// r.Methods("POST").Path("/profiles/{id}/addresses/")
r := request.(postAddressRequest)
profileID := url.QueryEscape(r.ProfileID)
req.Method, req.URL.Path = "POST", "/profiles/"+profileID+"/addresses/"
return encodeRequest(ctx, req, request)
}
func encodeDeleteAddressRequest(ctx context.Context, req *http.Request, request interface{}) error {
// r.Methods("DELETE").Path("/profiles/{id}/addresses/{addressID}")
r := request.(deleteAddressRequest)
profileID := url.QueryEscape(r.ProfileID)
addressID := url.QueryEscape(r.AddressID)
req.Method, req.URL.Path = "DELETE", "/profiles/"+profileID+"/addresses/"+addressID
return encodeRequest(ctx, req, request)
}
func decodePostProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response postProfileResponse
err := json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
func decodeGetProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response getProfileResponse
err := json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
func decodePutProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response putProfileResponse
err := json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
func decodePatchProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response patchProfileResponse
err := json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
func decodeDeleteProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response deleteProfileResponse
err := json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
func decodeGetAddressesResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response getAddressesResponse
err := json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
func decodeGetAddressResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response getAddressResponse
err := json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
func decodePostAddressResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response postAddressResponse
err := json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
func decodeDeleteAddressResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response deleteAddressResponse
err := json.NewDecoder(resp.Body).Decode(&response)
return response, err
}
// errorer is implemented by all concrete response types that may contain
// errors. It allows us to change the HTTP response code without needing to
// trigger an endpoint (transport-level) error. For more information, read the
// big comment in endpoints.go.
type errorer interface {
error() error
}
// encodeResponse is the common method to encode all response types to the
// client. I chose to do it this way because, since we're using JSON, there's no
// reason to provide anything more specific. It's certainly possible to
// specialize on a per-response (per-method) basis.
func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
if e, ok := response.(errorer); ok && e.error() != nil {
// Not a Go kit transport error, but a business-logic error.
// Provide those as HTTP errors.
encodeError(ctx, e.error(), w)
return nil
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
return json.NewEncoder(w).Encode(response)
}
// encodeRequest likewise JSON-encodes the request to the HTTP request body.
// Don't use it directly as a transport/http.Client EncodeRequestFunc:
// profilesvc endpoints require mutating the HTTP method and request path.
func encodeRequest(_ context.Context, req *http.Request, request interface{}) error {
var buf bytes.Buffer
err := json.NewEncoder(&buf).Encode(request)
if err != nil {
return err
}
req.Body = ioutil.NopCloser(&buf)
return nil
}
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
if err == nil {
panic("encodeError with nil error")
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(codeFrom(err))
json.NewEncoder(w).Encode(map[string]interface{}{
"error": err.Error(),
})
}
func codeFrom(err error) int {
switch err {
case ErrNotFound:
return http.StatusNotFound
case ErrAlreadyExists, ErrInconsistentIDs:
return http.StatusBadRequest
default:
if e, ok := err.(httptransport.Error); ok {
switch e.Domain {
case httptransport.DomainDecode:
return http.StatusBadRequest
case httptransport.DomainDo:
return http.StatusServiceUnavailable
default:
return http.StatusInternalServerError
}
}
return http.StatusInternalServerError
}
}

View File

@@ -1,25 +0,0 @@
# shipping
This example demonstrates a more real-world application consisting of multiple services.
## Description
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).
### Organization
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.
- __booking__ - used by the shipping company to book and route cargos.
- __handling__ - used by our staff around the world to register whenever the cargo has been received, loaded etc.
- __tracking__ - used by the customer to track the cargo along the route
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.
`inmem` contains in-memory implementations for the repositories found in the domain packages.
The `routing` package provides a _domain service_ that is used to query an external application for possible routes.
## Contributing
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.

View File

@@ -1,140 +0,0 @@
package booking
import (
"time"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/examples/shipping/cargo"
"github.com/go-kit/kit/examples/shipping/location"
)
type bookCargoRequest struct {
Origin location.UNLocode
Destination location.UNLocode
ArrivalDeadline time.Time
}
type bookCargoResponse struct {
ID cargo.TrackingID `json:"tracking_id,omitempty"`
Err error `json:"error,omitempty"`
}
func (r bookCargoResponse) error() error { return r.Err }
func makeBookCargoEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(bookCargoRequest)
id, err := s.BookNewCargo(req.Origin, req.Destination, req.ArrivalDeadline)
return bookCargoResponse{ID: id, Err: err}, nil
}
}
type loadCargoRequest struct {
ID cargo.TrackingID
}
type loadCargoResponse struct {
Cargo *Cargo `json:"cargo,omitempty"`
Err error `json:"error,omitempty"`
}
func (r loadCargoResponse) error() error { return r.Err }
func makeLoadCargoEndpoint(bs Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(loadCargoRequest)
c, err := bs.LoadCargo(req.ID)
return loadCargoResponse{Cargo: &c, Err: err}, nil
}
}
type requestRoutesRequest struct {
ID cargo.TrackingID
}
type requestRoutesResponse struct {
Routes []cargo.Itinerary `json:"routes,omitempty"`
Err error `json:"error,omitempty"`
}
func (r requestRoutesResponse) error() error { return r.Err }
func makeRequestRoutesEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(requestRoutesRequest)
itin := s.RequestPossibleRoutesForCargo(req.ID)
return requestRoutesResponse{Routes: itin, Err: nil}, nil
}
}
type assignToRouteRequest struct {
ID cargo.TrackingID
Itinerary cargo.Itinerary
}
type assignToRouteResponse struct {
Err error `json:"error,omitempty"`
}
func (r assignToRouteResponse) error() error { return r.Err }
func makeAssignToRouteEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(assignToRouteRequest)
err := s.AssignCargoToRoute(req.ID, req.Itinerary)
return assignToRouteResponse{Err: err}, nil
}
}
type changeDestinationRequest struct {
ID cargo.TrackingID
Destination location.UNLocode
}
type changeDestinationResponse struct {
Err error `json:"error,omitempty"`
}
func (r changeDestinationResponse) error() error { return r.Err }
func makeChangeDestinationEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(changeDestinationRequest)
err := s.ChangeDestination(req.ID, req.Destination)
return changeDestinationResponse{Err: err}, nil
}
}
type listCargosRequest struct{}
type listCargosResponse struct {
Cargos []Cargo `json:"cargos,omitempty"`
Err error `json:"error,omitempty"`
}
func (r listCargosResponse) error() error { return r.Err }
func makeListCargosEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
_ = request.(listCargosRequest)
return listCargosResponse{Cargos: s.Cargos(), Err: nil}, nil
}
}
type listLocationsRequest struct {
}
type listLocationsResponse struct {
Locations []Location `json:"locations,omitempty"`
Err error `json:"error,omitempty"`
}
func makeListLocationsEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
_ = request.(listLocationsRequest)
return listLocationsResponse{Locations: s.Locations(), Err: nil}, nil
}
}

View File

@@ -1,88 +0,0 @@
package booking
import (
"time"
"github.com/go-kit/kit/metrics"
"github.com/go-kit/kit/examples/shipping/cargo"
"github.com/go-kit/kit/examples/shipping/location"
)
type instrumentingService struct {
requestCount metrics.Counter
requestLatency metrics.Histogram
Service
}
// NewInstrumentingService returns an instance of an instrumenting Service.
func NewInstrumentingService(counter metrics.Counter, latency metrics.Histogram, s Service) Service {
return &instrumentingService{
requestCount: counter,
requestLatency: latency,
Service: s,
}
}
func (s *instrumentingService) BookNewCargo(origin, destination location.UNLocode, deadline time.Time) (cargo.TrackingID, error) {
defer func(begin time.Time) {
s.requestCount.With("method", "book").Add(1)
s.requestLatency.With("method", "book").Observe(time.Since(begin).Seconds())
}(time.Now())
return s.Service.BookNewCargo(origin, destination, deadline)
}
func (s *instrumentingService) LoadCargo(id cargo.TrackingID) (c Cargo, err error) {
defer func(begin time.Time) {
s.requestCount.With("method", "load").Add(1)
s.requestLatency.With("method", "load").Observe(time.Since(begin).Seconds())
}(time.Now())
return s.Service.LoadCargo(id)
}
func (s *instrumentingService) RequestPossibleRoutesForCargo(id cargo.TrackingID) []cargo.Itinerary {
defer func(begin time.Time) {
s.requestCount.With("method", "request_routes").Add(1)
s.requestLatency.With("method", "request_routes").Observe(time.Since(begin).Seconds())
}(time.Now())
return s.Service.RequestPossibleRoutesForCargo(id)
}
func (s *instrumentingService) AssignCargoToRoute(id cargo.TrackingID, itinerary cargo.Itinerary) (err error) {
defer func(begin time.Time) {
s.requestCount.With("method", "assign_to_route").Add(1)
s.requestLatency.With("method", "assign_to_route").Observe(time.Since(begin).Seconds())
}(time.Now())
return s.Service.AssignCargoToRoute(id, itinerary)
}
func (s *instrumentingService) ChangeDestination(id cargo.TrackingID, l location.UNLocode) (err error) {
defer func(begin time.Time) {
s.requestCount.With("method", "change_destination").Add(1)
s.requestLatency.With("method", "change_destination").Observe(time.Since(begin).Seconds())
}(time.Now())
return s.Service.ChangeDestination(id, l)
}
func (s *instrumentingService) Cargos() []Cargo {
defer func(begin time.Time) {
s.requestCount.With("method", "list_cargos").Add(1)
s.requestLatency.With("method", "list_cargos").Observe(time.Since(begin).Seconds())
}(time.Now())
return s.Service.Cargos()
}
func (s *instrumentingService) Locations() []Location {
defer func(begin time.Time) {
s.requestCount.With("method", "list_locations").Add(1)
s.requestLatency.With("method", "list_locations").Observe(time.Since(begin).Seconds())
}(time.Now())
return s.Service.Locations()
}

View File

@@ -1,102 +0,0 @@
package booking
import (
"time"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/examples/shipping/cargo"
"github.com/go-kit/kit/examples/shipping/location"
)
type loggingService struct {
logger log.Logger
Service
}
// NewLoggingService returns a new instance of a logging Service.
func NewLoggingService(logger log.Logger, s Service) Service {
return &loggingService{logger, s}
}
func (s *loggingService) BookNewCargo(origin location.UNLocode, destination location.UNLocode, deadline time.Time) (id cargo.TrackingID, err error) {
defer func(begin time.Time) {
s.logger.Log(
"method", "book",
"origin", origin,
"destination", destination,
"arrival_deadline", deadline,
"took", time.Since(begin),
"err", err,
)
}(time.Now())
return s.Service.BookNewCargo(origin, destination, deadline)
}
func (s *loggingService) LoadCargo(id cargo.TrackingID) (c Cargo, err error) {
defer func(begin time.Time) {
s.logger.Log(
"method", "load",
"tracking_id", id,
"took", time.Since(begin),
"err", err,
)
}(time.Now())
return s.Service.LoadCargo(id)
}
func (s *loggingService) RequestPossibleRoutesForCargo(id cargo.TrackingID) []cargo.Itinerary {
defer func(begin time.Time) {
s.logger.Log(
"method", "request_routes",
"tracking_id", id,
"took", time.Since(begin),
)
}(time.Now())
return s.Service.RequestPossibleRoutesForCargo(id)
}
func (s *loggingService) AssignCargoToRoute(id cargo.TrackingID, itinerary cargo.Itinerary) (err error) {
defer func(begin time.Time) {
s.logger.Log(
"method", "assign_to_route",
"tracking_id", id,
"took", time.Since(begin),
"err", err,
)
}(time.Now())
return s.Service.AssignCargoToRoute(id, itinerary)
}
func (s *loggingService) ChangeDestination(id cargo.TrackingID, l location.UNLocode) (err error) {
defer func(begin time.Time) {
s.logger.Log(
"method", "change_destination",
"tracking_id", id,
"destination", l,
"took", time.Since(begin),
"err", err,
)
}(time.Now())
return s.Service.ChangeDestination(id, l)
}
func (s *loggingService) Cargos() []Cargo {
defer func(begin time.Time) {
s.logger.Log(
"method", "list_cargos",
"took", time.Since(begin),
)
}(time.Now())
return s.Service.Cargos()
}
func (s *loggingService) Locations() []Location {
defer func(begin time.Time) {
s.logger.Log(
"method", "list_locations",
"took", time.Since(begin),
)
}(time.Now())
return s.Service.Locations()
}

View File

@@ -1,197 +0,0 @@
// Package booking provides the use-case of booking a cargo. Used by views
// facing an administrator.
package booking
import (
"errors"
"time"
"github.com/go-kit/kit/examples/shipping/cargo"
"github.com/go-kit/kit/examples/shipping/location"
"github.com/go-kit/kit/examples/shipping/routing"
)
// ErrInvalidArgument is returned when one or more arguments are invalid.
var ErrInvalidArgument = errors.New("invalid argument")
// Service is the interface that provides booking methods.
type Service interface {
// BookNewCargo registers a new cargo in the tracking system, not yet
// routed.
BookNewCargo(origin location.UNLocode, destination location.UNLocode, deadline time.Time) (cargo.TrackingID, error)
// LoadCargo returns a read model of a cargo.
LoadCargo(id cargo.TrackingID) (Cargo, error)
// RequestPossibleRoutesForCargo requests a list of itineraries describing
// possible routes for this cargo.
RequestPossibleRoutesForCargo(id cargo.TrackingID) []cargo.Itinerary
// AssignCargoToRoute assigns a cargo to the route specified by the
// itinerary.
AssignCargoToRoute(id cargo.TrackingID, itinerary cargo.Itinerary) error
// ChangeDestination changes the destination of a cargo.
ChangeDestination(id cargo.TrackingID, destination location.UNLocode) error
// Cargos returns a list of all cargos that have been booked.
Cargos() []Cargo
// Locations returns a list of registered locations.
Locations() []Location
}
type service struct {
cargos cargo.Repository
locations location.Repository
handlingEvents cargo.HandlingEventRepository
routingService routing.Service
}
func (s *service) AssignCargoToRoute(id cargo.TrackingID, itinerary cargo.Itinerary) error {
if id == "" || len(itinerary.Legs) == 0 {
return ErrInvalidArgument
}
c, err := s.cargos.Find(id)
if err != nil {
return err
}
c.AssignToRoute(itinerary)
return s.cargos.Store(c)
}
func (s *service) BookNewCargo(origin, destination location.UNLocode, deadline time.Time) (cargo.TrackingID, error) {
if origin == "" || destination == "" || deadline.IsZero() {
return "", ErrInvalidArgument
}
id := cargo.NextTrackingID()
rs := cargo.RouteSpecification{
Origin: origin,
Destination: destination,
ArrivalDeadline: deadline,
}
c := cargo.New(id, rs)
if err := s.cargos.Store(c); err != nil {
return "", err
}
return c.TrackingID, nil
}
func (s *service) LoadCargo(id cargo.TrackingID) (Cargo, error) {
if id == "" {
return Cargo{}, ErrInvalidArgument
}
c, err := s.cargos.Find(id)
if err != nil {
return Cargo{}, err
}
return assemble(c, s.handlingEvents), nil
}
func (s *service) ChangeDestination(id cargo.TrackingID, destination location.UNLocode) error {
if id == "" || destination == "" {
return ErrInvalidArgument
}
c, err := s.cargos.Find(id)
if err != nil {
return err
}
l, err := s.locations.Find(destination)
if err != nil {
return err
}
c.SpecifyNewRoute(cargo.RouteSpecification{
Origin: c.Origin,
Destination: l.UNLocode,
ArrivalDeadline: c.RouteSpecification.ArrivalDeadline,
})
if err := s.cargos.Store(c); err != nil {
return err
}
return nil
}
func (s *service) RequestPossibleRoutesForCargo(id cargo.TrackingID) []cargo.Itinerary {
if id == "" {
return nil
}
c, err := s.cargos.Find(id)
if err != nil {
return []cargo.Itinerary{}
}
return s.routingService.FetchRoutesForSpecification(c.RouteSpecification)
}
func (s *service) Cargos() []Cargo {
var result []Cargo
for _, c := range s.cargos.FindAll() {
result = append(result, assemble(c, s.handlingEvents))
}
return result
}
func (s *service) Locations() []Location {
var result []Location
for _, v := range s.locations.FindAll() {
result = append(result, Location{
UNLocode: string(v.UNLocode),
Name: v.Name,
})
}
return result
}
// NewService creates a booking service with necessary dependencies.
func NewService(cargos cargo.Repository, locations location.Repository, events cargo.HandlingEventRepository, rs routing.Service) Service {
return &service{
cargos: cargos,
locations: locations,
handlingEvents: events,
routingService: rs,
}
}
// Location is a read model for booking views.
type Location struct {
UNLocode string `json:"locode"`
Name string `json:"name"`
}
// Cargo is a read model for booking views.
type Cargo struct {
ArrivalDeadline time.Time `json:"arrival_deadline"`
Destination string `json:"destination"`
Legs []cargo.Leg `json:"legs,omitempty"`
Misrouted bool `json:"misrouted"`
Origin string `json:"origin"`
Routed bool `json:"routed"`
TrackingID string `json:"tracking_id"`
}
func assemble(c *cargo.Cargo, events cargo.HandlingEventRepository) Cargo {
return Cargo{
TrackingID: string(c.TrackingID),
Origin: string(c.Origin),
Destination: string(c.RouteSpecification.Destination),
Misrouted: c.Delivery.RoutingStatus == cargo.Misrouted,
Routed: !c.Itinerary.IsEmpty(),
ArrivalDeadline: c.RouteSpecification.ArrivalDeadline,
Legs: c.Itinerary.Legs,
}
}

View File

@@ -1,201 +0,0 @@
package booking
import (
"encoding/json"
"errors"
"net/http"
"time"
"github.com/gorilla/mux"
"golang.org/x/net/context"
kitlog "github.com/go-kit/kit/log"
kithttp "github.com/go-kit/kit/transport/http"
"github.com/go-kit/kit/examples/shipping/cargo"
"github.com/go-kit/kit/examples/shipping/location"
)
// MakeHandler returns a handler for the booking service.
func MakeHandler(ctx context.Context, bs Service, logger kitlog.Logger) http.Handler {
opts := []kithttp.ServerOption{
kithttp.ServerErrorLogger(logger),
kithttp.ServerErrorEncoder(encodeError),
}
bookCargoHandler := kithttp.NewServer(
ctx,
makeBookCargoEndpoint(bs),
decodeBookCargoRequest,
encodeResponse,
opts...,
)
loadCargoHandler := kithttp.NewServer(
ctx,
makeLoadCargoEndpoint(bs),
decodeLoadCargoRequest,
encodeResponse,
opts...,
)
requestRoutesHandler := kithttp.NewServer(
ctx,
makeRequestRoutesEndpoint(bs),
decodeRequestRoutesRequest,
encodeResponse,
opts...,
)
assignToRouteHandler := kithttp.NewServer(
ctx,
makeAssignToRouteEndpoint(bs),
decodeAssignToRouteRequest,
encodeResponse,
opts...,
)
changeDestinationHandler := kithttp.NewServer(
ctx,
makeChangeDestinationEndpoint(bs),
decodeChangeDestinationRequest,
encodeResponse,
opts...,
)
listCargosHandler := kithttp.NewServer(
ctx,
makeListCargosEndpoint(bs),
decodeListCargosRequest,
encodeResponse,
opts...,
)
listLocationsHandler := kithttp.NewServer(
ctx,
makeListLocationsEndpoint(bs),
decodeListLocationsRequest,
encodeResponse,
opts...,
)
r := mux.NewRouter()
r.Handle("/booking/v1/cargos", bookCargoHandler).Methods("POST")
r.Handle("/booking/v1/cargos", listCargosHandler).Methods("GET")
r.Handle("/booking/v1/cargos/{id}", loadCargoHandler).Methods("GET")
r.Handle("/booking/v1/cargos/{id}/request_routes", requestRoutesHandler).Methods("GET")
r.Handle("/booking/v1/cargos/{id}/assign_to_route", assignToRouteHandler).Methods("POST")
r.Handle("/booking/v1/cargos/{id}/change_destination", changeDestinationHandler).Methods("POST")
r.Handle("/booking/v1/locations", listLocationsHandler).Methods("GET")
return r
}
var errBadRoute = errors.New("bad route")
func decodeBookCargoRequest(_ context.Context, r *http.Request) (interface{}, error) {
var body struct {
Origin string `json:"origin"`
Destination string `json:"destination"`
ArrivalDeadline time.Time `json:"arrival_deadline"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
return nil, err
}
return bookCargoRequest{
Origin: location.UNLocode(body.Origin),
Destination: location.UNLocode(body.Destination),
ArrivalDeadline: body.ArrivalDeadline,
}, nil
}
func decodeLoadCargoRequest(_ context.Context, r *http.Request) (interface{}, error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, errBadRoute
}
return loadCargoRequest{ID: cargo.TrackingID(id)}, nil
}
func decodeRequestRoutesRequest(_ context.Context, r *http.Request) (interface{}, error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, errBadRoute
}
return requestRoutesRequest{ID: cargo.TrackingID(id)}, nil
}
func decodeAssignToRouteRequest(_ context.Context, r *http.Request) (interface{}, error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, errBadRoute
}
var itinerary cargo.Itinerary
if err := json.NewDecoder(r.Body).Decode(&itinerary); err != nil {
return nil, err
}
return assignToRouteRequest{
ID: cargo.TrackingID(id),
Itinerary: itinerary,
}, nil
}
func decodeChangeDestinationRequest(_ context.Context, r *http.Request) (interface{}, error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, errBadRoute
}
var body struct {
Destination string `json:"destination"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
return nil, err
}
return changeDestinationRequest{
ID: cargo.TrackingID(id),
Destination: location.UNLocode(body.Destination),
}, nil
}
func decodeListCargosRequest(_ context.Context, r *http.Request) (interface{}, error) {
return listCargosRequest{}, nil
}
func decodeListLocationsRequest(_ context.Context, r *http.Request) (interface{}, error) {
return listLocationsRequest{}, nil
}
func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
if e, ok := response.(errorer); ok && e.error() != nil {
encodeError(ctx, e.error(), w)
return nil
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
return json.NewEncoder(w).Encode(response)
}
type errorer interface {
error() error
}
// encode errors from business-logic
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
switch err {
case cargo.ErrUnknown:
w.WriteHeader(http.StatusNotFound)
case ErrInvalidArgument:
w.WriteHeader(http.StatusBadRequest)
default:
w.WriteHeader(http.StatusInternalServerError)
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(map[string]interface{}{
"error": err.Error(),
})
}

View File

@@ -1,137 +0,0 @@
// Package cargo contains the heart of the domain model.
package cargo
import (
"errors"
"strings"
"time"
"github.com/pborman/uuid"
"github.com/go-kit/kit/examples/shipping/location"
)
// TrackingID uniquely identifies a particular cargo.
type TrackingID string
// Cargo is the central class in the domain model.
type Cargo struct {
TrackingID TrackingID
Origin location.UNLocode
RouteSpecification RouteSpecification
Itinerary Itinerary
Delivery Delivery
}
// SpecifyNewRoute specifies a new route for this cargo.
func (c *Cargo) SpecifyNewRoute(rs RouteSpecification) {
c.RouteSpecification = rs
c.Delivery = c.Delivery.UpdateOnRouting(c.RouteSpecification, c.Itinerary)
}
// AssignToRoute attaches a new itinerary to this cargo.
func (c *Cargo) AssignToRoute(itinerary Itinerary) {
c.Itinerary = itinerary
c.Delivery = c.Delivery.UpdateOnRouting(c.RouteSpecification, c.Itinerary)
}
// DeriveDeliveryProgress updates all aspects of the cargo aggregate status
// based on the current route specification, itinerary and handling of the cargo.
func (c *Cargo) DeriveDeliveryProgress(history HandlingHistory) {
c.Delivery = DeriveDeliveryFrom(c.RouteSpecification, c.Itinerary, history)
}
// New creates a new, unrouted cargo.
func New(id TrackingID, rs RouteSpecification) *Cargo {
itinerary := Itinerary{}
history := HandlingHistory{make([]HandlingEvent, 0)}
return &Cargo{
TrackingID: id,
Origin: rs.Origin,
RouteSpecification: rs,
Delivery: DeriveDeliveryFrom(rs, itinerary, history),
}
}
// Repository provides access a cargo store.
type Repository interface {
Store(cargo *Cargo) error
Find(id TrackingID) (*Cargo, error)
FindAll() []*Cargo
}
// ErrUnknown is used when a cargo could not be found.
var ErrUnknown = errors.New("unknown cargo")
// NextTrackingID generates a new tracking ID.
// TODO: Move to infrastructure(?)
func NextTrackingID() TrackingID {
return TrackingID(strings.Split(strings.ToUpper(uuid.New()), "-")[0])
}
// RouteSpecification Contains information about a route: its origin,
// destination and arrival deadline.
type RouteSpecification struct {
Origin location.UNLocode
Destination location.UNLocode
ArrivalDeadline time.Time
}
// IsSatisfiedBy checks whether provided itinerary satisfies this
// specification.
func (s RouteSpecification) IsSatisfiedBy(itinerary Itinerary) bool {
return itinerary.Legs != nil &&
s.Origin == itinerary.InitialDepartureLocation() &&
s.Destination == itinerary.FinalArrivalLocation()
}
// RoutingStatus describes status of cargo routing.
type RoutingStatus int
// Valid routing statuses.
const (
NotRouted RoutingStatus = iota
Misrouted
Routed
)
func (s RoutingStatus) String() string {
switch s {
case NotRouted:
return "Not routed"
case Misrouted:
return "Misrouted"
case Routed:
return "Routed"
}
return ""
}
// TransportStatus describes status of cargo transportation.
type TransportStatus int
// Valid transport statuses.
const (
NotReceived TransportStatus = iota
InPort
OnboardCarrier
Claimed
Unknown
)
func (s TransportStatus) String() string {
switch s {
case NotReceived:
return "Not received"
case InPort:
return "In port"
case OnboardCarrier:
return "Onboard carrier"
case Claimed:
return "Claimed"
case Unknown:
return "Unknown"
}
return ""
}

View File

@@ -1,174 +0,0 @@
package cargo
import (
"time"
"github.com/go-kit/kit/examples/shipping/location"
"github.com/go-kit/kit/examples/shipping/voyage"
)
// Delivery is the actual transportation of the cargo, as opposed to the
// customer requirement (RouteSpecification) and the plan (Itinerary).
type Delivery struct {
Itinerary Itinerary
RouteSpecification RouteSpecification
RoutingStatus RoutingStatus
TransportStatus TransportStatus
NextExpectedActivity HandlingActivity
LastEvent HandlingEvent
LastKnownLocation location.UNLocode
CurrentVoyage voyage.Number
ETA time.Time
IsMisdirected bool
IsUnloadedAtDestination bool
}
// UpdateOnRouting creates a new delivery snapshot to reflect changes in
// routing, i.e. when the route specification or the itinerary has changed but
// no additional handling of the cargo has been performed.
func (d Delivery) UpdateOnRouting(rs RouteSpecification, itinerary Itinerary) Delivery {
return newDelivery(d.LastEvent, itinerary, rs)
}
// IsOnTrack checks if the delivery is on track.
func (d Delivery) IsOnTrack() bool {
return d.RoutingStatus == Routed && !d.IsMisdirected
}
// DeriveDeliveryFrom creates a new delivery snapshot based on the complete
// handling history of a cargo, as well as its route specification and
// itinerary.
func DeriveDeliveryFrom(rs RouteSpecification, itinerary Itinerary, history HandlingHistory) Delivery {
lastEvent, _ := history.MostRecentlyCompletedEvent()
return newDelivery(lastEvent, itinerary, rs)
}
// newDelivery creates a up-to-date delivery based on an handling event,
// itinerary and a route specification.
func newDelivery(lastEvent HandlingEvent, itinerary Itinerary, rs RouteSpecification) Delivery {
var (
routingStatus = calculateRoutingStatus(itinerary, rs)
transportStatus = calculateTransportStatus(lastEvent)
lastKnownLocation = calculateLastKnownLocation(lastEvent)
isMisdirected = calculateMisdirectedStatus(lastEvent, itinerary)
isUnloadedAtDestination = calculateUnloadedAtDestination(lastEvent, rs)
currentVoyage = calculateCurrentVoyage(transportStatus, lastEvent)
)
d := Delivery{
LastEvent: lastEvent,
Itinerary: itinerary,
RouteSpecification: rs,
RoutingStatus: routingStatus,
TransportStatus: transportStatus,
LastKnownLocation: lastKnownLocation,
IsMisdirected: isMisdirected,
IsUnloadedAtDestination: isUnloadedAtDestination,
CurrentVoyage: currentVoyage,
}
d.NextExpectedActivity = calculateNextExpectedActivity(d)
d.ETA = calculateETA(d)
return d
}
// Below are internal functions used when creating a new delivery.
func calculateRoutingStatus(itinerary Itinerary, rs RouteSpecification) RoutingStatus {
if itinerary.Legs == nil {
return NotRouted
}
if rs.IsSatisfiedBy(itinerary) {
return Routed
}
return Misrouted
}
func calculateMisdirectedStatus(event HandlingEvent, itinerary Itinerary) bool {
if event.Activity.Type == NotHandled {
return false
}
return !itinerary.IsExpected(event)
}
func calculateUnloadedAtDestination(event HandlingEvent, rs RouteSpecification) bool {
if event.Activity.Type == NotHandled {
return false
}
return event.Activity.Type == Unload && rs.Destination == event.Activity.Location
}
func calculateTransportStatus(event HandlingEvent) TransportStatus {
switch event.Activity.Type {
case NotHandled:
return NotReceived
case Load:
return OnboardCarrier
case Unload:
return InPort
case Receive:
return InPort
case Customs:
return InPort
case Claim:
return Claimed
}
return Unknown
}
func calculateLastKnownLocation(event HandlingEvent) location.UNLocode {
return event.Activity.Location
}
func calculateNextExpectedActivity(d Delivery) HandlingActivity {
if !d.IsOnTrack() {
return HandlingActivity{}
}
switch d.LastEvent.Activity.Type {
case NotHandled:
return HandlingActivity{Type: Receive, Location: d.RouteSpecification.Origin}
case Receive:
l := d.Itinerary.Legs[0]
return HandlingActivity{Type: Load, Location: l.LoadLocation, VoyageNumber: l.VoyageNumber}
case Load:
for _, l := range d.Itinerary.Legs {
if l.LoadLocation == d.LastEvent.Activity.Location {
return HandlingActivity{Type: Unload, Location: l.UnloadLocation, VoyageNumber: l.VoyageNumber}
}
}
case Unload:
for i, l := range d.Itinerary.Legs {
if l.UnloadLocation == d.LastEvent.Activity.Location {
if i < len(d.Itinerary.Legs)-1 {
return HandlingActivity{Type: Load, Location: d.Itinerary.Legs[i+1].LoadLocation, VoyageNumber: d.Itinerary.Legs[i+1].VoyageNumber}
}
return HandlingActivity{Type: Claim, Location: l.UnloadLocation}
}
}
}
return HandlingActivity{}
}
func calculateCurrentVoyage(transportStatus TransportStatus, event HandlingEvent) voyage.Number {
if transportStatus == OnboardCarrier && event.Activity.Type != NotHandled {
return event.Activity.VoyageNumber
}
return voyage.Number("")
}
func calculateETA(d Delivery) time.Time {
if !d.IsOnTrack() {
return time.Time{}
}
return d.Itinerary.FinalArrivalTime()
}

View File

@@ -1,121 +0,0 @@
package cargo
// TODO: It would make sense to have this in its own package. Unfortunately,
// then there would be a circular dependency between the cargo and handling
// packages since cargo.Delivery would use handling.HandlingEvent and
// handling.HandlingEvent would use cargo.TrackingID. Also,
// HandlingEventFactory depends on the cargo repository.
//
// It would make sense not having the cargo package depend on handling.
import (
"errors"
"time"
"github.com/go-kit/kit/examples/shipping/location"
"github.com/go-kit/kit/examples/shipping/voyage"
)
// HandlingActivity represents how and where a cargo can be handled, and can
// be used to express predictions about what is expected to happen to a cargo
// in the future.
type HandlingActivity struct {
Type HandlingEventType
Location location.UNLocode
VoyageNumber voyage.Number
}
// HandlingEvent is used to register the event when, for instance, a cargo is
// unloaded from a carrier at a some location at a given time.
type HandlingEvent struct {
TrackingID TrackingID
Activity HandlingActivity
}
// HandlingEventType describes type of a handling event.
type HandlingEventType int
// Valid handling event types.
const (
NotHandled HandlingEventType = iota
Load
Unload
Receive
Claim
Customs
)
func (t HandlingEventType) String() string {
switch t {
case NotHandled:
return "Not Handled"
case Load:
return "Load"
case Unload:
return "Unload"
case Receive:
return "Receive"
case Claim:
return "Claim"
case Customs:
return "Customs"
}
return ""
}
// HandlingHistory is the handling history of a cargo.
type HandlingHistory struct {
HandlingEvents []HandlingEvent
}
// MostRecentlyCompletedEvent returns most recently completed handling event.
func (h HandlingHistory) MostRecentlyCompletedEvent() (HandlingEvent, error) {
if len(h.HandlingEvents) == 0 {
return HandlingEvent{}, errors.New("delivery history is empty")
}
return h.HandlingEvents[len(h.HandlingEvents)-1], nil
}
// HandlingEventRepository provides access a handling event store.
type HandlingEventRepository interface {
Store(e HandlingEvent)
QueryHandlingHistory(TrackingID) HandlingHistory
}
// HandlingEventFactory creates handling events.
type HandlingEventFactory struct {
CargoRepository Repository
VoyageRepository voyage.Repository
LocationRepository location.Repository
}
// CreateHandlingEvent creates a validated handling event.
func (f *HandlingEventFactory) CreateHandlingEvent(registered time.Time, completed time.Time, id TrackingID,
voyageNumber voyage.Number, unLocode location.UNLocode, eventType HandlingEventType) (HandlingEvent, error) {
if _, err := f.CargoRepository.Find(id); err != nil {
return HandlingEvent{}, err
}
if _, err := f.VoyageRepository.Find(voyageNumber); err != nil {
// TODO: This is pretty ugly, but when creating a Receive event, the voyage number is not known.
if len(voyageNumber) > 0 {
return HandlingEvent{}, err
}
}
if _, err := f.LocationRepository.Find(unLocode); err != nil {
return HandlingEvent{}, err
}
return HandlingEvent{
TrackingID: id,
Activity: HandlingActivity{
Type: eventType,
Location: unLocode,
VoyageNumber: voyageNumber,
},
}, nil
}

View File

@@ -1,91 +0,0 @@
package cargo
import (
"time"
"github.com/go-kit/kit/examples/shipping/location"
"github.com/go-kit/kit/examples/shipping/voyage"
)
// Leg describes the transportation between two locations on a voyage.
type Leg struct {
VoyageNumber voyage.Number `json:"voyage_number"`
LoadLocation location.UNLocode `json:"from"`
UnloadLocation location.UNLocode `json:"to"`
LoadTime time.Time `json:"load_time"`
UnloadTime time.Time `json:"unload_time"`
}
// NewLeg creates a new itinerary leg.
func NewLeg(voyageNumber voyage.Number, loadLocation, unloadLocation location.UNLocode, loadTime, unloadTime time.Time) Leg {
return Leg{
VoyageNumber: voyageNumber,
LoadLocation: loadLocation,
UnloadLocation: unloadLocation,
LoadTime: loadTime,
UnloadTime: unloadTime,
}
}
// Itinerary specifies steps required to transport a cargo from its origin to
// destination.
type Itinerary struct {
Legs []Leg `json:"legs"`
}
// InitialDepartureLocation returns the start of the itinerary.
func (i Itinerary) InitialDepartureLocation() location.UNLocode {
if i.IsEmpty() {
return location.UNLocode("")
}
return i.Legs[0].LoadLocation
}
// FinalArrivalLocation returns the end of the itinerary.
func (i Itinerary) FinalArrivalLocation() location.UNLocode {
if i.IsEmpty() {
return location.UNLocode("")
}
return i.Legs[len(i.Legs)-1].UnloadLocation
}
// FinalArrivalTime returns the expected arrival time at final destination.
func (i Itinerary) FinalArrivalTime() time.Time {
return i.Legs[len(i.Legs)-1].UnloadTime
}
// IsEmpty checks if the itinerary contains at least one leg.
func (i Itinerary) IsEmpty() bool {
return i.Legs == nil || len(i.Legs) == 0
}
// IsExpected checks if the given handling event is expected when executing
// this itinerary.
func (i Itinerary) IsExpected(event HandlingEvent) bool {
if i.IsEmpty() {
return true
}
switch event.Activity.Type {
case Receive:
return i.InitialDepartureLocation() == event.Activity.Location
case Load:
for _, l := range i.Legs {
if l.LoadLocation == event.Activity.Location && l.VoyageNumber == event.Activity.VoyageNumber {
return true
}
}
return false
case Unload:
for _, l := range i.Legs {
if l.UnloadLocation == event.Activity.Location && l.VoyageNumber == event.Activity.VoyageNumber {
return true
}
}
return false
case Claim:
return i.FinalArrivalLocation() == event.Activity.Location
}
return true
}

View File

@@ -1,35 +0,0 @@
package handling
import (
"time"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/examples/shipping/cargo"
"github.com/go-kit/kit/examples/shipping/location"
"github.com/go-kit/kit/examples/shipping/voyage"
)
type registerIncidentRequest struct {
ID cargo.TrackingID
Location location.UNLocode
Voyage voyage.Number
EventType cargo.HandlingEventType
CompletionTime time.Time
}
type registerIncidentResponse struct {
Err error `json:"error,omitempty"`
}
func (r registerIncidentResponse) error() error { return r.Err }
func makeRegisterIncidentEndpoint(hs Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(registerIncidentRequest)
err := hs.RegisterHandlingEvent(req.CompletionTime, req.ID, req.Voyage, req.Location, req.EventType)
return registerIncidentResponse{Err: err}, nil
}
}

View File

@@ -1,37 +0,0 @@
package handling
import (
"time"
"github.com/go-kit/kit/metrics"
"github.com/go-kit/kit/examples/shipping/cargo"
"github.com/go-kit/kit/examples/shipping/location"
"github.com/go-kit/kit/examples/shipping/voyage"
)
type instrumentingService struct {
requestCount metrics.Counter
requestLatency metrics.Histogram
Service
}
// NewInstrumentingService returns an instance of an instrumenting Service.
func NewInstrumentingService(counter metrics.Counter, latency metrics.Histogram, s Service) Service {
return &instrumentingService{
requestCount: counter,
requestLatency: latency,
Service: s,
}
}
func (s *instrumentingService) RegisterHandlingEvent(completed time.Time, id cargo.TrackingID, voyageNumber voyage.Number,
loc location.UNLocode, eventType cargo.HandlingEventType) error {
defer func(begin time.Time) {
s.requestCount.With("method", "register_incident").Add(1)
s.requestLatency.With("method", "register_incident").Observe(time.Since(begin).Seconds())
}(time.Now())
return s.Service.RegisterHandlingEvent(completed, id, voyageNumber, loc, eventType)
}

View File

@@ -1,38 +0,0 @@
package handling
import (
"time"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/examples/shipping/cargo"
"github.com/go-kit/kit/examples/shipping/location"
"github.com/go-kit/kit/examples/shipping/voyage"
)
type loggingService struct {
logger log.Logger
Service
}
// NewLoggingService returns a new instance of a logging Service.
func NewLoggingService(logger log.Logger, s Service) Service {
return &loggingService{logger, s}
}
func (s *loggingService) RegisterHandlingEvent(completed time.Time, id cargo.TrackingID, voyageNumber voyage.Number,
unLocode location.UNLocode, eventType cargo.HandlingEventType) (err error) {
defer func(begin time.Time) {
s.logger.Log(
"method", "register_incident",
"tracking_id", id,
"location", unLocode,
"voyage", voyageNumber,
"event_type", eventType,
"completion_time", completed,
"took", time.Since(begin),
"err", err,
)
}(time.Now())
return s.Service.RegisterHandlingEvent(completed, id, voyageNumber, unLocode, eventType)
}

View File

@@ -1,76 +0,0 @@
// Package handling provides the use-case for registering incidents. Used by
// views facing the people handling the cargo along its route.
package handling
import (
"errors"
"time"
"github.com/go-kit/kit/examples/shipping/cargo"
"github.com/go-kit/kit/examples/shipping/inspection"
"github.com/go-kit/kit/examples/shipping/location"
"github.com/go-kit/kit/examples/shipping/voyage"
)
// ErrInvalidArgument is returned when one or more arguments are invalid.
var ErrInvalidArgument = errors.New("invalid argument")
// EventHandler provides a means of subscribing to registered handling events.
type EventHandler interface {
CargoWasHandled(cargo.HandlingEvent)
}
// Service provides handling operations.
type Service interface {
// RegisterHandlingEvent registers a handling event in the system, and
// notifies interested parties that a cargo has been handled.
RegisterHandlingEvent(completed time.Time, id cargo.TrackingID, voyageNumber voyage.Number,
unLocode location.UNLocode, eventType cargo.HandlingEventType) error
}
type service struct {
handlingEventRepository cargo.HandlingEventRepository
handlingEventFactory cargo.HandlingEventFactory
handlingEventHandler EventHandler
}
func (s *service) RegisterHandlingEvent(completed time.Time, id cargo.TrackingID, voyageNumber voyage.Number,
loc location.UNLocode, eventType cargo.HandlingEventType) error {
if completed.IsZero() || id == "" || loc == "" || eventType == cargo.NotHandled {
return ErrInvalidArgument
}
e, err := s.handlingEventFactory.CreateHandlingEvent(time.Now(), completed, id, voyageNumber, loc, eventType)
if err != nil {
return err
}
s.handlingEventRepository.Store(e)
s.handlingEventHandler.CargoWasHandled(e)
return nil
}
// NewService creates a handling event service with necessary dependencies.
func NewService(r cargo.HandlingEventRepository, f cargo.HandlingEventFactory, h EventHandler) Service {
return &service{
handlingEventRepository: r,
handlingEventFactory: f,
handlingEventHandler: h,
}
}
type handlingEventHandler struct {
InspectionService inspection.Service
}
func (h *handlingEventHandler) CargoWasHandled(event cargo.HandlingEvent) {
h.InspectionService.InspectCargo(event.TrackingID)
}
// NewEventHandler returns a new instance of a EventHandler.
func NewEventHandler(s inspection.Service) EventHandler {
return &handlingEventHandler{
InspectionService: s,
}
}

View File

@@ -1,101 +0,0 @@
package handling
import (
"encoding/json"
"net/http"
"time"
"github.com/gorilla/mux"
"golang.org/x/net/context"
kitlog "github.com/go-kit/kit/log"
kithttp "github.com/go-kit/kit/transport/http"
"github.com/go-kit/kit/examples/shipping/cargo"
"github.com/go-kit/kit/examples/shipping/location"
"github.com/go-kit/kit/examples/shipping/voyage"
)
// MakeHandler returns a handler for the handling service.
func MakeHandler(ctx context.Context, hs Service, logger kitlog.Logger) http.Handler {
r := mux.NewRouter()
opts := []kithttp.ServerOption{
kithttp.ServerErrorLogger(logger),
kithttp.ServerErrorEncoder(encodeError),
}
registerIncidentHandler := kithttp.NewServer(
ctx,
makeRegisterIncidentEndpoint(hs),
decodeRegisterIncidentRequest,
encodeResponse,
opts...,
)
r.Handle("/handling/v1/incidents", registerIncidentHandler).Methods("POST")
return r
}
func decodeRegisterIncidentRequest(_ context.Context, r *http.Request) (interface{}, error) {
var body struct {
CompletionTime time.Time `json:"completion_time"`
TrackingID string `json:"tracking_id"`
VoyageNumber string `json:"voyage"`
Location string `json:"location"`
EventType string `json:"event_type"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
return nil, err
}
return registerIncidentRequest{
CompletionTime: body.CompletionTime,
ID: cargo.TrackingID(body.TrackingID),
Voyage: voyage.Number(body.VoyageNumber),
Location: location.UNLocode(body.Location),
EventType: stringToEventType(body.EventType),
}, nil
}
func stringToEventType(s string) cargo.HandlingEventType {
types := map[string]cargo.HandlingEventType{
cargo.Receive.String(): cargo.Receive,
cargo.Load.String(): cargo.Load,
cargo.Unload.String(): cargo.Unload,
cargo.Customs.String(): cargo.Customs,
cargo.Claim.String(): cargo.Claim,
}
return types[s]
}
func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
if e, ok := response.(errorer); ok && e.error() != nil {
encodeError(ctx, e.error(), w)
return nil
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
return json.NewEncoder(w).Encode(response)
}
type errorer interface {
error() error
}
// encode errors from business-logic
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
switch err {
case cargo.ErrUnknown:
w.WriteHeader(http.StatusNotFound)
case ErrInvalidArgument:
w.WriteHeader(http.StatusBadRequest)
default:
w.WriteHeader(http.StatusInternalServerError)
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(map[string]interface{}{
"error": err.Error(),
})
}

View File

@@ -1,142 +0,0 @@
// Package inmem provides in-memory implementations of all the domain repositories.
package inmem
import (
"sync"
"github.com/go-kit/kit/examples/shipping/cargo"
"github.com/go-kit/kit/examples/shipping/location"
"github.com/go-kit/kit/examples/shipping/voyage"
)
type cargoRepository struct {
mtx sync.RWMutex
cargos map[cargo.TrackingID]*cargo.Cargo
}
func (r *cargoRepository) Store(c *cargo.Cargo) error {
r.mtx.Lock()
defer r.mtx.Unlock()
r.cargos[c.TrackingID] = c
return nil
}
func (r *cargoRepository) Find(id cargo.TrackingID) (*cargo.Cargo, error) {
r.mtx.RLock()
defer r.mtx.RUnlock()
if val, ok := r.cargos[id]; ok {
return val, nil
}
return nil, cargo.ErrUnknown
}
func (r *cargoRepository) FindAll() []*cargo.Cargo {
r.mtx.RLock()
defer r.mtx.RUnlock()
c := make([]*cargo.Cargo, 0, len(r.cargos))
for _, val := range r.cargos {
c = append(c, val)
}
return c
}
// NewCargoRepository returns a new instance of a in-memory cargo repository.
func NewCargoRepository() cargo.Repository {
return &cargoRepository{
cargos: make(map[cargo.TrackingID]*cargo.Cargo),
}
}
type locationRepository struct {
locations map[location.UNLocode]*location.Location
}
func (r *locationRepository) Find(locode location.UNLocode) (*location.Location, error) {
if l, ok := r.locations[locode]; ok {
return l, nil
}
return nil, location.ErrUnknown
}
func (r *locationRepository) FindAll() []*location.Location {
l := make([]*location.Location, 0, len(r.locations))
for _, val := range r.locations {
l = append(l, val)
}
return l
}
// NewLocationRepository returns a new instance of a in-memory location repository.
func NewLocationRepository() location.Repository {
r := &locationRepository{
locations: make(map[location.UNLocode]*location.Location),
}
r.locations[location.SESTO] = location.Stockholm
r.locations[location.AUMEL] = location.Melbourne
r.locations[location.CNHKG] = location.Hongkong
r.locations[location.JNTKO] = location.Tokyo
r.locations[location.NLRTM] = location.Rotterdam
r.locations[location.DEHAM] = location.Hamburg
return r
}
type voyageRepository struct {
voyages map[voyage.Number]*voyage.Voyage
}
func (r *voyageRepository) Find(voyageNumber voyage.Number) (*voyage.Voyage, error) {
if v, ok := r.voyages[voyageNumber]; ok {
return v, nil
}
return nil, voyage.ErrUnknown
}
// NewVoyageRepository returns a new instance of a in-memory voyage repository.
func NewVoyageRepository() voyage.Repository {
r := &voyageRepository{
voyages: make(map[voyage.Number]*voyage.Voyage),
}
r.voyages[voyage.V100.Number] = voyage.V100
r.voyages[voyage.V300.Number] = voyage.V300
r.voyages[voyage.V400.Number] = voyage.V400
r.voyages[voyage.V0100S.Number] = voyage.V0100S
r.voyages[voyage.V0200T.Number] = voyage.V0200T
r.voyages[voyage.V0300A.Number] = voyage.V0300A
r.voyages[voyage.V0301S.Number] = voyage.V0301S
r.voyages[voyage.V0400S.Number] = voyage.V0400S
return r
}
type handlingEventRepository struct {
mtx sync.RWMutex
events map[cargo.TrackingID][]cargo.HandlingEvent
}
func (r *handlingEventRepository) Store(e cargo.HandlingEvent) {
r.mtx.Lock()
defer r.mtx.Unlock()
// Make array if it's the first event with this tracking ID.
if _, ok := r.events[e.TrackingID]; !ok {
r.events[e.TrackingID] = make([]cargo.HandlingEvent, 0)
}
r.events[e.TrackingID] = append(r.events[e.TrackingID], e)
}
func (r *handlingEventRepository) QueryHandlingHistory(id cargo.TrackingID) cargo.HandlingHistory {
r.mtx.RLock()
defer r.mtx.RUnlock()
return cargo.HandlingHistory{HandlingEvents: r.events[id]}
}
// NewHandlingEventRepository returns a new instance of a in-memory handling event repository.
func NewHandlingEventRepository() cargo.HandlingEventRepository {
return &handlingEventRepository{
events: make(map[cargo.TrackingID][]cargo.HandlingEvent),
}
}

View File

@@ -1,51 +0,0 @@
// Package inspection provides means to inspect cargos.
package inspection
import "github.com/go-kit/kit/examples/shipping/cargo"
// EventHandler provides means of subscribing to inspection events.
type EventHandler interface {
CargoWasMisdirected(*cargo.Cargo)
CargoHasArrived(*cargo.Cargo)
}
// Service provides cargo inspection operations.
type Service interface {
// InspectCargo inspects cargo and send relevant notifications to
// interested parties, for example if a cargo has been misdirected, or
// unloaded at the final destination.
InspectCargo(id cargo.TrackingID)
}
type service struct {
cargos cargo.Repository
events cargo.HandlingEventRepository
handler EventHandler
}
// TODO: Should be transactional
func (s *service) InspectCargo(id cargo.TrackingID) {
c, err := s.cargos.Find(id)
if err != nil {
return
}
h := s.events.QueryHandlingHistory(id)
c.DeriveDeliveryProgress(h)
if c.Delivery.IsMisdirected {
s.handler.CargoWasMisdirected(c)
}
if c.Delivery.IsUnloadedAtDestination {
s.handler.CargoHasArrived(c)
}
s.cargos.Store(c)
}
// NewService creates a inspection service with necessary dependencies.
func NewService(cargos cargo.Repository, events cargo.HandlingEventRepository, handler EventHandler) Service {
return &service{cargos, events, handler}
}

View File

@@ -1,27 +0,0 @@
// Package location provides the Location aggregate.
package location
import "errors"
// UNLocode is the United Nations location code that uniquely identifies a
// particular location.
//
// http://www.unece.org/cefact/locode/
// http://www.unece.org/cefact/locode/DocColumnDescription.htm#LOCODE
type UNLocode string
// Location is a location is our model is stops on a journey, such as cargo
// origin or destination, or carrier movement endpoints.
type Location struct {
UNLocode UNLocode
Name string
}
// ErrUnknown is used when a location could not be found.
var ErrUnknown = errors.New("unknown location")
// Repository provides access a location store.
type Repository interface {
Find(locode UNLocode) (*Location, error)
FindAll() []*Location
}

View File

@@ -1,27 +0,0 @@
package location
// Sample UN locodes.
var (
SESTO UNLocode = "SESTO"
AUMEL UNLocode = "AUMEL"
CNHKG UNLocode = "CNHKG"
USNYC UNLocode = "USNYC"
USCHI UNLocode = "USCHI"
JNTKO UNLocode = "JNTKO"
DEHAM UNLocode = "DEHAM"
NLRTM UNLocode = "NLRTM"
FIHEL UNLocode = "FIHEL"
)
// Sample locations.
var (
Stockholm = &Location{SESTO, "Stockholm"}
Melbourne = &Location{AUMEL, "Melbourne"}
Hongkong = &Location{CNHKG, "Hongkong"}
NewYork = &Location{USNYC, "New York"}
Chicago = &Location{USCHI, "Chicago"}
Tokyo = &Location{JNTKO, "Tokyo"}
Hamburg = &Location{DEHAM, "Hamburg"}
Rotterdam = &Location{NLRTM, "Rotterdam"}
Helsinki = &Location{FIHEL, "Helsinki"}
)

View File

@@ -1,212 +0,0 @@
package main
import (
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
"time"
stdprometheus "github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
"github.com/go-kit/kit/log"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
"github.com/go-kit/kit/examples/shipping/booking"
"github.com/go-kit/kit/examples/shipping/cargo"
"github.com/go-kit/kit/examples/shipping/handling"
"github.com/go-kit/kit/examples/shipping/inmem"
"github.com/go-kit/kit/examples/shipping/inspection"
"github.com/go-kit/kit/examples/shipping/location"
"github.com/go-kit/kit/examples/shipping/routing"
"github.com/go-kit/kit/examples/shipping/tracking"
)
const (
defaultPort = "8080"
defaultRoutingServiceURL = "http://localhost:7878"
)
func main() {
var (
addr = envString("PORT", defaultPort)
rsurl = envString("ROUTINGSERVICE_URL", defaultRoutingServiceURL)
httpAddr = flag.String("http.addr", ":"+addr, "HTTP listen address")
routingServiceURL = flag.String("service.routing", rsurl, "routing service URL")
ctx = context.Background()
)
flag.Parse()
var logger log.Logger
logger = log.NewLogfmtLogger(os.Stderr)
logger = &serializedLogger{Logger: logger}
logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
var (
cargos = inmem.NewCargoRepository()
locations = inmem.NewLocationRepository()
voyages = inmem.NewVoyageRepository()
handlingEvents = inmem.NewHandlingEventRepository()
)
// Configure some questionable dependencies.
var (
handlingEventFactory = cargo.HandlingEventFactory{
CargoRepository: cargos,
VoyageRepository: voyages,
LocationRepository: locations,
}
handlingEventHandler = handling.NewEventHandler(
inspection.NewService(cargos, handlingEvents, nil),
)
)
// Facilitate testing by adding some cargos.
storeTestData(cargos)
fieldKeys := []string{"method"}
var rs routing.Service
rs = routing.NewProxyingMiddleware(ctx, *routingServiceURL)(rs)
var bs booking.Service
bs = booking.NewService(cargos, locations, handlingEvents, rs)
bs = booking.NewLoggingService(log.NewContext(logger).With("component", "booking"), bs)
bs = booking.NewInstrumentingService(
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "api",
Subsystem: "booking_service",
Name: "request_count",
Help: "Number of requests received.",
}, fieldKeys),
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
Namespace: "api",
Subsystem: "booking_service",
Name: "request_latency_microseconds",
Help: "Total duration of requests in microseconds.",
}, fieldKeys),
bs,
)
var ts tracking.Service
ts = tracking.NewService(cargos, handlingEvents)
ts = tracking.NewLoggingService(log.NewContext(logger).With("component", "tracking"), ts)
ts = tracking.NewInstrumentingService(
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "api",
Subsystem: "tracking_service",
Name: "request_count",
Help: "Number of requests received.",
}, fieldKeys),
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
Namespace: "api",
Subsystem: "tracking_service",
Name: "request_latency_microseconds",
Help: "Total duration of requests in microseconds.",
}, fieldKeys),
ts,
)
var hs handling.Service
hs = handling.NewService(handlingEvents, handlingEventFactory, handlingEventHandler)
hs = handling.NewLoggingService(log.NewContext(logger).With("component", "handling"), hs)
hs = handling.NewInstrumentingService(
kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "api",
Subsystem: "handling_service",
Name: "request_count",
Help: "Number of requests received.",
}, fieldKeys),
kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
Namespace: "api",
Subsystem: "handling_service",
Name: "request_latency_microseconds",
Help: "Total duration of requests in microseconds.",
}, fieldKeys),
hs,
)
httpLogger := log.NewContext(logger).With("component", "http")
mux := http.NewServeMux()
mux.Handle("/booking/v1/", booking.MakeHandler(ctx, bs, httpLogger))
mux.Handle("/tracking/v1/", tracking.MakeHandler(ctx, ts, httpLogger))
mux.Handle("/handling/v1/", handling.MakeHandler(ctx, hs, httpLogger))
http.Handle("/", accessControl(mux))
http.Handle("/metrics", stdprometheus.Handler())
errs := make(chan error, 2)
go func() {
logger.Log("transport", "http", "address", *httpAddr, "msg", "listening")
errs <- http.ListenAndServe(*httpAddr, nil)
}()
go func() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT)
errs <- fmt.Errorf("%s", <-c)
}()
logger.Log("terminated", <-errs)
}
func accessControl(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type")
if r.Method == "OPTIONS" {
return
}
h.ServeHTTP(w, r)
})
}
func envString(env, fallback string) string {
e := os.Getenv(env)
if e == "" {
return fallback
}
return e
}
func storeTestData(r cargo.Repository) {
test1 := cargo.New("FTL456", cargo.RouteSpecification{
Origin: location.AUMEL,
Destination: location.SESTO,
ArrivalDeadline: time.Now().AddDate(0, 0, 7),
})
if err := r.Store(test1); err != nil {
panic(err)
}
test2 := cargo.New("ABC123", cargo.RouteSpecification{
Origin: location.SESTO,
Destination: location.CNHKG,
ArrivalDeadline: time.Now().AddDate(0, 0, 14),
})
if err := r.Store(test2); err != nil {
panic(err)
}
}
type serializedLogger struct {
mtx sync.Mutex
log.Logger
}
func (l *serializedLogger) Log(keyvals ...interface{}) error {
l.mtx.Lock()
defer l.mtx.Unlock()
return l.Logger.Log(keyvals...)
}

View File

@@ -1,118 +0,0 @@
package routing
import (
"encoding/json"
"net/http"
"net/url"
"time"
"golang.org/x/net/context"
"github.com/go-kit/kit/circuitbreaker"
"github.com/go-kit/kit/endpoint"
kithttp "github.com/go-kit/kit/transport/http"
"github.com/go-kit/kit/examples/shipping/cargo"
"github.com/go-kit/kit/examples/shipping/location"
"github.com/go-kit/kit/examples/shipping/voyage"
)
type proxyService struct {
context.Context
FetchRoutesEndpoint endpoint.Endpoint
Service
}
func (s proxyService) FetchRoutesForSpecification(rs cargo.RouteSpecification) []cargo.Itinerary {
response, err := s.FetchRoutesEndpoint(s.Context, fetchRoutesRequest{
From: string(rs.Origin),
To: string(rs.Destination),
})
if err != nil {
return []cargo.Itinerary{}
}
resp := response.(fetchRoutesResponse)
var itineraries []cargo.Itinerary
for _, r := range resp.Paths {
var legs []cargo.Leg
for _, e := range r.Edges {
legs = append(legs, cargo.Leg{
VoyageNumber: voyage.Number(e.Voyage),
LoadLocation: location.UNLocode(e.Origin),
UnloadLocation: location.UNLocode(e.Destination),
LoadTime: e.Departure,
UnloadTime: e.Arrival,
})
}
itineraries = append(itineraries, cargo.Itinerary{Legs: legs})
}
return itineraries
}
// ServiceMiddleware defines a middleware for a routing service.
type ServiceMiddleware func(Service) Service
// NewProxyingMiddleware returns a new instance of a proxying middleware.
func NewProxyingMiddleware(ctx context.Context, proxyURL string) ServiceMiddleware {
return func(next Service) Service {
var e endpoint.Endpoint
e = makeFetchRoutesEndpoint(ctx, proxyURL)
e = circuitbreaker.Hystrix("fetch-routes")(e)
return proxyService{ctx, e, next}
}
}
type fetchRoutesRequest struct {
From string
To string
}
type fetchRoutesResponse struct {
Paths []struct {
Edges []struct {
Origin string `json:"origin"`
Destination string `json:"destination"`
Voyage string `json:"voyage"`
Departure time.Time `json:"departure"`
Arrival time.Time `json:"arrival"`
} `json:"edges"`
} `json:"paths"`
}
func makeFetchRoutesEndpoint(ctx context.Context, instance string) endpoint.Endpoint {
u, err := url.Parse(instance)
if err != nil {
panic(err)
}
if u.Path == "" {
u.Path = "/paths"
}
return kithttp.NewClient(
"GET", u,
encodeFetchRoutesRequest,
decodeFetchRoutesResponse,
).Endpoint()
}
func decodeFetchRoutesResponse(_ context.Context, resp *http.Response) (interface{}, error) {
var response fetchRoutesResponse
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return nil, err
}
return response, nil
}
func encodeFetchRoutesRequest(_ context.Context, r *http.Request, request interface{}) error {
req := request.(fetchRoutesRequest)
vals := r.URL.Query()
vals.Add("from", req.From)
vals.Add("to", req.To)
r.URL.RawQuery = vals.Encode()
return nil
}

View File

@@ -1,13 +0,0 @@
// Package routing provides the routing domain service. It does not actually
// implement the routing service but merely acts as a proxy for a separate
// bounded context.
package routing
import "github.com/go-kit/kit/examples/shipping/cargo"
// Service provides access to an external routing service.
type Service interface {
// FetchRoutesForSpecification finds all possible routes that satisfy a
// given specification.
FetchRoutesForSpecification(rs cargo.RouteSpecification) []cargo.Itinerary
}

View File

@@ -1,26 +0,0 @@
package tracking
import (
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
)
type trackCargoRequest struct {
ID string
}
type trackCargoResponse struct {
Cargo *Cargo `json:"cargo,omitempty"`
Err error `json:"error,omitempty"`
}
func (r trackCargoResponse) error() error { return r.Err }
func makeTrackCargoEndpoint(ts Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(trackCargoRequest)
c, err := ts.Track(req.ID)
return trackCargoResponse{Cargo: &c, Err: err}, nil
}
}

View File

@@ -1,31 +0,0 @@
package tracking
import (
"time"
"github.com/go-kit/kit/metrics"
)
type instrumentingService struct {
requestCount metrics.Counter
requestLatency metrics.Histogram
Service
}
// NewInstrumentingService returns an instance of an instrumenting Service.
func NewInstrumentingService(counter metrics.Counter, latency metrics.Histogram, s Service) Service {
return &instrumentingService{
requestCount: counter,
requestLatency: latency,
Service: s,
}
}
func (s *instrumentingService) Track(id string) (Cargo, error) {
defer func(begin time.Time) {
s.requestCount.With("method", "track").Add(1)
s.requestLatency.With("method", "track").Observe(time.Since(begin).Seconds())
}(time.Now())
return s.Service.Track(id)
}

View File

@@ -1,24 +0,0 @@
package tracking
import (
"time"
"github.com/go-kit/kit/log"
)
type loggingService struct {
logger log.Logger
Service
}
// NewLoggingService returns a new instance of a logging Service.
func NewLoggingService(logger log.Logger, s Service) Service {
return &loggingService{logger, s}
}
func (s *loggingService) Track(id string) (c Cargo, err error) {
defer func(begin time.Time) {
s.logger.Log("method", "track", "tracking_id", id, "took", time.Since(begin), "err", err)
}(time.Now())
return s.Service.Track(id)
}

View File

@@ -1,163 +0,0 @@
// Package tracking provides the use-case of tracking a cargo. Used by views
// facing the end-user.
package tracking
import (
"errors"
"fmt"
"strings"
"time"
"github.com/go-kit/kit/examples/shipping/cargo"
)
// ErrInvalidArgument is returned when one or more arguments are invalid.
var ErrInvalidArgument = errors.New("invalid argument")
// Service is the interface that provides the basic Track method.
type Service interface {
// Track returns a cargo matching a tracking ID.
Track(id string) (Cargo, error)
}
type service struct {
cargos cargo.Repository
handlingEvents cargo.HandlingEventRepository
}
func (s *service) Track(id string) (Cargo, error) {
if id == "" {
return Cargo{}, ErrInvalidArgument
}
c, err := s.cargos.Find(cargo.TrackingID(id))
if err != nil {
return Cargo{}, err
}
return assemble(c, s.handlingEvents), nil
}
// NewService returns a new instance of the default Service.
func NewService(cargos cargo.Repository, events cargo.HandlingEventRepository) Service {
return &service{
cargos: cargos,
handlingEvents: events,
}
}
// Cargo is a read model for tracking views.
type Cargo struct {
TrackingID string `json:"tracking_id"`
StatusText string `json:"status_text"`
Origin string `json:"origin"`
Destination string `json:"destination"`
ETA time.Time `json:"eta"`
NextExpectedActivity string `json:"next_expected_activity"`
ArrivalDeadline time.Time `json:"arrival_deadline"`
Events []Event `json:"events"`
}
// Leg is a read model for booking views.
type Leg struct {
VoyageNumber string `json:"voyage_number"`
From string `json:"from"`
To string `json:"to"`
LoadTime time.Time `json:"load_time"`
UnloadTime time.Time `json:"unload_time"`
}
// Event is a read model for tracking views.
type Event struct {
Description string `json:"description"`
Expected bool `json:"expected"`
}
func assemble(c *cargo.Cargo, events cargo.HandlingEventRepository) Cargo {
return Cargo{
TrackingID: string(c.TrackingID),
Origin: string(c.Origin),
Destination: string(c.RouteSpecification.Destination),
ETA: c.Delivery.ETA,
NextExpectedActivity: nextExpectedActivity(c),
ArrivalDeadline: c.RouteSpecification.ArrivalDeadline,
StatusText: assembleStatusText(c),
Events: assembleEvents(c, events),
}
}
func assembleLegs(c cargo.Cargo) []Leg {
var legs []Leg
for _, l := range c.Itinerary.Legs {
legs = append(legs, Leg{
VoyageNumber: string(l.VoyageNumber),
From: string(l.LoadLocation),
To: string(l.UnloadLocation),
LoadTime: l.LoadTime,
UnloadTime: l.UnloadTime,
})
}
return legs
}
func nextExpectedActivity(c *cargo.Cargo) string {
a := c.Delivery.NextExpectedActivity
prefix := "Next expected activity is to"
switch a.Type {
case cargo.Load:
return fmt.Sprintf("%s %s cargo onto voyage %s in %s.", prefix, strings.ToLower(a.Type.String()), a.VoyageNumber, a.Location)
case cargo.Unload:
return fmt.Sprintf("%s %s cargo off of voyage %s in %s.", prefix, strings.ToLower(a.Type.String()), a.VoyageNumber, a.Location)
case cargo.NotHandled:
return "There are currently no expected activities for this cargo."
}
return fmt.Sprintf("%s %s cargo in %s.", prefix, strings.ToLower(a.Type.String()), a.Location)
}
func assembleStatusText(c *cargo.Cargo) string {
switch c.Delivery.TransportStatus {
case cargo.NotReceived:
return "Not received"
case cargo.InPort:
return fmt.Sprintf("In port %s", c.Delivery.LastKnownLocation)
case cargo.OnboardCarrier:
return fmt.Sprintf("Onboard voyage %s", c.Delivery.CurrentVoyage)
case cargo.Claimed:
return "Claimed"
default:
return "Unknown"
}
}
func assembleEvents(c *cargo.Cargo, handlingEvents cargo.HandlingEventRepository) []Event {
h := handlingEvents.QueryHandlingHistory(c.TrackingID)
var events []Event
for _, e := range h.HandlingEvents {
var description string
switch e.Activity.Type {
case cargo.NotHandled:
description = "Cargo has not yet been received."
case cargo.Receive:
description = fmt.Sprintf("Received in %s, at %s", e.Activity.Location, time.Now().Format(time.RFC3339))
case cargo.Load:
description = fmt.Sprintf("Loaded onto voyage %s in %s, at %s.", e.Activity.VoyageNumber, e.Activity.Location, time.Now().Format(time.RFC3339))
case cargo.Unload:
description = fmt.Sprintf("Unloaded off voyage %s in %s, at %s.", e.Activity.VoyageNumber, e.Activity.Location, time.Now().Format(time.RFC3339))
case cargo.Claim:
description = fmt.Sprintf("Claimed in %s, at %s.", e.Activity.Location, time.Now().Format(time.RFC3339))
case cargo.Customs:
description = fmt.Sprintf("Cleared customs in %s, at %s.", e.Activity.Location, time.Now().Format(time.RFC3339))
default:
description = "[Unknown status]"
}
events = append(events, Event{
Description: description,
Expected: c.Itinerary.IsExpected(e),
})
}
return events
}

View File

@@ -1,75 +0,0 @@
package tracking
import (
"encoding/json"
"errors"
"net/http"
"github.com/gorilla/mux"
"golang.org/x/net/context"
kitlog "github.com/go-kit/kit/log"
kithttp "github.com/go-kit/kit/transport/http"
"github.com/go-kit/kit/examples/shipping/cargo"
)
// MakeHandler returns a handler for the tracking service.
func MakeHandler(ctx context.Context, ts Service, logger kitlog.Logger) http.Handler {
r := mux.NewRouter()
opts := []kithttp.ServerOption{
kithttp.ServerErrorLogger(logger),
kithttp.ServerErrorEncoder(encodeError),
}
trackCargoHandler := kithttp.NewServer(
ctx,
makeTrackCargoEndpoint(ts),
decodeTrackCargoRequest,
encodeResponse,
opts...,
)
r.Handle("/tracking/v1/cargos/{id}", trackCargoHandler).Methods("GET")
return r
}
func decodeTrackCargoRequest(_ context.Context, r *http.Request) (interface{}, error) {
vars := mux.Vars(r)
id, ok := vars["id"]
if !ok {
return nil, errors.New("bad route")
}
return trackCargoRequest{ID: id}, nil
}
func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
if e, ok := response.(errorer); ok && e.error() != nil {
encodeError(ctx, e.error(), w)
return nil
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
return json.NewEncoder(w).Encode(response)
}
type errorer interface {
error() error
}
// encode errors from business-logic
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
switch err {
case cargo.ErrUnknown:
w.WriteHeader(http.StatusNotFound)
case ErrInvalidArgument:
w.WriteHeader(http.StatusBadRequest)
default:
w.WriteHeader(http.StatusInternalServerError)
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(map[string]interface{}{
"error": err.Error(),
})
}

View File

@@ -1,40 +0,0 @@
package voyage
import "github.com/go-kit/kit/examples/shipping/location"
// A set of sample voyages.
var (
V100 = New("V100", Schedule{
[]CarrierMovement{
{DepartureLocation: location.CNHKG, ArrivalLocation: location.JNTKO},
{DepartureLocation: location.JNTKO, ArrivalLocation: location.USNYC},
},
})
V300 = New("V300", Schedule{
[]CarrierMovement{
{DepartureLocation: location.JNTKO, ArrivalLocation: location.NLRTM},
{DepartureLocation: location.NLRTM, ArrivalLocation: location.DEHAM},
{DepartureLocation: location.DEHAM, ArrivalLocation: location.AUMEL},
{DepartureLocation: location.AUMEL, ArrivalLocation: location.JNTKO},
},
})
V400 = New("V400", Schedule{
[]CarrierMovement{
{DepartureLocation: location.DEHAM, ArrivalLocation: location.SESTO},
{DepartureLocation: location.SESTO, ArrivalLocation: location.FIHEL},
{DepartureLocation: location.FIHEL, ArrivalLocation: location.DEHAM},
},
})
)
// These voyages are hard-coded into the current pathfinder. Make sure
// they exist.
var (
V0100S = New("0100S", Schedule{[]CarrierMovement{}})
V0200T = New("0200T", Schedule{[]CarrierMovement{}})
V0300A = New("0300A", Schedule{[]CarrierMovement{}})
V0301S = New("0301S", Schedule{[]CarrierMovement{}})
V0400S = New("0400S", Schedule{[]CarrierMovement{}})
)

View File

@@ -1,44 +0,0 @@
// Package voyage provides the Voyage aggregate.
package voyage
import (
"errors"
"time"
"github.com/go-kit/kit/examples/shipping/location"
)
// Number uniquely identifies a particular Voyage.
type Number string
// Voyage is a uniquely identifiable series of carrier movements.
type Voyage struct {
Number Number
Schedule Schedule
}
// New creates a voyage with a voyage number and a provided schedule.
func New(n Number, s Schedule) *Voyage {
return &Voyage{Number: n, Schedule: s}
}
// Schedule describes a voyage schedule.
type Schedule struct {
CarrierMovements []CarrierMovement
}
// CarrierMovement is a vessel voyage from one location to another.
type CarrierMovement struct {
DepartureLocation location.UNLocode
ArrivalLocation location.UNLocode
DepartureTime time.Time
ArrivalTime time.Time
}
// ErrUnknown is used when a voyage could not be found.
var ErrUnknown = errors.New("unknown voyage")
// Repository provides access a voyage store.
type Repository interface {
Find(Number) (*Voyage, error)
}

View File

@@ -1,115 +0,0 @@
package main
import (
"encoding/json"
"errors"
"log"
"net/http"
"strings"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
httptransport "github.com/go-kit/kit/transport/http"
)
// StringService provides operations on strings.
type StringService interface {
Uppercase(string) (string, error)
Count(string) int
}
type stringService struct{}
func (stringService) Uppercase(s string) (string, error) {
if s == "" {
return "", ErrEmpty
}
return strings.ToUpper(s), nil
}
func (stringService) Count(s string) int {
return len(s)
}
func main() {
ctx := context.Background()
svc := stringService{}
uppercaseHandler := httptransport.NewServer(
ctx,
makeUppercaseEndpoint(svc),
decodeUppercaseRequest,
encodeResponse,
)
countHandler := httptransport.NewServer(
ctx,
makeCountEndpoint(svc),
decodeCountRequest,
encodeResponse,
)
http.Handle("/uppercase", uppercaseHandler)
http.Handle("/count", countHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func makeUppercaseEndpoint(svc StringService) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(uppercaseRequest)
v, err := svc.Uppercase(req.S)
if err != nil {
return uppercaseResponse{v, err.Error()}, nil
}
return uppercaseResponse{v, ""}, nil
}
}
func makeCountEndpoint(svc StringService) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(countRequest)
v := svc.Count(req.S)
return countResponse{v}, nil
}
}
func decodeUppercaseRequest(_ context.Context, r *http.Request) (interface{}, error) {
var request uppercaseRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return nil, err
}
return request, nil
}
func decodeCountRequest(_ context.Context, r *http.Request) (interface{}, error) {
var request countRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return nil, err
}
return request, nil
}
func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
return json.NewEncoder(w).Encode(response)
}
type uppercaseRequest struct {
S string `json:"s"`
}
type uppercaseResponse struct {
V string `json:"v"`
Err string `json:"err,omitempty"` // errors don't define JSON marshaling
}
type countRequest struct {
S string `json:"s"`
}
type countResponse struct {
V int `json:"v"`
}
// ErrEmpty is returned when an input string is empty.
var ErrEmpty = errors.New("empty string")

View File

@@ -1,38 +0,0 @@
package main
import (
"fmt"
"time"
"github.com/go-kit/kit/metrics"
)
type instrumentingMiddleware struct {
requestCount metrics.Counter
requestLatency metrics.Histogram
countResult metrics.Histogram
next StringService
}
func (mw instrumentingMiddleware) Uppercase(s string) (output string, err error) {
defer func(begin time.Time) {
lvs := []string{"method", "uppercase", "error", fmt.Sprint(err != nil)}
mw.requestCount.With(lvs...).Add(1)
mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
}(time.Now())
output, err = mw.next.Uppercase(s)
return
}
func (mw instrumentingMiddleware) Count(s string) (n int) {
defer func(begin time.Time) {
lvs := []string{"method", "count", "error", "false"}
mw.requestCount.With(lvs...).Add(1)
mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
mw.countResult.Observe(float64(n))
}(time.Now())
n = mw.next.Count(s)
return
}

View File

@@ -1,41 +0,0 @@
package main
import (
"time"
"github.com/go-kit/kit/log"
)
type loggingMiddleware struct {
logger log.Logger
next StringService
}
func (mw loggingMiddleware) Uppercase(s string) (output string, err error) {
defer func(begin time.Time) {
_ = mw.logger.Log(
"method", "uppercase",
"input", s,
"output", output,
"err", err,
"took", time.Since(begin),
)
}(time.Now())
output, err = mw.next.Uppercase(s)
return
}
func (mw loggingMiddleware) Count(s string) (n int) {
defer func(begin time.Time) {
_ = mw.logger.Log(
"method", "count",
"input", s,
"n", n,
"took", time.Since(begin),
)
}(time.Now())
n = mw.next.Count(s)
return
}

View File

@@ -1,63 +0,0 @@
package main
import (
"net/http"
"os"
stdprometheus "github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
"github.com/go-kit/kit/log"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
httptransport "github.com/go-kit/kit/transport/http"
)
func main() {
ctx := context.Background()
logger := log.NewLogfmtLogger(os.Stderr)
fieldKeys := []string{"method", "error"}
requestCount := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "my_group",
Subsystem: "string_service",
Name: "request_count",
Help: "Number of requests received.",
}, fieldKeys)
requestLatency := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
Namespace: "my_group",
Subsystem: "string_service",
Name: "request_latency_microseconds",
Help: "Total duration of requests in microseconds.",
}, fieldKeys)
countResult := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
Namespace: "my_group",
Subsystem: "string_service",
Name: "count_result",
Help: "The result of each count method.",
}, []string{}) // no fields here
var svc StringService
svc = stringService{}
svc = loggingMiddleware{logger, svc}
svc = instrumentingMiddleware{requestCount, requestLatency, countResult, svc}
uppercaseHandler := httptransport.NewServer(
ctx,
makeUppercaseEndpoint(svc),
decodeUppercaseRequest,
encodeResponse,
)
countHandler := httptransport.NewServer(
ctx,
makeCountEndpoint(svc),
decodeCountRequest,
encodeResponse,
)
http.Handle("/uppercase", uppercaseHandler)
http.Handle("/count", countHandler)
http.Handle("/metrics", stdprometheus.Handler())
logger.Log("msg", "HTTP", "addr", ":8080")
logger.Log("err", http.ListenAndServe(":8080", nil))
}

View File

@@ -1,28 +0,0 @@
package main
import (
"errors"
"strings"
)
// StringService provides operations on strings.
type StringService interface {
Uppercase(string) (string, error)
Count(string) int
}
type stringService struct{}
func (stringService) Uppercase(s string) (string, error) {
if s == "" {
return "", ErrEmpty
}
return strings.ToUpper(s), nil
}
func (stringService) Count(s string) int {
return len(s)
}
// ErrEmpty is returned when an input string is empty.
var ErrEmpty = errors.New("empty string")

View File

@@ -1,66 +0,0 @@
package main
import (
"encoding/json"
"net/http"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
)
func makeUppercaseEndpoint(svc StringService) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(uppercaseRequest)
v, err := svc.Uppercase(req.S)
if err != nil {
return uppercaseResponse{v, err.Error()}, nil
}
return uppercaseResponse{v, ""}, nil
}
}
func makeCountEndpoint(svc StringService) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(countRequest)
v := svc.Count(req.S)
return countResponse{v}, nil
}
}
func decodeUppercaseRequest(_ context.Context, r *http.Request) (interface{}, error) {
var request uppercaseRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return nil, err
}
return request, nil
}
func decodeCountRequest(_ context.Context, r *http.Request) (interface{}, error) {
var request countRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return nil, err
}
return request, nil
}
func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
return json.NewEncoder(w).Encode(response)
}
type uppercaseRequest struct {
S string `json:"s"`
}
type uppercaseResponse struct {
V string `json:"v"`
Err string `json:"err,omitempty"`
}
type countRequest struct {
S string `json:"s"`
}
type countResponse struct {
V int `json:"v"`
}

View File

@@ -1,48 +0,0 @@
package main
import (
"fmt"
"time"
"github.com/go-kit/kit/metrics"
)
func instrumentingMiddleware(
requestCount metrics.Counter,
requestLatency metrics.Histogram,
countResult metrics.Histogram,
) ServiceMiddleware {
return func(next StringService) StringService {
return instrmw{requestCount, requestLatency, countResult, next}
}
}
type instrmw struct {
requestCount metrics.Counter
requestLatency metrics.Histogram
countResult metrics.Histogram
StringService
}
func (mw instrmw) Uppercase(s string) (output string, err error) {
defer func(begin time.Time) {
lvs := []string{"method", "uppercase", "error", fmt.Sprint(err != nil)}
mw.requestCount.With(lvs...).Add(1)
mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
}(time.Now())
output, err = mw.StringService.Uppercase(s)
return
}
func (mw instrmw) Count(s string) (n int) {
defer func(begin time.Time) {
lvs := []string{"method", "count", "error", "false"}
mw.requestCount.With(lvs...).Add(1)
mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
mw.countResult.Observe(float64(n))
}(time.Now())
n = mw.StringService.Count(s)
return
}

View File

@@ -1,47 +0,0 @@
package main
import (
"time"
"github.com/go-kit/kit/log"
)
func loggingMiddleware(logger log.Logger) ServiceMiddleware {
return func(next StringService) StringService {
return logmw{logger, next}
}
}
type logmw struct {
logger log.Logger
StringService
}
func (mw logmw) Uppercase(s string) (output string, err error) {
defer func(begin time.Time) {
_ = mw.logger.Log(
"method", "uppercase",
"input", s,
"output", output,
"err", err,
"took", time.Since(begin),
)
}(time.Now())
output, err = mw.StringService.Uppercase(s)
return
}
func (mw logmw) Count(s string) (n int) {
defer func(begin time.Time) {
_ = mw.logger.Log(
"method", "count",
"input", s,
"n", n,
"took", time.Since(begin),
)
}(time.Now())
n = mw.StringService.Count(s)
return
}

View File

@@ -1,73 +0,0 @@
package main
import (
"flag"
"net/http"
"os"
stdprometheus "github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
"github.com/go-kit/kit/log"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
httptransport "github.com/go-kit/kit/transport/http"
)
func main() {
var (
listen = flag.String("listen", ":8080", "HTTP listen address")
proxy = flag.String("proxy", "", "Optional comma-separated list of URLs to proxy uppercase requests")
)
flag.Parse()
var logger log.Logger
logger = log.NewLogfmtLogger(os.Stderr)
logger = log.NewContext(logger).With("listen", *listen).With("caller", log.DefaultCaller)
ctx := context.Background()
fieldKeys := []string{"method", "error"}
requestCount := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: "my_group",
Subsystem: "string_service",
Name: "request_count",
Help: "Number of requests received.",
}, fieldKeys)
requestLatency := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
Namespace: "my_group",
Subsystem: "string_service",
Name: "request_latency_microseconds",
Help: "Total duration of requests in microseconds.",
}, fieldKeys)
countResult := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
Namespace: "my_group",
Subsystem: "string_service",
Name: "count_result",
Help: "The result of each count method.",
}, []string{})
var svc StringService
svc = stringService{}
svc = proxyingMiddleware(*proxy, ctx, logger)(svc)
svc = loggingMiddleware(logger)(svc)
svc = instrumentingMiddleware(requestCount, requestLatency, countResult)(svc)
uppercaseHandler := httptransport.NewServer(
ctx,
makeUppercaseEndpoint(svc),
decodeUppercaseRequest,
encodeResponse,
)
countHandler := httptransport.NewServer(
ctx,
makeCountEndpoint(svc),
decodeCountRequest,
encodeResponse,
)
http.Handle("/uppercase", uppercaseHandler)
http.Handle("/count", countHandler)
http.Handle("/metrics", stdprometheus.Handler())
logger.Log("msg", "HTTP", "addr", *listen)
logger.Log("err", http.ListenAndServe(*listen, nil))
}

View File

@@ -1,116 +0,0 @@
package main
import (
"errors"
"fmt"
"net/url"
"strings"
"time"
jujuratelimit "github.com/juju/ratelimit"
"github.com/sony/gobreaker"
"golang.org/x/net/context"
"github.com/go-kit/kit/circuitbreaker"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/ratelimit"
"github.com/go-kit/kit/sd"
"github.com/go-kit/kit/sd/lb"
httptransport "github.com/go-kit/kit/transport/http"
)
func proxyingMiddleware(instances string, ctx context.Context, logger log.Logger) ServiceMiddleware {
// If instances is empty, don't proxy.
if instances == "" {
logger.Log("proxy_to", "none")
return func(next StringService) StringService { return next }
}
// Set some parameters for our client.
var (
qps = 100 // beyond which we will return an error
maxAttempts = 3 // per request, before giving up
maxTime = 250 * time.Millisecond // wallclock time, before giving up
)
// Otherwise, construct an endpoint for each instance in the list, and add
// it to a fixed set of endpoints. In a real service, rather than doing this
// by hand, you'd probably use package sd's support for your service
// discovery system.
var (
instanceList = split(instances)
subscriber sd.FixedSubscriber
)
logger.Log("proxy_to", fmt.Sprint(instanceList))
for _, instance := range instanceList {
var e endpoint.Endpoint
e = makeUppercaseProxy(ctx, instance)
e = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{}))(e)
e = ratelimit.NewTokenBucketLimiter(jujuratelimit.NewBucketWithRate(float64(qps), int64(qps)))(e)
subscriber = append(subscriber, e)
}
// Now, build a single, retrying, load-balancing endpoint out of all of
// those individual endpoints.
balancer := lb.NewRoundRobin(subscriber)
retry := lb.Retry(maxAttempts, maxTime, balancer)
// And finally, return the ServiceMiddleware, implemented by proxymw.
return func(next StringService) StringService {
return proxymw{ctx, next, retry}
}
}
// proxymw implements StringService, forwarding Uppercase requests to the
// provided endpoint, and serving all other (i.e. Count) requests via the
// next StringService.
type proxymw struct {
ctx context.Context
next StringService // Serve most requests via this service...
uppercase endpoint.Endpoint // ...except Uppercase, which gets served by this endpoint
}
func (mw proxymw) Count(s string) int {
return mw.next.Count(s)
}
func (mw proxymw) Uppercase(s string) (string, error) {
response, err := mw.uppercase(mw.ctx, uppercaseRequest{S: s})
if err != nil {
return "", err
}
resp := response.(uppercaseResponse)
if resp.Err != "" {
return resp.V, errors.New(resp.Err)
}
return resp.V, nil
}
func makeUppercaseProxy(ctx context.Context, instance string) endpoint.Endpoint {
if !strings.HasPrefix(instance, "http") {
instance = "http://" + instance
}
u, err := url.Parse(instance)
if err != nil {
panic(err)
}
if u.Path == "" {
u.Path = "/uppercase"
}
return httptransport.NewClient(
"GET",
u,
encodeRequest,
decodeUppercaseResponse,
).Endpoint()
}
func split(s string) []string {
a := strings.Split(s, ",")
for i := range a {
a[i] = strings.TrimSpace(a[i])
}
return a
}

View File

@@ -1,31 +0,0 @@
package main
import (
"errors"
"strings"
)
// StringService provides operations on strings.
type StringService interface {
Uppercase(string) (string, error)
Count(string) int
}
type stringService struct{}
func (stringService) Uppercase(s string) (string, error) {
if s == "" {
return "", ErrEmpty
}
return strings.ToUpper(s), nil
}
func (stringService) Count(s string) int {
return len(s)
}
// ErrEmpty is returned when an input string is empty.
var ErrEmpty = errors.New("empty string")
// ServiceMiddleware is a chainable behavior modifier for StringService.
type ServiceMiddleware func(StringService) StringService

View File

@@ -1,85 +0,0 @@
package main
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
)
func makeUppercaseEndpoint(svc StringService) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(uppercaseRequest)
v, err := svc.Uppercase(req.S)
if err != nil {
return uppercaseResponse{v, err.Error()}, nil
}
return uppercaseResponse{v, ""}, nil
}
}
func makeCountEndpoint(svc StringService) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(countRequest)
v := svc.Count(req.S)
return countResponse{v}, nil
}
}
func decodeUppercaseRequest(_ context.Context, r *http.Request) (interface{}, error) {
var request uppercaseRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return nil, err
}
return request, nil
}
func decodeCountRequest(_ context.Context, r *http.Request) (interface{}, error) {
var request countRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return nil, err
}
return request, nil
}
func decodeUppercaseResponse(_ context.Context, r *http.Response) (interface{}, error) {
var response uppercaseResponse
if err := json.NewDecoder(r.Body).Decode(&response); err != nil {
return nil, err
}
return response, nil
}
func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
return json.NewEncoder(w).Encode(response)
}
func encodeRequest(_ context.Context, r *http.Request, request interface{}) error {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(request); err != nil {
return err
}
r.Body = ioutil.NopCloser(&buf)
return nil
}
type uppercaseRequest struct {
S string `json:"s"`
}
type uppercaseResponse struct {
V string `json:"v"`
Err string `json:"err,omitempty"`
}
type countRequest struct {
S string `json:"s"`
}
type countResponse struct {
V int `json:"v"`
}

View File

@@ -1,21 +0,0 @@
package log_test
import (
"testing"
"github.com/go-kit/kit/log"
)
func benchmarkRunner(b *testing.B, logger log.Logger, f func(log.Logger)) {
lc := log.NewContext(logger).With("common_key", "common_value")
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
f(lc)
}
}
var (
baseMessage = func(logger log.Logger) { logger.Log("foo_key", "foo_value") }
withMessage = func(logger log.Logger) { log.NewContext(logger).With("a", "b").Log("c", "d") }
)

View File

@@ -1,40 +0,0 @@
package log_test
import (
"math"
"testing"
"github.com/go-kit/kit/log"
)
// These test are designed to be run with the race detector.
func testConcurrency(t *testing.T, logger log.Logger, total int) {
n := int(math.Sqrt(float64(total)))
share := total / n
errC := make(chan error, n)
for i := 0; i < n; i++ {
go func() {
errC <- spam(logger, share)
}()
}
for i := 0; i < n; i++ {
err := <-errC
if err != nil {
t.Fatalf("concurrent logging error: %v", err)
}
}
}
func spam(logger log.Logger, count int) error {
for i := 0; i < count; i++ {
err := logger.Log("key", i)
if err != nil {
return err
}
}
return nil
}

View File

@@ -1,102 +0,0 @@
package log_test
import (
"os"
"time"
"github.com/go-kit/kit/log"
)
func Example_basic() {
logger := log.NewLogfmtLogger(os.Stdout)
type Task struct {
ID int
}
RunTask := func(task Task, logger log.Logger) {
logger.Log("taskID", task.ID, "event", "starting task")
logger.Log("taskID", task.ID, "event", "task complete")
}
RunTask(Task{ID: 1}, logger)
// Output:
// taskID=1 event="starting task"
// taskID=1 event="task complete"
}
func Example_context() {
logger := log.NewLogfmtLogger(os.Stdout)
type Task struct {
ID int
Cmd string
}
taskHelper := func(cmd string, logger log.Logger) {
// execute(cmd)
logger.Log("cmd", cmd, "dur", 42*time.Millisecond)
}
RunTask := func(task Task, logger log.Logger) {
logger = log.NewContext(logger).With("taskID", task.ID)
logger.Log("event", "starting task")
taskHelper(task.Cmd, logger)
logger.Log("event", "task complete")
}
RunTask(Task{ID: 1, Cmd: "echo Hello, world!"}, logger)
// Output:
// taskID=1 event="starting task"
// taskID=1 cmd="echo Hello, world!" dur=42ms
// taskID=1 event="task complete"
}
func Example_valuer() {
logger := log.NewLogfmtLogger(os.Stdout)
count := 0
counter := func() interface{} {
count++
return count
}
logger = log.NewContext(logger).With("count", log.Valuer(counter))
logger.Log("call", "first")
logger.Log("call", "second")
// Output:
// count=1 call=first
// count=2 call=second
}
func Example_debugInfo() {
logger := log.NewLogfmtLogger(os.Stdout)
// make time predictable for this test
baseTime := time.Date(2015, time.February, 3, 10, 0, 0, 0, time.UTC)
mockTime := func() time.Time {
baseTime = baseTime.Add(time.Second)
return baseTime
}
logger = log.NewContext(logger).With("time", log.Timestamp(mockTime), "caller", log.DefaultCaller)
logger.Log("call", "first")
logger.Log("call", "second")
// ...
logger.Log("call", "third")
// Output:
// time=2015-02-03T10:00:01Z caller=example_test.go:91 call=first
// time=2015-02-03T10:00:02Z caller=example_test.go:92 call=second
// time=2015-02-03T10:00:03Z caller=example_test.go:96 call=third
}

View File

@@ -1,65 +0,0 @@
package level_test
import (
"io/ioutil"
"testing"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/experimental_level"
)
func BenchmarkNopBaseline(b *testing.B) {
benchmarkRunner(b, log.NewNopLogger())
}
func BenchmarkNopDisallowedLevel(b *testing.B) {
benchmarkRunner(b, level.New(log.NewNopLogger(), level.Config{
Allowed: level.AllowInfoAndAbove(),
}))
}
func BenchmarkNopAllowedLevel(b *testing.B) {
benchmarkRunner(b, level.New(log.NewNopLogger(), level.Config{
Allowed: level.AllowAll(),
}))
}
func BenchmarkJSONBaseline(b *testing.B) {
benchmarkRunner(b, log.NewJSONLogger(ioutil.Discard))
}
func BenchmarkJSONDisallowedLevel(b *testing.B) {
benchmarkRunner(b, level.New(log.NewJSONLogger(ioutil.Discard), level.Config{
Allowed: level.AllowInfoAndAbove(),
}))
}
func BenchmarkJSONAllowedLevel(b *testing.B) {
benchmarkRunner(b, level.New(log.NewJSONLogger(ioutil.Discard), level.Config{
Allowed: level.AllowAll(),
}))
}
func BenchmarkLogfmtBaseline(b *testing.B) {
benchmarkRunner(b, log.NewLogfmtLogger(ioutil.Discard))
}
func BenchmarkLogfmtDisallowedLevel(b *testing.B) {
benchmarkRunner(b, level.New(log.NewLogfmtLogger(ioutil.Discard), level.Config{
Allowed: level.AllowInfoAndAbove(),
}))
}
func BenchmarkLogfmtAllowedLevel(b *testing.B) {
benchmarkRunner(b, level.New(log.NewLogfmtLogger(ioutil.Discard), level.Config{
Allowed: level.AllowAll(),
}))
}
func benchmarkRunner(b *testing.B, logger log.Logger) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
level.Debug(logger).Log("foo", "bar")
}
}

View File

@@ -1,27 +0,0 @@
// Package level is an EXPERIMENTAL levelled logging package. The API will
// definitely have breaking changes and may be deleted altogether. Be warned!
//
// To use the level package, create a logger as per normal in your func main,
// and wrap it with level.New.
//
// var logger log.Logger
// logger = log.NewLogfmtLogger(os.Stderr)
// logger = level.New(logger, level.Config{Allowed: level.AllowInfoAndAbove}) // <--
// logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
//
// Then, at the callsites, use one of the level.Debug, Info, Warn, or Error
// helper methods to emit leveled log events.
//
// logger.Log("foo", "bar") // as normal, no level
// level.Debug(logger).Log("request_id", reqID, "trace_data", trace.Get())
// if value > 100 {
// level.Error(logger).Log("value", value)
// }
//
// The leveled logger allows precise control over what should happen if a log
// event is emitted without a level key, or if a squelched level is used. Check
// the Config struct for details. And, you can easily use non-default level
// values: create new string constants for whatever you want to change, pass
// them explicitly to the Config struct, and write your own level.Foo-style
// helper methods.
package level

View File

@@ -1,146 +0,0 @@
package level
import (
"github.com/go-kit/kit/log"
)
var (
levelKey = "level"
errorLevelValue = "error"
warnLevelValue = "warn"
infoLevelValue = "info"
debugLevelValue = "debug"
)
// AllowAll is an alias for AllowDebugAndAbove.
func AllowAll() []string {
return AllowDebugAndAbove()
}
// AllowDebugAndAbove allows all of the four default log levels.
// Its return value may be provided as the Allowed parameter in the Config.
func AllowDebugAndAbove() []string {
return []string{errorLevelValue, warnLevelValue, infoLevelValue, debugLevelValue}
}
// AllowInfoAndAbove allows the default info, warn, and error log levels.
// Its return value may be provided as the Allowed parameter in the Config.
func AllowInfoAndAbove() []string {
return []string{errorLevelValue, warnLevelValue, infoLevelValue}
}
// AllowWarnAndAbove allows the default warn and error log levels.
// Its return value may be provided as the Allowed parameter in the Config.
func AllowWarnAndAbove() []string {
return []string{errorLevelValue, warnLevelValue}
}
// AllowErrorOnly allows only the default error log level.
// Its return value may be provided as the Allowed parameter in the Config.
func AllowErrorOnly() []string {
return []string{errorLevelValue}
}
// AllowNone allows none of the default log levels.
// Its return value may be provided as the Allowed parameter in the Config.
func AllowNone() []string {
return []string{}
}
// Error returns a logger with the level key set to ErrorLevelValue.
func Error(logger log.Logger) log.Logger {
return log.NewContext(logger).With(levelKey, errorLevelValue)
}
// Warn returns a logger with the level key set to WarnLevelValue.
func Warn(logger log.Logger) log.Logger {
return log.NewContext(logger).With(levelKey, warnLevelValue)
}
// Info returns a logger with the level key set to InfoLevelValue.
func Info(logger log.Logger) log.Logger {
return log.NewContext(logger).With(levelKey, infoLevelValue)
}
// Debug returns a logger with the level key set to DebugLevelValue.
func Debug(logger log.Logger) log.Logger {
return log.NewContext(logger).With(levelKey, debugLevelValue)
}
// Config parameterizes the leveled logger.
type Config struct {
// Allowed enumerates the accepted log levels. If a log event is encountered
// with a level key set to a value that isn't explicitly allowed, the event
// will be squelched, and ErrNotAllowed returned.
Allowed []string
// ErrNotAllowed is returned to the caller when Log is invoked with a level
// key that hasn't been explicitly allowed. By default, ErrNotAllowed is
// nil; in this case, the log event is squelched with no error.
ErrNotAllowed error
// SquelchNoLevel will squelch log events with no level key, so that they
// don't proceed through to the wrapped logger. If SquelchNoLevel is set to
// true and a log event is squelched in this way, ErrNoLevel is returned to
// the caller.
SquelchNoLevel bool
// ErrNoLevel is returned to the caller when SquelchNoLevel is true, and Log
// is invoked without a level key. By default, ErrNoLevel is nil; in this
// case, the log event is squelched with no error.
ErrNoLevel error
}
// New wraps the logger and implements level checking. See the commentary on the
// Config object for a detailed description of how to configure levels.
func New(next log.Logger, config Config) log.Logger {
return &logger{
next: next,
allowed: makeSet(config.Allowed),
errNotAllowed: config.ErrNotAllowed,
squelchNoLevel: config.SquelchNoLevel,
errNoLevel: config.ErrNoLevel,
}
}
type logger struct {
next log.Logger
allowed map[string]struct{}
errNotAllowed error
squelchNoLevel bool
errNoLevel error
}
func (l *logger) Log(keyvals ...interface{}) error {
var hasLevel, levelAllowed bool
for i := 0; i < len(keyvals); i += 2 {
if k, ok := keyvals[i].(string); !ok || k != levelKey {
continue
}
hasLevel = true
if i >= len(keyvals) {
continue
}
v, ok := keyvals[i+1].(string)
if !ok {
continue
}
_, levelAllowed = l.allowed[v]
break
}
if !hasLevel && l.squelchNoLevel {
return l.errNoLevel
}
if hasLevel && !levelAllowed {
return l.errNotAllowed
}
return l.next.Log(keyvals...)
}
func makeSet(a []string) map[string]struct{} {
m := make(map[string]struct{}, len(a))
for _, s := range a {
m[s] = struct{}{}
}
return m
}

View File

@@ -1,154 +0,0 @@
package level_test
import (
"bytes"
"errors"
"strings"
"testing"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/experimental_level"
)
func TestVariousLevels(t *testing.T) {
for _, testcase := range []struct {
allowed []string
want string
}{
{
level.AllowAll(),
strings.Join([]string{
`{"level":"debug","this is":"debug log"}`,
`{"level":"info","this is":"info log"}`,
`{"level":"warn","this is":"warn log"}`,
`{"level":"error","this is":"error log"}`,
}, "\n"),
},
{
level.AllowDebugAndAbove(),
strings.Join([]string{
`{"level":"debug","this is":"debug log"}`,
`{"level":"info","this is":"info log"}`,
`{"level":"warn","this is":"warn log"}`,
`{"level":"error","this is":"error log"}`,
}, "\n"),
},
{
level.AllowInfoAndAbove(),
strings.Join([]string{
`{"level":"info","this is":"info log"}`,
`{"level":"warn","this is":"warn log"}`,
`{"level":"error","this is":"error log"}`,
}, "\n"),
},
{
level.AllowWarnAndAbove(),
strings.Join([]string{
`{"level":"warn","this is":"warn log"}`,
`{"level":"error","this is":"error log"}`,
}, "\n"),
},
{
level.AllowErrorOnly(),
strings.Join([]string{
`{"level":"error","this is":"error log"}`,
}, "\n"),
},
{
level.AllowNone(),
``,
},
} {
var buf bytes.Buffer
logger := level.New(log.NewJSONLogger(&buf), level.Config{Allowed: testcase.allowed})
level.Debug(logger).Log("this is", "debug log")
level.Info(logger).Log("this is", "info log")
level.Warn(logger).Log("this is", "warn log")
level.Error(logger).Log("this is", "error log")
if want, have := testcase.want, strings.TrimSpace(buf.String()); want != have {
t.Errorf("given Allowed=%v: want\n%s\nhave\n%s", testcase.allowed, want, have)
}
}
}
func TestErrNotAllowed(t *testing.T) {
myError := errors.New("squelched!")
logger := level.New(log.NewNopLogger(), level.Config{
Allowed: level.AllowWarnAndAbove(),
ErrNotAllowed: myError,
})
if want, have := myError, level.Info(logger).Log("foo", "bar"); want != have {
t.Errorf("want %#+v, have %#+v", want, have)
}
if want, have := error(nil), level.Warn(logger).Log("foo", "bar"); want != have {
t.Errorf("want %#+v, have %#+v", want, have)
}
}
func TestErrNoLevel(t *testing.T) {
myError := errors.New("no level specified")
var buf bytes.Buffer
logger := level.New(log.NewJSONLogger(&buf), level.Config{
SquelchNoLevel: true,
ErrNoLevel: myError,
})
if want, have := myError, logger.Log("foo", "bar"); want != have {
t.Errorf("want %v, have %v", want, have)
}
if want, have := ``, strings.TrimSpace(buf.String()); want != have {
t.Errorf("want %q, have %q", want, have)
}
}
func TestAllowNoLevel(t *testing.T) {
var buf bytes.Buffer
logger := level.New(log.NewJSONLogger(&buf), level.Config{
SquelchNoLevel: false,
ErrNoLevel: errors.New("I should never be returned!"),
})
if want, have := error(nil), logger.Log("foo", "bar"); want != have {
t.Errorf("want %v, have %v", want, have)
}
if want, have := `{"foo":"bar"}`, strings.TrimSpace(buf.String()); want != have {
t.Errorf("want %q, have %q", want, have)
}
}
func TestLevelContext(t *testing.T) {
var buf bytes.Buffer
// Wrapping the level logger with a context allows users to use
// log.DefaultCaller as per normal.
var logger log.Logger
logger = log.NewLogfmtLogger(&buf)
logger = level.New(logger, level.Config{Allowed: level.AllowAll()})
logger = log.NewContext(logger).With("caller", log.DefaultCaller)
level.Info(logger).Log("foo", "bar")
if want, have := `caller=level_test.go:134 level=info foo=bar`, strings.TrimSpace(buf.String()); want != have {
t.Errorf("want %q, have %q", want, have)
}
}
func TestContextLevel(t *testing.T) {
var buf bytes.Buffer
// Wrapping a context with the level logger still works, but requires users
// to specify a higher callstack depth value.
var logger log.Logger
logger = log.NewLogfmtLogger(&buf)
logger = log.NewContext(logger).With("caller", log.Caller(5))
logger = level.New(logger, level.Config{Allowed: level.AllowAll()})
level.Info(logger).Log("foo", "bar")
if want, have := `caller=level_test.go:150 level=info foo=bar`, strings.TrimSpace(buf.String()); want != have {
t.Errorf("want %q, have %q", want, have)
}
}

View File

@@ -1,158 +0,0 @@
package log_test
import (
"bytes"
"errors"
"io/ioutil"
"testing"
"github.com/go-kit/kit/log"
)
func TestJSONLoggerCaller(t *testing.T) {
t.Parallel()
buf := &bytes.Buffer{}
logger := log.NewJSONLogger(buf)
logger = log.NewContext(logger).With("caller", log.DefaultCaller)
if err := logger.Log(); err != nil {
t.Fatal(err)
}
if want, have := `{"caller":"json_logger_test.go:18"}`+"\n", buf.String(); want != have {
t.Errorf("\nwant %#v\nhave %#v", want, have)
}
}
func TestJSONLogger(t *testing.T) {
t.Parallel()
buf := &bytes.Buffer{}
logger := log.NewJSONLogger(buf)
if err := logger.Log("err", errors.New("err"), "m", map[string]int{"0": 0}, "a", []int{1, 2, 3}); err != nil {
t.Fatal(err)
}
if want, have := `{"a":[1,2,3],"err":"err","m":{"0":0}}`+"\n", buf.String(); want != have {
t.Errorf("\nwant %#v\nhave %#v", want, have)
}
}
func TestJSONLoggerMissingValue(t *testing.T) {
t.Parallel()
buf := &bytes.Buffer{}
logger := log.NewJSONLogger(buf)
if err := logger.Log("k"); err != nil {
t.Fatal(err)
}
if want, have := `{"k":"(MISSING)"}`+"\n", buf.String(); want != have {
t.Errorf("\nwant %#v\nhave %#v", want, have)
}
}
func TestJSONLoggerNilStringerKey(t *testing.T) {
t.Parallel()
buf := &bytes.Buffer{}
logger := log.NewJSONLogger(buf)
if err := logger.Log((*stringer)(nil), "v"); err != nil {
t.Fatal(err)
}
if want, have := `{"NULL":"v"}`+"\n", buf.String(); want != have {
t.Errorf("\nwant %#v\nhave %#v", want, have)
}
}
func TestJSONLoggerNilErrorValue(t *testing.T) {
t.Parallel()
buf := &bytes.Buffer{}
logger := log.NewJSONLogger(buf)
if err := logger.Log("err", (*stringError)(nil)); err != nil {
t.Fatal(err)
}
if want, have := `{"err":null}`+"\n", buf.String(); want != have {
t.Errorf("\nwant %#v\nhave %#v", want, have)
}
}
// aller implements json.Marshaler, encoding.TextMarshaler, and fmt.Stringer.
type aller struct{}
func (aller) MarshalJSON() ([]byte, error) {
return []byte("\"json\""), nil
}
func (aller) MarshalText() ([]byte, error) {
return []byte("text"), nil
}
func (aller) String() string {
return "string"
}
// textstringer implements encoding.TextMarshaler and fmt.Stringer.
type textstringer struct{}
func (textstringer) MarshalText() ([]byte, error) {
return []byte("text"), nil
}
func (textstringer) String() string {
return "string"
}
func TestJSONLoggerStringValue(t *testing.T) {
t.Parallel()
tests := []struct {
v interface{}
expected string
}{
{
v: aller{},
expected: `{"v":"json"}`,
},
{
v: textstringer{},
expected: `{"v":"text"}`,
},
{
v: stringer("string"),
expected: `{"v":"string"}`,
},
}
for _, test := range tests {
buf := &bytes.Buffer{}
logger := log.NewJSONLogger(buf)
if err := logger.Log("v", test.v); err != nil {
t.Fatal(err)
}
if want, have := test.expected+"\n", buf.String(); want != have {
t.Errorf("\nwant %#v\nhave %#v", want, have)
}
}
}
type stringer string
func (s stringer) String() string {
return string(s)
}
type stringError string
func (s stringError) Error() string {
return string(s)
}
func BenchmarkJSONLoggerSimple(b *testing.B) {
benchmarkRunner(b, log.NewJSONLogger(ioutil.Discard), baseMessage)
}
func BenchmarkJSONLoggerContextual(b *testing.B) {
benchmarkRunner(b, log.NewJSONLogger(ioutil.Discard), withMessage)
}
func TestJSONLoggerConcurrency(t *testing.T) {
t.Parallel()
testConcurrency(t, log.NewJSONLogger(ioutil.Discard), 10000)
}

View File

@@ -1,127 +0,0 @@
package levels
import "github.com/go-kit/kit/log"
// Levels provides a leveled logging wrapper around a logger. It has five
// levels: debug, info, warning (warn), error, and critical (crit). If you
// want a different set of levels, you can create your own levels type very
// easily, and you can elide the configuration.
type Levels struct {
ctx *log.Context
levelKey string
// We have a choice between storing level values in string fields or
// making a separate context for each level. When using string fields the
// Log method must combine the base context, the level data, and the
// logged keyvals; but the With method only requires updating one context.
// If we instead keep a separate context for each level the Log method
// must only append the new keyvals; but the With method would have to
// update all five contexts.
// Roughly speaking, storing multiple contexts breaks even if the ratio of
// Log/With calls is more than the number of levels. We have chosen to
// make the With method cheap and the Log method a bit more costly because
// we do not expect most applications to Log more than five times for each
// call to With.
debugValue string
infoValue string
warnValue string
errorValue string
critValue string
}
// New creates a new leveled logger, wrapping the passed logger.
func New(logger log.Logger, options ...Option) Levels {
l := Levels{
ctx: log.NewContext(logger),
levelKey: "level",
debugValue: "debug",
infoValue: "info",
warnValue: "warn",
errorValue: "error",
critValue: "crit",
}
for _, option := range options {
option(&l)
}
return l
}
// With returns a new leveled logger that includes keyvals in all log events.
func (l Levels) With(keyvals ...interface{}) Levels {
return Levels{
ctx: l.ctx.With(keyvals...),
levelKey: l.levelKey,
debugValue: l.debugValue,
infoValue: l.infoValue,
warnValue: l.warnValue,
errorValue: l.errorValue,
critValue: l.critValue,
}
}
// Debug returns a debug level logger.
func (l Levels) Debug() log.Logger {
return l.ctx.WithPrefix(l.levelKey, l.debugValue)
}
// Info returns an info level logger.
func (l Levels) Info() log.Logger {
return l.ctx.WithPrefix(l.levelKey, l.infoValue)
}
// Warn returns a warning level logger.
func (l Levels) Warn() log.Logger {
return l.ctx.WithPrefix(l.levelKey, l.warnValue)
}
// Error returns an error level logger.
func (l Levels) Error() log.Logger {
return l.ctx.WithPrefix(l.levelKey, l.errorValue)
}
// Crit returns a critical level logger.
func (l Levels) Crit() log.Logger {
return l.ctx.WithPrefix(l.levelKey, l.critValue)
}
// Option sets a parameter for leveled loggers.
type Option func(*Levels)
// Key sets the key for the field used to indicate log level. By default,
// the key is "level".
func Key(key string) Option {
return func(l *Levels) { l.levelKey = key }
}
// DebugValue sets the value for the field used to indicate the debug log
// level. By default, the value is "debug".
func DebugValue(value string) Option {
return func(l *Levels) { l.debugValue = value }
}
// InfoValue sets the value for the field used to indicate the info log level.
// By default, the value is "info".
func InfoValue(value string) Option {
return func(l *Levels) { l.infoValue = value }
}
// WarnValue sets the value for the field used to indicate the warning log
// level. By default, the value is "warn".
func WarnValue(value string) Option {
return func(l *Levels) { l.warnValue = value }
}
// ErrorValue sets the value for the field used to indicate the error log
// level. By default, the value is "error".
func ErrorValue(value string) Option {
return func(l *Levels) { l.errorValue = value }
}
// CritValue sets the value for the field used to indicate the critical log
// level. By default, the value is "crit".
func CritValue(value string) Option {
return func(l *Levels) { l.critValue = value }
}

View File

@@ -1,65 +0,0 @@
package levels_test
import (
"bytes"
"os"
"testing"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/levels"
)
func TestDefaultLevels(t *testing.T) {
buf := bytes.Buffer{}
logger := levels.New(log.NewLogfmtLogger(&buf))
logger.Debug().Log("msg", "résumé") // of course you'd want to do this
if want, have := "level=debug msg=résumé\n", buf.String(); want != have {
t.Errorf("want %#v, have %#v", want, have)
}
buf.Reset()
logger.Info().Log("msg", "Åhus")
if want, have := "level=info msg=Åhus\n", buf.String(); want != have {
t.Errorf("want %#v, have %#v", want, have)
}
buf.Reset()
logger.Error().Log("msg", "© violation")
if want, have := "level=error msg=\"© violation\"\n", buf.String(); want != have {
t.Errorf("want %#v, have %#v", want, have)
}
buf.Reset()
logger.Crit().Log("msg", " ")
if want, have := "level=crit msg=\"\\t\"\n", buf.String(); want != have {
t.Errorf("want %#v, have %#v", want, have)
}
}
func TestModifiedLevels(t *testing.T) {
buf := bytes.Buffer{}
logger := levels.New(
log.NewJSONLogger(&buf),
levels.Key("l"),
levels.DebugValue("dbg"),
levels.InfoValue("nfo"),
levels.WarnValue("wrn"),
levels.ErrorValue("err"),
levels.CritValue("crt"),
)
logger.With("easter_island", "176°").Debug().Log("msg", "moai")
if want, have := `{"easter_island":"176°","l":"dbg","msg":"moai"}`+"\n", buf.String(); want != have {
t.Errorf("want %#v, have %#v", want, have)
}
}
func ExampleLevels() {
logger := levels.New(log.NewLogfmtLogger(os.Stdout))
logger.Debug().Log("msg", "hello")
logger.With("context", "foo").Warn().Log("err", "error")
// Output:
// level=debug msg=hello
// level=warn context=foo err=error
}

View File

@@ -1,210 +0,0 @@
package log_test
import (
"bytes"
"fmt"
"sync"
"testing"
"github.com/go-kit/kit/log"
"github.com/go-stack/stack"
)
func TestContext(t *testing.T) {
t.Parallel()
buf := &bytes.Buffer{}
logger := log.NewLogfmtLogger(buf)
kvs := []interface{}{"a", 123}
lc := log.NewContext(logger).With(kvs...)
kvs[1] = 0 // With should copy its key values
lc = lc.With("b", "c") // With should stack
if err := lc.Log("msg", "message"); err != nil {
t.Fatal(err)
}
if want, have := "a=123 b=c msg=message\n", buf.String(); want != have {
t.Errorf("\nwant: %shave: %s", want, have)
}
buf.Reset()
lc = lc.WithPrefix("p", "first")
if err := lc.Log("msg", "message"); err != nil {
t.Fatal(err)
}
if want, have := "p=first a=123 b=c msg=message\n", buf.String(); want != have {
t.Errorf("\nwant: %shave: %s", want, have)
}
}
func TestContextMissingValue(t *testing.T) {
t.Parallel()
var output []interface{}
logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error {
output = keyvals
return nil
}))
lc := log.NewContext(logger)
lc.Log("k")
if want, have := 2, len(output); want != have {
t.Errorf("want len(output) == %v, have %v", want, have)
}
if want, have := log.ErrMissingValue, output[1]; want != have {
t.Errorf("want %#v, have %#v", want, have)
}
lc.With("k1").WithPrefix("k0").Log("k2")
if want, have := 6, len(output); want != have {
t.Errorf("want len(output) == %v, have %v", want, have)
}
for i := 1; i < 6; i += 2 {
if want, have := log.ErrMissingValue, output[i]; want != have {
t.Errorf("want output[%d] == %#v, have %#v", i, want, have)
}
}
}
// Test that Context.Log has a consistent function stack depth when binding
// log.Valuers, regardless of how many times Context.With has been called or
// whether Context.Log is called via an interface typed variable or a concrete
// typed variable.
func TestContextStackDepth(t *testing.T) {
t.Parallel()
fn := fmt.Sprintf("%n", stack.Caller(0))
var output []interface{}
logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error {
output = keyvals
return nil
}))
stackValuer := log.Valuer(func() interface{} {
for i, c := range stack.Trace() {
if fmt.Sprintf("%n", c) == fn {
return i
}
}
t.Fatal("Test function not found in stack trace.")
return nil
})
concrete := log.NewContext(logger).With("stack", stackValuer)
var iface log.Logger = concrete
// Call through interface to get baseline.
iface.Log("k", "v")
want := output[1].(int)
for len(output) < 10 {
concrete.Log("k", "v")
if have := output[1]; have != want {
t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want)
}
iface.Log("k", "v")
if have := output[1]; have != want {
t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want)
}
wrapped := log.NewContext(concrete)
wrapped.Log("k", "v")
if have := output[1]; have != want {
t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want)
}
concrete = concrete.With("k", "v")
iface = concrete
}
}
// Test that With returns a Logger safe for concurrent use. This test
// validates that the stored logging context does not get corrupted when
// multiple clients concurrently log additional keyvals.
//
// This test must be run with go test -cpu 2 (or more) to achieve its goal.
func TestWithConcurrent(t *testing.T) {
// Create some buckets to count how many events each goroutine logs.
const goroutines = 8
counts := [goroutines]int{}
// This logger extracts a goroutine id from the last value field and
// increments the referenced bucket.
logger := log.LoggerFunc(func(kv ...interface{}) error {
goroutine := kv[len(kv)-1].(int)
counts[goroutine]++
return nil
})
// With must be careful about handling slices that can grow without
// copying the underlying array, so give it a challenge.
l := log.NewContext(logger).With(make([]interface{}, 0, 2)...)
// Start logging concurrently. Each goroutine logs its id so the logger
// can bucket the event counts.
var wg sync.WaitGroup
wg.Add(goroutines)
const n = 10000
for i := 0; i < goroutines; i++ {
go func(idx int) {
defer wg.Done()
for j := 0; j < n; j++ {
l.Log("goroutineIdx", idx)
}
}(i)
}
wg.Wait()
for bucket, have := range counts {
if want := n; want != have {
t.Errorf("bucket %d: want %d, have %d", bucket, want, have) // note Errorf
}
}
}
func BenchmarkDiscard(b *testing.B) {
logger := log.NewNopLogger()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Log("k", "v")
}
}
func BenchmarkOneWith(b *testing.B) {
logger := log.NewNopLogger()
lc := log.NewContext(logger).With("k", "v")
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
lc.Log("k", "v")
}
}
func BenchmarkTwoWith(b *testing.B) {
logger := log.NewNopLogger()
lc := log.NewContext(logger).With("k", "v")
for i := 1; i < 2; i++ {
lc = lc.With("k", "v")
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
lc.Log("k", "v")
}
}
func BenchmarkTenWith(b *testing.B) {
logger := log.NewNopLogger()
lc := log.NewContext(logger).With("k", "v")
for i := 1; i < 10; i++ {
lc = lc.With("k", "v")
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
lc.Log("k", "v")
}
}

View File

@@ -1,57 +0,0 @@
package log_test
import (
"bytes"
"errors"
"io/ioutil"
"testing"
"github.com/go-kit/kit/log"
"github.com/go-logfmt/logfmt"
)
func TestLogfmtLogger(t *testing.T) {
t.Parallel()
buf := &bytes.Buffer{}
logger := log.NewLogfmtLogger(buf)
if err := logger.Log("hello", "world"); err != nil {
t.Fatal(err)
}
if want, have := "hello=world\n", buf.String(); want != have {
t.Errorf("want %#v, have %#v", want, have)
}
buf.Reset()
if err := logger.Log("a", 1, "err", errors.New("error")); err != nil {
t.Fatal(err)
}
if want, have := "a=1 err=error\n", buf.String(); want != have {
t.Errorf("want %#v, have %#v", want, have)
}
buf.Reset()
if err := logger.Log("std_map", map[int]int{1: 2}, "my_map", mymap{0: 0}); err != nil {
t.Fatal(err)
}
if want, have := "std_map=\""+logfmt.ErrUnsupportedValueType.Error()+"\" my_map=special_behavior\n", buf.String(); want != have {
t.Errorf("want %#v, have %#v", want, have)
}
}
func BenchmarkLogfmtLoggerSimple(b *testing.B) {
benchmarkRunner(b, log.NewLogfmtLogger(ioutil.Discard), baseMessage)
}
func BenchmarkLogfmtLoggerContextual(b *testing.B) {
benchmarkRunner(b, log.NewLogfmtLogger(ioutil.Discard), withMessage)
}
func TestLogfmtLoggerConcurrency(t *testing.T) {
t.Parallel()
testConcurrency(t, log.NewLogfmtLogger(ioutil.Discard), 10000)
}
type mymap map[int]int
func (m mymap) String() string { return "special_behavior" }

View File

@@ -1,26 +0,0 @@
package log_test
import (
"testing"
"github.com/go-kit/kit/log"
)
func TestNopLogger(t *testing.T) {
t.Parallel()
logger := log.NewNopLogger()
if err := logger.Log("abc", 123); err != nil {
t.Error(err)
}
if err := log.NewContext(logger).With("def", "ghi").Log(); err != nil {
t.Error(err)
}
}
func BenchmarkNopLoggerSimple(b *testing.B) {
benchmarkRunner(b, log.NewNopLogger(), baseMessage)
}
func BenchmarkNopLoggerContextual(b *testing.B) {
benchmarkRunner(b, log.NewNopLogger(), withMessage)
}

View File

@@ -1,205 +0,0 @@
package log
import (
"bytes"
"fmt"
"log"
"testing"
"time"
)
func TestStdlibWriter(t *testing.T) {
buf := &bytes.Buffer{}
log.SetOutput(buf)
log.SetFlags(log.LstdFlags)
logger := NewLogfmtLogger(StdlibWriter{})
logger.Log("key", "val")
timestamp := time.Now().Format("2006/01/02 15:04:05")
if want, have := timestamp+" key=val\n", buf.String(); want != have {
t.Errorf("want %q, have %q", want, have)
}
}
func TestStdlibAdapterUsage(t *testing.T) {
buf := &bytes.Buffer{}
logger := NewLogfmtLogger(buf)
writer := NewStdlibAdapter(logger)
stdlog := log.New(writer, "", 0)
now := time.Now()
date := now.Format("2006/01/02")
time := now.Format("15:04:05")
for flag, want := range map[int]string{
0: "msg=hello\n",
log.Ldate: "ts=" + date + " msg=hello\n",
log.Ltime: "ts=" + time + " msg=hello\n",
log.Ldate | log.Ltime: "ts=\"" + date + " " + time + "\" msg=hello\n",
log.Lshortfile: "file=stdlib_test.go:44 msg=hello\n",
log.Lshortfile | log.Ldate: "ts=" + date + " file=stdlib_test.go:44 msg=hello\n",
log.Lshortfile | log.Ldate | log.Ltime: "ts=\"" + date + " " + time + "\" file=stdlib_test.go:44 msg=hello\n",
} {
buf.Reset()
stdlog.SetFlags(flag)
stdlog.Print("hello")
if have := buf.String(); want != have {
t.Errorf("flag=%d: want %#v, have %#v", flag, want, have)
}
}
}
func TestStdLibAdapterExtraction(t *testing.T) {
buf := &bytes.Buffer{}
logger := NewLogfmtLogger(buf)
writer := NewStdlibAdapter(logger)
for input, want := range map[string]string{
"hello": "msg=hello\n",
"2009/01/23: hello": "ts=2009/01/23 msg=hello\n",
"2009/01/23 01:23:23: hello": "ts=\"2009/01/23 01:23:23\" msg=hello\n",
"01:23:23: hello": "ts=01:23:23 msg=hello\n",
"2009/01/23 01:23:23.123123: hello": "ts=\"2009/01/23 01:23:23.123123\" msg=hello\n",
"2009/01/23 01:23:23.123123 /a/b/c/d.go:23: hello": "ts=\"2009/01/23 01:23:23.123123\" file=/a/b/c/d.go:23 msg=hello\n",
"01:23:23.123123 /a/b/c/d.go:23: hello": "ts=01:23:23.123123 file=/a/b/c/d.go:23 msg=hello\n",
"2009/01/23 01:23:23 /a/b/c/d.go:23: hello": "ts=\"2009/01/23 01:23:23\" file=/a/b/c/d.go:23 msg=hello\n",
"2009/01/23 /a/b/c/d.go:23: hello": "ts=2009/01/23 file=/a/b/c/d.go:23 msg=hello\n",
"/a/b/c/d.go:23: hello": "file=/a/b/c/d.go:23 msg=hello\n",
} {
buf.Reset()
fmt.Fprint(writer, input)
if have := buf.String(); want != have {
t.Errorf("%q: want %#v, have %#v", input, want, have)
}
}
}
func TestStdlibAdapterSubexps(t *testing.T) {
for input, wantMap := range map[string]map[string]string{
"hello world": {
"date": "",
"time": "",
"file": "",
"msg": "hello world",
},
"2009/01/23: hello world": {
"date": "2009/01/23",
"time": "",
"file": "",
"msg": "hello world",
},
"2009/01/23 01:23:23: hello world": {
"date": "2009/01/23",
"time": "01:23:23",
"file": "",
"msg": "hello world",
},
"01:23:23: hello world": {
"date": "",
"time": "01:23:23",
"file": "",
"msg": "hello world",
},
"2009/01/23 01:23:23.123123: hello world": {
"date": "2009/01/23",
"time": "01:23:23.123123",
"file": "",
"msg": "hello world",
},
"2009/01/23 01:23:23.123123 /a/b/c/d.go:23: hello world": {
"date": "2009/01/23",
"time": "01:23:23.123123",
"file": "/a/b/c/d.go:23",
"msg": "hello world",
},
"01:23:23.123123 /a/b/c/d.go:23: hello world": {
"date": "",
"time": "01:23:23.123123",
"file": "/a/b/c/d.go:23",
"msg": "hello world",
},
"2009/01/23 01:23:23 /a/b/c/d.go:23: hello world": {
"date": "2009/01/23",
"time": "01:23:23",
"file": "/a/b/c/d.go:23",
"msg": "hello world",
},
"2009/01/23 /a/b/c/d.go:23: hello world": {
"date": "2009/01/23",
"time": "",
"file": "/a/b/c/d.go:23",
"msg": "hello world",
},
"/a/b/c/d.go:23: hello world": {
"date": "",
"time": "",
"file": "/a/b/c/d.go:23",
"msg": "hello world",
},
"2009/01/23 01:23:23.123123 C:/a/b/c/d.go:23: hello world": {
"date": "2009/01/23",
"time": "01:23:23.123123",
"file": "C:/a/b/c/d.go:23",
"msg": "hello world",
},
"01:23:23.123123 C:/a/b/c/d.go:23: hello world": {
"date": "",
"time": "01:23:23.123123",
"file": "C:/a/b/c/d.go:23",
"msg": "hello world",
},
"2009/01/23 01:23:23 C:/a/b/c/d.go:23: hello world": {
"date": "2009/01/23",
"time": "01:23:23",
"file": "C:/a/b/c/d.go:23",
"msg": "hello world",
},
"2009/01/23 C:/a/b/c/d.go:23: hello world": {
"date": "2009/01/23",
"time": "",
"file": "C:/a/b/c/d.go:23",
"msg": "hello world",
},
"C:/a/b/c/d.go:23: hello world": {
"date": "",
"time": "",
"file": "C:/a/b/c/d.go:23",
"msg": "hello world",
},
"2009/01/23 01:23:23.123123 C:/a/b/c/d.go:23: :.;<>_#{[]}\"\\": {
"date": "2009/01/23",
"time": "01:23:23.123123",
"file": "C:/a/b/c/d.go:23",
"msg": ":.;<>_#{[]}\"\\",
},
"01:23:23.123123 C:/a/b/c/d.go:23: :.;<>_#{[]}\"\\": {
"date": "",
"time": "01:23:23.123123",
"file": "C:/a/b/c/d.go:23",
"msg": ":.;<>_#{[]}\"\\",
},
"2009/01/23 01:23:23 C:/a/b/c/d.go:23: :.;<>_#{[]}\"\\": {
"date": "2009/01/23",
"time": "01:23:23",
"file": "C:/a/b/c/d.go:23",
"msg": ":.;<>_#{[]}\"\\",
},
"2009/01/23 C:/a/b/c/d.go:23: :.;<>_#{[]}\"\\": {
"date": "2009/01/23",
"time": "",
"file": "C:/a/b/c/d.go:23",
"msg": ":.;<>_#{[]}\"\\",
},
"C:/a/b/c/d.go:23: :.;<>_#{[]}\"\\": {
"date": "",
"time": "",
"file": "C:/a/b/c/d.go:23",
"msg": ":.;<>_#{[]}\"\\",
},
} {
haveMap := subexps([]byte(input))
for key, want := range wantMap {
if have := haveMap[key]; want != have {
t.Errorf("%q: %q: want %q, have %q", input, key, want, have)
}
}
}
}

View File

@@ -1,72 +0,0 @@
package log_test
import (
"bytes"
"io"
"testing"
"github.com/go-kit/kit/log"
)
func TestSwapLogger(t *testing.T) {
t.Parallel()
var logger log.SwapLogger
// Zero value does not panic or error.
err := logger.Log("k", "v")
if got, want := err, error(nil); got != want {
t.Errorf("got %v, want %v", got, want)
}
buf := &bytes.Buffer{}
json := log.NewJSONLogger(buf)
logger.Swap(json)
if err := logger.Log("k", "v"); err != nil {
t.Error(err)
}
if got, want := buf.String(), `{"k":"v"}`+"\n"; got != want {
t.Errorf("got %v, want %v", got, want)
}
buf.Reset()
prefix := log.NewLogfmtLogger(buf)
logger.Swap(prefix)
if err := logger.Log("k", "v"); err != nil {
t.Error(err)
}
if got, want := buf.String(), "k=v\n"; got != want {
t.Errorf("got %v, want %v", got, want)
}
buf.Reset()
logger.Swap(nil)
if err := logger.Log("k", "v"); err != nil {
t.Error(err)
}
if got, want := buf.String(), ""; got != want {
t.Errorf("got %v, want %v", got, want)
}
}
func TestSwapLoggerConcurrency(t *testing.T) {
t.Parallel()
testConcurrency(t, &log.SwapLogger{}, 10000)
}
func TestSyncLoggerConcurrency(t *testing.T) {
var w io.Writer
w = &bytes.Buffer{}
logger := log.NewLogfmtLogger(w)
logger = log.NewSyncLogger(logger)
testConcurrency(t, logger, 10000)
}
func TestSyncWriterConcurrency(t *testing.T) {
var w io.Writer
w = &bytes.Buffer{}
w = log.NewSyncWriter(w)
testConcurrency(t, log.NewLogfmtLogger(w), 10000)
}

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014 Simon Eskildsen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,144 +0,0 @@
package term
import (
"bytes"
"fmt"
"io"
"sync"
"github.com/go-kit/kit/log"
)
// Color represents an ANSI color. The zero value is Default.
type Color uint8
// ANSI colors.
const (
Default = Color(iota)
Black
DarkRed
DarkGreen
Brown
DarkBlue
DarkMagenta
DarkCyan
Gray
DarkGray
Red
Green
Yellow
Blue
Magenta
Cyan
White
numColors
)
// For more on ANSI escape codes see
// https://en.wikipedia.org/wiki/ANSI_escape_code. See in particular
// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors.
var (
resetColorBytes = []byte("\x1b[39;49m")
fgColorBytes [][]byte
bgColorBytes [][]byte
)
func init() {
// Default
fgColorBytes = append(fgColorBytes, []byte("\x1b[39m"))
bgColorBytes = append(bgColorBytes, []byte("\x1b[49m"))
// dark colors
for color := Black; color < DarkGray; color++ {
fgColorBytes = append(fgColorBytes, []byte(fmt.Sprintf("\x1b[%dm", 30+color-Black)))
bgColorBytes = append(bgColorBytes, []byte(fmt.Sprintf("\x1b[%dm", 40+color-Black)))
}
// bright colors
for color := DarkGray; color < numColors; color++ {
fgColorBytes = append(fgColorBytes, []byte(fmt.Sprintf("\x1b[%d;1m", 30+color-DarkGray)))
bgColorBytes = append(bgColorBytes, []byte(fmt.Sprintf("\x1b[%d;1m", 40+color-DarkGray)))
}
}
// FgBgColor represents a foreground and background color.
type FgBgColor struct {
Fg, Bg Color
}
func (c FgBgColor) isZero() bool {
return c.Fg == Default && c.Bg == Default
}
// NewColorLogger returns a Logger which writes colored logs to w. ANSI color
// codes for the colors returned by color are added to the formatted output
// from the Logger returned by newLogger and the combined result written to w.
func NewColorLogger(w io.Writer, newLogger func(io.Writer) log.Logger, color func(keyvals ...interface{}) FgBgColor) log.Logger {
if color == nil {
panic("color func nil")
}
return &colorLogger{
w: w,
newLogger: newLogger,
color: color,
bufPool: sync.Pool{New: func() interface{} { return &loggerBuf{} }},
noColorLogger: newLogger(w),
}
}
type colorLogger struct {
w io.Writer
newLogger func(io.Writer) log.Logger
color func(keyvals ...interface{}) FgBgColor
bufPool sync.Pool
noColorLogger log.Logger
}
func (l *colorLogger) Log(keyvals ...interface{}) error {
color := l.color(keyvals...)
if color.isZero() {
return l.noColorLogger.Log(keyvals...)
}
lb := l.getLoggerBuf()
defer l.putLoggerBuf(lb)
if color.Fg != Default {
lb.buf.Write(fgColorBytes[color.Fg])
}
if color.Bg != Default {
lb.buf.Write(bgColorBytes[color.Bg])
}
err := lb.logger.Log(keyvals...)
if err != nil {
return err
}
if color.Fg != Default || color.Bg != Default {
lb.buf.Write(resetColorBytes)
}
_, err = io.Copy(l.w, lb.buf)
return err
}
type loggerBuf struct {
buf *bytes.Buffer
logger log.Logger
}
func (l *colorLogger) getLoggerBuf() *loggerBuf {
lb := l.bufPool.Get().(*loggerBuf)
if lb.buf == nil {
lb.buf = &bytes.Buffer{}
lb.logger = l.newLogger(lb.buf)
} else {
lb.buf.Reset()
}
return lb
}
func (l *colorLogger) putLoggerBuf(cb *loggerBuf) {
l.bufPool.Put(cb)
}

Some files were not shown because too many files have changed in this diff Show More