1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
|
// Copyright (c) The go-grpc-middleware Authors.
// Licensed under the Apache License 2.0.
package logging
import (
"context"
"fmt"
"time"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// LoggableEvent defines the events a log line can be added on.
type LoggableEvent uint
const (
// StartCall is a loggable event representing start of the gRPC call.
StartCall LoggableEvent = iota
// FinishCall is a loggable event representing finish of the gRPC call.
FinishCall
// PayloadReceived is a loggable event representing received request (server) or response (client).
// Log line for this event also includes (potentially big) proto.Message of that payload in
// "grpc.request.content" (server) or "grpc.response.content" (client) field.
// NOTE: This can get quite verbose, especially for streaming calls, use with caution (e.g. debug only purposes).
PayloadReceived
// PayloadSent is a loggable event representing sent response (server) or request (client).
// Log line for this event also includes (potentially big) proto.Message of that payload in
// "grpc.response.content" (server) or "grpc.request.content" (client) field.
// NOTE: This can get quite verbose, especially for streaming calls, use with caution (e.g. debug only purposes).
PayloadSent
)
func has(events []LoggableEvent, event LoggableEvent) bool {
for _, e := range events {
if e == event {
return true
}
}
return false
}
var (
defaultOptions = &options{
loggableEvents: []LoggableEvent{StartCall, FinishCall},
codeFunc: DefaultErrorToCode,
durationFieldFunc: DefaultDurationToFields,
// levelFunc depends if it's client or server.
levelFunc: nil,
timestampFormat: time.RFC3339,
disableGrpcLogFields: nil,
}
)
type options struct {
levelFunc CodeToLevel
loggableEvents []LoggableEvent
codeFunc ErrorToCode
durationFieldFunc DurationToFields
timestampFormat string
fieldsFromCtxCallMetaFn fieldsFromCtxCallMetaFn
disableGrpcLogFields []string
}
type Option func(*options)
func evaluateServerOpt(opts []Option) *options {
optCopy := &options{}
*optCopy = *defaultOptions
optCopy.levelFunc = DefaultServerCodeToLevel
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
}
// DurationToFields function defines how to produce duration fields for logging.
type DurationToFields func(duration time.Duration) Fields
// ErrorToCode function determines the error code of an error.
// This makes using custom errors with grpc middleware easier.
type ErrorToCode func(err error) codes.Code
func DefaultErrorToCode(err error) codes.Code {
return status.Code(err)
}
// CodeToLevel function defines the mapping between gRPC return codes and interceptor log level.
type CodeToLevel func(code codes.Code) Level
// DefaultServerCodeToLevel is the helper mapper that maps gRPC return codes to log levels for server side.
func DefaultServerCodeToLevel(code codes.Code) Level {
switch code {
case codes.OK, codes.NotFound, codes.Canceled, codes.AlreadyExists, codes.InvalidArgument, codes.Unauthenticated:
return LevelInfo
case codes.DeadlineExceeded, codes.PermissionDenied, codes.ResourceExhausted, codes.FailedPrecondition, codes.Aborted,
codes.OutOfRange, codes.Unavailable:
return LevelWarn
case codes.Unknown, codes.Unimplemented, codes.Internal, codes.DataLoss:
return LevelError
default:
return LevelError
}
}
// DefaultClientCodeToLevel is the helper mapper that maps gRPC return codes to log levels for client side.
func DefaultClientCodeToLevel(code codes.Code) Level {
switch code {
case codes.OK, codes.Canceled, codes.InvalidArgument, codes.NotFound, codes.AlreadyExists, codes.ResourceExhausted,
codes.FailedPrecondition, codes.Aborted, codes.OutOfRange:
return LevelDebug
case codes.Unknown, codes.DeadlineExceeded, codes.PermissionDenied, codes.Unauthenticated:
return LevelInfo
case codes.Unimplemented, codes.Internal, codes.Unavailable, codes.DataLoss:
return LevelWarn
default:
return LevelInfo
}
}
type (
fieldsFromCtxFn func(ctx context.Context) Fields
fieldsFromCtxCallMetaFn func(ctx context.Context, c interceptors.CallMeta) Fields
)
// WithFieldsFromContext allows overriding existing or adding extra fields to all log messages per given context
func WithFieldsFromContext(f fieldsFromCtxFn) Option {
return func(o *options) {
o.fieldsFromCtxCallMetaFn = func(ctx context.Context, _ interceptors.CallMeta) Fields {
return f(ctx)
}
}
}
// WithFieldsFromContextAndCallMeta allows overriding existing or adding extra fields to all log messages per given context and interceptor.CallMeta
func WithFieldsFromContextAndCallMeta(f fieldsFromCtxCallMetaFn) Option {
return func(o *options) {
o.fieldsFromCtxCallMetaFn = f
}
}
// WithLogOnEvents customizes on what events the gRPC interceptor should log on.
func WithLogOnEvents(events ...LoggableEvent) Option {
return func(o *options) {
o.loggableEvents = events
}
}
// 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 ErrorToCode) Option {
return func(o *options) {
o.codeFunc = f
}
}
// WithDurationField customizes the function for mapping request durations to log fields.
func WithDurationField(f DurationToFields) Option {
return func(o *options) {
o.durationFieldFunc = f
}
}
// DefaultDurationToFields is the default implementation of converting request duration to a field.
var DefaultDurationToFields = DurationToTimeMillisFields
// DurationToTimeMillisFields converts the duration to milliseconds and uses the key `grpc.time_ms`.
func DurationToTimeMillisFields(duration time.Duration) Fields {
return Fields{"grpc.time_ms", fmt.Sprintf("%v", durationToMilliseconds(duration))}
}
// DurationToDurationField uses a Duration field to log the request duration
// and leaves it up to Log's encoder settings to determine how that is output.
func DurationToDurationField(duration time.Duration) Fields {
return Fields{"grpc.duration", duration.String()}
}
func durationToMilliseconds(duration time.Duration) float32 {
return float32(duration.Nanoseconds()/1000) / 1000
}
// WithTimestampFormat customizes the timestamps emitted in the log fields.
func WithTimestampFormat(format string) Option {
return func(o *options) {
o.timestampFormat = format
}
}
// WithDisableLoggingFields disables logging of gRPC fields provided.
// The following are the default logging fields:
// - SystemTag[0]
// - ComponentFieldKey
// - ServiceFieldKey
// - MethodFieldKey
// - MethodTypeFieldKey
//
// Usage example - WithDisableLoggingFields(logging.MethodFieldKey, logging.MethodTypeFieldKey)
func WithDisableLoggingFields(disableGrpcLogFields ...string) Option {
return func(o *options) {
o.disableGrpcLogFields = disableGrpcLogFields
}
}
|