Commit e1d6fb39 authored by protolambda's avatar protolambda

op-service: type-safe CLI log utils

parent 9801eb1d
......@@ -646,8 +646,8 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
TxMgrConfig: newTxMgrConfig(sys.EthInstances["l1"].WSEndpoint(), cfg.Secrets.Proposer),
AllowNonFinalized: cfg.NonFinalizedProposals,
LogConfig: oplog.CLIConfig{
Level: "info",
Format: "text",
Level: log.LvlInfo,
Format: oplog.FormatText,
},
}, sys.cfg.Loggers["proposer"], proposermetrics.NoopMetrics)
if err != nil {
......@@ -675,8 +675,8 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
PollInterval: 50 * time.Millisecond,
TxMgrConfig: newTxMgrConfig(sys.EthInstances["l1"].WSEndpoint(), cfg.Secrets.Batcher),
LogConfig: oplog.CLIConfig{
Level: "info",
Format: "text",
Level: log.LvlInfo,
Format: oplog.FormatText,
},
}, sys.cfg.Loggers["batcher"], batchermetrics.NoopMetrics)
if err != nil {
......
package main
import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-program/client"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
)
......@@ -9,8 +11,8 @@ func main() {
// Default to a machine parsable but relatively human friendly log format.
// Don't do anything fancy to detect if color output is supported.
logger := oplog.NewLogger(nil, oplog.CLIConfig{
Level: "info",
Format: "logfmt",
Level: log.LvlInfo,
Format: oplog.FormatLogFmt,
Color: false,
})
client.Main(logger)
......
......@@ -6,10 +6,11 @@ import (
"os"
"strings"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"
"golang.org/x/term"
"github.com/ethereum/go-ethereum/log"
opservice "github.com/ethereum-optimism/optimism/op-service"
)
......@@ -21,33 +22,17 @@ const (
func CLIFlags(envPrefix string) []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
&cli.GenericFlag{
Name: LevelFlagName,
Usage: "The lowest log level that will be output",
Value: "info",
Value: NewLvlFlagValue(log.LvlInfo),
EnvVars: opservice.PrefixEnvVar(envPrefix, "LOG_LEVEL"),
Action: func(ctx *cli.Context, s string) error {
level := strings.ToLower(s)
_, err := log.LvlFromString(level)
if err != nil {
return fmt.Errorf("unrecognized log level: %w", err)
}
return nil
},
},
&cli.StringFlag{
&cli.GenericFlag{
Name: FormatFlagName,
Usage: "Format the log output. Supported formats: 'text', 'terminal', 'logfmt', 'json', 'json-pretty',",
Value: "text",
Value: NewFormatFlagValue(FormatText),
EnvVars: opservice.PrefixEnvVar(envPrefix, "LOG_FORMAT"),
Action: func(ctx *cli.Context, s string) error {
switch s {
case "json", "json-pretty", "terminal", "text", "logfmt":
return nil
default:
return fmt.Errorf("unrecognized log format: %s", s)
}
},
},
&cli.BoolFlag{
Name: ColorFlagName,
......@@ -57,10 +42,101 @@ func CLIFlags(envPrefix string) []cli.Flag {
}
}
// LvlFlagValue is a value type for cli.GenericFlag to parse and validate log-level values.
// Log level: trace, debug, info, warn, error, crit. Capitals are accepted too.
type LvlFlagValue log.Lvl
func NewLvlFlagValue(lvl log.Lvl) *LvlFlagValue {
return (*LvlFlagValue)(&lvl)
}
func (fv *LvlFlagValue) Set(value string) error {
value = strings.ToLower(value) // ignore case
lvl, err := log.LvlFromString(value)
if err != nil {
return err
}
*fv = LvlFlagValue(lvl)
return nil
}
func (fv LvlFlagValue) String() string {
return log.Lvl(fv).String()
}
func (fv LvlFlagValue) LogLvl() log.Lvl {
return log.Lvl(fv)
}
var _ cli.Generic = (*LvlFlagValue)(nil)
// FormatType defines a type of log format.
// Supported formats: 'text', 'terminal', 'logfmt', 'json', 'json-pretty'
type FormatType string
const (
FormatText FormatType = "text"
FormatTerminal FormatType = "terminal"
FormatLogFmt FormatType = "logfmt"
FormatJSON FormatType = "json"
FormatJSONPretty FormatType = "json-pretty"
)
// Formatter turns a format type and color into a structured Format object
func (ft FormatType) Formatter(color bool) log.Format {
switch ft {
case FormatJSON:
return log.JSONFormat()
case FormatJSONPretty:
return log.JSONFormatEx(true, true)
case FormatText:
if term.IsTerminal(int(os.Stdout.Fd())) {
return log.TerminalFormat(color)
} else {
return log.LogfmtFormat()
}
case FormatTerminal:
return log.TerminalFormat(color)
case FormatLogFmt:
return log.LogfmtFormat()
default:
panic(fmt.Errorf("failed to create `log.Format` for format-type=%q and color=%v", ft, color))
}
}
func (ft FormatType) String() string {
return string(ft)
}
// FormatFlagValue is a value type for cli.GenericFlag to parse and validate log-formatting-type values
type FormatFlagValue FormatType
func NewFormatFlagValue(fmtType FormatType) *FormatFlagValue {
return (*FormatFlagValue)(&fmtType)
}
func (fv *FormatFlagValue) Set(value string) error {
switch FormatType(value) {
case FormatText, FormatTerminal, FormatLogFmt, FormatJSON, FormatJSONPretty:
*fv = FormatFlagValue(value)
return nil
default:
return fmt.Errorf("unrecognized flag format: %q", value)
}
}
func (fv FormatFlagValue) String() string {
return FormatType(fv).String()
}
func (fv FormatFlagValue) FormatType() FormatType {
return FormatType(fv)
}
type CLIConfig struct {
Level string // Log level: trace, debug, info, warn, error, crit. Capitals are accepted too.
Color bool // Color the log output. Defaults to true if terminal is detected.
Format string // Format the log output. Supported formats: 'text', 'terminal', 'logfmt', 'json', 'json-pretty'
Level log.Lvl
Color bool
Format FormatType
}
// NewLogger creates a new configured logger.
......@@ -70,60 +146,30 @@ func NewLogger(ctx *cli.Context, cfg CLIConfig) log.Logger {
if ctx != nil && ctx.App != nil {
wr = ctx.App.Writer
}
handler := log.StreamHandler(wr, Format(cfg.Format, cfg.Color))
handler := log.StreamHandler(wr, cfg.Format.Formatter(cfg.Color))
handler = log.SyncHandler(handler)
handler = NewDynamicLogHandler(Level(cfg.Level), handler)
handler = NewDynamicLogHandler(cfg.Level, handler)
logger := log.New()
logger.SetHandler(handler)
return logger
}
// DefaultCLIConfig creates a default log configuration.
// Color defaults to true if terminal is detected.
func DefaultCLIConfig() CLIConfig {
return CLIConfig{
Level: "info",
Format: "text",
Level: log.LvlInfo,
Format: FormatText,
Color: term.IsTerminal(int(os.Stdout.Fd())),
}
}
func ReadCLIConfig(ctx *cli.Context) CLIConfig {
cfg := DefaultCLIConfig()
cfg.Level = ctx.String(LevelFlagName)
cfg.Format = ctx.String(FormatFlagName)
cfg.Level = ctx.Generic(LevelFlagName).(*LvlFlagValue).LogLvl()
cfg.Format = ctx.Generic(FormatFlagName).(*FormatFlagValue).FormatType()
if ctx.IsSet(ColorFlagName) {
cfg.Color = ctx.Bool(ColorFlagName)
}
return cfg
}
// Format turns a string and color into a structured Format object
func Format(lf string, color bool) log.Format {
switch lf {
case "json":
return log.JSONFormat()
case "json-pretty":
return log.JSONFormatEx(true, true)
case "text":
if term.IsTerminal(int(os.Stdout.Fd())) {
return log.TerminalFormat(color)
} else {
return log.LogfmtFormat()
}
case "terminal":
return log.TerminalFormat(color)
case "logfmt":
return log.LogfmtFormat()
default:
panic("Failed to create `log.Format` from options")
}
}
// Level parses the level string into an appropriate object
func Level(s string) log.Lvl {
s = strings.ToLower(s) // ignore case
l, err := log.LvlFromString(s)
if err != nil {
panic(fmt.Sprintf("Could not parse log level: %v", err))
}
return l
}
......@@ -29,7 +29,7 @@ func main() {
app.Before = func(c *cli.Context) error {
log.Root().SetHandler(
log.LvlFilterHandler(
oplog.Level(c.String(wheel.GlobalGethLogLvlFlag.Name)),
c.Generic(wheel.GlobalGethLogLvlFlag.Name).(*oplog.LvlFlagValue).LogLvl(),
log.StreamHandler(os.Stdout, log.TerminalFormat(true)),
),
)
......
......@@ -11,12 +11,14 @@ import (
"strings"
"time"
"github.com/urfave/cli/v2"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-node/client"
opservice "github.com/ethereum-optimism/optimism/op-service"
......@@ -33,11 +35,11 @@ func prefixEnvVars(name string) []string {
}
var (
GlobalGethLogLvlFlag = &cli.StringFlag{
GlobalGethLogLvlFlag = &cli.GenericFlag{
Name: "geth-log-level",
Usage: "Set the global geth logging level",
EnvVars: prefixEnvVars("GETH_LOG_LEVEL"),
Value: "error",
Value: oplog.NewLvlFlagValue(log.LvlError),
}
DataDirFlag = &cli.StringFlag{
Name: "data-dir",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment