mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
Adding a way to inject a request ID (#1046)
* Adding a way to inject a request ID It is very useful to associate a request ID to each incoming request, this change allows to provide a function to do that via Server Option. The change comes with a default function which will generate a new request ID. The request ID is put in the request context along with a common logger which always logs the request-id We add gRPC interceptors to the server so it can get the request ID out of the gRPC metadata and put it in the common logger stored in the context so as all the log lines using the common logger from the context will have the request ID logged
This commit is contained in:
391
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/DOC.md
generated
vendored
Normal file
391
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/DOC.md
generated
vendored
Normal file
@@ -0,0 +1,391 @@
|
||||
# grpc_logrus
|
||||
`import "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"`
|
||||
|
||||
* [Overview](#pkg-overview)
|
||||
* [Imported Packages](#pkg-imports)
|
||||
* [Index](#pkg-index)
|
||||
* [Examples](#pkg-examples)
|
||||
|
||||
## <a name="pkg-overview">Overview</a>
|
||||
`grpc_logrus` is a gRPC logging middleware backed by Logrus loggers
|
||||
|
||||
It accepts a user-configured `logrus.Entry` that will be used for logging completed gRPC calls. The same
|
||||
`logrus.Entry` will be used for logging completed gRPC calls, and be populated into the `context.Context` passed into gRPC handler code.
|
||||
|
||||
On calling `StreamServerInterceptor` or `UnaryServerInterceptor` this logging middleware will add gRPC call information
|
||||
to the ctx so that it will be present on subsequent use of the `ctxlogrus` logger.
|
||||
|
||||
This package also implements request and response *payload* logging, both for server-side and client-side. These will be
|
||||
logged as structured `jsonpb` 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.
|
||||
|
||||
If a deadline is present on the gRPC request the grpc.request.deadline tag is populated when the request begins. grpc.request.deadline
|
||||
is a string representing the time (RFC3339) when the current call will expire.
|
||||
|
||||
Logrus can also be made as a backend for gRPC library internals. For that use `ReplaceGrpcLogger`.
|
||||
|
||||
*Server Interceptor*
|
||||
Below is a JSON formatted example of a log that would be logged by the server interceptor:
|
||||
|
||||
{
|
||||
"level": "info", // string logrus log levels
|
||||
"msg": "finished unary call", // string log message
|
||||
"grpc.code": "OK", // string grpc status code
|
||||
"grpc.method": "Ping", // string method name
|
||||
"grpc.service": "mwitkow.testproto.TestService", // string full name of the called service
|
||||
"grpc.start_time": "2006-01-02T15:04:05Z07:00", // string RFC3339 representation of the start time
|
||||
"grpc.request.deadline": "2006-01-02T15:04:05Z07:00", // string RFC3339 deadline of the current request if supplied
|
||||
"grpc.request.value": "something", // string value on the request
|
||||
"grpc.time_ms": 1.234, // float32 run time of the call in ms
|
||||
"peer.address": {
|
||||
"IP": "127.0.0.1", // string IP address of calling party
|
||||
"Port": 60216, // int port call is coming in on
|
||||
"Zone": "" // string peer zone for caller
|
||||
},
|
||||
"span.kind": "server", // string client | server
|
||||
"system": "grpc" // string
|
||||
|
||||
"custom_field": "custom_value", // string user defined field
|
||||
"custom_tags.int": 1337, // int user defined tag on the ctx
|
||||
"custom_tags.string": "something", // string user defined tag on the ctx
|
||||
}
|
||||
|
||||
*Payload Interceptor*
|
||||
Below is a JSON formatted example of a log that would be logged by the payload interceptor:
|
||||
|
||||
{
|
||||
"level": "info", // string logrus log levels
|
||||
"msg": "client request payload logged as grpc.request.content", // string log message
|
||||
|
||||
"grpc.request.content": { // object content of RPC request
|
||||
"value": "something", // string defined by caller
|
||||
"sleepTimeMs": 9999 // int defined by caller
|
||||
},
|
||||
"grpc.method": "Ping", // string method being called
|
||||
"grpc.service": "mwitkow.testproto.TestService", // string service being called
|
||||
"span.kind": "client", // string client | server
|
||||
"system": "grpc" // string
|
||||
}
|
||||
|
||||
Note - due to implementation ZAP differs from Logrus in the "grpc.request.content" object by having an inner "msg" object.
|
||||
|
||||
Please see examples and tests for examples of use.
|
||||
|
||||
#### Example:
|
||||
|
||||
<details>
|
||||
<summary>Click to expand code.</summary>
|
||||
|
||||
```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 logrus Logger as well.
|
||||
grpc_logrus.ReplaceGrpcLogger(logrusEntry)
|
||||
// Create a server, make sure we put the grpc_ctxtags context before everything else.
|
||||
_ = 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...),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### Example:
|
||||
|
||||
<details>
|
||||
<summary>Click to expand code.</summary>
|
||||
|
||||
```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()
|
||||
}),
|
||||
}
|
||||
_ = grpc.NewServer(
|
||||
grpc_middleware.WithUnaryServerChain(
|
||||
grpc_ctxtags.UnaryServerInterceptor(),
|
||||
grpc_logrus.UnaryServerInterceptor(logrusEntry, opts...),
|
||||
),
|
||||
grpc_middleware.WithStreamServerChain(
|
||||
grpc_ctxtags.StreamServerInterceptor(),
|
||||
grpc_logrus.StreamServerInterceptor(logrusEntry, opts...),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## <a name="pkg-imports">Imported Packages</a>
|
||||
|
||||
- [github.com/golang/protobuf/jsonpb](https://godoc.org/github.com/golang/protobuf/jsonpb)
|
||||
- [github.com/golang/protobuf/proto](https://godoc.org/github.com/golang/protobuf/proto)
|
||||
- [github.com/grpc-ecosystem/go-grpc-middleware](./../..)
|
||||
- [github.com/grpc-ecosystem/go-grpc-middleware/logging](./..)
|
||||
- [github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus](./ctxlogrus)
|
||||
- [github.com/grpc-ecosystem/go-grpc-middleware/tags/logrus](./../../tags/logrus)
|
||||
- [github.com/sirupsen/logrus](https://godoc.org/github.com/sirupsen/logrus)
|
||||
- [golang.org/x/net/context](https://godoc.org/golang.org/x/net/context)
|
||||
- [google.golang.org/grpc](https://godoc.org/google.golang.org/grpc)
|
||||
- [google.golang.org/grpc/codes](https://godoc.org/google.golang.org/grpc/codes)
|
||||
- [google.golang.org/grpc/grpclog](https://godoc.org/google.golang.org/grpc/grpclog)
|
||||
|
||||
## <a name="pkg-index">Index</a>
|
||||
* [Variables](#pkg-variables)
|
||||
* [func AddFields(ctx context.Context, fields logrus.Fields)](#AddFields)
|
||||
* [func DefaultClientCodeToLevel(code codes.Code) logrus.Level](#DefaultClientCodeToLevel)
|
||||
* [func DefaultCodeToLevel(code codes.Code) logrus.Level](#DefaultCodeToLevel)
|
||||
* [func DurationToDurationField(duration time.Duration) (key string, value interface{})](#DurationToDurationField)
|
||||
* [func DurationToTimeMillisField(duration time.Duration) (key string, value interface{})](#DurationToTimeMillisField)
|
||||
* [func Extract(ctx context.Context) \*logrus.Entry](#Extract)
|
||||
* [func PayloadStreamClientInterceptor(entry \*logrus.Entry, decider grpc\_logging.ClientPayloadLoggingDecider) grpc.StreamClientInterceptor](#PayloadStreamClientInterceptor)
|
||||
* [func PayloadStreamServerInterceptor(entry \*logrus.Entry, decider grpc\_logging.ServerPayloadLoggingDecider) grpc.StreamServerInterceptor](#PayloadStreamServerInterceptor)
|
||||
* [func PayloadUnaryClientInterceptor(entry \*logrus.Entry, decider grpc\_logging.ClientPayloadLoggingDecider) grpc.UnaryClientInterceptor](#PayloadUnaryClientInterceptor)
|
||||
* [func PayloadUnaryServerInterceptor(entry \*logrus.Entry, decider grpc\_logging.ServerPayloadLoggingDecider) grpc.UnaryServerInterceptor](#PayloadUnaryServerInterceptor)
|
||||
* [func ReplaceGrpcLogger(logger \*logrus.Entry)](#ReplaceGrpcLogger)
|
||||
* [func StreamClientInterceptor(entry \*logrus.Entry, opts ...Option) grpc.StreamClientInterceptor](#StreamClientInterceptor)
|
||||
* [func StreamServerInterceptor(entry \*logrus.Entry, opts ...Option) grpc.StreamServerInterceptor](#StreamServerInterceptor)
|
||||
* [func UnaryClientInterceptor(entry \*logrus.Entry, opts ...Option) grpc.UnaryClientInterceptor](#UnaryClientInterceptor)
|
||||
* [func UnaryServerInterceptor(entry \*logrus.Entry, opts ...Option) grpc.UnaryServerInterceptor](#UnaryServerInterceptor)
|
||||
* [type CodeToLevel](#CodeToLevel)
|
||||
* [type DurationToField](#DurationToField)
|
||||
* [type Option](#Option)
|
||||
* [func WithCodes(f grpc\_logging.ErrorToCode) Option](#WithCodes)
|
||||
* [func WithDecider(f grpc\_logging.Decider) Option](#WithDecider)
|
||||
* [func WithDurationField(f DurationToField) Option](#WithDurationField)
|
||||
* [func WithLevels(f CodeToLevel) Option](#WithLevels)
|
||||
|
||||
#### <a name="pkg-examples">Examples</a>
|
||||
* [Extract (Unary)](#example_Extract_unary)
|
||||
* [WithDecider](#example_WithDecider)
|
||||
* [Package (Initialization)](#example__initialization)
|
||||
* [Package (InitializationWithDurationFieldOverride)](#example__initializationWithDurationFieldOverride)
|
||||
|
||||
#### <a name="pkg-files">Package files</a>
|
||||
[client_interceptors.go](./client_interceptors.go) [context.go](./context.go) [doc.go](./doc.go) [grpclogger.go](./grpclogger.go) [options.go](./options.go) [payload_interceptors.go](./payload_interceptors.go) [server_interceptors.go](./server_interceptors.go)
|
||||
|
||||
## <a name="pkg-variables">Variables</a>
|
||||
``` go
|
||||
var (
|
||||
// SystemField is used in every log statement made through grpc_logrus. Can be overwritten before any initialization code.
|
||||
SystemField = "system"
|
||||
|
||||
// KindField describes the log gield used to incicate whether this is a server or a client log statment.
|
||||
KindField = "span.kind"
|
||||
)
|
||||
```
|
||||
``` go
|
||||
var DefaultDurationToField = DurationToTimeMillisField
|
||||
```
|
||||
DefaultDurationToField is the default implementation of converting request duration to a log field (key and value).
|
||||
|
||||
``` go
|
||||
var (
|
||||
// JsonPbMarshaller is the marshaller used for serializing protobuf messages.
|
||||
JsonPbMarshaller = &jsonpb.Marshaler{}
|
||||
)
|
||||
```
|
||||
|
||||
## <a name="AddFields">func</a> [AddFields](./context.go#L11)
|
||||
``` go
|
||||
func AddFields(ctx context.Context, fields logrus.Fields)
|
||||
```
|
||||
AddFields adds logrus fields to the logger.
|
||||
Deprecated: should use the ctxlogrus.Extract instead
|
||||
|
||||
## <a name="DefaultClientCodeToLevel">func</a> [DefaultClientCodeToLevel](./options.go#L129)
|
||||
``` go
|
||||
func DefaultClientCodeToLevel(code codes.Code) logrus.Level
|
||||
```
|
||||
DefaultClientCodeToLevel is the default implementation of gRPC return codes to log levels for client side.
|
||||
|
||||
## <a name="DefaultCodeToLevel">func</a> [DefaultCodeToLevel](./options.go#L87)
|
||||
``` go
|
||||
func DefaultCodeToLevel(code codes.Code) logrus.Level
|
||||
```
|
||||
DefaultCodeToLevel is the default implementation of gRPC return codes to log levels for server side.
|
||||
|
||||
## <a name="DurationToDurationField">func</a> [DurationToDurationField](./options.go#L179)
|
||||
``` go
|
||||
func DurationToDurationField(duration time.Duration) (key string, value interface{})
|
||||
```
|
||||
DurationToDurationField uses the duration value to log the request duration.
|
||||
|
||||
## <a name="DurationToTimeMillisField">func</a> [DurationToTimeMillisField](./options.go#L174)
|
||||
``` go
|
||||
func DurationToTimeMillisField(duration time.Duration) (key string, value interface{})
|
||||
```
|
||||
DurationToTimeMillisField converts the duration to milliseconds and uses the key `grpc.time_ms`.
|
||||
|
||||
## <a name="Extract">func</a> [Extract](./context.go#L17)
|
||||
``` go
|
||||
func Extract(ctx context.Context) *logrus.Entry
|
||||
```
|
||||
Extract takes the call-scoped logrus.Entry from grpc_logrus middleware.
|
||||
Deprecated: should use the ctxlogrus.Extract instead
|
||||
|
||||
#### Example:
|
||||
|
||||
<details>
|
||||
<summary>Click to expand code.</summary>
|
||||
|
||||
```go
|
||||
_ = 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 single request-scoped logrus.Logger and log messages.
|
||||
l := ctx_logrus.Extract(ctx)
|
||||
l.Info("some ping")
|
||||
l.Info("another ping")
|
||||
return &pb_testproto.PingResponse{Value: ping.Value}, nil
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## <a name="PayloadStreamClientInterceptor">func</a> [PayloadStreamClientInterceptor](./payload_interceptors.go#L74)
|
||||
``` go
|
||||
func PayloadStreamClientInterceptor(entry *logrus.Entry, decider grpc_logging.ClientPayloadLoggingDecider) grpc.StreamClientInterceptor
|
||||
```
|
||||
PayloadStreamClientInterceptor returns a new streaming client interceptor that logs the paylods of requests and responses.
|
||||
|
||||
## <a name="PayloadStreamServerInterceptor">func</a> [PayloadStreamServerInterceptor](./payload_interceptors.go#L45)
|
||||
``` go
|
||||
func PayloadStreamServerInterceptor(entry *logrus.Entry, decider grpc_logging.ServerPayloadLoggingDecider) grpc.StreamServerInterceptor
|
||||
```
|
||||
PayloadStreamServerInterceptor returns a new server server interceptors that logs the payloads of requests.
|
||||
|
||||
This *only* works when placed *after* the `grpc_logrus.StreamServerInterceptor`. However, the logging can be done to a
|
||||
separate instance of the logger.
|
||||
|
||||
## <a name="PayloadUnaryClientInterceptor">func</a> [PayloadUnaryClientInterceptor](./payload_interceptors.go#L58)
|
||||
``` go
|
||||
func PayloadUnaryClientInterceptor(entry *logrus.Entry, decider grpc_logging.ClientPayloadLoggingDecider) grpc.UnaryClientInterceptor
|
||||
```
|
||||
PayloadUnaryClientInterceptor returns a new unary client interceptor that logs the paylods of requests and responses.
|
||||
|
||||
## <a name="PayloadUnaryServerInterceptor">func</a> [PayloadUnaryServerInterceptor](./payload_interceptors.go#L25)
|
||||
``` go
|
||||
func PayloadUnaryServerInterceptor(entry *logrus.Entry, decider grpc_logging.ServerPayloadLoggingDecider) grpc.UnaryServerInterceptor
|
||||
```
|
||||
PayloadUnaryServerInterceptor returns a new unary server interceptors that logs the payloads of requests.
|
||||
|
||||
This *only* works when placed *after* the `grpc_logrus.UnaryServerInterceptor`. However, the logging can be done to a
|
||||
separate instance of the logger.
|
||||
|
||||
## <a name="ReplaceGrpcLogger">func</a> [ReplaceGrpcLogger](./grpclogger.go#L13)
|
||||
``` go
|
||||
func ReplaceGrpcLogger(logger *logrus.Entry)
|
||||
```
|
||||
ReplaceGrpcLogger sets the given logrus.Logger as a gRPC-level logger.
|
||||
This should be called *before* any other initialization, preferably from init() functions.
|
||||
|
||||
## <a name="StreamClientInterceptor">func</a> [StreamClientInterceptor](./client_interceptors.go#L28)
|
||||
``` go
|
||||
func StreamClientInterceptor(entry *logrus.Entry, opts ...Option) grpc.StreamClientInterceptor
|
||||
```
|
||||
StreamServerInterceptor returns a new streaming client interceptor that optionally logs the execution of external gRPC calls.
|
||||
|
||||
## <a name="StreamServerInterceptor">func</a> [StreamServerInterceptor](./server_interceptors.go#L58)
|
||||
``` go
|
||||
func StreamServerInterceptor(entry *logrus.Entry, opts ...Option) grpc.StreamServerInterceptor
|
||||
```
|
||||
StreamServerInterceptor returns a new streaming server interceptor that adds logrus.Entry to the context.
|
||||
|
||||
## <a name="UnaryClientInterceptor">func</a> [UnaryClientInterceptor](./client_interceptors.go#L16)
|
||||
``` go
|
||||
func UnaryClientInterceptor(entry *logrus.Entry, opts ...Option) grpc.UnaryClientInterceptor
|
||||
```
|
||||
UnaryClientInterceptor returns a new unary client interceptor that optionally logs the execution of external gRPC calls.
|
||||
|
||||
## <a name="UnaryServerInterceptor">func</a> [UnaryServerInterceptor](./server_interceptors.go#L26)
|
||||
``` go
|
||||
func UnaryServerInterceptor(entry *logrus.Entry, opts ...Option) grpc.UnaryServerInterceptor
|
||||
```
|
||||
UnaryServerInterceptor returns a new unary server interceptors that adds logrus.Entry to the context.
|
||||
|
||||
## <a name="CodeToLevel">type</a> [CodeToLevel](./options.go#L53)
|
||||
``` go
|
||||
type CodeToLevel func(code codes.Code) logrus.Level
|
||||
```
|
||||
CodeToLevel function defines the mapping between gRPC return codes and interceptor log level.
|
||||
|
||||
## <a name="DurationToField">type</a> [DurationToField](./options.go#L56)
|
||||
``` go
|
||||
type DurationToField func(duration time.Duration) (key string, value interface{})
|
||||
```
|
||||
DurationToField function defines how to produce duration fields for logging
|
||||
|
||||
## <a name="Option">type</a> [Option](./options.go#L50)
|
||||
``` go
|
||||
type Option func(*options)
|
||||
```
|
||||
|
||||
### <a name="WithCodes">func</a> [WithCodes](./options.go#L73)
|
||||
``` go
|
||||
func WithCodes(f grpc_logging.ErrorToCode) Option
|
||||
```
|
||||
WithCodes customizes the function for mapping errors to error codes.
|
||||
|
||||
### <a name="WithDecider">func</a> [WithDecider](./options.go#L59)
|
||||
``` go
|
||||
func WithDecider(f grpc_logging.Decider) Option
|
||||
```
|
||||
WithDecider customizes the function for deciding if the gRPC interceptor logs should log.
|
||||
|
||||
#### Example:
|
||||
|
||||
<details>
|
||||
<summary>Click to expand code.</summary>
|
||||
|
||||
```go
|
||||
opts := []grpc_logrus.Option{
|
||||
grpc_logrus.WithDecider(func(methodFullName string, err error) bool {
|
||||
// will not log gRPC calls if it was a call to healthcheck and no error was raised
|
||||
if err == nil && methodFullName == "blah.foo.healthcheck" {
|
||||
return false
|
||||
}
|
||||
|
||||
// by default you will log all calls
|
||||
return true
|
||||
}),
|
||||
}
|
||||
|
||||
_ = []grpc.ServerOption{
|
||||
grpc_middleware.WithStreamServerChain(
|
||||
grpc_ctxtags.StreamServerInterceptor(),
|
||||
grpc_logrus.StreamServerInterceptor(logrus.NewEntry(logrus.New()), opts...)),
|
||||
grpc_middleware.WithUnaryServerChain(
|
||||
grpc_ctxtags.UnaryServerInterceptor(),
|
||||
grpc_logrus.UnaryServerInterceptor(logrus.NewEntry(logrus.New()), opts...)),
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
### <a name="WithDurationField">func</a> [WithDurationField](./options.go#L80)
|
||||
``` go
|
||||
func WithDurationField(f DurationToField) Option
|
||||
```
|
||||
WithDurationField customizes the function for mapping request durations to log fields.
|
||||
|
||||
### <a name="WithLevels">func</a> [WithLevels](./options.go#L66)
|
||||
``` go
|
||||
func WithLevels(f CodeToLevel) Option
|
||||
```
|
||||
WithLevels customizes the function for mapping gRPC return codes and interceptor log level statements.
|
||||
|
||||
- - -
|
||||
Generated by [godoc2ghmd](https://github.com/GandalfUK/godoc2ghmd)
|
||||
1
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/README.md
generated
vendored
Symbolic link
1
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/README.md
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
DOC.md
|
||||
65
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/client_interceptors.go
generated
vendored
Normal file
65
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/client_interceptors.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2017 Michal Witkowski. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
package grpc_logrus
|
||||
|
||||
import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// UnaryClientInterceptor returns a new unary client interceptor that optionally logs the execution of external gRPC calls.
|
||||
func UnaryClientInterceptor(entry *logrus.Entry, opts ...Option) grpc.UnaryClientInterceptor {
|
||||
o := evaluateClientOpt(opts)
|
||||
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||
fields := newClientLoggerFields(ctx, method)
|
||||
startTime := time.Now()
|
||||
err := invoker(ctx, method, req, reply, cc, opts...)
|
||||
logFinalClientLine(o, entry.WithFields(fields), startTime, err, "finished client unary call")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// StreamServerInterceptor returns a new streaming client interceptor that optionally logs the execution of external gRPC calls.
|
||||
func StreamClientInterceptor(entry *logrus.Entry, opts ...Option) grpc.StreamClientInterceptor {
|
||||
o := evaluateClientOpt(opts)
|
||||
return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
fields := newClientLoggerFields(ctx, method)
|
||||
startTime := time.Now()
|
||||
clientStream, err := streamer(ctx, desc, cc, method, opts...)
|
||||
logFinalClientLine(o, entry.WithFields(fields), startTime, err, "finished client streaming call")
|
||||
return clientStream, err
|
||||
}
|
||||
}
|
||||
|
||||
func logFinalClientLine(o *options, entry *logrus.Entry, startTime time.Time, err error, msg string) {
|
||||
code := o.codeFunc(err)
|
||||
level := o.levelFunc(code)
|
||||
durField, durVal := o.durationFunc(time.Now().Sub(startTime))
|
||||
fields := logrus.Fields{
|
||||
"grpc.code": code.String(),
|
||||
durField: durVal,
|
||||
}
|
||||
if err != nil {
|
||||
fields[logrus.ErrorKey] = err
|
||||
}
|
||||
levelLogf(
|
||||
entry.WithFields(fields),
|
||||
level,
|
||||
msg)
|
||||
}
|
||||
|
||||
func newClientLoggerFields(ctx context.Context, fullMethodString string) logrus.Fields {
|
||||
service := path.Dir(fullMethodString)[1:]
|
||||
method := path.Base(fullMethodString)
|
||||
return logrus.Fields{
|
||||
SystemField: "grpc",
|
||||
KindField: "client",
|
||||
"grpc.service": service,
|
||||
"grpc.method": method,
|
||||
}
|
||||
}
|
||||
189
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/client_interceptors_test.go
generated
vendored
Normal file
189
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/client_interceptors_test.go
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
package grpc_logrus_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
pb_testproto "github.com/grpc-ecosystem/go-grpc-middleware/testing/testproto"
|
||||
)
|
||||
|
||||
func customClientCodeToLevel(c codes.Code) logrus.Level {
|
||||
if c == codes.Unauthenticated {
|
||||
// Make this a special case for tests, and an error.
|
||||
return logrus.ErrorLevel
|
||||
}
|
||||
level := grpc_logrus.DefaultClientCodeToLevel(c)
|
||||
return level
|
||||
}
|
||||
|
||||
func TestLogrusClientSuite(t *testing.T) {
|
||||
if strings.HasPrefix(runtime.Version(), "go1.7") {
|
||||
t.Skipf("Skipping due to json.RawMessage incompatibility with go1.7")
|
||||
return
|
||||
}
|
||||
opts := []grpc_logrus.Option{
|
||||
grpc_logrus.WithLevels(customClientCodeToLevel),
|
||||
}
|
||||
b := newLogrusBaseSuite(t)
|
||||
b.logger.Level = logrus.DebugLevel // a lot of our stuff is on debug level by default
|
||||
b.InterceptorTestSuite.ClientOpts = []grpc.DialOption{
|
||||
grpc.WithUnaryInterceptor(grpc_logrus.UnaryClientInterceptor(logrus.NewEntry(b.logger), opts...)),
|
||||
grpc.WithStreamInterceptor(grpc_logrus.StreamClientInterceptor(logrus.NewEntry(b.logger), opts...)),
|
||||
}
|
||||
suite.Run(t, &logrusClientSuite{b})
|
||||
}
|
||||
|
||||
type logrusClientSuite struct {
|
||||
*logrusBaseSuite
|
||||
}
|
||||
|
||||
func (s *logrusClientSuite) TestPing() {
|
||||
_, err := s.Client.Ping(s.SimpleCtx(), goodPing)
|
||||
assert.NoError(s.T(), err, "there must be not be an on a successful call")
|
||||
|
||||
msgs := s.getOutputJSONs()
|
||||
require.Len(s.T(), msgs, 1, "one log statement should be logged")
|
||||
|
||||
assert.Equal(s.T(), msgs[0]["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain the correct service name")
|
||||
assert.Equal(s.T(), msgs[0]["grpc.method"], "Ping", "all lines must contain the correct method name")
|
||||
assert.Equal(s.T(), msgs[0]["msg"], "finished client unary call", "handler's message must contain the correct message")
|
||||
assert.Equal(s.T(), msgs[0]["span.kind"], "client", "all lines must contain the kind of call (client)")
|
||||
assert.Equal(s.T(), msgs[0]["level"], "debug", "OK codes must be logged on debug level.")
|
||||
|
||||
assert.Contains(s.T(), msgs[0], "grpc.time_ms", "interceptor log statement should contain execution time (duration in ms)")
|
||||
}
|
||||
|
||||
func (s *logrusClientSuite) TestPingList() {
|
||||
stream, err := s.Client.PingList(s.SimpleCtx(), goodPing)
|
||||
require.NoError(s.T(), err, "should not fail on establishing the stream")
|
||||
for {
|
||||
_, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
require.NoError(s.T(), err, "reading stream should not fail")
|
||||
}
|
||||
|
||||
msgs := s.getOutputJSONs()
|
||||
require.Len(s.T(), msgs, 1, "one log statement should be logged")
|
||||
|
||||
assert.Equal(s.T(), msgs[0]["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain the correct service name")
|
||||
assert.Equal(s.T(), msgs[0]["grpc.method"], "PingList", "all lines must contain the correct method name")
|
||||
assert.Equal(s.T(), msgs[0]["msg"], "finished client streaming call", "handler's message must contain the correct message")
|
||||
assert.Equal(s.T(), msgs[0]["span.kind"], "client", "all lines must contain the kind of call (client)")
|
||||
assert.Equal(s.T(), msgs[0]["level"], "debug", "OK codes must be logged on debug level.")
|
||||
assert.Contains(s.T(), msgs[0], "grpc.time_ms", "interceptor log statement should contain execution time (duration in ms)")
|
||||
}
|
||||
|
||||
func (s *logrusClientSuite) TestPingError_WithCustomLevels() {
|
||||
for _, tcase := range []struct {
|
||||
code codes.Code
|
||||
level logrus.Level
|
||||
msg string
|
||||
}{
|
||||
{
|
||||
code: codes.Internal,
|
||||
level: logrus.WarnLevel,
|
||||
msg: "Internal must remap to ErrorLevel in DefaultClientCodeToLevel",
|
||||
},
|
||||
{
|
||||
code: codes.NotFound,
|
||||
level: logrus.DebugLevel,
|
||||
msg: "NotFound must remap to InfoLevel in DefaultClientCodeToLevel",
|
||||
},
|
||||
{
|
||||
code: codes.FailedPrecondition,
|
||||
level: logrus.DebugLevel,
|
||||
msg: "FailedPrecondition must remap to WarnLevel in DefaultClientCodeToLevel",
|
||||
},
|
||||
{
|
||||
code: codes.Unauthenticated,
|
||||
level: logrus.ErrorLevel,
|
||||
msg: "Unauthenticated is overwritten to ErrorLevel with customClientCodeToLevel override, which probably didn't work",
|
||||
},
|
||||
} {
|
||||
s.SetupTest()
|
||||
_, err := s.Client.PingError(
|
||||
s.SimpleCtx(),
|
||||
&pb_testproto.PingRequest{Value: "something", ErrorCodeReturned: uint32(tcase.code)})
|
||||
|
||||
assert.Error(s.T(), err, "each call here must return an error")
|
||||
|
||||
msgs := s.getOutputJSONs()
|
||||
require.Len(s.T(), msgs, 1, "only a single log message is printed")
|
||||
|
||||
assert.Equal(s.T(), msgs[0]["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain the correct service name")
|
||||
assert.Equal(s.T(), msgs[0]["grpc.method"], "PingError", "all lines must contain the correct method name")
|
||||
assert.Equal(s.T(), msgs[0]["grpc.code"], tcase.code.String(), "all lines must contain a grpc code")
|
||||
assert.Equal(s.T(), msgs[0]["level"], tcase.level.String(), tcase.msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogrusClientOverrideSuite(t *testing.T) {
|
||||
if strings.HasPrefix(runtime.Version(), "go1.7") {
|
||||
t.Skip("Skipping due to json.RawMessage incompatibility with go1.7")
|
||||
return
|
||||
}
|
||||
opts := []grpc_logrus.Option{
|
||||
grpc_logrus.WithDurationField(grpc_logrus.DurationToDurationField),
|
||||
}
|
||||
b := newLogrusBaseSuite(t)
|
||||
b.logger.Level = logrus.DebugLevel // a lot of our stuff is on debug level by default
|
||||
b.InterceptorTestSuite.ClientOpts = []grpc.DialOption{
|
||||
grpc.WithUnaryInterceptor(grpc_logrus.UnaryClientInterceptor(logrus.NewEntry(b.logger), opts...)),
|
||||
grpc.WithStreamInterceptor(grpc_logrus.StreamClientInterceptor(logrus.NewEntry(b.logger), opts...)),
|
||||
}
|
||||
suite.Run(t, &logrusClientOverrideSuite{b})
|
||||
}
|
||||
|
||||
type logrusClientOverrideSuite struct {
|
||||
*logrusBaseSuite
|
||||
}
|
||||
|
||||
func (s *logrusClientOverrideSuite) TestPing_HasOverrides() {
|
||||
_, err := s.Client.Ping(s.SimpleCtx(), goodPing)
|
||||
assert.NoError(s.T(), err, "there must be not be an on a successful call")
|
||||
|
||||
msgs := s.getOutputJSONs()
|
||||
require.Len(s.T(), msgs, 1, "one log statement should be logged")
|
||||
assert.Equal(s.T(), msgs[0]["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain the correct service name")
|
||||
assert.Equal(s.T(), msgs[0]["grpc.method"], "Ping", "all lines must contain the correct method name")
|
||||
assert.Equal(s.T(), msgs[0]["msg"], "finished client unary call", "handler's message must contain the correct message")
|
||||
|
||||
assert.NotContains(s.T(), msgs[0], "grpc.time_ms", "message must not contain default duration")
|
||||
assert.Contains(s.T(), msgs[0], "grpc.duration", "message must contain overridden duration")
|
||||
}
|
||||
|
||||
func (s *logrusClientOverrideSuite) TestPingList_HasOverrides() {
|
||||
stream, err := s.Client.PingList(s.SimpleCtx(), goodPing)
|
||||
require.NoError(s.T(), err, "should not fail on establishing the stream")
|
||||
for {
|
||||
_, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
require.NoError(s.T(), err, "reading stream should not fail")
|
||||
}
|
||||
|
||||
msgs := s.getOutputJSONs()
|
||||
require.Len(s.T(), msgs, 1, "one log statement should be logged")
|
||||
|
||||
assert.Equal(s.T(), msgs[0]["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain the correct service name")
|
||||
assert.Equal(s.T(), msgs[0]["grpc.method"], "PingList", "all lines must contain the correct method name")
|
||||
assert.Equal(s.T(), msgs[0]["msg"], "finished client streaming call", "log message must be correct")
|
||||
assert.Equal(s.T(), msgs[0]["span.kind"], "client", "all lines must contain the kind of call (client)")
|
||||
assert.Equal(s.T(), msgs[0]["level"], "debug", "OK codes must be logged on debug level.")
|
||||
|
||||
assert.NotContains(s.T(), msgs[0], "grpc.time_ms", "message must not contain default duration")
|
||||
assert.Contains(s.T(), msgs[0], "grpc.duration", "message must contain overridden duration")
|
||||
}
|
||||
19
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/context.go
generated
vendored
Normal file
19
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/context.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
package grpc_logrus
|
||||
|
||||
import (
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// AddFields adds logrus fields to the logger.
|
||||
// Deprecated: should use the ctxlogrus.Extract instead
|
||||
func AddFields(ctx context.Context, fields logrus.Fields) {
|
||||
ctxlogrus.AddFields(ctx, fields)
|
||||
}
|
||||
|
||||
// Extract takes the call-scoped logrus.Entry from grpc_logrus middleware.
|
||||
// Deprecated: should use the ctxlogrus.Extract instead
|
||||
func Extract(ctx context.Context) *logrus.Entry {
|
||||
return ctxlogrus.Extract(ctx)
|
||||
}
|
||||
93
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus/DOC.md
generated
vendored
Normal file
93
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus/DOC.md
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
# ctxlogrus
|
||||
`import "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"`
|
||||
|
||||
* [Overview](#pkg-overview)
|
||||
* [Imported Packages](#pkg-imports)
|
||||
* [Index](#pkg-index)
|
||||
* [Examples](#pkg-examples)
|
||||
|
||||
## <a name="pkg-overview">Overview</a>
|
||||
`ctxlogrus` is a ctxlogger that is backed by logrus
|
||||
|
||||
It accepts a user-configured `logrus.Logger` that will be used for logging. The same `logrus.Logger` will
|
||||
be populated into the `context.Context` passed into gRPC handler code.
|
||||
|
||||
You can use `ctx_logrus.Extract` to log into a request-scoped `logrus.Logger` instance in your handler code.
|
||||
|
||||
As `ctx_logrus.Extract` will iterate all tags on from `grpc_ctxtags` it is therefore expensive so it is advised that you
|
||||
extract once at the start of the function from the context and reuse it for the remainder of the function (see examples).
|
||||
|
||||
Please see examples and tests for examples of use.
|
||||
|
||||
## <a name="pkg-imports">Imported Packages</a>
|
||||
|
||||
- [github.com/grpc-ecosystem/go-grpc-middleware/tags](./../../../tags)
|
||||
- [github.com/sirupsen/logrus](https://godoc.org/github.com/sirupsen/logrus)
|
||||
- [golang.org/x/net/context](https://godoc.org/golang.org/x/net/context)
|
||||
|
||||
## <a name="pkg-index">Index</a>
|
||||
* [func AddFields(ctx context.Context, fields logrus.Fields)](#AddFields)
|
||||
* [func Extract(ctx context.Context) \*logrus.Entry](#Extract)
|
||||
* [func ToContext(ctx context.Context, entry \*logrus.Entry) context.Context](#ToContext)
|
||||
|
||||
#### <a name="pkg-examples">Examples</a>
|
||||
* [Extract (Unary)](#example_Extract_unary)
|
||||
|
||||
#### <a name="pkg-files">Package files</a>
|
||||
[context.go](./context.go) [doc.go](./doc.go) [noop.go](./noop.go)
|
||||
|
||||
## <a name="AddFields">func</a> [AddFields](./context.go#L21)
|
||||
``` go
|
||||
func AddFields(ctx context.Context, fields logrus.Fields)
|
||||
```
|
||||
AddFields adds logrus fields to the logger.
|
||||
|
||||
## <a name="Extract">func</a> [Extract](./context.go#L35)
|
||||
``` go
|
||||
func Extract(ctx context.Context) *logrus.Entry
|
||||
```
|
||||
Extract takes the call-scoped logrus.Entry from ctx_logrus middleware.
|
||||
|
||||
If the ctx_logrus middleware wasn't used, a no-op `logrus.Entry` is returned. This makes it safe to
|
||||
use regardless.
|
||||
|
||||
#### Example:
|
||||
|
||||
<details>
|
||||
<summary>Click to expand code.</summary>
|
||||
|
||||
```go
|
||||
package ctxlogrus_test
|
||||
|
||||
import (
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/tags"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var logrusLogger *logrus.Logger
|
||||
|
||||
// Simple unary handler that adds custom fields to the requests's context. These will be used for all log statements.
|
||||
func ExampleExtract_unary() {
|
||||
ctx := context.Background()
|
||||
// setting tags will be added to the logger as log fields
|
||||
grpc_ctxtags.Extract(ctx).Set("custom_tags.string", "something").Set("custom_tags.int", 1337)
|
||||
// Extract a single request-scoped logrus.Logger and log messages.
|
||||
l := ctxlogrus.Extract(ctx)
|
||||
l.Info("some ping")
|
||||
l.Info("another ping")
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## <a name="ToContext">func</a> [ToContext](./context.go#L59)
|
||||
``` go
|
||||
func ToContext(ctx context.Context, entry *logrus.Entry) context.Context
|
||||
```
|
||||
ToContext adds the logrus.Entry to the context for extraction later.
|
||||
Returning the new context that has been created.
|
||||
|
||||
- - -
|
||||
Generated by [godoc2ghmd](https://github.com/GandalfUK/godoc2ghmd)
|
||||
58
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus/README.md
generated
vendored
Normal file
58
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus/README.md
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
# ctx_logrus
|
||||
`import "github.com/grpc-ecosystem/go-grpc-middleware/tags/logrus"`
|
||||
|
||||
* [Overview](#pkg-overview)
|
||||
* [Imported Packages](#pkg-imports)
|
||||
* [Index](#pkg-index)
|
||||
|
||||
## <a name="pkg-overview">Overview</a>
|
||||
`ctx_logrus` is a ctxlogger that is backed by logrus
|
||||
|
||||
It accepts a user-configured `logrus.Logger` that will be used for logging. The same `logrus.Logger` will
|
||||
be populated into the `context.Context` passed into gRPC handler code.
|
||||
|
||||
You can use `ctx_logrus.Extract` to log into a request-scoped `logrus.Logger` instance in your handler code.
|
||||
|
||||
As `ctx_logrus.Extract` will iterate all tags on from `grpc_ctxtags` it is therefore expensive so it is advised that you
|
||||
extract once at the start of the function from the context and reuse it for the remainder of the function (see examples).
|
||||
|
||||
Please see examples and tests for examples of use.
|
||||
|
||||
## <a name="pkg-imports">Imported Packages</a>
|
||||
|
||||
- [github.com/grpc-ecosystem/go-grpc-middleware/tags](./..)
|
||||
- [github.com/sirupsen/logrus](https://godoc.org/github.com/sirupsen/logrus)
|
||||
- [golang.org/x/net/context](https://godoc.org/golang.org/x/net/context)
|
||||
|
||||
## <a name="pkg-index">Index</a>
|
||||
* [func AddFields(ctx context.Context, fields logrus.Fields)](#AddFields)
|
||||
* [func Extract(ctx context.Context) \*logrus.Entry](#Extract)
|
||||
* [func ToContext(ctx context.Context, entry \*logrus.Entry) context.Context](#ToContext)
|
||||
|
||||
#### <a name="pkg-files">Package files</a>
|
||||
[context.go](./context.go) [doc.go](./doc.go) [noop.go](./noop.go)
|
||||
|
||||
## <a name="AddFields">func</a> [AddFields](./context.go#L21)
|
||||
``` go
|
||||
func AddFields(ctx context.Context, fields logrus.Fields)
|
||||
```
|
||||
AddFields adds logrus fields to the logger.
|
||||
|
||||
## <a name="Extract">func</a> [Extract](./context.go#L35)
|
||||
``` go
|
||||
func Extract(ctx context.Context) *logrus.Entry
|
||||
```
|
||||
Extract takes the call-scoped logrus.Entry from ctx_logrus middleware.
|
||||
|
||||
If the ctx_logrus middleware wasn't used, a no-op `logrus.Entry` is returned. This makes it safe to
|
||||
use regardless.
|
||||
|
||||
## <a name="ToContext">func</a> [ToContext](./context.go#L59)
|
||||
``` go
|
||||
func ToContext(ctx context.Context, entry *logrus.Entry) context.Context
|
||||
```
|
||||
ToContext adds the logrus.Entry to the context for extraction later.
|
||||
Returning the new context that has been created.
|
||||
|
||||
- - -
|
||||
Generated by [godoc2ghmd](https://github.com/GandalfUK/godoc2ghmd)
|
||||
65
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus/context.go
generated
vendored
Normal file
65
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus/context.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package ctxlogrus
|
||||
|
||||
import (
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/tags"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type ctxLoggerMarker struct{}
|
||||
|
||||
type ctxLogger struct {
|
||||
logger *logrus.Entry
|
||||
fields logrus.Fields
|
||||
}
|
||||
|
||||
var (
|
||||
ctxLoggerKey = &ctxLoggerMarker{}
|
||||
)
|
||||
|
||||
// AddFields adds logrus fields to the logger.
|
||||
func AddFields(ctx context.Context, fields logrus.Fields) {
|
||||
l, ok := ctx.Value(ctxLoggerKey).(*ctxLogger)
|
||||
if !ok || l == nil {
|
||||
return
|
||||
}
|
||||
for k, v := range fields {
|
||||
l.fields[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Extract takes the call-scoped logrus.Entry from ctx_logrus middleware.
|
||||
//
|
||||
// If the ctx_logrus middleware wasn't used, a no-op `logrus.Entry` is returned. This makes it safe to
|
||||
// use regardless.
|
||||
func Extract(ctx context.Context) *logrus.Entry {
|
||||
l, ok := ctx.Value(ctxLoggerKey).(*ctxLogger)
|
||||
if !ok || l == nil {
|
||||
return logrus.NewEntry(nullLogger)
|
||||
}
|
||||
|
||||
fields := logrus.Fields{}
|
||||
|
||||
// Add grpc_ctxtags tags metadata until now.
|
||||
tags := grpc_ctxtags.Extract(ctx)
|
||||
for k, v := range tags.Values() {
|
||||
fields[k] = v
|
||||
}
|
||||
|
||||
// Add logrus fields added until now.
|
||||
for k, v := range l.fields {
|
||||
fields[k] = v
|
||||
}
|
||||
|
||||
return l.logger.WithFields(fields)
|
||||
}
|
||||
|
||||
// ToContext adds the logrus.Entry to the context for extraction later.
|
||||
// Returning the new context that has been created.
|
||||
func ToContext(ctx context.Context, entry *logrus.Entry) context.Context {
|
||||
l := &ctxLogger{
|
||||
logger: entry,
|
||||
fields: logrus.Fields{},
|
||||
}
|
||||
return context.WithValue(ctx, ctxLoggerKey, l)
|
||||
}
|
||||
14
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus/doc.go
generated
vendored
Normal file
14
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus/doc.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
`ctxlogrus` is a ctxlogger that is backed by logrus
|
||||
|
||||
It accepts a user-configured `logrus.Logger` that will be used for logging. The same `logrus.Logger` will
|
||||
be populated into the `context.Context` passed into gRPC handler code.
|
||||
|
||||
You can use `ctx_logrus.Extract` to log into a request-scoped `logrus.Logger` instance in your handler code.
|
||||
|
||||
As `ctx_logrus.Extract` will iterate all tags on from `grpc_ctxtags` it is therefore expensive so it is advised that you
|
||||
extract once at the start of the function from the context and reuse it for the remainder of the function (see examples).
|
||||
|
||||
Please see examples and tests for examples of use.
|
||||
*/
|
||||
package ctxlogrus
|
||||
21
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus/examples_test.go
generated
vendored
Normal file
21
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus/examples_test.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
package ctxlogrus_test
|
||||
|
||||
import (
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/tags"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var logrusLogger *logrus.Logger
|
||||
|
||||
// Simple unary handler that adds custom fields to the requests's context. These will be used for all log statements.
|
||||
func ExampleExtract_unary() {
|
||||
ctx := context.Background()
|
||||
// setting tags will be added to the logger as log fields
|
||||
grpc_ctxtags.Extract(ctx).Set("custom_tags.string", "something").Set("custom_tags.int", 1337)
|
||||
// Extract a single request-scoped logrus.Logger and log messages.
|
||||
l := ctxlogrus.Extract(ctx)
|
||||
l.Info("some ping")
|
||||
l.Info("another ping")
|
||||
}
|
||||
16
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus/noop.go
generated
vendored
Normal file
16
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus/noop.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
package ctxlogrus
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
nullLogger = &logrus.Logger{
|
||||
Out: ioutil.Discard,
|
||||
Formatter: new(logrus.TextFormatter),
|
||||
Hooks: make(logrus.LevelHooks),
|
||||
Level: logrus.PanicLevel,
|
||||
}
|
||||
)
|
||||
67
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/doc.go
generated
vendored
Normal file
67
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/doc.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
`grpc_logrus` is a gRPC logging middleware backed by Logrus loggers
|
||||
|
||||
It accepts a user-configured `logrus.Entry` that will be used for logging completed gRPC calls. The same
|
||||
`logrus.Entry` will be used for logging completed gRPC calls, and be populated into the `context.Context` passed into gRPC handler code.
|
||||
|
||||
On calling `StreamServerInterceptor` or `UnaryServerInterceptor` this logging middleware will add gRPC call information
|
||||
to the ctx so that it will be present on subsequent use of the `ctxlogrus` logger.
|
||||
|
||||
This package also implements request and response *payload* logging, both for server-side and client-side. These will be
|
||||
logged as structured `jsonpb` 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.
|
||||
|
||||
If a deadline is present on the gRPC request the grpc.request.deadline tag is populated when the request begins. grpc.request.deadline
|
||||
is a string representing the time (RFC3339) when the current call will expire.
|
||||
|
||||
Logrus can also be made as a backend for gRPC library internals. For that use `ReplaceGrpcLogger`.
|
||||
|
||||
*Server Interceptor*
|
||||
Below is a JSON formatted example of a log that would be logged by the server interceptor:
|
||||
|
||||
{
|
||||
"level": "info", // string logrus log levels
|
||||
"msg": "finished unary call", // string log message
|
||||
"grpc.code": "OK", // string grpc status code
|
||||
"grpc.method": "Ping", // string method name
|
||||
"grpc.service": "mwitkow.testproto.TestService", // string full name of the called service
|
||||
"grpc.start_time": "2006-01-02T15:04:05Z07:00", // string RFC3339 representation of the start time
|
||||
"grpc.request.deadline": "2006-01-02T15:04:05Z07:00", // string RFC3339 deadline of the current request if supplied
|
||||
"grpc.request.value": "something", // string value on the request
|
||||
"grpc.time_ms": 1.234, // float32 run time of the call in ms
|
||||
"peer.address": {
|
||||
"IP": "127.0.0.1", // string IP address of calling party
|
||||
"Port": 60216, // int port call is coming in on
|
||||
"Zone": "" // string peer zone for caller
|
||||
},
|
||||
"span.kind": "server", // string client | server
|
||||
"system": "grpc" // string
|
||||
|
||||
"custom_field": "custom_value", // string user defined field
|
||||
"custom_tags.int": 1337, // int user defined tag on the ctx
|
||||
"custom_tags.string": "something", // string user defined tag on the ctx
|
||||
}
|
||||
|
||||
*Payload Interceptor*
|
||||
Below is a JSON formatted example of a log that would be logged by the payload interceptor:
|
||||
|
||||
{
|
||||
"level": "info", // string logrus log levels
|
||||
"msg": "client request payload logged as grpc.request.content", // string log message
|
||||
|
||||
"grpc.request.content": { // object content of RPC request
|
||||
"value": "something", // string defined by caller
|
||||
"sleepTimeMs": 9999 // int defined by caller
|
||||
},
|
||||
"grpc.method": "Ping", // string method being called
|
||||
"grpc.service": "mwitkow.testproto.TestService", // string service being called
|
||||
"span.kind": "client", // string client | server
|
||||
"system": "grpc" // string
|
||||
}
|
||||
|
||||
Note - due to implementation ZAP differs from Logrus in the "grpc.request.content" object by having an inner "msg" object.
|
||||
|
||||
Please see examples and tests for examples of use.
|
||||
*/
|
||||
package grpc_logrus
|
||||
99
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/examples_test.go
generated
vendored
Normal file
99
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/examples_test.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
package grpc_logrus_test
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/tags"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/tags/logrus"
|
||||
pb_testproto "github.com/grpc-ecosystem/go-grpc-middleware/testing/testproto"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var (
|
||||
logrusLogger *logrus.Logger
|
||||
customFunc grpc_logrus.CodeToLevel
|
||||
)
|
||||
|
||||
// Initialization shows a relatively complex initialization sequence.
|
||||
func Example_initialization() {
|
||||
// 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 logrus Logger as well.
|
||||
grpc_logrus.ReplaceGrpcLogger(logrusEntry)
|
||||
// Create a server, make sure we put the grpc_ctxtags context before everything else.
|
||||
_ = 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...),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func Example_initializationWithDurationFieldOverride() {
|
||||
// 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()
|
||||
}),
|
||||
}
|
||||
_ = grpc.NewServer(
|
||||
grpc_middleware.WithUnaryServerChain(
|
||||
grpc_ctxtags.UnaryServerInterceptor(),
|
||||
grpc_logrus.UnaryServerInterceptor(logrusEntry, opts...),
|
||||
),
|
||||
grpc_middleware.WithStreamServerChain(
|
||||
grpc_ctxtags.StreamServerInterceptor(),
|
||||
grpc_logrus.StreamServerInterceptor(logrusEntry, opts...),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Simple unary handler that adds custom fields to the requests's context. These will be used for all log statements.
|
||||
func ExampleExtract_unary() {
|
||||
_ = 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 single request-scoped logrus.Logger and log messages.
|
||||
l := ctx_logrus.Extract(ctx)
|
||||
l.Info("some ping")
|
||||
l.Info("another ping")
|
||||
return &pb_testproto.PingResponse{Value: ping.Value}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleWithDecider() {
|
||||
opts := []grpc_logrus.Option{
|
||||
grpc_logrus.WithDecider(func(methodFullName string, err error) bool {
|
||||
// will not log gRPC calls if it was a call to healthcheck and no error was raised
|
||||
if err == nil && methodFullName == "blah.foo.healthcheck" {
|
||||
return false
|
||||
}
|
||||
|
||||
// by default you will log all calls
|
||||
return true
|
||||
}),
|
||||
}
|
||||
|
||||
_ = []grpc.ServerOption{
|
||||
grpc_middleware.WithStreamServerChain(
|
||||
grpc_ctxtags.StreamServerInterceptor(),
|
||||
grpc_logrus.StreamServerInterceptor(logrus.NewEntry(logrus.New()), opts...)),
|
||||
grpc_middleware.WithUnaryServerChain(
|
||||
grpc_ctxtags.UnaryServerInterceptor(),
|
||||
grpc_logrus.UnaryServerInterceptor(logrus.NewEntry(logrus.New()), opts...)),
|
||||
}
|
||||
}
|
||||
15
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/grpclogger.go
generated
vendored
Normal file
15
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/grpclogger.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2017 Michal Witkowski. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
package grpc_logrus
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
// ReplaceGrpcLogger sets the given logrus.Logger as a gRPC-level logger.
|
||||
// This should be called *before* any other initialization, preferably from init() functions.
|
||||
func ReplaceGrpcLogger(logger *logrus.Entry) {
|
||||
grpclog.SetLogger(logger.WithField("system", SystemField))
|
||||
}
|
||||
185
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/options.go
generated
vendored
Normal file
185
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/options.go
generated
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
// Copyright 2017 Michal Witkowski. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
package grpc_logrus
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/logging"
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultOptions = &options{
|
||||
levelFunc: nil,
|
||||
shouldLog: grpc_logging.DefaultDeciderMethod,
|
||||
codeFunc: grpc_logging.DefaultErrorToCode,
|
||||
durationFunc: DefaultDurationToField,
|
||||
}
|
||||
)
|
||||
|
||||
type options struct {
|
||||
levelFunc CodeToLevel
|
||||
shouldLog grpc_logging.Decider
|
||||
codeFunc grpc_logging.ErrorToCode
|
||||
durationFunc DurationToField
|
||||
}
|
||||
|
||||
func evaluateServerOpt(opts []Option) *options {
|
||||
optCopy := &options{}
|
||||
*optCopy = *defaultOptions
|
||||
optCopy.levelFunc = DefaultCodeToLevel
|
||||
for _, o := range opts {
|
||||
o(optCopy)
|
||||
}
|
||||
return optCopy
|
||||
}
|
||||
|
||||
func evaluateClientOpt(opts []Option) *options {
|
||||
optCopy := &options{}
|
||||
*optCopy = *defaultOptions
|
||||
optCopy.levelFunc = DefaultClientCodeToLevel
|
||||
for _, o := range opts {
|
||||
o(optCopy)
|
||||
}
|
||||
return optCopy
|
||||
}
|
||||
|
||||
type Option func(*options)
|
||||
|
||||
// CodeToLevel function defines the mapping between gRPC return codes and interceptor log level.
|
||||
type CodeToLevel func(code codes.Code) logrus.Level
|
||||
|
||||
// DurationToField function defines how to produce duration fields for logging
|
||||
type DurationToField func(duration time.Duration) (key string, value interface{})
|
||||
|
||||
// WithDecider customizes the function for deciding if the gRPC interceptor logs should log.
|
||||
func WithDecider(f grpc_logging.Decider) Option {
|
||||
return func(o *options) {
|
||||
o.shouldLog = f
|
||||
}
|
||||
}
|
||||
|
||||
// WithLevels customizes the function for mapping gRPC return codes and interceptor log level statements.
|
||||
func WithLevels(f CodeToLevel) Option {
|
||||
return func(o *options) {
|
||||
o.levelFunc = f
|
||||
}
|
||||
}
|
||||
|
||||
// WithCodes customizes the function for mapping errors to error codes.
|
||||
func WithCodes(f grpc_logging.ErrorToCode) Option {
|
||||
return func(o *options) {
|
||||
o.codeFunc = f
|
||||
}
|
||||
}
|
||||
|
||||
// WithDurationField customizes the function for mapping request durations to log fields.
|
||||
func WithDurationField(f DurationToField) Option {
|
||||
return func(o *options) {
|
||||
o.durationFunc = f
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultCodeToLevel is the default implementation of gRPC return codes to log levels for server side.
|
||||
func DefaultCodeToLevel(code codes.Code) logrus.Level {
|
||||
switch code {
|
||||
case codes.OK:
|
||||
return logrus.InfoLevel
|
||||
case codes.Canceled:
|
||||
return logrus.InfoLevel
|
||||
case codes.Unknown:
|
||||
return logrus.ErrorLevel
|
||||
case codes.InvalidArgument:
|
||||
return logrus.InfoLevel
|
||||
case codes.DeadlineExceeded:
|
||||
return logrus.WarnLevel
|
||||
case codes.NotFound:
|
||||
return logrus.InfoLevel
|
||||
case codes.AlreadyExists:
|
||||
return logrus.InfoLevel
|
||||
case codes.PermissionDenied:
|
||||
return logrus.WarnLevel
|
||||
case codes.Unauthenticated:
|
||||
return logrus.InfoLevel // unauthenticated requests can happen
|
||||
case codes.ResourceExhausted:
|
||||
return logrus.WarnLevel
|
||||
case codes.FailedPrecondition:
|
||||
return logrus.WarnLevel
|
||||
case codes.Aborted:
|
||||
return logrus.WarnLevel
|
||||
case codes.OutOfRange:
|
||||
return logrus.WarnLevel
|
||||
case codes.Unimplemented:
|
||||
return logrus.ErrorLevel
|
||||
case codes.Internal:
|
||||
return logrus.ErrorLevel
|
||||
case codes.Unavailable:
|
||||
return logrus.WarnLevel
|
||||
case codes.DataLoss:
|
||||
return logrus.ErrorLevel
|
||||
default:
|
||||
return logrus.ErrorLevel
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultClientCodeToLevel is the default implementation of gRPC return codes to log levels for client side.
|
||||
func DefaultClientCodeToLevel(code codes.Code) logrus.Level {
|
||||
switch code {
|
||||
case codes.OK:
|
||||
return logrus.DebugLevel
|
||||
case codes.Canceled:
|
||||
return logrus.DebugLevel
|
||||
case codes.Unknown:
|
||||
return logrus.InfoLevel
|
||||
case codes.InvalidArgument:
|
||||
return logrus.DebugLevel
|
||||
case codes.DeadlineExceeded:
|
||||
return logrus.InfoLevel
|
||||
case codes.NotFound:
|
||||
return logrus.DebugLevel
|
||||
case codes.AlreadyExists:
|
||||
return logrus.DebugLevel
|
||||
case codes.PermissionDenied:
|
||||
return logrus.InfoLevel
|
||||
case codes.Unauthenticated:
|
||||
return logrus.InfoLevel // unauthenticated requests can happen
|
||||
case codes.ResourceExhausted:
|
||||
return logrus.DebugLevel
|
||||
case codes.FailedPrecondition:
|
||||
return logrus.DebugLevel
|
||||
case codes.Aborted:
|
||||
return logrus.DebugLevel
|
||||
case codes.OutOfRange:
|
||||
return logrus.DebugLevel
|
||||
case codes.Unimplemented:
|
||||
return logrus.WarnLevel
|
||||
case codes.Internal:
|
||||
return logrus.WarnLevel
|
||||
case codes.Unavailable:
|
||||
return logrus.WarnLevel
|
||||
case codes.DataLoss:
|
||||
return logrus.WarnLevel
|
||||
default:
|
||||
return logrus.InfoLevel
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultDurationToField is the default implementation of converting request duration to a log field (key and value).
|
||||
var DefaultDurationToField = DurationToTimeMillisField
|
||||
|
||||
// DurationToTimeMillisField converts the duration to milliseconds and uses the key `grpc.time_ms`.
|
||||
func DurationToTimeMillisField(duration time.Duration) (key string, value interface{}) {
|
||||
return "grpc.time_ms", durationToMilliseconds(duration)
|
||||
}
|
||||
|
||||
// DurationToDurationField uses the duration value to log the request duration.
|
||||
func DurationToDurationField(duration time.Duration) (key string, value interface{}) {
|
||||
return "grpc.duration", duration
|
||||
}
|
||||
|
||||
func durationToMilliseconds(duration time.Duration) float32 {
|
||||
return float32(duration.Nanoseconds()/1000) / 1000
|
||||
}
|
||||
12
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/options_test.go
generated
vendored
Normal file
12
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/options_test.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
package grpc_logrus
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDurationToTimeMillisField(t *testing.T) {
|
||||
_, val := DurationToTimeMillisField(time.Microsecond * 100)
|
||||
assert.Equal(t, val.(float32), float32(0.1), "sub millisecond values should be correct")
|
||||
}
|
||||
144
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/payload_interceptors.go
generated
vendored
Normal file
144
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/payload_interceptors.go
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
package grpc_logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/logging"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/tags/logrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var (
|
||||
// JsonPbMarshaller is the marshaller used for serializing protobuf messages.
|
||||
JsonPbMarshaller = &jsonpb.Marshaler{}
|
||||
)
|
||||
|
||||
// PayloadUnaryServerInterceptor returns a new unary server interceptors that logs the payloads of requests.
|
||||
//
|
||||
// This *only* works when placed *after* the `grpc_logrus.UnaryServerInterceptor`. However, the logging can be done to a
|
||||
// separate instance of the logger.
|
||||
func PayloadUnaryServerInterceptor(entry *logrus.Entry, decider grpc_logging.ServerPayloadLoggingDecider) grpc.UnaryServerInterceptor {
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
if !decider(ctx, info.FullMethod, info.Server) {
|
||||
return handler(ctx, req)
|
||||
}
|
||||
// Use the provided logrus.Entry for logging but use the fields from context.
|
||||
logEntry := entry.WithFields(ctx_logrus.Extract(ctx).Data)
|
||||
logProtoMessageAsJson(logEntry, req, "grpc.request.content", "server request payload logged as grpc.request.content field")
|
||||
resp, err := handler(ctx, req)
|
||||
if err == nil {
|
||||
logProtoMessageAsJson(logEntry, resp, "grpc.response.content", "server response payload logged as grpc.request.content field")
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
// PayloadStreamServerInterceptor returns a new server server interceptors that logs the payloads of requests.
|
||||
//
|
||||
// This *only* works when placed *after* the `grpc_logrus.StreamServerInterceptor`. However, the logging can be done to a
|
||||
// separate instance of the logger.
|
||||
func PayloadStreamServerInterceptor(entry *logrus.Entry, decider grpc_logging.ServerPayloadLoggingDecider) grpc.StreamServerInterceptor {
|
||||
return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
if !decider(stream.Context(), info.FullMethod, srv) {
|
||||
return handler(srv, stream)
|
||||
}
|
||||
// Use the provided logrus.Entry for logging but use the fields from context.
|
||||
logEntry := entry.WithFields(Extract(stream.Context()).Data)
|
||||
newStream := &loggingServerStream{ServerStream: stream, entry: logEntry}
|
||||
return handler(srv, newStream)
|
||||
}
|
||||
}
|
||||
|
||||
// PayloadUnaryClientInterceptor returns a new unary client interceptor that logs the paylods of requests and responses.
|
||||
func PayloadUnaryClientInterceptor(entry *logrus.Entry, decider grpc_logging.ClientPayloadLoggingDecider) grpc.UnaryClientInterceptor {
|
||||
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||
if !decider(ctx, method) {
|
||||
return invoker(ctx, method, req, reply, cc, opts...)
|
||||
}
|
||||
logEntry := entry.WithFields(newClientLoggerFields(ctx, method))
|
||||
logProtoMessageAsJson(logEntry, req, "grpc.request.content", "client request payload logged as grpc.request.content")
|
||||
err := invoker(ctx, method, req, reply, cc, opts...)
|
||||
if err == nil {
|
||||
logProtoMessageAsJson(logEntry, reply, "grpc.response.content", "client response payload logged as grpc.response.content")
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// PayloadStreamClientInterceptor returns a new streaming client interceptor that logs the paylods of requests and responses.
|
||||
func PayloadStreamClientInterceptor(entry *logrus.Entry, decider grpc_logging.ClientPayloadLoggingDecider) grpc.StreamClientInterceptor {
|
||||
return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
if !decider(ctx, method) {
|
||||
return streamer(ctx, desc, cc, method, opts...)
|
||||
}
|
||||
logEntry := entry.WithFields(newClientLoggerFields(ctx, method))
|
||||
clientStream, err := streamer(ctx, desc, cc, method, opts...)
|
||||
newStream := &loggingClientStream{ClientStream: clientStream, entry: logEntry}
|
||||
return newStream, err
|
||||
}
|
||||
}
|
||||
|
||||
type loggingClientStream struct {
|
||||
grpc.ClientStream
|
||||
entry *logrus.Entry
|
||||
}
|
||||
|
||||
func (l *loggingClientStream) SendMsg(m interface{}) error {
|
||||
err := l.ClientStream.SendMsg(m)
|
||||
if err == nil {
|
||||
logProtoMessageAsJson(l.entry, m, "grpc.request.content", "server request payload logged as grpc.request.content field")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *loggingClientStream) RecvMsg(m interface{}) error {
|
||||
err := l.ClientStream.RecvMsg(m)
|
||||
if err == nil {
|
||||
logProtoMessageAsJson(l.entry, m, "grpc.response.content", "server response payload logged as grpc.response.content field")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type loggingServerStream struct {
|
||||
grpc.ServerStream
|
||||
entry *logrus.Entry
|
||||
}
|
||||
|
||||
func (l *loggingServerStream) SendMsg(m interface{}) error {
|
||||
err := l.ServerStream.SendMsg(m)
|
||||
if err == nil {
|
||||
logProtoMessageAsJson(l.entry, m, "grpc.response.content", "server response payload logged as grpc.response.content field")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *loggingServerStream) RecvMsg(m interface{}) error {
|
||||
err := l.ServerStream.RecvMsg(m)
|
||||
if err == nil {
|
||||
logProtoMessageAsJson(l.entry, m, "grpc.request.content", "server request payload logged as grpc.request.content field")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func logProtoMessageAsJson(entry *logrus.Entry, pbMsg interface{}, key string, msg string) {
|
||||
if p, ok := pbMsg.(proto.Message); ok {
|
||||
entry.WithField(key, &jsonpbMarshalleble{p}).Info(msg)
|
||||
}
|
||||
}
|
||||
|
||||
type jsonpbMarshalleble struct {
|
||||
proto.Message
|
||||
}
|
||||
|
||||
func (j *jsonpbMarshalleble) MarshalJSON() ([]byte, error) {
|
||||
b := &bytes.Buffer{}
|
||||
if err := JsonPbMarshaller.Marshal(b, j.Message); err != nil {
|
||||
return nil, fmt.Errorf("jsonpb serializer failed: %v", err)
|
||||
}
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
135
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/payload_interceptors_test.go
generated
vendored
Normal file
135
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/payload_interceptors_test.go
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
package grpc_logrus_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/tags"
|
||||
pb_testproto "github.com/grpc-ecosystem/go-grpc-middleware/testing/testproto"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var (
|
||||
nullLogger = &logrus.Logger{
|
||||
Out: ioutil.Discard,
|
||||
Formatter: new(logrus.TextFormatter),
|
||||
Hooks: make(logrus.LevelHooks),
|
||||
Level: logrus.PanicLevel,
|
||||
}
|
||||
)
|
||||
|
||||
func TestLogrusPayloadSuite(t *testing.T) {
|
||||
if strings.HasPrefix(runtime.Version(), "go1.7") {
|
||||
t.Skipf("Skipping due to json.RawMessage incompatibility with go1.7")
|
||||
return
|
||||
}
|
||||
alwaysLoggingDeciderServer := func(ctx context.Context, fullMethodName string, servingObject interface{}) bool { return true }
|
||||
alwaysLoggingDeciderClient := func(ctx context.Context, fullMethodName string) bool { return true }
|
||||
b := newLogrusBaseSuite(t)
|
||||
b.InterceptorTestSuite.ClientOpts = []grpc.DialOption{
|
||||
grpc.WithUnaryInterceptor(grpc_logrus.PayloadUnaryClientInterceptor(logrus.NewEntry(b.logger), alwaysLoggingDeciderClient)),
|
||||
grpc.WithStreamInterceptor(grpc_logrus.PayloadStreamClientInterceptor(logrus.NewEntry(b.logger), alwaysLoggingDeciderClient)),
|
||||
}
|
||||
b.InterceptorTestSuite.ServerOpts = []grpc.ServerOption{
|
||||
grpc_middleware.WithStreamServerChain(
|
||||
grpc_ctxtags.StreamServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)),
|
||||
grpc_logrus.StreamServerInterceptor(logrus.NewEntry(nullLogger)),
|
||||
grpc_logrus.PayloadStreamServerInterceptor(logrus.NewEntry(b.logger), alwaysLoggingDeciderServer)),
|
||||
grpc_middleware.WithUnaryServerChain(
|
||||
grpc_ctxtags.UnaryServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)),
|
||||
grpc_logrus.UnaryServerInterceptor(logrus.NewEntry(nullLogger)),
|
||||
grpc_logrus.PayloadUnaryServerInterceptor(logrus.NewEntry(b.logger), alwaysLoggingDeciderServer)),
|
||||
}
|
||||
suite.Run(t, &logrusPayloadSuite{b})
|
||||
}
|
||||
|
||||
type logrusPayloadSuite struct {
|
||||
*logrusBaseSuite
|
||||
}
|
||||
|
||||
func (s *logrusPayloadSuite) getServerAndClientMessages(expectedServer int, expectedClient int) (serverMsgs []map[string]interface{}, clientMsgs []map[string]interface{}) {
|
||||
msgs := s.getOutputJSONs()
|
||||
for _, m := range msgs {
|
||||
if m["span.kind"] == "server" {
|
||||
serverMsgs = append(serverMsgs, m)
|
||||
} else if m["span.kind"] == "client" {
|
||||
clientMsgs = append(clientMsgs, m)
|
||||
}
|
||||
}
|
||||
|
||||
require.Len(s.T(), serverMsgs, expectedServer, "must match expected number of server log messages")
|
||||
require.Len(s.T(), clientMsgs, expectedClient, "must match expected number of client log messages")
|
||||
return serverMsgs, clientMsgs
|
||||
}
|
||||
|
||||
func (s *logrusPayloadSuite) TestPing_LogsBothRequestAndResponse() {
|
||||
_, err := s.Client.Ping(s.SimpleCtx(), goodPing)
|
||||
require.NoError(s.T(), err, "there must be not be an on a successful call")
|
||||
serverMsgs, clientMsgs := s.getServerAndClientMessages(2, 2)
|
||||
|
||||
for _, m := range append(serverMsgs, clientMsgs...) {
|
||||
assert.Equal(s.T(), m["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain the correct service name")
|
||||
assert.Equal(s.T(), m["grpc.method"], "Ping", "all lines must contain the correct method name")
|
||||
assert.Equal(s.T(), m["level"], "info", "all lines must contain method name")
|
||||
}
|
||||
|
||||
serverReq, serverResp := serverMsgs[0], serverMsgs[1]
|
||||
clientReq, clientResp := clientMsgs[0], clientMsgs[1]
|
||||
assert.Contains(s.T(), clientReq, "grpc.request.content", "request payload must be logged in a structured way")
|
||||
assert.Contains(s.T(), serverReq, "grpc.request.content", "request payload must be logged in a structured way")
|
||||
assert.Contains(s.T(), clientResp, "grpc.response.content", "response payload must be logged in a structured way")
|
||||
assert.Contains(s.T(), serverResp, "grpc.response.content", "response payload must be logged in a structured way")
|
||||
}
|
||||
|
||||
func (s *logrusPayloadSuite) TestPingError_LogsOnlyRequestsOnError() {
|
||||
_, err := s.Client.PingError(s.SimpleCtx(), &pb_testproto.PingRequest{Value: "something", ErrorCodeReturned: uint32(4)})
|
||||
require.Error(s.T(), err, "there must be not be an error on a successful call")
|
||||
|
||||
serverMsgs, clientMsgs := s.getServerAndClientMessages(1, 1)
|
||||
for _, m := range append(serverMsgs, clientMsgs...) {
|
||||
assert.Equal(s.T(), m["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain the correct service name")
|
||||
assert.Equal(s.T(), m["grpc.method"], "PingError", "all lines must contain the correct method name")
|
||||
assert.Equal(s.T(), m["level"], "info", "all lines must be logged at info level")
|
||||
}
|
||||
|
||||
assert.Contains(s.T(), clientMsgs[0], "grpc.request.content", "request payload must be logged by the client")
|
||||
assert.Contains(s.T(), serverMsgs[0], "grpc.request.content", "request payload must be logged by the server")
|
||||
}
|
||||
|
||||
func (s *logrusPayloadSuite) TestPingStream_LogsAllRequestsAndResponses() {
|
||||
messagesExpected := 20
|
||||
stream, err := s.Client.PingStream(s.SimpleCtx())
|
||||
require.NoError(s.T(), err, "no error on stream creation")
|
||||
for i := 0; i < messagesExpected; i++ {
|
||||
require.NoError(s.T(), stream.Send(goodPing), "sending must succeed")
|
||||
}
|
||||
require.NoError(s.T(), stream.CloseSend(), "no error on close of stream")
|
||||
|
||||
for {
|
||||
pong := &pb_testproto.PingResponse{}
|
||||
err := stream.RecvMsg(pong)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
require.NoError(s.T(), err, "no error on receive")
|
||||
}
|
||||
serverMsgs, clientMsgs := s.getServerAndClientMessages(2*messagesExpected, 2*messagesExpected)
|
||||
for _, m := range append(serverMsgs, clientMsgs...) {
|
||||
assert.Equal(s.T(), m["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain the correct service name")
|
||||
assert.Equal(s.T(), m["grpc.method"], "PingStream", "all lines must contain the correct method name")
|
||||
assert.Equal(s.T(), m["level"], "info", "all lines must be at info log level")
|
||||
|
||||
content := m["grpc.request.content"] != nil || m["grpc.response.content"] != nil
|
||||
assert.True(s.T(), content, "all messages must contain a payload")
|
||||
}
|
||||
}
|
||||
129
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/server_interceptors.go
generated
vendored
Normal file
129
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/server_interceptors.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
// Copyright (c) Improbable Worlds Ltd, All Rights Reserved
|
||||
|
||||
package grpc_logrus
|
||||
|
||||
import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/tags/logrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var (
|
||||
// SystemField is used in every log statement made through grpc_logrus. Can be overwritten before any initialization code.
|
||||
SystemField = "system"
|
||||
|
||||
// KindField describes the log gield used to incicate whether this is a server or a client log statment.
|
||||
KindField = "span.kind"
|
||||
)
|
||||
|
||||
// UnaryServerInterceptor returns a new unary server interceptors that adds logrus.Entry to the context.
|
||||
func UnaryServerInterceptor(entry *logrus.Entry, opts ...Option) grpc.UnaryServerInterceptor {
|
||||
o := evaluateServerOpt(opts)
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
startTime := time.Now()
|
||||
newCtx := newLoggerForCall(ctx, entry, info.FullMethod, startTime)
|
||||
|
||||
resp, err := handler(newCtx, req)
|
||||
|
||||
if !o.shouldLog(info.FullMethod, err) {
|
||||
return resp, err
|
||||
}
|
||||
code := o.codeFunc(err)
|
||||
level := o.levelFunc(code)
|
||||
durField, durVal := o.durationFunc(time.Since(startTime))
|
||||
fields := logrus.Fields{
|
||||
"grpc.code": code.String(),
|
||||
durField: durVal,
|
||||
}
|
||||
if err != nil {
|
||||
fields[logrus.ErrorKey] = err
|
||||
}
|
||||
|
||||
levelLogf(
|
||||
ctx_logrus.Extract(newCtx).WithFields(fields), // re-extract logger from newCtx, as it may have extra fields that changed in the holder.
|
||||
level,
|
||||
"finished unary call with code "+code.String())
|
||||
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
// StreamServerInterceptor returns a new streaming server interceptor that adds logrus.Entry to the context.
|
||||
func StreamServerInterceptor(entry *logrus.Entry, opts ...Option) grpc.StreamServerInterceptor {
|
||||
o := evaluateServerOpt(opts)
|
||||
return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
startTime := time.Now()
|
||||
newCtx := newLoggerForCall(stream.Context(), entry, info.FullMethod, startTime)
|
||||
wrapped := grpc_middleware.WrapServerStream(stream)
|
||||
wrapped.WrappedContext = newCtx
|
||||
|
||||
err := handler(srv, wrapped)
|
||||
|
||||
if !o.shouldLog(info.FullMethod, err) {
|
||||
return err
|
||||
}
|
||||
code := o.codeFunc(err)
|
||||
level := o.levelFunc(code)
|
||||
durField, durVal := o.durationFunc(time.Since(startTime))
|
||||
fields := logrus.Fields{
|
||||
"grpc.code": code.String(),
|
||||
durField: durVal,
|
||||
}
|
||||
if err != nil {
|
||||
fields[logrus.ErrorKey] = err
|
||||
}
|
||||
|
||||
levelLogf(
|
||||
ctx_logrus.Extract(newCtx).WithFields(fields), // re-extract logger from newCtx, as it may have extra fields that changed in the holder.
|
||||
level,
|
||||
"finished streaming call with code "+code.String())
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func levelLogf(entry *logrus.Entry, level logrus.Level, format string, args ...interface{}) {
|
||||
switch level {
|
||||
case logrus.DebugLevel:
|
||||
entry.Debugf(format, args...)
|
||||
case logrus.InfoLevel:
|
||||
entry.Infof(format, args...)
|
||||
case logrus.WarnLevel:
|
||||
entry.Warningf(format, args...)
|
||||
case logrus.ErrorLevel:
|
||||
entry.Errorf(format, args...)
|
||||
case logrus.FatalLevel:
|
||||
entry.Fatalf(format, args...)
|
||||
case logrus.PanicLevel:
|
||||
entry.Panicf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func newLoggerForCall(ctx context.Context, entry *logrus.Entry, fullMethodString string, start time.Time) context.Context {
|
||||
service := path.Dir(fullMethodString)[1:]
|
||||
method := path.Base(fullMethodString)
|
||||
callLog := entry.WithFields(
|
||||
logrus.Fields{
|
||||
SystemField: "grpc",
|
||||
KindField: "server",
|
||||
"grpc.service": service,
|
||||
"grpc.method": method,
|
||||
"grpc.start_time": start.Format(time.RFC3339),
|
||||
})
|
||||
|
||||
if d, ok := ctx.Deadline(); ok {
|
||||
callLog = callLog.WithFields(
|
||||
logrus.Fields{
|
||||
"grpc.request.deadline": d.Format(time.RFC3339),
|
||||
})
|
||||
}
|
||||
|
||||
callLog = callLog.WithFields(ctx_logrus.Extract(ctx).Data)
|
||||
return ctxlogrus.ToContext(ctx, callLog)
|
||||
}
|
||||
322
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/server_interceptors_test.go
generated
vendored
Normal file
322
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/server_interceptors_test.go
generated
vendored
Normal file
@@ -0,0 +1,322 @@
|
||||
package grpc_logrus_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/tags"
|
||||
pb_testproto "github.com/grpc-ecosystem/go-grpc-middleware/testing/testproto"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
func TestLogrusServerSuite(t *testing.T) {
|
||||
if strings.HasPrefix(runtime.Version(), "go1.7") {
|
||||
t.Skipf("Skipping due to json.RawMessage incompatibility with go1.7")
|
||||
return
|
||||
}
|
||||
opts := []grpc_logrus.Option{
|
||||
grpc_logrus.WithLevels(customCodeToLevel),
|
||||
}
|
||||
b := newLogrusBaseSuite(t)
|
||||
b.InterceptorTestSuite.ServerOpts = []grpc.ServerOption{
|
||||
grpc_middleware.WithStreamServerChain(
|
||||
grpc_ctxtags.StreamServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)),
|
||||
grpc_logrus.StreamServerInterceptor(logrus.NewEntry(b.logger), opts...)),
|
||||
grpc_middleware.WithUnaryServerChain(
|
||||
grpc_ctxtags.UnaryServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)),
|
||||
grpc_logrus.UnaryServerInterceptor(logrus.NewEntry(b.logger), opts...)),
|
||||
}
|
||||
suite.Run(t, &logrusServerSuite{b})
|
||||
}
|
||||
|
||||
type logrusServerSuite struct {
|
||||
*logrusBaseSuite
|
||||
}
|
||||
|
||||
func (s *logrusServerSuite) TestPing_WithCustomTags() {
|
||||
deadline := time.Now().Add(3 * time.Second)
|
||||
_, err := s.Client.Ping(s.DeadlineCtx(deadline), goodPing)
|
||||
require.NoError(s.T(), err, "there must be not be an error on a successful call")
|
||||
|
||||
msgs := s.getOutputJSONs()
|
||||
require.Len(s.T(), msgs, 2, "two log statements should be logged")
|
||||
|
||||
for _, m := range msgs {
|
||||
assert.Equal(s.T(), m["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain the correct service name")
|
||||
assert.Equal(s.T(), m["grpc.method"], "Ping", "all lines must contain the correct method name")
|
||||
assert.Equal(s.T(), m["span.kind"], "server", "all lines must contain the kind of call (server)")
|
||||
assert.Equal(s.T(), m["custom_tags.string"], "something", "all lines must contain `custom_tags.string` with expected value")
|
||||
assert.Equal(s.T(), m["grpc.request.value"], "something", "all lines must contain the correct request value")
|
||||
assert.Equal(s.T(), m["custom_field"], "custom_value", "all lines must contain `custom_field` with the correct value")
|
||||
|
||||
assert.Contains(s.T(), m, "custom_tags.int", "all lines must contain `custom_tags.int`")
|
||||
require.Contains(s.T(), m, "grpc.start_time", "all lines must contain the start time of the call")
|
||||
_, err := time.Parse(time.RFC3339, m["grpc.start_time"].(string))
|
||||
assert.NoError(s.T(), err, "should be able to parse start time as RFC3339")
|
||||
|
||||
require.Contains(s.T(), m, "grpc.request.deadline", "all lines must contain the deadline of the call")
|
||||
_, err = time.Parse(time.RFC3339, m["grpc.request.deadline"].(string))
|
||||
require.NoError(s.T(), err, "should be able to parse deadline as RFC3339")
|
||||
assert.Equal(s.T(), m["grpc.request.deadline"], deadline.Format(time.RFC3339), "should have the same deadline that was set by the caller")
|
||||
}
|
||||
|
||||
assert.Equal(s.T(), msgs[0]["msg"], "some ping", "first message must contain the correct user message")
|
||||
assert.Equal(s.T(), msgs[1]["msg"], "finished unary call with code OK", "second message must contain the correct user message")
|
||||
assert.Equal(s.T(), msgs[1]["level"], "info", "OK codes must be logged on info level.")
|
||||
|
||||
assert.Contains(s.T(), msgs[1], "grpc.time_ms", "interceptor log statement should contain execution time")
|
||||
}
|
||||
|
||||
func (s *logrusServerSuite) TestPingError_WithCustomLevels() {
|
||||
for _, tcase := range []struct {
|
||||
code codes.Code
|
||||
level logrus.Level
|
||||
msg string
|
||||
}{
|
||||
{
|
||||
code: codes.Internal,
|
||||
level: logrus.ErrorLevel,
|
||||
msg: "Internal must remap to ErrorLevel in DefaultCodeToLevel",
|
||||
},
|
||||
{
|
||||
code: codes.NotFound,
|
||||
level: logrus.InfoLevel,
|
||||
msg: "NotFound must remap to InfoLevel in DefaultCodeToLevel",
|
||||
},
|
||||
{
|
||||
code: codes.FailedPrecondition,
|
||||
level: logrus.WarnLevel,
|
||||
msg: "FailedPrecondition must remap to WarnLevel in DefaultCodeToLevel",
|
||||
},
|
||||
{
|
||||
code: codes.Unauthenticated,
|
||||
level: logrus.ErrorLevel,
|
||||
msg: "Unauthenticated is overwritten to ErrorLevel with customCodeToLevel override, which probably didn't work",
|
||||
},
|
||||
} {
|
||||
s.buffer.Reset()
|
||||
_, err := s.Client.PingError(
|
||||
s.SimpleCtx(),
|
||||
&pb_testproto.PingRequest{Value: "something", ErrorCodeReturned: uint32(tcase.code)})
|
||||
require.Error(s.T(), err, "each call here must return an error")
|
||||
|
||||
msgs := s.getOutputJSONs()
|
||||
require.Len(s.T(), msgs, 1, "only the interceptor log message is printed in PingErr")
|
||||
m := msgs[0]
|
||||
assert.Equal(s.T(), m["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain the correct service name")
|
||||
assert.Equal(s.T(), m["grpc.method"], "PingError", "all lines must contain the correct method name")
|
||||
assert.Equal(s.T(), m["grpc.code"], tcase.code.String(), "a gRPC code must be present")
|
||||
assert.Equal(s.T(), m["level"], tcase.level.String(), tcase.msg)
|
||||
assert.Equal(s.T(), m["msg"], "finished unary call with code "+tcase.code.String(), "must have the correct finish message")
|
||||
|
||||
require.Contains(s.T(), m, "grpc.start_time", "all lines must contain a start time for the call")
|
||||
_, err = time.Parse(time.RFC3339, m["grpc.start_time"].(string))
|
||||
assert.NoError(s.T(), err, "should be able to parse the start time as RFC3339")
|
||||
|
||||
require.Contains(s.T(), m, "grpc.request.deadline", "all lines must contain the deadline of the call")
|
||||
_, err = time.Parse(time.RFC3339, m["grpc.request.deadline"].(string))
|
||||
require.NoError(s.T(), err, "should be able to parse deadline as RFC3339")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *logrusServerSuite) TestPingList_WithCustomTags() {
|
||||
stream, err := s.Client.PingList(s.SimpleCtx(), goodPing)
|
||||
require.NoError(s.T(), err, "should not fail on establishing the stream")
|
||||
for {
|
||||
_, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
require.NoError(s.T(), err, "reading stream should not fail")
|
||||
}
|
||||
|
||||
msgs := s.getOutputJSONs()
|
||||
require.Len(s.T(), msgs, 2, "two log statements should be logged")
|
||||
for _, m := range msgs {
|
||||
assert.Equal(s.T(), m["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain the correct service name")
|
||||
assert.Equal(s.T(), m["grpc.method"], "PingList", "all lines must contain the correct method name")
|
||||
assert.Equal(s.T(), m["span.kind"], "server", "all lines must contain the kind of call (server)")
|
||||
assert.Equal(s.T(), m["custom_tags.string"], "something", "all lines must contain the correct `custom_tags.string`")
|
||||
assert.Equal(s.T(), m["grpc.request.value"], "something", "all lines must contain the correct request value")
|
||||
|
||||
assert.Contains(s.T(), m, "custom_tags.int", "all lines must contain `custom_tags.int`")
|
||||
require.Contains(s.T(), m, "grpc.start_time", "all lines must contain the start time for the call")
|
||||
_, err := time.Parse(time.RFC3339, m["grpc.start_time"].(string))
|
||||
assert.NoError(s.T(), err, "should be able to parse start time as RFC3339")
|
||||
|
||||
require.Contains(s.T(), m, "grpc.request.deadline", "all lines must contain the deadline of the call")
|
||||
_, err = time.Parse(time.RFC3339, m["grpc.request.deadline"].(string))
|
||||
require.NoError(s.T(), err, "should be able to parse deadline as RFC3339")
|
||||
}
|
||||
|
||||
assert.Equal(s.T(), msgs[0]["msg"], "some pinglist", "msg must be the correct message")
|
||||
assert.Equal(s.T(), msgs[1]["msg"], "finished streaming call with code OK", "msg must be the correct message")
|
||||
assert.Equal(s.T(), msgs[1]["level"], "info", "OK codes must be logged on info level.")
|
||||
|
||||
assert.Contains(s.T(), msgs[1], "grpc.time_ms", "interceptor log statement should contain execution time")
|
||||
}
|
||||
|
||||
func TestLogrusServerOverrideSuite(t *testing.T) {
|
||||
if strings.HasPrefix(runtime.Version(), "go1.7") {
|
||||
t.Skip("Skipping due to json.RawMessage incompatibility with go1.7")
|
||||
return
|
||||
}
|
||||
opts := []grpc_logrus.Option{
|
||||
grpc_logrus.WithDurationField(grpc_logrus.DurationToDurationField),
|
||||
}
|
||||
b := newLogrusBaseSuite(t)
|
||||
b.InterceptorTestSuite.ServerOpts = []grpc.ServerOption{
|
||||
grpc_middleware.WithStreamServerChain(
|
||||
grpc_ctxtags.StreamServerInterceptor(),
|
||||
grpc_logrus.StreamServerInterceptor(logrus.NewEntry(b.logger), opts...)),
|
||||
grpc_middleware.WithUnaryServerChain(
|
||||
grpc_ctxtags.UnaryServerInterceptor(),
|
||||
grpc_logrus.UnaryServerInterceptor(logrus.NewEntry(b.logger), opts...)),
|
||||
}
|
||||
suite.Run(t, &logrusServerOverrideSuite{b})
|
||||
}
|
||||
|
||||
type logrusServerOverrideSuite struct {
|
||||
*logrusBaseSuite
|
||||
}
|
||||
|
||||
func (s *logrusServerOverrideSuite) TestPing_HasOverriddenDuration() {
|
||||
_, err := s.Client.Ping(s.SimpleCtx(), goodPing)
|
||||
require.NoError(s.T(), err, "there must be not be an error on a successful call")
|
||||
|
||||
msgs := s.getOutputJSONs()
|
||||
require.Len(s.T(), msgs, 2, "two log statements should be logged")
|
||||
for _, m := range msgs {
|
||||
assert.Equal(s.T(), m["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain service name")
|
||||
assert.Equal(s.T(), m["grpc.method"], "Ping", "all lines must contain method name")
|
||||
}
|
||||
|
||||
assert.Equal(s.T(), msgs[0]["msg"], "some ping", "first message must be correct")
|
||||
assert.NotContains(s.T(), msgs[0], "grpc.time_ms", "first message must not contain default duration")
|
||||
assert.NotContains(s.T(), msgs[0], "grpc.duration", "first message must not contain overridden duration")
|
||||
|
||||
assert.Equal(s.T(), msgs[1]["msg"], "finished unary call with code OK", "second message must be correct")
|
||||
assert.Equal(s.T(), msgs[1]["level"], "info", "second must be logged on info level.")
|
||||
assert.NotContains(s.T(), msgs[1], "grpc.time_ms", "second message must not contain default duration")
|
||||
assert.Contains(s.T(), msgs[1], "grpc.duration", "second message must contain overridden duration")
|
||||
}
|
||||
|
||||
func (s *logrusServerOverrideSuite) TestPingList_HasOverriddenDuration() {
|
||||
stream, err := s.Client.PingList(s.SimpleCtx(), goodPing)
|
||||
require.NoError(s.T(), err, "should not fail on establishing the stream")
|
||||
for {
|
||||
_, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
require.NoError(s.T(), err, "reading stream should not fail")
|
||||
}
|
||||
msgs := s.getOutputJSONs()
|
||||
require.Len(s.T(), msgs, 2, "two log statements should be logged")
|
||||
|
||||
for _, m := range msgs {
|
||||
assert.Equal(s.T(), m["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain service name")
|
||||
assert.Equal(s.T(), m["grpc.method"], "PingList", "all lines must contain method name")
|
||||
}
|
||||
|
||||
assert.Equal(s.T(), msgs[0]["msg"], "some pinglist", "first message must contain user message")
|
||||
assert.NotContains(s.T(), msgs[0], "grpc.time_ms", "first message must not contain default duration")
|
||||
assert.NotContains(s.T(), msgs[0], "grpc.duration", "first message must not contain overridden duration")
|
||||
|
||||
assert.Equal(s.T(), msgs[1]["msg"], "finished streaming call with code OK", "second message must contain correct message")
|
||||
assert.Equal(s.T(), msgs[1]["level"], "info", "second message must be logged on info level.")
|
||||
assert.NotContains(s.T(), msgs[1], "grpc.time_ms", "second message must not contain default duration")
|
||||
assert.Contains(s.T(), msgs[1], "grpc.duration", "second message must contain overridden duration")
|
||||
}
|
||||
|
||||
func TestLogrusServerOverrideDeciderSuite(t *testing.T) {
|
||||
if strings.HasPrefix(runtime.Version(), "go1.7") {
|
||||
t.Skip("Skipping due to json.RawMessage incompatibility with go1.7")
|
||||
return
|
||||
}
|
||||
opts := []grpc_logrus.Option{
|
||||
grpc_logrus.WithDecider(func(method string, err error) bool {
|
||||
if err != nil && method == "/mwitkow.testproto.TestService/PingError" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}),
|
||||
}
|
||||
b := newLogrusBaseSuite(t)
|
||||
b.InterceptorTestSuite.ServerOpts = []grpc.ServerOption{
|
||||
grpc_middleware.WithStreamServerChain(
|
||||
grpc_ctxtags.StreamServerInterceptor(),
|
||||
grpc_logrus.StreamServerInterceptor(logrus.NewEntry(b.logger), opts...)),
|
||||
grpc_middleware.WithUnaryServerChain(
|
||||
grpc_ctxtags.UnaryServerInterceptor(),
|
||||
grpc_logrus.UnaryServerInterceptor(logrus.NewEntry(b.logger), opts...)),
|
||||
}
|
||||
suite.Run(t, &logrusServerOverrideDeciderSuite{b})
|
||||
}
|
||||
|
||||
type logrusServerOverrideDeciderSuite struct {
|
||||
*logrusBaseSuite
|
||||
}
|
||||
|
||||
func (s *logrusServerOverrideDeciderSuite) TestPing_HasOverriddenDecider() {
|
||||
_, err := s.Client.Ping(s.SimpleCtx(), goodPing)
|
||||
require.NoError(s.T(), err, "there must be not be an error on a successful call")
|
||||
|
||||
msgs := s.getOutputJSONs()
|
||||
require.Len(s.T(), msgs, 1, "single log statements should be logged")
|
||||
assert.Equal(s.T(), msgs[0]["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain service name")
|
||||
assert.Equal(s.T(), msgs[0]["grpc.method"], "Ping", "all lines must contain method name")
|
||||
assert.Equal(s.T(), msgs[0]["msg"], "some ping", "handler's message must contain user message")
|
||||
}
|
||||
|
||||
func (s *logrusServerOverrideDeciderSuite) TestPingError_HasOverriddenDecider() {
|
||||
code := codes.NotFound
|
||||
level := logrus.InfoLevel
|
||||
msg := "NotFound must remap to InfoLevel in DefaultCodeToLevel"
|
||||
|
||||
s.buffer.Reset()
|
||||
_, err := s.Client.PingError(
|
||||
s.SimpleCtx(),
|
||||
&pb_testproto.PingRequest{Value: "something", ErrorCodeReturned: uint32(code)})
|
||||
require.Error(s.T(), err, "each call here must return an error")
|
||||
|
||||
msgs := s.getOutputJSONs()
|
||||
require.Len(s.T(), msgs, 1, "only the interceptor log message is printed in PingErr")
|
||||
m := msgs[0]
|
||||
assert.Equal(s.T(), m["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain service name")
|
||||
assert.Equal(s.T(), m["grpc.method"], "PingError", "all lines must contain method name")
|
||||
assert.Equal(s.T(), m["grpc.code"], code.String(), "all lines must correct gRPC code")
|
||||
assert.Equal(s.T(), m["level"], level.String(), msg)
|
||||
}
|
||||
|
||||
func (s *logrusServerOverrideDeciderSuite) TestPingList_HasOverriddenDecider() {
|
||||
stream, err := s.Client.PingList(s.SimpleCtx(), goodPing)
|
||||
require.NoError(s.T(), err, "should not fail on establishing the stream")
|
||||
for {
|
||||
_, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
require.NoError(s.T(), err, "reading stream should not fail")
|
||||
}
|
||||
|
||||
msgs := s.getOutputJSONs()
|
||||
require.Len(s.T(), msgs, 1, "single log statements should be logged")
|
||||
assert.Equal(s.T(), msgs[0]["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain service name")
|
||||
assert.Equal(s.T(), msgs[0]["grpc.method"], "PingList", "all lines must contain method name")
|
||||
assert.Equal(s.T(), msgs[0]["msg"], "some pinglist", "handler's message must contain user message")
|
||||
|
||||
assert.NotContains(s.T(), msgs[0], "grpc.time_ms", "handler's message must not contain default duration")
|
||||
assert.NotContains(s.T(), msgs[0], "grpc.duration", "handler's message must not contain overridden duration")
|
||||
}
|
||||
105
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/shared_test.go
generated
vendored
Normal file
105
vendor/github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/shared_test.go
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
package grpc_logrus_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/tags"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/tags/logrus"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/testing"
|
||||
pb_testproto "github.com/grpc-ecosystem/go-grpc-middleware/testing/testproto"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
var (
|
||||
goodPing = &pb_testproto.PingRequest{Value: "something", SleepTimeMs: 9999}
|
||||
)
|
||||
|
||||
type loggingPingService struct {
|
||||
pb_testproto.TestServiceServer
|
||||
}
|
||||
|
||||
func customCodeToLevel(c codes.Code) logrus.Level {
|
||||
if c == codes.Unauthenticated {
|
||||
// Make this a special case for tests, and an error.
|
||||
return logrus.ErrorLevel
|
||||
}
|
||||
level := grpc_logrus.DefaultCodeToLevel(c)
|
||||
return level
|
||||
}
|
||||
|
||||
func (s *loggingPingService) Ping(ctx context.Context, ping *pb_testproto.PingRequest) (*pb_testproto.PingResponse, error) {
|
||||
grpc_ctxtags.Extract(ctx).Set("custom_tags.string", "something").Set("custom_tags.int", 1337)
|
||||
ctx_logrus.AddFields(ctx, logrus.Fields{"custom_field": "custom_value"})
|
||||
ctx_logrus.Extract(ctx).Info("some ping")
|
||||
return s.TestServiceServer.Ping(ctx, ping)
|
||||
}
|
||||
|
||||
func (s *loggingPingService) PingError(ctx context.Context, ping *pb_testproto.PingRequest) (*pb_testproto.Empty, error) {
|
||||
return s.TestServiceServer.PingError(ctx, ping)
|
||||
}
|
||||
|
||||
func (s *loggingPingService) PingList(ping *pb_testproto.PingRequest, stream pb_testproto.TestService_PingListServer) error {
|
||||
grpc_ctxtags.Extract(stream.Context()).Set("custom_tags.string", "something").Set("custom_tags.int", 1337)
|
||||
ctx_logrus.AddFields(stream.Context(), logrus.Fields{"custom_field": "custom_value"})
|
||||
ctx_logrus.Extract(stream.Context()).Info("some pinglist")
|
||||
return s.TestServiceServer.PingList(ping, stream)
|
||||
}
|
||||
|
||||
func (s *loggingPingService) PingEmpty(ctx context.Context, empty *pb_testproto.Empty) (*pb_testproto.PingResponse, error) {
|
||||
return s.TestServiceServer.PingEmpty(ctx, empty)
|
||||
}
|
||||
|
||||
type logrusBaseSuite struct {
|
||||
*grpc_testing.InterceptorTestSuite
|
||||
mutexBuffer *grpc_testing.MutexReadWriter
|
||||
buffer *bytes.Buffer
|
||||
logger *logrus.Logger
|
||||
}
|
||||
|
||||
func newLogrusBaseSuite(t *testing.T) *logrusBaseSuite {
|
||||
b := &bytes.Buffer{}
|
||||
muB := grpc_testing.NewMutexReadWriter(b)
|
||||
logger := logrus.New()
|
||||
logger.Out = muB
|
||||
logger.Formatter = &logrus.JSONFormatter{DisableTimestamp: true}
|
||||
return &logrusBaseSuite{
|
||||
logger: logger,
|
||||
buffer: b,
|
||||
mutexBuffer: muB,
|
||||
InterceptorTestSuite: &grpc_testing.InterceptorTestSuite{
|
||||
TestService: &loggingPingService{&grpc_testing.TestPingService{T: t}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *logrusBaseSuite) SetupTest() {
|
||||
s.mutexBuffer.Lock()
|
||||
s.buffer.Reset()
|
||||
s.mutexBuffer.Unlock()
|
||||
}
|
||||
|
||||
func (s *logrusBaseSuite) getOutputJSONs() []map[string]interface{} {
|
||||
ret := make([]map[string]interface{}, 0)
|
||||
dec := json.NewDecoder(s.mutexBuffer)
|
||||
|
||||
for {
|
||||
var val map[string]interface{}
|
||||
err := dec.Decode(&val)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
s.T().Fatalf("failed decoding output from Logrus JSON: %v", err)
|
||||
}
|
||||
|
||||
ret = append(ret, val)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
Reference in New Issue
Block a user