From 0cf65eef70b82abbee85a39dec6772db375e21e6 Mon Sep 17 00:00:00 2001 From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> Date: Sun, 14 Apr 2024 11:15:04 +0200 Subject: [PATCH] feat: improve gnodev logging (#1790) --- contribs/gnodev/go.mod | 10 ++ contribs/gnodev/go.sum | 24 +++ contribs/gnodev/main.go | 205 +++++++++++++---------- contribs/gnodev/pkg/dev/node.go | 91 +++++++--- contribs/gnodev/pkg/logger/colors.go | 60 +++++++ contribs/gnodev/pkg/logger/log_column.go | 199 ++++++++++++++++++++++ contribs/gnodev/pkg/logger/log_zap.go | 38 +++++ contribs/gnodev/pkg/rawterm/rawterm.go | 114 ++----------- gno.land/pkg/gnoland/app.go | 42 +++-- gno.land/pkg/gnoland/node_inmemory.go | 25 +-- gno.land/pkg/integration/testing_node.go | 7 +- 11 files changed, 573 insertions(+), 242 deletions(-) create mode 100644 contribs/gnodev/pkg/logger/colors.go create mode 100644 contribs/gnodev/pkg/logger/log_column.go create mode 100644 contribs/gnodev/pkg/logger/log_zap.go diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index 2f74709f360..9a9d1a9f798 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -5,15 +5,19 @@ go 1.21 replace github.com/gnolang/gno => ../.. require ( + github.com/charmbracelet/lipgloss v0.9.1 + github.com/charmbracelet/log v0.3.1 github.com/fsnotify/fsnotify v1.7.0 github.com/gnolang/gno v0.0.0-00010101000000-000000000000 github.com/gorilla/websocket v1.5.1 + github.com/muesli/termenv v0.15.2 go.uber.org/zap v1.27.0 golang.org/x/term v0.18.0 ) require ( dario.cat/mergo v1.0.0 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/btcsuite/btcd/btcutil v1.1.5 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect @@ -22,6 +26,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -33,10 +38,15 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect github.com/jaekwon/testify v1.6.1 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/muesli/reflow v0.3.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/peterbourgon/ff/v3 v3.4.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rivo/uniseg v0.4.3 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/rs/cors v1.10.1 // indirect github.com/stretchr/testify v1.9.0 // indirect diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum index 57943aa60ee..028c3f6f56e 100644 --- a/contribs/gnodev/go.sum +++ b/contribs/gnodev/go.sum @@ -1,6 +1,8 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd h1:js1gPwhcFflTZ7Nzl7WHaOTlTr5hIrR4n1NM4v9n4Kw= @@ -28,6 +30,10 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= +github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= +github.com/charmbracelet/log v0.3.1 h1:TjuY4OBNbxmHWSwO3tosgqs5I3biyY8sQPny/eCMTYw= +github.com/charmbracelet/log v0.3.1/go.mod h1:OR4E1hutLsax3ZKpXbgUqPtTjQfrh1pG3zwHGWuuq8g= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= @@ -51,6 +57,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk= github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -108,6 +116,17 @@ github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6 github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/linxGnu/grocksdb v1.6.20 h1:C0SNv12/OBr/zOdGw6reXS+mKpIdQGb/AkZWjHYnO64= github.com/linxGnu/grocksdb v1.6.20/go.mod h1:IbTMGpmWg/1pg2hcG9LlxkqyqiJymdCweaUrzsLRFmg= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= @@ -129,6 +148,10 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= +github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= @@ -194,6 +217,7 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= diff --git a/contribs/gnodev/main.go b/contribs/gnodev/main.go index df68bf54bec..6e6d12fcbdc 100644 --- a/contribs/gnodev/main.go +++ b/contribs/gnodev/main.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "io" + "log/slog" "net" "net/http" "os" @@ -12,27 +13,28 @@ import ( "strings" "time" + "github.com/charmbracelet/lipgloss" "github.com/fsnotify/fsnotify" "github.com/gnolang/gno/contribs/gnodev/pkg/dev" gnodev "github.com/gnolang/gno/contribs/gnodev/pkg/dev" "github.com/gnolang/gno/contribs/gnodev/pkg/emitter" + "github.com/gnolang/gno/contribs/gnodev/pkg/logger" "github.com/gnolang/gno/contribs/gnodev/pkg/rawterm" "github.com/gnolang/gno/contribs/gnodev/pkg/watcher" "github.com/gnolang/gno/gno.land/pkg/gnoweb" - "github.com/gnolang/gno/gno.land/pkg/log" + zaplog "github.com/gnolang/gno/gno.land/pkg/log" "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/tm2/pkg/commands" osm "github.com/gnolang/gno/tm2/pkg/os" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" + "github.com/muesli/termenv" ) const ( NodeLogName = "Node" WebLogName = "GnoWeb" KeyPressLogName = "KeyPress" - EventServerLogName = "Events" + EventServerLogName = "Event" ) type devCfg struct { @@ -41,13 +43,14 @@ type devCfg struct { nodeP2PListenerAddr string nodeProxyAppListenerAddr string - minimal bool - verbose bool - hotreload bool - noWatch bool - noReplay bool - maxGas int64 - chainId string + minimal bool + verbose bool + hotreload bool + noWatch bool + noReplay bool + maxGas int64 + chainId string + serverMode bool } var defaultDevOptions = &devCfg{ @@ -105,6 +108,13 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) { "do not load packages from examples directory", ) + fs.BoolVar( + &c.serverMode, + "server-mode", + defaultDevOptions.serverMode, + "disable interaction, and adjust logging for server use.", + ) + fs.BoolVar( &c.verbose, "verbose", @@ -141,7 +151,7 @@ func (c *devCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execDev(cfg *devCfg, args []string, io commands.IO) error { +func execDev(cfg *devCfg, args []string, io commands.IO) (err error) { ctx, cancel := context.WithCancelCause(context.Background()) defer cancel(nil) @@ -160,7 +170,7 @@ func execDev(cfg *devCfg, args []string, io commands.IO) error { } // Setup Raw Terminal - rt, restore, err := setupRawTerm(io) + rt, restore, err := setupRawTerm(cfg, io) if err != nil { return fmt.Errorf("unable to init raw term: %w", err) } @@ -168,25 +178,28 @@ func execDev(cfg *devCfg, args []string, io commands.IO) error { // Setup trap signal osm.TrapSignal(func() { - restore() cancel(nil) + restore() }) - zapLoggerEvents := NewZapLogger(rt.NamespacedWriter(EventServerLogName), zapcore.DebugLevel) - loggerEvents := log.ZapLoggerToSlog(zapLoggerEvents) + logger := setuplogger(cfg, rt) + loggerEvents := logger.WithGroup(EventServerLogName) emitterServer := emitter.NewServer(loggerEvents) // Setup Dev Node // XXX: find a good way to export or display node logs - devNode, err := setupDevNode(ctx, cfg, emitterServer, rt, pkgpaths) + devNode, err := setupDevNode(ctx, logger, cfg, emitterServer, pkgpaths) if err != nil { return err } defer devNode.Close() - rt.Taskf(NodeLogName, "Listener: %s\n", devNode.GetRemoteAddress()) - rt.Taskf(NodeLogName, "Default Address: %s\n", gnodev.DefaultCreator.String()) - rt.Taskf(NodeLogName, "Chain ID: %s\n", cfg.chainId) + logger.WithGroup(NodeLogName). + Info("node started", + "lisn", devNode.GetRemoteAddress(), + "addr", gnodev.DefaultCreator.String(), + "chainID", cfg.chainId, + ) // Create server mux := http.NewServeMux() @@ -197,7 +210,7 @@ func execDev(cfg *devCfg, args []string, io commands.IO) error { defer server.Close() // Setup gnoweb - webhandler := setupGnoWebServer(cfg, devNode, rt) + webhandler := setupGnoWebServer(logger.WithGroup(WebLogName), cfg, devNode) // Setup HotReload if needed if !cfg.noWatch { @@ -213,7 +226,9 @@ func execDev(cfg *devCfg, args []string, io commands.IO) error { cancel(err) }() - rt.Taskf(WebLogName, "Listener: http://%s\n", server.Addr) + logger.WithGroup(WebLogName). + Info("gnoweb started", + "lisn", fmt.Sprintf("http://%s", server.Addr)) watcher, err := watcher.NewPackageWatcher(loggerEvents, emitterServer) if err != nil { @@ -224,35 +239,28 @@ func execDev(cfg *devCfg, args []string, io commands.IO) error { // Add node pkgs to watcher watcher.AddPackages(devNode.ListPkgs()...) - // GnoDev should be ready, run event loop - rt.Taskf("[Ready]", "for commands and help, press `h`") + logger.WithGroup("--- READY").Info("for commands and help, press `h`") // Run the main event loop - return runEventLoop(ctx, cfg, rt, devNode, watcher) + return runEventLoop(ctx, logger, rt, devNode, watcher) } -// XXX: Automatize this the same way command does -func printHelper(rt *rawterm.RawTerm) { - rt.Taskf("Helper", ` -Gno Dev Helper: - H Help - display this message - R Reload - Reload all packages to take change into account. - Ctrl+R Reset - Reset application state. - Ctrl+C Exit - Exit the application -`) -} +var helper string = ` +H Help - display this message +R Reload - Reload all packages to take change into account. +Ctrl+R Reset - Reset application state. +Ctrl+C Exit - Exit the application +` func runEventLoop( ctx context.Context, - cfg *devCfg, + logger *slog.Logger, rt *rawterm.RawTerm, dnode *dev.Node, watch *watcher.PackageWatcher, ) error { - nodeOut := rt.NamespacedWriter(NodeLogName) - keyOut := rt.NamespacedWriter(KeyPressLogName) - keyPressCh := listenForKeyPress(keyOut, rt) + keyPressCh := listenForKeyPress(logger.WithGroup(KeyPressLogName), rt) for { var err error @@ -264,41 +272,54 @@ func runEventLoop( return nil } - fmt.Fprintln(nodeOut, "Loading package updates...") + // fmt.Fprintln(nodeOut, "Loading package updates...") if err = dnode.UpdatePackages(pkgs.PackagesPath()...); err != nil { return fmt.Errorf("unable to update packages: %w", err) } - fmt.Fprintln(nodeOut, "Reloading...") - err = dnode.Reload(ctx) - - checkForError(rt, err) + logger.WithGroup(NodeLogName).Info("reloading...") + if err = dnode.Reload(ctx); err != nil { + logger.WithGroup(NodeLogName). + Error("unable to reload node", "err", err) + } case key, ok := <-keyPressCh: if !ok { return nil } - if cfg.verbose { - fmt.Fprintf(keyOut, "<%s>\n", key.String()) - } + logger.WithGroup(KeyPressLogName).Debug( + fmt.Sprintf("<%s>", key.String()), + ) switch key.Upper() { case rawterm.KeyH: - printHelper(rt) + logger.Info("Gno Dev Helper", "helper", helper) case rawterm.KeyR: - fmt.Fprintln(nodeOut, "Reloading all packages...") - checkForError(nodeOut, dnode.ReloadAll(ctx)) + logger.WithGroup(NodeLogName).Info("reloading...") + if err = dnode.ReloadAll(ctx); err != nil { + logger.WithGroup(NodeLogName). + Error("unable to reload node", "err", err) + + } + case rawterm.KeyCtrlR: - fmt.Fprintln(nodeOut, "Reseting state...") - checkForError(nodeOut, dnode.Reset(ctx)) + logger.WithGroup(NodeLogName).Info("reseting node state...") + if err = dnode.Reset(ctx); err != nil { + logger.WithGroup(NodeLogName). + Error("unable to reset node state", "err", err) + } + case rawterm.KeyCtrlC: return nil + case rawterm.KeyCtrlE: + panic("NOOOO") + return nil default: } - // Listen for the next keypress - keyPressCh = listenForKeyPress(keyOut, rt) + // Reset listen for the next keypress + keyPressCh = listenForKeyPress(logger.WithGroup(KeyPressLogName), rt) } } } @@ -348,15 +369,20 @@ func runPkgsWatcher(ctx context.Context, cfg *devCfg, pkgs []gnomod.Pkg, changed } } -func setupRawTerm(io commands.IO) (rt *rawterm.RawTerm, restore func() error, err error) { - rt = rawterm.NewRawTerm() +var noopRestore = func() error { return nil } - restore, err = rt.Init() - if err != nil { - return nil, nil, err +func setupRawTerm(cfg *devCfg, io commands.IO) (*rawterm.RawTerm, func() error, error) { + rt := rawterm.NewRawTerm() + restore := noopRestore + if !cfg.serverMode { + var err error + restore, err = rt.Init() + if err != nil { + return nil, nil, err + } } - // Correctly format output for terminal + // correctly format output for terminal io.SetOut(commands.WriteNopCloser(rt)) return rt, restore, nil } @@ -364,13 +390,12 @@ func setupRawTerm(io commands.IO) (rt *rawterm.RawTerm, restore func() error, er // setupDevNode initializes and returns a new DevNode. func setupDevNode( ctx context.Context, + logger *slog.Logger, cfg *devCfg, remitter emitter.Emitter, - rt *rawterm.RawTerm, pkgspath []string, ) (*gnodev.Node, error) { - nodeOut := rt.NamespacedWriter("Node") - zapLogger := NewZapLogger(nodeOut, zapcore.ErrorLevel) + nodeLogger := logger.WithGroup(NodeLogName) gnoroot := gnoenv.RootDir() @@ -379,7 +404,6 @@ func setupDevNode( config.PackagesPathList = pkgspath config.TMConfig.RPC.ListenAddress = resolveUnixOrTCPAddr(cfg.nodeRPCListenerAddr) config.NoReplay = cfg.noReplay - config.SkipFailingGenesisTxs = true config.MaxGasPerBlock = cfg.maxGas config.ChainID = cfg.chainId @@ -387,18 +411,17 @@ func setupDevNode( config.TMConfig.P2P.ListenAddress = defaultDevOptions.nodeP2PListenerAddr config.TMConfig.ProxyApp = defaultDevOptions.nodeProxyAppListenerAddr - return gnodev.NewDevNode(ctx, log.ZapLoggerToSlog(zapLogger), remitter, config) + return gnodev.NewDevNode(ctx, nodeLogger, remitter, config) } // setupGnowebServer initializes and starts the Gnoweb server. -func setupGnoWebServer(cfg *devCfg, dnode *gnodev.Node, rt *rawterm.RawTerm) http.Handler { +func setupGnoWebServer(logger *slog.Logger, cfg *devCfg, dnode *gnodev.Node) http.Handler { webConfig := gnoweb.NewDefaultConfig() webConfig.RemoteAddr = dnode.GetRemoteAddress() webConfig.HelpRemote = dnode.GetRemoteAddress() webConfig.HelpChainID = cfg.chainId - zapLogger := NewZapLogger(rt.NamespacedWriter("GnoWeb"), zapcore.DebugLevel) - app := gnoweb.MakeApp(log.ZapLoggerToSlog(zapLogger), webConfig) + app := gnoweb.MakeApp(logger, webConfig) return app.Router } @@ -421,13 +444,13 @@ func parseArgsPackages(args []string) (paths []string, err error) { return paths, nil } -func listenForKeyPress(w io.Writer, rt *rawterm.RawTerm) <-chan rawterm.KeyPress { +func listenForKeyPress(logger *slog.Logger, rt *rawterm.RawTerm) <-chan rawterm.KeyPress { cc := make(chan rawterm.KeyPress, 1) go func() { defer close(cc) key, err := rt.ReadKeyPress() if err != nil { - fmt.Fprintf(w, "unable to read keypress: %s\n", err.Error()) + logger.Error("unable to read keypress", "err", err) return } @@ -437,15 +460,6 @@ func listenForKeyPress(w io.Writer, rt *rawterm.RawTerm) <-chan rawterm.KeyPress return cc } -func checkForError(w io.Writer, err error) { - if err != nil { - fmt.Fprintf(w, "[ERROR] - %s\n", err.Error()) - return - } - - fmt.Fprintln(w, "[DONE]") -} - func resolveUnixOrTCPAddr(in string) (out string) { var err error var addr net.Addr @@ -469,15 +483,26 @@ func resolveUnixOrTCPAddr(in string) (out string) { panic(err) } -// NewZapLogger creates a zap logger with a console encoder for development use. -func NewZapLogger(w io.Writer, level zapcore.Level) *zap.Logger { - // Build encoder config - consoleConfig := zap.NewDevelopmentEncoderConfig() - consoleConfig.TimeKey = "" - consoleConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder - consoleConfig.EncodeName = zapcore.FullNameEncoder - - // Build encoder - enc := zapcore.NewConsoleEncoder(consoleConfig) - return log.NewZapLogger(enc, w, level) +func setuplogger(cfg *devCfg, out io.Writer) *slog.Logger { + level := slog.LevelInfo + if cfg.verbose { + level = slog.LevelDebug + } + + if cfg.serverMode { + zaplogger := logger.NewZapLogger(out, level) + return zaplog.ZapLoggerToSlog(zaplogger) + } + + // Detect term color profile + colorProfile := termenv.DefaultOutput().Profile + clogger := logger.NewColumnLogger(out, level, colorProfile) + + // Register well known group color with system colors + clogger.RegisterGroupColor(NodeLogName, lipgloss.Color("3")) + clogger.RegisterGroupColor(WebLogName, lipgloss.Color("4")) + clogger.RegisterGroupColor(KeyPressLogName, lipgloss.Color("5")) + clogger.RegisterGroupColor(EventServerLogName, lipgloss.Color("6")) + + return slog.New(clogger) } diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go index 23ac66a5f9a..02d97dff733 100644 --- a/contribs/gnodev/pkg/dev/node.go +++ b/contribs/gnodev/pkg/dev/node.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "log/slog" + "strings" + "unicode" "github.com/gnolang/gno/contribs/gnodev/pkg/emitter" "github.com/gnolang/gno/contribs/gnodev/pkg/events" @@ -17,18 +19,19 @@ import ( bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/crypto" tm2events "github.com/gnolang/gno/tm2/pkg/events" + "github.com/gnolang/gno/tm2/pkg/log" + "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/std" // backup "github.com/gnolang/tx-archive/backup/client" // restore "github.com/gnolang/tx-archive/restore/client" ) type NodeConfig struct { - PackagesPathList []string - TMConfig *tmcfg.Config - SkipFailingGenesisTxs bool - NoReplay bool - MaxGasPerBlock int64 - ChainID string + PackagesPathList []string + TMConfig *tmcfg.Config + NoReplay bool + MaxGasPerBlock int64 + ChainID string } func DefaultNodeConfig(rootdir string) *NodeConfig { @@ -36,11 +39,10 @@ func DefaultNodeConfig(rootdir string) *NodeConfig { tmc.Consensus.SkipTimeoutCommit = false // avoid time drifting, see issue #1507 return &NodeConfig{ - ChainID: tmc.ChainID(), - PackagesPathList: []string{}, - TMConfig: tmc, - SkipFailingGenesisTxs: true, - MaxGasPerBlock: 10_000_000_000, + ChainID: tmc.ChainID(), + PackagesPathList: []string{}, + TMConfig: tmc, + MaxGasPerBlock: 10_000_000_000, } } @@ -80,6 +82,8 @@ func NewDevNode(ctx context.Context, logger *slog.Logger, emitter emitter.Emitte return nil, fmt.Errorf("unable to load genesis packages: %w", err) } + logger.Info("pkgs loaded", "path", cfg.PackagesPathList) + // generate genesis state genesis := gnoland.GnoGenesisState{ Balances: DefaultBalance, @@ -125,6 +129,7 @@ func (d *Node) GetRemoteAddress() string { // UpdatePackages updates the currently known packages. It will be taken into // consideration in the next reload of the node. func (d *Node) UpdatePackages(paths ...string) error { + var n int for _, path := range paths { // List all packages from target path pkgslist, err := gnomod.ListPkgs(path) @@ -135,9 +140,13 @@ func (d *Node) UpdatePackages(paths ...string) error { // Update or add package in the current known list. for _, pkg := range pkgslist { d.pkgs[pkg.Dir] = pkg + d.logger.Debug("pkgs update", "name", pkg.Name, "path", pkg.Dir) } + + n += len(pkgslist) } + d.logger.Info(fmt.Sprintf("updated %d pacakges", n)) return nil } @@ -230,6 +239,36 @@ func (d *Node) Reload(ctx context.Context) error { return nil } +func (d *Node) genesisTxHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) { + if res.IsErr() { + // XXX: for now, this is only way to catch the error + before, after, found := strings.Cut(res.Log, "\n") + if !found { + d.logger.Error("unable to send tx", "err", res.Error, "log", res.Log) + return + } + + var attrs []slog.Attr + + // Add error + attrs = append(attrs, slog.Any("err", res.Error)) + + // Fetch first line as error message + msg := strings.TrimFunc(before, func(r rune) bool { + return unicode.IsSpace(r) || r == ':' + }) + attrs = append(attrs, slog.String("err", msg)) + + // If debug is enable, also append stack + if d.logger.Enabled(context.Background(), slog.LevelDebug) { + attrs = append(attrs, slog.String("stack", after)) + + } + + d.logger.LogAttrs(context.Background(), slog.LevelError, "unable to deliver tx", attrs...) + } +} + // GetBlockTransactions returns the transactions contained // within the specified block, if any func (d *Node) GetBlockTransactions(blockNum uint64) ([]std.Tx, error) { @@ -322,30 +361,30 @@ func (n *Node) stopIfRunning() error { return nil } -func (n *Node) reset(ctx context.Context, genesis gnoland.GnoGenesisState) error { +func (n *Node) reset(ctx context.Context, genesis gnoland.GnoGenesisState) (err error) { // Setup node config nodeConfig := newNodeConfig(n.config.TMConfig, n.config.ChainID, genesis) - nodeConfig.SkipFailingGenesisTxs = n.config.SkipFailingGenesisTxs + nodeConfig.GenesisTxHandler = n.genesisTxHandler nodeConfig.Genesis.ConsensusParams.Block.MaxGas = n.config.MaxGasPerBlock - var recoverErr error - // recoverFromError handles panics and converts them to errors. recoverFromError := func() { if r := recover(); r != nil { - var ok bool - if recoverErr, ok = r.(error); !ok { - panic(r) // Re-panic if not an error. + switch val := r.(type) { + case error: + err = val + case string: + err = fmt.Errorf("error: %s", val) + default: + err = fmt.Errorf("unknown error: %#v", val) } } } // Execute node creation and handle any errors. defer recoverFromError() + node, nodeErr := buildNode(n.logger, n.emitter, nodeConfig) - if recoverErr != nil { // First check for recover error in case of panic - return fmt.Errorf("recovered from a node panic: %w", recoverErr) - } if nodeErr != nil { // Then for any node error return fmt.Errorf("unable to build the node: %w", nodeErr) } @@ -361,8 +400,11 @@ func (n *Node) reset(ctx context.Context, genesis gnoland.GnoGenesisState) error return nil } +var noopLogger = log.NewNoopLogger() + func buildNode(logger *slog.Logger, emitter emitter.Emitter, cfg *gnoland.InMemoryNodeConfig) (*node.Node, error) { - node, err := gnoland.NewInMemoryNode(logger, cfg) + // XXX(TODO): Redirect the node log somewhere else + node, err := gnoland.NewInMemoryNode(noopLogger, cfg) if err != nil { return nil, fmt.Errorf("unable to create a new node: %w", err) } @@ -371,8 +413,9 @@ func buildNode(logger *slog.Logger, emitter emitter.Emitter, cfg *gnoland.InMemo switch data := evt.(type) { case bft.EventTx: resEvt := events.TxResult{ - Height: data.Result.Height, - Index: data.Result.Index, + Height: data.Result.Height, + Index: data.Result.Index, + // XXX: Update this to split error for stack Response: data.Result.Response, } diff --git a/contribs/gnodev/pkg/logger/colors.go b/contribs/gnodev/pkg/logger/colors.go new file mode 100644 index 00000000000..b0499e01722 --- /dev/null +++ b/contribs/gnodev/pkg/logger/colors.go @@ -0,0 +1,60 @@ +package logger + +import ( + "fmt" + "hash/fnv" + "math" + + "github.com/charmbracelet/lipgloss" +) + +func colorFromString(s string, saturation, lightness float64) lipgloss.Color { + hue := float64(hash32a(s) % 360) + + r, g, b := hslToRGB(float64(hue), saturation, lightness) + hex := rgbToHex(r, g, b) + return lipgloss.Color(hex) +} + +func hash32a(s string) uint32 { + h := fnv.New32a() + h.Write([]byte(s)) + return h.Sum32() +} + +// from: https://www.rapidtables.com/convert/color/hsl-to-rgb.html +// hslToRGB converts an HSL triple to an RGB triple. +func hslToRGB(h, s, l float64) (r, g, b uint8) { + if h < 0 || h >= 360 || s < 0 || s > 1 || l < 0 || l > 1 { + return 0, 0, 0 + } + + C := (1 - math.Abs((2*l)-1)) * s + X := C * (1 - math.Abs(math.Mod(h/60, 2)-1)) + m := l - (C / 2) + + var rNot, gNot, bNot float64 + switch { + case 0 <= h && h < 60: + rNot, gNot, bNot = C, X, 0 + case 60 <= h && h < 120: + rNot, gNot, bNot = X, C, 0 + case 120 <= h && h < 180: + rNot, gNot, bNot = 0, C, X + case 180 <= h && h < 240: + rNot, gNot, bNot = 0, X, C + case 240 <= h && h < 300: + rNot, gNot, bNot = X, 0, C + case 300 <= h && h < 360: + rNot, gNot, bNot = C, 0, X + } + + r = uint8(math.Round((rNot + m) * 255)) + g = uint8(math.Round((gNot + m) * 255)) + b = uint8(math.Round((bNot + m) * 255)) + return r, g, b +} + +func rgbToHex(r, g, b uint8) string { + return fmt.Sprintf("#%02X%02X%02X", r, g, b) +} diff --git a/contribs/gnodev/pkg/logger/log_column.go b/contribs/gnodev/pkg/logger/log_column.go new file mode 100644 index 00000000000..8e145e89175 --- /dev/null +++ b/contribs/gnodev/pkg/logger/log_column.go @@ -0,0 +1,199 @@ +package logger + +import ( + "bytes" + "fmt" + "io" + "log/slog" + "strings" + "sync" + + "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/log" + "github.com/muesli/termenv" +) + +func NewColumnLogger(w io.Writer, level slog.Level, profile termenv.Profile) *ColumnLogger { + charmLogger := log.NewWithOptions(w, log.Options{ + ReportTimestamp: false, + ReportCaller: false, + Prefix: "", + }) + + // Default column output + defaultOutput := newColumeWriter(lipgloss.NewStyle(), "", w) + charmLogger.SetOutput(defaultOutput) + charmLogger.SetStyles(defaultStyles()) + charmLogger.SetColorProfile(profile) + charmLogger.SetReportCaller(false) + switch level { + case slog.LevelDebug: + charmLogger.SetLevel(log.DebugLevel) + case slog.LevelError: + charmLogger.SetLevel(log.ErrorLevel) + case slog.LevelInfo: + charmLogger.SetLevel(log.InfoLevel) + case slog.LevelWarn: + charmLogger.SetLevel(log.WarnLevel) + default: + panic("invalid slog level") + } + + return &ColumnLogger{ + Logger: charmLogger, + writer: w, + prefix: charmLogger.GetPrefix(), + colors: map[string]lipgloss.Color{}, + } +} + +type ColumnLogger struct { + *log.Logger + + prefix string + writer io.Writer + colorProfile termenv.Profile + + colors map[string]lipgloss.Color + muColors sync.RWMutex +} + +func (cl *ColumnLogger) WithGroup(group string) slog.Handler { + cl.muColors.RLock() + defer cl.muColors.RUnlock() + + if cl.prefix != "" { + group = fmt.Sprintf("%.1s.%s", cl.prefix, group) + } + + // check if we already know this group + fg, ok := cl.colors[group] + if !ok { + // generate bright color based on the group name + fg = colorFromString(group, 0.5, 0.6) + } + baseStyle := lipgloss.NewStyle().Foreground(fg) + + nlog := cl.Logger.With() // clone logger + nlog.SetOutput(newColumeWriter(baseStyle, group, cl.writer)) + nlog.SetColorProfile(cl.colorProfile) + return &ColumnLogger{ + Logger: nlog, + prefix: group, + writer: cl.writer, + } +} + +func (cl *ColumnLogger) RegisterGroupColor(group string, color lipgloss.Color) { + cl.muColors.Lock() + cl.colors[group] = color + cl.muColors.Unlock() +} + +var lf = []byte{'\n'} + +type columnWriter struct { + inline bool + style lipgloss.Style + prefix string + writer io.Writer +} + +func newColumeWriter(baseStyle lipgloss.Style, prefix string, writer io.Writer) *columnWriter { + const width = 12 + + style := baseStyle. + Border(lipgloss.ThickBorder(), false, true, false, false). + BorderForeground(baseStyle.GetForeground()). + Bold(true). + Width(width) + + if len(prefix) >= width { + prefix = prefix[:width-3] + "..." + } + + return &columnWriter{style: style, prefix: prefix, writer: writer} +} + +func (cl *columnWriter) Write(buf []byte) (n int, err error) { + for line := 0; len(buf) > 0; line++ { + i := bytes.IndexByte(buf, '\n') + todo := len(buf) + if i >= 0 { + todo = i + } + + if !cl.inline { + var prefix string + if line == 0 { + prefix = cl.prefix + } + + fmt.Fprint(cl.writer, cl.style.Render(prefix)+" ") + } + + var nn int + nn, err = cl.writer.Write(buf[:todo]) + n += nn + if err != nil { + return n, err + } + buf = buf[todo:] + + if cl.inline = i < 0; !cl.inline { + if _, err = cl.writer.Write([]byte(lf)); err != nil { + return n, err + } + n++ + buf = buf[1:] + } + } + + return n, nil +} + +// defaultStyles returns the default lipgloss styles for column logger +func defaultStyles() *log.Styles { + style := log.DefaultStyles() + style.Levels = map[log.Level]lipgloss.Style{ + log.DebugLevel: lipgloss.NewStyle(). + SetString(strings.ToUpper(log.DebugLevel.String())). + Bold(true). + MaxWidth(1). + Foreground(lipgloss.Color("63")), + log.InfoLevel: lipgloss.NewStyle(). + SetString(strings.ToUpper(log.InfoLevel.String())). + MaxWidth(1). + Foreground(lipgloss.Color("12")), + + log.WarnLevel: lipgloss.NewStyle(). + SetString(strings.ToUpper(log.WarnLevel.String())). + Bold(true). + MaxWidth(1). + Foreground(lipgloss.Color("192")), + log.ErrorLevel: lipgloss.NewStyle(). + SetString(strings.ToUpper(log.ErrorLevel.String())). + Bold(true). + MaxWidth(1). + Foreground(lipgloss.Color("204")), + log.FatalLevel: lipgloss.NewStyle(). + SetString(strings.ToUpper(log.FatalLevel.String())). + Bold(true). + MaxWidth(1). + Foreground(lipgloss.Color("134")), + } + style.Keys = map[string]lipgloss.Style{ + "err": lipgloss.NewStyle(). + Foreground(lipgloss.Color("204")), + "error": lipgloss.NewStyle(). + Foreground(lipgloss.Color("204")), + } + style.Values = map[string]lipgloss.Style{ + "err": lipgloss.NewStyle(). + Foreground(lipgloss.Color("204")), + "error": lipgloss.NewStyle(). + Foreground(lipgloss.Color("204")), + } + + return style +} diff --git a/contribs/gnodev/pkg/logger/log_zap.go b/contribs/gnodev/pkg/logger/log_zap.go new file mode 100644 index 00000000000..b7e5b34cc4e --- /dev/null +++ b/contribs/gnodev/pkg/logger/log_zap.go @@ -0,0 +1,38 @@ +package logger + +import ( + "io" + "log/slog" + + "github.com/gnolang/gno/gno.land/pkg/log" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func NewZapLogger(w io.Writer, slevel slog.Level) *zap.Logger { + // Build encoder config + consoleConfig := zap.NewDevelopmentEncoderConfig() + consoleConfig.EncodeCaller = zapcore.FullCallerEncoder + consoleConfig.EncodeTime = zapcore.RFC3339TimeEncoder + consoleConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder + consoleConfig.EncodeName = zapcore.FullNameEncoder + + // Build encoder + enc := zapcore.NewConsoleEncoder(consoleConfig) + + var level zapcore.Level + switch slevel { + case slog.LevelDebug: + level = zapcore.DebugLevel + case slog.LevelError: + level = zapcore.ErrorLevel + case slog.LevelInfo: + level = zapcore.InfoLevel + case slog.LevelWarn: + level = zapcore.WarnLevel + default: + panic("invalid slog level") + } + + return log.NewZapLogger(enc, w, level) +} diff --git a/contribs/gnodev/pkg/rawterm/rawterm.go b/contribs/gnodev/pkg/rawterm/rawterm.go index f6d6e7534e2..58b8dde1530 100644 --- a/contribs/gnodev/pkg/rawterm/rawterm.go +++ b/contribs/gnodev/pkg/rawterm/rawterm.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "os" - "strings" "sync" "golang.org/x/term" @@ -17,16 +16,16 @@ var CRLF = []byte{'\r', '\n'} type RawTerm struct { syncWriter sync.Mutex - fsin *os.File - reader io.Reader - taskWriter TaskWriter + fsin *os.File + reader io.Reader + writer io.Writer } func NewRawTerm() *RawTerm { return &RawTerm{ - fsin: os.Stdin, - reader: os.Stdin, - taskWriter: &rawTaskWriter{os.Stdout}, + fsin: os.Stdin, + reader: os.Stdin, + writer: os.Stdout, } } @@ -38,38 +37,16 @@ func (rt *RawTerm) Init() (restore func() error, err error) { } rt.reader = rt.fsin - rt.taskWriter = &columnTaskWriter{os.Stdout} return func() error { return term.Restore(fd, oldstate) }, nil } -func (rt *RawTerm) Taskf(task string, format string, args ...interface{}) (n int, err error) { - format = strings.TrimSpace(format) - if len(args) > 0 { - str := fmt.Sprintf(format, args...) - return rt.taskWriter.WriteTask(task, []byte(str+"\n")) - } - - return rt.taskWriter.WriteTask(task, []byte(format+"\n")) -} - func (rt *RawTerm) Write(buf []byte) (n int, err error) { rt.syncWriter.Lock() defer rt.syncWriter.Unlock() - return rt.taskWriter.Write(buf) -} - -func (rt *RawTerm) WriteTask(name string, buf []byte) (n int, err error) { - rt.syncWriter.Lock() - defer rt.syncWriter.Unlock() - - return rt.taskWriter.WriteTask(name, buf) -} - -func (rt *RawTerm) NamespacedWriter(namepsace string) io.Writer { - return &namespaceWriter{namepsace, rt} + return writeWithCRLF(rt.writer, buf) } func (rt *RawTerm) read(buf []byte) (n int, err error) { @@ -85,31 +62,9 @@ func (rt *RawTerm) ReadKeyPress() (KeyPress, error) { return KeyPress(buf[0]), nil } -type namespaceWriter struct { - namespace string - writer TaskWriter -} - -func (r *namespaceWriter) Write(buf []byte) (n int, err error) { - return r.writer.WriteTask(r.namespace, buf) -} - -type TaskWriter interface { - io.Writer - WriteTask(task string, buf []byte) (n int, err error) -} - -type columnTaskWriter struct { - writer io.Writer -} - -func (r *columnTaskWriter) Write(buf []byte) (n int, err error) { - return r.WriteTask("", buf) -} - -func (r *columnTaskWriter) WriteTask(left string, buf []byte) (n int, err error) { - var nline int - for nline = 0; len(buf) > 0; nline++ { +// writeWithCRLF writes buf to w but replaces all occurrences of \n with \r\n. +func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) { + for len(buf) > 0 { i := bytes.IndexByte(buf, '\n') todo := len(buf) if i >= 0 { @@ -117,23 +72,15 @@ func (r *columnTaskWriter) WriteTask(left string, buf []byte) (n int, err error) } var nn int - switch { - case nline == 0, left == "": // first line or left side is empty - nn, err = r.writeColumnLine(left, buf[:todo]) - case i < 0 || i+1 == len(buf): // last line - nn, err = r.writeColumnLine(" └─", buf[:todo]) - default: // middle lines - nn, err = r.writeColumnLine(" │", buf[:todo]) - } - + nn, err = w.Write(buf[:todo]) n += nn if err != nil { return n, err } buf = buf[todo:] - if i >= 0 { // always jump a line on the last line - if _, err = r.writer.Write(CRLF); err != nil { + if i >= 0 { + if _, err = w.Write(CRLF); err != nil { return n, err } n++ @@ -141,38 +88,5 @@ func (r *columnTaskWriter) WriteTask(left string, buf []byte) (n int, err error) } } - return -} - -func (r *columnTaskWriter) writeColumnLine(left string, line []byte) (n int, err error) { - // Write left column - if n, err = fmt.Fprintf(r.writer, "%-15s | ", left); err != nil { - return n, err - } - - // Write left line - var nn int - nn, err = r.writer.Write(line) - n += nn - - return -} - -type rawTaskWriter struct { - writer io.Writer -} - -func (r *rawTaskWriter) Write(buf []byte) (n int, err error) { - return r.writer.Write(buf) -} - -func (r *rawTaskWriter) WriteTask(task string, buf []byte) (n int, err error) { - if task != "" { - n, err = r.writer.Write([]byte(task + ": ")) - } - - var nn int - nn, err = r.writer.Write(buf) - n += nn - return + return n, nil } diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index cc15f74134e..f67b86fd735 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -28,17 +28,18 @@ type AppOptions struct { DB dbm.DB // `gnoRootDir` should point to the local location of the gno repository. // It serves as the gno equivalent of GOROOT. - GnoRootDir string - SkipFailingGenesisTxs bool - Logger *slog.Logger - MaxCycles int64 + GnoRootDir string + GenesisTxHandler GenesisTxHandler + Logger *slog.Logger + MaxCycles int64 } func NewAppOptions() *AppOptions { return &AppOptions{ - Logger: log.NewNoopLogger(), - DB: memdb.NewMemDB(), - GnoRootDir: gnoenv.RootDir(), + GenesisTxHandler: PanicOnFailingTxHandler, + Logger: log.NewNoopLogger(), + DB: memdb.NewMemDB(), + GnoRootDir: gnoenv.RootDir(), } } @@ -81,7 +82,7 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { vmKpr := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, stdlibsDir, cfg.MaxCycles) // Set InitChainer - baseApp.SetInitChainer(InitChainer(baseApp, acctKpr, bankKpr, cfg.SkipFailingGenesisTxs)) + baseApp.SetInitChainer(InitChainer(baseApp, acctKpr, bankKpr, cfg.GenesisTxHandler)) // Set AnteHandler authOptions := auth.AnteOptions{ @@ -127,7 +128,9 @@ func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger *slog.Logger, var err error cfg := NewAppOptions() - cfg.SkipFailingGenesisTxs = skipFailingGenesisTxs + if skipFailingGenesisTxs { + cfg.GenesisTxHandler = NoopGenesisTxHandler + } // Get main DB. cfg.DB, err = dbm.NewDB("gnolang", dbm.GoLevelDBBackend, filepath.Join(dataRootDir, "data")) @@ -140,8 +143,18 @@ func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger *slog.Logger, return NewAppWithOptions(cfg) } +type GenesisTxHandler func(ctx sdk.Context, tx std.Tx, res sdk.Result) + +func NoopGenesisTxHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) {} + +func PanicOnFailingTxHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) { + if res.IsErr() { + panic(res.Log) + } +} + // InitChainer returns a function that can initialize the chain with genesis. -func InitChainer(baseApp *sdk.BaseApp, acctKpr auth.AccountKeeperI, bankKpr bank.BankKeeperI, skipFailingGenesisTxs bool) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain { +func InitChainer(baseApp *sdk.BaseApp, acctKpr auth.AccountKeeperI, bankKpr bank.BankKeeperI, resHandler GenesisTxHandler) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { // Get genesis state. genState := req.AppState.(GnoGenesisState) @@ -154,21 +167,20 @@ func InitChainer(baseApp *sdk.BaseApp, acctKpr auth.AccountKeeperI, bankKpr bank panic(err) } } + // Run genesis txs. for i, tx := range genState.Txs { res := baseApp.Deliver(tx) if res.IsErr() { ctx.Logger().Error("LOG", "log", res.Log) ctx.Logger().Error(fmt.Sprintf("#%d", i), "value", string(amino.MustMarshalJSON(tx))) - - // NOTE: comment out to ignore. - if !skipFailingGenesisTxs { - panic(res.Log) - } } else { ctx.Logger().Info("SUCCESS:", "value", string(amino.MustMarshalJSON(tx))) } + + resHandler(ctx, tx, res) } + // Done! return abci.ResponseInitChain{ Validators: req.Validators, diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go index 89f222738d0..d48d0e2ab47 100644 --- a/gno.land/pkg/gnoland/node_inmemory.go +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -21,11 +21,11 @@ import ( ) type InMemoryNodeConfig struct { - PrivValidator bft.PrivValidator // identity of the validator - Genesis *bft.GenesisDoc - TMConfig *tmcfg.Config - SkipFailingGenesisTxs bool - GenesisMaxVMCycles int64 + PrivValidator bft.PrivValidator // identity of the validator + Genesis *bft.GenesisDoc + TMConfig *tmcfg.Config + GenesisTxHandler GenesisTxHandler + GenesisMaxVMCycles int64 } // NewMockedPrivValidator generate a new key @@ -82,6 +82,7 @@ func NewDefaultInMemoryNodeConfig(rootdir string) *InMemoryNodeConfig { PrivValidator: pv, TMConfig: tm, Genesis: genesis, + GenesisTxHandler: PanicOnFailingTxHandler, GenesisMaxVMCycles: 10_000_000, } } @@ -99,6 +100,10 @@ func (cfg *InMemoryNodeConfig) validate() error { return fmt.Errorf("`TMConfig.RootDir` is required to locate `stdlibs` directory") } + if cfg.GenesisTxHandler == nil { + return fmt.Errorf("`GenesisTxHandler` is required but not provided") + } + return nil } @@ -112,11 +117,11 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, // Initialize the application with the provided options gnoApp, err := NewAppWithOptions(&AppOptions{ - Logger: logger, - GnoRootDir: cfg.TMConfig.RootDir, - SkipFailingGenesisTxs: cfg.SkipFailingGenesisTxs, - MaxCycles: cfg.GenesisMaxVMCycles, - DB: memdb.NewMemDB(), + Logger: logger, + GnoRootDir: cfg.TMConfig.RootDir, + GenesisTxHandler: cfg.GenesisTxHandler, + MaxCycles: cfg.GenesisMaxVMCycles, + DB: memdb.NewMemDB(), }) if err != nil { return nil, fmt.Errorf("error initializing new app: %w", err) diff --git a/gno.land/pkg/integration/testing_node.go b/gno.land/pkg/integration/testing_node.go index 106c5b14bb8..16b23b6565c 100644 --- a/gno.land/pkg/integration/testing_node.go +++ b/gno.land/pkg/integration/testing_node.go @@ -71,9 +71,10 @@ func TestingMinimalNodeConfig(t TestingTS, gnoroot string) *gnoland.InMemoryNode genesis := DefaultTestingGenesisConfig(t, gnoroot, pv.GetPubKey(), tmconfig) return &gnoland.InMemoryNodeConfig{ - PrivValidator: pv, - Genesis: genesis, - TMConfig: tmconfig, + PrivValidator: pv, + Genesis: genesis, + TMConfig: tmconfig, + GenesisTxHandler: gnoland.PanicOnFailingTxHandler, } }