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 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
185 lines
4.9 KiB
Go
185 lines
4.9 KiB
Go
package grpc_zap
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/grpc-ecosystem/go-grpc-middleware/logging"
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
"google.golang.org/grpc/codes"
|
|
)
|
|
|
|
var (
|
|
defaultOptions = &options{
|
|
levelFunc: DefaultCodeToLevel,
|
|
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) zapcore.Level
|
|
|
|
// DurationToField function defines how to produce duration fields for logging
|
|
type DurationToField func(duration time.Duration) zapcore.Field
|
|
|
|
// 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 Zap fields.
|
|
func WithDurationField(f DurationToField) Option {
|
|
return func(o *options) {
|
|
o.durationFunc = f
|
|
}
|
|
}
|
|
|
|
// DefaultCodeToLevel is the default implementation of gRPC return codes and interceptor log level for server side.
|
|
func DefaultCodeToLevel(code codes.Code) zapcore.Level {
|
|
switch code {
|
|
case codes.OK:
|
|
return zap.InfoLevel
|
|
case codes.Canceled:
|
|
return zap.InfoLevel
|
|
case codes.Unknown:
|
|
return zap.ErrorLevel
|
|
case codes.InvalidArgument:
|
|
return zap.InfoLevel
|
|
case codes.DeadlineExceeded:
|
|
return zap.WarnLevel
|
|
case codes.NotFound:
|
|
return zap.InfoLevel
|
|
case codes.AlreadyExists:
|
|
return zap.InfoLevel
|
|
case codes.PermissionDenied:
|
|
return zap.WarnLevel
|
|
case codes.Unauthenticated:
|
|
return zap.InfoLevel // unauthenticated requests can happen
|
|
case codes.ResourceExhausted:
|
|
return zap.WarnLevel
|
|
case codes.FailedPrecondition:
|
|
return zap.WarnLevel
|
|
case codes.Aborted:
|
|
return zap.WarnLevel
|
|
case codes.OutOfRange:
|
|
return zap.WarnLevel
|
|
case codes.Unimplemented:
|
|
return zap.ErrorLevel
|
|
case codes.Internal:
|
|
return zap.ErrorLevel
|
|
case codes.Unavailable:
|
|
return zap.WarnLevel
|
|
case codes.DataLoss:
|
|
return zap.ErrorLevel
|
|
default:
|
|
return zap.ErrorLevel
|
|
}
|
|
}
|
|
|
|
// DefaultClientCodeToLevel is the default implementation of gRPC return codes to log levels for client side.
|
|
func DefaultClientCodeToLevel(code codes.Code) zapcore.Level {
|
|
switch code {
|
|
case codes.OK:
|
|
return zap.DebugLevel
|
|
case codes.Canceled:
|
|
return zap.DebugLevel
|
|
case codes.Unknown:
|
|
return zap.InfoLevel
|
|
case codes.InvalidArgument:
|
|
return zap.DebugLevel
|
|
case codes.DeadlineExceeded:
|
|
return zap.InfoLevel
|
|
case codes.NotFound:
|
|
return zap.DebugLevel
|
|
case codes.AlreadyExists:
|
|
return zap.DebugLevel
|
|
case codes.PermissionDenied:
|
|
return zap.InfoLevel
|
|
case codes.Unauthenticated:
|
|
return zap.InfoLevel // unauthenticated requests can happen
|
|
case codes.ResourceExhausted:
|
|
return zap.DebugLevel
|
|
case codes.FailedPrecondition:
|
|
return zap.DebugLevel
|
|
case codes.Aborted:
|
|
return zap.DebugLevel
|
|
case codes.OutOfRange:
|
|
return zap.DebugLevel
|
|
case codes.Unimplemented:
|
|
return zap.WarnLevel
|
|
case codes.Internal:
|
|
return zap.WarnLevel
|
|
case codes.Unavailable:
|
|
return zap.WarnLevel
|
|
case codes.DataLoss:
|
|
return zap.WarnLevel
|
|
default:
|
|
return zap.InfoLevel
|
|
}
|
|
}
|
|
|
|
// DefaultDurationToField is the default implementation of converting request duration to a Zap field.
|
|
var DefaultDurationToField = DurationToTimeMillisField
|
|
|
|
// DurationToTimeMillisField converts the duration to milliseconds and uses the key `grpc.time_ms`.
|
|
func DurationToTimeMillisField(duration time.Duration) zapcore.Field {
|
|
return zap.Float32("grpc.time_ms", durationToMilliseconds(duration))
|
|
}
|
|
|
|
// DurationToDurationField uses a Duration field to log the request duration
|
|
// and leaves it up to Zap's encoder settings to determine how that is output.
|
|
func DurationToDurationField(duration time.Duration) zapcore.Field {
|
|
return zap.Duration("grpc.duration", duration)
|
|
}
|
|
|
|
func durationToMilliseconds(duration time.Duration) float32 {
|
|
return float32(duration.Nanoseconds()/1000) / 1000
|
|
}
|