From 79749b01e0bbba92d58f6687c9d12b3da81f85bf Mon Sep 17 00:00:00 2001 From: Chris Marslender Date: Sat, 16 Mar 2024 22:13:00 -0500 Subject: [PATCH] =?UTF-8?q?Add=20support=20to=20RPC=20for=20slog=20logger,?= =?UTF-8?q?=20and=20add=20some=20helpers=20for=20all=20defa=E2=80=A6=20(#1?= =?UTF-8?q?14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add support to RPC for slog logger, and add some helpers for all default log levels to easily pass to the client option * Update usages of log. to the slog compatible logger * Update readme to show how to set up logging (and a bit of reorganization) * Update pkg/rpc/readme.md Co-authored-by: StartToaster --------- Co-authored-by: StartToaster --- go.mod | 2 +- pkg/httpclient/httpclient.go | 8 ++++ pkg/rpc/clientoptions.go | 9 ++++ pkg/rpc/readme.md | 60 ++++++++++++++++++++------ pkg/rpcinterface/client.go | 4 ++ pkg/rpcinterface/loghandler.go | 46 ++++++++++++++++++++ pkg/util/bytes.go | 2 - pkg/websocketclient/websocketclient.go | 23 ++++++---- 8 files changed, 130 insertions(+), 24 deletions(-) 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/rpc/readme.md b/pkg/rpc/readme.md index 1a2563c..8bfcc1e 100644 --- a/pkg/rpc/readme.md +++ b/pkg/rpc/readme.md @@ -189,6 +189,53 @@ 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 +334,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. 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/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 a241a94..7f5b0ce 100644 --- a/pkg/websocketclient/websocketclient.go +++ b/pkg/websocketclient/websocketclient.go @@ -7,7 +7,7 @@ import ( "encoding/json" "fmt" "io" - "log" + "log/slog" "net/http" "net/url" "sync" @@ -28,6 +28,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 +63,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 +113,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{ @@ -314,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 { @@ -330,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) } } @@ -397,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