Skip to content

Commit cc6f172

Browse files
authored
Use slog and improve logging (#290)
Default run (no flags): <img width="1920" height="835" alt="Screenshot_20260105_155531" src="https://github.com/user-attachments/assets/7d98bca1-0cf3-4d36-b384-accb0ff83955" /> With `--log-level=trace` or `--log-level=debug`: <img width="1920" height="1005" alt="Screenshot_20260105_161730" src="https://github.com/user-attachments/assets/d867596a-7eca-4df1-b621-bd43583d00e1" />
1 parent e0fe9fe commit cc6f172

File tree

10 files changed

+162
-46
lines changed

10 files changed

+162
-46
lines changed

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ require (
1010
github.com/docker/docker v28.0.1+incompatible
1111
github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0
1212
github.com/ethereum/go-ethereum v1.15.10
13+
github.com/fatih/color v1.18.0
1314
github.com/flashbots/go-boost-utils v1.9.1-0.20250819134059-e5294cb450c9
1415
github.com/flashbots/go-template v1.0.0
1516
github.com/flashbots/mev-boost-relay v0.32.0-rc2
1617
github.com/go-chi/httplog/v2 v2.1.1
1718
github.com/hashicorp/go-uuid v1.0.3
1819
github.com/holiman/uint256 v1.3.2
1920
github.com/otiai10/copy v1.14.1
21+
github.com/phsym/console-slog v0.3.1
2022
github.com/sirupsen/logrus v1.9.3
2123
github.com/spf13/cobra v1.9.1
2224
github.com/spf13/pflag v1.0.6
@@ -93,6 +95,7 @@ require (
9395
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
9496
github.com/lib/pq v1.10.9 // indirect
9597
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
98+
github.com/mattn/go-colorable v0.1.14 // indirect
9699
github.com/mattn/go-isatty v0.0.20 // indirect
97100
github.com/mattn/go-localereader v0.0.1 // indirect
98101
github.com/mattn/go-runewidth v0.0.16 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ github.com/ethereum/go-ethereum v1.15.10 h1:UxqBhpsF2TNF1f7Z/k3RUUHEuLvDGAlHuh/l
126126
github.com/ethereum/go-ethereum v1.15.10/go.mod h1:+S9k+jFzlyVTNcYGvqFhzN/SFhI6vA+aOY4T5tLSPL0=
127127
github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8=
128128
github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
129+
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
130+
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
129131
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
130132
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
131133
github.com/ferranbt/fastssz v0.0.0-20210120143747-11b9eff30ea9/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM=
@@ -357,6 +359,8 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
357359
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
358360
github.com/peterh/liner v1.2.0 h1:w/UPXyl5GfahFxcTOz2j9wCIHNI+pUPr2laqpojKNCg=
359361
github.com/peterh/liner v1.2.0/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
362+
github.com/phsym/console-slog v0.3.1 h1:Fuzcrjr40xTc004S9Kni8XfNsk+qrptQmyR+wZw9/7A=
363+
github.com/phsym/console-slog v0.3.1/go.mod h1:oJskjp/X6e6c0mGpfP8ELkfKUsrkDifYRAqJQgmdDS0=
360364
github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk=
361365
github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
362366
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=

main.go

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,24 @@ import (
55
_ "embed"
66
"fmt"
77
"log"
8+
"log/slog"
89
"os"
910
"sort"
1011
"strings"
1112
"time"
1213

14+
"github.com/fatih/color"
1315
"github.com/flashbots/builder-playground/playground"
1416
"github.com/flashbots/builder-playground/utils"
17+
"github.com/flashbots/builder-playground/utils/logging"
1518
"github.com/flashbots/builder-playground/utils/mainctx"
1619
"github.com/spf13/cobra"
1720
)
1821

1922
var version = "dev"
2023

24+
var greenColor = color.New(color.FgGreen)
25+
2126
var (
2227
keepFlag bool
2328
outputFlag string
@@ -315,17 +320,17 @@ func main() {
315320
}
316321

317322
func runIt(recipe playground.Recipe) error {
323+
fmt.Println()
324+
318325
var logLevel playground.LogLevel
319326
if err := logLevel.Unmarshal(logLevelFlag); err != nil {
320327
return fmt.Errorf("failed to parse log level: %w", err)
321328
}
322-
323-
if logLevel == playground.LevelTrace {
324-
// TODO: We can remove this once we have a logger with log.Trace support
325-
utils.TraceMode = true
326-
}
327-
328-
log.Printf("Log level: %s\n", logLevel)
329+
logging.ConfigureSlog(logLevelFlag)
330+
sessionID := utils.GeneratePetName()
331+
slog.Info("Welcome to Builder Playground! ⚡️🤖")
332+
slog.Info("Session ID: "+greenColor.Sprint(sessionID), "log-level", logLevel)
333+
slog.Info("")
329334

330335
// parse the overrides
331336
overrides := map[string]string{}
@@ -342,7 +347,7 @@ func runIt(recipe playground.Recipe) error {
342347
return err
343348
}
344349

345-
log.Println("Building artifacts...")
350+
slog.Debug("Building artifacts...")
346351
builder := recipe.Artifacts()
347352
builder.GenesisDelay(genesisDelayFlag)
348353
builder.PrefundedAccounts(prefundedAccounts)
@@ -360,12 +365,12 @@ func runIt(recipe playground.Recipe) error {
360365
},
361366
}
362367

363-
svcManager := playground.NewManifest(exCtx, out)
368+
svcManager := playground.NewManifest(exCtx, out, sessionID)
364369

365370
recipe.Apply(svcManager)
366371

367372
// generate the dot graph
368-
log.Println("Generating dot graph...")
373+
slog.Debug("Generating dot graph...")
369374
dotGraph := svcManager.GenerateDotGraph()
370375
if err := out.WriteFile("graph.dot", dotGraph); err != nil {
371376
return err
@@ -410,11 +415,9 @@ func runIt(recipe playground.Recipe) error {
410415
}
411416

412417
// Add callback to log service updates in debug mode
413-
if logLevel == playground.LevelDebug {
414-
cfg.AddCallback(func(serviceName string, update playground.TaskStatus) {
415-
log.Printf("[DEBUG] [%s] %s\n", serviceName, update)
416-
})
417-
}
418+
cfg.AddCallback(func(serviceName string, update playground.TaskStatus) {
419+
slog.Debug("service update", "service", serviceName, "status", update)
420+
})
418421

419422
dockerRunner, err := playground.NewLocalRunner(cfg)
420423
if err != nil {
@@ -423,50 +426,53 @@ func runIt(recipe playground.Recipe) error {
423426

424427
ctx := mainctx.Get()
425428

426-
log.Println("Starting services...")
429+
slog.Info("Starting services... ⏳", "session-id", svcManager.ID)
427430
if err := dockerRunner.Run(ctx); err != nil {
428431
dockerRunner.Stop(keepFlag)
429432
return fmt.Errorf("failed to run docker: %w", err)
430433
}
431434

432435
if !interactive {
436+
log.Println()
437+
log.Println("All services started! ✅")
433438
// print services info
434-
fmt.Printf("\n========= Services started =========\n")
435439
for _, ss := range svcManager.Services {
436440
ports := ss.GetPorts()
437441
sort.Slice(ports, func(i, j int) bool {
438442
return ports[i].Name < ports[j].Name
439443
})
440444

441-
portsStr := []string{}
445+
var portsStr []any
442446
for _, p := range ports {
443447
protocol := ""
444448
if p.Protocol == playground.ProtocolUDP {
445449
protocol = "/udp"
446450
}
447-
portsStr = append(portsStr, fmt.Sprintf("%s: %d/%d%s", p.Name, p.Port, p.HostPort, protocol))
451+
portsStr = append(portsStr, p.Name, fmt.Sprintf("%d/%d%s", p.Port, p.HostPort, protocol))
448452
}
449-
fmt.Printf("- %s (%s)\n", ss.Name, strings.Join(portsStr, ", "))
453+
slog.Info("• "+ss.Name, portsStr...)
450454
}
455+
log.Println()
451456
}
452457

453-
fmt.Println("\nWaiting for services to get healthy...")
458+
log.Println("Waiting for services to get healthy...")
454459
waitCtx, cancel := context.WithTimeout(ctx, time.Minute)
455460
defer cancel()
456461
if err := dockerRunner.WaitForReady(waitCtx); err != nil {
457462
dockerRunner.Stop(keepFlag)
458463
return fmt.Errorf("failed to wait for service readiness: %w", err)
459464
}
460465

461-
fmt.Println("\nAll services are healthy! Ready to accept transactions.")
462-
fmt.Println("Session ID:", svcManager.ID)
466+
slog.Info("All services are healthy! Ready to accept transactions. 🚀", "session-id", svcManager.ID)
463467

464468
// get the output from the recipe
465469
output := recipe.Output(svcManager)
466470
if len(output) > 0 {
467-
fmt.Printf("\n========= Output =========\n")
471+
slog.Info("")
472+
slog.Info("Recipe outputs 🔍")
468473
for k, v := range output {
469-
fmt.Printf("- %s: %v\n", k, v)
474+
valStr := fmt.Sprintf("%v", v)
475+
slog.Info("• " + k + ": " + greenColor.Sprint(valStr))
470476
}
471477
}
472478

@@ -492,13 +498,13 @@ func runIt(recipe playground.Recipe) error {
492498

493499
select {
494500
case <-ctx.Done():
495-
fmt.Println("Stopping...")
501+
log.Println("Stopping...")
496502
case err := <-dockerRunner.ExitErr():
497-
fmt.Println("Service failed:", err)
503+
log.Println("Service failed:", err)
498504
case err := <-watchdogErr:
499-
fmt.Println("Watchdog failed:", err)
505+
log.Println("Watchdog failed:", err)
500506
case <-timerCh:
501-
fmt.Println("Timeout reached")
507+
log.Println("Timeout reached")
502508
}
503509

504510
if err := dockerRunner.Stop(keepFlag); err != nil {

playground/artifacts.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"fmt"
1313
"io"
1414
"log"
15+
"log/slog"
1516
"maps"
1617
"math/big"
1718
"os"
@@ -176,7 +177,7 @@ func (b *ArtifactsBuilder) Build(out *output) error {
176177
}
177178

178179
block := gen.ToBlock()
179-
log.Printf("Genesis block hash: %s", block.Hash())
180+
slog.Info("Genesis block created", "hash", block.Hash())
180181

181182
var v int
182183
if b.applyLatestL1Fork {
@@ -185,7 +186,7 @@ func (b *ArtifactsBuilder) Build(out *output) error {
185186
v = version.Deneb
186187
}
187188

188-
log.Println("Generating keys...")
189+
slog.Debug("Generating keys...")
189190
keys, err := keys.GetPregeneratedBLSKeys()
190191
if err != nil {
191192
return err
@@ -213,7 +214,7 @@ func (b *ArtifactsBuilder) Build(out *output) error {
213214
return err
214215
}
215216

216-
log.Println("Writing artifacts...")
217+
slog.Debug("Writing artifacts...")
217218
err = out.WriteBatch(map[string]interface{}{
218219
"testnet/config.yaml": func() ([]byte, error) { return convert(config) },
219220
"testnet/genesis.ssz": state,
@@ -228,7 +229,7 @@ func (b *ArtifactsBuilder) Build(out *output) error {
228229
if err != nil {
229230
return err
230231
}
231-
log.Println("Done writing artifacts.")
232+
slog.Debug("Done writing artifacts.")
232233

233234
{
234235
// We have to start slightly ahead of L1 genesis time

playground/manifest.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,13 @@ func (m *Manifest) ApplyOverrides(overrides map[string]string) error {
7272
return nil
7373
}
7474

75-
func NewManifest(ctx *ExContext, out *output) *Manifest {
75+
func NewManifest(ctx *ExContext, out *output, name ...string) *Manifest {
76+
if len(name) == 0 {
77+
name = []string{utils.GeneratePetName()}
78+
}
7679
ctx.Output = out
7780
return &Manifest{
78-
ID: utils.GeneratePetName(),
81+
ID: name[0],
7982
ctx: ctx,
8083
out: out,
8184
overrides: make(map[string]string),

playground/recipe_buildernet.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func (b *BuilderNetRecipe) Output(manifest *Manifest) map[string]interface{} {
5454
admin := builderHubService.MustGetPort("admin")
5555
internal := builderHubService.MustGetPort("internal")
5656

57-
output["builder-hub-http (proxy)"] = fmt.Sprintf("http://localhost:%d", http.HostPort)
57+
output["builder-hub-proxy"] = fmt.Sprintf("http://localhost:%d", http.HostPort)
5858
output["builder-hub-admin"] = fmt.Sprintf("http://localhost:%d", admin.HostPort)
5959
output["builder-hub-internal"] = fmt.Sprintf("http://localhost:%d", internal.HostPort)
6060

utils/logging/logging.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package logging
2+
3+
import (
4+
"log"
5+
"log/slog"
6+
"os"
7+
"time"
8+
9+
"github.com/phsym/console-slog"
10+
)
11+
12+
// ConfigureSlog configures the default logger.
13+
func ConfigureSlog(logLevel string) slog.Level {
14+
// We use debug level for trace-level logs.
15+
if logLevel == "trace" {
16+
logLevel = "debug"
17+
}
18+
var level slog.Level
19+
err := level.UnmarshalText([]byte(logLevel))
20+
if err != nil {
21+
log.Fatalf("invalid log level: %s", logLevel)
22+
}
23+
logger := newConsoleLogger(level <= slog.LevelDebug)
24+
slog.SetDefault(logger)
25+
return level
26+
}
27+
28+
func newConsoleLogger(debug bool) *slog.Logger {
29+
level := slog.LevelInfo.Level()
30+
if debug {
31+
level = slog.LevelDebug.Level()
32+
}
33+
handler := console.NewHandler(os.Stdout, &console.HandlerOptions{
34+
Level: level,
35+
TimeFormat: time.DateTime,
36+
Theme: newTheme(),
37+
})
38+
return slog.New(handler)
39+
}

utils/logging/theme.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package logging
2+
3+
import (
4+
"log/slog"
5+
6+
"github.com/phsym/console-slog"
7+
)
8+
9+
type themeDef struct {
10+
name string
11+
timestamp console.ANSIMod
12+
source console.ANSIMod
13+
message console.ANSIMod
14+
messageDebug console.ANSIMod
15+
attrKey console.ANSIMod
16+
attrValue console.ANSIMod
17+
attrValueError console.ANSIMod
18+
levelError console.ANSIMod
19+
levelWarn console.ANSIMod
20+
levelInfo console.ANSIMod
21+
levelDebug console.ANSIMod
22+
}
23+
24+
func (t themeDef) Name() string { return t.name }
25+
func (t themeDef) Timestamp() console.ANSIMod { return t.timestamp }
26+
func (t themeDef) Source() console.ANSIMod { return t.source }
27+
func (t themeDef) Message() console.ANSIMod { return t.message }
28+
func (t themeDef) MessageDebug() console.ANSIMod { return t.messageDebug }
29+
func (t themeDef) AttrKey() console.ANSIMod { return t.attrKey }
30+
func (t themeDef) AttrValue() console.ANSIMod { return t.attrValue }
31+
func (t themeDef) AttrValueError() console.ANSIMod { return t.attrValueError }
32+
func (t themeDef) LevelError() console.ANSIMod { return t.levelError }
33+
func (t themeDef) LevelWarn() console.ANSIMod { return t.levelWarn }
34+
func (t themeDef) LevelInfo() console.ANSIMod { return t.levelInfo }
35+
func (t themeDef) LevelDebug() console.ANSIMod { return t.levelDebug }
36+
func (t themeDef) Level(level slog.Level) console.ANSIMod {
37+
switch {
38+
case level >= slog.LevelError:
39+
return t.LevelError()
40+
case level >= slog.LevelWarn:
41+
return t.LevelWarn()
42+
case level >= slog.LevelInfo:
43+
return t.LevelInfo()
44+
default:
45+
return t.LevelDebug()
46+
}
47+
}
48+
49+
func newTheme() console.Theme {
50+
return themeDef{
51+
name: "builder-playground",
52+
timestamp: console.ToANSICode(console.BrightBlack),
53+
source: console.ToANSICode(console.Bold, console.BrightBlack),
54+
message: console.ToANSICode(console.Bold),
55+
messageDebug: console.ToANSICode(console.Faint),
56+
attrKey: console.ToANSICode(console.Yellow, console.Faint, console.Bold),
57+
attrValue: console.ToANSICode(console.Faint),
58+
attrValueError: console.ToANSICode(console.Red),
59+
levelError: console.ToANSICode(console.Red),
60+
levelWarn: console.ToANSICode(console.Yellow),
61+
levelInfo: console.ToANSICode(console.Faint),
62+
levelDebug: console.ToANSICode(console.Cyan, console.Faint),
63+
}
64+
}

0 commit comments

Comments
 (0)