Repository: surrealdb/surrealdb.go
Pull request #92 (2023-08-09 17:28:51)

Refactor logger
fix #91

ElecTwix: ```
?       github.com/surrealdb/surrealdb.go/internal/mock [no test files]
?       github.com/surrealdb/surrealdb.go/internal/rpc  [no test files]
?       github.com/surrealdb/surrealdb.go/pkg/gorilla   [no test files]
?       github.com/surrealdb/surrealdb.go/pkg/rand      [no test files]
?       github.com/surrealdb/surrealdb.go/pkg/websocket [no test files]
ok      github.com/surrealdb/surrealdb.go       0.354s
ok      github.com/surrealdb/surrealdb.go/internal/benchmark    (cached) [no tests to run]
ok      github.com/surrealdb/surrealdb.go/pkg/logger    0.002s
```

Seems like something is wrong with CI, I tested with two different machines.

ElecTwix: waiting for #88
ElecTwix: for information: this PR removed 1.20 golang version for slog

Patch:
diff --git a/pkg/gorilla/gorilla.go b/pkg/gorilla/gorilla.go
index a58bf39..1ed4377 100644
--- a/pkg/gorilla/gorilla.go
+++ b/pkg/gorilla/gorilla.go
@@ -34,7 +34,7 @@ type WebSocket struct {
 	connLock sync.Mutex
 	Timeout  time.Duration
 	Option   []Option
-	logger   *logger.LogData
+	logger   logger.Logger
 
 	responseChannels     map[string]chan rpc.RPCResponse
 	responseChannelsLock sync.RWMutex
@@ -85,12 +85,12 @@ func (ws *WebSocket) SetTimeOut(timeout time.Duration) *WebSocket {
 }
 
 // If path is empty it will use os.stdout/os.stderr
-func (ws *WebSocket) Logger(logData *logger.LogData) *WebSocket {
+func (ws *WebSocket) Logger(logData logger.Logger) *WebSocket {
 	ws.logger = logData
 	return ws
 }
 
-func (ws *WebSocket) RawLogger(logData *logger.LogData) *WebSocket {
+func (ws *WebSocket) RawLogger(logData logger.Logger) *WebSocket {
 	ws.logger = logData
 	return ws
 }
@@ -114,8 +114,7 @@ func (ws *WebSocket) Close() error {
 func (ws *WebSocket) LiveNotifications(liveQueryID string) (chan model.Notification, error) {
 	c, err := ws.createNotificationChannel(liveQueryID)
 	if err != nil {
-		ws.logger.Logger.Err(err)
-		ws.logger.LogChannel <- err.Error()
+		ws.logger.Error(err.Error())
 	}
 	return c, err
 }
@@ -240,8 +239,7 @@ func (ws *WebSocket) initialize() {
 				var res rpc.RPCResponse
 				err := ws.read(&res)
 				if err != nil {
-					ws.logger.Logger.Err(err)
-					ws.logger.LogChannel <- err.Error()
+					ws.logger.Error(err.Error())
 					continue
 				}
 				go ws.handleResponse(res)
@@ -256,8 +254,7 @@ func (ws *WebSocket) handleResponse(res rpc.RPCResponse) {
 		responseChan, ok := ws.getResponseChannel(fmt.Sprintf("%v", res.ID))
 		if !ok {
 			err := fmt.Errorf("unavailable ResponseChannel %+v", res.ID)
-			ws.logger.Logger.Err(err)
-			ws.logger.LogChannel <- err.Error()
+			ws.logger.Error(err.Error())
 			return
 		}
 		defer close(responseChan)
@@ -268,22 +265,20 @@ func (ws *WebSocket) handleResponse(res rpc.RPCResponse) {
 		resolvedID, ok := mappedRes["id"]
 		if !ok {
 			err := fmt.Errorf("response did not contain an 'id' field")
-			ws.logger.Logger.With().Str("result", fmt.Sprintf("%s", res.Result)).Err(err)
-			ws.logger.LogChannel <- err.Error()
+
+			ws.logger.Error(err.Error(), "result", fmt.Sprint(res.Result))
 			return
 		}
 		var notification model.Notification
 		err := unmarshalMapToStruct(mappedRes, &notification)
 		if err != nil {
-			ws.logger.Logger.With().Str("result", fmt.Sprintf("%s", res.Result)).Err(err)
-			ws.logger.LogChannel <- err.Error()
+			ws.logger.Error(err.Error(), "result", fmt.Sprint(res.Result))
 			return
 		}
 		LiveNotificationChan, ok := ws.getLiveChannel(notification.ID)
 		if !ok {
 			err := fmt.Errorf("unavailable ResponseChannel %+v", resolvedID)
-			ws.logger.Logger.Err(err)
-			ws.logger.LogChannel <- err.Error()
+			ws.logger.Error(err.Error(), "result", fmt.Sprint(res.Result))
 			return
 		}
 		LiveNotificationChan <- notification
diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go
index fb31330..2cce154 100644
--- a/pkg/logger/logger.go
+++ b/pkg/logger/logger.go
@@ -1,60 +1,8 @@
 package logger
 
-import (
-	"io"
-	"os"
-
-	"github.com/rs/zerolog"
-)
-
-const (
-	permission = 0664
-)
-
-type LogBuild struct {
-	writer     io.Writer
-	path       string
-	LogChannel chan string
-}
-
-type LogData struct {
-	writer     io.Writer
-	LogFile    *os.File
-	Logger     zerolog.Logger
-	LogChannel chan string
-}
-
-func New() *LogBuild {
-	return &LogBuild{}
-}
-
-func (build *LogBuild) FromPath(path string) *LogBuild {
-	build.path = path
-	return build
-}
-
-func (build *LogBuild) FromBuffer(w io.Writer) *LogBuild {
-	build.writer = w
-	return build
-}
-
-func (build *LogBuild) FromChannel(chn chan string) *LogBuild {
-	build.LogChannel = chn
-	return build
-}
-
-func (build *LogBuild) Make() (logData *LogData, err error) {
-	logData = new(LogData)
-	logData.writer = os.Stdout
-	logData.writer = build.writer
-	logData.LogChannel = build.LogChannel
-	if build.path != "" {
-		logData.LogFile, err = os.OpenFile(build.path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, permission)
-		if err != nil {
-			return nil, err
-		}
-		logData.writer = zerolog.SyncWriter(logData.LogFile)
-	}
-	logData.Logger = zerolog.New(logData.writer).With().Timestamp().Logger()
-	return
+type Logger interface {
+	Error(msg string, args ...any)
+	Warn(msg string, args ...any)
+	Info(msg string, args ...any)
+	Debug(msg string, args ...any)
 }
diff --git a/pkg/logger/slog/slog.go b/pkg/logger/slog/slog.go
new file mode 100644
index 0000000..a8e58e6
--- /dev/null
+++ b/pkg/logger/slog/slog.go
@@ -0,0 +1,30 @@
+package slog
+
+import (
+	"log/slog"
+)
+
+type SlogHandler struct {
+	logger *slog.Logger
+}
+
+func New(h slog.Handler) *SlogHandler {
+	logger := slog.New(h)
+	return &SlogHandler{logger: logger}
+}
+
+func (handler *SlogHandler) Error(msg string, args ...any) {
+	handler.logger.Error(msg, args...)
+}
+
+func (handler *SlogHandler) Warn(msg string, args ...any) {
+	handler.logger.Warn(msg, args...)
+}
+
+func (handler *SlogHandler) Info(msg string, args ...any) {
+	handler.logger.Info(msg, args...)
+}
+
+func (handler *SlogHandler) Debug(msg string, args ...any) {
+	handler.logger.Debug(msg, args...)
+}


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index b9c88e6..a87ed65 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -13,7 +13,7 @@ jobs:
   test:
     strategy:
       matrix:
-        go-version: [ '1.20', '1.21']
+        go-version: ['1.21']
     permissions:
       contents: read
       pull-requests: read
diff --git a/db_test.go b/db_test.go
index 30988f9..5eeaf70 100644
--- a/db_test.go
+++ b/db_test.go
@@ -4,12 +4,14 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
+	rawslog "log/slog"
 	"os"
 	"sync"
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/surrealdb/surrealdb.go/pkg/logger/slog"
 	"github.com/surrealdb/surrealdb.go/pkg/model"
 
 	"github.com/stretchr/testify/require"
@@ -17,6 +19,7 @@ import (
 	"github.com/surrealdb/surrealdb.go"
 	"github.com/surrealdb/surrealdb.go/pkg/constants"
 	gorilla "github.com/surrealdb/surrealdb.go/pkg/gorilla"
+
 	"github.com/surrealdb/surrealdb.go/pkg/logger"
 	"github.com/surrealdb/surrealdb.go/pkg/marshal"
 	"github.com/surrealdb/surrealdb.go/pkg/websocket"
@@ -43,22 +46,21 @@ func TestSurrealDBSuite(t *testing.T) {
 	SurrealDBSuite.wsImplementations = make(map[string]websocket.WebSocket)
 
 	// Without options
-	logData, err := createLogData(t)
-	require.NoError(t, err)
+	logData := createLogger(t)
 	SurrealDBSuite.wsImplementations["gorilla"] = gorilla.Create().Logger(logData)
 
 	// With options
-	logData, err = createLogData(t)
-	require.NoError(t, err)
+	logData = createLogger(t)
 	SurrealDBSuite.wsImplementations["gorilla_opt"] = gorilla.Create().SetTimeOut(time.Minute).SetCompression(true).Logger(logData)
 
 	RunWsMap(t, SurrealDBSuite)
 }
 
-func createLogData(t *testing.T) (*logger.LogData, error) {
+func createLogger(t *testing.T) logger.Logger {
 	t.Helper()
 	buff := bytes.NewBuffer([]byte{})
-	return logger.New().FromBuffer(buff).Make()
+	handler := rawslog.NewJSONHandler(buff, &rawslog.HandlerOptions{Level: rawslog.LevelDebug})
+	return slog.New(handler)
 }
 
 func RunWsMap(t *testing.T, s *SurrealDBTestSuite) {
diff --git a/pkg/logger/logger_test.go b/pkg/logger/logger_test.go
deleted file mode 100644
index 5269ec6..0000000
--- a/pkg/logger/logger_test.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package logger_test
-
-import (
-	"bytes"
-	"testing"
-
-	"github.com/stretchr/testify/require"
-	"github.com/surrealdb/surrealdb.go/pkg/logger"
-)
-
-func TestLog(t *testing.T) {
-	buff := bytes.NewBuffer([]byte{})
-	templogger, err := logger.New().FromBuffer(buff).Make()
-	require.NoError(t, err)
-	require.NotNil(t, templogger)
-	require.NotNil(t, templogger.Logger)
-	// Get Stats Before
-	require.Equal(t, buff.Len(), 0)
-	templogger.Logger.Info().Msg("Test")
-	// Get Stats After
-	require.Contains(t, buff.String(), "Test")
-}
diff --git a/pkg/logger/slog/slog_test.go b/pkg/logger/slog/slog_test.go
new file mode 100644
index 0000000..f86c01b
--- /dev/null
+++ b/pkg/logger/slog/slog_test.go
@@ -0,0 +1,71 @@
+package slog_test
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"testing"
+	"time"
+
+	rawslog "log/slog"
+
+	"github.com/stretchr/testify/require"
+	"github.com/surrealdb/surrealdb.go/pkg/logger/slog"
+)
+
+type testMethod struct {
+	fn    func(msg string, args ...any)
+	level rawslog.Level
+}
+
+var (
+	LogText         string = "Test Log Value"
+	CustomFieldName string = "Somekey"
+	CustomFieldVal  any    = "SomeVal"
+)
+
+type testLogJSON struct {
+	Time  time.Time `json:"time"`
+	Level string    `json:"level"`
+	Msg   string    `json:"msg"`
+	// Json field needs to match with CustomFieldName
+	CustomVal any `json:"SomeKey"`
+}
+
+func TestLogger(t *testing.T) {
+	buffer := bytes.NewBuffer([]byte{})
+
+	// level needs to be set to debug for log all
+	handler := rawslog.NewJSONHandler(buffer, &rawslog.HandlerOptions{Level: rawslog.LevelDebug})
+	logger := slog.New(handler)
+
+	testMethods := []testMethod{
+		{fn: logger.Error, level: rawslog.LevelError},
+		{fn: logger.Warn, level: rawslog.LevelWarn},
+		{fn: logger.Info, level: rawslog.LevelInfo},
+		{fn: logger.Debug, level: rawslog.LevelDebug},
+	}
+
+	for _, v := range testMethods {
+		t.Run(fmt.Sprintf("testing %s", v.level.String()), func(tAlt *testing.T) {
+			checkMethod(v.fn, buffer, v.level.String(), tAlt)
+		})
+		buffer.Reset()
+	}
+}
+
+func checkMethod(loggerFunc func(msg string, args ...any), buffer *bytes.Buffer, levelStr string, t *testing.T) {
+	require.NotEmpty(t, buffer)
+
+	loggerFunc(LogText, CustomFieldName, CustomFieldVal)
+
+	line := buffer.Bytes()
+
+	testLogJSONVal := new(testLogJSON)
+	err := json.Unmarshal(line, &testLogJSONVal)
+	require.NoError(t, err)
+
+	require.Equal(t, levelStr, testLogJSONVal.Level)
+	require.Equal(t, LogText, testLogJSONVal.Msg)
+	require.Equal(t, CustomFieldVal, testLogJSONVal.CustomVal)
+}