Files
2026-05-07 16:50:40 +02:00

138 lines
3.4 KiB
Go

package logging
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
func InterceptorLogger() logging.Logger {
return logging.LoggerFunc(func(ctx context.Context, lvl logging.Level, msg string, fields ...any) {
l := zerolog.Ctx(ctx).With().Fields(fields).Logger()
switch lvl {
case logging.LevelDebug:
l.Debug().Msg(msg)
case logging.LevelInfo:
l.Info().Msg(msg)
case logging.LevelWarn:
l.Warn().Msg(msg)
case logging.LevelError:
l.Error().Msg(msg)
default:
l.Info().Msg(msg)
}
})
}
func LoggingOpts() []logging.Option {
return []logging.Option{
logging.WithLogOnEvents(logging.StartCall, logging.FinishCall),
logging.WithLevels(levelFunc),
logging.WithDurationField(func(d time.Duration) logging.Fields {
return logging.Fields{"grpc.duration_ms", d.Milliseconds()}
}),
}
}
func levelFunc(code codes.Code) logging.Level {
switch code {
case codes.OK, codes.NotFound, codes.Canceled:
return logging.LevelInfo
case codes.InvalidArgument, codes.AlreadyExists, codes.Unauthenticated:
return logging.LevelWarn
default:
return logging.LevelError
}
}
func RequestIDInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
requestID := uuid.New().String()
logger := log.Logger.With().
Str("request_id", requestID).
Str("grpc.method", info.FullMethod).
Logger()
ctx = logger.WithContext(ctx)
resp, err := handler(ctx, req)
if err != nil {
st, _ := status.FromError(err)
zerolog.Ctx(ctx).Debug().
Str("grpc.code", st.Code().String()).
Str("grpc.error", st.Message()).
Msg("request failed")
}
return resp, err
}
}
func PeerInfoInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
md, ok := metadata.FromIncomingContext(ctx)
if ok {
evt := zerolog.Ctx(ctx).With()
if ua := md.Get("user-agent"); len(ua) > 0 {
evt = evt.Str("peer.user_agent", ua[0])
}
if auth := md.Get("authorization"); len(auth) > 0 {
evt = evt.Bool("peer.authenticated", true)
}
logger := evt.Logger()
ctx = logger.WithContext(ctx)
}
return handler(ctx, req)
}
}
func StreamRequestIDInterceptor() grpc.StreamServerInterceptor {
return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
requestID := uuid.New().String()
logger := log.Logger.With().
Str("request_id", requestID).
Str("grpc.method", info.FullMethod).
Logger()
ctx := logger.WithContext(ss.Context())
wrapped := &wrappedStream{ServerStream: ss, ctx: ctx}
return handler(srv, wrapped)
}
}
type wrappedStream struct {
grpc.ServerStream
ctx context.Context
}
func (w *wrappedStream) Context() context.Context {
return w.ctx
}
func (w *wrappedStream) SendMsg(m any) error {
zerolog.Ctx(w.ctx).Trace().Str("direction", "send").Msg(fmt.Sprintf("stream message: %T", m))
return w.ServerStream.SendMsg(m)
}
func (w *wrappedStream) RecvMsg(m any) error {
err := w.ServerStream.RecvMsg(m)
if err == nil {
zerolog.Ctx(w.ctx).Trace().Str("direction", "recv").Msg(fmt.Sprintf("stream message: %T", m))
}
return err
}