diff --git a/README.md b/README.md index ef21047d9..6842f9d41 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ [gRPC Go](https://github.com/grpc/grpc-go) Middleware: interceptors, helpers, utilities. -**Important** The repo recently moved from `github.com/grpc-ecosystem/go-grpc-middleware`, please update your import paths. +**Important** The repo recently moved to `github.com/grpc-ecosystem/go-grpc-middleware`, please update your import paths. ## Middleware @@ -24,7 +24,7 @@ These are generic building blocks that make it easy to build multiple microservi The purpose of this repository is to act as a go-to point for such reusable functionality. It contains some of them itself, but also will link to useful external repos. -`grpc_middleware` itself provides support for chaining interceptors. Se [Documentation](DOC.md), but here's an example: +`grpc_middleware` itself provides support for chaining interceptors. See [Documentation](DOC.md), but here's an example: ```go import "github.com/grpc-ecosystem/go-grpc-middleware" diff --git a/auth/DOC.md b/auth/DOC.md index 4ba4c463b..5c4de1179 100644 --- a/auth/DOC.md +++ b/auth/DOC.md @@ -21,6 +21,42 @@ It also allows for per-service implementation overrides of `AuthFunc`. See `Serv Please see examples for simple examples of use. +#### Example: + +
+Click to expand code. + +```go +token, err := grpc_auth.AuthFromMD(ctx, "bearer") + if err != nil { + return nil, err + } + tokenInfo, err := parseToken(token) + if err != nil { + return nil, grpc.Errorf(codes.Unauthenticated, "invalid auth token: %v", err) + } + grpc_ctxtags.Extract(ctx).Set("auth.sub", userClaimFromToken(tokenInfo)) + newCtx := context.WithValue(ctx, "tokenInfo", tokenInfo) + return newCtx, nil +``` + +
+ +#### Example: + +
+Click to expand code. + +```go +server := grpc.NewServer( + grpc.StreamInterceptor(grpc_auth.StreamServerInterceptor(Example_authfunc)), + grpc.UnaryInterceptor(grpc_auth.UnaryServerInterceptor(Example_authfunc)), + ) + return server +``` + +
+ ## Imported Packages - [github.com/grpc-ecosystem/go-grpc-middleware](./..) diff --git a/fixup.sh b/fixup.sh index 8202d6678..850ec557d 100755 --- a/fixup.sh +++ b/fixup.sh @@ -17,7 +17,7 @@ function generate_markdown { package=${realdir##${GOPATH}/src/} echo "$package" cd ${dir} - ${GOBIN}/godoc2ghmd -file DOC.md ${package} + ${GOBIN}/godoc2ghmd -ex -file DOC.md ${package} ln -s DOC.md README.md 2> /dev/null # can fail cd ${oldpwd} done; diff --git a/logging/logrus/DOC.md b/logging/logrus/DOC.md index a42831258..e12aaec93 100644 --- a/logging/logrus/DOC.md +++ b/logging/logrus/DOC.md @@ -15,10 +15,92 @@ It accepts a user-configured `logrus.Entry` that will be used for logging comple You can use `Extract` to log into a request-scoped `logrus.Entry` instance in your handler code. The fields set on the logger correspond to the grpc_ctxtags.Tags attached to the context. +This package also implements request and response *payload* logging, both for server-side and client-side. These will be +logged as structured `jsonbp` fields for every message received/sent (both unary and streaming). For that please use +`Payload*Interceptor` functions for that. Please note that the user-provided function that determines whetether to log +the full request/response payload needs to be written with care, this can significantly slow down gRPC. + Logrus can also be made as a backend for gRPC library internals. For that use `ReplaceGrpcLogger`. Please see examples and tests for examples of use. +#### Example: + +
+Click to expand code. + +```go +x := func(ctx context.Context, ping *pb_testproto.PingRequest) (*pb_testproto.PingResponse, error) { + // Add fields the ctxtags of the request which will be added to all extracted loggers. + grpc_ctxtags.Extract(ctx).Set("custom_tags.string", "something").Set("custom_tags.int", 1337) + // Extract a request-scoped zap.Logger and log a message. + grpc_logrus.Extract(ctx).Info("some ping") + return &pb_testproto.PingResponse{Value: ping.Value}, nil + } + return x +``` + +
+ +#### Example: + +
+Click to expand code. + +```go +// Logrus entry is used, allowing pre-definition of certain fields by the user. + logrusEntry := logrus.NewEntry(logrusLogger) + // Shared options for the logger, with a custom gRPC code to log level function. + opts := []grpc_logrus.Option{ + grpc_logrus.WithLevels(customFunc), + } + // Make sure that log statements internal to gRPC library are logged using the zapLogger as well. + grpc_logrus.ReplaceGrpcLogger(logrusEntry) + // Create a server, make sure we put the grpc_ctxtags context before everything else. + server := grpc.NewServer( + grpc_middleware.WithUnaryServerChain( + grpc_ctxtags.UnaryServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)), + grpc_logrus.UnaryServerInterceptor(logrusEntry, opts...), + ), + grpc_middleware.WithStreamServerChain( + grpc_ctxtags.StreamServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)), + grpc_logrus.StreamServerInterceptor(logrusEntry, opts...), + ), + ) + return server +``` + +
+ +#### Example: + +
+Click to expand code. + +```go +// Logrus entry is used, allowing pre-definition of certain fields by the user. + logrusEntry := logrus.NewEntry(logrusLogger) + // Shared options for the logger, with a custom duration to log field function. + opts := []grpc_logrus.Option{ + grpc_logrus.WithDurationField(func(duration time.Duration) (key string, value interface{}) { + return "grpc.time_ns", duration.Nanoseconds() + }), + } + server := grpc.NewServer( + grpc_middleware.WithUnaryServerChain( + grpc_ctxtags.UnaryServerInterceptor(), + grpc_logrus.UnaryServerInterceptor(logrusEntry, opts...), + ), + grpc_middleware.WithStreamServerChain( + grpc_ctxtags.StreamServerInterceptor(), + grpc_logrus.StreamServerInterceptor(logrusEntry, opts...), + ), + ) + return server +``` + +
+ ## Imported Packages - github.com/Sirupsen/logrus diff --git a/logging/logrus/doc.go b/logging/logrus/doc.go index 5fddae9bc..0b91720f4 100644 --- a/logging/logrus/doc.go +++ b/logging/logrus/doc.go @@ -10,6 +10,11 @@ It accepts a user-configured `logrus.Entry` that will be used for logging comple You can use `Extract` to log into a request-scoped `logrus.Entry` instance in your handler code. The fields set on the logger correspond to the grpc_ctxtags.Tags attached to the context. +This package also implements request and response *payload* logging, both for server-side and client-side. These will be +logged as structured `jsonbp` fields for every message received/sent (both unary and streaming). For that please use +`Payload*Interceptor` functions for that. Please note that the user-provided function that determines whetether to log +the full request/response payload needs to be written with care, this can significantly slow down gRPC. + Logrus can also be made as a backend for gRPC library internals. For that use `ReplaceGrpcLogger`. Please see examples and tests for examples of use. diff --git a/logging/zap/DOC.md b/logging/zap/DOC.md index bc3a7d285..d97de2c77 100644 --- a/logging/zap/DOC.md +++ b/logging/zap/DOC.md @@ -15,10 +15,89 @@ be used for logging completed gRPC calls, and be populated into the `context.Con You can use `Extract` to log into a request-scoped `zap.Logger` instance in your handler code. The fields set on the logger correspond to the grpc_ctxtags.Tags attached to the context. +This package also implements request and response *payload* logging, both for server-side and client-side. These will be +logged as structured `jsonbp` fields for every message received/sent (both unary and streaming). For that please use +`Payload*Interceptor` functions for that. Please note that the user-provided function that determines whetether to log +the full request/response payload needs to be written with care, this can significantly slow down gRPC. + ZAP can also be made as a backend for gRPC library internals. For that use `ReplaceGrpcLogger`. Please see examples and tests for examples of use. +#### Example: + +
+Click to expand code. + +```go +x := func(ctx context.Context, ping *pb_testproto.PingRequest) (*pb_testproto.PingResponse, error) { + // Add fields the ctxtags of the request which will be added to all extracted loggers. + grpc_ctxtags.Extract(ctx).Set("custom_tags.string", "something").Set("custom_tags.int", 1337) + // Extract a request-scoped zap.Logger and log a message. + grpc_zap.Extract(ctx).Info("some ping") + return &pb_testproto.PingResponse{Value: ping.Value}, nil + } + return x +``` + +
+ +#### Example: + +
+Click to expand code. + +```go +// Shared options for the logger, with a custom gRPC code to log level function. + opts := []grpc_zap.Option{ + grpc_zap.WithLevels(customFunc), + } + // Make sure that log statements internal to gRPC library are logged using the zapLogger as well. + grpc_zap.ReplaceGrpcLogger(zapLogger) + // Create a server, make sure we put the grpc_ctxtags context before everything else. + server := grpc.NewServer( + grpc_middleware.WithUnaryServerChain( + grpc_ctxtags.UnaryServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)), + grpc_zap.UnaryServerInterceptor(zapLogger, opts...), + ), + grpc_middleware.WithStreamServerChain( + grpc_ctxtags.StreamServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)), + grpc_zap.StreamServerInterceptor(zapLogger, opts...), + ), + ) + return server +``` + +
+ +#### Example: + +
+Click to expand code. + +```go +opts := []grpc_zap.Option{ + grpc_zap.WithDurationField(func(duration time.Duration) zapcore.Field { + return zap.Int64("grpc.time_ns", duration.Nanoseconds()) + }), + } + + server := grpc.NewServer( + grpc_middleware.WithUnaryServerChain( + grpc_ctxtags.UnaryServerInterceptor(), + grpc_zap.UnaryServerInterceptor(zapLogger, opts...), + ), + grpc_middleware.WithStreamServerChain( + grpc_ctxtags.StreamServerInterceptor(), + grpc_zap.StreamServerInterceptor(zapLogger, opts...), + ), + ) + + return server +``` + +
+ ## Imported Packages - [github.com/golang/protobuf/jsonpb](https://godoc.org/github.com/golang/protobuf/jsonpb) diff --git a/logging/zap/doc.go b/logging/zap/doc.go index aa333d97d..c6d2169e3 100644 --- a/logging/zap/doc.go +++ b/logging/zap/doc.go @@ -10,6 +10,11 @@ be used for logging completed gRPC calls, and be populated into the `context.Con You can use `Extract` to log into a request-scoped `zap.Logger` instance in your handler code. The fields set on the logger correspond to the grpc_ctxtags.Tags attached to the context. +This package also implements request and response *payload* logging, both for server-side and client-side. These will be +logged as structured `jsonbp` fields for every message received/sent (both unary and streaming). For that please use +`Payload*Interceptor` functions for that. Please note that the user-provided function that determines whetether to log +the full request/response payload needs to be written with care, this can significantly slow down gRPC. + ZAP can also be made as a backend for gRPC library internals. For that use `ReplaceGrpcLogger`. Please see examples and tests for examples of use. diff --git a/recovery/DOC.md b/recovery/DOC.md index e34a48d59..6f391f58f 100644 --- a/recovery/DOC.md +++ b/recovery/DOC.md @@ -7,7 +7,7 @@ * [Examples](#pkg-examples) ## Overview -`grpc_recovery` conversion of panics into gRPC errors +`grpc_recovery` are intereceptors that recover from gRPC handler panics. ### Server Side Recovery Middleware By default a panic will be converted into a gRPC error with `code.Internal`. @@ -16,6 +16,31 @@ Handling can be customised by providing an alternate recovery function. Please see examples for simple examples of use. +#### Example: + +
+Click to expand code. + +```go +// Shared options for the logger, with a custom gRPC code to log level function. + opts := []grpc_recovery.Option{ + grpc_recovery.WithRecoveryHandler(customFunc), + } + // Create a server. Recovery handlers should typically be last in the chain so that other middleware + // (e.g. logging) can operate on the recovered state instead of being directly affected by any panic + server := grpc.NewServer( + grpc_middleware.WithUnaryServerChain( + grpc_recovery.UnaryServerInterceptor(opts...), + ), + grpc_middleware.WithStreamServerChain( + grpc_recovery.StreamServerInterceptor(opts...), + ), + ) + return server +``` + +
+ ## Imported Packages - [golang.org/x/net/context](https://godoc.org/golang.org/x/net/context) diff --git a/recovery/doc.go b/recovery/doc.go index d0ec6e6da..da40190c5 100644 --- a/recovery/doc.go +++ b/recovery/doc.go @@ -2,7 +2,7 @@ // See LICENSE for licensing terms. /* -`grpc_recovery` conversion of panics into gRPC errors +`grpc_recovery` are intereceptors that recover from gRPC handler panics. Server Side Recovery Middleware diff --git a/retry/DOC.md b/retry/DOC.md index cb99689f7..8a76d11f2 100644 --- a/retry/DOC.md +++ b/retry/DOC.md @@ -23,6 +23,84 @@ linear backoff with 10% jitter. Please see examples for more advanced use. +#### Example: + +
+Click to expand code. + +```go +client := pb_testproto.NewTestServiceClient(cc) + pong, err := client.Ping( + newCtx(5*time.Second), + &pb_testproto.PingRequest{}, + grpc_retry.WithMax(3), + grpc_retry.WithPerRetryTimeout(1*time.Second)) + if err != nil { + return err + } + fmt.Printf("got pong: %v", pong) + return nil +``` + +
+ +#### Example: + +
+Click to expand code. + +```go +opts := []grpc_retry.CallOption{ + grpc_retry.WithBackoff(grpc_retry.BackoffLinear(100 * time.Millisecond)), + grpc_retry.WithCodes(codes.NotFound, codes.Aborted), + } + return grpc.Dial("myservice.example.com", + grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor(opts...)), + grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(opts...)), + ) +``` + +
+ +#### Example: + +
+Click to expand code. + +```go +return grpc.Dial("myservice.example.com", + grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor()), + grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor()), + ) +``` + +
+ +#### Example: + +
+Click to expand code. + +```go +client := pb_testproto.NewTestServiceClient(cc) + stream, err := client.PingList(newCtx(1*time.Second), &pb_testproto.PingRequest{}, grpc_retry.WithMax(3)) + if err != nil { + return err + } + for { + pong, err := stream.Recv() // retries happen here + if err == io.EOF { + break + } else if err != nil { + return err + } + fmt.Printf("got pong: %v", pong) + } + return nil +``` + +
+ ## Imported Packages - [github.com/grpc-ecosystem/go-grpc-middleware/util/backoffutils](./../util/backoffutils) diff --git a/tags/DOC.md b/tags/DOC.md index b9823c642..62ec88118 100644 --- a/tags/DOC.md +++ b/tags/DOC.md @@ -7,6 +7,38 @@ * [Examples](#pkg-examples) ## Overview +`grpc_ctxtags` adds a Tag object to the context that can be used by other middleware to add context about a request. + +### Request Context Tags +Tags describe information about the request, and can be set and used by other middleware, or handlers. Tags are used +for logging and tracing of requests. Tags are populated both upwards, *and* downwards in the interceptor-handler stack. + +You can automatically extract tags (in `grpc.request.`) from request payloads (in unary and server-streaming) +by passing in the `WithFieldExtractor` option. + +If a user doesn't use the interceptors that initialize the `Tags` object, all operations following from an `Extract(ctx)` +will be no-ops. This is to ensure that code doesn't panic if the interceptors weren't used. + +Tags fields are typed, and shallow and should follow the OpenTracing semantics convention: +https://github.com/opentracing/specification/blob/master/semantic_conventions.md + +#### Example: + +
+Click to expand code. + +```go +opts := []grpc_ctxtags.Option{ + grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.TagBasedRequestFieldExtractor("log_fields")), + } + server := grpc.NewServer( + grpc.StreamInterceptor(grpc_ctxtags.StreamServerInterceptor(opts...)), + grpc.UnaryInterceptor(grpc_ctxtags.UnaryServerInterceptor(opts...)), + ) + return server +``` + +
## Imported Packages diff --git a/tags/doc.go b/tags/doc.go index 03b8cd881..8bfdb0ddc 100644 --- a/tags/doc.go +++ b/tags/doc.go @@ -2,7 +2,7 @@ // See LICENSE for licensing terms. /* -`grpc_ctxtags` adds a Tag object to the context that can be used by other middleware to add context about a request +`grpc_ctxtags` adds a Tag object to the context that can be used by other middleware to add context about a request. Request Context Tags @@ -17,7 +17,5 @@ will be no-ops. This is to ensure that code doesn't panic if the interceptors we Tags fields are typed, and shallow and should follow the OpenTracing semantics convention: https://github.com/opentracing/specification/blob/master/semantic_conventions.md - */ - package grpc_ctxtags diff --git a/tracing/opentracing/DOC.md b/tracing/opentracing/DOC.md index 09ec447cd..823b05d81 100644 --- a/tracing/opentracing/DOC.md +++ b/tracing/opentracing/DOC.md @@ -6,6 +6,20 @@ * [Index](#pkg-index) ## Overview +`grpc_opentracing` adds OpenTracing + +### OpenTracing Interceptors +These are both client-side and server-side interceptors for OpenTracing. They are a provider-agnostic, with backends +such as Zipkin, or Google Stackdriver Trace. + +For a service that sends out requests and receives requests, you *need* to use both, otherwise downstream requests will +not have the appropriate requests propagated. + +All server-side spans are tagged with grpc_ctxtags information. + +For more information see: +http://opentracing.io/documentation/ +https://github.com/opentracing/specification/blob/master/semantic_conventions.md ## Imported Packages diff --git a/tracing/opentracing/doc.go b/tracing/opentracing/doc.go index 9471059c7..7a58efc22 100644 --- a/tracing/opentracing/doc.go +++ b/tracing/opentracing/doc.go @@ -19,5 +19,4 @@ http://opentracing.io/documentation/ https://github.com/opentracing/specification/blob/master/semantic_conventions.md */ - package grpc_opentracing