diff --git a/cmd/run.go b/cmd/run.go index 673d7bd9..3a3cd02b 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -92,7 +92,7 @@ var runCmd = &cobra.Command{ // Create a new logger from the config loggerCfg := loggerConfig() logger := logging.NewLogger(loggerCfg) - // TODO: Use https://github.com/dcarbone/zadapters to adapt hclog to zerolog + // This is a notification hook, so we don't care about the result. data, err := structpb.NewStruct(map[string]interface{}{ "timeFormat": loggerCfg.TimeFormat, @@ -215,12 +215,6 @@ var runCmd = &cobra.Command{ // Internal event-loop load balancing options gnet.WithLoadBalancing(serverConfig.LoadBalancer), - // Logger options - // TODO: This is a temporary solution and will be replaced. - // gnet.WithLogger(logrus.New()), - // gnet.WithLogPath("./gnet.log"), - // gnet.WithLogLevel(zapcore.DebugLevel), - // Buffer options gnet.WithReadBufferCap(serverConfig.ReadBufferCap), gnet.WithWriteBufferCap(serverConfig.WriteBufferCap), diff --git a/go.mod b/go.mod index 5b595925..1650873b 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.19 require ( github.com/fergusstrange/embedded-postgres v1.19.0 github.com/google/go-cmp v0.5.9 + github.com/hashicorp/go-hclog v1.4.0 github.com/hashicorp/go-plugin v1.4.8 github.com/knadh/koanf v1.4.4 github.com/mitchellh/mapstructure v1.5.0 @@ -21,7 +22,6 @@ require ( github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/hashicorp/go-hclog v1.4.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lib/pq v1.10.7 // indirect diff --git a/logging/hclog_adapter.go b/logging/hclog_adapter.go new file mode 100644 index 00000000..3e8c97c4 --- /dev/null +++ b/logging/hclog_adapter.go @@ -0,0 +1,220 @@ +package logging + +import ( + "fmt" + "io" + "log" + "reflect" + + "github.com/hashicorp/go-hclog" + "github.com/rs/zerolog" +) + +// NewHcLogAdapter creates a new hclog.Logger that wraps a zerolog.Logger. +func NewHcLogAdapter(logger *zerolog.Logger, name string) hclog.Logger { + return &HcLogAdapter{logger, name, nil} +} + +type HcLogAdapter struct { + logger *zerolog.Logger + name string + + impliedArgs []interface{} +} + +func (h HcLogAdapter) Log(level hclog.Level, msg string, args ...interface{}) { + switch level { + case hclog.Off: + return + case hclog.NoLevel: + return + case hclog.Trace: + h.Trace(msg, args...) + case hclog.Debug: + h.Debug(msg, args...) + case hclog.Info: + h.Info(msg, args...) + case hclog.Warn: + h.Warn(msg, args...) + case hclog.Error: + h.Error(msg, args...) + } +} + +func (h HcLogAdapter) Trace(msg string, args ...interface{}) { + h.logger.Trace().Fields(ToMap(args)).Msg(msg) +} + +func (h HcLogAdapter) Debug(msg string, args ...interface{}) { + h.logger.Debug().Fields(ToMap(args)).Msg(msg) +} + +func (h HcLogAdapter) Info(msg string, args ...interface{}) { + h.logger.Info().Fields(ToMap(args)).Msg(msg) +} + +func (h HcLogAdapter) Warn(msg string, args ...interface{}) { + h.logger.Warn().Fields(ToMap(args)).Msg(msg) +} + +func (h HcLogAdapter) Error(msg string, args ...interface{}) { + h.logger.Error().Fields(ToMap(args)).Msg(msg) +} + +func (h HcLogAdapter) GetLevel() hclog.Level { + switch h.logger.GetLevel() { + case zerolog.Disabled: + return hclog.Off + case zerolog.NoLevel: + return hclog.NoLevel + case zerolog.TraceLevel: + return hclog.Trace + case zerolog.DebugLevel: + return hclog.Debug + case zerolog.InfoLevel: + return hclog.Info + case zerolog.WarnLevel: + return hclog.Warn + case zerolog.ErrorLevel: + return hclog.Error + case zerolog.FatalLevel: + return hclog.Error + case zerolog.PanicLevel: + return hclog.Error + } + return hclog.NoLevel +} + +func (h HcLogAdapter) IsTrace() bool { + return h.logger.GetLevel() >= zerolog.TraceLevel +} + +func (h HcLogAdapter) IsDebug() bool { + return h.logger.GetLevel() >= zerolog.DebugLevel +} + +func (h HcLogAdapter) IsInfo() bool { + return h.logger.GetLevel() >= zerolog.InfoLevel +} + +func (h HcLogAdapter) IsWarn() bool { + return h.logger.GetLevel() >= zerolog.WarnLevel +} + +func (h HcLogAdapter) IsError() bool { + return h.logger.GetLevel() >= zerolog.ErrorLevel +} + +func (h HcLogAdapter) ImpliedArgs() []interface{} { + // Not supported + return nil +} + +func (h HcLogAdapter) With(args ...interface{}) hclog.Logger { + logger := h.logger.With().Fields(ToMap(args)).Logger() + return NewHcLogAdapter(&logger, h.Name()) +} + +func (h HcLogAdapter) Name() string { + return h.name +} + +func (h HcLogAdapter) Named(name string) hclog.Logger { + return NewHcLogAdapter(h.logger, name) +} + +func (h HcLogAdapter) ResetNamed(name string) hclog.Logger { + return &h +} + +func (h *HcLogAdapter) SetLevel(level hclog.Level) { + leveledLog := h.logger.Level(convertLevel(level)) + h.logger = &leveledLog +} + +func (h HcLogAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger { + if opts == nil { + opts = &hclog.StandardLoggerOptions{} + } + return log.New(h.StandardWriter(opts), "", 0) +} + +func (h HcLogAdapter) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer { + v := reflect.ValueOf(h.logger) + w := v.FieldByName("w") + writer, ok := w.Interface().(zerolog.LevelWriter) + if !ok { + return nil + } + return writer +} + +func convertLevel(level hclog.Level) zerolog.Level { + switch level { + case hclog.Off: + return zerolog.Disabled + case hclog.NoLevel: + return zerolog.NoLevel + case hclog.Trace: + return zerolog.TraceLevel + case hclog.Debug: + return zerolog.DebugLevel + case hclog.Info: + return zerolog.InfoLevel + case hclog.Warn: + return zerolog.WarnLevel + case hclog.Error: + return zerolog.ErrorLevel + } + return zerolog.NoLevel +} + +func ToMap(keyValues []interface{}) map[string]interface{} { + mapped := map[string]interface{}{} + + if len(keyValues) == 0 { + return mapped + } + + if len(keyValues)%2 == 1 { + keyValues = append(keyValues, nil) + } + + for i := 0; i < len(keyValues); i += 2 { + merge(mapped, keyValues[i], keyValues[i+1]) + } + + return mapped +} + +func merge(mapped map[string]interface{}, key, value interface{}) { + var casted string + + switch castedKey := key.(type) { + case string: + casted = castedKey + case fmt.Stringer: + casted = safeString(castedKey) + default: + casted = fmt.Sprint(castedKey) + } + + mapped[casted] = value +} + +//nolint:nonamedreturns +func safeString(str fmt.Stringer) (s string) { + defer func() { + if panicVal := recover(); panicVal != nil { + if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() { + s = "NULL" + } else { + panic(panicVal) + } + } + }() + + s = str.String() + + return +} diff --git a/plugin/registry.go b/plugin/registry.go index d35bc0c3..1b19f6b0 100644 --- a/plugin/registry.go +++ b/plugin/registry.go @@ -4,6 +4,7 @@ import ( "context" "os/exec" + "github.com/gatewayd-io/gatewayd/logging" pluginV1 "github.com/gatewayd-io/gatewayd/plugin/v1" "github.com/gatewayd-io/gatewayd/pool" goplugin "github.com/hashicorp/go-plugin" @@ -13,9 +14,10 @@ import ( ) const ( - DefaultMinPort uint = 50000 - DefaultMaxPort uint = 60000 - PluginPriorityStart uint = 1000 + DefaultMinPort uint = 50000 + DefaultMaxPort uint = 60000 + PluginPriorityStart uint = 1000 + LoggerName string = "plugin" ) var handshakeConfig = goplugin.HandshakeConfig{ @@ -140,6 +142,8 @@ func (reg *RegistryImpl) LoadPlugins(pluginConfig *koanf.Koanf) { // have a priority of 0 to 999, and user-defined plugins have a priority of 1000 or greater. plugin.Priority = Priority(PluginPriorityStart + uint(priority)) + logAdapter := logging.NewHcLogAdapter(®.hooksConfig.Logger, LoggerName) + plugin.client = goplugin.NewClient( &goplugin.ClientConfig{ HandshakeConfig: handshakeConfig, @@ -149,6 +153,7 @@ func (reg *RegistryImpl) LoadPlugins(pluginConfig *koanf.Koanf) { goplugin.ProtocolGRPC, }, // SecureConfig: nil, + Logger: logAdapter, Managed: true, MinPort: DefaultMinPort, MaxPort: DefaultMaxPort,