Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
42c9f2c
feat: add support for builtin plugins
nnicora Jul 14, 2025
a7300a4
feat: change the builtin plugins
nnicora Jul 14, 2025
87dc89d
feat: change the builtin plugins
nnicora Jul 14, 2025
0b03b93
feat: change the builtin plugins
nnicora Jul 14, 2025
00a2b40
feat: change the builtin plugins
nnicora Jul 14, 2025
17d0c35
feat: improve the error handling
nnicora Jul 14, 2025
3896337
feat: fix logger of the plugin
nnicora Jul 14, 2025
710510c
fix the test
nnicora Jul 14, 2025
dc04049
remove unused resources
nnicora Jul 14, 2025
1d7d77a
remove unused resources
nnicora Jul 14, 2025
37e7609
small changes on the plugins
nnicora Jul 14, 2025
842771e
small changes on the plugins
nnicora Jul 14, 2025
60f1fbc
small changes on the plugins
nnicora Jul 14, 2025
634125e
small changes on the plugins
nnicora Jul 14, 2025
aa4d1cb
small changes on the plugins
nnicora Jul 14, 2025
77dcaf7
small change
nnicora Jul 15, 2025
712c6a4
small changes
nnicora Jul 15, 2025
1a0c47a
fix logger
nnicora Jul 15, 2025
80b88dc
add some validation for missing plugin name and type values
nnicora Jul 15, 2025
6d73116
fix the tests
nnicora Jul 15, 2025
ef6a946
change the value of path
nnicora Jul 15, 2025
a75ace2
use default logger if not specified
nnicora Jul 15, 2025
c7b8409
refactor the plugin logger
nnicora Jul 15, 2025
4062fa2
log text renaming
nnicora Jul 16, 2025
f1d1675
refactor the plugin logger
nnicora Jul 16, 2025
55673ff
refactor the plugin logger
nnicora Jul 16, 2025
66cf279
separate the logger test frm plugin
nnicora Jul 16, 2025
e18ff74
renamed a method
nnicora Jul 16, 2025
a522fc4
refactor the slog2hclog
nnicora Jul 16, 2025
8fdce43
refactor the slog2hclog
nnicora Jul 16, 2025
f85269c
refactor the slog2hclog
nnicora Jul 16, 2025
ed7a8c9
refactor the slog2hclog
nnicora Jul 16, 2025
4078f61
refactor the slog2hclog
nnicora Jul 16, 2025
036bfb5
refactor the slog2hclog
nnicora Jul 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ go 1.24.2
require (
github.com/hashicorp/go-hclog v1.6.3
github.com/hashicorp/go-plugin v1.6.3
github.com/magodo/slog2hclog v0.0.0-20240614031327-090ebd72a033
github.com/zeebo/errs/v2 v2.0.5
golang.org/x/sys v0.33.0
google.golang.org/grpc v1.72.2
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
github.com/magodo/slog2hclog v0.0.0-20240614031327-090ebd72a033 h1:K2seYsMAzoICCLdDe7uU2WyaACLW+tvdTWG3QB+pyec=
github.com/magodo/slog2hclog v0.0.0-20240614031327-090ebd72a033/go.mod h1:8PvdX1kpjMEmR7LTNZ0QulFpD9j3E/eJijru+4nHY7M=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
Expand Down
2 changes: 1 addition & 1 deletion internal/bootstrap/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type HostDialer interface {

// register given servers with the gRPC server. The given dialer and logger will
// be used when the plugins are initialized.
func register(s *grpc.Server, servers []api.ServiceServer, logger hclog.Logger, dialer HostDialer) {
func Register(s *grpc.Server, servers []api.ServiceServer, logger hclog.Logger, dialer HostDialer) {
var names []string
var impls []any
for _, server := range servers {
Expand Down
2 changes: 1 addition & 1 deletion internal/bootstrap/register_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func TestRegister(t *testing.T) {
grpcSrv := grpc.NewServer()

// Act
register(grpcSrv, tc.svcs, hclog.Default(), hostDialerMock{})
Register(grpcSrv, tc.svcs, hclog.Default(), hostDialerMock{})
})
}
}
Expand Down
2 changes: 1 addition & 1 deletion internal/bootstrap/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func newHCPlugin(logger hclog.Logger, pluginServer api.PluginServer, serviceServ
}

func (p *hcServer) GRPCServer(broker *goplugin.GRPCBroker, server *grpc.Server) (err error) {
register(server, p.servers, p.logger, &hcDialer{broker: broker})
Register(server, p.servers, p.logger, &hcDialer{broker: broker})
return nil
}

Expand Down
87 changes: 87 additions & 0 deletions internal/slog2hclog/logger_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package slog2hclog

import (
"log/slog"
"testing"

"github.com/hashicorp/go-hclog"
)

func TestSetLogLevel(t *testing.T) {
// create test cases
tests := []struct {
name string
input string
want hclog.Level
}{
{
name: "zero values",
want: hclog.Info,
}, {
name: "invalid input",
input: "invalid",
want: hclog.Info,
}, {
name: "debug lower case",
input: "debug",
want: hclog.Debug,
}, {
name: "debug upper case",
input: "DEBUG",
want: hclog.Debug,
}, {
name: "debug mixed case",
input: "DEbug",
want: hclog.Debug,
}, {
name: "info lower case",
input: "info",
want: hclog.Info,
}, {
name: "info upper case",
input: "INFO",
want: hclog.Info,
}, {
name: "info mixed case",
input: "INfo",
want: hclog.Info,
}, {
name: "warn lower case",
input: "warn",
want: hclog.Warn,
}, {
name: "warn upper case",
input: "WARN",
want: hclog.Warn,
}, {
name: "warn mixed case",
input: "WArn",
want: hclog.Warn,
}, {
name: "error lower case",
input: "error",
want: hclog.Error,
}, {
name: "error upper case",
input: "ERROR",
want: hclog.Error,
}, {
name: "error mixed case",
input: "ERRor",
want: hclog.Error,
},
}

// run the tests
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Act
log := NewWithLevel(slog.Default(), tc.input)

// Assert
if got := log.GetLevel(); got != tc.want {
t.Errorf("expected value: %v, got: %s", tc.want, got)
}
})
}
}
245 changes: 245 additions & 0 deletions internal/slog2hclog/slog2hclog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
package slog2hclog

import (
"context"
"io"
"log"
"log/slog"
"sort"
"strings"

"github.com/hashicorp/go-hclog"
)

type stdslogWrapper struct {
slog *slog.Logger
oriSlog *slog.Logger
lvar *slog.LevelVar
names []string
args []interface{}
}

func (s *stdslogWrapper) clone() *stdslogWrapper {
newSlog := *s.slog
var oriSlog *slog.Logger = nil
if s.oriSlog != nil {
newOriSlog := *s.oriSlog
oriSlog = &newOriSlog
}
return &stdslogWrapper{
slog: &newSlog,
oriSlog: oriSlog,
names: append([]string{}, s.names...),
args: append([]interface{}{}, s.args...),
}
}

var _ hclog.Logger = &stdslogWrapper{}

const (
SlogLevelTrace = slog.LevelDebug - 4
SlogLevelOff = slog.LevelError + 4
)

var levelMapToSlog = map[hclog.Level]slog.Level{
hclog.Off: SlogLevelOff,
hclog.Error: slog.LevelError,
hclog.Warn: slog.LevelWarn,
hclog.Info: slog.LevelInfo,
hclog.Debug: slog.LevelDebug,
hclog.Trace: SlogLevelTrace,
}

var levelMapFromSlog = map[slog.Level]hclog.Level{
SlogLevelOff: hclog.Off,
slog.LevelError: hclog.Error,
slog.LevelWarn: hclog.Warn,
slog.LevelInfo: hclog.Info,
slog.LevelDebug: hclog.Debug,
SlogLevelTrace: hclog.Trace,
}

type levelOverrideHandler struct {
slog.Handler
overrideLevel slog.Level
}

func (h *levelOverrideHandler) Enabled(_ context.Context, level slog.Level) bool {
// Force-enable only messages equal or above overrideLevel
return level >= h.overrideLevel
}

// New wraps a slog.Logger to a hclog.Logger with info log level.
func New(l *slog.Logger) hclog.Logger {
return NewWithLevel(l, "info")
}

// NewWithLevel wraps a slog.Logger to a hclog.Logger.
func NewWithLevel(l *slog.Logger, logLevel string) hclog.Logger {
level := hclog.LevelFromString(logLevel)
wrapper := &stdslogWrapper{
slog: slog.New(&levelOverrideHandler{
Handler: l.Handler(),
overrideLevel: levelMapToSlog[level],
}),
oriSlog: nil,
lvar: new(slog.LevelVar),
names: []string{},
args: []interface{}{},
}
wrapper.SetLevel(level)

return wrapper
}

func (s *stdslogWrapper) Trace(msg string, args ...interface{}) {
s.slog.Log(context.Background(), SlogLevelTrace, msg, args...)
}

func (s *stdslogWrapper) Debug(msg string, args ...interface{}) {
s.slog.Debug(msg, args...)
}

func (s *stdslogWrapper) Info(msg string, args ...interface{}) {
s.slog.Info(msg, args...)
}

func (s *stdslogWrapper) Warn(msg string, args ...interface{}) {
s.slog.Warn(msg, args...)
}

func (s *stdslogWrapper) Error(msg string, args ...interface{}) {
s.slog.Error(msg, args...)
}

func (s *stdslogWrapper) GetLevel() hclog.Level {
if s.lvar == nil {
// lvar not set indicates the source slog.Logger has a fixed log level (or a default level, which equals to Info).
// In this case, we enumerate the log levels from lowest (Trace) to get the effective log level.
return s.getLowestLevel()
}
return levelMapFromSlog[s.lvar.Level()]
}

// SetLevel only applies when the source slog.Logger has a slog.LevelVar level.
func (s *stdslogWrapper) SetLevel(level hclog.Level) {
if s.lvar != nil {
s.lvar.Set(levelMapToSlog[level])
}
}

func (s *stdslogWrapper) IsTrace() bool {
return s.slog.Enabled(context.Background(), SlogLevelTrace)
}

func (s *stdslogWrapper) IsDebug() bool {
return s.slog.Enabled(context.Background(), slog.LevelDebug)
}

func (s *stdslogWrapper) IsInfo() bool {
return s.slog.Enabled(context.Background(), slog.LevelInfo)
}

func (s *stdslogWrapper) IsWarn() bool {
return s.slog.Enabled(context.Background(), slog.LevelWarn)
}

func (s *stdslogWrapper) IsError() bool {
return s.slog.Enabled(context.Background(), slog.LevelError)
}

func (s *stdslogWrapper) Log(level hclog.Level, msg string, args ...interface{}) {
s.slog.Log(context.Background(), levelMapToSlog[level], msg, args...)
}

func (s *stdslogWrapper) Name() string {
return strings.Join(s.names, ".")
}

func (s *stdslogWrapper) Named(name string) hclog.Logger {
sl := s.clone()
if len(s.names) == 0 {
newSlog := *sl.slog
sl.oriSlog = &newSlog
}
sl.names = append(sl.names, name)
sl.slog = slog.New(&levelOverrideHandler{
Handler: s.slog.WithGroup(name).Handler(),
overrideLevel: levelMapToSlog[s.GetLevel()],
})
return sl
}

func (s *stdslogWrapper) ResetNamed(name string) hclog.Logger {
sl := s.clone()

// Empty name indicates to clear the name
if name == "" {
if len(sl.names) == 0 {
return sl
}
sl.names = []string{}
sl.slog = sl.oriSlog
sl.oriSlog = nil
return sl
}

// Non-empty name indicates to set the name
if len(sl.names) == 0 {
return sl.Named(name)
}
sl.names = []string{}
sl.slog = sl.oriSlog
sl.oriSlog = nil
return sl.Named(name)
}

func (s *stdslogWrapper) With(args ...interface{}) hclog.Logger {
sl := s.clone()
sl.slog = s.slog.With(args...)
sl.args = append(sl.args, args...)
return sl
}

func (s *stdslogWrapper) ImpliedArgs() []interface{} {
return s.args
}

func (s *stdslogWrapper) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger {
if opts == nil {
opts = &hclog.StandardLoggerOptions{}
}

return log.New(s.StandardWriter(opts), "", 0)
}

func (s *stdslogWrapper) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer {
newLog := s.clone()
return &stdlogAdapter{
log: newLog,
inferLevels: opts.InferLevels,
inferLevelsWithTimestamp: opts.InferLevelsWithTimestamp,
forceLevel: opts.ForceLevel,
}
}

func (s *stdslogWrapper) getLowestLevel() hclog.Level {
ctx := context.Background()

var slogLvls []slog.Level
for lvlSlog := range levelMapFromSlog {
slogLvls = append(slogLvls, lvlSlog)
}
// Sort the slog levels from Trace up to Error
sort.Slice(slogLvls, func(i, j int) bool {
return int(slogLvls[i]) < int(slogLvls[j])
})

for _, lvlSlog := range slogLvls {
lvl := levelMapFromSlog[lvlSlog]
if s.slog.Enabled(ctx, lvlSlog) {
return lvl
}
}
return hclog.Off
}
Loading