├── third_party ├── README.md ├── validate │ └── README.md ├── errors │ └── errors.proto └── google │ └── api │ ├── annotations.proto │ └── httpbody.proto ├── testdata ├── config.yaml ├── lane_rule ├── auth ├── helloworld.proto ├── lane_info └── route2 ├── pkg ├── util │ ├── flag.go │ ├── net.go │ └── backoff.go ├── version │ └── version.go ├── sys │ ├── trace │ │ ├── key.go │ │ ├── trace.go │ │ ├── logger.go │ │ └── reporter.go │ ├── tag │ │ ├── rule.go │ │ └── tag.go │ ├── monitor │ │ ├── logger.go │ │ └── stat.go │ ├── apiMeta │ │ ├── apiMeta_test.go │ │ └── apiMeta.go │ └── metrics │ │ └── gops.go ├── route │ ├── route.go │ ├── composite │ │ └── composite.go │ ├── lane │ │ └── laneRule.go │ └── router │ │ └── rule.go ├── auth │ ├── auth.go │ └── authenticator │ │ ├── rule.go │ │ └── authenticator.go ├── config │ ├── config.go │ ├── source.go │ └── consul │ │ └── consul_test.go ├── balancer │ ├── balancer.go │ └── random │ │ └── random.go ├── metric │ ├── iterator.go │ ├── metric.go │ ├── reduce.go │ ├── rolling_counter.go │ ├── rolling_policy.go │ └── window.go ├── http │ ├── tsf.go │ └── client.go ├── grpc │ ├── server │ │ ├── wrapper.go │ │ ├── recovery.go │ │ └── chain.go │ ├── client │ │ ├── trace.go │ │ ├── chain.go │ │ └── client.go │ ├── encoding │ │ └── json │ │ │ └── json.go │ ├── apiMeta.go │ ├── resolver │ │ └── resolver.go │ └── balancer │ │ └── multi │ │ └── multi.go ├── meta │ ├── key.go │ └── metadata.go ├── naming │ ├── naming.go │ └── consul │ │ └── consul_test.go └── proxy │ └── proxy.go ├── route ├── route.go ├── composite │ └── composite.go ├── lane │ └── laneRule.go └── router │ └── rule.go ├── docs ├── Metadata.md ├── Config.md ├── Balancer.md ├── Breaker.md ├── Log.md ├── Swagger.md ├── Error.md └── Trace.md ├── examples ├── atom │ ├── provider │ │ ├── atom.go │ │ └── main.go │ └── proto │ │ └── atom.proto ├── error │ ├── errors │ │ ├── error_reason.proto │ │ └── error_reason_errors.pb.go │ ├── consumer │ │ └── main.go │ └── provider │ │ └── main.go ├── helloworld │ ├── grpc │ │ ├── consumer │ │ │ ├── Dockerfile │ │ │ ├── main.go │ │ │ └── service.go │ │ └── provider │ │ │ ├── Dockerfile │ │ │ └── main.go │ ├── http │ │ ├── consumer │ │ │ ├── Dockerfile │ │ │ ├── main.go │ │ │ └── service.go │ │ └── provider │ │ │ ├── Dockerfile │ │ │ └── main.go │ ├── proto │ │ ├── helloworld.proto │ │ ├── helloworld_http.pb.go │ │ └── helloworld_grpc.pb.go │ └── gin │ │ └── provider │ │ └── main.go ├── go.mod ├── log │ └── main.go ├── config │ └── main.go ├── tracing │ ├── consumer │ │ ├── main.go │ │ └── service.go │ └── provider │ │ └── main.go └── breaker │ ├── provider │ └── main.go │ └── consumer │ └── main.go ├── balancer ├── balancer.go ├── random │ └── random.go └── hash │ ├── hash_test.go │ └── balancer.go ├── log └── log_test.go ├── .gitignore ├── util └── util.go ├── LICENSE ├── auth.go ├── tracing ├── logger.go ├── statsHandler.go ├── propagator.go ├── exporter.go ├── mysqlotel │ └── mysql.go └── redisotel │ └── redis.go ├── breaker.go ├── naming ├── consul │ └── consul.go └── service.go ├── shared.go ├── go.mod ├── http └── balancer │ └── multi │ └── multi.go ├── config └── config.go ├── breaker ├── sre_breaker.go └── breaker.go ├── apimeta.go ├── metrics.go ├── gin └── gin.go ├── app.go └── grpc └── balancer └── multi └── multi.go /third_party/README.md: -------------------------------------------------------------------------------- 1 | # third_party 2 | -------------------------------------------------------------------------------- /testdata/config.yaml: -------------------------------------------------------------------------------- 1 | stone: "key" 2 | auth: 3 | key: "mima" -------------------------------------------------------------------------------- /third_party/validate/README.md: -------------------------------------------------------------------------------- 1 | # protoc-gen-validate (PGV) 2 | 3 | * https://github.com/envoyproxy/protoc-gen-validate 4 | -------------------------------------------------------------------------------- /pkg/util/flag.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "flag" 5 | "sync" 6 | ) 7 | 8 | var mu sync.Mutex 9 | 10 | func ParseFlag() { 11 | mu.Lock() 12 | defer mu.Unlock() 13 | if !flag.Parsed() { 14 | flag.Parse() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /pkg/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import "fmt" 4 | 5 | const version string = "v0.1.10" 6 | 7 | // GetHumanVersion return version 8 | func GetHumanVersion() string { 9 | return fmt.Sprintf("tsf-go %s", version) 10 | } 11 | -------------------------------------------------------------------------------- /pkg/sys/trace/key.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | const ( 4 | TraceID = "X-B3-TraceId" 5 | SpanID = "X-B3-SpanId" 6 | ParentSpanID = "X-B3-ParentSpanId" 7 | Sampled = "X-B3-Sampled" 8 | Flags = "X-B3-Flags" 9 | Context = "B3" 10 | ) 11 | -------------------------------------------------------------------------------- /route/route.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/tencentyun/tsf-go/naming" 7 | ) 8 | 9 | type Router interface { 10 | Select(ctx context.Context, svc naming.Service, nodes []naming.Instance) (selects []naming.Instance) 11 | } 12 | -------------------------------------------------------------------------------- /pkg/route/route.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/tencentyun/tsf-go/pkg/naming" 7 | ) 8 | 9 | type Router interface { 10 | Select(ctx context.Context, svc naming.Service, nodes []naming.Instance) (selects []naming.Instance) 11 | } 12 | -------------------------------------------------------------------------------- /pkg/util/net.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | ) 7 | 8 | func IPFromAddr(addr net.Addr) (ip string) { 9 | if len(addr.String()) == 0 { 10 | return 11 | } 12 | addrs := strings.SplitN(addr.String(), ":", 2) 13 | if len(addrs) == 0 { 14 | return 15 | } 16 | ip = addrs[0] 17 | return 18 | } 19 | -------------------------------------------------------------------------------- /pkg/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/tencentyun/tsf-go/pkg/config" 7 | "github.com/tencentyun/tsf-go/pkg/naming" 8 | ) 9 | 10 | type Builder interface { 11 | Build(cfg config.Source, svc naming.Service) Auth 12 | } 13 | 14 | type Auth interface { 15 | // api为被访问的接口名 16 | Verify(ctx context.Context, api string) error 17 | } 18 | -------------------------------------------------------------------------------- /docs/Metadata.md: -------------------------------------------------------------------------------- 1 | # 用户自定义标签 2 | 自定义标签的作用:[系统和业务自定义标签](https://cloud.tencent.com/document/product/649/34136) 3 | 4 | 在链路中传递自定义标签: 5 | ```go 6 | import "github.com/tencentyun/tsf-go/pkg/meta" 7 | 8 | ctx = meta.WithUser(ctx, meta.UserPair{Key: "user", Value: "test2233"}) 9 | s.client.SayHello(ctx, req) 10 | ``` 11 | 在下游server中获取自定义标签: 12 | ```go 13 | import "github.com/tencentyun/tsf-go/pkg/meta" 14 | 15 | fmt.Println(meta.User(ctx,"user")) 16 | ``` -------------------------------------------------------------------------------- /pkg/sys/trace/trace.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/openzipkin/zipkin-go/reporter" 7 | ) 8 | 9 | var report reporter.Reporter 10 | var mu sync.Mutex 11 | 12 | func GetReporter() reporter.Reporter { 13 | mu.Lock() 14 | defer mu.Unlock() 15 | if report == nil { 16 | report = &tsfReporter{logger: DefaultLogger} 17 | } 18 | return report 19 | } 20 | 21 | func CloseReporter() { 22 | report.Close() 23 | } 24 | -------------------------------------------------------------------------------- /examples/atom/provider/atom.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "os" 4 | 5 | func AtomMetadata() map[string]string { 6 | return map[string]string{ 7 | "ATOM_CLIENT_SDK_VERSION": "2.0.0-RELEASE", 8 | "ATOM_GROUP_ID": os.Getenv("atom_group_id"), 9 | "ATOM_NAMESPACE_ID": os.Getenv("atom_namespace_id"), 10 | "ATOM_CLUSTER_ID": os.Getenv("atom_cluster_id"), 11 | "ATOM_INSTANCE_ID": os.Getenv("atom_instance_id"), 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/error/errors/error_reason.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package helloworld; 4 | 5 | // import third party proto 6 | import "errors/errors.proto"; 7 | 8 | option go_package = "github.com/tencentyun/tsf-go/examples/error/errors"; 9 | 10 | enum ErrorReason { 11 | // 设置缺省错误码 12 | option (errors.default_code) = 500; 13 | 14 | // 为某个枚举单独设置错误码 15 | USER_NOT_FOUND = 0 [(errors.code) = 404]; 16 | 17 | CONTENT_MISSING = 1 [(errors.code) = 400];; 18 | } -------------------------------------------------------------------------------- /testdata/lane_rule: -------------------------------------------------------------------------------- 1 | createTime: 2020-11-23T07:32:47Z 2 | enable: true 3 | laneId: lane-4y48nmgv 4 | priority: 1 5 | remark: '' 6 | ruleId: lane-r-gvk6z4la 7 | ruleName: test_longxia 8 | ruleTagList: 9 | - {createTime: !!timestamp '2020-11-23T07:32:47Z', laneRuleId: lane-r-gvk6z4la, tagId: lane-t-5yr3en2y, 10 | tagName: user, tagOperator: EQUAL, tagValue: test2233, updateTime: !!timestamp '2020-11-23T07:32:47Z'} 11 | ruleTagRelationship: RELEATION_AND 12 | updateTime: 2020-11-23T07:32:47Z -------------------------------------------------------------------------------- /examples/helloworld/grpc/consumer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | 3 | RUN echo "ip_resolve=4" >> /etc/yum.conf 4 | #RUN yum update -y && yum install -y ca-certificates 5 | 6 | RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 7 | RUN echo "Asia/Shanghai" > /etc/timezone 8 | ENV workdir /app/ 9 | 10 | COPY consumer ${workdir} 11 | WORKDIR ${workdir} 12 | 13 | # 如果加了${JAVA_OPTS},需要在TSF的容器部署组启动参数中删除默认的"-Xms128m xxx"参数,否则会启动失败 14 | CMD ["sh", "-ec", "exec ${workdir}consumer ${JAVA_OPTS}"] -------------------------------------------------------------------------------- /examples/helloworld/grpc/provider/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | 3 | RUN echo "ip_resolve=4" >> /etc/yum.conf 4 | #RUN yum update -y && yum install -y ca-certificates 5 | 6 | RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 7 | RUN echo "Asia/Shanghai" > /etc/timezone 8 | ENV workdir /app/ 9 | 10 | COPY provider ${workdir} 11 | WORKDIR ${workdir} 12 | 13 | # 如果加了${JAVA_OPTS},需要在TSF的容器部署组启动参数中删除默认的"-Xms128m xxx"参数,否则会启动失败 14 | CMD ["sh", "-ec", "exec ${workdir}provider ${JAVA_OPTS}"] -------------------------------------------------------------------------------- /examples/helloworld/http/consumer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | 3 | RUN echo "ip_resolve=4" >> /etc/yum.conf 4 | #RUN yum update -y && yum install -y ca-certificates 5 | 6 | RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 7 | RUN echo "Asia/Shanghai" > /etc/timezone 8 | ENV workdir /app/ 9 | 10 | COPY consumer ${workdir} 11 | WORKDIR ${workdir} 12 | 13 | # 如果加了${JAVA_OPTS},需要在TSF的容器部署组启动参数中删除默认的"-Xms128m xxx"参数,否则会启动失败 14 | CMD ["sh", "-ec", "exec ${workdir}consumer ${JAVA_OPTS}"] -------------------------------------------------------------------------------- /examples/helloworld/http/provider/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | 3 | RUN echo "ip_resolve=4" >> /etc/yum.conf 4 | #RUN yum update -y && yum install -y ca-certificates 5 | 6 | RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 7 | RUN echo "Asia/Shanghai" > /etc/timezone 8 | ENV workdir /app/ 9 | 10 | COPY provider ${workdir} 11 | WORKDIR ${workdir} 12 | 13 | # 如果加了${JAVA_OPTS},需要在TSF的容器部署组启动参数中删除默认的"-Xms128m xxx"参数,否则会启动失败 14 | CMD ["sh", "-ec", "exec ${workdir}provider ${JAVA_OPTS}"] -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Config is config interface 8 | type Config interface { 9 | Raw() []byte 10 | Unmarshal(v interface{}) error 11 | Get(key string) (interface{}, bool) 12 | GetString(key string) (string, bool) 13 | GetBool(key string) (bool, bool) 14 | GetInt(key string) (int64, bool) 15 | GetFloat(key string) (float64, bool) 16 | GetDuration(key string) (time.Duration, bool) 17 | GetTime(key string) (time.Time, bool) 18 | } 19 | -------------------------------------------------------------------------------- /balancer/balancer.go: -------------------------------------------------------------------------------- 1 | package balancer 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/tencentyun/tsf-go/naming" 7 | ) 8 | 9 | // DoneInfo is callback when rpc done 10 | type DoneInfo struct { 11 | Err error 12 | Trailer map[string]string 13 | } 14 | 15 | // Balancer is picker 16 | type Balancer interface { 17 | Pick(ctx context.Context, nodes []naming.Instance) (node *naming.Instance, done func(DoneInfo)) 18 | Schema() string 19 | } 20 | 21 | type Printable interface { 22 | PrintStats() 23 | } 24 | -------------------------------------------------------------------------------- /third_party/errors/errors.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package errors; 4 | 5 | option go_package = "github.com/go-kratos/kratos/v2/errors;errors"; 6 | option java_multiple_files = true; 7 | option java_package = "com.github.kratos.errors"; 8 | option objc_class_prefix = "KratosErrors"; 9 | 10 | import "google/protobuf/descriptor.proto"; 11 | 12 | extend google.protobuf.EnumOptions { 13 | int32 default_code = 1108; 14 | } 15 | 16 | extend google.protobuf.EnumValueOptions { 17 | int32 code = 1109; 18 | } 19 | -------------------------------------------------------------------------------- /pkg/balancer/balancer.go: -------------------------------------------------------------------------------- 1 | package balancer 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/tencentyun/tsf-go/pkg/naming" 7 | ) 8 | 9 | // DoneInfo is callback when rpc done 10 | type DoneInfo struct { 11 | Err error 12 | Trailer map[string]string 13 | } 14 | 15 | // Balancer is picker 16 | type Balancer interface { 17 | Pick(ctx context.Context, nodes []naming.Instance) (node *naming.Instance, done func(DoneInfo)) 18 | Schema() string 19 | } 20 | 21 | type Printable interface { 22 | PrintStats() 23 | } 24 | -------------------------------------------------------------------------------- /log/log_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/go-kratos/kratos/v2/log" 8 | "github.com/tencentyun/tsf-go/pkg/meta" 9 | ) 10 | 11 | func TestLog(t *testing.T) { 12 | log := log.NewHelper(NewLogger()) 13 | log.Infof("2233") 14 | log.Info("2233", "niang", "5566") 15 | log.Infow("name", "niang") 16 | log.Infow("msg", "request", "name", "niang") 17 | 18 | ctx := meta.WithSys(context.Background(), meta.SysPair{ 19 | Key: meta.ServiceName, 20 | Value: "provider", 21 | }) 22 | log.WithContext(ctx).Warn("test trace") 23 | } 24 | -------------------------------------------------------------------------------- /examples/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tencentyun/tsf-go/examples 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.7.3 7 | github.com/go-kratos/kratos/v2 v2.0.5 8 | github.com/go-redis/redis/v8 v8.11.5 9 | github.com/go-sql-driver/mysql v1.6.0 10 | github.com/luna-duclos/instrumentedsql v1.1.3 11 | github.com/tencentyun/tsf-go v0.0.0-20220323120705-9f1a22c7d03b 12 | google.golang.org/genproto v0.0.0-20210811021853-ddbe55d93216 13 | google.golang.org/grpc v1.40.0 14 | google.golang.org/protobuf v1.27.1 15 | ) 16 | 17 | replace github.com/tencentyun/tsf-go => ../ 18 | -------------------------------------------------------------------------------- /testdata/auth: -------------------------------------------------------------------------------- 1 | - rules: 2 | - ruleId: auth-rule-gvk79dyo 3 | ruleName: asdasd 4 | tags: 5 | - tagId: 5736 6 | tagType: S 7 | tagField: source.service.name 8 | tagOperator: EQUAL 9 | tagValue: client-grpc 10 | - tagId: 5737 11 | tagType: S 12 | tagField: source.group.id 13 | tagOperator: EQUAL 14 | tagValue: group-c0 15 | - tagId: 5738 16 | tagType: U 17 | tagField: user 18 | tagOperator: EQUAL 19 | tagValue: test2233 20 | tagProgram: '{5736} AND {5737} AND {5738}' 21 | ruleProgram: '{auth-rule-gvk79dyo}' 22 | type: B 23 | -------------------------------------------------------------------------------- /testdata/helloworld.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package tsf.test.helloworld; 4 | option go_package = ".;testdata"; 5 | 6 | // The greeting service definition. 7 | service Greeter { 8 | // Sends a SayHello greeting 9 | rpc SayHello (HelloRequest) returns (HelloReply) {} 10 | // Sends a SayHello greeting by Stream 11 | rpc SayHelloStream(stream HelloRequest) returns (stream HelloReply) {}; 12 | } 13 | 14 | // The request message containing the user's name. 15 | message HelloRequest { 16 | string name = 1; 17 | } 18 | 19 | // The response message containing the greetings 20 | message HelloReply { 21 | string message = 1; 22 | } -------------------------------------------------------------------------------- /testdata/lane_info: -------------------------------------------------------------------------------- 1 | createTime: 2020-11-23T07:19:01Z 2 | laneGroupList: 3 | - {applicationId: application-gateway, clusterType: C, createTime: !!timestamp '2020-11-23T07:20:40Z', 4 | entrance: true, groupId: group-gateway, laneGroupId: lane-g-ov6oen9v, laneId: lane-4y48nmgv, 5 | namespaceId: namespace-dap96mgv, updateTime: !!timestamp '2020-11-23T07:20:43Z'} 6 | - {applicationId: application-p, clusterType: C, createTime: !!timestamp '2020-11-23T07:19:01Z', 7 | entrance: false, groupId: group-p0, laneGroupId: lane-g-ba2wm9ja, laneId: lane-4y48nmgv, 8 | namespaceId: namespace-dap96mgv, updateTime: !!timestamp '2020-11-23T07:20:44Z'} 9 | laneId: lane-4y48nmgv -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .DS_Store 3 | *.test 4 | example/grpc-simple/consumer/consumer 5 | example/grpc-simple/provider/provider 6 | root.log 7 | trace_log.log 8 | trace.log 9 | monitor.log 10 | invocation_log.log 11 | examples/helloworld/grpc/consumer/consumer 12 | examples/helloworld/grpc/provider/provider 13 | examples/helloworld/http/consumer/consumer 14 | examples/helloworld/http/provider/provider 15 | examples/error/consumer/consumer 16 | examples/error/provider/provider 17 | examples/tracing/consumer/consumer 18 | examples/tracing/provider/provider 19 | examples/breaker/consumer/consumer 20 | examples/breaker/provider/provider 21 | examples/atom/provider/provider 22 | -------------------------------------------------------------------------------- /pkg/config/source.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type Spec struct { 8 | Key string 9 | Data Data 10 | } 11 | 12 | type Data interface { 13 | Unmarshal(interface{}) error 14 | Raw() []byte 15 | } 16 | 17 | // Source is config interface 18 | type Source interface { 19 | // 如果path是以/结尾,则是目录,否则当作key处理 20 | Subscribe(path string) Watcher 21 | Get(ctx context.Context, path string) []Spec 22 | } 23 | 24 | // Watcher is topic watch 25 | type Watcher interface { 26 | // Watch 第一次访问的时候如果有值则立马返回spec;后面watch的时候有变更才返回 27 | // 如果不传key则watch整个文件变动 28 | // 如果超时或者Watcher被Close应当抛出错误 29 | Watch(ctx context.Context) ([]Spec, error) 30 | Close() 31 | } 32 | -------------------------------------------------------------------------------- /examples/log/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/tencentyun/tsf-go/log" 7 | "github.com/tencentyun/tsf-go/pkg/meta" 8 | ) 9 | 10 | func main() { 11 | 12 | log := log.NewHelper( 13 | log.NewLogger( 14 | log.WithLevel(log.LevelDebug), 15 | log.WithPath("stdout"), 16 | log.WithTrace(true), 17 | ), 18 | ) 19 | log.Infof("app start: %d", 1) 20 | log.Info("2233", "niang", "5566") 21 | log.Infow("name", "niang") 22 | log.Infow("msg", "request", "name", "niang") 23 | 24 | ctx := meta.WithSys(context.Background(), meta.SysPair{ 25 | Key: meta.ServiceName, 26 | Value: "provider", 27 | }) 28 | log.WithContext(ctx).Warn("test trace") 29 | } 30 | -------------------------------------------------------------------------------- /pkg/balancer/random/random.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | 7 | "github.com/tencentyun/tsf-go/pkg/balancer" 8 | "github.com/tencentyun/tsf-go/pkg/naming" 9 | ) 10 | 11 | var ( 12 | _ balancer.Balancer = &Picker{} 13 | 14 | Name = "random" 15 | ) 16 | 17 | type Picker struct { 18 | } 19 | 20 | func (p *Picker) Pick(ctx context.Context, nodes []naming.Instance) (node *naming.Instance, done func(balancer.DoneInfo)) { 21 | if len(nodes) == 0 { 22 | return nil, func(balancer.DoneInfo) {} 23 | } 24 | cur := rand.Intn(len(nodes)) 25 | return &nodes[cur], func(balancer.DoneInfo) {} 26 | } 27 | 28 | func (p *Picker) Schema() string { 29 | return Name 30 | } 31 | -------------------------------------------------------------------------------- /pkg/metric/iterator.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import "fmt" 4 | 5 | // Iterator iterates the buckets within the window. 6 | type Iterator struct { 7 | count int 8 | iteratedCount int 9 | cur *Bucket 10 | } 11 | 12 | // Next returns true util all of the buckets has been iterated. 13 | func (i *Iterator) Next() bool { 14 | return i.count != i.iteratedCount 15 | } 16 | 17 | // Bucket gets current bucket. 18 | func (i *Iterator) Bucket() Bucket { 19 | if !(i.Next()) { 20 | panic(fmt.Errorf("stat/metric: iteration out of range iteratedCount: %d count: %d", i.iteratedCount, i.count)) 21 | } 22 | bucket := *i.cur 23 | i.iteratedCount++ 24 | i.cur = i.cur.Next() 25 | return bucket 26 | } 27 | -------------------------------------------------------------------------------- /docs/Config.md: -------------------------------------------------------------------------------- 1 | ### 分布式配置 2 | #### 1. 引入配置模块 3 | `"github.com/tencentyun/tsf-go/config"` 4 | 5 | #### 2. 非阻塞获取某一个配置值 6 | ```go 7 | if prefix, ok := config.GetString("prefix");ok { 8 | fmt.Println(prefix) 9 | } 10 | ``` 11 | #### 3. 订阅某一个配置文件的变化 12 | ```go 13 | type AppConfig struct { 14 | Stone string `yaml:"stone"` 15 | Auth Auth `yaml:"auth"` 16 | } 17 | type Auth struct { 18 | Key string `yaml:"key"` 19 | } 20 | config.WatchConfig(func(conf *config.Config) { 21 | var appCfg AppConfig 22 | err := conf.Unmarshal(&appCfg) 23 | if err != nil { 24 | panic(err) 25 | } 26 | fmt.Printf("appConfig: %v\n", appCfg) 27 | }) 28 | ``` 29 | > 更多 TSF 分布式配置的说明请参考 [配置管理概述](https://cloud.tencent.com/document/product/649/17956)。 -------------------------------------------------------------------------------- /examples/helloworld/proto/helloworld.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package helloworld; 4 | 5 | import "google/api/annotations.proto"; 6 | 7 | option go_package = "github.com/tencentyun/tsf-go/examples/helloworld/proto"; 8 | 9 | // The greeting service definition. 10 | service Greeter { 11 | // Sends a greeting 12 | rpc SayHello (HelloRequest) returns (HelloReply) { 13 | option (google.api.http) = { 14 | get: "/helloworld/{name}", 15 | }; 16 | } 17 | } 18 | 19 | // The request message containing the user's name. 20 | message HelloRequest { 21 | string name = 1; 22 | } 23 | 24 | // The response message containing the greetings 25 | message HelloReply { 26 | string message = 1; 27 | } 28 | -------------------------------------------------------------------------------- /balancer/random/random.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | 7 | "github.com/tencentyun/tsf-go/balancer" 8 | "github.com/tencentyun/tsf-go/naming" 9 | ) 10 | 11 | var ( 12 | _ balancer.Balancer = &Picker{} 13 | 14 | Name = "random" 15 | ) 16 | 17 | type Picker struct { 18 | } 19 | 20 | func New() *Picker { 21 | return &Picker{} 22 | } 23 | 24 | func (p *Picker) Pick(ctx context.Context, nodes []naming.Instance) (node *naming.Instance, done func(balancer.DoneInfo)) { 25 | if len(nodes) == 0 { 26 | return nil, func(balancer.DoneInfo) {} 27 | } 28 | cur := rand.Intn(len(nodes)) 29 | return &nodes[cur], func(balancer.DoneInfo) {} 30 | } 31 | 32 | func (p *Picker) Schema() string { 33 | return Name 34 | } 35 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // ParseTarget parse targe 10 | func ParseTarget(endpoint string) (string, error) { 11 | u, err := url.Parse(endpoint) 12 | if err != nil { 13 | if u, err = url.Parse("http://" + endpoint); err != nil { 14 | return "", err 15 | } 16 | } 17 | var service string 18 | if len(u.Path) > 1 { 19 | service = u.Path[1:] 20 | } 21 | return service, nil 22 | } 23 | 24 | func ParseAddr(addr string) (ip string, port uint16) { 25 | strs := strings.Split(addr, ":") 26 | if len(strs) > 0 { 27 | ip = strs[0] 28 | } 29 | if len(strs) > 1 { 30 | uport, _ := strconv.ParseUint(strs[1], 10, 16) 31 | port = uint16(uport) 32 | } 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /pkg/http/tsf.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | type Metadata struct { 4 | ApplicationID string `json:"ai"` 5 | ApplicationVersion string `json:"av"` 6 | ServiceName string `json:"sn"` 7 | InstanceID string `json:"ii"` 8 | GroupID string `json:"gi"` 9 | LocalIP string `json:"li"` 10 | NamespaceID string `json:"ni"` 11 | } 12 | 13 | type AtomMetadata struct { 14 | ApplicationID string `json:"application.id"` 15 | ServiceName string `json:"service.name"` 16 | InstanceID string `json:"instance.id"` 17 | GroupID string `json:"group.id"` 18 | LocalIP string `json:"connection.ip"` 19 | LocalPort string `json:"service.port"` 20 | NamespaceID string `json:"namespace.id"` 21 | Interface string `json:"interface"` 22 | } 23 | -------------------------------------------------------------------------------- /balancer/hash/hash_test.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "testing" 7 | ) 8 | 9 | func TestNew(t *testing.T) { 10 | c := NewHash() 11 | c.Add(Node{"cacheA", 0}) 12 | c.Add(Node{"cacheB", 1}) 13 | c.Add(Node{"cacheC", 2}) 14 | users := []string{"user_mcnulty", "user_bunk", "user_omar", "user_bunny", "user_stringer"} 15 | for _, u := range users { 16 | server, err := c.Get(u) 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | fmt.Printf("%s => %s %d\n", u, server, c.Index(server)) 21 | } 22 | fmt.Println("add node z!") 23 | c.Add(Node{"cacheZ", 3}) 24 | c.Add(Node{"cacheD", 4}) 25 | 26 | for _, u := range users { 27 | server, err := c.Get(u) 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | fmt.Printf("%s => %s %d\n", u, server, c.Index(server)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pkg/sys/tag/rule.go: -------------------------------------------------------------------------------- 1 | package tag 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type Relation int32 8 | 9 | const ( 10 | AND Relation = 0 11 | OR Relation = 1 12 | COMPOSITE Relation = 2 13 | ) 14 | 15 | type Rule struct { 16 | ID string 17 | Name string 18 | Tags []Tag 19 | Expression Relation 20 | } 21 | 22 | func (r *Rule) Hit(ctx context.Context) bool { 23 | if len(r.Tags) == 0 { 24 | return true 25 | } else if r.Expression == AND { 26 | for _, tag := range r.Tags { 27 | if !tag.Hit(ctx) { 28 | return false 29 | } 30 | } 31 | return true 32 | } else if r.Expression == OR { 33 | for _, tag := range r.Tags { 34 | if tag.Hit(ctx) { 35 | return true 36 | } 37 | } 38 | } else if r.Expression == COMPOSITE { 39 | // TODO: impl 40 | return false 41 | } 42 | return false 43 | } 44 | -------------------------------------------------------------------------------- /docs/Balancer.md: -------------------------------------------------------------------------------- 1 | # 负载均衡 2 | TSF 默认提供了三种负载均衡算法:Random 、P2C 、 Consistent Hashing 3 | 默认算法是 P2C 4 | 5 | #### 1.Random 6 | 随机调度策略 7 | ```go 8 | import "github.com/tencentyun/tsf-go/balancer/random" 9 | 10 | clientOpts = append(clientOpts, tsf.ClientGrpcOptions(random.New())...) 11 | ``` 12 | 13 | #### 2.P2C (默认) 14 | 基于[Power of Two choices](http://www.eecs.harvard.edu/~michaelm/NEWWORK/postscripts/twosurvey.pdf)算法,同时结合请求延迟、错误率、并发数数据实时调整权重的负载均衡策略,从而降低请求响应延迟和后端负载 15 | ```go 16 | import "github.com/tencentyun/tsf-go/balancer/p2c" 17 | 18 | clientOpts = append(clientOpts, tsf.ClientGrpcOptions(p2c.New())...) 19 | ``` 20 | #### 3.Consistent Hashing 21 | 一致性Hash算法 22 | ```go 23 | import "github.com/tencentyun/tsf-go/balancer/hash" 24 | 25 | clientOpts = append(clientOpts, tsf.ClientGrpcOptions(hash.New())...) 26 | //将 hash key注入至context中,一致性Hash负载均衡会根据这个key的值进行hash 27 | ctx = hash.NewContext(ctx,"test_key") 28 | client.SayHello(ctx, in) 29 | ``` -------------------------------------------------------------------------------- /pkg/grpc/server/wrapper.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc" 7 | ) 8 | 9 | // WrappedServerStream is a thin wrapper around grpc.ServerStream that allows modifying context. 10 | type WrappedServerStream struct { 11 | grpc.ServerStream 12 | // WrappedContext is the wrapper's own Context. You can assign it. 13 | WrappedContext context.Context 14 | } 15 | 16 | // Context returns the wrapper's WrappedContext, overwriting the nested grpc.ServerStream.Context() 17 | func (w *WrappedServerStream) Context() context.Context { 18 | return w.WrappedContext 19 | } 20 | 21 | // WrapServerStream returns a ServerStream that has the ability to overwrite context. 22 | func WrapServerStream(stream grpc.ServerStream) *WrappedServerStream { 23 | if existing, ok := stream.(*WrappedServerStream); ok { 24 | return existing 25 | } 26 | return &WrappedServerStream{ServerStream: stream, WrappedContext: stream.Context()} 27 | } 28 | -------------------------------------------------------------------------------- /examples/config/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "github.com/tencentyun/tsf-go/config" 8 | ) 9 | 10 | type AppConfig struct { 11 | Stone string `yaml:"stone"` 12 | Auth Auth `yaml:"auth"` 13 | } 14 | type Auth struct { 15 | Key string `yaml:"key"` 16 | } 17 | 18 | func main() { 19 | flag.Parse() 20 | cfg := config.GetConfig() 21 | value, _ := cfg.GetString("stone") 22 | fmt.Println("stone:", value) 23 | // 监听应用配置文件变化 24 | config.WatchConfig(func(conf *config.Config) { 25 | var appCfg AppConfig 26 | err := conf.Unmarshal(&appCfg) 27 | if err != nil { 28 | panic(err) 29 | } 30 | fmt.Printf("appConfig: %v\n", appCfg) 31 | }) 32 | // 订阅全局配置(命名空间纬度) 33 | config.WatchConfig(func(conf *config.Config) { 34 | var gloablCfg AppConfig 35 | err := conf.Unmarshal(&gloablCfg) 36 | if err != nil { 37 | panic(err) 38 | } 39 | fmt.Printf("globalConfig: %v\n", gloablCfg) 40 | }, config.WithGlobal(true)) 41 | } 42 | -------------------------------------------------------------------------------- /examples/helloworld/http/consumer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "log" 7 | "time" 8 | 9 | transhttp "github.com/go-kratos/kratos/v2/transport/http" 10 | pb "github.com/tencentyun/tsf-go/examples/helloworld/proto" 11 | "github.com/tencentyun/tsf-go/naming/consul" 12 | ) 13 | 14 | func main() { 15 | flag.Parse() 16 | 17 | c := consul.DefaultConsul() 18 | 19 | go func() { 20 | for { 21 | time.Sleep(time.Millisecond * 1000) 22 | callHTTP() 23 | time.Sleep(time.Second) 24 | } 25 | }() 26 | 27 | newService(c) 28 | } 29 | 30 | func callHTTP() { 31 | conn, err := transhttp.NewClient( 32 | context.Background(), 33 | transhttp.WithEndpoint("127.0.0.1:8080"), 34 | ) 35 | if err != nil { 36 | panic(err) 37 | } 38 | client := pb.NewGreeterHTTPClient(conn) 39 | reply, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "kratos_http"}) 40 | if err != nil { 41 | panic(err) 42 | } 43 | log.Printf("[http] SayHello %s\n", reply.Message) 44 | } 45 | -------------------------------------------------------------------------------- /examples/helloworld/grpc/consumer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "log" 7 | "time" 8 | 9 | "github.com/go-kratos/kratos/v2/transport/grpc" 10 | pb "github.com/tencentyun/tsf-go/examples/helloworld/proto" 11 | "github.com/tencentyun/tsf-go/naming/consul" 12 | ) 13 | 14 | func main() { 15 | flag.Parse() 16 | 17 | c := consul.DefaultConsul() 18 | 19 | go func() { 20 | for { 21 | time.Sleep(time.Millisecond * 1000) 22 | callGRPC() 23 | time.Sleep(time.Second) 24 | } 25 | }() 26 | 27 | newService(c) 28 | } 29 | 30 | func callGRPC() { 31 | conn, err := grpc.DialInsecure( 32 | context.Background(), 33 | grpc.WithEndpoint("127.0.0.1:9090"), 34 | ) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | client := pb.NewGreeterClient(conn) 39 | reply, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "kratos_grpc"}) 40 | if err != nil { 41 | log.Fatal("say hello failed!", err) 42 | } 43 | log.Printf("[grpc] SayHello %+v\n", reply) 44 | } 45 | -------------------------------------------------------------------------------- /docs/Breaker.md: -------------------------------------------------------------------------------- 1 | # 熔断 2 | tsf-go支持[google sre 自适应熔断算法](https://pandaychen.github.io/2020/05/10/A-GOOGLE-SRE-BREAKER/),但是默认不开启,需要用户插入Middleware 3 | 1. 插入Breaker Middleware: 4 | ```go 5 | clientOpts = append(clientOpts, tsf.ClientHTTPOptions(tsf.WithMiddlewares( 6 | // 插入Breaker Middleware 7 | tsf.BreakerMiddleware()), 8 | )...) 9 | ``` 10 | 2. 自定义error hook 11 | ```go 12 | // tsf breaker middleware 默认error code大于等于500才认为出错并MarkFailed 13 | // 这里我们改成大于400就认为出错 14 | errHook := func(ctx context.Context, operation string, err error) (success bool) { 15 | if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) || errors.FromError(err).StatusCode() > 400 { 16 | return false 17 | } 18 | return true 19 | } 20 | ``` 21 | 3. 自定义breaker配置 22 | ```go 23 | cfg := &breaker.Config{ 24 | // 成功率放大系数,即k * success < total时触发熔断 25 | // 默认1.5 26 | K: 1.4, 27 | // 熔断触发临界请求量 28 | // 统计窗口内请求量低于Request值则不触发熔断 29 | // 默认值 20 30 | Request: 10, 31 | } 32 | ``` 33 | 具体使用方法参考[breaker examples](/examples/breaker) -------------------------------------------------------------------------------- /examples/error/errors/error_reason_errors.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-errors. DO NOT EDIT. 2 | 3 | package errors 4 | 5 | import ( 6 | fmt "fmt" 7 | errors "github.com/go-kratos/kratos/v2/errors" 8 | ) 9 | 10 | // This is a compile-time assertion to ensure that this generated file 11 | // is compatible with the kratos package it is being compiled against. 12 | const _ = errors.SupportPackageIsVersion1 13 | 14 | func IsUserNotFound(err error) bool { 15 | e := errors.FromError(err) 16 | return e.Reason == ErrorReason_USER_NOT_FOUND.String() && e.Code == 404 17 | } 18 | 19 | func ErrorUserNotFound(format string, args ...interface{}) *errors.Error { 20 | return errors.New(404, ErrorReason_USER_NOT_FOUND.String(), fmt.Sprintf(format, args...)) 21 | } 22 | 23 | func IsContentMissing(err error) bool { 24 | e := errors.FromError(err) 25 | return e.Reason == ErrorReason_CONTENT_MISSING.String() && e.Code == 400 26 | } 27 | 28 | func ErrorContentMissing(format string, args ...interface{}) *errors.Error { 29 | return errors.New(400, ErrorReason_CONTENT_MISSING.String(), fmt.Sprintf(format, args...)) 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 腾讯云 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. -------------------------------------------------------------------------------- /examples/tracing/consumer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "log" 7 | "time" 8 | 9 | transhttp "github.com/go-kratos/kratos/v2/transport/http" 10 | pb "github.com/tencentyun/tsf-go/examples/helloworld/proto" 11 | "github.com/tencentyun/tsf-go/naming/consul" 12 | "github.com/tencentyun/tsf-go/tracing" 13 | ) 14 | 15 | func main() { 16 | flag.Parse() 17 | // 将tracing采样率提升至100% 18 | // 如果不设置,默认为10% 19 | tracing.SetProvider(tracing.WithSampleRatio(1.0)) 20 | go func() { 21 | for { 22 | time.Sleep(time.Millisecond * 1000) 23 | callHTTP() 24 | time.Sleep(time.Second) 25 | } 26 | }() 27 | 28 | newService(consul.DefaultConsul()) 29 | } 30 | 31 | func callHTTP() { 32 | conn, err := transhttp.NewClient( 33 | context.Background(), 34 | transhttp.WithEndpoint("127.0.0.1:8080"), 35 | ) 36 | if err != nil { 37 | panic(err) 38 | } 39 | client := pb.NewGreeterHTTPClient(conn) 40 | reply, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "kratos_http"}) 41 | if err != nil { 42 | panic(err) 43 | } 44 | log.Printf("[http] SayHello %s\n", reply.Message) 45 | } 46 | -------------------------------------------------------------------------------- /auth.go: -------------------------------------------------------------------------------- 1 | package tsf 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | 7 | "github.com/go-kratos/kratos/v2" 8 | "github.com/go-kratos/kratos/v2/middleware" 9 | "github.com/tencentyun/tsf-go/pkg/auth" 10 | "github.com/tencentyun/tsf-go/pkg/auth/authenticator" 11 | "github.com/tencentyun/tsf-go/pkg/config/consul" 12 | "github.com/tencentyun/tsf-go/pkg/naming" 13 | "github.com/tencentyun/tsf-go/pkg/sys/env" 14 | ) 15 | 16 | func authMiddleware() middleware.Middleware { 17 | var authen auth.Auth 18 | var once sync.Once 19 | 20 | return func(handler middleware.Handler) middleware.Handler { 21 | return func(ctx context.Context, req interface{}) (resp interface{}, err error) { 22 | once.Do(func() { 23 | k, _ := kratos.FromContext(ctx) 24 | serviceName := k.Name() 25 | builder := &authenticator.Builder{} 26 | authen = builder.Build(consul.DefaultConsul(), naming.NewService(env.NamespaceID(), serviceName)) 27 | }) 28 | _, operation := ServerOperation(ctx) 29 | // 鉴权 30 | err = authen.Verify(ctx, operation) 31 | if err != nil { 32 | return 33 | } 34 | return handler(ctx, req) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pkg/sys/monitor/logger.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import ( 4 | "github.com/natefinch/lumberjack" 5 | "github.com/tencentyun/tsf-go/pkg/sys/env" 6 | "go.uber.org/zap" 7 | "go.uber.org/zap/zapcore" 8 | ) 9 | 10 | var logger *zap.Logger 11 | 12 | func init() { 13 | initMonitor() 14 | } 15 | 16 | func initMonitor() { 17 | path := env.MonitorPath() 18 | encoding := zapcore.EncoderConfig{ 19 | TimeKey: "", 20 | LevelKey: "", 21 | NameKey: "", 22 | CallerKey: "", 23 | FunctionKey: zapcore.OmitKey, 24 | MessageKey: "msg", 25 | StacktraceKey: "", 26 | LineEnding: zapcore.DefaultLineEnding, 27 | EncodeLevel: zapcore.LowercaseLevelEncoder, 28 | EncodeTime: zapcore.EpochTimeEncoder, 29 | EncodeDuration: zapcore.SecondsDurationEncoder, 30 | EncodeCaller: zapcore.ShortCallerEncoder, 31 | } 32 | 33 | w := zapcore.AddSync(&lumberjack.Logger{ 34 | Filename: path, 35 | MaxSize: 100, // megabytes 36 | MaxBackups: 3, 37 | MaxAge: 10, // days 38 | }) 39 | core := zapcore.NewCore( 40 | zapcore.NewConsoleEncoder(encoding), 41 | w, 42 | zapcore.Level(zap.InfoLevel), 43 | ) 44 | logger = zap.New(core) 45 | } 46 | -------------------------------------------------------------------------------- /examples/helloworld/gin/provider/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/go-kratos/kratos/v2" 9 | "github.com/tencentyun/tsf-go" 10 | tgin "github.com/tencentyun/tsf-go/gin" 11 | 12 | "github.com/go-kratos/kratos/v2/errors" 13 | "github.com/go-kratos/kratos/v2/transport/http" 14 | ) 15 | 16 | type Reply struct { 17 | Message string `json:"message"` 18 | } 19 | 20 | func main() { 21 | router := gin.Default() 22 | router.Use(tgin.Middlewares(tsf.ServerMiddleware())) 23 | 24 | router.GET("/helloworld/:name", func(ctx *gin.Context) { 25 | name := ctx.Param("name") 26 | if name != "error" { 27 | ctx.JSON(200, Reply{Message: fmt.Sprintf("Hello %v!", name)}) 28 | } else { 29 | tgin.Error(ctx, errors.Unauthorized("auth_error", "no authentication")) 30 | } 31 | }) 32 | 33 | httpSrv := http.NewServer(http.Address(":8000")) 34 | httpSrv.HandlePrefix("/", router) 35 | 36 | opts := []kratos.Option{kratos.Name("provider-http"), kratos.Server(httpSrv)} 37 | opts = append(opts, tsf.AppOptions()...) 38 | app := kratos.New(opts...) 39 | 40 | if err := app.Run(); err != nil { 41 | log.Println(err) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /docs/Log.md: -------------------------------------------------------------------------------- 1 | # TSF日志输出 2 | ## Quick Start 3 | #### 1. 初始化log helper 4 | ```go 5 | import "github.com/tencentyun/tsf-go/log" 6 | 7 | // 默认日志配置项,等同于log.NewLogger() 8 | // 当没有显示配置日志输出地址时,运行在tsf平台时默认会输出到/data/logs/root.log 9 | logger := log.DefaultLogger 10 | 11 | log := log.NewHelper(logger) 12 | ``` 13 | #### 2. 打印日志 14 | ```go 15 | // 打印format后的日志 16 | log.Infof("app started!date: %v", time.Now()) 17 | // 打印key value pair日志 18 | log.Infow("msg", "welcome to tsf world!", "name", "tsf") 19 | // 注入上下文中trace id等信息至日志中 20 | log.WithContext(ctx).Infof("get request message!") 21 | ``` 22 | #### 3.TSF 控制台日志配置 23 | 需要在 TSF [日志服务]-[日志配置] 中新建一个配置,并绑定部署组并发布 24 | 配置日志类型为自定义 Logback 25 | 日志格式为 `%d{yyyy-MM-dd HH:mm:ss.SSS} %level %thread %trace %msg%n` 26 | 采集路径为/data/logs/root.log 27 | > 更多 TSF 日志配置可参考 [日志服务说明](https://cloud.tencent.com/document/product/649/18196)。 28 | 29 | 30 | ## 配置参数说明 31 | 1. WithLevel 32 | 配置日志显示等级,默认为Info 33 | 也可通过环境变量tsf_log_level来控制 34 | 2. WithTrace 35 | 是否开启Trace信息,默认为true 36 | 如果打印日志时不通过WithContext传递Go的context,会导致日志中不打印traceID。 37 | 3. WithPath 38 | 日志输出路径,运行在tsf平台时默认为/data/logs/root.log 39 | 运行在本地环境时默认是stdout 40 | 也可通过环境变量tsf_log_path来控制 41 | 4. WithZap 42 | 替换整个logger核心组件 -------------------------------------------------------------------------------- /third_party/google/api/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/api/http.proto"; 20 | import "google/protobuf/descriptor.proto"; 21 | 22 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "AnnotationsProto"; 25 | option java_package = "com.google.api"; 26 | option objc_class_prefix = "GAPI"; 27 | 28 | extend google.protobuf.MethodOptions { 29 | // See `HttpRule`. 30 | HttpRule http = 72295728; 31 | } 32 | -------------------------------------------------------------------------------- /route/composite/composite.go: -------------------------------------------------------------------------------- 1 | package composite 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | 7 | "github.com/tencentyun/tsf-go/naming" 8 | "github.com/tencentyun/tsf-go/route" 9 | "github.com/tencentyun/tsf-go/route/router" 10 | 11 | "github.com/tencentyun/tsf-go/route/lane" 12 | ) 13 | 14 | var ( 15 | _ route.Router = &Composite{} 16 | 17 | mu sync.Mutex 18 | defaultComposite *Composite 19 | ) 20 | 21 | type Composite struct { 22 | route route.Router 23 | lane *lane.Lane 24 | } 25 | 26 | func DefaultComposite() *Composite { 27 | mu.Lock() 28 | defer mu.Unlock() 29 | if defaultComposite == nil { 30 | defaultComposite = New(router.DefaultRouter(), lane.DefaultLane()) 31 | } 32 | return defaultComposite 33 | } 34 | 35 | func New(router *router.Router, lane *lane.Lane) *Composite { 36 | return &Composite{route: router, lane: lane} 37 | } 38 | 39 | func (c *Composite) Select(ctx context.Context, svc naming.Service, nodes []naming.Instance) []naming.Instance { 40 | res := c.lane.Select(ctx, svc, nodes) 41 | if len(res) == 0 { 42 | return res 43 | } 44 | return c.route.Select(ctx, svc, res) 45 | } 46 | 47 | func (c *Composite) Lane() *lane.Lane { 48 | return c.lane 49 | } 50 | -------------------------------------------------------------------------------- /docs/Swagger.md: -------------------------------------------------------------------------------- 1 | # Swagger API 2 | ### 集成 3 | tsf-go自动集成了swagger api 4 | 在应用的启动入口`kratos.New()`中加了`tsf.AppOptions()`这个Option就会自动生成服务的swagger json并上报至TSF治理平台 5 | 6 | ### 限制 7 | 1.现在一个微服务只支持注册一个proto service的swagger json,如果引入的proto文件中定义了了多个Proto Service,那么需要通过`tsf.ProtoServiceName`这个Option手动指定上报哪个service的swagger json: 8 | `tsf.AppOptions(tsf.ProtoServiceName(""))` 9 | `` 需要替换成实际的Proto Service名字(比如`helloworld.Greeter`) 10 | 11 | 2.如果启动的时候日志出现报错`failed to decompress enc: bad gzipped descriptor: EOF`的报错说明被依赖的proto生成时传入的路径不对, 12 | 比如: 13 | - api/basedata/tag/v1/tag.proto 14 | - api/basedata/article/v1/article.proto 15 | 16 | (其中 **api/basedata/article/v1/article.proto** 依赖 **api/basedata/tag/v1/tag.proto**; 17 | service定义在**api/basedata/article/v1/article.proto** 文件中) 18 | 19 | 20 | 这种情况是由于生成tag.pb.go时没有传入完整的路径,导致生成的source变成了`tag.proto` 21 | 那我们只要将完整的依赖路径传给protoc就可以修复了(当然此时需要改成在父目录中执行protoc了): protoc --proto_path=. --proto_path=./third_party --go_out=paths=source_relative:. api/basedata/tag/v1/tag.proto 22 | 这样生成的tag.pb.go文件中source就是正确的:`api/basedata/tag/v1/tag.proto` 23 | 24 | 3.如果需要在http server中集成swagger调试页面请参考[OpenAPI Swagger 使用 25 | ](https://go-kratos.dev/docs/guide/openapi) -------------------------------------------------------------------------------- /pkg/route/composite/composite.go: -------------------------------------------------------------------------------- 1 | package composite 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | 7 | "github.com/tencentyun/tsf-go/pkg/naming" 8 | "github.com/tencentyun/tsf-go/pkg/route" 9 | "github.com/tencentyun/tsf-go/pkg/route/lane" 10 | "github.com/tencentyun/tsf-go/pkg/route/router" 11 | ) 12 | 13 | var ( 14 | _ route.Router = &Composite{} 15 | 16 | mu sync.Mutex 17 | defaultComposite *Composite 18 | ) 19 | 20 | type Composite struct { 21 | router route.Router 22 | lane *lane.Lane 23 | } 24 | 25 | func DefaultComposite() *Composite { 26 | mu.Lock() 27 | defer mu.Unlock() 28 | if defaultComposite == nil { 29 | defaultComposite = New(router.DefaultRouter(), lane.DefaultLane()) 30 | } 31 | return defaultComposite 32 | } 33 | 34 | func New(router route.Router, lane *lane.Lane) *Composite { 35 | return &Composite{router: router, lane: lane} 36 | } 37 | 38 | func (c *Composite) Select(ctx context.Context, svc naming.Service, nodes []naming.Instance) []naming.Instance { 39 | res := c.lane.Select(ctx, svc, nodes) 40 | if len(res) == 0 { 41 | return res 42 | } 43 | return c.router.Select(ctx, svc, res) 44 | } 45 | 46 | func (c *Composite) Lane() *lane.Lane { 47 | return c.lane 48 | } 49 | -------------------------------------------------------------------------------- /pkg/sys/monitor/stat.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import "time" 4 | 5 | const ( 6 | CategoryMS = "MS" 7 | 8 | KindClient = "CLIENT" 9 | KindServer = "SERVER" 10 | ) 11 | 12 | type Stat struct { 13 | Begin time.Time 14 | End time.Time 15 | Category string 16 | Kind string 17 | Local *Endpoint 18 | Remote *Endpoint 19 | StatusCode int 20 | } 21 | 22 | type Endpoint struct { 23 | ServiceName string `json:"service"` 24 | InterfaceName string `json:"interface"` 25 | Method string `json:"method"` 26 | Path string `json:"path"` 27 | } 28 | 29 | func NewStat(category string, kind string, local *Endpoint, remote *Endpoint) *Stat { 30 | return &Stat{ 31 | Begin: time.Now(), 32 | Category: category, 33 | Kind: kind, 34 | Local: local, 35 | Remote: remote, 36 | } 37 | } 38 | 39 | func (s *Stat) Record(statusCode int) { 40 | s.End = time.Now() 41 | s.StatusCode = statusCode 42 | monitor.saveStat(s) 43 | } 44 | 45 | func (s *Stat) HashCode() string { 46 | hc := s.Category + s.Kind + s.Local.ServiceName + "/" + s.Local.InterfaceName 47 | if s.Remote == nil { 48 | return hc 49 | } 50 | return hc + "-" + s.Remote.ServiceName + "/" + s.Remote.InterfaceName 51 | } 52 | -------------------------------------------------------------------------------- /tracing/logger.go: -------------------------------------------------------------------------------- 1 | package tracing 2 | 3 | import ( 4 | "github.com/natefinch/lumberjack" 5 | "github.com/tencentyun/tsf-go/pkg/sys/env" 6 | "go.uber.org/zap" 7 | "go.uber.org/zap/zapcore" 8 | ) 9 | 10 | // defaultLogger is default trace logger 11 | var defaultLogger *zap.Logger 12 | 13 | func newLogger() *zap.Logger { 14 | path := env.TracePath() 15 | encoding := zapcore.EncoderConfig{ 16 | TimeKey: "", 17 | LevelKey: "", 18 | NameKey: "", 19 | CallerKey: "", 20 | FunctionKey: zapcore.OmitKey, 21 | MessageKey: "msg", 22 | StacktraceKey: "", 23 | LineEnding: zapcore.DefaultLineEnding, 24 | EncodeLevel: zapcore.LowercaseLevelEncoder, 25 | EncodeTime: zapcore.EpochTimeEncoder, 26 | EncodeDuration: zapcore.SecondsDurationEncoder, 27 | EncodeCaller: zapcore.ShortCallerEncoder, 28 | } 29 | w := zapcore.AddSync(&lumberjack.Logger{ 30 | Filename: path, 31 | MaxSize: 100, // megabytes 32 | MaxBackups: 3, 33 | MaxAge: 10, // days 34 | }) 35 | core := zapcore.NewCore( 36 | zapcore.NewConsoleEncoder(encoding), 37 | w, 38 | zapcore.Level(zap.InfoLevel), 39 | ) 40 | return zap.New(core) 41 | } 42 | 43 | func init() { 44 | defaultLogger = newLogger() 45 | } 46 | -------------------------------------------------------------------------------- /pkg/sys/trace/logger.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import ( 4 | "github.com/tencentyun/tsf-go/pkg/sys/env" 5 | 6 | "github.com/natefinch/lumberjack" 7 | "go.uber.org/zap" 8 | "go.uber.org/zap/zapcore" 9 | ) 10 | 11 | // DefaultLogger is default trace logger 12 | var DefaultLogger *zap.Logger 13 | 14 | func init() { 15 | DefaultLogger = getLogger() 16 | } 17 | 18 | func getLogger() *zap.Logger { 19 | path := env.TracePath() 20 | encoding := zapcore.EncoderConfig{ 21 | TimeKey: "", 22 | LevelKey: "", 23 | NameKey: "", 24 | CallerKey: "", 25 | FunctionKey: zapcore.OmitKey, 26 | MessageKey: "msg", 27 | StacktraceKey: "", 28 | LineEnding: zapcore.DefaultLineEnding, 29 | EncodeLevel: zapcore.LowercaseLevelEncoder, 30 | EncodeTime: zapcore.EpochTimeEncoder, 31 | EncodeDuration: zapcore.SecondsDurationEncoder, 32 | EncodeCaller: zapcore.ShortCallerEncoder, 33 | } 34 | w := zapcore.AddSync(&lumberjack.Logger{ 35 | Filename: path, 36 | MaxSize: 100, // megabytes 37 | MaxBackups: 3, 38 | MaxAge: 10, // days 39 | }) 40 | core := zapcore.NewCore( 41 | zapcore.NewConsoleEncoder(encoding), 42 | w, 43 | zapcore.Level(zap.InfoLevel), 44 | ) 45 | return zap.New(core) 46 | } 47 | -------------------------------------------------------------------------------- /pkg/grpc/client/trace.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | // Copyright 2019 The OpenZipkin Authors 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | import ( 18 | "context" 19 | "strings" 20 | 21 | "github.com/openzipkin/zipkin-go" 22 | "github.com/openzipkin/zipkin-go/model" 23 | "google.golang.org/grpc/peer" 24 | ) 25 | 26 | func spanName(method string) string { 27 | name := strings.TrimPrefix(method, "/") 28 | name = strings.Replace(name, "/", ".", -1) 29 | return name 30 | } 31 | 32 | func remoteEndpointFromContext(ctx context.Context, name string) *model.Endpoint { 33 | remoteAddr := "" 34 | 35 | p, ok := peer.FromContext(ctx) 36 | if ok { 37 | remoteAddr = p.Addr.String() 38 | } 39 | 40 | ep, _ := zipkin.NewEndpoint(name, remoteAddr) 41 | return ep 42 | } 43 | -------------------------------------------------------------------------------- /pkg/grpc/encoding/json/json.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | 7 | "github.com/gogo/protobuf/jsonpb" 8 | "github.com/gogo/protobuf/proto" 9 | "google.golang.org/grpc/encoding" 10 | ) 11 | 12 | //Reference https://jbrandhorst.com/post/grpc-json/ 13 | func Init() { 14 | if encoding.GetCodec(JSON{}.Name()) == nil { 15 | encoding.RegisterCodec(JSON{ 16 | Marshaler: jsonpb.Marshaler{ 17 | EmitDefaults: true, 18 | OrigName: true, 19 | }, 20 | }) 21 | } 22 | } 23 | 24 | // JSON is impl of encoding.Codec 25 | type JSON struct { 26 | jsonpb.Marshaler 27 | jsonpb.Unmarshaler 28 | } 29 | 30 | // Name is name of JSON 31 | func (j JSON) Name() string { 32 | return "json" 33 | } 34 | 35 | // Marshal is json marshal 36 | func (j JSON) Marshal(v interface{}) (out []byte, err error) { 37 | if pm, ok := v.(proto.Message); ok { 38 | b := new(bytes.Buffer) 39 | err := j.Marshaler.Marshal(b, pm) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return b.Bytes(), nil 44 | } 45 | return json.Marshal(v) 46 | } 47 | 48 | // Unmarshal is json unmarshal 49 | func (j JSON) Unmarshal(data []byte, v interface{}) (err error) { 50 | if len(data) == 0 { 51 | data = []byte("{}") 52 | } 53 | if pm, ok := v.(proto.Message); ok { 54 | b := bytes.NewBuffer(data) 55 | return j.Unmarshaler.Unmarshal(b, pm) 56 | } 57 | return json.Unmarshal(data, v) 58 | } 59 | -------------------------------------------------------------------------------- /tracing/statsHandler.go: -------------------------------------------------------------------------------- 1 | package tracing 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/tencentyun/tsf-go/util" 7 | "go.opentelemetry.io/otel/attribute" 8 | "go.opentelemetry.io/otel/trace" 9 | "google.golang.org/grpc/peer" 10 | "google.golang.org/grpc/stats" 11 | ) 12 | 13 | type ClientHandler struct { 14 | } 15 | 16 | // HandleConn exists to satisfy gRPC stats.Handler. 17 | func (c *ClientHandler) HandleConn(ctx context.Context, cs stats.ConnStats) { 18 | } 19 | 20 | // TagConn exists to satisfy gRPC stats.Handler. 21 | func (c *ClientHandler) TagConn(ctx context.Context, cti *stats.ConnTagInfo) context.Context { 22 | return ctx 23 | } 24 | 25 | // HandleRPC implements per-RPC tracing and stats instrumentation. 26 | func (c *ClientHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) { 27 | if _, ok := rs.(*stats.OutHeader); ok { 28 | if p, ok := peer.FromContext(ctx); ok { 29 | remoteAddr := p.Addr.String() 30 | if span := trace.SpanFromContext(ctx); span.SpanContext().HasTraceID() { 31 | remoteIP, remotePort := util.ParseAddr(remoteAddr) 32 | span.SetAttributes(attribute.String("peer.ip", remoteIP)) 33 | span.SetAttributes(attribute.Int64("peer.port", int64(remotePort))) 34 | } 35 | } 36 | } 37 | } 38 | 39 | // TagRPC implements per-RPC context management. 40 | func (c *ClientHandler) TagRPC(ctx context.Context, rti *stats.RPCTagInfo) context.Context { 41 | 42 | return ctx 43 | } 44 | -------------------------------------------------------------------------------- /examples/helloworld/http/provider/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | 8 | "github.com/go-kratos/kratos/v2" 9 | "github.com/go-kratos/kratos/v2/middleware/logging" 10 | "github.com/go-kratos/kratos/v2/middleware/recovery" 11 | "github.com/go-kratos/kratos/v2/transport/http" 12 | tsf "github.com/tencentyun/tsf-go" 13 | pb "github.com/tencentyun/tsf-go/examples/helloworld/proto" 14 | "github.com/tencentyun/tsf-go/log" 15 | ) 16 | 17 | // server is used to implement helloworld.GreeterServer. 18 | type server struct { 19 | pb.UnimplementedGreeterServer 20 | } 21 | 22 | // SayHello implements helloworld.GreeterServer 23 | func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { 24 | return &pb.HelloReply{Message: fmt.Sprintf("Welcome %+v!", in.Name)}, nil 25 | } 26 | 27 | func main() { 28 | flag.Parse() 29 | logger := log.DefaultLogger 30 | log := log.NewHelper(logger) 31 | 32 | s := &server{} 33 | httpSrv := http.NewServer( 34 | http.Address("0.0.0.0:8000"), 35 | http.Middleware( 36 | recovery.Recovery(), 37 | tsf.ServerMiddleware(), 38 | logging.Server(logger), 39 | ), 40 | ) 41 | pb.RegisterGreeterHTTPServer(httpSrv, s) 42 | 43 | opts := []kratos.Option{kratos.Name("provider-http"), kratos.Server(httpSrv)} 44 | opts = append(opts, tsf.AppOptions()...) 45 | app := kratos.New(opts...) 46 | 47 | if err := app.Run(); err != nil { 48 | log.Errorf("app run failed:%v", err) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pkg/sys/tag/tag.go: -------------------------------------------------------------------------------- 1 | package tag 2 | 3 | import ( 4 | "context" 5 | "regexp" 6 | "strings" 7 | 8 | "github.com/tencentyun/tsf-go/log" 9 | "github.com/tencentyun/tsf-go/pkg/meta" 10 | ) 11 | 12 | type TagType int32 13 | 14 | const ( 15 | TypeSys TagType = 0 16 | TypeUser TagType = 1 17 | 18 | Equal = "EQUAL" 19 | NotEqual = "NOT_EQUAL" 20 | In = "IN" 21 | NotIn = "NOT_IN" 22 | Regex = "REGEX" 23 | ) 24 | 25 | // Tag is tsf tag 26 | type Tag struct { 27 | Type TagType 28 | Field string 29 | Operator string 30 | Value string 31 | } 32 | 33 | func (t Tag) Hit(ctx context.Context) bool { 34 | var v interface{} 35 | if t.Type == TypeSys { 36 | v = meta.Sys(ctx, t.Field) 37 | log.DefaultLog.WithContext(ctx).Debugw("msg", "hit sys:", "field", t.Field, "value", v) 38 | } else { 39 | v = meta.User(ctx, t.Field) 40 | log.DefaultLog.WithContext(ctx).Debugw("msg", "hit user:", "field", t.Field, "value", v) 41 | } 42 | if v == nil { 43 | return false 44 | } 45 | target, ok := v.(string) 46 | if !ok { 47 | return false 48 | } 49 | if t.Operator == Equal { 50 | return target == t.Value 51 | } else if t.Operator == NotEqual { 52 | return !(target == t.Value) 53 | } else if t.Operator == In { 54 | return strings.Contains(t.Value, target) 55 | } else if t.Operator == NotIn { 56 | return !strings.Contains(t.Value, target) 57 | } else if t.Operator == Regex { 58 | ok, _ = regexp.MatchString(t.Value, target) 59 | return ok 60 | } 61 | return false 62 | } 63 | -------------------------------------------------------------------------------- /pkg/metric/metric.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | // Opts contains the common arguments for creating Metric. 4 | type Opts struct { 5 | } 6 | 7 | // Metric is a sample interface. 8 | // Implementations of Metrics in metric package are Counter, Gauge, 9 | // PointGauge, RollingCounter and RollingGauge. 10 | type Metric interface { 11 | // Add adds the given value to the counter. 12 | Add(int64) 13 | // Value gets the current value. 14 | // If the metric's type is PointGauge, RollingCounter, RollingGauge, 15 | // it returns the sum value within the window. 16 | Value() int64 17 | } 18 | 19 | // Aggregation contains some common aggregation function. 20 | // Each aggregation can compute summary statistics of window. 21 | type Aggregation interface { 22 | // Min finds the min value within the window. 23 | Min() float64 24 | // Max finds the max value within the window. 25 | Max() float64 26 | // Avg computes average value within the window. 27 | Avg() float64 28 | // Sum computes sum value within the window. 29 | Sum() float64 30 | } 31 | 32 | // VectorOpts contains the common arguments for creating vec Metric.. 33 | type VectorOpts struct { 34 | Namespace string 35 | Subsystem string 36 | Name string 37 | Help string 38 | Labels []string 39 | } 40 | 41 | const ( 42 | _businessNamespace = "business" 43 | _businessSubsystemCount = "count" 44 | _businessSubSystemGauge = "gauge" 45 | _businessSubSystemHistogram = "histogram" 46 | ) 47 | 48 | var ( 49 | _defaultBuckets = []float64{5, 10, 25, 50, 100, 250, 500} 50 | ) 51 | -------------------------------------------------------------------------------- /examples/error/consumer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-kratos/kratos/v2/transport/http" 7 | "github.com/tencentyun/tsf-go" 8 | pb "github.com/tencentyun/tsf-go/examples/helloworld/proto" 9 | "github.com/tencentyun/tsf-go/log" 10 | ) 11 | 12 | func main() { 13 | callHTTP() 14 | } 15 | 16 | func callHTTP() { 17 | logger := log.DefaultLogger 18 | log := log.NewHelper(logger) 19 | 20 | clientOpts := []http.ClientOption{http.WithEndpoint("127.0.0.1:8000")} 21 | clientOpts = append(clientOpts, tsf.ClientHTTPOptions()...) 22 | httpConn, err := http.NewClient(context.Background(), clientOpts...) 23 | if err != nil { 24 | log.Fatalf("dial http err:%v", err) 25 | } 26 | client := pb.NewGreeterHTTPClient(httpConn) 27 | 28 | reply, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "empty"}) 29 | if err != nil { 30 | log.Errorf("[http] SayHello(%s) failed!err:=%v\n", "empty", err) 31 | } else { 32 | log.Infof("[http] SayHello %s\n", reply.Message) 33 | } 34 | 35 | reply, err = client.SayHello(context.Background(), &pb.HelloRequest{Name: "kratos_http"}) 36 | if err != nil { 37 | log.Errorf("[http] SayHello(%s) failed!err:=%v\n", "kratos_http", err) 38 | } else { 39 | log.Infof("[http] SayHello %s\n", reply.Message) 40 | } 41 | 42 | reply, err = client.SayHello(context.Background(), &pb.HelloRequest{Name: "tsf"}) 43 | if err != nil { 44 | log.Errorf("[http] SayHello(%s) failed!err:=%v\n", "tsf", err) 45 | } else { 46 | log.Infof("[http] SayHello %s\n", reply.Message) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/atom/proto/atom.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package atom; 4 | 5 | import "google/api/annotations.proto"; 6 | import "google/protobuf/empty.proto"; 7 | 8 | option go_package = "github.com/tencentyun/tsf-go/examples/atom/proto"; 9 | 10 | // The greeting service definition. 11 | service Greeter { 12 | // Sends a greeting 13 | rpc Hello (google.protobuf.Empty) returns (HelloReply) { 14 | option (google.api.http) = { 15 | get: "/hello", 16 | additional_bindings { 17 | post: "/hello" 18 | }, 19 | additional_bindings { 20 | put: "/hello" 21 | }, 22 | additional_bindings { 23 | delete: "/hello" 24 | }, 25 | additional_bindings { 26 | patch: "/hello" 27 | }, 28 | }; 29 | } 30 | 31 | rpc Echo(EchoRequest) returns (EchoReply){ 32 | option (google.api.http) = { 33 | get: "/echo/{param}", 34 | }; 35 | } 36 | 37 | rpc EchoError(EchoRequest) returns (EchoReply){ 38 | option (google.api.http) = { 39 | get: "/echo/error/{param}", 40 | }; 41 | } 42 | 43 | rpc EchoSlow(EchoRequest) returns (EchoReply){ 44 | option (google.api.http) = { 45 | get: "/echo/slow/{param}", 46 | }; 47 | } 48 | } 49 | 50 | // The response message containing the greetings 51 | message HelloReply { 52 | string message = 1; 53 | } 54 | 55 | message EchoRequest{ 56 | string param = 1; 57 | } 58 | 59 | message EchoReply { 60 | string param = 1; 61 | string applicationName = 2; 62 | string sleepTime = 3; 63 | } 64 | -------------------------------------------------------------------------------- /examples/helloworld/grpc/provider/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | 8 | tsf "github.com/tencentyun/tsf-go" 9 | pb "github.com/tencentyun/tsf-go/examples/helloworld/proto" 10 | "github.com/tencentyun/tsf-go/log" 11 | 12 | "github.com/go-kratos/kratos/v2" 13 | klog "github.com/go-kratos/kratos/v2/log" 14 | "github.com/go-kratos/kratos/v2/middleware/logging" 15 | "github.com/go-kratos/kratos/v2/middleware/recovery" 16 | "github.com/go-kratos/kratos/v2/transport/grpc" 17 | ) 18 | 19 | // server is used to implement helloworld.GreeterServer. 20 | type server struct { 21 | l *klog.Helper 22 | pb.UnimplementedGreeterServer 23 | } 24 | 25 | // SayHello implements helloworld.GreeterServer 26 | func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { 27 | s.l.Infof("recv name:%s", in.Name) 28 | return &pb.HelloReply{Message: fmt.Sprintf("Welcome %+v!", in.Name)}, nil 29 | } 30 | 31 | func main() { 32 | flag.Parse() 33 | logger := log.DefaultLogger 34 | log := log.NewHelper(logger) 35 | 36 | grpcSrv := grpc.NewServer( 37 | grpc.Address(":9000"), 38 | grpc.Middleware( 39 | recovery.Recovery(), 40 | tsf.ServerMiddleware(), 41 | logging.Server(logger), 42 | ), 43 | ) 44 | s := &server{l: log} 45 | pb.RegisterGreeterServer(grpcSrv, s) 46 | 47 | opts := []kratos.Option{kratos.Name("provider-grpc"), kratos.Server(grpcSrv)} 48 | opts = append(opts, tsf.AppOptions()...) 49 | app := kratos.New(opts...) 50 | if err := app.Run(); err != nil { 51 | log.Errorf("app run failed:%v", err) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /pkg/grpc/server/recovery.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "runtime" 8 | 9 | "github.com/go-kratos/kratos/v2/errors" 10 | "github.com/tencentyun/tsf-go/log" 11 | "google.golang.org/grpc" 12 | ) 13 | 14 | func (s *Server) recovery(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { 15 | defer func() { 16 | if rerr := recover(); rerr != nil { 17 | const size = 64 << 10 18 | buf := make([]byte, size) 19 | rs := runtime.Stack(buf, false) 20 | if rs > size { 21 | rs = size 22 | } 23 | buf = buf[:rs] 24 | pl := fmt.Sprintf("grpc server panic: %v\n%v\n%s\n", req, rerr, buf) 25 | fmt.Fprintf(os.Stderr, pl) 26 | log.DefaultLog.WithContext(ctx).Error(pl) 27 | err = errors.InternalServer(errors.UnknownReason, "") 28 | } 29 | }() 30 | resp, err = handler(ctx, req) 31 | return 32 | } 33 | 34 | func (s *Server) recoveryStream(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) (err error) { 35 | defer func() { 36 | if rerr := recover(); rerr != nil { 37 | const size = 64 << 10 38 | buf := make([]byte, size) 39 | rs := runtime.Stack(buf, false) 40 | if rs > size { 41 | rs = size 42 | } 43 | buf = buf[:rs] 44 | pl := fmt.Sprintf("grpc server panic: %v\n%v\n%s\n", srv, rerr, buf) 45 | fmt.Fprintf(os.Stderr, pl) 46 | log.DefaultLog.WithContext(stream.Context()).Error(pl) 47 | err = errors.InternalServer(errors.UnknownReason, "") 48 | } 49 | }() 50 | err = handler(srv, stream) 51 | return 52 | } 53 | -------------------------------------------------------------------------------- /examples/breaker/provider/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | 8 | "github.com/go-kratos/kratos/v2" 9 | "github.com/go-kratos/kratos/v2/errors" 10 | "github.com/go-kratos/kratos/v2/middleware/logging" 11 | "github.com/go-kratos/kratos/v2/middleware/recovery" 12 | "github.com/go-kratos/kratos/v2/transport/http" 13 | tsf "github.com/tencentyun/tsf-go" 14 | pb "github.com/tencentyun/tsf-go/examples/helloworld/proto" 15 | "github.com/tencentyun/tsf-go/log" 16 | ) 17 | 18 | // server is used to implement helloworld.GreeterServer. 19 | type server struct { 20 | pb.UnimplementedGreeterServer 21 | } 22 | 23 | // SayHello implements helloworld.GreeterServer 24 | func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { 25 | if in.Name == "error" { 26 | return nil, errors.Forbidden("provider_error", "") 27 | } 28 | return &pb.HelloReply{Message: fmt.Sprintf("Welcome %+v!", in.Name)}, nil 29 | } 30 | 31 | func main() { 32 | flag.Parse() 33 | logger := log.DefaultLogger 34 | log := log.NewHelper(logger) 35 | 36 | s := &server{} 37 | httpSrv := http.NewServer( 38 | http.Address("0.0.0.0:8000"), 39 | http.Middleware( 40 | recovery.Recovery(), 41 | tsf.ServerMiddleware(), 42 | logging.Server(logger), 43 | ), 44 | ) 45 | pb.RegisterGreeterHTTPServer(httpSrv, s) 46 | 47 | opts := []kratos.Option{kratos.Name("provider-http"), kratos.Server(httpSrv)} 48 | opts = append(opts, tsf.AppOptions(tsf.EnableReigstry(false))...) 49 | app := kratos.New(opts...) 50 | 51 | if err := app.Run(); err != nil { 52 | log.Errorf("app run failed:%v", err) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /breaker.go: -------------------------------------------------------------------------------- 1 | package tsf 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-kratos/kratos/v2/errors" 7 | "github.com/go-kratos/kratos/v2/middleware" 8 | "github.com/go-kratos/kratos/v2/transport" 9 | "github.com/tencentyun/tsf-go/breaker" 10 | ) 11 | 12 | func BreakerMiddleware(opts ...ClientOption) middleware.Middleware { 13 | var o clientOpionts 14 | for _, opt := range opts { 15 | opt(&o) 16 | } 17 | if o.breakerCfg != nil && o.breakerCfg.SwitchOff { 18 | return func(handler middleware.Handler) middleware.Handler { 19 | return func(ctx context.Context, req interface{}) (interface{}, error) { 20 | return handler(ctx, req) 21 | } 22 | } 23 | } 24 | group := breaker.NewGroup(o.breakerCfg) 25 | return func(handler middleware.Handler) middleware.Handler { 26 | return func(ctx context.Context, req interface{}) (reply interface{}, err error) { 27 | if tr, ok := transport.FromClientContext(ctx); ok { 28 | if tr.Operation() != "" { 29 | breaker := group.Get(tr.Operation()) 30 | if err = breaker.Allow(); err != nil { 31 | return 32 | } 33 | defer func() { 34 | if err != nil { 35 | if o.breakerErrorHook != nil { 36 | if !o.breakerErrorHook(ctx, tr.Operation(), err) { 37 | breaker.MarkFailed() 38 | return 39 | } 40 | } else if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) || errors.FromError(err).GetCode() >= 500 { 41 | breaker.MarkFailed() 42 | return 43 | } 44 | } 45 | breaker.MarkSuccess() 46 | }() 47 | } 48 | } 49 | reply, err = handler(ctx, req) 50 | return 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /naming/consul/consul.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "sync/atomic" 7 | "time" 8 | 9 | "github.com/go-kratos/kratos/v2/registry" 10 | "github.com/tencentyun/tsf-go/naming" 11 | "github.com/tencentyun/tsf-go/pkg/http" 12 | "github.com/tencentyun/tsf-go/pkg/sys/env" 13 | "github.com/tencentyun/tsf-go/pkg/util" 14 | ) 15 | 16 | var _ registry.Discovery = &Consul{} 17 | var _ registry.Registrar = &Consul{} 18 | 19 | var defaultConsul *Consul 20 | var mu sync.Mutex 21 | 22 | type insInfo struct { 23 | ins *naming.Instance 24 | cancel context.CancelFunc 25 | } 26 | 27 | type svcInfo struct { 28 | info naming.Service 29 | nodes atomic.Value 30 | watcher map[*Watcher]struct{} 31 | cancel context.CancelFunc 32 | consul *Consul 33 | } 34 | 35 | func DefaultConsul() *Consul { 36 | mu.Lock() 37 | defer mu.Unlock() 38 | if defaultConsul == nil { 39 | defaultConsul = New(&Config{Address: env.ConsulAddressList(), Token: env.Token()}) 40 | } 41 | return defaultConsul 42 | } 43 | 44 | func New(conf *Config) *Consul { 45 | c := &Consul{ 46 | queryCli: http.NewClient(http.WithTimeout(time.Second * 120)), 47 | setCli: http.NewClient(http.WithTimeout(time.Second * 30)), 48 | registry: make(map[string]*insInfo), 49 | discovery: make(map[naming.Service]*svcInfo), 50 | bc: &util.BackoffConfig{ 51 | MaxDelay: 25 * time.Second, 52 | BaseDelay: 500 * time.Millisecond, 53 | Factor: 1.5, 54 | Jitter: 0.2, 55 | }, 56 | conf: conf, 57 | } 58 | if conf != nil && conf.Catalog { 59 | go c.Catalog() 60 | } 61 | return c 62 | } 63 | 64 | func (c *Consul) Scheme() string { 65 | return "consul" 66 | } 67 | -------------------------------------------------------------------------------- /pkg/sys/apiMeta/apiMeta_test.go: -------------------------------------------------------------------------------- 1 | package apiMeta 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestDecode(t *testing.T) { 9 | str := "H4sIAAAAAAAA/6xVwY7aMBC95ytGbo8rQPTGbU/d3qqyVQ/VqjLJBLxybO94vFtU5d8rG0KcAIWK5RSPZ57fG88zfwoA4d/keo0kFiDmk5m4izFlaisWEPcBBCvWGPc3qLV9s6SriSPLNiUDiFckr6yJKftPMJbBI4sCoE2QLNdeLOBnqtgBAwgjm4T8mRAZSaR4WwA8pSJfbrDBvk48PD5+7U6N38tu8SP7WooDQGmNDwME6ZxWpWRlzfTZW9PnOrJVKK/MlbzxfYemWWf2UqZLuX2I0UNWLLOeszWAsA4pHfClyvrw61B816cSemeNRz9AABDz2WwUAhAV+pKU4/293IMPZYne10FDhzTJ4FNR6rc8AgMQHwnriPNhWmGtjIq4PpOd2H5Dp7diUNpmqzY/TVRYy6D5MnMDweBvhyVjBUhk6f0EkCuXLDn4f7AuTvAXTpJs4lX147L7jcR0A76y1XZMVplzO4QvQRHGkWAK+N639BLQ8zWKnzLFAwPvYwPbpoIih2gP7s/o9K45OT2ZW3jrUu/s6hlLPvQoGtUhsRo5QTTovVzj2B4djGdSZt1zbYdc786Q2jXrBlr7EbiBU3psV6G+Nzf1J+Z/J301l+zyX6UOl0QM3F1baiSn+d4yXhLY2/AGeaWtzlJUhjH+yZ3hqAx/mp9W/r9DlZVWyFLpo+e6K5VEcuh8oRibcf5ZW+dTcfq9OjZj0RZ/AwAA//9aGDaN9QcAAA==" 10 | _, err := Decode(str) 11 | if err != nil { 12 | t.Fatalf("decode failed!err:=%v", err) 13 | } 14 | } 15 | 16 | func TestEncodeDecode(t *testing.T) { 17 | api := API{ 18 | Paths: map[string]map[string]Response{}, 19 | } 20 | resp := Response{Codes: make(map[string]ResponseEntity)} 21 | resp.Codes["200"] = ResponseEntity{Schema: make(map[string]string), Description: "OK"} 22 | paths := make(map[string]Response) 23 | paths["post"] = resp 24 | api.Paths["sayHello2233"] = paths 25 | 26 | enStr, err := Encode(&api) 27 | if err != nil { 28 | t.Fatalf("encode,err:=%v", err) 29 | } 30 | apiDe, err := Decode(enStr) 31 | if err != nil { 32 | t.Fatalf("decode,err:=%v", err) 33 | } 34 | if !reflect.DeepEqual(*apiDe, api) { 35 | t.Fatalf("api(%v) not eqaul apiDe(%v)", api, *apiDe) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /docs/Error.md: -------------------------------------------------------------------------------- 1 | # 业务错误处理 2 | ### 设计理念 3 | 在 API 中,业务错误主要通过 proto 进行定义,并且通过工具自动生成辅助代码。 4 | 在 errors.Error 中,主要实现了 HTTP 和 gRPC 的接口: 5 | ```go 6 | // 渲染http status code 7 | StatusCode() int 8 | // grpc status 9 | GRPCStatus() *grpc.Status 10 | ``` 11 | 可以看下一个error的基本定义 12 | ```go 13 | type Error struct { 14 | // 错误码,跟 http-status 一致,并且在 grpc 中可以转换成 grpc-status。 15 | Code int32 16 | // 错误原因,定义为业务判定错误码。 17 | Reason string 18 | // 错误信息,为用户可读的信息,可作为用户提示内容。 19 | Message string 20 | // 错误元信息,为错误添加附加可扩展信息。 21 | Metadata map[string]string 22 | } 23 | ``` 24 | ### 定义Error 25 | 1.安装 errors 辅助代码生成工具: 26 | `go get github.com/go-kratos/kratos/cmd/protoc-gen-go-errors` 27 | 2.定义错误码Reason 28 | ```protobuf 29 | syntax = "proto3"; 30 | 31 | package helloworld; 32 | 33 | // import third party proto 34 | import "errors/errors.proto"; 35 | 36 | option go_package = "github.com/tencentyun/tsf-go/examples/error/errors"; 37 | 38 | enum ErrorReason { 39 | // 设置缺省错误码 40 | option (errors.default_code) = 500; 41 | 42 | // 为某个枚举单独设置错误码 43 | USER_NOT_FOUND = 0 [(errors.code) = 404]; 44 | 45 | CONTENT_MISSING = 1 [(errors.code) = 400];; 46 | } 47 | ``` 48 | 3.通过 proto 生成对应的代码: 49 | ```bash 50 | protoc --proto_path=. \ 51 | --proto_path=./third_party \ 52 | --go_out=paths=source_relative:. \ 53 | --go-errors_out=paths=source_relative:. \ 54 | *.proto 55 | ``` 56 | 注意需要将proto依赖的[third_party](https://github.com/tencentyun/tsf-go/tree/master/third_party)下载至您的项目中,并替换成实际路径 57 | 4.使用生成的 errors 辅助代码: 58 | ```go 59 | // server return error 60 | pb.ErrorUserNotFound("user %s not found", "kratos") 61 | // client get and compare error 62 | pb.IsUserNotFound(err) 63 | ``` -------------------------------------------------------------------------------- /shared.go: -------------------------------------------------------------------------------- 1 | package tsf 2 | 3 | import ( 4 | "context" 5 | "net/url" 6 | 7 | "github.com/go-kratos/kratos/v2" 8 | "github.com/go-kratos/kratos/v2/transport" 9 | "github.com/go-kratos/kratos/v2/transport/http" 10 | "github.com/tencentyun/tsf-go/gin" 11 | "github.com/tencentyun/tsf-go/util" 12 | ) 13 | 14 | func ServerOperation(ctx context.Context) (method string, operation string) { 15 | method = "POST" 16 | if c, ok := gin.FromGinContext(ctx); ok { 17 | operation = c.Ctx.FullPath() 18 | method = c.Ctx.Request.Method 19 | } else if tr, ok := transport.FromServerContext(ctx); ok { 20 | operation = tr.Operation() 21 | if tr.Kind() == transport.KindHTTP { 22 | if ht, ok := tr.(*http.Transport); ok { 23 | operation = ht.PathTemplate() 24 | method = ht.Request().Method 25 | } 26 | } 27 | } 28 | return 29 | } 30 | 31 | func ClientOperation(ctx context.Context) (method string, operation string) { 32 | method = "POST" 33 | if tr, ok := transport.FromClientContext(ctx); ok { 34 | operation = tr.Operation() 35 | if tr.Kind() == transport.KindHTTP { 36 | if ht, ok := tr.(*http.Transport); ok { 37 | operation = ht.PathTemplate() 38 | method = ht.Request().Method 39 | } 40 | } 41 | } 42 | return 43 | } 44 | 45 | func LocalEndpoint(ctx context.Context) (local struct { 46 | Service string 47 | IP string 48 | Port uint16 49 | }) { 50 | k, _ := kratos.FromContext(ctx) 51 | if k != nil { 52 | local.Service = k.Name() 53 | } 54 | var localAddr string 55 | if tr, ok := transport.FromServerContext(ctx); ok { 56 | u, _ := url.Parse(tr.Endpoint()) 57 | localAddr = u.Host 58 | } 59 | local.IP, local.Port = util.ParseAddr(localAddr) 60 | return 61 | } 62 | -------------------------------------------------------------------------------- /examples/error/provider/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | 8 | "github.com/go-kratos/kratos/v2" 9 | "github.com/go-kratos/kratos/v2/middleware/logging" 10 | "github.com/go-kratos/kratos/v2/middleware/recovery" 11 | "github.com/go-kratos/kratos/v2/transport/http" 12 | tsf "github.com/tencentyun/tsf-go" 13 | "github.com/tencentyun/tsf-go/examples/error/errors" 14 | pb "github.com/tencentyun/tsf-go/examples/helloworld/proto" 15 | "github.com/tencentyun/tsf-go/log" 16 | ) 17 | 18 | // server is used to implement helloworld.GreeterServer. 19 | type server struct { 20 | pb.UnimplementedGreeterServer 21 | } 22 | 23 | // SayHello implements helloworld.GreeterServer 24 | func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { 25 | if in.Name == "empty" { 26 | return nil, errors.ErrorContentMissing("name is empty") 27 | } 28 | if in.Name != "tsf" { 29 | return nil, errors.ErrorUserNotFound("name is not found") 30 | } 31 | return &pb.HelloReply{Message: fmt.Sprintf("Welcome %+v!", in.Name)}, nil 32 | } 33 | 34 | func main() { 35 | flag.Parse() 36 | logger := log.DefaultLogger 37 | log := log.NewHelper(logger) 38 | 39 | s := &server{} 40 | httpSrv := http.NewServer( 41 | http.Address("0.0.0.0:8000"), 42 | http.Middleware( 43 | recovery.Recovery(), 44 | tsf.ServerMiddleware(), 45 | logging.Server(logger), 46 | ), 47 | ) 48 | pb.RegisterGreeterHTTPServer(httpSrv, s) 49 | 50 | opts := []kratos.Option{kratos.Name("provider-http"), kratos.Server(httpSrv)} 51 | opts = append(opts, tsf.AppOptions(tsf.EnableReigstry(false))...) 52 | app := kratos.New(opts...) 53 | 54 | if err := app.Run(); err != nil { 55 | log.Errorf("app run failed:%v", err) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /pkg/metric/reduce.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | // Sum the values within the window. 4 | func Sum(iterator Iterator) float64 { 5 | var result = 0.0 6 | for iterator.Next() { 7 | bucket := iterator.Bucket() 8 | for _, p := range bucket.Points { 9 | result = result + p 10 | } 11 | } 12 | return result 13 | } 14 | 15 | // Avg the values within the window. 16 | func Avg(iterator Iterator) float64 { 17 | var result = 0.0 18 | var count = 0.0 19 | for iterator.Next() { 20 | bucket := iterator.Bucket() 21 | for _, p := range bucket.Points { 22 | result = result + p 23 | count = count + 1 24 | } 25 | } 26 | return result / count 27 | } 28 | 29 | // Min the values within the window. 30 | func Min(iterator Iterator) float64 { 31 | var result = 0.0 32 | var started = false 33 | for iterator.Next() { 34 | bucket := iterator.Bucket() 35 | for _, p := range bucket.Points { 36 | if !started { 37 | result = p 38 | started = true 39 | continue 40 | } 41 | if p < result { 42 | result = p 43 | } 44 | } 45 | } 46 | return result 47 | } 48 | 49 | // Max the values within the window. 50 | func Max(iterator Iterator) float64 { 51 | var result = 0.0 52 | var started = false 53 | for iterator.Next() { 54 | bucket := iterator.Bucket() 55 | for _, p := range bucket.Points { 56 | if !started { 57 | result = p 58 | started = true 59 | continue 60 | } 61 | if p > result { 62 | result = p 63 | } 64 | } 65 | } 66 | return result 67 | } 68 | 69 | // Count sums the count value within the window. 70 | func Count(iterator Iterator) float64 { 71 | var result int64 72 | for iterator.Next() { 73 | bucket := iterator.Bucket() 74 | result += bucket.Count 75 | } 76 | return float64(result) 77 | } 78 | -------------------------------------------------------------------------------- /pkg/sys/metrics/gops.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | "net/http/pprof" 9 | 10 | "github.com/google/gops/agent" 11 | "github.com/tencentyun/tsf-go/log" 12 | "github.com/tencentyun/tsf-go/pkg/sys/env" 13 | "go.uber.org/zap" 14 | ) 15 | 16 | func StartAgent() { 17 | go startGops() 18 | go startPprof() 19 | } 20 | 21 | func startPprof() { 22 | if !env.DisableDisablePprof() { 23 | mux := http.NewServeMux() 24 | mux.HandleFunc("/debug/pprof/", pprof.Index) 25 | mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 26 | mux.HandleFunc("/debug/pprof/profile", pprof.Profile) 27 | mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 28 | mux.HandleFunc("/debug/pprof/trace", pprof.Symbol) 29 | 30 | addr := fmt.Sprintf(":%d", env.PprofPort()) 31 | 32 | lis, err := net.Listen("tcp", addr) 33 | if err != nil { 34 | log.DefaultLog.Errorf("pprof server listen %s err: %v", addr, err) 35 | return 36 | } 37 | server := http.Server{ 38 | Handler: mux, 39 | Addr: addr, 40 | } 41 | log.DefaultLog.Debugf("pprof http server start serve. To disable it,set tsf_disable_pprof=true", zap.String("addr", addr)) 42 | if err = server.Serve(lis); err != nil { 43 | log.DefaultLog.Errorf("pprof server serve err: %v", err) 44 | return 45 | } 46 | } 47 | } 48 | 49 | func startGops() { 50 | if !env.DisableDisableGops() { 51 | addr := fmt.Sprintf(":%d", env.GopsPort()) 52 | log.DefaultLog.Debug(context.Background(), "gops agent start serve. To disable it,set tsf_disable_gops=true", zap.String("addr", addr)) 53 | if err := agent.Listen(agent.Options{Addr: addr}); err != nil { 54 | log.DefaultLog.Errorf("gops agent.Listen %s err: %v", addr, err) 55 | return 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /balancer/hash/balancer.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | "sync" 7 | 8 | "github.com/tencentyun/tsf-go/balancer" 9 | "github.com/tencentyun/tsf-go/log" 10 | "github.com/tencentyun/tsf-go/naming" 11 | ) 12 | 13 | type hashKey struct{} 14 | 15 | // NewContext creates a new context with hashkey. 16 | func NewContext(ctx context.Context, key string) context.Context { 17 | return context.WithValue(ctx, hashKey{}, key) 18 | } 19 | 20 | // FromContext returns the hashkey in ctx if it exists. 21 | func FromContext(ctx context.Context) (string, bool) { 22 | key, ok := ctx.Value(hashKey{}).(string) 23 | return key, ok 24 | } 25 | 26 | var ( 27 | _ balancer.Balancer = &Picker{} 28 | 29 | Name = "consistent_hash" 30 | ) 31 | 32 | type Picker struct { 33 | c *Consistent 34 | lk sync.Mutex 35 | } 36 | 37 | func New() *Picker { 38 | return &Picker{ 39 | c: NewHash(), 40 | } 41 | } 42 | 43 | func (p *Picker) Pick(ctx context.Context, nodes []naming.Instance) (node *naming.Instance, done func(balancer.DoneInfo)) { 44 | if len(nodes) == 0 { 45 | return nil, func(balancer.DoneInfo) {} 46 | } 47 | 48 | key, ok := FromContext(ctx) 49 | if !ok { 50 | cur := rand.Intn(len(nodes)) 51 | return &nodes[cur], func(balancer.DoneInfo) {} 52 | } 53 | p.lk.Lock() 54 | defer p.lk.Unlock() 55 | var inss []Node 56 | for i, node := range nodes { 57 | inss = append(inss, Node{name: node.Addr(), idx: i}) 58 | } 59 | p.c.Set(inss) 60 | addr, err := p.c.Get(key) 61 | if err != nil { 62 | log.DefaultLog.Errorf("hash picker: get failed! %s", err) 63 | cur := rand.Intn(len(nodes)) 64 | return &nodes[cur], func(balancer.DoneInfo) {} 65 | } 66 | return &nodes[p.c.Index(addr)], func(balancer.DoneInfo) {} 67 | } 68 | 69 | func (p *Picker) Schema() string { 70 | return Name 71 | } 72 | -------------------------------------------------------------------------------- /pkg/sys/trace/reporter.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/tencentyun/tsf-go/log" 7 | "go.uber.org/zap" 8 | 9 | "github.com/openzipkin/zipkin-go/model" 10 | "github.com/openzipkin/zipkin-go/reporter" 11 | ) 12 | 13 | var ( 14 | _ reporter.Reporter = &tsfReporter{} 15 | ) 16 | 17 | type Span struct { 18 | model.SpanContext 19 | Kind model.Kind `json:"kind,omitempty"` 20 | Name string `json:"name,omitempty"` 21 | Timestamp int64 `json:"timestamp,omitempty"` 22 | Duration int64 `json:"duration,omitempty"` 23 | Shared bool `json:"shared,omitempty"` 24 | LocalEndpoint *model.Endpoint `json:"localEndpoint,omitempty"` 25 | RemoteEndpoint *model.Endpoint `json:"remoteEndpoint,omitempty"` 26 | Annotations []model.Annotation `json:"annotations,omitempty"` 27 | Tags map[string]string `json:"tags,omitempty"` 28 | } 29 | 30 | type tsfReporter struct { 31 | logger *zap.Logger 32 | } 33 | 34 | // Send Span data to the reporter 35 | func (r *tsfReporter) Send(s model.SpanModel) { 36 | span := Span{ 37 | SpanContext: s.SpanContext, 38 | Name: s.Name, 39 | Kind: s.Kind, 40 | Timestamp: s.Timestamp.UnixNano() / 1000, 41 | Duration: int64(s.Duration) / 1000, 42 | LocalEndpoint: s.LocalEndpoint, 43 | RemoteEndpoint: s.RemoteEndpoint, 44 | Annotations: s.Annotations, 45 | Tags: s.Tags, 46 | Shared: s.Shared, 47 | } 48 | content, err := json.Marshal(span) 49 | if err != nil { 50 | log.DefaultLog.Errorw("msg", "tsfReporter Marshal failed!", "span", span) 51 | return 52 | } 53 | r.logger.Info(string(content)) 54 | } 55 | 56 | // Close the reporter 57 | func (r *tsfReporter) Close() error { 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /pkg/meta/key.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import "strings" 4 | 5 | const ( 6 | PrefixDest = "destination." 7 | PrefixSource = "source." 8 | PrefixUser = "user_def." 9 | ) 10 | 11 | const ( 12 | ApplicationID = "application.id" 13 | GroupID = "group.id" 14 | ConnnectionIP = "connection.ip" 15 | ApplicationVersion = "application.version" 16 | ServiceName = "service.name" 17 | Interface = "interface" 18 | RequestHTTPMethod = "request.http.method" 19 | ServiceNamespace = "service.namespace" 20 | Namespace = "namespace" 21 | 22 | Tracer = "tsf.tracer" 23 | LaneID = "lane.id" 24 | ) 25 | 26 | var carriedKey = map[string]struct{}{ 27 | ApplicationID: struct{}{}, 28 | GroupID: struct{}{}, 29 | ConnnectionIP: struct{}{}, 30 | ApplicationVersion: struct{}{}, 31 | ServiceName: struct{}{}, 32 | Interface: struct{}{}, 33 | RequestHTTPMethod: struct{}{}, 34 | ServiceNamespace: struct{}{}, 35 | } 36 | 37 | var linkKey = map[string]struct{}{ 38 | LaneID: struct{}{}, 39 | } 40 | 41 | func IsLinkKey(key string) (ok bool) { 42 | _, ok = linkKey[key] 43 | return 44 | } 45 | 46 | func IsIncomming(key string) (ok bool) { 47 | _, ok = carriedKey[key] 48 | return 49 | } 50 | 51 | func IsOutgoing(key string) (ok bool) { 52 | _, ok = carriedKey[key] 53 | if !ok { 54 | _, ok = linkKey[key] 55 | } 56 | return 57 | } 58 | 59 | func UserKey(key string) string { 60 | return PrefixUser + key 61 | } 62 | 63 | func GetUserKey(key string) string { 64 | return strings.TrimPrefix(key, PrefixUser) 65 | } 66 | 67 | func IsUserKey(key string) bool { 68 | return strings.HasPrefix(key, PrefixUser) 69 | } 70 | 71 | func SourceKey(key string) string { 72 | return PrefixSource + key 73 | } 74 | 75 | func DestKey(key string) string { 76 | return PrefixDest + key 77 | } 78 | -------------------------------------------------------------------------------- /pkg/auth/authenticator/rule.go: -------------------------------------------------------------------------------- 1 | package authenticator 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/tencentyun/tsf-go/pkg/meta" 7 | "github.com/tencentyun/tsf-go/pkg/sys/tag" 8 | ) 9 | 10 | type AuthConfig struct { 11 | Rules []AuthRule `yaml:"rules"` 12 | Type string `yaml:"type"` 13 | } 14 | 15 | type AuthRule struct { 16 | ID string `yaml:"ruleId"` 17 | Name string `yaml:"ruleName"` 18 | Tags []Tag `yaml:"tags"` 19 | tagRule tag.Rule 20 | } 21 | 22 | type Tag struct { 23 | ID string `yaml:"tagId"` 24 | Type string `yaml:"tagType"` 25 | Field string `yaml:"tagField"` 26 | Operator string `yaml:"tagOperator"` 27 | Value string `yaml:"tagValue"` 28 | } 29 | 30 | func (rule *AuthRule) genTagRules() { 31 | var tagRule tag.Rule 32 | tagRule.Expression = tag.AND 33 | tagRule.ID = rule.ID 34 | for _, authTag := range rule.Tags { 35 | var t tag.Tag 36 | if authTag.Type == "S" && authTag.Field == "source.namespace.service.name" { 37 | values := strings.SplitN(authTag.Value, "/", 2) 38 | if len(values) != 2 { 39 | continue 40 | } 41 | t.Field = meta.Namespace 42 | t.Operator = authTag.Operator 43 | t.Type = tag.TypeSys 44 | t.Value = values[0] 45 | tagRule.Tags = append(tagRule.Tags, t) 46 | 47 | t.Field = meta.ServiceName 48 | t.Operator = authTag.Operator 49 | t.Type = tag.TypeSys 50 | t.Value = values[1] 51 | tagRule.Tags = append(tagRule.Tags, t) 52 | continue 53 | } 54 | t.Field = authTag.Field 55 | if strings.HasPrefix(t.Field, meta.PrefixDest) { 56 | t.Field = strings.TrimPrefix(t.Field, meta.PrefixDest) 57 | } 58 | t.Operator = authTag.Operator 59 | if authTag.Type == "S" { 60 | t.Type = tag.TypeSys 61 | } else { 62 | t.Type = tag.TypeUser 63 | } 64 | t.Value = authTag.Value 65 | tagRule.Tags = append(tagRule.Tags, t) 66 | } 67 | rule.tagRule = tagRule 68 | } 69 | -------------------------------------------------------------------------------- /examples/tracing/consumer/service.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-kratos/kratos/v2" 7 | "github.com/go-kratos/kratos/v2/middleware/logging" 8 | "github.com/go-kratos/kratos/v2/middleware/recovery" 9 | "github.com/go-kratos/kratos/v2/transport/http" 10 | tsf "github.com/tencentyun/tsf-go" 11 | pb "github.com/tencentyun/tsf-go/examples/helloworld/proto" 12 | "github.com/tencentyun/tsf-go/log" 13 | "github.com/tencentyun/tsf-go/naming/consul" 14 | ) 15 | 16 | // server is used to implement helloworld.GreeterServer. 17 | type server struct { 18 | pb.UnimplementedGreeterServer 19 | 20 | httpClient pb.GreeterHTTPClient 21 | } 22 | 23 | // SayHello implements helloworld.GreeterServer 24 | func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { 25 | return s.httpClient.SayHello(ctx, in) 26 | } 27 | 28 | func newService(c *consul.Consul) { 29 | logger := log.DefaultLogger 30 | log := log.NewHelper(logger) 31 | 32 | clientOpts := []http.ClientOption{http.WithEndpoint("discovery:///provider-http")} 33 | clientOpts = append(clientOpts, tsf.ClientHTTPOptions()...) 34 | httpConn, err := http.NewClient(context.Background(), clientOpts...) 35 | if err != nil { 36 | log.Errorf("dial http err:%v", err) 37 | return 38 | } 39 | s := &server{ 40 | httpClient: pb.NewGreeterHTTPClient(httpConn), 41 | } 42 | 43 | httpSrv := http.NewServer( 44 | http.Address("0.0.0.0:8080"), 45 | http.Middleware( 46 | recovery.Recovery(), 47 | tsf.ServerMiddleware(), 48 | logging.Server(logger), 49 | ), 50 | ) 51 | pb.RegisterGreeterHTTPServer(httpSrv, s) 52 | 53 | opts := []kratos.Option{kratos.Name("consumer-http"), kratos.Server(httpSrv)} 54 | opts = append(opts, tsf.AppOptions()...) 55 | app := kratos.New(opts...) 56 | 57 | if err := app.Run(); err != nil { 58 | log.Errorf("app run failed:%v", err) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/helloworld/http/consumer/service.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-kratos/kratos/v2" 7 | "github.com/go-kratos/kratos/v2/middleware/logging" 8 | "github.com/go-kratos/kratos/v2/middleware/recovery" 9 | "github.com/go-kratos/kratos/v2/transport/http" 10 | tsf "github.com/tencentyun/tsf-go" 11 | pb "github.com/tencentyun/tsf-go/examples/helloworld/proto" 12 | "github.com/tencentyun/tsf-go/log" 13 | "github.com/tencentyun/tsf-go/naming/consul" 14 | ) 15 | 16 | // server is used to implement helloworld.GreeterServer. 17 | type server struct { 18 | pb.UnimplementedGreeterServer 19 | 20 | httpClient pb.GreeterHTTPClient 21 | } 22 | 23 | // SayHello implements helloworld.GreeterServer 24 | func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { 25 | return s.httpClient.SayHello(ctx, in) 26 | } 27 | 28 | func newService(c *consul.Consul) { 29 | logger := log.DefaultLogger 30 | log := log.NewHelper(logger) 31 | 32 | clientOpts := []http.ClientOption{http.WithEndpoint("discovery:///provider-http")} 33 | clientOpts = append(clientOpts, tsf.ClientHTTPOptions()...) 34 | httpConn, err := http.NewClient(context.Background(), clientOpts...) 35 | if err != nil { 36 | log.Errorf("dial http err:%v", err) 37 | return 38 | } 39 | s := &server{ 40 | httpClient: pb.NewGreeterHTTPClient(httpConn), 41 | } 42 | 43 | httpSrv := http.NewServer( 44 | http.Address("0.0.0.0:8080"), 45 | http.Middleware( 46 | recovery.Recovery(), 47 | tsf.ServerMiddleware(), 48 | logging.Server(logger), 49 | ), 50 | ) 51 | pb.RegisterGreeterHTTPServer(httpSrv, s) 52 | 53 | opts := []kratos.Option{kratos.Name("consumer-http"), kratos.Server(httpSrv)} 54 | opts = append(opts, tsf.AppOptions()...) 55 | app := kratos.New(opts...) 56 | 57 | if err := app.Run(); err != nil { 58 | log.Errorf("app run failed:%v", err) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /route/lane/laneRule.go: -------------------------------------------------------------------------------- 1 | package lane 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/tencentyun/tsf-go/pkg/sys/tag" 7 | ) 8 | 9 | type LaneRule struct { 10 | ID string `yaml:"ruleId"` //本身LaneRule的ID 11 | Name string `yaml:"ruleName"` 12 | Enable bool `yaml:"enable"` 13 | LaneID string `yaml:"laneId"` //对于的泳道信息的ID,不是LaneRuleID 14 | Priority int64 `yaml:"priority"` 15 | TagList []TagRule `yaml:"ruleTagList"` 16 | Relationship string `yaml:"ruleTagRelationship"` 17 | CreateTime time.Time `yaml:"createTime"` 18 | } 19 | 20 | type TagRule struct { 21 | ID string `yaml:"tagId"` //本身tag的ID 22 | Name string `yaml:"tagName"` 23 | Operator string `yaml:"tagOperator"` 24 | Value string `yaml:"tagValue"` 25 | } 26 | 27 | func (rule LaneRule) toCommonTagRule() tag.Rule { 28 | var tagRule tag.Rule 29 | tagRule.ID = rule.ID 30 | if rule.Relationship == "RELEATION_OR" { 31 | tagRule.Expression = tag.OR 32 | } else { 33 | tagRule.Expression = tag.AND 34 | } 35 | for _, routeTag := range rule.TagList { 36 | var t tag.Tag 37 | t.Field = routeTag.Name 38 | t.Operator = routeTag.Operator 39 | t.Type = tag.TypeUser 40 | t.Value = routeTag.Value 41 | tagRule.Tags = append(tagRule.Tags, t) 42 | } 43 | return tagRule 44 | } 45 | 46 | type LaneInfo struct { 47 | ID string `yaml:"laneId"` 48 | Name string `yaml:"laneName"` 49 | GroupList []LaneGroup `yaml:"laneGroupList"` 50 | CreateTime time.Time `yaml:"createTime"` 51 | } 52 | 53 | type LaneGroup struct { 54 | ApplicationID string `yaml:"applicationId"` 55 | ApplicationName string `yaml:"applicationName"` 56 | ClusterType string `yaml:"clusterType"` 57 | Entrance bool `yaml:"entrance"` 58 | GroupID string `yaml:"groupId"` 59 | NamespaceID string `yaml:"namespaceId"` 60 | GroupName string `yaml:"groupName"` 61 | } 62 | -------------------------------------------------------------------------------- /pkg/route/lane/laneRule.go: -------------------------------------------------------------------------------- 1 | package lane 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/tencentyun/tsf-go/pkg/sys/tag" 7 | ) 8 | 9 | type LaneRule struct { 10 | ID string `yaml:"ruleId"` //本身LaneRule的ID 11 | Name string `yaml:"ruleName"` 12 | Enable bool `yaml:"enable"` 13 | LaneID string `yaml:"laneId"` //对于的泳道信息的ID,不是LaneRuleID 14 | Priority int64 `yaml:"priority"` 15 | TagList []TagRule `yaml:"ruleTagList"` 16 | Relationship string `yaml:"ruleTagRelationship"` 17 | CreateTime time.Time `yaml:"createTime"` 18 | } 19 | 20 | type TagRule struct { 21 | ID string `yaml:"tagId"` //本身tag的ID 22 | Name string `yaml:"tagName"` 23 | Operator string `yaml:"tagOperator"` 24 | Value string `yaml:"tagValue"` 25 | } 26 | 27 | func (rule LaneRule) toCommonTagRule() tag.Rule { 28 | var tagRule tag.Rule 29 | tagRule.ID = rule.ID 30 | if rule.Relationship == "RELEATION_OR" { 31 | tagRule.Expression = tag.OR 32 | } else { 33 | tagRule.Expression = tag.AND 34 | } 35 | for _, routeTag := range rule.TagList { 36 | var t tag.Tag 37 | t.Field = routeTag.Name 38 | t.Operator = routeTag.Operator 39 | t.Type = tag.TypeUser 40 | t.Value = routeTag.Value 41 | tagRule.Tags = append(tagRule.Tags, t) 42 | } 43 | return tagRule 44 | } 45 | 46 | type LaneInfo struct { 47 | ID string `yaml:"laneId"` 48 | Name string `yaml:"laneName"` 49 | GroupList []LaneGroup `yaml:"laneGroupList"` 50 | CreateTime time.Time `yaml:"createTime"` 51 | } 52 | 53 | type LaneGroup struct { 54 | ApplicationID string `yaml:"applicationId"` 55 | ApplicationName string `yaml:"applicationName"` 56 | ClusterType string `yaml:"clusterType"` 57 | Entrance bool `yaml:"entrance"` 58 | GroupID string `yaml:"groupId"` 59 | NamespaceID string `yaml:"namespaceId"` 60 | GroupName string `yaml:"groupName"` 61 | } 62 | -------------------------------------------------------------------------------- /pkg/metric/rolling_counter.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | var _ Metric = &rollingCounter{} 9 | var _ Aggregation = &rollingCounter{} 10 | 11 | // RollingCounter represents a ring window based on time duration. 12 | // e.g. [[1], [3], [5]] 13 | type RollingCounter interface { 14 | Metric 15 | Aggregation 16 | Timespan() int 17 | // Reduce applies the reduction function to all buckets within the window. 18 | Reduce(func(Iterator) float64) float64 19 | } 20 | 21 | // RollingCounterOpts contains the arguments for creating RollingCounter. 22 | type RollingCounterOpts struct { 23 | Size int 24 | BucketDuration time.Duration 25 | } 26 | 27 | type rollingCounter struct { 28 | policy *RollingPolicy 29 | } 30 | 31 | // NewRollingCounter creates a new RollingCounter bases on RollingCounterOpts. 32 | func NewRollingCounter(opts RollingCounterOpts) RollingCounter { 33 | window := NewWindow(WindowOpts{Size: opts.Size}) 34 | policy := NewRollingPolicy(window, RollingPolicyOpts{BucketDuration: opts.BucketDuration}) 35 | return &rollingCounter{ 36 | policy: policy, 37 | } 38 | } 39 | 40 | func (r *rollingCounter) Add(val int64) { 41 | if val < 0 { 42 | panic(fmt.Errorf("stat/metric: cannot decrease in value. val: %d", val)) 43 | } 44 | r.policy.Add(float64(val)) 45 | } 46 | 47 | func (r *rollingCounter) Reduce(f func(Iterator) float64) float64 { 48 | return r.policy.Reduce(f) 49 | } 50 | 51 | func (r *rollingCounter) Avg() float64 { 52 | return r.policy.Reduce(Avg) 53 | } 54 | 55 | func (r *rollingCounter) Min() float64 { 56 | return r.policy.Reduce(Min) 57 | } 58 | 59 | func (r *rollingCounter) Max() float64 { 60 | return r.policy.Reduce(Max) 61 | } 62 | 63 | func (r *rollingCounter) Sum() float64 { 64 | return r.policy.Reduce(Sum) 65 | } 66 | 67 | func (r *rollingCounter) Value() int64 { 68 | return int64(r.Sum()) 69 | } 70 | 71 | func (r *rollingCounter) Timespan() int { 72 | return r.policy.timespan() 73 | } 74 | -------------------------------------------------------------------------------- /pkg/grpc/apiMeta.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | "time" 8 | 9 | "github.com/fullstorydev/grpcurl" 10 | "github.com/jhump/protoreflect/grpcreflect" 11 | "github.com/tencentyun/tsf-go/log" 12 | "github.com/tencentyun/tsf-go/pkg/sys/apiMeta" 13 | "google.golang.org/grpc" 14 | rpb "google.golang.org/grpc/reflection/grpc_reflection_v1alpha" 15 | ) 16 | 17 | func GetServiceMethods(addr string) (serDesc map[string]*apiMeta.Service, err error) { 18 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 19 | defer cancel() 20 | conn, err := grpc.DialContext(ctx, addr, grpc.WithInsecure(), grpc.WithBlock()) 21 | if err != nil { 22 | panic(fmt.Errorf("dail grpc server failed,process exit now!addr:=%v err:=%v", addr, err)) 23 | } 24 | cli := rpb.NewServerReflectionClient(conn) 25 | refClient := grpcreflect.NewClient(ctx, cli) 26 | reflSource := grpcurl.DescriptorSourceFromServer(ctx, refClient) 27 | services, err := reflSource.ListServices() 28 | if err != nil { 29 | return 30 | } 31 | serDesc = make(map[string]*apiMeta.Service, 0) 32 | for _, service := range services { 33 | if service == "grpc.reflection.v1alpha.ServerReflection" { 34 | continue 35 | } 36 | desc, err := reflSource.FindSymbol(service) 37 | if err != nil { 38 | log.DefaultLog.Errorw("msg", "FindSymbol failed!", "symbol", service, "err", err) 39 | continue 40 | } 41 | for _, s := range desc.GetFile().GetServices() { 42 | packageName := desc.GetFile().GetPackage() 43 | serviceName := strings.TrimPrefix(service, packageName+".") 44 | if serviceName == s.GetName() { 45 | desc := &apiMeta.Service{PackageName: packageName, ServiceName: serviceName} 46 | for _, method := range s.GetMethods() { 47 | desc.Paths = append(desc.Paths, apiMeta.Path{FullName: fmt.Sprintf("/%s/%s", service, method.GetName()), Method: "post"}) 48 | } 49 | serDesc[service] = desc 50 | } 51 | } 52 | } 53 | return 54 | } 55 | -------------------------------------------------------------------------------- /examples/helloworld/grpc/consumer/service.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-kratos/kratos/v2" 7 | "github.com/go-kratos/kratos/v2/middleware/logging" 8 | "github.com/go-kratos/kratos/v2/middleware/recovery" 9 | "github.com/go-kratos/kratos/v2/transport/grpc" 10 | pb "github.com/tencentyun/tsf-go/examples/helloworld/proto" 11 | "github.com/tencentyun/tsf-go/log" 12 | "github.com/tencentyun/tsf-go/naming/consul" 13 | 14 | tsf "github.com/tencentyun/tsf-go" 15 | ) 16 | 17 | // server is used to implement helloworld.GreeterServer. 18 | type server struct { 19 | pb.UnimplementedGreeterServer 20 | 21 | client pb.GreeterClient 22 | } 23 | 24 | // SayHello implements helloworld.GreeterServer 25 | func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { 26 | return s.client.SayHello(ctx, in) 27 | } 28 | 29 | func newService(c *consul.Consul) { 30 | logger := log.DefaultLogger 31 | log := log.NewHelper(logger) 32 | // 指定被调方服务连接地址::/// 33 | // 如果使用服务发现,此处scheme固定为discovery,authority留空 34 | clientOpts := []grpc.ClientOption{grpc.WithEndpoint("discovery:///provider-grpc")} 35 | clientOpts = append(clientOpts, tsf.ClientGrpcOptions()...) 36 | conn, err := grpc.DialInsecure(context.Background(), clientOpts...) 37 | if err != nil { 38 | log.Errorf("dial grpc err:%v", err) 39 | return 40 | } 41 | 42 | s := &server{ 43 | client: pb.NewGreeterClient(conn), 44 | } 45 | 46 | grpcSrv := grpc.NewServer( 47 | grpc.Address("0.0.0.0:9090"), 48 | grpc.Middleware( 49 | recovery.Recovery(), 50 | tsf.ServerMiddleware(), 51 | logging.Server(logger), 52 | ), 53 | ) 54 | pb.RegisterGreeterServer(grpcSrv, s) 55 | 56 | opts := []kratos.Option{kratos.Name("consumer-grpc"), kratos.Server(grpcSrv)} 57 | opts = append(opts, tsf.AppOptions()...) 58 | app := kratos.New(opts...) 59 | 60 | if err := app.Run(); err != nil { 61 | log.Errorf("app run failed:%v", err) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /docs/Trace.md: -------------------------------------------------------------------------------- 1 | 链路追踪 2 | tsf-go已自动集成Opentelemery SDK(opentracing协议),同时会上报给TSF 调用链追踪平台,默认10%采样率 3 | 1. 自定义采样率: 4 | ```go 5 | import "github.com/tencentyun/tsf-go/tracing" 6 | // 设置采样率为100% 7 | tracing.SetProvider(tracing.WithSampleRatio(1.0)) 8 | ``` 9 | 2. 自定义trace span输出(tsf默认以zipkin协议格式输出至/data/tsf_apm/trace/log/trace_log.log) 10 | ```go 11 | import "github.com/tencentyun/tsf-go/tracing" 12 | 13 | type exporter struct { 14 | } 15 | 16 | func (e exporter) ExportSpans(ctx context.Context, ss []tracesdk.ReadOnlySpan) error { 17 | //输出至stdout 18 | fmt.Println(ss) 19 | } 20 | 21 | func (e exporter) Shutdown(ctx context.Context) error { 22 | return nil 23 | } 24 | 25 | // 设置span exporter 26 | tracing.SetProvider(tracing.WithTracerExporter(exporter{})) 27 | ``` 28 | 3. 替换Trace Propagator协议(tsf默认使用zipkin b3协议进行Header传播、解析) 29 | ```go 30 | import "go.opentelemetry.io/otel" 31 | import "go.opentelemetry.io/otel/propagation" 32 | 33 | // 可以通过SetTextMapPropagator替换 34 | // 比如这里替换成W3C Trace Context标准格式 35 | // https://www.w3.org/TR/trace-context/ 36 | otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{})) 37 | ``` 38 | 4. Redis\Mysql tracing支持 39 | ```go 40 | import "database/sql" 41 | 42 | import "github.com/go-sql-driver/mysql" 43 | import "github.com/go-redis/redis/v8" 44 | import "github.com/luna-duclos/instrumentedsql" 45 | import "github.com/tencentyun/tsf-go/tracing/mysqlotel" 46 | import "github.com/tencentyun/tsf-go/tracing/redisotel" 47 | 48 | // 添加redis tracing hook 49 | redisClient.AddHook(redisotel.New("127.0.0.1:6379")) 50 | 51 | 52 | // 注册mysql的tracing instrument 53 | sql.Register("tracing-mysql", 54 | instrumentedsql.WrapDriver(mysql.MySQLDriver{}, 55 | instrumentedsql.WithTracer(mysqlotel.NewTracer("127.0.0.1:3306")), 56 | instrumentedsql.WithOmitArgs(), 57 | ), 58 | ) 59 | db, err := sql.Open("tracing-mysql", "root:123456@tcp(127.0.0.1:3306)/pie") 60 | ``` 61 | 具体使用方式参考[tracing examples](/examples/tracing)中范例代码 62 | -------------------------------------------------------------------------------- /testdata/route2: -------------------------------------------------------------------------------- 1 | - action: null 2 | appId: '1300555551' 3 | enableStatus: false 4 | fallbackStatus: false 5 | kv: null 6 | limit: null 7 | microserviceId: ms-dapxlogv 8 | microserviceName: provider-demo 9 | namespaceId: namespace-n0 10 | namespaceName: pepy-cls-docker_default 11 | offset: null 12 | operator: null 13 | orderBy: null 14 | orderType: null 15 | projectId: null 16 | region: null 17 | requestId: null 18 | routeDesc: '' 19 | routeId: route-4y49b2ja 20 | routeName: test 21 | ruleList: 22 | - destList: 23 | - destId: route-d-gvkqgnjv 24 | destItemList: 25 | - {destItemField: TSF_APPLICATION_ID, destItemValue: app-s0, routeDestId: route-d-gvkqgnjv, 26 | routeDestItemId: route-d-i-5yrd2x9v} 27 | - {destItemField: TSF_GROUP_ID, destItemValue: group-s0, routeDestId: route-d-gvkqgnjv, 28 | routeDestItemId: route-d-i-qv3db2na} 29 | destWeight: 80 30 | routeRuleId: route-r-ba2pbnxv 31 | - destId: route-d-2233 32 | destItemList: 33 | - {destItemField: TSF_APPLICATION_ID, destItemValue: app-s0, routeDestId: route-d-2233, 34 | routeDestItemId: route-d-i-5yrd2x9q} 35 | - {destItemField: TSF_GROUP_ID, destItemValue: group-s1, routeDestId: route-d-2233, 36 | routeDestItemId: route-d-i-qv3db2nh} 37 | destWeight: 20 38 | routeRuleId: route-r-ba2pbnxv 39 | routeId: route-4y49b2ja 40 | routeRuleId: route-r-ba2pbnxv 41 | routeRuleIndex: 1 42 | tagList: 43 | - {routeRuleId: route-r-ba2pbnxv, tagField: source.service.name, tagId: route-t-ov6w4zky, 44 | tagOperator: EQUAL, tagType: S, tagValue: client-grpc} 45 | - {routeRuleId: route-r-9ynlglev, tagField: user, tagId: route-t-zvwk7kwv, tagOperator: EQUAL, 46 | tagType: U, tagValue: test2233} 47 | - {routeRuleId: route-r-9ynlglev, tagField: source.group.id, tagId: route-t-mae5q5na, 48 | tagOperator: EQUAL, tagType: S, tagValue: group-c0} 49 | searchWord: null 50 | subAccountUin: '120877931' 51 | uin: '100011913960' -------------------------------------------------------------------------------- /examples/breaker/consumer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | "time" 7 | 8 | tsf "github.com/tencentyun/tsf-go" 9 | "github.com/tencentyun/tsf-go/breaker" 10 | pb "github.com/tencentyun/tsf-go/examples/helloworld/proto" 11 | "github.com/tencentyun/tsf-go/log" 12 | 13 | "github.com/go-kratos/kratos/v2/errors" 14 | "github.com/go-kratos/kratos/v2/transport/http" 15 | ) 16 | 17 | func main() { 18 | callHTTP() 19 | } 20 | 21 | func callHTTP() { 22 | logger := log.DefaultLogger 23 | log := log.NewHelper(logger) 24 | 25 | clientOpts := []http.ClientOption{http.WithEndpoint("127.0.0.1:8000")} 26 | 27 | // tsf breaker middleware 默认error code大于等于500才认为出错并MarkFailed 28 | // 这里我们改成大于400就认为出错 29 | errHook := func(ctx context.Context, operation string, err error) (success bool) { 30 | if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) || errors.FromError(err).GetCode() > 400 { 31 | return false 32 | } 33 | return true 34 | } 35 | 36 | cfg := &breaker.Config{ 37 | // 成功率放大系数,即k * success < total时触发熔断 38 | // 默认1.5 39 | K: 1.4, 40 | // 熔断触发临界请求量 41 | // 统计窗口内请求量低于Request值则不触发熔断 42 | // 默认值 20 43 | Request: 10, 44 | } 45 | clientOpts = append(clientOpts, tsf.ClientHTTPOptions(tsf.WithMiddlewares( 46 | // 插入Breaker Middleware 47 | tsf.BreakerMiddleware( 48 | tsf.WithBreakerErrorHook(errHook), 49 | tsf.WithBreakerConfig(cfg), 50 | )), 51 | )...) 52 | httpConn, err := http.NewClient(context.Background(), clientOpts...) 53 | if err != nil { 54 | log.Fatalf("dial http err:%v", err) 55 | } 56 | client := pb.NewGreeterHTTPClient(httpConn) 57 | for { 58 | var name = "ok" 59 | if rand.Intn(100) >= 40 { 60 | name = "error" 61 | } 62 | reply, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: name}) 63 | if err != nil { 64 | log.Errorf(" SayHello failed!err:=%v\n", err) 65 | } else { 66 | log.Infof("SayHello success!:%s", reply.Message) 67 | } 68 | time.Sleep(time.Millisecond * 200) 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /pkg/util/backoff.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | // DefaultBackoffConfig uses values specified for backoff in common. 9 | var DefaultBackoffConfig = BackoffConfig{ 10 | MaxDelay: 120 * time.Second, 11 | BaseDelay: 1.0 * time.Second, 12 | Factor: 1.6, 13 | Jitter: 0.2, 14 | } 15 | 16 | // Backoff defines the methodology for backing off after a call failure. 17 | type Backoff interface { 18 | // Backoff returns the amount of time to wait before the next retry given 19 | // the number of consecutive failures. 20 | Backoff(retries int) time.Duration 21 | } 22 | 23 | // BackoffConfig defines the parameters for the default backoff strategy. 24 | type BackoffConfig struct { 25 | // MaxDelay is the upper bound of backoff delay. 26 | MaxDelay time.Duration 27 | 28 | // baseDelay is the amount of time to wait before retrying after the first 29 | // failure. 30 | BaseDelay time.Duration 31 | 32 | // factor is applied to the backoff after each retry. 33 | Factor float64 34 | 35 | // jitter provides a range to randomize backoff delays. 36 | Jitter float64 37 | } 38 | 39 | /* 40 | // NOTE TODO avoid use unexcept config. 41 | func (bc *BackoffConfig) Fix() { 42 | md := bc.MaxDelay 43 | *bc = DefaultBackoffConfig 44 | if md > 0 { 45 | bc.MaxDelay = md 46 | } 47 | } 48 | */ 49 | 50 | // Backoff returns the amount of time to wait before the next retry given 51 | // the number of consecutive failures. 52 | func (bc *BackoffConfig) Backoff(retries int) time.Duration { 53 | if retries == 0 { 54 | return bc.BaseDelay 55 | } 56 | backoff, max := float64(bc.BaseDelay), float64(bc.MaxDelay) 57 | for backoff < max && retries > 0 { 58 | backoff *= bc.Factor 59 | retries-- 60 | } 61 | if backoff > max { 62 | backoff = max 63 | } 64 | // Randomize backoff delays so that if a cluster of requests start at 65 | // the same time, they won't operate in lockstep. 66 | backoff *= 1 + bc.Jitter*(rand.Float64()*2-1) 67 | if backoff < 0 { 68 | return 0 69 | } 70 | return time.Duration(backoff) 71 | } 72 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tencentyun/tsf-go 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/elazarl/goproxy v0.0.0-20210801061803-8e322dfb79c4 7 | github.com/fullstorydev/grpcurl v1.8.2 8 | github.com/gin-gonic/gin v1.7.3 9 | github.com/go-kratos/kratos/v2 v2.0.5 10 | github.com/go-kratos/swagger-api v1.0.1 11 | github.com/go-playground/validator/v10 v10.9.0 // indirect 12 | github.com/go-redis/redis/extra/rediscmd v0.2.0 13 | github.com/go-redis/redis/v8 v8.11.5 14 | github.com/gobwas/pool v0.2.1 // indirect 15 | github.com/gobwas/ws v1.0.3 // indirect 16 | github.com/gogo/protobuf v1.3.2 17 | github.com/google/gops v0.3.19 18 | github.com/gorilla/websocket v1.4.2 // indirect 19 | github.com/jhump/protoreflect v1.9.0 20 | github.com/json-iterator/go v1.1.11 // indirect 21 | github.com/klauspost/compress v1.13.3 // indirect 22 | github.com/longXboy/go-grpc-http1 v0.0.0-20201202084506-0a6dbcb9e0f7 23 | github.com/luna-duclos/instrumentedsql v1.1.3 24 | github.com/mattn/go-isatty v0.0.13 // indirect 25 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 26 | github.com/modern-go/reflect2 v1.0.1 // indirect 27 | github.com/natefinch/lumberjack v2.0.0+incompatible 28 | github.com/openzipkin/zipkin-go v0.2.5 29 | github.com/stretchr/testify v1.7.0 30 | github.com/ugorji/go v1.2.6 // indirect 31 | go.opentelemetry.io/contrib/propagators v0.22.0 32 | go.opentelemetry.io/otel v1.0.0-RC2 33 | go.opentelemetry.io/otel/sdk v1.0.0-RC2 34 | go.opentelemetry.io/otel/trace v1.0.0-RC2 35 | go.uber.org/atomic v1.9.0 // indirect 36 | go.uber.org/multierr v1.7.0 // indirect 37 | go.uber.org/zap v1.19.0 38 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 39 | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d 40 | golang.org/x/text v0.3.7 // indirect 41 | google.golang.org/genproto v0.0.0-20210811021853-ddbe55d93216 // indirect 42 | google.golang.org/grpc v1.40.0 43 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect 44 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b 45 | nhooyr.io/websocket v1.8.7 // indirect 46 | ) 47 | -------------------------------------------------------------------------------- /pkg/meta/metadata.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import "context" 4 | 5 | type sysKey struct{} 6 | type userKey struct{} 7 | 8 | type SysMeta map[string]interface{} 9 | type UserMeta map[string]string 10 | 11 | type SysPair struct { 12 | Key string 13 | Value interface{} 14 | } 15 | 16 | type UserPair struct { 17 | Key string 18 | Value string 19 | } 20 | 21 | func WithUser(ctx context.Context, pairs ...UserPair) context.Context { 22 | origin, _ := ctx.Value(userKey{}).(UserMeta) 23 | copied := make(UserMeta, len(origin)+len(pairs)) 24 | for k, v := range origin { 25 | copied[k] = v 26 | } 27 | for _, pair := range pairs { 28 | copied[pair.Key] = pair.Value 29 | } 30 | return context.WithValue(ctx, userKey{}, copied) 31 | } 32 | 33 | func WithSys(ctx context.Context, pairs ...SysPair) context.Context { 34 | origin, _ := ctx.Value(sysKey{}).(SysMeta) 35 | copied := make(SysMeta, len(origin)+len(pairs)) 36 | for k, v := range origin { 37 | copied[k] = v 38 | } 39 | for _, pair := range pairs { 40 | copied[pair.Key] = pair.Value 41 | } 42 | return context.WithValue(ctx, sysKey{}, copied) 43 | } 44 | 45 | type Context struct { 46 | context.Context 47 | } 48 | 49 | func RangeSys(ctx context.Context, f func(key string, value interface{})) { 50 | origin, ok := ctx.Value(sysKey{}).(SysMeta) 51 | if !ok || origin == nil { 52 | return 53 | } 54 | for k, v := range origin { 55 | f(k, v) 56 | } 57 | } 58 | 59 | func RangeUser(ctx context.Context, f func(key string, value string)) { 60 | origin, ok := ctx.Value(userKey{}).(UserMeta) 61 | if !ok || origin == nil { 62 | return 63 | } 64 | for k, v := range origin { 65 | f(k, v) 66 | } 67 | } 68 | 69 | // TODO: remove to internal package 70 | func Sys(ctx context.Context, key string) (res interface{}) { 71 | origin, ok := ctx.Value(sysKey{}).(SysMeta) 72 | if !ok || origin == nil { 73 | return 74 | } 75 | res = origin[key] 76 | return 77 | } 78 | 79 | func User(ctx context.Context, key string) (res string) { 80 | origin, ok := ctx.Value(userKey{}).(UserMeta) 81 | if !ok || origin == nil { 82 | return 83 | } 84 | res = origin[key] 85 | return 86 | } 87 | -------------------------------------------------------------------------------- /http/balancer/multi/multi.go: -------------------------------------------------------------------------------- 1 | package multi 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | 8 | "github.com/go-kratos/kratos/v2/registry" 9 | "github.com/go-kratos/kratos/v2/transport/http/balancer" 10 | "github.com/openzipkin/zipkin-go" 11 | tBalancer "github.com/tencentyun/tsf-go/balancer" 12 | "github.com/tencentyun/tsf-go/log" 13 | "github.com/tencentyun/tsf-go/naming" 14 | "github.com/tencentyun/tsf-go/pkg/meta" 15 | "github.com/tencentyun/tsf-go/route" 16 | ) 17 | 18 | type Balancer struct { 19 | r route.Router //路由&泳道 20 | b tBalancer.Balancer 21 | 22 | lock sync.RWMutex 23 | nodes []naming.Instance 24 | } 25 | 26 | func New(router route.Router, b tBalancer.Balancer) *Balancer { 27 | return &Balancer{ 28 | r: router, b: b, 29 | } 30 | } 31 | 32 | func (b *Balancer) Pick(ctx context.Context) (node *registry.ServiceInstance, done func(context.Context, balancer.DoneInfo), err error) { 33 | b.lock.RLock() 34 | nodes := b.nodes 35 | b.lock.RUnlock() 36 | svc := naming.NewService(meta.Sys(ctx, meta.DestKey(meta.ServiceNamespace)).(string), meta.Sys(ctx, meta.DestKey(meta.ServiceName)).(string)) 37 | if len(nodes) == 0 { 38 | log.DefaultLog.Errorf("picker: ErrNoSubConnAvailable! %s", svc.Name) 39 | return nil, nil, fmt.Errorf("no instances available") 40 | } 41 | log.DefaultLog.Debugw("msg", "picker pick", "svc", svc, "nodes", nodes) 42 | filters := b.r.Select(ctx, *svc, nodes) 43 | if len(filters) == 0 { 44 | log.DefaultLog.Errorf("picker: ErrNoSubConnAvailable after route filter! %s", svc.Name) 45 | return nil, nil, fmt.Errorf("no instances available") 46 | } 47 | ins, _ := b.b.Pick(ctx, filters) 48 | span := zipkin.SpanFromContext(ctx) 49 | if span != nil { 50 | ep, _ := zipkin.NewEndpoint(ins.Service.Name, ins.Addr()) 51 | span.SetRemoteEndpoint(ep) 52 | } 53 | return ins.ToKratosInstance(), func(context.Context, balancer.DoneInfo) {}, nil 54 | } 55 | 56 | func (b *Balancer) Update(nodes []*registry.ServiceInstance) { 57 | b.lock.Lock() 58 | defer b.lock.Unlock() 59 | var inss []naming.Instance 60 | for _, node := range nodes { 61 | inss = append(inss, *naming.FromKratosInstance(node)[0]) 62 | } 63 | b.nodes = inss 64 | } 65 | -------------------------------------------------------------------------------- /pkg/sys/apiMeta/apiMeta.go: -------------------------------------------------------------------------------- 1 | package apiMeta 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "encoding/base64" 7 | "encoding/json" 8 | "fmt" 9 | "io/ioutil" 10 | ) 11 | 12 | type Service struct { 13 | PackageName string 14 | ServiceName string 15 | Paths []Path 16 | } 17 | 18 | type Path struct { 19 | Method string 20 | FullName string 21 | } 22 | 23 | type API struct { 24 | Paths map[string]map[string]Response `json:"paths"` 25 | } 26 | 27 | type Response struct { 28 | Codes map[string]ResponseEntity `json:"responses"` 29 | } 30 | 31 | type ResponseEntity struct { 32 | Schema map[string]string `json:"schema"` 33 | Description string `json:"description"` 34 | } 35 | 36 | func Encode(api *API) (res string, err error) { 37 | if api == nil { 38 | return 39 | } 40 | str, err := json.Marshal(api) 41 | if err != nil { 42 | return 43 | } 44 | var buf bytes.Buffer 45 | zw := gzip.NewWriter(&buf) 46 | n, err := zw.Write([]byte(str)) 47 | if err != nil { 48 | return 49 | } 50 | if n <= 0 { 51 | err = fmt.Errorf("encode api err!") 52 | return 53 | } 54 | err = zw.Close() 55 | if err != nil { 56 | return 57 | } 58 | res = base64.StdEncoding.EncodeToString(buf.Bytes()) 59 | return 60 | } 61 | 62 | func Decode(s string) (api *API, err error) { 63 | raw, err := base64.StdEncoding.DecodeString(s) 64 | if err != nil { 65 | return 66 | } 67 | r, err := gzip.NewReader(bytes.NewReader(raw)) 68 | if err != nil { 69 | return 70 | } 71 | res, err := ioutil.ReadAll(r) 72 | if err != nil { 73 | return 74 | } 75 | fmt.Println("result:", string(res)) 76 | api = new(API) 77 | err = json.Unmarshal(res, api) 78 | return 79 | } 80 | 81 | func GenApiMeta(serDesc map[string]*Service) *API { 82 | api := API{ 83 | Paths: make(map[string]map[string]Response), 84 | } 85 | for _, desc := range serDesc { 86 | for _, path := range desc.Paths { 87 | resp := Response{Codes: make(map[string]ResponseEntity)} 88 | resp.Codes["200"] = ResponseEntity{Schema: make(map[string]string), Description: "OK"} 89 | methods := make(map[string]Response) 90 | methods[path.Method] = resp 91 | api.Paths[path.FullName] = methods 92 | } 93 | } 94 | return &api 95 | } 96 | -------------------------------------------------------------------------------- /tracing/propagator.go: -------------------------------------------------------------------------------- 1 | package tracing 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "go.opentelemetry.io/otel/propagation" 8 | "go.opentelemetry.io/otel/trace" 9 | ) 10 | 11 | // Default B3 Header keys 12 | const ( 13 | TraceID = "x-b3-traceid" 14 | SpanID = "x-b3-spanid" 15 | ParentSpanID = "x-b3-parentspanid" 16 | Sampled = "x-b3-sampled" 17 | Flags = "x-b3-flags" 18 | Context = "b3" 19 | ) 20 | 21 | type propagator struct { 22 | } 23 | 24 | // Inject set cross-cutting concerns from the Context into the carrier. 25 | func (propagator) Inject(ctx context.Context, c propagation.TextMapCarrier) { 26 | sc := trace.SpanContextFromContext(ctx) 27 | if !sc.IsValid() { 28 | return 29 | } 30 | c.Set(TraceID, sc.TraceID().String()) 31 | c.Set(SpanID, sc.SpanID().String()) 32 | c.Set(ParentSpanID, sc.SpanID().String()) 33 | if sc.TraceFlags().IsSampled() { 34 | c.Set(Sampled, "1") 35 | } else { 36 | c.Set(Sampled, "0") 37 | } 38 | } 39 | 40 | // DO NOT CHANGE: any modification will not be backwards compatible and 41 | // must never be done outside of a new major release. 42 | 43 | // Extract reads cross-cutting concerns from the carrier into a Context. 44 | func (propagator) Extract(ctx context.Context, c propagation.TextMapCarrier) context.Context { 45 | var err error 46 | var scc trace.SpanContextConfig 47 | scc.TraceID, err = trace.TraceIDFromHex(c.Get(TraceID)) 48 | if err != nil { 49 | return ctx 50 | } 51 | scc.SpanID, err = trace.SpanIDFromHex(c.Get(SpanID)) 52 | if err != nil { 53 | return ctx 54 | } 55 | 56 | switch strings.ToLower(c.Get(Sampled)) { 57 | case "0", "false": 58 | case "1", "true": 59 | scc.TraceFlags = trace.FlagsSampled 60 | case "": 61 | // sc.Sampled = nil 62 | default: 63 | return ctx 64 | } 65 | scc.Remote = true 66 | sc := trace.NewSpanContext(scc) 67 | if !sc.IsValid() { 68 | return ctx 69 | } 70 | return trace.ContextWithRemoteSpanContext(ctx, sc) 71 | } 72 | 73 | // DO NOT CHANGE: any modification will not be backwards compatible and 74 | // must never be done outside of a new major release. 75 | 76 | // Fields returns the keys who's values are set with Inject. 77 | func (propagator) Fields() []string { 78 | return []string{TraceID, SpanID, ParentSpanID, Sampled} 79 | } 80 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/tencentyun/tsf-go/log" 7 | "github.com/tencentyun/tsf-go/pkg/config" 8 | ) 9 | 10 | var _ config.Config = &Config{} 11 | 12 | // Config is tsf config 13 | type Config struct { 14 | v map[string]interface{} 15 | config.Data 16 | } 17 | 18 | func newTsfConfig(d config.Data) *Config { 19 | c := &Config{v: map[string]interface{}{}, Data: d} 20 | c.refill() 21 | return c 22 | } 23 | 24 | func (c *Config) Get(key string) (v interface{}, ok bool) { 25 | return c.get(key) 26 | } 27 | 28 | func (c *Config) GetString(key string) (v string, ok bool) { 29 | res, ok := c.get(key) 30 | if ok { 31 | v, ok = res.(string) 32 | } 33 | return 34 | } 35 | 36 | func (c *Config) GetBool(key string) (v bool, ok bool) { 37 | res, ok := c.get(key) 38 | if ok { 39 | v, ok = res.(bool) 40 | } 41 | return 42 | } 43 | 44 | func (c *Config) GetInt(key string) (v int64, ok bool) { 45 | res, ok := c.get(key) 46 | if ok { 47 | v, ok = res.(int64) 48 | } 49 | return 50 | } 51 | 52 | func (c *Config) GetFloat(key string) (v float64, ok bool) { 53 | res, ok := c.get(key) 54 | if ok { 55 | v, ok = res.(float64) 56 | } 57 | return 58 | } 59 | 60 | func (c *Config) GetDuration(key string) (v time.Duration, ok bool) { 61 | res, ok := c.get(key) 62 | if ok { 63 | v, ok = res.(time.Duration) 64 | } 65 | return 66 | } 67 | 68 | func (c *Config) GetTime(key string) (v time.Time, ok bool) { 69 | res, ok := c.get(key) 70 | if ok { 71 | v, ok = res.(time.Time) 72 | } 73 | return 74 | } 75 | 76 | func (c *Config) get(key string) (res interface{}, ok bool) { 77 | if c == nil { 78 | return 79 | } 80 | res, ok = c.v[key] 81 | return 82 | } 83 | 84 | func (c *Config) Unmarshal(v interface{}) error { 85 | if c == nil || c.Data == nil { 86 | return nil 87 | } 88 | return c.Data.Unmarshal(v) 89 | } 90 | 91 | func (c *Config) Raw() []byte { 92 | if c == nil || c.Data == nil { 93 | return nil 94 | } 95 | return c.Data.Raw() 96 | } 97 | 98 | func (c *Config) refill() { 99 | err := c.Data.Unmarshal(c.v) 100 | if err != nil { 101 | log.DefaultLog.Errorw("msg", "config refill failed!", "err", err, "raw", string(c.Raw())) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /examples/atom/provider/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/go-kratos/kratos/v2" 10 | "github.com/go-kratos/kratos/v2/errors" 11 | "github.com/go-kratos/kratos/v2/middleware/logging" 12 | "github.com/go-kratos/kratos/v2/middleware/recovery" 13 | "github.com/go-kratos/kratos/v2/transport/http" 14 | tsf "github.com/tencentyun/tsf-go" 15 | pb "github.com/tencentyun/tsf-go/examples/atom/proto" 16 | "github.com/tencentyun/tsf-go/log" 17 | emptypb "google.golang.org/protobuf/types/known/emptypb" 18 | ) 19 | 20 | // server is used to implement helloworld.GreeterServer. 21 | type server struct { 22 | } 23 | 24 | // SayHello implements helloworld.GreeterServer 25 | func (s *server) Hello(ctx context.Context, in *emptypb.Empty) (*pb.HelloReply, error) { 26 | return &pb.HelloReply{Message: fmt.Sprintf("Hello!")}, nil 27 | } 28 | 29 | // SayHello implements helloworld.GreeterServer 30 | func (s *server) Echo(ctx context.Context, in *pb.EchoRequest) (*pb.EchoReply, error) { 31 | return &pb.EchoReply{Param: in.Param, ApplicationName: "provider-demo"}, nil 32 | } 33 | 34 | // SayHello implements helloworld.GreeterServer 35 | func (s *server) EchoError(ctx context.Context, in *pb.EchoRequest) (*pb.EchoReply, error) { 36 | return nil, errors.ServiceUnavailable(errors.UnknownReason, "provider-demo not available") 37 | } 38 | 39 | // SayHello implements helloworld.GreeterServer 40 | func (s *server) EchoSlow(ctx context.Context, in *pb.EchoRequest) (*pb.EchoReply, error) { 41 | time.Sleep(time.Second) 42 | return &pb.EchoReply{Param: in.Param, ApplicationName: "provider-demo", SleepTime: "1000ms"}, nil 43 | } 44 | 45 | func main() { 46 | flag.Parse() 47 | logger := log.DefaultLogger 48 | log := log.NewHelper(logger) 49 | 50 | s := &server{} 51 | httpSrv := http.NewServer( 52 | http.Address("0.0.0.0:8000"), 53 | http.Middleware( 54 | recovery.Recovery(), 55 | tsf.ServerMiddleware(), 56 | logging.Server(logger), 57 | ), 58 | ) 59 | pb.RegisterGreeterHTTPServer(httpSrv, s) 60 | 61 | opts := []kratos.Option{kratos.Name("provider-demo-go"), kratos.Server(httpSrv)} 62 | opts = append(opts, tsf.AppOptions(tsf.Medata(AtomMetadata()))...) 63 | app := kratos.New(opts...) 64 | 65 | if err := app.Run(); err != nil { 66 | log.Errorf("app run failed:%v", err) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tracing/exporter.go: -------------------------------------------------------------------------------- 1 | package tracing 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/tencentyun/tsf-go/log" 10 | 11 | "go.opentelemetry.io/otel/attribute" 12 | tracesdk "go.opentelemetry.io/otel/sdk/trace" 13 | "go.uber.org/zap" 14 | ) 15 | 16 | type Exporter struct { 17 | logger *zap.Logger 18 | } 19 | 20 | func (e *Exporter) ExportSpans(ctx context.Context, ss []tracesdk.ReadOnlySpan) error { 21 | for _, s := range ss { 22 | attrs := make(map[string]attribute.Value, 0) 23 | for _, attr := range s.Attributes() { 24 | attrs[string(attr.Key)] = attr.Value 25 | } 26 | span := Span{ 27 | TraceID: s.SpanContext().TraceID().String(), 28 | ID: s.SpanContext().SpanID().String(), 29 | Kind: strings.ToUpper(s.SpanKind().String()), 30 | Name: s.Name(), 31 | Timestamp: s.StartTime().UnixNano() / 1000, 32 | Duration: int64(s.EndTime().Sub(s.StartTime())) / 1000, 33 | LocalEndpoint: &Endpoint{ 34 | ServiceName: attrs["local.service"].AsString(), 35 | IPv4: attrs["local.ip"].AsString(), 36 | Port: uint16(attrs["local.port"].AsInt64()), 37 | }, 38 | RemoteEndpoint: &Endpoint{ 39 | ServiceName: attrs["peer.service"].AsString(), 40 | IPv4: attrs["peer.ip"].AsString(), 41 | Port: uint16(attrs["peer.port"].AsInt64()), 42 | }, 43 | Tags: map[string]string{}, 44 | } 45 | if s.Parent().HasSpanID() { 46 | span.ParentID = s.Parent().SpanID().String() 47 | } 48 | if v, ok := attrs["annotations"]; ok { 49 | span.Annotations = append(span.Annotations, Annotation{Timestamp: s.StartTime().UnixNano() / 1000, Value: v.AsString()}) 50 | } 51 | delete(attrs, "local.service") 52 | delete(attrs, "local.ip") 53 | delete(attrs, "local.port") 54 | delete(attrs, "peer.service") 55 | delete(attrs, "peer.ip") 56 | delete(attrs, "peer.port") 57 | for k, v := range attrs { 58 | if v.Type() == attribute.STRING { 59 | span.Tags[k] = v.AsString() 60 | } else if v.Type() == attribute.INT64 { 61 | span.Tags[k] = strconv.FormatInt(v.AsInt64(), 10) 62 | } 63 | } 64 | content, err := json.Marshal(span) 65 | if err != nil { 66 | log.DefaultLog.Errorf("tsfReporter Marshal failed!%v %s", span, err) 67 | continue 68 | } 69 | e.logger.Info(string(content)) 70 | } 71 | return nil 72 | } 73 | 74 | func (e *Exporter) Shutdown(ctx context.Context) error { 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /examples/helloworld/proto/helloworld_http.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-http. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go-http v2.1.0 4 | 5 | package proto 6 | 7 | import ( 8 | context "context" 9 | http "github.com/go-kratos/kratos/v2/transport/http" 10 | binding "github.com/go-kratos/kratos/v2/transport/http/binding" 11 | ) 12 | 13 | // This is a compile-time assertion to ensure that this generated file 14 | // is compatible with the kratos package it is being compiled against. 15 | var _ = new(context.Context) 16 | var _ = binding.EncodeURL 17 | 18 | const _ = http.SupportPackageIsVersion1 19 | 20 | type GreeterHTTPServer interface { 21 | SayHello(context.Context, *HelloRequest) (*HelloReply, error) 22 | } 23 | 24 | func RegisterGreeterHTTPServer(s *http.Server, srv GreeterHTTPServer) { 25 | r := s.Route("/") 26 | r.GET("/helloworld/{name}", _Greeter_SayHello0_HTTP_Handler(srv)) 27 | } 28 | 29 | func _Greeter_SayHello0_HTTP_Handler(srv GreeterHTTPServer) func(ctx http.Context) error { 30 | return func(ctx http.Context) error { 31 | var in HelloRequest 32 | if err := ctx.BindQuery(&in); err != nil { 33 | return err 34 | } 35 | if err := ctx.BindVars(&in); err != nil { 36 | return err 37 | } 38 | http.SetOperation(ctx, "/helloworld.Greeter/SayHello") 39 | h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { 40 | return srv.SayHello(ctx, req.(*HelloRequest)) 41 | }) 42 | out, err := h(ctx, &in) 43 | if err != nil { 44 | return err 45 | } 46 | reply := out.(*HelloReply) 47 | return ctx.Result(200, reply) 48 | } 49 | } 50 | 51 | type GreeterHTTPClient interface { 52 | SayHello(ctx context.Context, req *HelloRequest, opts ...http.CallOption) (rsp *HelloReply, err error) 53 | } 54 | 55 | type GreeterHTTPClientImpl struct { 56 | cc *http.Client 57 | } 58 | 59 | func NewGreeterHTTPClient(client *http.Client) GreeterHTTPClient { 60 | return &GreeterHTTPClientImpl{client} 61 | } 62 | 63 | func (c *GreeterHTTPClientImpl) SayHello(ctx context.Context, in *HelloRequest, opts ...http.CallOption) (*HelloReply, error) { 64 | var out HelloReply 65 | pattern := "/helloworld/{name}" 66 | path := binding.EncodeURL(pattern, in, true) 67 | opts = append(opts, http.Operation("/helloworld.Greeter/SayHello")) 68 | opts = append(opts, http.PathTemplate(pattern)) 69 | err := c.cc.Invoke(ctx, "GET", path, nil, &out, opts...) 70 | if err != nil { 71 | return nil, err 72 | } 73 | return &out, err 74 | } 75 | -------------------------------------------------------------------------------- /breaker/sre_breaker.go: -------------------------------------------------------------------------------- 1 | package breaker 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | "sync" 7 | "sync/atomic" 8 | "time" 9 | 10 | "github.com/go-kratos/kratos/v2/errors" 11 | "github.com/tencentyun/tsf-go/pkg/metric" 12 | ) 13 | 14 | // sreBreaker is a sre CircuitBreaker pattern. 15 | type sreBreaker struct { 16 | stat metric.RollingCounter 17 | r *rand.Rand 18 | // rand.New(...) returns a non thread safe object 19 | randLock sync.Mutex 20 | 21 | k float64 22 | request int64 23 | 24 | state int32 25 | } 26 | 27 | func newSRE(c *Config) Breaker { 28 | counterOpts := metric.RollingCounterOpts{ 29 | Size: c.Bucket, 30 | BucketDuration: time.Duration(int64(c.Window) / int64(c.Bucket)), 31 | } 32 | stat := metric.NewRollingCounter(counterOpts) 33 | return &sreBreaker{ 34 | stat: stat, 35 | r: rand.New(rand.NewSource(time.Now().UnixNano())), 36 | 37 | request: c.Request, 38 | k: c.K, 39 | state: StateClosed, 40 | } 41 | } 42 | 43 | func (b *sreBreaker) summary() (success int64, total int64) { 44 | b.stat.Reduce(func(iterator metric.Iterator) float64 { 45 | for iterator.Next() { 46 | bucket := iterator.Bucket() 47 | total += bucket.Count 48 | for _, p := range bucket.Points { 49 | success += int64(p) 50 | } 51 | } 52 | return 0 53 | }) 54 | return 55 | } 56 | 57 | func (b *sreBreaker) Allow() error { 58 | success, total := b.summary() 59 | k := b.k * float64(success) 60 | 61 | // check overflow requests = K * success 62 | if total < b.request || float64(total) < k { 63 | if atomic.LoadInt32(&b.state) == StateOpen { 64 | atomic.CompareAndSwapInt32(&b.state, StateOpen, StateClosed) 65 | } 66 | return nil 67 | } 68 | if atomic.LoadInt32(&b.state) == StateClosed { 69 | atomic.CompareAndSwapInt32(&b.state, StateClosed, StateOpen) 70 | } 71 | dr := math.Max(0, (float64(total)-k)/float64(total+1)) 72 | drop := b.trueOnProba(dr) 73 | 74 | if drop { 75 | return errors.ServiceUnavailable("circuit_breaker_open", "circuit breaker is open") 76 | } 77 | return nil 78 | } 79 | 80 | func (b *sreBreaker) MarkSuccess() { 81 | b.stat.Add(1) 82 | } 83 | 84 | func (b *sreBreaker) MarkFailed() { 85 | // NOTE: when client reject requets locally, continue add counter let the 86 | // drop ratio higher. 87 | b.stat.Add(0) 88 | } 89 | 90 | func (b *sreBreaker) trueOnProba(proba float64) (truth bool) { 91 | b.randLock.Lock() 92 | truth = b.r.Float64() < proba 93 | b.randLock.Unlock() 94 | return 95 | } 96 | -------------------------------------------------------------------------------- /apimeta.go: -------------------------------------------------------------------------------- 1 | package tsf 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "context" 7 | "encoding/base64" 8 | "time" 9 | 10 | "github.com/go-kratos/kratos/v2/api/metadata" 11 | "github.com/go-kratos/swagger-api/openapiv2" 12 | "github.com/tencentyun/tsf-go/log" 13 | ) 14 | 15 | func genAPIMeta(md map[string]string, srv *openapiv2.Service, serviceName string) { 16 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) 17 | defer cancel() 18 | var httpAPIMeta string 19 | var rpcAPIMeta string 20 | var err error 21 | if serviceName != "" { 22 | httpAPIMeta, err = srv.GetServiceOpenAPI(ctx, &metadata.GetServiceDescRequest{Name: serviceName}, false) 23 | if err != nil { 24 | log.DefaultLog.Errorf("GetServiceOpenAPI %s failed!err:=%v", serviceName, err) 25 | } 26 | rpcAPIMeta, err = srv.GetServiceOpenAPI(ctx, &metadata.GetServiceDescRequest{Name: serviceName}, true) 27 | if err != nil { 28 | log.DefaultLog.Errorf("GetServiceOpenAPI %s failed!err:=%v", serviceName, err) 29 | } 30 | } else { 31 | reply, err := srv.ListServices(ctx, &metadata.ListServicesRequest{}) 32 | if err == nil { 33 | for _, service := range reply.Services { 34 | if service != "grpc.health.v1.Health" && service != "grpc.reflection.v1alpha.ServerReflection" && service != "kratos.api.Metadata" { 35 | httpAPIMeta, err = srv.GetServiceOpenAPI(ctx, &metadata.GetServiceDescRequest{Name: service}, false) 36 | if err != nil { 37 | log.DefaultLog.Errorf("GetServiceOpenAPI %s failed!err:=%v", serviceName, err) 38 | } 39 | rpcAPIMeta, err = srv.GetServiceOpenAPI(ctx, &metadata.GetServiceDescRequest{Name: service}, true) 40 | if err != nil { 41 | log.DefaultLog.Errorf("GetServiceOpenAPI %s failed!err:=%v", serviceName, err) 42 | } 43 | break 44 | } 45 | } 46 | } else if err != nil { 47 | log.DefaultLog.Errorf("ListServicesOpenAPI %s failed!err:=%v", serviceName, err) 48 | } 49 | } 50 | if httpAPIMeta != "" { 51 | var buf bytes.Buffer 52 | zw := gzip.NewWriter(&buf) 53 | _, err := zw.Write([]byte(httpAPIMeta)) 54 | if err == nil { 55 | err = zw.Close() 56 | if err == nil { 57 | res := base64.StdEncoding.EncodeToString(buf.Bytes()) 58 | md["TSF_API_METAS_HTTP"] = res 59 | } 60 | } 61 | } 62 | if rpcAPIMeta != "" { 63 | var buf bytes.Buffer 64 | zw := gzip.NewWriter(&buf) 65 | _, err := zw.Write([]byte(rpcAPIMeta)) 66 | if err == nil { 67 | err = zw.Close() 68 | if err == nil { 69 | res := base64.StdEncoding.EncodeToString(buf.Bytes()) 70 | md["TSF_API_METAS_GRPC"] = res 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /pkg/naming/naming.go: -------------------------------------------------------------------------------- 1 | package naming 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | 7 | "github.com/tencentyun/tsf-go/pkg/sys/env" 8 | ) 9 | 10 | const ( 11 | StatusUp = 0 12 | StatusDown = 1 13 | 14 | GroupID = "TSF_GROUP_ID" 15 | NamespaceID = "TSF_NAMESPACE_ID" 16 | ApplicationID = "TSF_APPLICATION_ID" 17 | 18 | NsLocal = "local" 19 | NsGlobal = "global" 20 | ) 21 | 22 | // Service 服务信息 23 | type Service struct { 24 | Namespace string 25 | Name string 26 | } 27 | 28 | func NewService(namespace string, name string) Service { 29 | if namespace == "" || namespace == NsLocal { 30 | namespace = env.NamespaceID() 31 | } 32 | return Service{Namespace: namespace, Name: name} 33 | } 34 | 35 | // Instance 服务实例信息 36 | type Instance struct { 37 | // 服务信息 38 | Service *Service `json:"service,omitempty"` 39 | // namespace下全局唯一的实例ID 40 | ID string `json:"id"` 41 | // 服务实例所属地域 42 | Region string `json:"region"` 43 | // 服务实例可访问的ip地址 44 | Host string `json:"addrs"` 45 | // 协议端口 46 | Port int `json:"port"` 47 | // 服务实例标签元信息,比如appVersion、group、weight等 48 | Metadata map[string]string `json:"metadata"` 49 | // 实例运行状态: up/down 50 | Status int64 `json:"status"` 51 | // 过滤用的标签 52 | Tags []string 53 | } 54 | 55 | func (i Instance) Addr() string { 56 | return i.Host + ":" + strconv.FormatInt(int64(i.Port), 10) 57 | } 58 | 59 | // Discovery 服务发现 60 | type Discovery interface { 61 | // 根据namespace,service name非阻塞获取服务信息,并返回是否初始化过 62 | Fetch(svc Service) ([]Instance, bool) 63 | // 根据namespace,service name订阅服务信息,直到服务有更新或超时返回(如果超时则success=false) 64 | Subscribe(svc Service) Watcher 65 | // discovery Scheme 66 | Scheme() string 67 | } 68 | 69 | // Watcher 消息订阅 70 | type Watcher interface { 71 | //第一次访问的时候如果有值立马返回cfg,后面watch的时候有变更才返回 72 | //如果超时或者被Close应当抛出错误 73 | Watch(ctx context.Context) ([]Instance, error) 74 | //如果不需要使用则关闭订阅 75 | Close() 76 | } 77 | 78 | // Registry 注册中心 79 | type Registry interface { 80 | // 注册实例 81 | Register(ins *Instance) error 82 | // 注销实例 83 | Deregister(ins *Instance) error 84 | } 85 | 86 | // Builder resolver builder. 87 | type Builder interface { 88 | Build(id string, options ...BuildOpt) Watcher 89 | Scheme() string 90 | } 91 | 92 | // BuildOptions build options. 93 | type BuildOptions struct { 94 | } 95 | 96 | // BuildOpt build option interface. 97 | type BuildOpt interface { 98 | Apply(*BuildOptions) 99 | } 100 | 101 | type funcOpt struct { 102 | f func(*BuildOptions) 103 | } 104 | 105 | func (f *funcOpt) Apply(opt *BuildOptions) { 106 | f.f(opt) 107 | } 108 | -------------------------------------------------------------------------------- /metrics.go: -------------------------------------------------------------------------------- 1 | package tsf 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | 7 | "github.com/go-kratos/kratos/v2" 8 | "github.com/go-kratos/kratos/v2/errors" 9 | "github.com/go-kratos/kratos/v2/middleware" 10 | "github.com/go-kratos/kratos/v2/transport" 11 | "github.com/tencentyun/tsf-go/pkg/meta" 12 | "github.com/tencentyun/tsf-go/pkg/sys/monitor" 13 | "github.com/tencentyun/tsf-go/util" 14 | ) 15 | 16 | func getStat(serviceName string, operation string, method string) *monitor.Stat { 17 | return monitor.NewStat(monitor.CategoryMS, monitor.KindServer, &monitor.Endpoint{ServiceName: serviceName, InterfaceName: operation, Path: operation, Method: method}, nil) 18 | } 19 | 20 | func getClientStat(ctx context.Context, remoteServiceName string, operation string, method string) *monitor.Stat { 21 | localService, _ := meta.Sys(ctx, meta.ServiceName).(string) 22 | localOperation, _ := meta.Sys(ctx, meta.Interface).(string) 23 | localMethod, _ := meta.Sys(ctx, meta.RequestHTTPMethod).(string) 24 | 25 | return monitor.NewStat(monitor.CategoryMS, monitor.KindClient, &monitor.Endpoint{ServiceName: localService, InterfaceName: localOperation, Path: localOperation, Method: localMethod}, &monitor.Endpoint{ServiceName: remoteServiceName, InterfaceName: operation, Path: operation, Method: method}) 26 | } 27 | 28 | func serverMetricsMiddleware() middleware.Middleware { 29 | var ( 30 | once sync.Once 31 | serviceName string 32 | ) 33 | return func(handler middleware.Handler) middleware.Handler { 34 | return func(ctx context.Context, req interface{}) (reply interface{}, err error) { 35 | once.Do(func() { 36 | k, _ := kratos.FromContext(ctx) 37 | serviceName = k.Name() 38 | }) 39 | 40 | method, operation := ServerOperation(ctx) 41 | stat := getStat(serviceName, operation, method) 42 | defer func() { 43 | var code = 200 44 | if err != nil { 45 | code = int(errors.FromError(err).GetCode()) 46 | } 47 | stat.Record(code) 48 | }() 49 | 50 | reply, err = handler(ctx, req) 51 | return 52 | } 53 | } 54 | } 55 | func clientMetricsMiddleware() middleware.Middleware { 56 | var remoteServiceName string 57 | var once sync.Once 58 | return func(handler middleware.Handler) middleware.Handler { 59 | return func(ctx context.Context, req interface{}) (reply interface{}, err error) { 60 | once.Do(func() { 61 | tr, _ := transport.FromClientContext(ctx) 62 | remoteServiceName, _ = util.ParseTarget(tr.Endpoint()) 63 | }) 64 | 65 | method, operation := ClientOperation(ctx) 66 | stat := getClientStat(ctx, remoteServiceName, operation, method) 67 | defer func() { 68 | var code = 200 69 | if err != nil { 70 | code = int(errors.FromError(err).GetCode()) 71 | } 72 | stat.Record(code) 73 | }() 74 | 75 | reply, err = handler(ctx, req) 76 | return 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /gin/gin.go: -------------------------------------------------------------------------------- 1 | package gin 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/go-kratos/kratos/v2/errors" 10 | "github.com/go-kratos/kratos/v2/middleware" 11 | thttp "github.com/go-kratos/kratos/v2/transport/http" 12 | ) 13 | 14 | const ( 15 | baseContentType = "application" 16 | ) 17 | 18 | type errorRender struct { 19 | body []byte 20 | contentType string 21 | } 22 | 23 | // Render (JSON) writes data with custom ContentType. 24 | func (er *errorRender) Render(w http.ResponseWriter) error { 25 | _, err := w.Write(er.body) 26 | return err 27 | } 28 | 29 | // WriteContentType (JSON) writes JSON ContentType. 30 | func (er *errorRender) WriteContentType(w http.ResponseWriter) { 31 | w.Header().Set("Content-Type", er.contentType) 32 | 33 | } 34 | 35 | // Error encodes the object to the HTTP response. 36 | func Error(c *gin.Context, se error) { 37 | if se == nil { 38 | c.Status(http.StatusOK) 39 | return 40 | } 41 | codec, _ := thttp.CodecForRequest(c.Request, "Accept") 42 | body, err := codec.Marshal(se) 43 | if err != nil { 44 | c.Status(http.StatusInternalServerError) 45 | return 46 | } 47 | contentType := codec.Name() 48 | var code int 49 | if sc, ok := se.(interface { 50 | StatusCode() int 51 | }); ok { 52 | code = sc.StatusCode() 53 | } else { 54 | code = http.StatusInternalServerError 55 | } 56 | 57 | c.Render(code, &errorRender{body: body, contentType: contentType}) 58 | return 59 | } 60 | 61 | // ContentType returns the content-type with base prefix. 62 | func ContentType(subtype string) string { 63 | return strings.Join([]string{baseContentType, subtype}, "/") 64 | } 65 | 66 | func Middlewares(m ...middleware.Middleware) gin.HandlerFunc { 67 | chain := middleware.Chain(m...) 68 | return func(c *gin.Context) { 69 | next := func(ctx context.Context, req interface{}) (interface{}, error) { 70 | c.Next() 71 | var err error 72 | if c.Writer.Status() >= 400 { 73 | err = errors.Errorf(c.Writer.Status(), errors.UnknownReason, errors.UnknownReason) 74 | } 75 | return c.Writer, err 76 | } 77 | next = chain(next) 78 | ctx := NewGinContext(c.Request.Context(), GinCtx{c}) 79 | c.Request = c.Request.WithContext(ctx) 80 | next(c.Request.Context(), c.Request) 81 | } 82 | } 83 | 84 | type ginKey struct{} 85 | 86 | type GinCtx struct { 87 | Ctx *gin.Context 88 | } 89 | 90 | // NewServerContext returns a new Context that carries value. 91 | func NewGinContext(ctx context.Context, c GinCtx) context.Context { 92 | return context.WithValue(ctx, ginKey{}, c) 93 | } 94 | 95 | // FromServerContext returns the Transport value stored in ctx, if any. 96 | func FromGinContext(ctx context.Context) (c GinCtx, ok bool) { 97 | c, ok = ctx.Value(ginKey{}).(GinCtx) 98 | return 99 | } 100 | -------------------------------------------------------------------------------- /pkg/metric/rolling_policy.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | // RollingPolicy is a policy for ring window based on time duration. 9 | // RollingPolicy moves bucket offset with time duration. 10 | // e.g. If the last point is appended one bucket duration ago, 11 | // RollingPolicy will increment current offset. 12 | type RollingPolicy struct { 13 | mu sync.RWMutex 14 | size int 15 | window *Window 16 | offset int 17 | 18 | bucketDuration time.Duration 19 | lastAppendTime time.Time 20 | } 21 | 22 | // RollingPolicyOpts contains the arguments for creating RollingPolicy. 23 | type RollingPolicyOpts struct { 24 | BucketDuration time.Duration 25 | } 26 | 27 | // NewRollingPolicy creates a new RollingPolicy based on the given window and RollingPolicyOpts. 28 | func NewRollingPolicy(window *Window, opts RollingPolicyOpts) *RollingPolicy { 29 | return &RollingPolicy{ 30 | window: window, 31 | size: window.Size(), 32 | offset: 0, 33 | 34 | bucketDuration: opts.BucketDuration, 35 | lastAppendTime: time.Now(), 36 | } 37 | } 38 | 39 | func (r *RollingPolicy) timespan() int { 40 | v := int(time.Since(r.lastAppendTime) / r.bucketDuration) 41 | if v > -1 { // maybe time backwards 42 | return v 43 | } 44 | return r.size 45 | } 46 | 47 | func (r *RollingPolicy) add(f func(offset int, val float64), val float64) { 48 | r.mu.Lock() 49 | timespan := r.timespan() 50 | if timespan > 0 { 51 | r.lastAppendTime = r.lastAppendTime.Add(time.Duration(timespan * int(r.bucketDuration))) 52 | offset := r.offset 53 | // reset the expired buckets 54 | s := offset + 1 55 | if timespan > r.size { 56 | timespan = r.size 57 | } 58 | e, e1 := s+timespan, 0 // e: reset offset must start from offset+1 59 | if e > r.size { 60 | e1 = e - r.size 61 | e = r.size 62 | } 63 | for i := s; i < e; i++ { 64 | r.window.ResetBucket(i) 65 | offset = i 66 | } 67 | for i := 0; i < e1; i++ { 68 | r.window.ResetBucket(i) 69 | offset = i 70 | } 71 | r.offset = offset 72 | } 73 | f(r.offset, val) 74 | r.mu.Unlock() 75 | } 76 | 77 | // Append appends the given points to the window. 78 | func (r *RollingPolicy) Append(val float64) { 79 | r.add(r.window.Append, val) 80 | } 81 | 82 | // Add adds the given value to the latest point within bucket. 83 | func (r *RollingPolicy) Add(val float64) { 84 | r.add(r.window.Add, val) 85 | } 86 | 87 | // Reduce applies the reduction function to all buckets within the window. 88 | func (r *RollingPolicy) Reduce(f func(Iterator) float64) (val float64) { 89 | r.mu.RLock() 90 | timespan := r.timespan() 91 | if count := r.size - timespan; count > 0 { 92 | offset := r.offset + timespan + 1 93 | if offset >= r.size { 94 | offset = offset - r.size 95 | } 96 | val = f(r.window.Iterator(offset, count)) 97 | } 98 | r.mu.RUnlock() 99 | return val 100 | } 101 | -------------------------------------------------------------------------------- /third_party/google/api/httpbody.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/protobuf/any.proto"; 20 | 21 | option cc_enable_arenas = true; 22 | option go_package = "google.golang.org/genproto/googleapis/api/httpbody;httpbody"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "HttpBodyProto"; 25 | option java_package = "com.google.api"; 26 | option objc_class_prefix = "GAPI"; 27 | 28 | // Message that represents an arbitrary HTTP body. It should only be used for 29 | // payload formats that can't be represented as JSON, such as raw binary or 30 | // an HTML page. 31 | // 32 | // 33 | // This message can be used both in streaming and non-streaming API methods in 34 | // the request as well as the response. 35 | // 36 | // It can be used as a top-level request field, which is convenient if one 37 | // wants to extract parameters from either the URL or HTTP template into the 38 | // request fields and also want access to the raw HTTP body. 39 | // 40 | // Example: 41 | // 42 | // message GetResourceRequest { 43 | // // A unique request id. 44 | // string request_id = 1; 45 | // 46 | // // The raw HTTP body is bound to this field. 47 | // google.api.HttpBody http_body = 2; 48 | // } 49 | // 50 | // service ResourceService { 51 | // rpc GetResource(GetResourceRequest) returns (google.api.HttpBody); 52 | // rpc UpdateResource(google.api.HttpBody) returns 53 | // (google.protobuf.Empty); 54 | // } 55 | // 56 | // Example with streaming methods: 57 | // 58 | // service CaldavService { 59 | // rpc GetCalendar(stream google.api.HttpBody) 60 | // returns (stream google.api.HttpBody); 61 | // rpc UpdateCalendar(stream google.api.HttpBody) 62 | // returns (stream google.api.HttpBody); 63 | // } 64 | // 65 | // Use of this type only changes how the request and response bodies are 66 | // handled, all other features will continue to work unchanged. 67 | message HttpBody { 68 | // The HTTP Content-Type header value specifying the content type of the body. 69 | string content_type = 1; 70 | 71 | // The HTTP request/response body as raw binary. 72 | bytes data = 2; 73 | 74 | // Application specific response metadata. Must be set in the first response 75 | // for streaming APIs. 76 | repeated google.protobuf.Any extensions = 3; 77 | } 78 | -------------------------------------------------------------------------------- /pkg/metric/window.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | // Bucket contains multiple float64 points. 4 | type Bucket struct { 5 | Points []float64 6 | Count int64 7 | next *Bucket 8 | } 9 | 10 | // Append appends the given value to the bucket. 11 | func (b *Bucket) Append(val float64) { 12 | b.Points = append(b.Points, val) 13 | b.Count++ 14 | } 15 | 16 | // Add adds the given value to the point. 17 | func (b *Bucket) Add(offset int, val float64) { 18 | b.Points[offset] += val 19 | b.Count++ 20 | } 21 | 22 | // Reset empties the bucket. 23 | func (b *Bucket) Reset() { 24 | b.Points = b.Points[:0] 25 | b.Count = 0 26 | } 27 | 28 | // Next returns the next bucket. 29 | func (b *Bucket) Next() *Bucket { 30 | return b.next 31 | } 32 | 33 | // Window contains multiple buckets. 34 | type Window struct { 35 | window []Bucket 36 | size int 37 | } 38 | 39 | // WindowOpts contains the arguments for creating Window. 40 | type WindowOpts struct { 41 | Size int 42 | } 43 | 44 | // NewWindow creates a new Window based on WindowOpts. 45 | func NewWindow(opts WindowOpts) *Window { 46 | buckets := make([]Bucket, opts.Size) 47 | for offset := range buckets { 48 | buckets[offset] = Bucket{Points: make([]float64, 0)} 49 | nextOffset := offset + 1 50 | if nextOffset == opts.Size { 51 | nextOffset = 0 52 | } 53 | buckets[offset].next = &buckets[nextOffset] 54 | } 55 | return &Window{window: buckets, size: opts.Size} 56 | } 57 | 58 | // ResetWindow empties all buckets within the window. 59 | func (w *Window) ResetWindow() { 60 | for offset := range w.window { 61 | w.ResetBucket(offset) 62 | } 63 | } 64 | 65 | // ResetBucket empties the bucket based on the given offset. 66 | func (w *Window) ResetBucket(offset int) { 67 | w.window[offset].Reset() 68 | } 69 | 70 | // ResetBuckets empties the buckets based on the given offsets. 71 | func (w *Window) ResetBuckets(offsets []int) { 72 | for _, offset := range offsets { 73 | w.ResetBucket(offset) 74 | } 75 | } 76 | 77 | // Append appends the given value to the bucket where index equals the given offset. 78 | func (w *Window) Append(offset int, val float64) { 79 | w.window[offset].Append(val) 80 | } 81 | 82 | // Add adds the given value to the latest point within bucket where index equals the given offset. 83 | func (w *Window) Add(offset int, val float64) { 84 | if w.window[offset].Count == 0 { 85 | w.window[offset].Append(val) 86 | return 87 | } 88 | w.window[offset].Add(0, val) 89 | } 90 | 91 | // Bucket returns the bucket where index equals the given offset. 92 | func (w *Window) Bucket(offset int) Bucket { 93 | return w.window[offset] 94 | } 95 | 96 | // Size returns the size of the window. 97 | func (w *Window) Size() int { 98 | return w.size 99 | } 100 | 101 | // Iterator returns the bucket iterator. 102 | func (w *Window) Iterator(offset int, count int) Iterator { 103 | return Iterator{ 104 | count: count, 105 | cur: &w.window[offset], 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /pkg/grpc/server/chain.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc" 7 | ) 8 | 9 | // Use attachs a global inteceptor to the server. 10 | // For example, this is the right place for a rate limiter or error management inteceptor. 11 | // This function is not concurrency safe. 12 | func (s *Server) Use(interceptors ...grpc.UnaryServerInterceptor) *Server { 13 | s.interceptors = append(s.interceptors, interceptors...) 14 | return s 15 | } 16 | 17 | // UseStream attachs a global inteceptor to the server. 18 | // For example, this is the right place for a rate limiter or error management inteceptor. 19 | // This function is not concurrency safe. 20 | func (s *Server) UseStream(interceptors ...grpc.StreamServerInterceptor) *Server { 21 | s.streamInterceptors = append(s.streamInterceptors, interceptors...) 22 | return s 23 | } 24 | 25 | // chainUnaryInterceptors creates a single interceptor out of a chain of many interceptors. 26 | // Execution is done in left-to-right order, including passing of context. 27 | // For example ChainUnaryServer(one, two, three) will execute one before two before three, and three 28 | // will see context changes of one and two. 29 | func (s *Server) chainUnaryInterceptors() grpc.UnaryServerInterceptor { 30 | return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 31 | n := len(s.interceptors) 32 | 33 | chainer := func(currentInter grpc.UnaryServerInterceptor, currentHandler grpc.UnaryHandler) grpc.UnaryHandler { 34 | return func(currentCtx context.Context, currentReq interface{}) (interface{}, error) { 35 | return currentInter(currentCtx, currentReq, info, currentHandler) 36 | } 37 | } 38 | 39 | chainedHandler := handler 40 | for i := n - 1; i >= 0; i-- { 41 | chainedHandler = chainer(s.interceptors[i], chainedHandler) 42 | } 43 | 44 | return chainedHandler(ctx, req) 45 | } 46 | } 47 | 48 | // chainStreamServer creates a single interceptor out of a chain of many interceptors. 49 | // 50 | // Execution is done in left-to-right order, including passing of context. 51 | // For example ChainUnaryServer(one, two, three) will execute one before two before three. 52 | // If you want to pass context between interceptors, use WrapServerStream. 53 | func (s *Server) chainStreamServer() grpc.StreamServerInterceptor { 54 | 55 | return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { 56 | n := len(s.streamInterceptors) 57 | 58 | chainer := func(currentInter grpc.StreamServerInterceptor, currentHandler grpc.StreamHandler) grpc.StreamHandler { 59 | return func(currentSrv interface{}, currentStream grpc.ServerStream) error { 60 | return currentInter(currentSrv, currentStream, info, currentHandler) 61 | } 62 | } 63 | 64 | chainedHandler := handler 65 | for i := n - 1; i >= 0; i-- { 66 | chainedHandler = chainer(s.streamInterceptors[i], chainedHandler) 67 | } 68 | 69 | return chainedHandler(srv, ss) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app.go: -------------------------------------------------------------------------------- 1 | package tsf 2 | 3 | import ( 4 | "github.com/tencentyun/tsf-go/naming/consul" 5 | "github.com/tencentyun/tsf-go/pkg/sys/env" 6 | "github.com/tencentyun/tsf-go/pkg/version" 7 | 8 | "github.com/go-kratos/kratos/v2" 9 | "github.com/go-kratos/swagger-api/openapiv2" 10 | "google.golang.org/grpc" 11 | ) 12 | 13 | // Option is HTTP server option. 14 | type Option func(*appOptions) 15 | 16 | // ProtoServiceName specific the proto service name() 17 | // generated by swagger api 18 | func ProtoServiceName(fullname string) Option { 19 | return func(a *appOptions) { 20 | a.protoService = fullname 21 | } 22 | } 23 | 24 | func GRPCServer(srv *grpc.Server) Option { 25 | return func(a *appOptions) { 26 | a.srv = srv 27 | } 28 | } 29 | 30 | func EnableReigstry(enable bool) Option { 31 | return func(a *appOptions) { 32 | a.enableReigstry = enable 33 | } 34 | } 35 | 36 | func Medata(metadata map[string]string) Option { 37 | return func(a *appOptions) { 38 | a.metadata = metadata 39 | } 40 | } 41 | 42 | type appOptions struct { 43 | protoService string 44 | srv *grpc.Server 45 | apiMeta bool 46 | enableReigstry bool 47 | metadata map[string]string 48 | } 49 | 50 | func APIMeta(enable bool) Option { 51 | return func(a *appOptions) { 52 | a.apiMeta = enable 53 | } 54 | } 55 | 56 | func Metadata(optFuncs ...Option) (opt kratos.Option) { 57 | enableApiMeta := true 58 | if env.Token() == "" { 59 | enableApiMeta = false 60 | } 61 | 62 | var opts appOptions = appOptions{} 63 | for _, o := range optFuncs { 64 | o(&opts) 65 | } 66 | if opts.apiMeta { 67 | enableApiMeta = true 68 | } 69 | 70 | md := map[string]string{ 71 | "TSF_APPLICATION_ID": env.ApplicationID(), 72 | "TSF_GROUP_ID": env.GroupID(), 73 | "TSF_INSTNACE_ID": env.InstanceId(), 74 | "TSF_PROG_VERSION": env.ProgVersion(), 75 | "TSF_ZONE": env.Zone(), 76 | "TSF_REGION": env.Region(), 77 | "TSF_NAMESPACE_ID": env.NamespaceID(), 78 | "TSF_SDK_VERSION": version.GetHumanVersion(), 79 | } 80 | if len(opts.metadata) > 0 { 81 | for k, v := range opts.metadata { 82 | md[k] = v 83 | } 84 | } 85 | if enableApiMeta { 86 | apiSrv := openapiv2.New(opts.srv) 87 | genAPIMeta(md, apiSrv, opts.protoService) 88 | } 89 | 90 | for k, v := range md { 91 | if v == "" || k == "" { 92 | delete(md, k) 93 | } 94 | } 95 | 96 | opt = kratos.Metadata(md) 97 | return 98 | } 99 | 100 | func ID(optFuncs ...Option) kratos.Option { 101 | return kratos.ID(env.InstanceId()) 102 | } 103 | func Registrar(optFuncs ...Option) kratos.Option { 104 | return kratos.Registrar(consul.DefaultConsul()) 105 | } 106 | 107 | func AppOptions(opts ...Option) []kratos.Option { 108 | o := appOptions{enableReigstry: true} 109 | for _, opt := range opts { 110 | opt(&o) 111 | } 112 | 113 | kopts := []kratos.Option{ 114 | ID(opts...), Metadata(opts...), 115 | } 116 | if o.enableReigstry { 117 | kopts = append(kopts, Registrar(opts...)) 118 | } 119 | return kopts 120 | } 121 | -------------------------------------------------------------------------------- /tracing/mysqlotel/mysql.go: -------------------------------------------------------------------------------- 1 | package mysqlotel 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "strings" 7 | 8 | tsf "github.com/tencentyun/tsf-go" 9 | 10 | "github.com/go-kratos/kratos/v2/errors" 11 | "github.com/luna-duclos/instrumentedsql" 12 | "go.opentelemetry.io/otel" 13 | "go.opentelemetry.io/otel/attribute" 14 | "go.opentelemetry.io/otel/codes" 15 | "go.opentelemetry.io/otel/trace" 16 | ) 17 | 18 | type Tracer struct { 19 | tracer trace.Tracer 20 | ip string 21 | port uint16 22 | } 23 | 24 | // NewTracer returns a tracer that will fetch spans using opentracing's SpanFromContext function 25 | // if traceOrphans is set to true, then spans with no parent will be traced anyway, if false, they will not be. 26 | func NewTracer(address string) instrumentedsql.Tracer { 27 | tracer := otel.Tracer("mysql") 28 | remoteIP, remotePort := parseAddr(address) 29 | 30 | return Tracer{tracer: tracer, ip: remoteIP, port: remotePort} 31 | } 32 | 33 | // GetSpan returns a span 34 | func (t Tracer) GetSpan(ctx context.Context) instrumentedsql.Span { 35 | return Span{ctx: ctx, t: &t} 36 | } 37 | 38 | type Span struct { 39 | t *Tracer 40 | ctx context.Context 41 | span trace.Span 42 | } 43 | 44 | func (s Span) NewChild(name string) instrumentedsql.Span { 45 | if !trace.SpanFromContext(s.ctx).IsRecording() { 46 | return Span{} 47 | } 48 | localEndpoint := tsf.LocalEndpoint(s.ctx) 49 | 50 | ctx, span := s.t.tracer.Start(s.ctx, name, trace.WithSpanKind(trace.SpanKindClient)) 51 | 52 | span.SetAttributes(attribute.String("local.ip", localEndpoint.IP)) 53 | span.SetAttributes(attribute.Int64("local.port", int64(localEndpoint.Port))) 54 | span.SetAttributes(attribute.String("local.service", localEndpoint.Service)) 55 | 56 | span.SetAttributes(attribute.String("peer.ip", s.t.ip)) 57 | span.SetAttributes(attribute.Int64("peer.port", int64(s.t.port))) 58 | span.SetAttributes(attribute.String("peer.service", "mysql-server")) 59 | span.SetAttributes(attribute.String("remoteComponent", "MYSQL")) 60 | 61 | return Span{ctx: ctx, span: span} 62 | } 63 | 64 | func (s Span) SetLabel(k, v string) { 65 | if s.span == nil { 66 | return 67 | } 68 | s.span.SetAttributes(attribute.String(k, v)) 69 | } 70 | 71 | func (s Span) SetError(err error) { 72 | if s.span == nil { 73 | return 74 | } 75 | var code = 200 76 | if err != nil { 77 | code = int(errors.FromError(err).GetCode()) 78 | s.span.RecordError(err) 79 | s.span.SetStatus(codes.Error, err.Error()) 80 | s.span.SetAttributes(attribute.String("exception", err.Error())) 81 | } else { 82 | s.span.SetStatus(codes.Ok, "OK") 83 | } 84 | 85 | s.span.SetAttributes( 86 | attribute.Int("resultStatus", code), 87 | ) 88 | } 89 | 90 | func (s Span) Finish() { 91 | if s.span == nil { 92 | return 93 | } 94 | s.span.End() 95 | } 96 | 97 | func parseAddr(addr string) (ip string, port uint16) { 98 | strs := strings.Split(addr, ":") 99 | if len(strs) > 0 { 100 | ip = strs[0] 101 | } 102 | if len(strs) > 1 { 103 | uport, _ := strconv.ParseUint(strs[1], 10, 16) 104 | port = uint16(uport) 105 | } 106 | return 107 | } 108 | -------------------------------------------------------------------------------- /pkg/grpc/client/chain.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc" 7 | ) 8 | 9 | // Use attachs a global inteceptor to the Client. 10 | // For example, this is the right place for a circuit breaker or error management inteceptor. 11 | // This function is not concurrency safe. 12 | func (c *Conn) Use(interceptors ...grpc.UnaryClientInterceptor) *Conn { 13 | c.interceptors = append(c.interceptors, interceptors...) 14 | return c 15 | } 16 | 17 | // UseStream attachs a global inteceptor to the Client. 18 | // For example, this is the right place for a circuit breaker or error management inteceptor. 19 | // This function is not concurrency safe. 20 | func (c *Conn) UseStream(interceptors ...grpc.StreamClientInterceptor) *Conn { 21 | c.streamInterceptors = append(c.streamInterceptors, interceptors...) 22 | return c 23 | } 24 | 25 | // ChainUnaryClient creates a single interceptor out of a chain of many interceptors. 26 | // 27 | // Execution is done in left-to-right order, including passing of context. 28 | // For example ChainUnaryClient(one, two, three) will execute one before two before three. 29 | func (c *Conn) chainUnaryClient() grpc.UnaryClientInterceptor { 30 | return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { 31 | n := len(c.interceptors) 32 | 33 | chainer := func(currentInter grpc.UnaryClientInterceptor, currentInvoker grpc.UnaryInvoker) grpc.UnaryInvoker { 34 | return func(currentCtx context.Context, currentMethod string, currentReq, currentRepl interface{}, currentConn *grpc.ClientConn, currentOpts ...grpc.CallOption) error { 35 | return currentInter(currentCtx, currentMethod, currentReq, currentRepl, currentConn, currentInvoker, currentOpts...) 36 | } 37 | } 38 | 39 | chainedInvoker := invoker 40 | for i := n - 1; i >= 0; i-- { 41 | chainedInvoker = chainer(c.interceptors[i], chainedInvoker) 42 | } 43 | 44 | return chainedInvoker(ctx, method, req, reply, cc, opts...) 45 | } 46 | } 47 | 48 | // chainStreamClient creates a single interceptor out of a chain of many interceptors. 49 | // 50 | // Execution is done in left-to-right order, including passing of context. 51 | // For example ChainStreamClient(one, two, three) will execute one before two before three. 52 | func (c *Conn) chainStreamClient() grpc.StreamClientInterceptor { 53 | return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { 54 | n := len(c.streamInterceptors) 55 | 56 | chainer := func(currentInter grpc.StreamClientInterceptor, currentStreamer grpc.Streamer) grpc.Streamer { 57 | return func(currentCtx context.Context, currentDesc *grpc.StreamDesc, currentConn *grpc.ClientConn, currentMethod string, currentOpts ...grpc.CallOption) (grpc.ClientStream, error) { 58 | return currentInter(currentCtx, currentDesc, currentConn, currentMethod, currentStreamer, currentOpts...) 59 | } 60 | } 61 | 62 | chainedStreamer := streamer 63 | for i := n - 1; i >= 0; i-- { 64 | chainedStreamer = chainer(c.streamInterceptors[i], chainedStreamer) 65 | } 66 | 67 | return chainedStreamer(ctx, desc, cc, method, opts...) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /pkg/route/router/rule.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/tencentyun/tsf-go/pkg/meta" 7 | "github.com/tencentyun/tsf-go/pkg/sys/tag" 8 | ) 9 | 10 | type RuleGroup struct { 11 | RouteId string `yaml:"routeId"` 12 | RouteName string `yaml:"routeName"` 13 | RouteDesc string `yaml:"routeDesc"` 14 | MicroserivceId string `yaml:"microserivceId"` 15 | RuleList []Rule `yaml:"ruleList"` 16 | NamespaceId string `yaml:"namespaceId"` 17 | MicroserviceName string `yaml:"microserviceName"` 18 | FallbackStatus bool `yaml:"fallbackStatus"` 19 | } 20 | 21 | type Rule struct { 22 | RouteRuleId string `yaml:"routeRuleId"` 23 | RouteId string `yaml:"routeId"` 24 | TagList []TagRule `yaml:"tagList"` 25 | DestList []Dest `yaml:"destList"` 26 | } 27 | 28 | type Dest struct { 29 | DestId string `yaml:"destId"` 30 | DestWeight int64 `yaml:"destWeight"` 31 | DestItemList []DestItem `yaml:"destItemList"` 32 | RouteRuleId string `yaml:"routeRuleId"` 33 | } 34 | 35 | type DestItem struct { 36 | RouteDestItemId string `yaml:"routeDestItemId"` 37 | RouteDestId string `yaml:"routeDestId"` 38 | DestItemField string `yaml:"destItemField"` 39 | DestItemValue string `yaml:"destItemValue"` 40 | } 41 | 42 | type TagRule struct { 43 | TagID string `yaml:"tagID"` 44 | TagType string `yaml:"tagType"` 45 | TagField string `yaml:"tagField"` 46 | TagOperator string `yaml:"tagOperator"` 47 | TagValue string `yaml:"tagValue"` 48 | RouteRuleId string `yaml:"routeRuleId"` 49 | } 50 | 51 | func (rule Rule) toCommonTagRule() tag.Rule { 52 | tagRule := tag.Rule{ 53 | Expression: tag.AND, 54 | } 55 | for _, routeTag := range rule.TagList { 56 | var t tag.Tag 57 | field := routeTag.TagField 58 | if routeTag.TagType != "U" { 59 | switch field { 60 | case "source.application.id": 61 | field = meta.ApplicationID 62 | case "source.group.id": 63 | field = meta.GroupID 64 | case "source.connection.ip": 65 | field = meta.ConnnectionIP 66 | case "source.application.version": 67 | field = meta.ApplicationVersion 68 | case "source.service.name": 69 | field = meta.ServiceName 70 | case "destination.interface": 71 | field = "destination.interface" 72 | case "request.http.method": 73 | field = "request.http.method" 74 | case "source.namespace.service.name": 75 | values := strings.SplitN(routeTag.TagValue, "/", 2) 76 | if len(values) != 2 { 77 | continue 78 | } 79 | t.Field = meta.Namespace 80 | t.Operator = routeTag.TagOperator 81 | t.Type = tag.TypeSys 82 | t.Value = values[0] 83 | tagRule.Tags = append(tagRule.Tags, t) 84 | 85 | t.Field = meta.ServiceName 86 | t.Operator = routeTag.TagOperator 87 | t.Type = tag.TypeSys 88 | t.Value = values[1] 89 | tagRule.Tags = append(tagRule.Tags, t) 90 | continue 91 | default: 92 | } 93 | } 94 | t.Field = field 95 | t.Operator = routeTag.TagOperator 96 | if routeTag.TagType == "U" { 97 | t.Type = tag.TypeUser 98 | } else { 99 | t.Type = tag.TypeSys 100 | } 101 | t.Value = routeTag.TagValue 102 | tagRule.Tags = append(tagRule.Tags, t) 103 | } 104 | return tagRule 105 | } 106 | -------------------------------------------------------------------------------- /route/router/rule.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/tencentyun/tsf-go/pkg/meta" 7 | "github.com/tencentyun/tsf-go/pkg/sys/tag" 8 | ) 9 | 10 | type RuleGroup struct { 11 | RouteId string `yaml:"routeId"` 12 | RouteName string `yaml:"routeName"` 13 | RouteDesc string `yaml:"routeDesc"` 14 | MicroserivceId string `yaml:"microserivceId"` 15 | RuleList []Rule `yaml:"ruleList"` 16 | NamespaceId string `yaml:"namespaceId"` 17 | MicroserviceName string `yaml:"microserviceName"` 18 | FallbackStatus bool `yaml:"fallbackStatus"` 19 | } 20 | 21 | type Rule struct { 22 | RouteRuleId string `yaml:"routeRuleId"` 23 | RouteId string `yaml:"routeId"` 24 | TagList []TagRule `yaml:"tagList"` 25 | DestList []Dest `yaml:"destList"` 26 | } 27 | 28 | type Dest struct { 29 | DestId string `yaml:"destId"` 30 | DestWeight int64 `yaml:"destWeight"` 31 | DestItemList []DestItem `yaml:"destItemList"` 32 | RouteRuleId string `yaml:"routeRuleId"` 33 | } 34 | 35 | type DestItem struct { 36 | RouteDestItemId string `yaml:"routeDestItemId"` 37 | RouteDestId string `yaml:"routeDestId"` 38 | DestItemField string `yaml:"destItemField"` 39 | DestItemValue string `yaml:"destItemValue"` 40 | } 41 | 42 | type TagRule struct { 43 | TagID string `yaml:"tagID"` 44 | TagType string `yaml:"tagType"` 45 | TagField string `yaml:"tagField"` 46 | TagOperator string `yaml:"tagOperator"` 47 | TagValue string `yaml:"tagValue"` 48 | RouteRuleId string `yaml:"routeRuleId"` 49 | } 50 | 51 | func (rule Rule) toCommonTagRule() tag.Rule { 52 | tagRule := tag.Rule{ 53 | Expression: tag.AND, 54 | } 55 | for _, routeTag := range rule.TagList { 56 | var t tag.Tag 57 | field := routeTag.TagField 58 | if routeTag.TagType != "U" { 59 | switch field { 60 | case "source.application.id": 61 | field = meta.ApplicationID 62 | case "source.group.id": 63 | field = meta.GroupID 64 | case "source.connection.ip": 65 | field = meta.ConnnectionIP 66 | case "source.application.version": 67 | field = meta.ApplicationVersion 68 | case "source.service.name": 69 | field = meta.ServiceName 70 | case "destination.interface": 71 | field = "destination.interface" 72 | case "request.http.method": 73 | field = "request.http.method" 74 | case "source.namespace.service.name": 75 | values := strings.SplitN(routeTag.TagValue, "/", 2) 76 | if len(values) != 2 { 77 | continue 78 | } 79 | t.Field = meta.Namespace 80 | t.Operator = routeTag.TagOperator 81 | t.Type = tag.TypeSys 82 | t.Value = values[0] 83 | tagRule.Tags = append(tagRule.Tags, t) 84 | 85 | t.Field = meta.ServiceName 86 | t.Operator = routeTag.TagOperator 87 | t.Type = tag.TypeSys 88 | t.Value = values[1] 89 | tagRule.Tags = append(tagRule.Tags, t) 90 | continue 91 | default: 92 | } 93 | } 94 | t.Field = field 95 | t.Operator = routeTag.TagOperator 96 | if routeTag.TagType == "U" { 97 | t.Type = tag.TypeUser 98 | } else { 99 | t.Type = tag.TypeSys 100 | } 101 | t.Value = routeTag.TagValue 102 | tagRule.Tags = append(tagRule.Tags, t) 103 | } 104 | return tagRule 105 | } 106 | -------------------------------------------------------------------------------- /pkg/grpc/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "net/url" 6 | "strings" 7 | "time" 8 | 9 | "github.com/tencentyun/tsf-go/pkg/balancer/p2c" 10 | "github.com/tencentyun/tsf-go/pkg/grpc/balancer/multi" 11 | "github.com/tencentyun/tsf-go/pkg/grpc/resolver" 12 | "github.com/tencentyun/tsf-go/pkg/naming" 13 | "github.com/tencentyun/tsf-go/pkg/naming/consul" 14 | "github.com/tencentyun/tsf-go/pkg/route/composite" 15 | "github.com/tencentyun/tsf-go/pkg/route/lane" 16 | "github.com/tencentyun/tsf-go/pkg/sys/env" 17 | "github.com/tencentyun/tsf-go/pkg/util" 18 | 19 | "google.golang.org/grpc" 20 | "google.golang.org/grpc/keepalive" 21 | ) 22 | 23 | // Conn is the framework's client side instance, it contains the ctx, opt and interceptors. 24 | // Create an instance of Client, by using NewClient(). 25 | type Conn struct { 26 | *grpc.ClientConn 27 | remoteService naming.Service 28 | interceptors []grpc.UnaryClientInterceptor 29 | streamInterceptors []grpc.StreamClientInterceptor 30 | 31 | opts []grpc.DialOption 32 | lane *lane.Lane 33 | } 34 | 35 | // DialWithBlock create a grpc client conn with context 36 | // It will block until create connection successfully 37 | // 核心依赖建议使用DialWithBlock,确保能够拉到服务提供者节点再进行后续的启动操作 38 | func DialWithBlock(ctx context.Context, target string, opts ...grpc.DialOption) (c *Conn, err error) { 39 | c = &Conn{} 40 | c.setup(target, true, opts...) 41 | if c.ClientConn, err = grpc.DialContext(ctx, target, c.opts...); err != nil { 42 | return 43 | } 44 | return 45 | } 46 | 47 | // Dial create a grpc client conn 48 | // It will return immediately without any blocking 49 | func Dial(target string, opts ...grpc.DialOption) (c *Conn, err error) { 50 | c = &Conn{} 51 | c.setup(target, false, opts...) 52 | if c.ClientConn, err = grpc.Dial(target, c.opts...); err != nil { 53 | return 54 | } 55 | return 56 | } 57 | 58 | func (c *Conn) setup(target string, block bool, o ...grpc.DialOption) error { 59 | util.ParseFlag() 60 | // 将consul服务发现模块注入至grpc 61 | resolver.Register(consul.DefaultConsul()) 62 | // 将wrr负载均衡模块注入至grpc 63 | router := composite.DefaultComposite() 64 | multi.Register(router) 65 | // 加载框架自带的middleware 66 | c.Use(c.handle) 67 | c.UseStream(c.handleStream) 68 | c.lane = router.Lane() 69 | 70 | c.opts = append(c.opts, 71 | grpc.WithInsecure(), 72 | grpc.WithKeepaliveParams(keepalive.ClientParameters{ 73 | Time: time.Second * 30, 74 | Timeout: time.Second * 10, 75 | PermitWithoutStream: true, 76 | }), 77 | grpc.WithUnaryInterceptor(c.chainUnaryClient()), 78 | grpc.WithStreamInterceptor(c.chainStreamClient()), 79 | ) 80 | if block { 81 | c.opts = append(c.opts, grpc.WithBlock()) 82 | } 83 | // opts can be overwritten by user defined grpc options 84 | c.opts = append(c.opts, o...) 85 | if raw, err := url.Parse(target); err == nil { 86 | if raw.Host == "" || raw.Host == "local" { 87 | c.remoteService.Namespace = env.NamespaceID() 88 | } else { 89 | c.remoteService.Namespace = raw.Host 90 | } 91 | c.remoteService.Name = strings.TrimLeft(raw.Path, "/") 92 | c.opts = append(c.opts, grpc.WithBalancerName(p2c.Name)) 93 | } 94 | return nil 95 | } 96 | 97 | // GrpcConn exports grpc connection 98 | func (c *Conn) GrpcConn() *grpc.ClientConn { 99 | return c.ClientConn 100 | } 101 | -------------------------------------------------------------------------------- /examples/tracing/provider/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "flag" 7 | "fmt" 8 | "time" 9 | 10 | tsf "github.com/tencentyun/tsf-go" 11 | pb "github.com/tencentyun/tsf-go/examples/helloworld/proto" 12 | "github.com/tencentyun/tsf-go/log" 13 | "github.com/tencentyun/tsf-go/tracing" 14 | "github.com/tencentyun/tsf-go/tracing/mysqlotel" 15 | "github.com/tencentyun/tsf-go/tracing/redisotel" 16 | 17 | "github.com/go-kratos/kratos/v2" 18 | klog "github.com/go-kratos/kratos/v2/log" 19 | "github.com/go-kratos/kratos/v2/middleware/logging" 20 | "github.com/go-kratos/kratos/v2/middleware/recovery" 21 | "github.com/go-kratos/kratos/v2/transport/http" 22 | "github.com/go-redis/redis/v8" 23 | "github.com/go-sql-driver/mysql" 24 | "github.com/luna-duclos/instrumentedsql" 25 | ) 26 | 27 | // server is used to implement helloworld.GreeterServer. 28 | type server struct { 29 | log *klog.Helper 30 | redisCli *redis.Client 31 | db *sql.DB 32 | } 33 | 34 | // SayHello implements helloworld.GreeterServer 35 | func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { 36 | err := s.redisCli.Incr(ctx, "hello_count").Err() 37 | if err != nil { 38 | s.log.Errorf("set redis incr failed!err:=%v", err) 39 | } 40 | row := s.db.QueryRowContext(ctx, "select id from users limit 1") 41 | var id int64 42 | err = row.Scan(&id) 43 | if err != nil { 44 | s.log.Errorf("get id from mysql failed!err:=%v", err) 45 | } 46 | return &pb.HelloReply{Message: fmt.Sprintf("Welcome %+v!", in.Name)}, nil 47 | } 48 | 49 | func main() { 50 | flag.Parse() 51 | logger := log.DefaultLogger 52 | log := log.NewHelper(logger) 53 | 54 | // 主动设置trace采样率为30% 55 | // 如果上游parent span中设置了是否采样,则以上游span为准,忽略采样率设置 56 | // 在这个example中,由于consumer采样率设置了100%,所以provider实际采样率也为100% 57 | tracing.SetProvider(tracing.WithSampleRatio(0.3)) 58 | // 初始化redis client 59 | redisClient := redis.NewClient(&redis.Options{ 60 | Addr: "127.0.0.1:6379", 61 | Password: "", 62 | DB: int(0), 63 | DialTimeout: time.Second * 3, 64 | WriteTimeout: time.Second * 3, 65 | ReadTimeout: time.Second * 10, 66 | }) 67 | // 给redis添加otel tracing钩子 68 | redisClient.AddHook(redisotel.New("127.0.0.1:6379")) 69 | 70 | // 注册mysql的tracing instrument 71 | sql.Register("tracing-mysql", 72 | instrumentedsql.WrapDriver(mysql.MySQLDriver{}, 73 | instrumentedsql.WithTracer(mysqlotel.NewTracer("127.0.0.1:3306")), 74 | instrumentedsql.WithOmitArgs(), 75 | ), 76 | ) 77 | db, err := sql.Open("tracing-mysql", "root:123456@tcp(127.0.0.1:3306)/pie") 78 | if err != nil { 79 | panic(err) 80 | } 81 | err = db.Ping() 82 | if err != nil { 83 | panic(err) 84 | } 85 | s := &server{ 86 | redisCli: redisClient, 87 | log: log, 88 | db: db, 89 | } 90 | httpSrv := http.NewServer( 91 | http.Address("0.0.0.0:8000"), 92 | http.Middleware( 93 | recovery.Recovery(), 94 | // 将tracing采样率提升至100% 95 | tsf.ServerMiddleware(), 96 | logging.Server(logger), 97 | ), 98 | ) 99 | pb.RegisterGreeterHTTPServer(httpSrv, s) 100 | 101 | opts := []kratos.Option{kratos.Name("provider-http"), kratos.Server(httpSrv)} 102 | opts = append(opts, tsf.AppOptions()...) 103 | app := kratos.New(opts...) 104 | 105 | if err := app.Run(); err != nil { 106 | log.Errorf("app run failed:%v", err) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /pkg/grpc/resolver/resolver.go: -------------------------------------------------------------------------------- 1 | package resolver 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | "sync" 8 | 9 | "github.com/go-kratos/kratos/v2/errors" 10 | "github.com/tencentyun/tsf-go/log" 11 | "github.com/tencentyun/tsf-go/pkg/naming" 12 | "github.com/tencentyun/tsf-go/pkg/sys/env" 13 | "google.golang.org/grpc/attributes" 14 | "google.golang.org/grpc/resolver" 15 | ) 16 | 17 | var ( 18 | _ resolver.Builder = &Builder{} 19 | _ resolver.Resolver = &Resolver{} 20 | 21 | mu sync.Mutex 22 | ) 23 | 24 | // Register register resolver builder if nil. 25 | func Register(d naming.Discovery) { 26 | mu.Lock() 27 | defer mu.Unlock() 28 | if resolver.Get(d.Scheme()) == nil { 29 | resolver.Register(&Builder{d}) 30 | } 31 | } 32 | 33 | // Set overwrite any registered builder 34 | func Set(b naming.Discovery) { 35 | mu.Lock() 36 | defer mu.Unlock() 37 | resolver.Register(&Builder{b}) 38 | } 39 | 40 | // builder is also a resolver builder. 41 | // It's build() function always returns itself. 42 | type Builder struct { 43 | naming.Discovery 44 | } 45 | 46 | // Build returns itself for Resolver, because it's both a builder and a resolver. 47 | // consul://local/provider-demo 48 | func (b *Builder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) { 49 | str := strings.SplitN(target.Endpoint, "?", 2) 50 | if len(str) == 0 { 51 | return nil, fmt.Errorf("[resolver] parse target.Endpoint(%s) failed!err:=endpoint is empty", target.Endpoint) 52 | } 53 | nid := target.Authority 54 | if target.Authority == "local" { 55 | nid = env.NamespaceID() 56 | } 57 | svc := naming.NewService(nid, str[0]) 58 | log.DefaultLog.Debugw("msg", "[grpc resovler] start subscribe service", "svc", svc.Name) 59 | r := &Resolver{ 60 | watcher: b.Subscribe(svc), 61 | cc: cc, 62 | svc: svc, 63 | } 64 | go r.updateproc() 65 | return r, nil 66 | } 67 | 68 | // Resolver watches for the updates on the specified target. 69 | // Updates include address updates and service config updates. 70 | type Resolver struct { 71 | svc naming.Service 72 | watcher naming.Watcher 73 | cc resolver.ClientConn 74 | } 75 | 76 | // Close is a noop for Resolver. 77 | func (r *Resolver) Close() { 78 | log.DefaultLog.Infow("msg", "[grpc resovler] close subscribe service", "serviceName", r.svc.Name, "namespace", r.svc.Namespace) 79 | r.watcher.Close() 80 | } 81 | 82 | // ResolveNow is a noop for Resolver. 83 | func (r *Resolver) ResolveNow(o resolver.ResolveNowOptions) { 84 | } 85 | 86 | func (r *Resolver) updateproc() { 87 | ctx := context.Background() 88 | for { 89 | nodes, err := r.watcher.Watch(ctx) 90 | if errors.IsClientClosed(err) { 91 | return 92 | } 93 | if len(nodes) > 0 { 94 | r.newAddress(nodes) 95 | } 96 | } 97 | } 98 | func (r *Resolver) newAddress(instances []naming.Instance) { 99 | if len(instances) <= 0 { 100 | return 101 | } 102 | addrs := make([]resolver.Address, 0, len(instances)) 103 | for _, ins := range instances { 104 | addr := resolver.Address{ 105 | Addr: ins.Addr(), 106 | ServerName: ins.Service.Name, 107 | } 108 | addr.Attributes = attributes.New("rawInstance", ins) 109 | addrs = append(addrs, addr) 110 | } 111 | log.DefaultLog.Info("msg", "[resolver] newAddress found!", "length", len(addrs), "serviceName", r.svc.Name, "namespace", r.svc.Namespace) 112 | r.cc.NewAddress(addrs) 113 | } 114 | -------------------------------------------------------------------------------- /pkg/auth/authenticator/authenticator.go: -------------------------------------------------------------------------------- 1 | package authenticator 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | 8 | "github.com/go-kratos/kratos/v2/errors" 9 | "github.com/tencentyun/tsf-go/log" 10 | "github.com/tencentyun/tsf-go/pkg/auth" 11 | "github.com/tencentyun/tsf-go/pkg/config" 12 | "github.com/tencentyun/tsf-go/pkg/naming" 13 | ) 14 | 15 | var ( 16 | _ auth.Builder = &Builder{} 17 | _ auth.Auth = &Authenticator{} 18 | ) 19 | 20 | type Builder struct { 21 | } 22 | 23 | func (b *Builder) Build(cfg config.Source, svc naming.Service) auth.Auth { 24 | watcher := cfg.Subscribe(fmt.Sprintf("authority/%s/%s/data", svc.Namespace, svc.Name)) 25 | a := &Authenticator{watcher: watcher, svc: svc} 26 | a.ctx, a.cancel = context.WithCancel(context.Background()) 27 | go a.refreshRule() 28 | return a 29 | } 30 | 31 | type Authenticator struct { 32 | watcher config.Watcher 33 | svc naming.Service 34 | authConfig *AuthConfig 35 | 36 | mu sync.RWMutex 37 | 38 | ctx context.Context 39 | cancel context.CancelFunc 40 | } 41 | 42 | func (a *Authenticator) Verify(ctx context.Context, method string) error { 43 | a.mu.RLock() 44 | authConfig := a.authConfig 45 | a.mu.RUnlock() 46 | if authConfig == nil || len(authConfig.Rules) == 0 { 47 | return nil 48 | } 49 | 50 | for _, rule := range a.authConfig.Rules { 51 | rule.genTagRules() 52 | if rule.tagRule.Hit(ctx) { 53 | if authConfig.Type == "W" { 54 | return nil 55 | } 56 | log.DefaultLog.Debugw("msg", "Authenticator.Verify hit blacklist,access blocked!", "rule", rule.tagRule) 57 | return errors.Forbidden(errors.UnknownReason, "") 58 | } 59 | } 60 | if authConfig.Type == "W" { 61 | log.DefaultLog.Debug("Authenticator.Verify not hit whitelist,access blocked!") 62 | return errors.Forbidden(errors.UnknownReason, "") 63 | } 64 | return nil 65 | } 66 | 67 | func (a *Authenticator) refreshRule() { 68 | for { 69 | specs, err := a.watcher.Watch(a.ctx) 70 | if err != nil { 71 | if errors.IsGatewayTimeout(err) || errors.IsClientClosed(err) { 72 | log.DefaultLog.Errorw("msg", "watch auth config deadline or clsoe!exit now!", "err", err) 73 | return 74 | } 75 | log.DefaultLog.Errorw("msg", "watch auth config failed!", "err", err) 76 | continue 77 | } 78 | var authConfigs []AuthConfig 79 | for _, spec := range specs { 80 | if spec.Key != fmt.Sprintf("authority/%s/%s/data", a.svc.Namespace, a.svc.Name) { 81 | err = fmt.Errorf("found invalid auth config key!") 82 | log.DefaultLog.Errorw("msg", "found invalid auth config key!", "key", spec.Key, "expect", fmt.Sprintf("authority/%s/%s/data", a.svc.Namespace, a.svc.Name)) 83 | continue 84 | } 85 | err = spec.Data.Unmarshal(&authConfigs) 86 | if err != nil { 87 | log.DefaultLog.Errorw("msg", "unmarshal auth config failed!", "err", err, "raw", string(spec.Data.Raw())) 88 | continue 89 | } 90 | } 91 | if len(authConfigs) == 0 && err != nil { 92 | log.DefaultLog.Error("get auth config failed,not override old data!") 93 | continue 94 | } 95 | var authConfig *AuthConfig 96 | if len(authConfigs) > 0 { 97 | authConfig = &authConfigs[0] 98 | for _, rule := range authConfig.Rules { 99 | rule.genTagRules() 100 | } 101 | } 102 | log.DefaultLog.Infof("[auth] found new auth rules,replace now!config: %v", authConfig) 103 | a.mu.Lock() 104 | a.authConfig = authConfig 105 | a.mu.Unlock() 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /pkg/grpc/balancer/multi/multi.go: -------------------------------------------------------------------------------- 1 | package multi 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | 7 | "github.com/openzipkin/zipkin-go" 8 | "github.com/tencentyun/tsf-go/log" 9 | tBalancer "github.com/tencentyun/tsf-go/pkg/balancer" 10 | "github.com/tencentyun/tsf-go/pkg/balancer/p2c" 11 | "github.com/tencentyun/tsf-go/pkg/balancer/p2ce" 12 | "github.com/tencentyun/tsf-go/pkg/balancer/random" 13 | "github.com/tencentyun/tsf-go/pkg/meta" 14 | "github.com/tencentyun/tsf-go/pkg/naming" 15 | "github.com/tencentyun/tsf-go/pkg/route" 16 | 17 | "google.golang.org/grpc/balancer" 18 | "google.golang.org/grpc/balancer/base" 19 | ) 20 | 21 | var ( 22 | _ base.PickerBuilder = &Builder{} 23 | _ balancer.Picker = &Picker{} 24 | 25 | mu sync.Mutex 26 | 27 | balancers []tBalancer.Balancer 28 | ) 29 | 30 | func init() { 31 | // p2c 32 | b := p2c.Builder{} 33 | balancers = append(balancers, b.Build(context.Background(), nil, nil)) 34 | 35 | // random 36 | balancers = append(balancers, &random.Picker{}) 37 | 38 | b2 := p2ce.Builder{} 39 | balancers = append(balancers, b2.Build(context.Background(), nil, nil)) 40 | } 41 | 42 | // Register register balancer builder if nil. 43 | func Register(router route.Router) { 44 | mu.Lock() 45 | defer mu.Unlock() 46 | for _, b := range balancers { 47 | if balancer.Get(b.Schema()) == nil { 48 | balancer.Register(newBuilder(router, b)) 49 | } 50 | } 51 | 52 | } 53 | 54 | // Set overwrite any balancer builder. 55 | func Set(router route.Router) { 56 | mu.Lock() 57 | defer mu.Unlock() 58 | for _, b := range balancers { 59 | balancer.Register(newBuilder(router, b)) 60 | } 61 | } 62 | 63 | type Builder struct { 64 | router route.Router 65 | b tBalancer.Balancer 66 | } 67 | 68 | // newBuilder creates a new weighted-roundrobin balancer builder. 69 | func newBuilder(router route.Router, b tBalancer.Balancer) balancer.Builder { 70 | return base.NewBalancerBuilder( 71 | b.Schema(), 72 | &Builder{router: router, b: b}, 73 | base.Config{HealthCheck: true}, 74 | ) 75 | } 76 | 77 | func (b *Builder) Build(info base.PickerBuildInfo) balancer.Picker { 78 | p := &Picker{ 79 | subConns: make(map[string]balancer.SubConn), 80 | r: b.router, 81 | b: b.b, 82 | } 83 | for conn, info := range info.ReadySCs { 84 | ins := info.Address.Attributes.Value("rawInstance").(naming.Instance) 85 | p.instances = append(p.instances, ins) 86 | p.subConns[ins.Addr()] = conn 87 | } 88 | return p 89 | } 90 | 91 | type Picker struct { 92 | instances []naming.Instance 93 | subConns map[string]balancer.SubConn 94 | r route.Router //路由&泳道 95 | b tBalancer.Balancer 96 | } 97 | 98 | // Pick pick instances 99 | func (p *Picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) { 100 | svc := naming.NewService( 101 | meta.Sys(info.Ctx, meta.DestKey(meta.ServiceNamespace)).(string), 102 | meta.Sys(info.Ctx, meta.DestKey(meta.ServiceName)).(string), 103 | ) 104 | log.DefaultLog.WithContext(info.Ctx).Debugw("msg", "picker pick", "svc", svc, "nodes", p.instances) 105 | 106 | nodes := p.r.Select(info.Ctx, svc, p.instances) 107 | if len(nodes) == 0 { 108 | log.DefaultLog.WithContext(info.Ctx).Errorw("msg", "picker: ErrNoSubConnAvailable!", "service", svc.Name) 109 | return balancer.PickResult{}, balancer.ErrNoSubConnAvailable 110 | } 111 | node, _ := p.b.Pick(info.Ctx, nodes) 112 | span := zipkin.SpanFromContext(info.Ctx) 113 | if span != nil { 114 | ep, _ := zipkin.NewEndpoint(node.Service.Name, node.Addr()) 115 | span.SetRemoteEndpoint(ep) 116 | } 117 | return balancer.PickResult{ 118 | SubConn: p.subConns[node.Addr()], 119 | Done: nil, 120 | }, nil 121 | } 122 | -------------------------------------------------------------------------------- /naming/service.go: -------------------------------------------------------------------------------- 1 | package naming 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/url" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/go-kratos/kratos/v2/registry" 11 | "github.com/tencentyun/tsf-go/pkg/sys/env" 12 | ) 13 | 14 | const ( 15 | StatusUp = 0 16 | StatusDown = 1 17 | 18 | GroupID = "TSF_GROUP_ID" 19 | NamespaceID = "TSF_NAMESPACE_ID" 20 | ApplicationID = "TSF_APPLICATION_ID" 21 | Region = "TSF_REGION" 22 | 23 | NsLocal = "local" 24 | NsGlobal = "global" 25 | ) 26 | 27 | // Service 服务信息 28 | type Service struct { 29 | Namespace string 30 | Name string 31 | } 32 | 33 | func NewService(namespace string, name string) *Service { 34 | if namespace == "" || namespace == NsLocal { 35 | namespace = env.NamespaceID() 36 | } 37 | return &Service{Namespace: namespace, Name: name} 38 | } 39 | 40 | // Instance 服务实例信息 41 | type Instance struct { 42 | // 服务信息 43 | Service *Service `json:"service,omitempty"` 44 | // namespace下全局唯一的实例ID 45 | ID string `json:"id"` 46 | // 服务实例所属地域 47 | Region string `json:"region"` 48 | // 服务实例可访问的ip地址 49 | Host string `json:"host"` 50 | // 协议端口 51 | Port int `json:"port"` 52 | // 服务实例标签元信息,比如appVersion、group、weight等 53 | Metadata map[string]string `json:"metadata"` 54 | // 实例运行状态: up/down 55 | Status int64 `json:"status"` 56 | // 过滤用的标签 57 | Tags []string `json:"tags"` 58 | } 59 | 60 | func (i Instance) Addr() string { 61 | return i.Host + ":" + strconv.FormatInt(int64(i.Port), 10) 62 | } 63 | 64 | func (i Instance) ToKratosInstance() *registry.ServiceInstance { 65 | metadata := make(map[string]string) 66 | for k, v := range i.Metadata { 67 | metadata[k] = v 68 | } 69 | metadata["tsf_status"] = strconv.FormatInt(i.Status, 10) 70 | tags, _ := json.Marshal(i.Tags) 71 | metadata["tsf_tags"] = string(tags) 72 | protocol := metadata["protocol"] 73 | if protocol == "" { 74 | protocol = "http" 75 | } 76 | ki := ®istry.ServiceInstance{ 77 | ID: i.ID, 78 | Name: i.Service.Name, 79 | Version: metadata["TSF_PROG_VERSION"], 80 | Metadata: metadata, 81 | Endpoints: []string{fmt.Sprintf("%s://%s:%d", protocol, i.Host, i.Port)}, 82 | } 83 | return ki 84 | } 85 | 86 | func FromKratosInstance(ki *registry.ServiceInstance) (inss []*Instance) { 87 | for _, e := range ki.Endpoints { 88 | scheme, ip, port := parseEndpoint(e) 89 | status, _ := strconv.Atoi(ki.Metadata["tsf_status"]) 90 | id := ki.ID 91 | if len(ki.Endpoints) > 1 { 92 | id += "-" + scheme 93 | } 94 | ins := &Instance{ 95 | Service: &Service{Namespace: ki.Metadata[NamespaceID], Name: ki.Name}, 96 | ID: id, 97 | Region: ki.Metadata[Region], 98 | Host: ip, 99 | Port: port, 100 | Metadata: ki.Metadata, 101 | Status: int64(status), 102 | } 103 | ins.Metadata = make(map[string]string) 104 | for k, v := range ki.Metadata { 105 | ins.Metadata[k] = v 106 | } 107 | ins.Metadata["protocol"] = scheme 108 | if scheme == "grpc" { 109 | if ins.Metadata["TSF_API_METAS_GRPC"] != "" { 110 | ins.Metadata["TSF_API_METAS"] = ins.Metadata["TSF_API_METAS_GRPC"] 111 | } 112 | } else if ins.Metadata["TSF_API_METAS_HTTP"] != "" { 113 | ins.Metadata["TSF_API_METAS"] = ins.Metadata["TSF_API_METAS_HTTP"] 114 | } 115 | delete(ins.Metadata, "TSF_API_METAS_GRPC") 116 | delete(ins.Metadata, "TSF_API_METAS_HTTP") 117 | json.Unmarshal([]byte(ki.Metadata["tsf_tags"]), &ins.Tags) 118 | inss = append(inss, ins) 119 | } 120 | return 121 | } 122 | 123 | func parseEndpoint(endpoint string) (string, string, int) { 124 | u, _ := url.Parse(endpoint) 125 | addrs := strings.Split(u.Host, ":") 126 | ip := addrs[0] 127 | port, _ := strconv.ParseInt(addrs[1], 10, 32) 128 | return u.Scheme, ip, int(port) 129 | } 130 | -------------------------------------------------------------------------------- /tracing/redisotel/redis.go: -------------------------------------------------------------------------------- 1 | package redisotel 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "strings" 7 | 8 | tsf "github.com/tencentyun/tsf-go" 9 | 10 | "github.com/go-kratos/kratos/v2/errors" 11 | "github.com/go-redis/redis/extra/rediscmd" 12 | "github.com/go-redis/redis/v8" 13 | "go.opentelemetry.io/otel" 14 | "go.opentelemetry.io/otel/attribute" 15 | "go.opentelemetry.io/otel/codes" 16 | "go.opentelemetry.io/otel/trace" 17 | ) 18 | 19 | func New(address string) RedisHook { 20 | tracer := otel.Tracer("redis") 21 | 22 | remoteIP, remotePort := parseAddr(address) 23 | return RedisHook{ip: remoteIP, port: remotePort, tracer: tracer} 24 | } 25 | 26 | type RedisHook struct { 27 | ip string 28 | port uint16 29 | tracer trace.Tracer 30 | } 31 | 32 | var _ redis.Hook = RedisHook{} 33 | 34 | func (rh RedisHook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) { 35 | if !trace.SpanFromContext(ctx).IsRecording() { 36 | return ctx, nil 37 | } 38 | localEndpoint := tsf.LocalEndpoint(ctx) 39 | 40 | ctx, span := rh.tracer.Start(ctx, cmd.FullName(), trace.WithSpanKind(trace.SpanKindClient)) 41 | span.SetAttributes(attribute.String("local.ip", localEndpoint.IP)) 42 | span.SetAttributes(attribute.Int64("local.port", int64(localEndpoint.Port))) 43 | span.SetAttributes(attribute.String("local.service", localEndpoint.Service)) 44 | 45 | span.SetAttributes(attribute.String("peer.ip", rh.ip)) 46 | span.SetAttributes(attribute.Int64("peer.port", int64(rh.port))) 47 | span.SetAttributes(attribute.String("peer.service", "redis-server")) 48 | span.SetAttributes(attribute.String("remoteComponent", "REDIS")) 49 | 50 | span.SetAttributes( 51 | attribute.String("db.system", "redis"), 52 | attribute.String("db.statement", rediscmd.CmdString(cmd)), 53 | ) 54 | 55 | return ctx, nil 56 | } 57 | 58 | func (RedisHook) AfterProcess(ctx context.Context, cmd redis.Cmder) error { 59 | recordError(ctx, cmd.Err()) 60 | return nil 61 | } 62 | 63 | func (rh RedisHook) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (context.Context, error) { 64 | if !trace.SpanFromContext(ctx).IsRecording() { 65 | return ctx, nil 66 | } 67 | 68 | summary, cmdsString := rediscmd.CmdsString(cmds) 69 | 70 | ctx, span := rh.tracer.Start(ctx, "pipeline "+summary) 71 | span.SetAttributes(attribute.String("peer.ip", rh.ip)) 72 | span.SetAttributes(attribute.Int64("peer.port", int64(rh.port))) 73 | span.SetAttributes(attribute.String("peer.service", "redis-server")) 74 | span.SetAttributes(attribute.String("remoteComponent", "REDIS")) 75 | 76 | span.SetAttributes( 77 | attribute.String("db.system", "redis"), 78 | attribute.Int("db.redis.num_cmd", len(cmds)), 79 | attribute.String("db.statement", cmdsString), 80 | ) 81 | 82 | return ctx, nil 83 | } 84 | 85 | func (RedisHook) AfterProcessPipeline(ctx context.Context, cmds []redis.Cmder) error { 86 | recordError(ctx, cmds[0].Err()) 87 | return nil 88 | } 89 | 90 | func recordError(ctx context.Context, err error) { 91 | span := trace.SpanFromContext(ctx) 92 | var code = 200 93 | if err != nil { 94 | code = int(errors.FromError(err).GetCode()) 95 | span.RecordError(err) 96 | span.SetStatus(codes.Error, err.Error()) 97 | span.SetAttributes(attribute.String("exception", err.Error())) 98 | } else { 99 | span.SetStatus(codes.Ok, "OK") 100 | } 101 | 102 | span.SetAttributes( 103 | attribute.Int("resultStatus", code), 104 | ) 105 | span.End() 106 | } 107 | 108 | func parseAddr(addr string) (ip string, port uint16) { 109 | strs := strings.Split(addr, ":") 110 | if len(strs) > 0 { 111 | ip = strs[0] 112 | } 113 | if len(strs) > 1 { 114 | uport, _ := strconv.ParseUint(strs[1], 10, 16) 115 | port = uint16(uport) 116 | } 117 | return 118 | } 119 | -------------------------------------------------------------------------------- /pkg/config/consul/consul_test.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "net/http" 8 | "reflect" 9 | "testing" 10 | "time" 11 | 12 | "github.com/tencentyun/tsf-go/pkg/config" 13 | "gopkg.in/yaml.v3" 14 | ) 15 | 16 | func TestMain(m *testing.M) { 17 | m.Run() 18 | 19 | } 20 | 21 | const testContent1 = `destList: 22 | - destId: route-2123123` 23 | 24 | const testContent2 = `destList: 25 | - destId: route-2123123 26 | - destId: route-wahaha` 27 | 28 | func TestSimpleConfig(t *testing.T) { 29 | err := set("com/tencent/tsf", []byte(testContent1)) 30 | if err != nil { 31 | t.Logf("setConsul com/tencent/tsf failed!err:=%v", err) 32 | t.FailNow() 33 | } 34 | config := New(&Config{ 35 | Address: "127.0.0.1:8500", 36 | }) 37 | watcher := config.Subscribe("com/tencent/tsf") 38 | 39 | checkConfig(t, watcher, testContent1) 40 | 41 | err = set("com/tencent/tsf", []byte(testContent2)) 42 | if err != nil { 43 | t.Logf("setConsul com/tencent/tsf failed!err:=%v", err) 44 | t.FailNow() 45 | } 46 | checkConfig(t, watcher, testContent2) 47 | err = deleteKey("com/tencent/tsf") 48 | if err != nil { 49 | t.Logf("delete com/tencent/tsf failed!err:=%v", err) 50 | t.FailNow() 51 | } 52 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) 53 | defer cancel() 54 | specs, err := watcher.Watch(ctx) 55 | if err != nil { 56 | t.Logf("watch com/tencent/tsf failed!err:=%v", err) 57 | t.FailNow() 58 | } 59 | if specs != nil { 60 | t.Logf("watch com/tencent/tsf msut be nil") 61 | t.FailNow() 62 | } 63 | err = set("com/tencent/tsf", []byte(testContent2)) 64 | if err != nil { 65 | t.Logf("setConsul com/tencent/tsf failed!err:=%v", err) 66 | t.FailNow() 67 | } 68 | checkConfig(t, watcher, testContent2) 69 | } 70 | 71 | type checkData struct { 72 | DestList []struct { 73 | DestId string `yaml:"destId"` 74 | } `yaml:"destList"` 75 | } 76 | 77 | func checkConfig(t *testing.T, watcher config.Watcher, expect string) { 78 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) 79 | defer cancel() 80 | specs, err := watcher.Watch(ctx) 81 | if err != nil { 82 | t.Logf("watch com/tencent/tsf failed!err:=%v", err) 83 | t.FailNow() 84 | } 85 | var res checkData 86 | err = specs[0].Data.Unmarshal(&res) 87 | if err != nil { 88 | t.Logf("Unmarshal com/tencent/tsf failed!err:=%v", err) 89 | t.FailNow() 90 | } 91 | var check checkData 92 | err = yaml.Unmarshal([]byte(expect), &check) 93 | if err != nil { 94 | t.Logf("Unmarshal expect(%s) failed!err:=%v", expect, err) 95 | t.FailNow() 96 | } 97 | if !reflect.DeepEqual(check, res) { 98 | t.Logf("DeepEqual check(%+v) res(%+v) failed!not euqal", check, res) 99 | t.FailNow() 100 | } 101 | } 102 | 103 | func deleteKey(key string) error { 104 | client := http.Client{Timeout: time.Second * 2} 105 | req, err := http.NewRequest("DELETE", fmt.Sprintf("http://127.0.0.1:8500/v1/kv/%s", key), nil) 106 | if err != nil { 107 | return err 108 | } 109 | resp, err := client.Do(req) 110 | if err != nil { 111 | return err 112 | } 113 | defer resp.Body.Close() 114 | if resp.StatusCode != 200 { 115 | return fmt.Errorf("response status (%d) not 200", resp.StatusCode) 116 | } 117 | return nil 118 | } 119 | 120 | func set(key string, value []byte) error { 121 | client := http.Client{Timeout: time.Second * 2} 122 | req, err := http.NewRequest("PUT", fmt.Sprintf("http://127.0.0.1:8500/v1/kv/%s", key), bytes.NewReader(value)) 123 | if err != nil { 124 | return err 125 | } 126 | resp, err := client.Do(req) 127 | if err != nil { 128 | return err 129 | } 130 | defer resp.Body.Close() 131 | if resp.StatusCode != 200 { 132 | return fmt.Errorf("response status (%d) not 200", resp.StatusCode) 133 | } 134 | return nil 135 | } 136 | -------------------------------------------------------------------------------- /pkg/http/client.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io" 7 | "io/ioutil" 8 | "net" 9 | "net/http" 10 | "time" 11 | 12 | "github.com/go-kratos/kratos/v2/errors" 13 | ) 14 | 15 | type Client struct { 16 | cli *http.Client 17 | } 18 | 19 | type options struct { 20 | timeout time.Duration 21 | maxConnsPerHost int 22 | } 23 | 24 | // Option configures how we set up the client. 25 | type Option interface { 26 | apply(*options) 27 | } 28 | 29 | // funcOption wraps a function that modifies Options into an 30 | // implementation of the Option interface. 31 | type funcOption struct { 32 | f func(*options) 33 | } 34 | 35 | func (fdo *funcOption) apply(do *options) { 36 | fdo.f(do) 37 | } 38 | 39 | func newFuncOption(f func(*options)) *funcOption { 40 | return &funcOption{ 41 | f: f, 42 | } 43 | } 44 | 45 | // WithTimeout returns a Option that configures a timeout for dialing a ClientConn initially. 46 | func WithTimeout(timeout time.Duration) Option { 47 | return newFuncOption(func(o *options) { 48 | o.timeout = timeout 49 | }) 50 | } 51 | 52 | // WithMaxConnPerHost returns a Option that configures a maxConnsPerHost for dialing a ClientConn initially. 53 | func WithMaxConnPerHost(max int) Option { 54 | return newFuncOption(func(o *options) { 55 | o.maxConnsPerHost = max 56 | }) 57 | } 58 | 59 | func NewClient(optFunc ...Option) *Client { 60 | opts := &options{} 61 | for _, f := range optFunc { 62 | f.apply(opts) 63 | } 64 | transport := &http.Transport{ 65 | Proxy: http.ProxyFromEnvironment, 66 | DialContext: (&net.Dialer{ 67 | Timeout: 5 * time.Second, 68 | KeepAlive: 30 * time.Second, 69 | }).DialContext, 70 | MaxIdleConns: 20, 71 | IdleConnTimeout: 90 * time.Second, 72 | TLSHandshakeTimeout: 6 * time.Second, 73 | ExpectContinueTimeout: 1 * time.Second, 74 | MaxIdleConnsPerHost: 6, 75 | MaxConnsPerHost: opts.maxConnsPerHost, 76 | } 77 | return &Client{cli: &http.Client{ 78 | Timeout: opts.timeout, 79 | Transport: transport, 80 | }} 81 | } 82 | 83 | // Get http get 84 | func (c *Client) Get(url string, respBody interface{}) (header http.Header, err error) { 85 | header, err = c.Do("GET", url, nil, respBody) 86 | return 87 | } 88 | 89 | // Put http put 90 | func (c *Client) Put(url string, reqBody interface{}, respBody interface{}) (err error) { 91 | _, err = c.Do("PUT", url, reqBody, respBody) 92 | return 93 | } 94 | 95 | // Post http post 96 | func (c *Client) Post(url string, reqBody interface{}, respBody interface{}) (err error) { 97 | _, err = c.Do("POST", url, reqBody, respBody) 98 | return 99 | } 100 | 101 | // Do http do 102 | func (c *Client) Do(method string, url string, reqBody interface{}, respBody interface{}) (header http.Header, err error) { 103 | var ( 104 | resp *http.Response 105 | content []byte 106 | body io.Reader 107 | req *http.Request 108 | ) 109 | if reqBody != nil { 110 | content, err = json.Marshal(reqBody) 111 | if err != nil { 112 | return 113 | } 114 | body = bytes.NewReader(content) 115 | } 116 | req, err = http.NewRequest(method, url, body) 117 | if err != nil { 118 | return 119 | } 120 | if body != nil { 121 | req.Header.Add("Content-Type", "application/json") 122 | } 123 | resp, err = c.cli.Do(req) 124 | if err != nil { 125 | return 126 | } 127 | defer resp.Body.Close() 128 | header = resp.Header 129 | if resp.StatusCode != http.StatusOK { 130 | content, _ = ioutil.ReadAll(resp.Body) 131 | err = errors.Newf(resp.StatusCode, errors.UnknownReason, string(content)) 132 | return 133 | } 134 | content, err = ioutil.ReadAll(resp.Body) 135 | if err != nil { 136 | return 137 | } 138 | if respBody != nil { 139 | err = json.Unmarshal(content, respBody) 140 | if err != nil { 141 | return 142 | } 143 | } 144 | return 145 | } 146 | -------------------------------------------------------------------------------- /examples/helloworld/proto/helloworld_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | 3 | package proto 4 | 5 | import ( 6 | context "context" 7 | grpc "google.golang.org/grpc" 8 | codes "google.golang.org/grpc/codes" 9 | status "google.golang.org/grpc/status" 10 | ) 11 | 12 | // This is a compile-time assertion to ensure that this generated file 13 | // is compatible with the grpc package it is being compiled against. 14 | // Requires gRPC-Go v1.32.0 or later. 15 | const _ = grpc.SupportPackageIsVersion7 16 | 17 | // GreeterClient is the client API for Greeter service. 18 | // 19 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 20 | type GreeterClient interface { 21 | // Sends a greeting 22 | SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) 23 | } 24 | 25 | type greeterClient struct { 26 | cc grpc.ClientConnInterface 27 | } 28 | 29 | func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient { 30 | return &greeterClient{cc} 31 | } 32 | 33 | func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { 34 | out := new(HelloReply) 35 | err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return out, nil 40 | } 41 | 42 | // GreeterServer is the server API for Greeter service. 43 | // All implementations must embed UnimplementedGreeterServer 44 | // for forward compatibility 45 | type GreeterServer interface { 46 | // Sends a greeting 47 | SayHello(context.Context, *HelloRequest) (*HelloReply, error) 48 | mustEmbedUnimplementedGreeterServer() 49 | } 50 | 51 | // UnimplementedGreeterServer must be embedded to have forward compatible implementations. 52 | type UnimplementedGreeterServer struct { 53 | } 54 | 55 | func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) { 56 | return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") 57 | } 58 | func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {} 59 | 60 | // UnsafeGreeterServer may be embedded to opt out of forward compatibility for this service. 61 | // Use of this interface is not recommended, as added methods to GreeterServer will 62 | // result in compilation errors. 63 | type UnsafeGreeterServer interface { 64 | mustEmbedUnimplementedGreeterServer() 65 | } 66 | 67 | func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) { 68 | s.RegisterService(&Greeter_ServiceDesc, srv) 69 | } 70 | 71 | func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 72 | in := new(HelloRequest) 73 | if err := dec(in); err != nil { 74 | return nil, err 75 | } 76 | if interceptor == nil { 77 | return srv.(GreeterServer).SayHello(ctx, in) 78 | } 79 | info := &grpc.UnaryServerInfo{ 80 | Server: srv, 81 | FullMethod: "/helloworld.Greeter/SayHello", 82 | } 83 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 84 | return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) 85 | } 86 | return interceptor(ctx, in, info, handler) 87 | } 88 | 89 | // Greeter_ServiceDesc is the grpc.ServiceDesc for Greeter service. 90 | // It's only intended for direct use with grpc.RegisterService, 91 | // and not to be introspected or modified (even as a copy) 92 | var Greeter_ServiceDesc = grpc.ServiceDesc{ 93 | ServiceName: "helloworld.Greeter", 94 | HandlerType: (*GreeterServer)(nil), 95 | Methods: []grpc.MethodDesc{ 96 | { 97 | MethodName: "SayHello", 98 | Handler: _Greeter_SayHello_Handler, 99 | }, 100 | }, 101 | Streams: []grpc.StreamDesc{}, 102 | Metadata: "helloworld.proto", 103 | } 104 | -------------------------------------------------------------------------------- /pkg/proxy/proxy.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "math/rand" 8 | "net" 9 | "net/http" 10 | "os" 11 | "sync" 12 | "time" 13 | 14 | "github.com/tencentyun/tsf-go/log" 15 | "github.com/tencentyun/tsf-go/pkg/sys/env" 16 | 17 | "github.com/elazarl/goproxy" 18 | "golang.org/x/crypto/ssh" 19 | ) 20 | 21 | var ( 22 | client *ssh.Client 23 | listener []net.Listener 24 | mu sync.Mutex 25 | inited bool 26 | ) 27 | 28 | func Inited() bool { 29 | mu.Lock() 30 | defer mu.Unlock() 31 | return inited 32 | } 33 | 34 | func Init() { 35 | mu.Lock() 36 | if inited { 37 | return 38 | } 39 | mu.Unlock() 40 | 41 | if env.SSHHost() == "" || env.SSHUser() == "" { 42 | log.DefaultLog.Infof("no ssh_host & ssh_user detected,proxy tunnel exit!") 43 | return 44 | } 45 | var auths []ssh.AuthMethod 46 | if env.SSHPass() != "" { 47 | auths = append(auths, ssh.Password(env.SSHPass())) 48 | } 49 | if env.SSHPass() == "" && env.SSHKey() != "" { 50 | key, err := ioutil.ReadFile(env.SSHKey()) 51 | if err != nil { 52 | log.DefaultLog.Errorf("unable to read private key: %v", err) 53 | return 54 | } 55 | // Create the Signer for this private key. 56 | signer, err := ssh.ParsePrivateKey(key) 57 | if err != nil { 58 | log.DefaultLog.Errorf("unable to parse private key: %v", err) 59 | return 60 | } 61 | auths = append(auths, ssh.PublicKeys(signer)) 62 | } 63 | 64 | config := &ssh.ClientConfig{ 65 | User: env.SSHUser(), 66 | Auth: auths, 67 | HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { 68 | return nil 69 | }, 70 | Timeout: 6 * time.Second, 71 | } 72 | addr := fmt.Sprintf("%s:%d", env.SSHHost(), env.SSHPort()) 73 | conn, err := ssh.Dial("tcp", addr, config) 74 | if err != nil { 75 | log.DefaultLog.Errorf("unable to connect to ssh host [%s]: %v", addr, err) 76 | return 77 | } 78 | client = conn 79 | prxy := goproxy.NewProxyHttpServer() 80 | prxy.Tr = &http.Transport{Dial: client.Dial} 81 | proxyPort := rand.Int31n(55535) + 10000 82 | proxyAddr := fmt.Sprintf("0.0.0.0:%d", proxyPort) 83 | os.Setenv("HTTP_PROXY", fmt.Sprintf("http://127.0.0.1:%d", proxyPort)) 84 | os.Setenv("HTTPS_PROXY", fmt.Sprintf("https://127.0.0.1:%d", proxyPort)) 85 | log.DefaultLog.Infof("listening for local HTTP PROXY connections on [%s]", proxyAddr) 86 | go func() { 87 | err = http.ListenAndServe(proxyAddr, prxy) 88 | log.DefaultLog.Infof("proxy ListenAndServe exit with err: %v", err) 89 | }() 90 | mu.Lock() 91 | inited = true 92 | client = conn 93 | mu.Unlock() 94 | } 95 | 96 | func Close() { 97 | mu.Lock() 98 | defer mu.Unlock() 99 | for _, l := range listener { 100 | l.Close() 101 | } 102 | client.Close() 103 | } 104 | 105 | func ListenRemote(lPort int, rPort int) { 106 | // Request the remote side to open port 8080 on all interfaces. 107 | log.DefaultLog.Infof("[ListenRemote] listening for remote conn on [:%d] and local conn on [:%d]", rPort, lPort) 108 | l, err := client.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", rPort)) 109 | if err != nil { 110 | log.DefaultLog.Errorf("[ListenRemote] unable to register tcp forward,err: %v", err) 111 | return 112 | } 113 | mu.Lock() 114 | listener = append(listener, l) 115 | mu.Unlock() 116 | for { 117 | conn, err := l.Accept() 118 | if err != nil { 119 | log.DefaultLog.Errorf("[ListenRemote] accept remote failed,err: %v", err) 120 | return 121 | } 122 | go serveTcp(conn, lPort) 123 | } 124 | } 125 | 126 | func serveTcp(conn net.Conn, localPort int) { 127 | defer conn.Close() 128 | localConn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", localPort)) 129 | if err != nil { 130 | log.DefaultLog.Errorf("[serveTcp] dial local addr 127.0.0.1:%d failed!err:=%v", localPort, err) 131 | return 132 | } 133 | defer localConn.Close() 134 | ch := make(chan struct{}, 0) 135 | go func() { 136 | io.Copy(conn, localConn) 137 | close(ch) 138 | }() 139 | io.Copy(localConn, conn) 140 | <-ch 141 | } 142 | -------------------------------------------------------------------------------- /pkg/naming/consul/consul_test.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "math/rand" 8 | "os" 9 | "os/signal" 10 | "sync/atomic" 11 | "syscall" 12 | "testing" 13 | "time" 14 | 15 | "github.com/tencentyun/tsf-go/log" 16 | "github.com/tencentyun/tsf-go/pkg/naming" 17 | "github.com/tencentyun/tsf-go/pkg/sys/env" 18 | ) 19 | 20 | var serviceNum int 21 | var nidNum int 22 | var insNum int 23 | var nidStart int 24 | var consulAddr string 25 | var token string 26 | var dereg bool 27 | var appID string 28 | var catalog bool 29 | var subscribe int 30 | 31 | func TestMain(m *testing.M) { 32 | // 这两个参数必传 33 | flag.StringVar(&consulAddr, "consulAddr", "127.0.0.1:8500", "-consulAddr 127.0.0.1:8500") 34 | flag.StringVar(&token, "token", "", "-token") 35 | 36 | flag.IntVar(&serviceNum, "serviceNum", 2, "-serviceNum 4") 37 | flag.IntVar(&nidStart, "nidStart", 0, "-nidStart 0") 38 | flag.IntVar(&nidNum, "nidNum", 1, "-nidNum 1") 39 | flag.IntVar(&insNum, "insNum", 10, "-insNum 3") 40 | flag.StringVar(&appID, "appID", "", "-appID ") 41 | flag.BoolVar(&catalog, "catalog", true, "-catalog true") 42 | flag.IntVar(&subscribe, "subscribe", 3, "-subscribe 3") 43 | flag.BoolVar(&dereg, "dereg", false, "-dereg false") 44 | 45 | flag.Parse() 46 | m.Run() 47 | } 48 | 49 | func TestConsul(t *testing.T) { 50 | ctx, cancel := context.WithTimeout(context.Background(), time.Hour*4) 51 | defer cancel() 52 | fmt.Println("param: ", serviceNum, nidStart, nidNum, insNum, consulAddr, token) 53 | count := 0 54 | ch := make(chan struct{}, 0) 55 | for n := nidStart; n < nidStart+nidNum; n++ { 56 | for i := 0; i < serviceNum; i++ { 57 | for j := 0; j < insNum; j++ { 58 | count++ 59 | go newClient(ctx, ch, fmt.Sprintf("namespace-test-%d", n), "server_test", fmt.Sprintf("server_test_%d_%d", i, j), i) 60 | time.Sleep(time.Millisecond * 25) 61 | } 62 | } 63 | } 64 | 65 | sigs := make(chan os.Signal, 1) 66 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGHUP) 67 | sig := <-sigs 68 | log.DefaultLog.Infow("msg", "[server] got signal,exit now!", "sig", sig.String()) 69 | cancel() 70 | for i := 0; i < count; i++ { 71 | <-ch 72 | } 73 | time.Sleep(time.Millisecond * 800) 74 | log.DefaultLog.Info("clear success!") 75 | return 76 | } 77 | 78 | var failCount int64 79 | var successCount int64 80 | 81 | func newClient(ctx context.Context, ch chan struct{}, nid string, name string, insID string, idx int) { 82 | serviceName := fmt.Sprintf("%s-%d", name, idx) 83 | consul := New(&Config{Address: []string{consulAddr}, Token: token, NamespaceID: nid, AppID: appID, Catalog: catalog}) 84 | ins := naming.Instance{ 85 | ID: insID + "-" + serviceName, 86 | Service: &naming.Service{Name: serviceName}, 87 | Host: env.LocalIP(), 88 | Port: 8080, 89 | Metadata: map[string]string{ 90 | "TSF_APPLICATION_ID": "application-maep2nv3", 91 | "TSF_GROUP_ID": "group-gyq46ea5", 92 | "TSF_INSTNACE_ID": "ins-3jiowz0y", 93 | "TSF_NAMESPACE_ID": "namespace-py5lr6v4", 94 | "TSF_PROG_VERSION": "provider2", 95 | "TSF_REGION": "ap-chongqing", 96 | "TSF_ZONE": "", 97 | }, 98 | Tags: []string{"secure=false"}, 99 | } 100 | for { 101 | var err error 102 | if dereg { 103 | err = consul.Deregister(&ins) 104 | } else { 105 | err = consul.Register(&ins) 106 | } 107 | if err != nil { 108 | failed := atomic.AddInt64(&failCount, 1) 109 | if failed > atomic.LoadInt64(&successCount) { 110 | panic(err) 111 | } 112 | time.Sleep(time.Second) 113 | } else { 114 | atomic.AddInt64(&successCount, 1) 115 | break 116 | } 117 | } 118 | if dereg { 119 | ch <- struct{}{} 120 | } 121 | time.Sleep(time.Minute * 2) 122 | for i := 0; i < subscribe; i++ { 123 | consul.Subscribe(naming.Service{Name: fmt.Sprintf("%s-%d", name, idx+i), Namespace: nid}) 124 | } 125 | <-ctx.Done() 126 | s := rand.Int63n(3000) 127 | time.Sleep(time.Millisecond * time.Duration(s)) 128 | consul.Deregister(&ins) 129 | ch <- struct{}{} 130 | } 131 | -------------------------------------------------------------------------------- /breaker/breaker.go: -------------------------------------------------------------------------------- 1 | package breaker 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | // Config broker config. 9 | type Config struct { 10 | // breaker switch 11 | // set true to close breaker 12 | SwitchOff bool 13 | 14 | // Google 15 | K float64 16 | 17 | // 统计时间窗口 18 | // 默认值 5s 19 | Window time.Duration 20 | Bucket int 21 | // 熔断触发临界请求量 22 | // 统计窗口内请求量低于Request值则不触发熔断 23 | // 默认值 20 24 | Request int64 25 | } 26 | 27 | func (conf *Config) fix() { 28 | if conf.K == 0 { 29 | conf.K = 1.5 30 | } 31 | if conf.Request == 0 { 32 | conf.Request = 50 33 | } 34 | if conf.Bucket == 0 { 35 | conf.Bucket = 10 36 | } 37 | if conf.Window == 0 { 38 | conf.Window = time.Duration(5 * time.Second) 39 | } 40 | } 41 | 42 | // Breaker is a CircuitBreaker pattern. 43 | // FIXME on int32 atomic.LoadInt32(&b.on) == _switchOn 44 | type Breaker interface { 45 | Allow() error 46 | MarkSuccess() 47 | MarkFailed() 48 | } 49 | 50 | // Group represents a class of CircuitBreaker and forms a namespace in which 51 | // units of CircuitBreaker. 52 | type Group struct { 53 | mu sync.RWMutex 54 | brks map[string]Breaker 55 | conf *Config 56 | } 57 | 58 | const ( 59 | // StateOpen when circuit breaker open, request not allowed, after sleep 60 | // some duration, allow one single request for testing the health, if ok 61 | // then state reset to closed, if not continue the step. 62 | StateOpen int32 = iota 63 | // StateClosed when circuit breaker closed, request allowed, the breaker 64 | // calc the succeed ratio, if request num greater request setting and 65 | // ratio lower than the setting ratio, then reset state to open. 66 | StateClosed 67 | // StateHalfopen when circuit breaker open, after slepp some duration, allow 68 | // one request, but not state closed. 69 | StateHalfopen 70 | 71 | //_switchOn int32 = iota 72 | // _switchOff 73 | ) 74 | 75 | var ( 76 | _mu sync.RWMutex 77 | _conf = &Config{ 78 | Window: time.Duration(3 * time.Second), 79 | Bucket: 10, 80 | Request: 20, 81 | 82 | // Percentage of failures must be lower than 33.33% 83 | K: 1.5, 84 | 85 | // Pattern: "", 86 | } 87 | _group = NewGroup(_conf) 88 | ) 89 | 90 | // Init init global breaker config, also can reload config after first time call. 91 | func Init(conf *Config) { 92 | if conf == nil { 93 | return 94 | } 95 | _mu.Lock() 96 | _conf = conf 97 | _mu.Unlock() 98 | } 99 | 100 | // Go runs your function while tracking the breaker state of default group. 101 | func Go(name string, run, fallback func() error) error { 102 | breaker := _group.Get(name) 103 | if err := breaker.Allow(); err != nil { 104 | return fallback() 105 | } 106 | return run() 107 | } 108 | 109 | // newBreaker new a breaker. 110 | func newBreaker(c *Config) (b Breaker) { 111 | // factory 112 | return newSRE(c) 113 | } 114 | 115 | // NewGroup new a breaker group container, if conf nil use default conf. 116 | func NewGroup(conf *Config) *Group { 117 | if conf == nil { 118 | _mu.RLock() 119 | conf = _conf 120 | _mu.RUnlock() 121 | } else { 122 | conf.fix() 123 | } 124 | return &Group{ 125 | conf: conf, 126 | brks: make(map[string]Breaker), 127 | } 128 | } 129 | 130 | // Get get a breaker by a specified key, if breaker not exists then make a new one. 131 | func (g *Group) Get(key string) Breaker { 132 | g.mu.RLock() 133 | brk, ok := g.brks[key] 134 | conf := g.conf 135 | g.mu.RUnlock() 136 | if ok { 137 | return brk 138 | } 139 | // NOTE here may new multi breaker for rarely case, let gc drop it. 140 | brk = newBreaker(conf) 141 | g.mu.Lock() 142 | if _, ok = g.brks[key]; !ok { 143 | g.brks[key] = brk 144 | } 145 | g.mu.Unlock() 146 | return brk 147 | } 148 | 149 | // Reload reload the group by specified config, this may let all inner breaker 150 | // reset to a new one. 151 | func (g *Group) Reload(conf *Config) { 152 | if conf == nil { 153 | return 154 | } 155 | conf.fix() 156 | g.mu.Lock() 157 | g.conf = conf 158 | g.brks = make(map[string]Breaker, len(g.brks)) 159 | g.mu.Unlock() 160 | } 161 | 162 | // Go runs your function while tracking the breaker state of group. 163 | func (g *Group) Go(name string, run, fallback func() error) error { 164 | breaker := g.Get(name) 165 | if err := breaker.Allow(); err != nil { 166 | return fallback() 167 | } 168 | return run() 169 | } 170 | -------------------------------------------------------------------------------- /grpc/balancer/multi/multi.go: -------------------------------------------------------------------------------- 1 | package multi 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/go-kratos/kratos/v2/registry" 8 | "github.com/openzipkin/zipkin-go" 9 | tBalancer "github.com/tencentyun/tsf-go/balancer" 10 | "github.com/tencentyun/tsf-go/balancer/hash" 11 | "github.com/tencentyun/tsf-go/balancer/p2c" 12 | "github.com/tencentyun/tsf-go/balancer/random" 13 | "github.com/tencentyun/tsf-go/log" 14 | "github.com/tencentyun/tsf-go/naming" 15 | "github.com/tencentyun/tsf-go/pkg/meta" 16 | "github.com/tencentyun/tsf-go/route" 17 | 18 | "google.golang.org/grpc/balancer" 19 | "google.golang.org/grpc/balancer/base" 20 | ) 21 | 22 | var ( 23 | _ base.PickerBuilder = &Builder{} 24 | _ balancer.Picker = &Picker{} 25 | 26 | mu sync.Mutex 27 | 28 | balancers []tBalancer.Balancer 29 | ) 30 | 31 | func init() { 32 | 33 | // random 34 | balancers = append(balancers, &random.Picker{}) 35 | // p2c 36 | balancers = append(balancers, p2c.New(nil)) 37 | 38 | balancers = append(balancers, hash.New()) 39 | 40 | } 41 | 42 | // Register register balancer builder if nil. 43 | func Register(router route.Router, b tBalancer.Balancer) { 44 | mu.Lock() 45 | defer mu.Unlock() 46 | balancer.Register(newBuilder(router, b)) 47 | } 48 | 49 | type Builder struct { 50 | router route.Router 51 | b tBalancer.Balancer 52 | } 53 | 54 | // newBuilder creates a new weighted-roundrobin balancer builder. 55 | func newBuilder(router route.Router, b tBalancer.Balancer) balancer.Builder { 56 | return base.NewBalancerBuilder( 57 | b.Schema(), 58 | &Builder{router: router, b: b}, 59 | base.Config{HealthCheck: true}, 60 | ) 61 | } 62 | 63 | func (b *Builder) Build(info base.PickerBuildInfo) balancer.Picker { 64 | p := &Picker{ 65 | subConns: make(map[string]balancer.SubConn), 66 | r: b.router, 67 | b: b.b, 68 | } 69 | for conn, info := range info.ReadySCs { 70 | metadata := make(map[string]string) 71 | metadata["protocol"], _ = info.Address.Attributes.Value("protocol").(string) 72 | metadata["tsf_status"], _ = info.Address.Attributes.Value("tsf_status").(string) 73 | metadata["tsf_tags"], _ = info.Address.Attributes.Value("tsf_tags").(string) 74 | metadata["TSF_APPLICATION_ID"], _ = info.Address.Attributes.Value("TSF_APPLICATION_ID").(string) 75 | metadata["TSF_GROUP_ID"], _ = info.Address.Attributes.Value("TSF_GROUP_ID").(string) 76 | metadata["TSF_INSTNACE_ID"], _ = info.Address.Attributes.Value("TSF_INSTNACE_ID").(string) 77 | metadata["TSF_PROG_VERSION"], _ = info.Address.Attributes.Value("TSF_PROG_VERSION").(string) 78 | metadata["TSF_ZONE"], _ = info.Address.Attributes.Value("TSF_ZONE").(string) 79 | metadata["TSF_REGION"], _ = info.Address.Attributes.Value("TSF_REGION").(string) 80 | metadata["TSF_NAMESPACE_ID"], _ = info.Address.Attributes.Value("TSF_NAMESPACE_ID").(string) 81 | metadata["TSF_SDK_VERSION"], _ = info.Address.Attributes.Value("TSF_SDK_VERSION").(string) 82 | 83 | si := ®istry.ServiceInstance{ 84 | Name: info.Address.ServerName, 85 | Endpoints: []string{fmt.Sprintf("grpc://%s", info.Address.Addr)}, 86 | Metadata: metadata, 87 | } 88 | 89 | p.instances = append(p.instances, *naming.FromKratosInstance(si)[0]) 90 | p.subConns[info.Address.Addr] = conn 91 | } 92 | return p 93 | } 94 | 95 | type Picker struct { 96 | instances []naming.Instance 97 | subConns map[string]balancer.SubConn 98 | r route.Router //路由&泳道 99 | b tBalancer.Balancer 100 | } 101 | 102 | // Pick pick instances 103 | func (p *Picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) { 104 | svc := naming.NewService(meta.Sys(info.Ctx, meta.DestKey(meta.ServiceNamespace)).(string), meta.Sys(info.Ctx, meta.DestKey(meta.ServiceName)).(string)) 105 | log.DefaultLog.Debugw("msg", "picker pick", "svc", svc, "nodes", p.instances) 106 | 107 | nodes := p.r.Select(info.Ctx, *svc, p.instances) 108 | if len(nodes) == 0 { 109 | log.DefaultLog.Errorw("msg", "picker: ErrNoSubConnAvailable!", "service", svc.Name) 110 | return balancer.PickResult{}, balancer.ErrNoSubConnAvailable 111 | } 112 | node, _ := p.b.Pick(info.Ctx, nodes) 113 | span := zipkin.SpanFromContext(info.Ctx) 114 | if span != nil { 115 | ep, _ := zipkin.NewEndpoint(node.Service.Name, node.Addr()) 116 | span.SetRemoteEndpoint(ep) 117 | } 118 | sc := p.subConns[node.Addr()] 119 | 120 | return balancer.PickResult{ 121 | SubConn: sc, 122 | Done: func(di balancer.DoneInfo) {}, 123 | }, nil 124 | } 125 | --------------------------------------------------------------------------------