Skip to content

Commit

Permalink
Simplified logging middleware; Fields are now "any" type; Moved loggi…
Browse files Browse the repository at this point in the history
…ng providers to examples only. (#552)

Fixes #517

* Simpler interface, only one method. This allows adapter to be ultra simple, so moved them to examples only (!).
* Fields are string, values are any.
* "More" slog compatibility.

I did not go for using slog natively as it's still experimental (see bigger discussion: https://gophers.slack.com/archives/C029RQSEE/p1679860885943619)

Signed-off-by: bwplotka <bwplotka@gmail.com>
  • Loading branch information
bwplotka authored Mar 30, 2023
1 parent e41e6bd commit 8c53766
Show file tree
Hide file tree
Showing 59 changed files with 1,116 additions and 4,426 deletions.
11 changes: 6 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,18 @@ lint: ## Runs various static analysis tools against our code.
lint: $(BUF) $(COPYRIGHT) fmt docs
@echo ">> lint proto files"
@$(BUF) lint


@echo ">> ensuring copyright headers"
@$(COPYRIGHT) $(shell go list -f "{{.Dir}}" ./... | xargs -i find "{}" -name "*.go")
@$(call require_clean_work_tree,"set copyright headers")
@echo ">> ensured all .go files have copyright headers"

@echo "Running lint for all modules: $(MODULES)"
@$(call require_clean_work_tree,"before lint")
for dir in $(MODULES) ; do \
$(MAKE) lint_module DIR=$${dir} ; \
done
@$(call require_clean_work_tree,"lint and format files")
@echo ">> ensuring copyright headers"
@$(COPYRIGHT) $(shell go list -f "{{.Dir}}" ./... | xargs -i find "{}" -name "*.go")
@$(call require_clean_work_tree,"set copyright headers")
@echo ">> ensured all .go files have copyright headers"

.PHONY: lint_module
# PROTIP:
Expand Down
27 changes: 10 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,23 @@ This repository offers ready-to-use middlewares that implements gRPC interceptor
Additional great feature of interceptors is the fact we can chain those. For example below you can find example server side chain of interceptors with full observabiliy correlation, auth and panic recovery:

```go mdox-exec="sed -n '116,132p' examples/server/main.go"
```go mdox-exec="sed -n '136,151p' examples/server/main.go"
grpcSrv := grpc.NewServer(
grpc.ChainUnaryInterceptor(
// Order matters e.g. tracing interceptor have to create span first for the later exemplars to work.
otelgrpc.UnaryServerInterceptor(),
srvMetrics.UnaryServerInterceptor(grpcprom.WithExemplarFromContext(exemplarFromContext)),
logging.UnaryServerInterceptor(kit.InterceptorLogger(rpcLogger), logging.WithFieldsFromContext(logTraceID)),
logging.UnaryServerInterceptor(interceptorLogger(rpcLogger), logging.WithFieldsFromContext(logTraceID)),
selector.UnaryServerInterceptor(auth.UnaryServerInterceptor(authFn), selector.MatchFunc(allButHealthZ)),
recovery.UnaryServerInterceptor(recovery.WithRecoveryHandler(grpcPanicRecoveryHandler)),
),
grpc.ChainStreamInterceptor(
otelgrpc.StreamServerInterceptor(),
srvMetrics.StreamServerInterceptor(grpcprom.WithExemplarFromContext(exemplarFromContext)),
logging.StreamServerInterceptor(kit.InterceptorLogger(rpcLogger), logging.WithFieldsFromContext(logTraceID)),
logging.StreamServerInterceptor(interceptorLogger(rpcLogger), logging.WithFieldsFromContext(logTraceID)),
selector.StreamServerInterceptor(auth.StreamServerInterceptor(authFn), selector.MatchFunc(allButHealthZ)),
recovery.StreamServerInterceptor(recovery.WithRecoveryHandler(grpcPanicRecoveryHandler)),
),
)
```

This pattern offers clean and explicit shared functionality for all your gRPC methods. Full, buildable examples can be found in [examples](examples) directory.
Expand All @@ -48,15 +47,8 @@ All paths should work with `go get <path>`.
* [`github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth`](interceptors/auth) - a customizable (via `AuthFunc`) piece of auth middleware.

#### Observability
* Metrics:
* [`github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus`](providers/prometheus) - Prometheus client-side and server-side monitoring middleware. Supports exemplars. Moved from deprecated now [`go-grpc-prometheus`](https://github.com/grpc-ecosystem/go-grpc-prometheus).
* [`github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging`](interceptors/logging) - a customizable logging middleware offering extended per request logging. It requires logging provider. Available ones:
* [`github.com/grpc-ecosystem/go-grpc-middleware/providers/logr`](providers/logr) - adapter for [logr](https://github.com/go-logr/logr).
* [`github.com/grpc-ecosystem/go-grpc-middleware/providers/logrus`](providers/logrus) - adapter for [logrus](https://github.com/sirupsen/logrus).
* [`github.com/grpc-ecosystem/go-grpc-middleware/providers/kit`](providers/kit) - adapter for [go-kit/log](https://github.com/go-kit/log).
* [`github.com/grpc-ecosystem/go-grpc-middleware/providers/phuslog`](providers/phuslog) - adapter for [phuslog](https://github.com/phuslu/log)
* [`github.com/grpc-ecosystem/go-grpc-middleware/providers/zap`](providers/zap) - adapter for [zap](https://github.com/uber-go/zap).
* [`github.com/grpc-ecosystem/go-grpc-middleware/providers/zerolog`](providers/zerolog) - adapter for [zerolog](https://github.com/rs/zerolog).
* Metrics with [`github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus`](providers/prometheus) - Prometheus client-side and server-side monitoring middleware. Supports exemplars. Moved from deprecated now [`go-grpc-prometheus`](https://github.com/grpc-ecosystem/go-grpc-prometheus). It's a separate module, so core module has limited number of dependencies.
* Logging with [`github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging`](interceptors/logging) - a customizable logging middleware offering extended per request logging. It requires logging adapter, see examples in [`interceptors/logging/examples`](interceptors/logging/examples) for `go-kit`, `log`, `logr`, `logrus`, `slog`, `zap` and `zerolog`.
* Tracing:
* (external) [`go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`](https://go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc) - official OpenTelemetry tracing interceptors as used in [example](examples).
* (external) [`github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing`](https://pkg.go.dev/github.com/grpc-ecosystem/go-grpc-middleware@v1.4.0/tracing/opentracing) - deprecated [OpenTracing](http://opentracing.io/) client-side and server-side interceptors if you still need it!
Expand All @@ -79,9 +71,9 @@ All paths should work with `go get <path>`.

## Structure of this repository

The main interceptors are available in the subdirectories of the [`interceptors` directory](interceptors) e.g. [`interceptors/validator`](interceptors/validator). Some interceptors work as a standalone piece of middleware with extra parameters e.g. [`auth`](interceptors/auth). Some, like [`interceptors/logging`](interceptors/logging) benefit from adapters maintained as a *separate Go Modules* in [`providers`](providers) directory. For example, there is an adapter for `go-kit/log` logger in [`providers/kit`](providers/kit) directory that works with logging e.g. `logging.StreamServerInterceptor(kit.InterceptorLogger(rpcLogger))`. The separate module, might be a little bit of the initial pain to discover and version in your `go.mod`, but it allows core interceptors to be ultra slim in terms of dependencies.
The main interceptors are available in the subdirectories of the [`interceptors` directory](interceptors) e.g. [`interceptors/validator`](interceptors/validator), [`interceptors/auth`](interceptors/auth) or [`interceptors/logging`](interceptors/logging).

Some providers are standalone interceptors too. For example [`providers/prometheus`](providers/prometheus) offer metrics middleware (there is no "interceptor/metrics" at the moment).
Some interceptors or utilities of interceptors requires opinionated code that depends on larger amount of dependencies. Those are places in `providers` directory as separate Go module, with separate versioning. For example [`providers/prometheus`](providers/prometheus) offer metrics middleware (there is no "interceptor/metrics" at the moment). The separate module, might be a little bit harder to discover and version in your `go.mod`, but it allows core interceptors to be ultra slim in terms of dependencies.

The [`interceptors` directory](interceptors) also holds generic interceptors that accepts [`Reporter`](interceptors/reporter.go) interface which allows creating your own middlewares with ease.

Expand All @@ -101,13 +93,14 @@ go get github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus

[go-grpc-middleware v1](https://pkg.go.dev/github.com/grpc-ecosystem/go-grpc-middleware) was created near 2015 and became a popular choice for gRPC users. However, many have changed since then. The main changes of v2 compared to v1:

* Multiple, separate Go modules in "providers" e.g. loggers, so interceptors can be extended without the dependency hell to the core framework (e.g. if use go-kit logger, do you really want to import logrus?). This allows greater extensibility.
* Path for separate, multiple Go modules in "providers". This allows to add in future specific providers for certain middlewares if needed. This allows interceptors to be extended without the dependency hell to the core framework (e.g. if use some other metric provider, do you want to import prometheus?). This allows greater extensibility.
* Loggers are removed. The [`interceptors/logging`](interceptors/logging) got simplified and writing adapter for each logger is straightforward. For convenience, we will maintain examples for popular providers in [`interceptors/logging/examples`](interceptors/logging/examples), but those are meant to be copied, not imported.
* `grpc_opentracing` interceptor was removed. This is because tracing instrumentation evolved. OpenTracing is deprecated and OpenTelemetry has now a [superior tracing interceptor](https://go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc).
* `grpc_ctxtags` interceptor was removed. Custom tags can be added to logging fields using `logging.InjectFields`. Proto option to add logging field was clunky in practice and we don't see any use of it nowadays, so it's removed.
* One of the most powerful interceptor was imported from https://github.com/grpc-ecosystem/go-grpc-prometheus (repo is now deprecated). This consolidation allows easier maintenance, easier use and consistent API.
* Chain interceptors was removed, because `grpc` implemented one.
* Moved to the new proto API (google.golang.org/protobuf).
* All "deciders", so functions that decide what to do based on gRPC service name and method (aka "fullMethodName") now use typed ["interceptors.CallMeta"](interceptors/callmeta.go) allowing better context and explicitness.
* All "deciders", so functions that decide what to do based on gRPC service name and method (aka "fullMethodName") are removed (!). Use [`github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/selector`](interceptors/selector) interceptor to select what method, type or service should use what interceptor.
* No more snake case package names. We have now single word meaningful package names. If you have collision in package names we recommend adding grpc prefix e.g. `grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus"`.
* All the options (if any) are in the form of `<package_name>.With<Option Name>`, with extensibility to add more of them.
* `v2` is the main (default) development branch.
Expand Down
25 changes: 22 additions & 3 deletions examples/client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ package main

import (
"context"
"fmt"
"net/http"
"os"
"syscall"
"time"

"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/grpc-ecosystem/go-grpc-middleware/providers/kit"
grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/timeout"
Expand All @@ -38,6 +38,25 @@ const (
targetGRPCAddr = "localhost:8080"
)

// interceptorLogger adapts go-kit logger to interceptor logger.
// This code is simple enough to be copied and not imported.
func interceptorLogger(l log.Logger) logging.Logger {
return logging.LoggerFunc(func(_ context.Context, lvl logging.Level, msg string, fields ...any) {
largs := append([]any{"msg", msg}, fields...)
switch lvl {
case logging.LevelDebug:
_ = level.Debug(l).Log(largs...)
case logging.LevelInfo:
_ = level.Info(l).Log(largs...)
case logging.LevelWarn:
_ = level.Warn(l).Log(largs...)
case logging.LevelError:
_ = level.Error(l).Log(largs...)
default:
panic(fmt.Sprintf("unknown level %v", lvl))
}
})
}
func main() {
// Setup logging.
logger := log.NewLogfmtLogger(os.Stderr)
Expand Down Expand Up @@ -85,11 +104,11 @@ func main() {
timeout.UnaryClientInterceptor(500*time.Millisecond),
otelgrpc.UnaryClientInterceptor(),
clMetrics.UnaryClientInterceptor(grpcprom.WithExemplarFromContext(exemplarFromContext)),
logging.UnaryClientInterceptor(kit.InterceptorLogger(rpcLogger), logging.WithFieldsFromContext(logTraceID))),
logging.UnaryClientInterceptor(interceptorLogger(rpcLogger), logging.WithFieldsFromContext(logTraceID))),
grpc.WithChainStreamInterceptor(
otelgrpc.StreamClientInterceptor(),
clMetrics.StreamClientInterceptor(grpcprom.WithExemplarFromContext(exemplarFromContext)),
logging.StreamClientInterceptor(kit.InterceptorLogger(rpcLogger), logging.WithFieldsFromContext(logTraceID))),
logging.StreamClientInterceptor(interceptorLogger(rpcLogger), logging.WithFieldsFromContext(logTraceID))),
)
if err != nil {
level.Error(logger).Log("err", err)
Expand Down
2 changes: 0 additions & 2 deletions examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ go 1.19

require (
github.com/go-kit/log v0.2.1
github.com/grpc-ecosystem/go-grpc-middleware/providers/kit v1.0.0
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.0
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0
github.com/oklog/run v1.1.0
Expand Down Expand Up @@ -44,7 +43,6 @@ require (
)

replace (
github.com/grpc-ecosystem/go-grpc-middleware/providers/kit => ../providers/kit
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus => ../providers/prometheus
github.com/grpc-ecosystem/go-grpc-middleware/v2 => ../
)
26 changes: 23 additions & 3 deletions examples/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package main

import (
"context"
"fmt"
"net"
"net/http"
"os"
Expand All @@ -13,7 +14,6 @@ import (

"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/grpc-ecosystem/go-grpc-middleware/providers/kit"
grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
Expand Down Expand Up @@ -43,6 +43,26 @@ const (
httpAddr = ":8081"
)

// interceptorLogger adapts go-kit logger to interceptor logger.
// This code is simple enough to be copied and not imported.
func interceptorLogger(l log.Logger) logging.Logger {
return logging.LoggerFunc(func(_ context.Context, lvl logging.Level, msg string, fields ...any) {
largs := append([]any{"msg", msg}, fields...)
switch lvl {
case logging.LevelDebug:
_ = level.Debug(l).Log(largs...)
case logging.LevelInfo:
_ = level.Info(l).Log(largs...)
case logging.LevelWarn:
_ = level.Warn(l).Log(largs...)
case logging.LevelError:
_ = level.Error(l).Log(largs...)
default:
panic(fmt.Sprintf("unknown level %v", lvl))
}
})
}

func main() {
// Setup logging.
logger := log.NewLogfmtLogger(os.Stderr)
Expand Down Expand Up @@ -118,14 +138,14 @@ func main() {
// Order matters e.g. tracing interceptor have to create span first for the later exemplars to work.
otelgrpc.UnaryServerInterceptor(),
srvMetrics.UnaryServerInterceptor(grpcprom.WithExemplarFromContext(exemplarFromContext)),
logging.UnaryServerInterceptor(kit.InterceptorLogger(rpcLogger), logging.WithFieldsFromContext(logTraceID)),
logging.UnaryServerInterceptor(interceptorLogger(rpcLogger), logging.WithFieldsFromContext(logTraceID)),
selector.UnaryServerInterceptor(auth.UnaryServerInterceptor(authFn), selector.MatchFunc(allButHealthZ)),
recovery.UnaryServerInterceptor(recovery.WithRecoveryHandler(grpcPanicRecoveryHandler)),
),
grpc.ChainStreamInterceptor(
otelgrpc.StreamServerInterceptor(),
srvMetrics.StreamServerInterceptor(grpcprom.WithExemplarFromContext(exemplarFromContext)),
logging.StreamServerInterceptor(kit.InterceptorLogger(rpcLogger), logging.WithFieldsFromContext(logTraceID)),
logging.StreamServerInterceptor(interceptorLogger(rpcLogger), logging.WithFieldsFromContext(logTraceID)),
selector.StreamServerInterceptor(auth.StreamServerInterceptor(authFn), selector.MatchFunc(allButHealthZ)),
recovery.StreamServerInterceptor(recovery.WithRecoveryHandler(grpcPanicRecoveryHandler)),
),
Expand Down
7 changes: 4 additions & 3 deletions interceptors/logging/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ https://github.com/opentracing/specification/blob/master/semantic_conventions.md
Implementations:
* providers/kit
* providers/logr
* providers/logrus
* providers/phuslog
* providers/slog
* providers/zap
* providers/kit
* providers/zerolog
* providers/phuslog
* providers/logr
*/
package logging
32 changes: 32 additions & 0 deletions interceptors/logging/examples/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module github.com/grpc-ecosystem/go-grpc-middleware/interceptors/logging/examples

go 1.19

require (
github.com/go-kit/log v0.2.1
github.com/go-logr/logr v1.2.4
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0
github.com/phuslu/log v1.0.83
github.com/rs/zerolog v1.29.0
github.com/sirupsen/logrus v1.9.0
go.uber.org/zap v1.24.0
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
google.golang.org/grpc v1.53.0
k8s.io/klog/v2 v2.90.1
)

require (
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/protobuf v1.30.0 // indirect
)

replace github.com/grpc-ecosystem/go-grpc-middleware/v2 => ../../../
Loading

0 comments on commit 8c53766

Please sign in to comment.