Skip to content

Commit

Permalink
refactor: change logger to slog
Browse files Browse the repository at this point in the history
  • Loading branch information
mrekucci committed Mar 14, 2024
1 parent f6d9ca6 commit f99244c
Show file tree
Hide file tree
Showing 12 changed files with 396 additions and 316 deletions.
249 changes: 121 additions & 128 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
package main

import (
"crypto/ecdsa"
"fmt"
"io"
"log/slog"
"net"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"time"

"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
contracts "github.com/primevprotocol/contracts-abi/config"
"github.com/primevprotocol/mev-oracle/pkg/keysigner"
"github.com/primevprotocol/mev-oracle/pkg/node"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
"golang.org/x/crypto/sha3"
)

const (
Expand All @@ -29,6 +27,46 @@ const (
defaultKeystore = "keystore"
)

var (
portCheck = func(c *cli.Context, p int) error {
if p < 0 || p > 65535 {
return fmt.Errorf("invalid port number %d, expected 0 <= port <= 65535", p)
}
return nil
}

stringInCheck = func(flag string, opts []string) func(c *cli.Context, s string) error {
return func(c *cli.Context, s string) error {
if !slices.Contains(opts, s) {
return fmt.Errorf("invalid %s option %q, expected one of %s", flag, s, strings.Join(opts, ", "))
}
return nil
}
}

addressPortCheck = func(c *cli.Context, s string) error {

Check failure on line 47 in cmd/main.go

View workflow job for this annotation

GitHub Actions / Build and Test

var `addressPortCheck` is unused (unused)
host, port, err := net.SplitHostPort(s)
if err != nil {
return fmt.Errorf("invalid value %q: %w", s, err)
}

p, err := strconv.Atoi(port)
if err != nil {
return fmt.Errorf("invalid value %q: invalid port: %w", s, err)
}

if err := portCheck(c, p); err != nil {
return fmt.Errorf("invalid value %q: invalid port: %w", s, err)
}

ip := net.ParseIP(host)
if ip == nil {
return fmt.Errorf("invalid value %q: incorrect ip address", s)
}
return nil
}
)

var (
optionConfig = &cli.StringFlag{
Name: "config",
Expand All @@ -48,22 +86,36 @@ var (
Usage: "port to listen on for HTTP requests",
EnvVars: []string{"MEV_ORACLE_HTTP_PORT"},
Value: defaultHTTPPort,
Action: func(c *cli.Context, p int) error {
if p < 0 || p > 65535 {
return fmt.Errorf("invalid port number: %d", p)
}
return nil
},
Action: portCheck,
})

optionLogFmt = altsrc.NewStringFlag(&cli.StringFlag{
Name: "log-fmt",
Usage: "log format to use, options are 'text' or 'json'",
EnvVars: []string{"MEV_ORACLE_LOG_FMT"},
Value: "text",
Action: stringInCheck("log-fmt", []string{"text", "json"}),
})

optionLogLevel = altsrc.NewStringFlag(&cli.StringFlag{
Name: "log-level",
Usage: "log level",
Usage: "log level to use, options are 'debug', 'info', 'warn', 'error'",
EnvVars: []string{"MEV_ORACLE_LOG_LEVEL"},
Value: "info",
Action: func(c *cli.Context, l string) error {
_, err := zerolog.ParseLevel(l)
return err
Action: stringInCheck("log-level", []string{"debug", "info", "warn", "error"}),
})

optionLogTags = altsrc.NewStringFlag(&cli.StringFlag{
Name: "log-tags",
Usage: "log tags is a comma-separated list of <name:value> pairs that will be inserted into each log line",
EnvVars: []string{"MEV_ORACLE_LOG_TAGS"},
Action: func(ctx *cli.Context, s string) error {
for i, p := range strings.Split(s, ",") {
if len(strings.Split(p, ":")) != 2 {
return fmt.Errorf("invalid log-tags at index %d, expecting <name:value>", i)
}
}
return nil
},
})

Expand Down Expand Up @@ -161,7 +213,9 @@ func main() {
optionConfig,
optionPrivKeyFile,
optionHTTPPort,
optionLogFmt,
optionLogLevel,
optionLogTags,
optionL1RPCUrl,
optionSettlementRPCUrl,
optionOracleContractAddr,
Expand Down Expand Up @@ -196,72 +250,6 @@ func main() {
}
}

func createKeyIfNotExists(c *cli.Context, path string) error {
// check if key already exists
if _, err := os.Stat(path); err == nil {
fmt.Fprintf(c.App.Writer, "Using existing private key: %s\n", path)
return nil
}

fmt.Fprintf(c.App.Writer, "Creating new private key: %s\n", path)

// check if parent directory exists
if _, err := os.Stat(filepath.Dir(path)); os.IsNotExist(err) {
// create parent directory
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
return err
}
}

privKey, err := crypto.GenerateKey()
if err != nil {
return err
}

f, err := os.Create(path)
if err != nil {
return err
}

defer f.Close()

if err := crypto.SaveECDSA(path, privKey); err != nil {
return err
}

wallet := getEthAddressFromPubKey(&privKey.PublicKey)

fmt.Fprintf(c.App.Writer, "Private key saved to file: %s\n", path)
fmt.Fprintf(c.App.Writer, "Wallet address: %s\n", wallet.Hex())
return nil
}

func resolveFilePath(path string) (string, error) {
if path == "" {
return "", fmt.Errorf("path is empty")
}

if strings.HasPrefix(path, "~") {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}

return filepath.Join(home, path[1:]), nil
}

return path, nil
}

func getEthAddressFromPubKey(key *ecdsa.PublicKey) common.Address {
pbBytes := crypto.FromECDSAPub(key)
hash := sha3.NewLegacyKeccak256()
hash.Write(pbBytes[1:])
address := hash.Sum(nil)[12:]

return common.BytesToAddress(address)
}

func initializeApplication(c *cli.Context) error {
if err := verifyKeystorePasswordPresence(c); err != nil {
return err
Expand All @@ -283,16 +271,21 @@ func verifyKeystorePasswordPresence(c *cli.Context) error {

// launchOracleWithConfig configures and starts the oracle based on the CLI context or config.yaml file.
func launchOracleWithConfig(c *cli.Context) error {
logger, err := newLogger(
c.String(optionLogLevel.Name),
c.String(optionLogFmt.Name),
c.String(optionLogTags.Name),
c.App.Writer,
)

if err != nil {
return fmt.Errorf("failed to create logger: %w", err)
}
keySigner, err := setupKeySigner(c)
if err != nil {
return fmt.Errorf("failed to setup key signer: %w", err)
}

lvl, _ := zerolog.ParseLevel(c.String(optionLogLevel.Name))

zerolog.SetGlobalLevel(lvl)
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Logger = log.Output(os.Stdout).With().Caller().Logger()
logger.Info("key signer account", "address", keySigner.GetAddress().Hex(), "url", keySigner.String())

nd, err := node.NewNode(&node.Options{
KeySigner: keySigner,
Expand Down Expand Up @@ -322,63 +315,63 @@ func launchOracleWithConfig(c *cli.Context) error {

err := nd.Close()
if err != nil {
log.Error().Err(err).Msg("failed to close node")
logger.Error("failed to close node", "error", err)
}
}()

select {
case <-closed:
case <-time.After(5 * time.Second):
log.Error().Msg("failed to close node in time")
logger.Error("failed to close node in time", "error", err)
}

return nil
}

func setupKeySigner(c *cli.Context) (keysigner.KeySigner, error) {
if c.IsSet(optionKeystorePath.Name) {
return setupKeystoreSigner(c)
// newLogger initializes a *slog.Logger with specified level, format, and sink.
// - lvl: string representation of slog.Level
// - logFmt: format of the log output: "text", "json", "none" defaults to "json"
// - tags: comma-separated list of <name:value> pairs that will be inserted into each log line
// - sink: destination for log output (e.g., os.Stdout, file)
//
// Returns a configured *slog.Logger on success or nil on failure.
func newLogger(lvl, logFmt, tags string, sink io.Writer) (*slog.Logger, error) {
level := new(slog.LevelVar)
if err := level.UnmarshalText([]byte(lvl)); err != nil {
return nil, fmt.Errorf("invalid log level: %w", err)
}
return setupPrivateKeySigner(c)
}

func setupKeystoreSigner(c *cli.Context) (keysigner.KeySigner, error) {
// Load the keystore file
ks := keystore.NewKeyStore(c.String(optionKeystorePath.Name), keystore.LightScryptN, keystore.LightScryptP)
password := c.String(optionKeystorePassword.Name)
ksAccounts := ks.Accounts()

var account accounts.Account
if len(ksAccounts) == 0 {
var err error
account, err = ks.NewAccount(password)
if err != nil {
return nil, fmt.Errorf("failed to create account: %w", err)
var (
handler slog.Handler
options = &slog.HandlerOptions{
AddSource: true,
Level: level,
}
} else {
account = ksAccounts[0]
)
switch logFmt {
case "text":
handler = slog.NewTextHandler(sink, options)
case "json", "none":
handler = slog.NewJSONHandler(sink, options)
default:
return nil, fmt.Errorf("invalid log format: %s", logFmt)
}

fmt.Fprintf(c.App.Writer, "Public address of the key: %s\n", account.Address.Hex())
fmt.Fprintf(c.App.Writer, "Path of the secret key file: %s\n", account.URL.Path)

return keysigner.NewKeystoreSigner(ks, password, account), nil
}

func setupPrivateKeySigner(c *cli.Context) (keysigner.KeySigner, error) {
privKeyFile, err := resolveFilePath(c.String(optionPrivKeyFile.Name))
if err != nil {
return nil, fmt.Errorf("failed to get private key file path: %w", err)
var args []any
for i, p := range strings.Split(tags, ",") {
kv := strings.Split(p, ":")
if len(kv) != 2 {
return nil, fmt.Errorf("invalid tag at index %d", i)
}
args = append(args, strings.ToValidUTF8(kv[0], "�"), strings.ToValidUTF8(kv[1], "�"))
}

if err := createKeyIfNotExists(c, privKeyFile); err != nil {
return nil, fmt.Errorf("failed to create private key: %w", err)
}
return slog.New(handler).With(args...), nil
}

privKey, err := crypto.LoadECDSA(privKeyFile)
if err != nil {
return nil, fmt.Errorf("failed to load private key from file '%s': %w", privKeyFile, err)
func setupKeySigner(c *cli.Context) (keysigner.KeySigner, error) {
if c.IsSet(optionKeystorePath.Name) {
return keysigner.NewKeystoreSigner(c.String(optionKeystorePath.Name), c.String(optionKeystorePassword.Name))
}

return keysigner.NewPrivateKeySigner(privKey), nil
return keysigner.NewPrivateKeySigner(c.String(optionPrivKeyFile.Name))
}
10 changes: 4 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ require (
github.com/lib/pq v1.10.9
github.com/primevprotocol/contracts-abi v0.2.0
github.com/prometheus/client_golang v1.14.0
github.com/rs/zerolog v1.31.0
github.com/testcontainers/testcontainers-go v0.27.0
github.com/urfave/cli/v2 v2.27.1
golang.org/x/crypto v0.18.0
golang.org/x/crypto v0.21.0
golang.org/x/sync v0.6.0
)

Expand Down Expand Up @@ -56,7 +55,6 @@ require (
github.com/klauspost/compress v1.17.5 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
Expand All @@ -67,7 +65,7 @@ require (
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
github.com/opencontainers/runc v1.1.5 // indirect
github.com/opencontainers/runc v1.1.12 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/prometheus/client_model v0.3.0 // indirect
Expand All @@ -86,8 +84,8 @@ require (
github.com/yusufpapurcu/wmi v1.2.3 // indirect
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/time v0.4.0 // indirect
golang.org/x/tools v0.17.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
Expand Down
Loading

0 comments on commit f99244c

Please sign in to comment.