From ca1b6ca20242c81e997f7a447c963a749da96b84 Mon Sep 17 00:00:00 2001 From: Chris Marslender Date: Sat, 16 Mar 2024 21:39:18 -0500 Subject: [PATCH 1/4] Add support to RPC for slog logger, and add some helpers for all default log levels to easily pass to the client option --- go.mod | 2 +- pkg/httpclient/httpclient.go | 8 +++++ pkg/rpc/clientoptions.go | 9 +++++ pkg/rpcinterface/client.go | 4 +++ pkg/rpcinterface/loghandler.go | 46 ++++++++++++++++++++++++++ pkg/websocketclient/websocketclient.go | 8 +++++ 6 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 pkg/rpcinterface/loghandler.go diff --git a/go.mod b/go.mod index 3c13a14..fa7e291 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/chia-network/go-chia-libs -go 1.18 +go 1.21 require ( github.com/google/go-querystring v1.1.0 diff --git a/pkg/httpclient/httpclient.go b/pkg/httpclient/httpclient.go index df3c214..adc1582 100644 --- a/pkg/httpclient/httpclient.go +++ b/pkg/httpclient/httpclient.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "net/http" "net/url" "time" @@ -21,6 +22,7 @@ import ( type HTTPClient struct { config *config.ChiaConfig baseURL *url.URL + logger *slog.Logger // If set > 0, will configure http requests with a cache cacheValidTime time.Duration @@ -61,6 +63,7 @@ type HTTPClient struct { func NewHTTPClient(cfg *config.ChiaConfig, options ...rpcinterface.ClientOptionFunc) (*HTTPClient, error) { c := &HTTPClient{ config: cfg, + logger: slog.New(rpcinterface.SlogInfo()), Timeout: 10 * time.Second, // Default, overridable with client option @@ -101,6 +104,11 @@ func (c *HTTPClient) SetBaseURL(url *url.URL) error { return nil } +// SetLogHandler sets a slog compatible log handler +func (c *HTTPClient) SetLogHandler(handler slog.Handler) { + c.logger = slog.New(handler) +} + // SetCacheValidTime sets how long cache should be valid for func (c *HTTPClient) SetCacheValidTime(validTime time.Duration) { c.cacheValidTime = validTime diff --git a/pkg/rpc/clientoptions.go b/pkg/rpc/clientoptions.go index add11d6..d5791e9 100644 --- a/pkg/rpc/clientoptions.go +++ b/pkg/rpc/clientoptions.go @@ -1,6 +1,7 @@ package rpc import ( + "log/slog" "net/url" "time" @@ -63,3 +64,11 @@ func WithTimeout(timeout time.Duration) rpcinterface.ClientOptionFunc { return nil } } + +// WithLogHandler sets a slog compatible log handler to be used for logging +func WithLogHandler(handler slog.Handler) rpcinterface.ClientOptionFunc { + return func(c rpcinterface.Client) error { + c.SetLogHandler(handler) + return nil + } +} diff --git a/pkg/rpcinterface/client.go b/pkg/rpcinterface/client.go index 1e0e1b8..05cffd1 100644 --- a/pkg/rpcinterface/client.go +++ b/pkg/rpcinterface/client.go @@ -1,6 +1,7 @@ package rpcinterface import ( + "log/slog" "net/http" "net/url" @@ -14,6 +15,9 @@ type Client interface { Do(req *Request, v interface{}) (*http.Response, error) SetBaseURL(url *url.URL) error + // SetLogHandler sets a slog compatible log handler + SetLogHandler(handler slog.Handler) + // The following are added for websocket compatibility // Any implementation that these don't make sense for should just do nothing / return nil as applicable diff --git a/pkg/rpcinterface/loghandler.go b/pkg/rpcinterface/loghandler.go new file mode 100644 index 0000000..5d8be38 --- /dev/null +++ b/pkg/rpcinterface/loghandler.go @@ -0,0 +1,46 @@ +package rpcinterface + +import ( + "log/slog" + "os" +) + +// SlogError returns a text handler preconfigured to ERROR log level +func SlogError() slog.Handler { + opts := &slog.HandlerOptions{ + Level: slog.LevelError, + } + + handler := slog.NewTextHandler(os.Stdout, opts) + return handler +} + +// SlogWarn returns a text handler preconfigured to WARN log level +func SlogWarn() slog.Handler { + opts := &slog.HandlerOptions{ + Level: slog.LevelWarn, + } + + handler := slog.NewTextHandler(os.Stdout, opts) + return handler +} + +// SlogInfo returns a text handler preconfigured to INFO log level +func SlogInfo() slog.Handler { + opts := &slog.HandlerOptions{ + Level: slog.LevelInfo, + } + + handler := slog.NewTextHandler(os.Stdout, opts) + return handler +} + +// SlogDebug returns a text handler preconfigured to DEBUG log level +func SlogDebug() slog.Handler { + opts := &slog.HandlerOptions{ + Level: slog.LevelDebug, + } + + handler := slog.NewTextHandler(os.Stdout, opts) + return handler +} diff --git a/pkg/websocketclient/websocketclient.go b/pkg/websocketclient/websocketclient.go index a241a94..4ba87e2 100644 --- a/pkg/websocketclient/websocketclient.go +++ b/pkg/websocketclient/websocketclient.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "log" + "log/slog" "net/http" "net/url" "sync" @@ -28,6 +29,7 @@ const origin string = "go-chia-rpc" type WebsocketClient struct { config *config.ChiaConfig baseURL *url.URL + logger *slog.Logger // Request timeout Timeout time.Duration @@ -62,6 +64,7 @@ type WebsocketClient struct { func NewWebsocketClient(cfg *config.ChiaConfig, options ...rpcinterface.ClientOptionFunc) (*WebsocketClient, error) { c := &WebsocketClient{ config: cfg, + logger: slog.New(rpcinterface.SlogInfo()), Timeout: 10 * time.Second, // Default, overridable with client option @@ -111,6 +114,11 @@ func (c *WebsocketClient) SetBaseURL(url *url.URL) error { return nil } +// SetLogHandler sets a slog compatible log handler +func (c *WebsocketClient) SetLogHandler(handler slog.Handler) { + c.logger = slog.New(handler) +} + // NewRequest creates an RPC request for the specified service func (c *WebsocketClient) NewRequest(service rpcinterface.ServiceType, rpcEndpoint rpcinterface.Endpoint, opt interface{}) (*rpcinterface.Request, error) { request := &rpcinterface.Request{ From 4f895b446449d7fe9af510b70332f2f3a9e2e8f4 Mon Sep 17 00:00:00 2001 From: Chris Marslender Date: Sat, 16 Mar 2024 21:46:36 -0500 Subject: [PATCH 2/4] Update usages of log. to the slog compatible logger --- pkg/util/bytes.go | 2 -- pkg/websocketclient/websocketclient.go | 15 +++++++-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/pkg/util/bytes.go b/pkg/util/bytes.go index 2092085..22bce4e 100644 --- a/pkg/util/bytes.go +++ b/pkg/util/bytes.go @@ -2,7 +2,6 @@ package util import ( "fmt" - "log" "github.com/chia-network/go-chia-libs/pkg/types" ) @@ -13,7 +12,6 @@ func FormatBytes(bytes types.Uint128) string { base := uint64(1024) value := bytes.Div64(base) - log.Printf("%s %s\n", value.String(), "KiB") for _, label := range labels { if value.FitsInUint64() { valueUint64 := float64(value.Uint64()) / float64(base) diff --git a/pkg/websocketclient/websocketclient.go b/pkg/websocketclient/websocketclient.go index 4ba87e2..7f5b0ce 100644 --- a/pkg/websocketclient/websocketclient.go +++ b/pkg/websocketclient/websocketclient.go @@ -7,7 +7,6 @@ import ( "encoding/json" "fmt" "io" - "log" "log/slog" "net/http" "net/url" @@ -322,14 +321,14 @@ func (c *WebsocketClient) reconnectLoop() { handler() } for { - log.Println("Trying to reconnect...") + c.logger.Info("Trying to reconnect...") err := c.ensureConnection() if err == nil { - log.Println("Reconnected!") + c.logger.Info("Reconnected!") for topic := range c.subscriptions { err = c.doSubscribe(topic) if err != nil { - log.Printf("Error subscribing to topic %s: %s\n", topic, err.Error()) + c.logger.Error("Error subscribing to topic", "topic", topic, "error", err.Error()) } } for _, handler := range c.reconnectHandlers { @@ -338,7 +337,7 @@ func (c *WebsocketClient) reconnectLoop() { return } - log.Printf("Unable to reconnect: %s\n", err.Error()) + c.logger.Error("Unable to reconnect", "error", err.Error()) time.Sleep(5 * time.Second) } } @@ -405,12 +404,12 @@ func (c *WebsocketClient) listen() { for { _, message, err := c.conn.ReadMessage() if err != nil { - log.Printf("Error reading message on chia websocket: %s\n", err.Error()) + c.logger.Error("Error reading message on chia websocket", "error", err.Error()) if _, isCloseErr := err.(*websocket.CloseError); !isCloseErr { - log.Println("Chia websocket sent close message, attempting to close connection...") + c.logger.Debug("Chia websocket sent close message, attempting to close connection...") closeConnErr := c.conn.Close() if closeConnErr != nil { - log.Printf("Error closing chia websocket connection: %s\n", closeConnErr.Error()) + c.logger.Error("Error closing chia websocket connection", "error", closeConnErr.Error()) } } c.conn = nil From 3a42d13aab60d96829d88a58b3a4faf567a208ee Mon Sep 17 00:00:00 2001 From: Chris Marslender Date: Sat, 16 Mar 2024 21:53:29 -0500 Subject: [PATCH 3/4] Update readme to show how to set up logging (and a bit of reorganization) --- pkg/rpc/readme.md | 61 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/pkg/rpc/readme.md b/pkg/rpc/readme.md index 1a2563c..a01c4d7 100644 --- a/pkg/rpc/readme.md +++ b/pkg/rpc/readme.md @@ -189,6 +189,54 @@ There are two helper functions to subscribe to events that come over the websock `client.Subscribe(service)` - Calling this method, with an appropriate service, subscribes to any events that chia may generate that are not necessarily in responses to requests made from this client (for instance, `metrics` events fire when relevant updates are available that may impact metrics services) +## Logging + +By default, a slog compatible text logger set to INFO level will be used to log any information from the RPC clients. + +### Change Log Level + +To change the log level of the default logger, you can use a client option like the following example: + +```go +package main + +import ( + "github.com/chia-network/go-chia-libs/pkg/rpc" + "github.com/chia-network/go-chia-libs/pkg/rpcinterface" +) + +func main() { + client, err := rpc.NewClient( + rpc.ConnectionModeWebsocket, + rpc.WithAutoConfig(), + rpc.WithLogHandler(rpcinterface.SlogDebug()), + ) + if err != nil { + // an error occurred + } +} +``` + +### Custom Log Handler + +The `rpc.WithLogHandler()` method accepts a `slog.Handler` interface. Any logger can be provided as long as it conforms +to the interface. + +## Request Cache + +When using HTTP mode, there is an optional request cache that can be enabled with a configurable cache duration. To use the cache, initialize the client with the `rpc.WithCache()` option like the following example: + +```go +client, err := rpc.NewClient(rpc.ConnectionModeHTTP, rpc.WithAutoConfig(), rpc.WithCache(60 * time.Second)) +if err != nil { + // error happened +} +``` + +This example sets the cache time to 60 seconds. Any identical requests within the 60 seconds will be served from the local cache rather than making another RPC call. + +## Example RPC Calls + ### Get Transactions #### HTTP Mode @@ -287,16 +335,3 @@ if state.BlockchainState.IsPresent() { log.Println(state.BlockchainState.MustGet().Space) } ``` - -### Request Cache - -When using HTTP mode, there is an optional request cache that can be enabled with a configurable cache duration. To use the cache, initialize the client with the `rpc.WithCache()` option like the following example: - -```go -client, err := rpc.NewClient(rpc.ConnectionModeHTTP, rpc.WithAutoConfig(), rpc.WithCache(60 * time.Second)) -if err != nil { - // error happened -} -``` - -This example sets the cache time to 60 seconds. Any identical requests within the 60 seconds will be served from the local cache rather than making another RPC call. From 47cae82a20bf60c97b42b060c0a11211c160a754 Mon Sep 17 00:00:00 2001 From: Chris Marslender Date: Sat, 16 Mar 2024 22:08:50 -0500 Subject: [PATCH 4/4] Update pkg/rpc/readme.md Co-authored-by: StartToaster --- pkg/rpc/readme.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/rpc/readme.md b/pkg/rpc/readme.md index a01c4d7..8bfcc1e 100644 --- a/pkg/rpc/readme.md +++ b/pkg/rpc/readme.md @@ -219,8 +219,7 @@ func main() { ### Custom Log Handler -The `rpc.WithLogHandler()` method accepts a `slog.Handler` interface. Any logger can be provided as long as it conforms -to the interface. +The `rpc.WithLogHandler()` method accepts a `slog.Handler` interface. Any logger can be provided as long as it conforms to the interface. ## Request Cache