├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── balancer ├── balancer.go └── consul │ ├── consul.go │ ├── consul_test.go │ └── resolver.go ├── config └── config.go ├── coordinator ├── consul │ ├── consul.go │ └── test_help.go └── coordinator.go ├── grpchelper ├── conn.go └── interceptor.go ├── health └── health.go ├── invoker ├── delay.go ├── failover.go └── invoker.go ├── logger ├── json_logger.go └── logger.go ├── requestid └── rid.go ├── service └── grpc.go ├── spec └── service.go ├── trace ├── prometheus.go └── trace.go └── version └── version.go /.gitignore: -------------------------------------------------------------------------------- 1 | testvendor/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.8.x 5 | - 1.9.x 6 | - tip 7 | 8 | before_install: 9 | - go get -v github.com/golang/lint/golint 10 | - go get -u github.com/kardianos/govendor 11 | 12 | script: 13 | - govendor init 14 | - govendor fetch +o -v 15 | - go test `go list ./...|grep -v vendor` 16 | - golint `go list ./...|grep -v vendor` 17 | - go vet `go list ./...|grep -v vendor` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.8-alpine 2 | 3 | RUN apk update \ 4 | && apk add git \ 5 | && apk add make 6 | 7 | RUN go get -u github.com/golang/lint/golint 8 | 9 | WORKDIR /go/src/github.com/servicekit/servicekit-go 10 | 11 | COPY . . 12 | 13 | RUN mv testvendor vendor 14 | 15 | ENTRYPOINT ["make"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 servicekit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | _ci: lint vet test 2 | 3 | lint: 4 | @echo "lint" 5 | @echo "-------------------" 6 | @golint $$(go list ./...|grep -v vendor) 7 | 8 | vet: 9 | @echo "vet" 10 | @echo "-------------------" 11 | @go vet $$(go list ./...|grep -v vendor) 12 | 13 | test: 14 | @echo "test" 15 | @echo "-------------------" 16 | @go test $$(go list ./...|grep -v vendor) 17 | 18 | ci: 19 | @docker build ${docker_build_args} -t servicekit-go-make . 20 | @docker run servicekit-go-make _ci 21 | 22 | travis: _ci 23 | 24 | fmt: 25 | @go fmt $$(go list ./...|grep -v vendor) 26 | 27 | govendor: 28 | @echo "install dependencies..." 29 | @govendor init 30 | @govendor fetch +o -v 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # servicekit for golang 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/servicekit/servicekit-go)](https://goreportcard.com/report/github.com/servicekit/servicekit-go) 4 | [![Travis CI Status](https://travis-ci.org/servicekit/servicekit-go.svg?branch=master)](https://travis-ci.org/servicekit/servicekit-go) 5 | [![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/servicekit/servicekit-go/master/LICENSE) 6 | 7 | servicekit is a simple, minimum viable service management library for 8 | golang. 9 | 10 | ## Quick Start 11 | 12 | * get library 13 | ``` 14 | go get github.com/servicekit/servicekit-go (>=go1.8) 15 | ``` 16 | 17 | * coordinator service 18 | ``` 19 | co, err := coordinator.NewConsul("127.0.0.1:8500", "http", "", log) 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | // Get Services 25 | services, meta, error:= co.GetServices(context.Background(), "service name", "tag string") 26 | ... 27 | 28 | // Register service 29 | err := co.Register(context.Background(), spec.Service{ID: "nginx1", Service: "nginx"}) 30 | ... 31 | 32 | // Deregister service 33 | err := co.Deregister(context.Background(), "service_id") 34 | ... 35 | 36 | ``` 37 | 38 | * grpc load balance 39 | 40 | grpc load balance was implemented by https://github.com/grpc/grpc/blob/master/doc/load-balancing.md 41 | ``` 42 | co, err := coordinator.NewConsul("127.0.0.1:8500", "http", "", log) 43 | if err != nil { 44 | panic(err) 45 | } 46 | 47 | b, err := balancer.NewConsulBalancer(co, "account_service", "", log) 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | r := b.GetResolver(context.Background()) 53 | 54 | conn, err := grpc.Dial( 55 | "", 56 | grpc.WithBalancer(grpc.RoundRobin(r))) 57 | ... 58 | 59 | * grpc invoke fault tolerant 60 | ``` 61 | i := invoker.NewFailoverInvoker(10, time.Second, invoker.NewFibDelay(time.Second)) 62 | err := i.Invoke(context.Background(), conn, "/Account/Auth", authRequest, authResponse) 63 | ... 64 | -------------------------------------------------------------------------------- /balancer/balancer.go: -------------------------------------------------------------------------------- 1 | package balancer 2 | 3 | import ( 4 | "golang.org/x/net/context" 5 | "google.golang.org/grpc/naming" 6 | ) 7 | 8 | // Balancer GRPC Balancer interface 9 | // See: https://github.com/grpc/grpc/blob/master/doc/load-balancing.md 10 | type Balancer interface { 11 | GetResolver(ctx context.Context) naming.Resolver 12 | } 13 | -------------------------------------------------------------------------------- /balancer/consul/consul.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "golang.org/x/net/context" 5 | "google.golang.org/grpc/naming" 6 | 7 | "github.com/servicekit/servicekit-go/balancer" 8 | "github.com/servicekit/servicekit-go/coordinator" 9 | "github.com/servicekit/servicekit-go/logger" 10 | ) 11 | 12 | // Balancer save meta info in Consul 13 | type Balancer struct { 14 | consul coordinator.Coordinator 15 | resolver naming.Resolver 16 | 17 | log *logger.Logger 18 | } 19 | 20 | // NewBalancer initializes and returns a new Balancer. 21 | func NewBalancer(consul coordinator.Coordinator, service, tag string, log *logger.Logger) (balancer.Balancer, error) { 22 | resolver, err := newResolver(consul, service, tag, log) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | return &Balancer{ 28 | consul: consul, 29 | resolver: resolver, 30 | }, nil 31 | } 32 | 33 | // GetResolver returns a Resolver 34 | func (b *Balancer) GetResolver(ctx context.Context) naming.Resolver { 35 | return b.resolver 36 | } 37 | 38 | // GetResolver returns a Resolver 39 | func GetResolver(consul coordinator.Coordinator, service, tag string, log *logger.Logger) (naming.Resolver, error) { 40 | resolver, err := newResolver(consul, service, tag, log) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return resolver, nil 46 | } 47 | -------------------------------------------------------------------------------- /balancer/consul/consul_test.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/hashicorp/consul/api" 7 | 8 | coordinator "github.com/servicekit/servicekit-go/coordinator/consul" 9 | "github.com/servicekit/servicekit-go/logger" 10 | "github.com/servicekit/servicekit-go/spec" 11 | ) 12 | 13 | func TestNewConsulBalancer(t *testing.T) { 14 | 15 | tc := &coordinator.TestConsul{ 16 | GetServicesServices: make([]*spec.Service, 0), 17 | GetServicesMeta: &api.QueryMeta{}, 18 | GetServicesError: nil, 19 | } 20 | 21 | NewBalancer(tc, "", "", &logger.Logger{}) 22 | } 23 | -------------------------------------------------------------------------------- /balancer/consul/resolver.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strconv" 7 | "time" 8 | 9 | "golang.org/x/net/context" 10 | 11 | "github.com/hashicorp/consul/api" 12 | "github.com/servicekit/servicekit-go/coordinator" 13 | "github.com/servicekit/servicekit-go/logger" 14 | "google.golang.org/grpc/naming" 15 | ) 16 | 17 | type contextKey string 18 | 19 | func (c contextKey) String() string { 20 | return "mypackage context key " + string(c) 21 | } 22 | 23 | // Resolver implements the gRPC Resolver interface using a Consul backend. 24 | // 25 | // See the gRPC load balancing documentation for details about Balancer and 26 | // Resolver: https://github.com/grpc/grpc/blob/master/doc/load-balancing.md. 27 | type Resolver struct { 28 | consul coordinator.Coordinator 29 | service string 30 | tag string 31 | passingOnly bool 32 | 33 | quitc chan struct{} 34 | updatesc chan []*naming.Update 35 | 36 | log *logger.Logger 37 | } 38 | 39 | // NewResolver initializes and returns a new Resolver. 40 | // 41 | // It resolves addresses for gRPC connections to the given service and tag. 42 | // If the tag is irrelevant, use an empty string. 43 | func newResolver(consul coordinator.Coordinator, service, tag string, log *logger.Logger) (*Resolver, error) { 44 | r := &Resolver{ 45 | consul: consul, 46 | service: service, 47 | tag: tag, 48 | passingOnly: true, 49 | quitc: make(chan struct{}), 50 | updatesc: make(chan []*naming.Update, 1), 51 | 52 | log: log, 53 | } 54 | 55 | // Retrieve instances immediately 56 | instances, index, err := r.getInstances(0) 57 | if err != nil { 58 | r.log.Warnf("Resolver: error retrieving instances from Consul: %v", err) 59 | } 60 | fmt.Println(instances) 61 | updates := r.makeUpdates(nil, instances) 62 | if len(updates) > 0 { 63 | r.updatesc <- updates 64 | } 65 | 66 | // Start updater 67 | go r.updater(instances, index) 68 | 69 | return r, nil 70 | } 71 | 72 | // Resolve creates a watcher for target. The watcher interface is implemented 73 | // by Resolver as well, see Next and Close. 74 | func (r *Resolver) Resolve(target string) (naming.Watcher, error) { 75 | return r, nil 76 | } 77 | 78 | // Next blocks until an update or error happens. It may return one or more 79 | // updates. The first call will return the full set of instances available 80 | // as NewConsulResolver will look those up. Subsequent calls to Next() will 81 | // block until the resolver finds any new or removed instance. 82 | // 83 | // An error is returned if and only if the watcher cannot recover. 84 | func (r *Resolver) Next() ([]*naming.Update, error) { 85 | return <-r.updatesc, nil 86 | } 87 | 88 | // Close closes the watcher. 89 | func (r *Resolver) Close() { 90 | select { 91 | case <-r.quitc: 92 | default: 93 | close(r.quitc) 94 | close(r.updatesc) 95 | } 96 | } 97 | 98 | // updater is a background process started in NewResolver. It takes 99 | // a list of previously resolved instances (in the format of host:port, e.g. 100 | // 192.168.0.1:1234) and the last index returned from Consul. 101 | func (r *Resolver) updater(instances []string, lastIndex uint64) { 102 | var err error 103 | var oldInstances = instances 104 | var newInstances []string 105 | 106 | // TODO Cache the updates for a while, so that we don't overwhelm Consul. 107 | for { 108 | select { 109 | case <-r.quitc: 110 | break 111 | default: 112 | newInstances, lastIndex, err = r.getInstances(lastIndex) 113 | if err != nil { 114 | r.log.Debugf("grpc/lb: error retrieving instances from Consul: %v", err) 115 | time.Sleep(1 * time.Second) 116 | continue 117 | } 118 | updates := r.makeUpdates(oldInstances, newInstances) 119 | if len(updates) > 0 { 120 | r.updatesc <- updates 121 | } 122 | oldInstances = newInstances 123 | time.Sleep(1 * time.Second) 124 | } 125 | } 126 | } 127 | 128 | // getInstances retrieves the new set of instances registered for the 129 | // service from Consul. 130 | func (r *Resolver) getInstances(lastIndex uint64) ([]string, uint64, error) { 131 | ctx := context.Background() 132 | 133 | context.WithValue(ctx, contextKey("passingOnly"), true) 134 | context.WithValue(ctx, contextKey("queryOptions"), &api.QueryOptions{ 135 | WaitIndex: lastIndex, 136 | }) 137 | services, meta, err := r.consul.GetServices(ctx, r.service, r.tag) 138 | if err != nil { 139 | return nil, lastIndex, err 140 | } 141 | 142 | _m, ok := meta.(*api.QueryMeta) 143 | if ok == false { 144 | return nil, lastIndex, fmt.Errorf("invalid meta data") 145 | } 146 | 147 | var instances []string 148 | for _, service := range services { 149 | s := service.Address 150 | if len(s) == 0 { 151 | s = service.NodeAddress 152 | } 153 | addr := net.JoinHostPort(s, strconv.Itoa(service.Port)) 154 | instances = append(instances, addr) 155 | } 156 | return instances, _m.LastIndex, nil 157 | } 158 | 159 | // makeUpdates calculates the difference between and old and a new set of 160 | // instances and turns it into an array of naming.Updates. 161 | func (r *Resolver) makeUpdates(oldInstances, newInstances []string) []*naming.Update { 162 | oldAddr := make(map[string]struct{}, len(oldInstances)) 163 | for _, instance := range oldInstances { 164 | oldAddr[instance] = struct{}{} 165 | } 166 | newAddr := make(map[string]struct{}, len(newInstances)) 167 | for _, instance := range newInstances { 168 | newAddr[instance] = struct{}{} 169 | } 170 | 171 | var updates []*naming.Update 172 | for addr := range newAddr { 173 | if _, ok := oldAddr[addr]; !ok { 174 | updates = append(updates, &naming.Update{Op: naming.Add, Addr: addr}) 175 | } 176 | } 177 | for addr := range oldAddr { 178 | if _, ok := newAddr[addr]; !ok { 179 | updates = append(updates, &naming.Update{Op: naming.Delete, Addr: addr}) 180 | } 181 | } 182 | 183 | return updates 184 | 185 | } 186 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "log/syslog" 6 | 7 | "github.com/kelseyhightower/envconfig" 8 | ) 9 | 10 | // ServiceENV string 11 | type ServiceENV string 12 | 13 | // ServiceVersion string 14 | type ServiceVersion string 15 | 16 | // NewCustomConfigPrefix returns a string which start with "SERVICE_CUSTOM" 17 | func NewCustomConfigPrefix(s string) string { 18 | return fmt.Sprintf("%s_%s", "SERVICE_CUSTOM", s) 19 | } 20 | 21 | // newConfigPrefix returns a string which start with "SERVICE" 22 | func newConfigPrefix(s string) string { 23 | return fmt.Sprintf("%s_%s", "SERVICE", s) 24 | } 25 | 26 | const ( 27 | // ServiceENVTesting used for testing env 28 | ServiceENVTesting ServiceENV = "testing" 29 | // ServiceENVDev used for dev env 30 | ServiceENVDev ServiceENV = "dev" 31 | // ServiceENVStaging used for staging env 32 | ServiceENVStaging ServiceENV = "staging" 33 | // ServiceENVProd used for prod env 34 | ServiceENVProd ServiceENV = "production" 35 | ) 36 | 37 | // ServiceConfig is used to describe configs 38 | type ServiceConfig struct { 39 | ServiceID string 40 | ServiceName string 41 | ServiceENV ServiceENV 42 | ServiceVersion string 43 | ServiceTags []string 44 | 45 | TraceHost string 46 | TracePort int 47 | 48 | LoggerNetwork string 49 | LoggerADDR string 50 | LoggerPriority syslog.Priority 51 | } 52 | 53 | // NewServiceConfig returns a ServiceConfig 54 | func NewServiceConfig() *ServiceConfig { 55 | var serviceConfig ServiceConfig 56 | envconfig.Process(newConfigPrefix("SERVICE"), &serviceConfig) 57 | fmt.Println(serviceConfig) 58 | return &serviceConfig 59 | } 60 | -------------------------------------------------------------------------------- /coordinator/consul/consul.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/hashicorp/consul/api" 8 | "golang.org/x/net/context" 9 | 10 | "github.com/servicekit/servicekit-go/logger" 11 | "github.com/servicekit/servicekit-go/spec" 12 | "github.com/servicekit/servicekit-go/version" 13 | ) 14 | 15 | const ( 16 | // DefaultTTL use to describe consul update period 17 | DefaultTTL = time.Minute 18 | // EnableTLS use to enable TSL 19 | EnableTLS = true 20 | ) 21 | 22 | // Consul is an implementation of coodinator 23 | type Consul struct { 24 | c *api.Client 25 | 26 | log *logger.Logger 27 | } 28 | 29 | // checkService returns true when service is available 30 | func checkService(checkID string, checks []*api.HealthCheck) bool { 31 | for _, c := range checks { 32 | if c.CheckID == checkID && c.Status == api.HealthPassing { 33 | return true 34 | } 35 | } 36 | 37 | return false 38 | } 39 | 40 | // NewConsul returns a Consul 41 | func NewConsul(addr, scheme, token string, log *logger.Logger) (*Consul, error) { 42 | // create a reusable client 43 | c, err := api.NewClient(&api.Config{Address: addr, Scheme: scheme, Token: token}) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | return &Consul{ 49 | c: c, 50 | 51 | log: log, 52 | }, nil 53 | } 54 | 55 | // GetServices returns all service by context, name and tag 56 | func (c *Consul) GetServices(ctx context.Context, name string, tag string) ([]*spec.Service, interface{}, error) { 57 | var passingOnly bool 58 | var queryOptions *api.QueryOptions 59 | 60 | if v, ok := ctx.Value("passingOnly").(bool); ok == false { 61 | passingOnly = true 62 | } else { 63 | passingOnly = v 64 | } 65 | 66 | if v, ok := ctx.Value("queryOptions").(*api.QueryOptions); ok == false { 67 | queryOptions = nil 68 | } else { 69 | queryOptions = v 70 | } 71 | 72 | serviceEntries, meta, err := c.c.Health().Service(name, tag, passingOnly, queryOptions) 73 | if err != nil { 74 | return nil, nil, err 75 | } 76 | 77 | services := make([]*spec.Service, 0) 78 | 79 | for _, serviceEntry := range serviceEntries { 80 | if checkService(fmt.Sprintf("service:%s", serviceEntry.Service.ID), serviceEntry.Checks) == false { 81 | continue 82 | } 83 | 84 | services = append(services, &spec.Service{ 85 | ID: serviceEntry.Service.ID, 86 | Service: serviceEntry.Service.Service, 87 | Tags: serviceEntry.Service.Tags, 88 | Version: version.GetVersion(serviceEntry.Service.Tags), 89 | Address: serviceEntry.Service.Address, 90 | Port: serviceEntry.Service.Port, 91 | CreateIndex: serviceEntry.Service.CreateIndex, 92 | ModifyIndex: serviceEntry.Service.ModifyIndex, 93 | NodeID: serviceEntry.Node.ID, 94 | Node: serviceEntry.Node.Node, 95 | NodeAddress: serviceEntry.Node.Address, 96 | Datacenter: serviceEntry.Node.Datacenter, 97 | }) 98 | } 99 | 100 | return services, meta, nil 101 | } 102 | 103 | // Register register a new service 104 | func (c *Consul) Register(ctx context.Context, serv *spec.Service, ttl time.Duration) error { 105 | enableTLS, ok := ctx.Value("enabletls").(bool) 106 | if ok != true { 107 | enableTLS = EnableTLS 108 | } 109 | 110 | service := &api.AgentServiceRegistration{ 111 | ID: serv.ID, 112 | Name: serv.Service, 113 | Address: serv.Address, 114 | Port: serv.Port, 115 | Tags: serv.Tags, 116 | Check: &api.AgentServiceCheck{ 117 | TTL: ttl.String(), 118 | TLSSkipVerify: enableTLS, 119 | }, 120 | } 121 | 122 | if err := c.c.Agent().ServiceRegister(service); err != nil { 123 | return err 124 | } 125 | 126 | go func(ctx context.Context) { 127 | c.log.Infof("consul: service: %s update ttl started", serv.ID) 128 | for { 129 | select { 130 | case <-ctx.Done(): 131 | c.log.Infof("consul: service: %s update ttl stopped", serv.ID) 132 | return 133 | default: 134 | c.log.Debugf("consul: service: %s updated ttl ", serv.ID) 135 | c.c.Agent().UpdateTTL(fmt.Sprintf("service:%s", serv.ID), "", api.HealthPassing) 136 | time.Sleep(ttl/2 - 1) 137 | } 138 | } 139 | }(ctx) 140 | 141 | return nil 142 | } 143 | 144 | // Deregister deregister a service 145 | func (c *Consul) Deregister(ctx context.Context, serviceID string) error { 146 | return c.c.Agent().ServiceDeregister(serviceID) 147 | } 148 | -------------------------------------------------------------------------------- /coordinator/consul/test_help.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "time" 5 | 6 | "golang.org/x/net/context" 7 | 8 | "github.com/hashicorp/consul/api" 9 | "github.com/servicekit/servicekit-go/spec" 10 | ) 11 | 12 | // TestConsul is a help stub for test 13 | type TestConsul struct { 14 | GetServicesServices []*spec.Service 15 | GetServicesMeta *api.QueryMeta 16 | GetServicesError error 17 | RegisterError error 18 | DeregisterError error 19 | } 20 | 21 | // GetServices returns some service which services do we want to return 22 | func (t *TestConsul) GetServices(ctx context.Context, name string, tag string) ([]*spec.Service, interface{}, error) { 23 | return t.GetServicesServices, t.GetServicesMeta, t.GetServicesError 24 | } 25 | 26 | // Register register a service which service do we want to register 27 | func (t *TestConsul) Register(ctx context.Context, serv *spec.Service, ttl time.Duration) error { 28 | return t.RegisterError 29 | } 30 | 31 | // Deregister deregister a service which service do we want to deregister 32 | func (t *TestConsul) Deregister(ctx context.Context, serviceID string) error { 33 | return t.DeregisterError 34 | } 35 | -------------------------------------------------------------------------------- /coordinator/coordinator.go: -------------------------------------------------------------------------------- 1 | package coordinator 2 | 3 | import ( 4 | "time" 5 | 6 | "golang.org/x/net/context" 7 | 8 | "github.com/servicekit/servicekit-go/spec" 9 | ) 10 | 11 | // Coordinator carries 12 | // a GetServices method that returns a Service 13 | // a Register method that Register a Service 14 | // a Deregister method that Deregister a Service 15 | type Coordinator interface { 16 | GetServices(ctx context.Context, name string, tag string) ([]*spec.Service, interface{}, error) 17 | Register(ctx context.Context, serv *spec.Service, ttl time.Duration) error 18 | Deregister(ctx context.Context, serviceID string) error 19 | } 20 | -------------------------------------------------------------------------------- /grpchelper/conn.go: -------------------------------------------------------------------------------- 1 | package grpchelper 2 | 3 | import ( 4 | "google.golang.org/grpc" 5 | "google.golang.org/grpc/credentials" 6 | 7 | balancer "github.com/servicekit/servicekit-go/balancer/consul" 8 | "github.com/servicekit/servicekit-go/coordinator" 9 | "github.com/servicekit/servicekit-go/logger" 10 | ) 11 | 12 | // BalanceDial returns a client that dialed 13 | func BalanceDial(credPath, credDesc string, c coordinator.Coordinator, service string, tag string, log *logger.Logger) (*grpc.ClientConn, error) { 14 | creds, err := credentials.NewClientTLSFromFile(credPath, credDesc) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | resolver, err := balancer.GetResolver(c, service, tag, log) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | conn, err := grpc.Dial( 25 | "", 26 | grpc.WithTransportCredentials(creds), 27 | grpc.WithBalancer(grpc.RoundRobin(resolver))) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | return conn, nil 33 | } 34 | -------------------------------------------------------------------------------- /grpchelper/interceptor.go: -------------------------------------------------------------------------------- 1 | package grpchelper 2 | 3 | import ( 4 | "reflect" 5 | "runtime" 6 | "time" 7 | 8 | "golang.org/x/net/context" 9 | "google.golang.org/grpc" 10 | 11 | "github.com/servicekit/servicekit-go/logger" 12 | "github.com/servicekit/servicekit-go/requestid" 13 | ) 14 | 15 | type requestIDKey struct{} 16 | 17 | // UnaryServerChan returns a UnaryServerInterceptor 18 | func UnaryServerChan(interceptors ...grpc.UnaryServerInterceptor) grpc.UnaryServerInterceptor { 19 | switch len(interceptors) { 20 | case 0: 21 | // do not want to return nil interceptor since this function was never defined to do so/for backwards compatibility 22 | return func(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 23 | requestID := requestid.HandleRequestIDChain(ctx) 24 | ctx = context.WithValue(ctx, requestIDKey{}, requestID) 25 | return handler(ctx, req) 26 | } 27 | case 1: 28 | return func(ctx context.Context, req interface{}, unaryServerInfo *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 29 | requestID := requestid.HandleRequestIDChain(ctx) 30 | ctx = context.WithValue(ctx, requestIDKey{}, requestID) 31 | return interceptors[0](ctx, req, unaryServerInfo, handler) 32 | } 33 | default: 34 | return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 35 | buildChain := func(current grpc.UnaryServerInterceptor, next grpc.UnaryHandler) grpc.UnaryHandler { 36 | return func(currentCtx context.Context, currentReq interface{}) (interface{}, error) { 37 | return current(currentCtx, currentReq, info, next) 38 | } 39 | } 40 | chain := handler 41 | for i := len(interceptors) - 1; i >= 0; i-- { 42 | chain = buildChain(interceptors[i], chain) 43 | } 44 | requestID := requestid.HandleRequestIDChain(ctx) 45 | ctx = requestid.UpdateContextWithRequestID(ctx, requestID) 46 | return chain(ctx, req) 47 | } 48 | } 49 | } 50 | 51 | // CommonUnaryServerInterceptor describe a server interceptor 52 | type CommonUnaryServerInterceptor struct { 53 | log *logger.Logger 54 | } 55 | 56 | // NewCommonUnaryServerInterceptor returns a CommonUnaryServerInterceptor 57 | func NewCommonUnaryServerInterceptor(log *logger.Logger) *CommonUnaryServerInterceptor { 58 | return &CommonUnaryServerInterceptor{ 59 | log: log, 60 | } 61 | } 62 | 63 | // RecoverInterceptor can recover a panic 64 | func (i *CommonUnaryServerInterceptor) RecoverInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { 65 | defer func() { 66 | if r := recover(); r != nil { 67 | stack := make([]byte, 1024*8) 68 | stack = stack[:runtime.Stack(stack, false)] 69 | i.log.Errorf("panic grpc invoke: %s, err=%v, stack:\n%s", info.FullMethod, r, string(stack)) 70 | } 71 | }() 72 | 73 | return handler(ctx, req) 74 | } 75 | 76 | // TraceInterceptor trace common info 77 | func (i *CommonUnaryServerInterceptor) TraceInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { 78 | startTime := time.Now() 79 | 80 | rid := reflect.ValueOf(req).Elem().FieldByName("RequestID") 81 | 82 | i.log.Infof("GRPC Request: %v start. RequestID: %v", info.FullMethod, rid) 83 | 84 | resp, err = handler(ctx, req) 85 | 86 | doneTime := time.Now().Sub(startTime) 87 | 88 | i.log.Infof("GRPC Request: %v done. RequestID: %v, time: %v", info.FullMethod, rid, doneTime.String()) 89 | 90 | return resp, err 91 | 92 | } 93 | -------------------------------------------------------------------------------- /health/health.go: -------------------------------------------------------------------------------- 1 | package health 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/servicekit/servicekit-go/logger" 8 | ) 9 | 10 | // ServiceState represents the state of service 11 | type ServiceState string 12 | 13 | const ( 14 | // ServiceStateOK represents the state of service that is OK 15 | ServiceStateOK ServiceState = "OK" 16 | // ServiceStateBusy represents the state of service that is Busy 17 | ServiceStateBusy ServiceState = "Busy" 18 | // ServiceStateIdling represents the state of service that is Idle 19 | ServiceStateIdling ServiceState = "Idle" 20 | 21 | // ServiceStateUnavailable represents the state of service that is Unavailable 22 | ServiceStateUnavailable ServiceState = "Unavailable" 23 | ) 24 | 25 | // Health represents the state info of service 26 | type Health struct { 27 | host string 28 | port int 29 | path string 30 | 31 | state ServiceState 32 | reason string 33 | c chan ServiceState 34 | 35 | log *logger.Logger 36 | } 37 | 38 | // NewHealth returns a Health 39 | func NewHealth(host string, port int, path string, log *logger.Logger) *Health { 40 | h := &Health{ 41 | host: host, 42 | port: port, 43 | path: path, 44 | 45 | state: ServiceStateUnavailable, 46 | c: make(chan ServiceState), 47 | 48 | log: log, 49 | } 50 | 51 | go h.start() 52 | go h.serve() 53 | 54 | log.Info("health: health started") 55 | 56 | return h 57 | } 58 | 59 | // start update the state of service periodically 60 | func (h *Health) start() { 61 | for { 62 | s := <-h.c 63 | oldState := h.state 64 | h.state = s 65 | 66 | if oldState != h.state { 67 | h.log.Infof("health: state changed. %v -> %v", oldState, h.state) 68 | } 69 | } 70 | 71 | } 72 | 73 | // GetChan returns a write-only channel that you can pass new state to it 74 | func (h *Health) GetChan() chan<- ServiceState { 75 | return h.c 76 | } 77 | 78 | // handler is a http hander 79 | func (h *Health) handler(w http.ResponseWriter, req *http.Request) { 80 | if h.state == ServiceStateUnavailable { 81 | w.WriteHeader(500) 82 | } 83 | } 84 | 85 | // serve serve a http server 86 | func (h *Health) serve() { 87 | http.HandleFunc(h.path, h.handler) 88 | http.ListenAndServe(fmt.Sprintf("%s:%d", h.host, h.port), nil) 89 | } 90 | -------------------------------------------------------------------------------- /invoker/delay.go: -------------------------------------------------------------------------------- 1 | package invoker 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Delay defin a interface that has a method: GetDelay returns delay seconds 8 | type Delay interface { 9 | GetDelay() time.Duration 10 | } 11 | 12 | // commonDelay is an implementation base on Delay 13 | type commonDelay struct { 14 | delay time.Duration 15 | } 16 | 17 | // NewDelay returns a commonDelay 18 | func NewDelay(delay time.Duration) Delay { 19 | return &commonDelay{ 20 | delay: delay, 21 | } 22 | } 23 | 24 | // GetDelay returns delay seconds 25 | func (c *commonDelay) GetDelay() time.Duration { 26 | return c.delay 27 | } 28 | 29 | // fibDelay is an implementation base on Delay 30 | // delay seconds will be increased by Fibonacci algorithm 31 | type fibDelay struct { 32 | delay time.Duration 33 | next func() time.Duration 34 | } 35 | 36 | // NewFibDelay return a fibDelay 37 | func NewFibDelay(delay time.Duration) Delay { 38 | first, second := 1, 2 39 | 40 | return &fibDelay{ 41 | delay: delay, 42 | next: func() time.Duration { 43 | ret := first 44 | first, second = second, first+second 45 | return time.Duration(ret) 46 | }, 47 | } 48 | } 49 | 50 | // GetDelay returns delay seconds 51 | func (f *fibDelay) GetDelay() time.Duration { 52 | return f.delay * f.next() 53 | } 54 | -------------------------------------------------------------------------------- /invoker/failover.go: -------------------------------------------------------------------------------- 1 | package invoker 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "golang.org/x/net/context" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | const ( 12 | // MaxTries represents the max count for retry 13 | MaxTries = 8 14 | // MaxTimeout represents the timeout seconds 15 | MaxTimeout = 8 * time.Second 16 | // MaxDelay represents the delay seconds 17 | MaxDelay = 8 * time.Minute 18 | ) 19 | 20 | // FailoverInvoker can be retry when invoke failed 21 | type FailoverInvoker struct { 22 | tries int 23 | timeout time.Duration 24 | delay Delay 25 | } 26 | 27 | // NewFailoverInvoker returns an Invoker 28 | func NewFailoverInvoker(tries int, timeout time.Duration, delay Delay) Invoker { 29 | if tries > MaxTries { 30 | tries = MaxTries 31 | } 32 | 33 | if timeout > MaxTimeout { 34 | timeout = MaxTimeout 35 | } 36 | 37 | return &FailoverInvoker{ 38 | tries: tries, 39 | timeout: timeout, 40 | delay: delay, 41 | } 42 | } 43 | 44 | // Invoke method invoke grpc.Invoke that can be retry when invoke failed 45 | func (f *FailoverInvoker) Invoke(ctx context.Context, conn *grpc.ClientConn, method string, request, response interface{}, opts ...grpc.CallOption) error { 46 | var err error 47 | 48 | for i := 0; i < f.tries; i++ { 49 | err = grpc.Invoke(ctx, method, request, response, conn, opts...) 50 | if err != nil { 51 | delay := f.delay.GetDelay() 52 | time.Sleep(delay) 53 | fmt.Println(i, delay) 54 | continue 55 | } 56 | 57 | return nil 58 | } 59 | 60 | return err 61 | } 62 | -------------------------------------------------------------------------------- /invoker/invoker.go: -------------------------------------------------------------------------------- 1 | package invoker 2 | 3 | import ( 4 | "time" 5 | 6 | "golang.org/x/net/context" 7 | "google.golang.org/grpc" 8 | ) 9 | 10 | // Invoker has a method Invoke used to invoke grpc.Invoke 11 | type Invoker interface { 12 | Invoke(ctx context.Context, conn *grpc.ClientConn, method string, request, response interface{}, opts ...grpc.CallOption) error 13 | } 14 | 15 | // FailfastInvoker will be returned immediately when invoking timeout 16 | type FailfastInvoker struct { 17 | timeout time.Duration 18 | } 19 | 20 | // FailsafeInvoker will be returned immediately when invoking timeout or failed 21 | type FailsafeInvoker struct { 22 | timeout time.Duration 23 | } 24 | -------------------------------------------------------------------------------- /logger/json_logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | ) 6 | 7 | // NullWriter does not write log 8 | type NullWriter struct { 9 | } 10 | 11 | // Write write nothging 12 | func (w *NullWriter) Write(p []byte) (n int, err error) { 13 | return 0, nil 14 | } 15 | 16 | // JSONLogger is a logger JSON formatter wrap 17 | type JSONLogger struct { 18 | logger *log.Logger 19 | } 20 | 21 | // NewJSONLogger returns a JsonLogger 22 | func NewJSONLogger(hidden bool) *JSONLogger { 23 | logger := &JSONLogger{} 24 | logger.logger = log.New() 25 | 26 | if hidden == true { 27 | logger.logger.Out = &NullWriter{} 28 | } 29 | 30 | logger.logger.Formatter = &log.JSONFormatter{ 31 | FieldMap: log.FieldMap{ 32 | log.FieldKeyTime: "@timestamp", 33 | log.FieldKeyLevel: "@level", 34 | log.FieldKeyMsg: "@message", 35 | }, 36 | } 37 | 38 | return logger 39 | } 40 | 41 | // WithFields returns a log entry 42 | func (logger *JSONLogger) WithFields(fields map[string]interface{}) *log.Entry { 43 | f := make(log.Fields) 44 | for k, v := range fields { 45 | f[k] = v 46 | } 47 | return logger.logger.WithFields(fields) 48 | } 49 | -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "log/syslog" 6 | "runtime" 7 | 8 | log "github.com/sirupsen/logrus" 9 | logrus_syslog "github.com/sirupsen/logrus/hooks/syslog" 10 | 11 | "github.com/servicekit/servicekit-go/config" 12 | ) 13 | 14 | func insert(slice []interface{}, insertion interface{}) []interface{} { 15 | result := make([]interface{}, len(slice)+1) 16 | result[0] = insertion 17 | copy(result[1:], slice) 18 | return result 19 | } 20 | 21 | // Logger is a abstraction base on log.Logger 22 | type Logger struct { 23 | logger *log.Logger 24 | Active bool 25 | } 26 | 27 | // NewLogger returns a Logger 28 | func NewLogger(serviceName, serviceVersion string, serviceENV config.ServiceENV, network, addr string, priority syslog.Priority) (*Logger, error) { 29 | logger := &Logger{Active: true} 30 | logger.logger = log.New() 31 | 32 | logger.logger.Formatter = &log.JSONFormatter{ 33 | FieldMap: log.FieldMap{ 34 | log.FieldKeyTime: "@timestamp", 35 | log.FieldKeyLevel: "@level", 36 | log.FieldKeyMsg: "@message", 37 | }, 38 | } 39 | 40 | hook, err := logrus_syslog.NewSyslogHook(network, addr, priority, fmt.Sprintf("%s_%s_%s", serviceName, serviceVersion, serviceENV)) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | logger.logger.Hooks.Add(hook) 46 | 47 | return logger, nil 48 | } 49 | 50 | // Debugf will invoke logrus.Debugf 51 | func (logger *Logger) Debugf(format string, args ...interface{}) { 52 | if logger.Active == false { 53 | return 54 | } 55 | 56 | f := format 57 | 58 | _, file, line, ok := runtime.Caller(1) 59 | if ok { 60 | f = fmt.Sprintf("%s:%d %s", file, line, format) 61 | } 62 | 63 | logger.logger.Debugf(f, args...) 64 | } 65 | 66 | // Infof will invoke logrus.Infof 67 | func (logger *Logger) Infof(format string, args ...interface{}) { 68 | if logger.Active == false { 69 | return 70 | } 71 | 72 | f := format 73 | 74 | _, file, line, ok := runtime.Caller(1) 75 | if ok { 76 | f = fmt.Sprintf("%s:%d %s", file, line, format) 77 | } 78 | 79 | logger.logger.Infof(f, args...) 80 | } 81 | 82 | // Printf will invoke logrus.Printf 83 | func (logger *Logger) Printf(format string, args ...interface{}) { 84 | if logger.Active == false { 85 | return 86 | } 87 | 88 | f := format 89 | 90 | _, file, line, ok := runtime.Caller(1) 91 | if ok { 92 | f = fmt.Sprintf("%s:%d %s", file, line, format) 93 | } 94 | 95 | logger.logger.Printf(f, args...) 96 | } 97 | 98 | // Warnf will invoke logrus.Warnf 99 | func (logger *Logger) Warnf(format string, args ...interface{}) { 100 | if logger.Active == false { 101 | return 102 | } 103 | 104 | f := format 105 | 106 | _, file, line, ok := runtime.Caller(1) 107 | if ok { 108 | f = fmt.Sprintf("%s:%d %s", file, line, format) 109 | } 110 | 111 | logger.logger.Warnf(f, args...) 112 | } 113 | 114 | // Errorf will invoke logrus.Warnf 115 | func (logger *Logger) Errorf(format string, args ...interface{}) { 116 | if logger.Active == false { 117 | return 118 | } 119 | 120 | f := format 121 | 122 | _, file, line, ok := runtime.Caller(1) 123 | if ok { 124 | f = fmt.Sprintf("%s:%d %s", file, line, format) 125 | } 126 | 127 | logger.logger.Errorf(f, args...) 128 | } 129 | 130 | // Fatalf will invoke logrus.Fatalf 131 | func (logger *Logger) Fatalf(format string, args ...interface{}) { 132 | if logger.Active == false { 133 | return 134 | } 135 | 136 | f := format 137 | 138 | _, file, line, ok := runtime.Caller(1) 139 | if ok { 140 | f = fmt.Sprintf("%s:%d %s", file, line, format) 141 | } 142 | 143 | logger.logger.Fatalf(f, args...) 144 | } 145 | 146 | // Panicf will invoke logrus.Panicf 147 | func (logger *Logger) Panicf(format string, args ...interface{}) { 148 | if logger.Active == false { 149 | return 150 | } 151 | 152 | f := format 153 | 154 | _, file, line, ok := runtime.Caller(1) 155 | if ok { 156 | f = fmt.Sprintf("%s:%d %s", file, line, format) 157 | } 158 | 159 | logger.logger.Panicf(f, args...) 160 | } 161 | 162 | // Debug will invoke logrus.Debug 163 | func (logger *Logger) Debug(args ...interface{}) { 164 | if logger.Active == false { 165 | return 166 | } 167 | 168 | _, file, line, ok := runtime.Caller(1) 169 | if ok { 170 | args = insert(args, fmt.Sprintf("%s:%d ", file, line)) 171 | } 172 | 173 | logger.logger.Debug(args...) 174 | } 175 | 176 | // Info will invoke logrus.Info 177 | func (logger *Logger) Info(args ...interface{}) { 178 | if logger.Active == false { 179 | return 180 | } 181 | 182 | _, file, line, ok := runtime.Caller(1) 183 | if ok { 184 | args = insert(args, fmt.Sprintf("%s:%d ", file, line)) 185 | } 186 | 187 | logger.logger.Info(args...) 188 | } 189 | 190 | // Print will invoke logrus.Print 191 | func (logger *Logger) Print(args ...interface{}) { 192 | if logger.Active == false { 193 | return 194 | } 195 | 196 | _, file, line, ok := runtime.Caller(1) 197 | if ok { 198 | args = insert(args, fmt.Sprintf("%s:%d ", file, line)) 199 | } 200 | 201 | logger.logger.Print(args...) 202 | } 203 | 204 | // Warn will invoke logrus.Warn 205 | func (logger *Logger) Warn(args ...interface{}) { 206 | if logger.Active == false { 207 | return 208 | } 209 | 210 | _, file, line, ok := runtime.Caller(1) 211 | if ok { 212 | args = insert(args, fmt.Sprintf("%s:%d ", file, line)) 213 | } 214 | 215 | logger.logger.Warn(args...) 216 | } 217 | 218 | // Error will invoke logrus.Error 219 | func (logger *Logger) Error(args ...interface{}) { 220 | if logger.Active == false { 221 | return 222 | } 223 | 224 | _, file, line, ok := runtime.Caller(1) 225 | if ok { 226 | args = insert(args, fmt.Sprintf("%s:%d ", file, line)) 227 | } 228 | 229 | logger.logger.Error(args...) 230 | } 231 | 232 | // Fatal will invoke logrus.Fatal 233 | func (logger *Logger) Fatal(args ...interface{}) { 234 | if logger.Active == false { 235 | return 236 | } 237 | 238 | _, file, line, ok := runtime.Caller(1) 239 | if ok { 240 | args = insert(args, fmt.Sprintf("%s:%d ", file, line)) 241 | } 242 | 243 | logger.logger.Fatal(args...) 244 | } 245 | 246 | // Panic will invoke logrus.Panic 247 | func (logger *Logger) Panic(args ...interface{}) { 248 | if logger.Active == false { 249 | return 250 | } 251 | 252 | _, file, line, ok := runtime.Caller(1) 253 | if ok { 254 | args = insert(args, fmt.Sprintf("%s:%d ", file, line)) 255 | } 256 | 257 | logger.logger.Panic(args...) 258 | } 259 | 260 | // WithFields returns an Entry with fields 261 | func (logger *Logger) WithFields(fields map[string]interface{}) *log.Entry { 262 | f := make(log.Fields) 263 | for k, v := range fields { 264 | f[k] = v 265 | } 266 | 267 | return logger.logger.WithFields(fields) 268 | } 269 | -------------------------------------------------------------------------------- /requestid/rid.go: -------------------------------------------------------------------------------- 1 | package requestid 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/rs/xid" 8 | "golang.org/x/net/context" 9 | "google.golang.org/grpc/metadata" 10 | ) 11 | 12 | type contextKey string 13 | 14 | func (c contextKey) String() string { 15 | return "mypackage context key " + string(c) 16 | } 17 | 18 | // RequestIDKey is metadata key name for request ID 19 | var RequestIDKey = "request-id" 20 | 21 | // HandleRequestID got requestid from context 22 | // If no requestid in context, create a new requestid 23 | func HandleRequestID(ctx context.Context) string { 24 | md, ok := metadata.FromIncomingContext(ctx) 25 | if !ok { 26 | return newRequestID() 27 | } 28 | 29 | header, ok := md[RequestIDKey] 30 | if !ok || len(header) == 0 { 31 | return newRequestID() 32 | } 33 | 34 | requestID := header[0] 35 | if requestID == "" { 36 | return newRequestID() 37 | } 38 | 39 | return requestID 40 | } 41 | 42 | // HandleRequestIDChain got old requestid(got from context or new) and new requestid 43 | func HandleRequestIDChain(ctx context.Context) string { 44 | md, ok := metadata.FromIncomingContext(ctx) 45 | if !ok { 46 | return newRequestID() 47 | } 48 | 49 | header, ok := md[RequestIDKey] 50 | if !ok || len(header) == 0 { 51 | return newRequestID() 52 | } 53 | 54 | requestID := header[0] 55 | if requestID == "" { 56 | return newRequestID() 57 | } 58 | 59 | return fmt.Sprintf("%s,%s", requestID, newRequestID()) 60 | } 61 | 62 | // newRequestID generates a requestid 63 | func newRequestID() string { 64 | return xid.New().String() 65 | } 66 | 67 | // UpdateContextWithRequestID set a requestID to context 68 | func UpdateContextWithRequestID(ctx context.Context, requestID string) context.Context { 69 | md := metadata.New(map[string]string{RequestIDKey: requestID}) 70 | _md, ok := metadata.FromIncomingContext(ctx) 71 | if ok { 72 | md = metadata.Join(_md, md) 73 | } 74 | 75 | ctx = metadata.NewOutgoingContext(ctx, md) 76 | ctx = context.WithValue(ctx, contextKey(RequestIDKey), requestID) 77 | return ctx 78 | } 79 | 80 | // GetRequestID got requestid from context 81 | func GetRequestID(ctx context.Context) string { 82 | md, ok := metadata.FromIncomingContext(ctx) 83 | if ok == false { 84 | return "" 85 | } 86 | 87 | header, ok := md[RequestIDKey] 88 | if !ok || len(header) == 0 { 89 | return "" 90 | } 91 | 92 | return header[0] 93 | } 94 | 95 | // GetRequestIDFromHTTPRequest got requestid from http request 96 | func GetRequestIDFromHTTPRequest(ctx context.Context, r *http.Request) (context.Context, string) { 97 | requestID := r.Header.Get(RequestIDKey) 98 | if requestID == "" { 99 | requestID = HandleRequestID(ctx) 100 | } 101 | 102 | ctx = context.WithValue(ctx, contextKey(RequestIDKey), requestID) 103 | return ctx, requestID 104 | } 105 | -------------------------------------------------------------------------------- /service/grpc.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "golang.org/x/net/context" 8 | 9 | "github.com/servicekit/servicekit-go/coordinator" 10 | "github.com/servicekit/servicekit-go/logger" 11 | "github.com/servicekit/servicekit-go/spec" 12 | ) 13 | 14 | // GRPCServer has a method Serve that serve a grpc server 15 | type GRPCServer interface { 16 | Serve(ctx context.Context, network, addr, pem, key string) error 17 | } 18 | 19 | // GRPCService is a service implementation for grpc 20 | type GRPCService struct { 21 | ID string 22 | Service string 23 | Tags []string 24 | Address string 25 | Port int 26 | 27 | server GRPCServer 28 | pem string 29 | key string 30 | 31 | TTL time.Duration 32 | 33 | c coordinator.Coordinator 34 | 35 | log *logger.Logger 36 | 37 | errorChan chan error 38 | } 39 | 40 | // NewGRPCService returns a GRPCService 41 | func NewGRPCService(id string, service string, tags []string, address string, port int, server GRPCServer, pem, key string, ttl time.Duration, c coordinator.Coordinator, log *logger.Logger) *GRPCService { 42 | return &GRPCService{ 43 | ID: id, 44 | Service: service, 45 | Tags: tags, 46 | Address: address, 47 | Port: port, 48 | 49 | server: server, 50 | pem: pem, 51 | key: key, 52 | 53 | TTL: ttl, 54 | c: c, 55 | log: log, 56 | 57 | errorChan: make(chan error), 58 | } 59 | } 60 | 61 | // getService returns a spec.Service 62 | func (g *GRPCService) getService() *spec.Service { 63 | return &spec.Service{ 64 | ID: g.ID, 65 | Service: g.Service, 66 | Tags: g.Tags, 67 | Address: g.Address, 68 | Port: g.Port, 69 | } 70 | } 71 | 72 | // Start will create a goroutine to invoke grpcservice.server.Serve 73 | // When no received an error from errorChan, register service to coordinator 74 | func (g *GRPCService) Start(ctx context.Context, delayRegisterTime time.Duration) error { 75 | go func() { 76 | err := g.server.Serve( 77 | ctx, 78 | "tcp", 79 | fmt.Sprintf("%s:%d", g.Address, g.Port), 80 | g.pem, 81 | g.key, 82 | ) 83 | g.errorChan <- err 84 | }() 85 | 86 | time.Sleep(delayRegisterTime) 87 | 88 | var err error 89 | 90 | select { 91 | case err = <-g.errorChan: 92 | default: 93 | err = g.c.Register(ctx, g.getService(), g.TTL) 94 | } 95 | 96 | return err 97 | } 98 | -------------------------------------------------------------------------------- /spec/service.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | // Service Define a standard Service 4 | type Service struct { 5 | ID string 6 | Service string 7 | Tags []string 8 | Version string 9 | Address string 10 | Port int 11 | CreateIndex uint64 12 | ModifyIndex uint64 13 | NodeID string 14 | NodeAddress string 15 | Node string 16 | Datacenter string 17 | } 18 | -------------------------------------------------------------------------------- /trace/prometheus.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/servicekit/servicekit-go/logger" 7 | 8 | "github.com/prometheus/client_golang/prometheus" 9 | ) 10 | 11 | // PrometheusVec defin a prometheus vector that have two method: 12 | // GetName returns a vector name 13 | // GetCollector return a prometheus.Collector 14 | type PrometheusVec interface { 15 | GetName() string 16 | GetCollector() prometheus.Collector 17 | } 18 | 19 | // PrometheusCounter is a PrometheusVec that collect counter vector 20 | type PrometheusCounter struct { 21 | Name string 22 | Help string 23 | Labels []string 24 | } 25 | 26 | // GetName return a count vector name 27 | func (p *PrometheusCounter) GetName() string { 28 | return p.Name 29 | } 30 | 31 | // GetCollector return a collector with a counter options 32 | func (p *PrometheusCounter) GetCollector() prometheus.Collector { 33 | opts := prometheus.CounterOpts{ 34 | Name: p.Name, 35 | Help: p.Help, 36 | } 37 | 38 | return prometheus.NewCounterVec(opts, p.Labels) 39 | } 40 | 41 | // PrometheusGauge is a PrometheusVec that collect gauge vector 42 | type PrometheusGauge struct { 43 | Name string 44 | Help string 45 | Labels []string 46 | } 47 | 48 | // GetName returns a gauge vector name 49 | func (p *PrometheusGauge) GetName() string { 50 | return p.Name 51 | } 52 | 53 | // GetCollector returns a collector with a gauge options 54 | func (p *PrometheusGauge) GetCollector() prometheus.Collector { 55 | opts := prometheus.GaugeOpts{ 56 | Name: p.Name, 57 | Help: p.Help, 58 | } 59 | 60 | return prometheus.NewGaugeVec(opts, p.Labels) 61 | } 62 | 63 | // PrometheusHistogram is a PrometheusVec that collect histogram vector 64 | type PrometheusHistogram struct { 65 | Name string 66 | Help string 67 | Labels []string 68 | } 69 | 70 | // GetName returns a histogram vector name 71 | func (p *PrometheusHistogram) GetName() string { 72 | return p.Name 73 | } 74 | 75 | // GetCollector returns a collector with a histogram options 76 | func (p *PrometheusHistogram) GetCollector() prometheus.Collector { 77 | opts := prometheus.HistogramOpts{ 78 | Name: p.Name, 79 | Help: p.Help, 80 | } 81 | 82 | return prometheus.NewHistogramVec(opts, p.Labels) 83 | } 84 | 85 | type prom struct { 86 | path string 87 | collectors map[string]prometheus.Collector 88 | 89 | inited bool 90 | 91 | log *logger.Logger 92 | 93 | sync.Mutex 94 | } 95 | 96 | func (p *prom) init(vecs ...PrometheusVec) { 97 | p.Lock() 98 | 99 | if p.inited == true { 100 | return 101 | } 102 | 103 | for _, v := range vecs { 104 | p.collectors[v.GetName()] = v.GetCollector() 105 | prometheus.MustRegister(p.collectors[v.GetName()]) 106 | } 107 | 108 | p.inited = true 109 | 110 | p.Unlock() 111 | } 112 | 113 | func (p *prom) getCounter(name string) *prometheus.CounterVec { 114 | v, ok := p.collectors[name] 115 | if ok == false { 116 | return nil 117 | } 118 | 119 | c, ok := v.(*prometheus.CounterVec) 120 | if ok == false { 121 | return nil 122 | } 123 | 124 | return c 125 | } 126 | 127 | func (p *prom) getSummary(name string) *prometheus.SummaryVec { 128 | v, ok := p.collectors[name] 129 | if ok == false { 130 | return nil 131 | } 132 | 133 | c, ok := v.(*prometheus.SummaryVec) 134 | if ok == false { 135 | return nil 136 | } 137 | 138 | return c 139 | } 140 | 141 | func (p *prom) getHistogram(name string) *prometheus.HistogramVec { 142 | v, ok := p.collectors[name] 143 | if ok == false { 144 | return nil 145 | } 146 | 147 | c, ok := v.(*prometheus.HistogramVec) 148 | if ok == false { 149 | return nil 150 | } 151 | 152 | return c 153 | } 154 | 155 | func (p *prom) getGauge(name string) *prometheus.GaugeVec { 156 | v, ok := p.collectors[name] 157 | if ok == false { 158 | return nil 159 | } 160 | 161 | c, ok := v.(*prometheus.GaugeVec) 162 | if ok == false { 163 | return nil 164 | } 165 | 166 | return c 167 | } 168 | -------------------------------------------------------------------------------- /trace/trace.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | 8 | "golang.org/x/net/context" 9 | 10 | "github.com/servicekit/servicekit-go/coordinator" 11 | "github.com/servicekit/servicekit-go/logger" 12 | "github.com/servicekit/servicekit-go/spec" 13 | 14 | "github.com/prometheus/client_golang/prometheus" 15 | "github.com/prometheus/client_golang/prometheus/promhttp" 16 | ) 17 | 18 | // Trace can init a Prometheus handler for provider Prometheus vectors 19 | type Trace struct { 20 | addr string 21 | 22 | prom *prom 23 | 24 | log *logger.Logger 25 | } 26 | 27 | // NewTrace returns a trace 28 | // Serve a http server that provider a http interface for push metrics 29 | // Register itself to the coordinator 30 | func NewTrace(c coordinator.Coordinator, id string, name string, tags []string, host string, port int, ttl time.Duration, log *logger.Logger) (*Trace, error) { 31 | t := &Trace{ 32 | addr: fmt.Sprintf("%s:%d", host, port), 33 | } 34 | 35 | http.Handle("/metrics", promhttp.Handler()) 36 | 37 | t.prom = &prom{ 38 | path: "/metrics", 39 | collectors: make(map[string]prometheus.Collector), 40 | log: log, 41 | } 42 | 43 | s := &spec.Service{ 44 | ID: id, 45 | Service: name, 46 | Tags: tags, 47 | Address: host, 48 | Port: port, 49 | } 50 | 51 | err := c.Register(context.Background(), s, ttl) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | return t, nil 57 | } 58 | 59 | // Serve serve a http server 60 | func (h *Trace) Serve() { 61 | err := http.ListenAndServe(h.addr, nil) 62 | if err != nil { 63 | panic(err) 64 | } 65 | } 66 | 67 | // InitPrometheus init a prometheus handler 68 | func (h *Trace) InitPrometheus(vecs ...PrometheusVec) { 69 | h.prom.init(vecs...) 70 | } 71 | 72 | // GetCounter returns a prometheus count vector 73 | func (h *Trace) GetCounter(name string) *prometheus.CounterVec { 74 | return h.prom.getCounter(name) 75 | } 76 | 77 | // GetSummary returns a prometheus summary vector 78 | func (h *Trace) GetSummary(name string) *prometheus.SummaryVec { 79 | return h.prom.getSummary(name) 80 | } 81 | 82 | // GetHistogram returns a prometheus histogram vector 83 | func (h *Trace) GetHistogram(name string) *prometheus.HistogramVec { 84 | return h.prom.getHistogram(name) 85 | } 86 | 87 | // GetGauge returns a prometheus gauge vector 88 | func (h *Trace) GetGauge(name string) *prometheus.GaugeVec { 89 | return h.prom.getGauge(name) 90 | } 91 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | const ( 4 | // Pattern is a regexp for format a version string 5 | Pattern = "v\\d+\\.\\d+\\.\\d+" 6 | ) 7 | 8 | // GetVersion return a version 9 | func GetVersion(tags []string) string { 10 | return "" 11 | } 12 | --------------------------------------------------------------------------------