From 6fa0e9cf5f0627771d516da348e8b645cfcd25cc Mon Sep 17 00:00:00 2001 From: Bastin <43618253+Inspector-Butters@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:20:12 +0100 Subject: [PATCH] Logrus hooks for terminal vs log-file output (#16102) ## Review after #16059 **What type of PR is this?** Feature **What does this PR do?** This PR introduces logrus writer hooks into the logging of prysm. when log-format is text: - set the default logrus output to be `io.Discard` - create a writer hook for terminal, with formatting and coloring enabled. - create a separate writer hook for log-file (if enabled), without coloring. This immediately allows for having formatted/colored terminal logs, while keeping the log-file clean. --- changelog/bastin_logrus-hooks-add.md | 4 ++++ cmd/beacon-chain/main.go | 17 +++++++++++----- cmd/client-stats/main.go | 2 +- cmd/validator/main.go | 18 ++++++++++++----- io/logs/BUILD.bazel | 2 ++ io/logs/hook.go | 29 ++++++++++++++++++++++++++++ io/logs/logutil.go | 23 ++++++++++++++++++++-- io/logs/logutil_test.go | 6 +++--- tools/bootnode/bootnode.go | 2 +- 9 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 changelog/bastin_logrus-hooks-add.md create mode 100644 io/logs/hook.go diff --git a/changelog/bastin_logrus-hooks-add.md b/changelog/bastin_logrus-hooks-add.md new file mode 100644 index 0000000000..4bc52321af --- /dev/null +++ b/changelog/bastin_logrus-hooks-add.md @@ -0,0 +1,4 @@ +### Added + +- Added separate logrus hooks for handling the formatting and output of terminal logs vs log-file logs, instead of the + default logrus output. \ No newline at end of file diff --git a/cmd/beacon-chain/main.go b/cmd/beacon-chain/main.go index df25adde48..d11489ed84 100644 --- a/cmd/beacon-chain/main.go +++ b/cmd/beacon-chain/main.go @@ -4,6 +4,7 @@ package main import ( "context" "fmt" + "io" "os" "path/filepath" runtimeDebug "runtime/debug" @@ -172,14 +173,20 @@ func before(ctx *cli.Context) error { switch format { case "text": + // disabling logrus default output so we can control it via different hooks + logrus.SetOutput(io.Discard) + + // create a custom formatter and hook for terminal output formatter := new(prefixed.TextFormatter) formatter.TimestampFormat = "2006-01-02 15:04:05.00" formatter.FullTimestamp = true + formatter.ForceFormatting = true + formatter.ForceColors = true - // If persistent log files are written - we disable the log messages coloring because - // the colors are ANSI codes and seen as gibberish in the log files. - formatter.DisableColors = ctx.String(cmd.LogFileName.Name) != "" - logrus.SetFormatter(formatter) + logrus.AddHook(&logs.WriterHook{ + Formatter: formatter, + Writer: os.Stderr, + }) case "fluentd": f := joonix.NewFormatter() @@ -202,7 +209,7 @@ func before(ctx *cli.Context) error { logFileName := ctx.String(cmd.LogFileName.Name) if logFileName != "" { - if err := logs.ConfigurePersistentLogging(logFileName); err != nil { + if err := logs.ConfigurePersistentLogging(logFileName, format); err != nil { log.WithError(err).Error("Failed to configuring logging to disk.") } } diff --git a/cmd/client-stats/main.go b/cmd/client-stats/main.go index 4c5b582ff6..4b749c9f1e 100644 --- a/cmd/client-stats/main.go +++ b/cmd/client-stats/main.go @@ -86,7 +86,7 @@ func main() { logFileName := ctx.String(cmd.LogFileName.Name) if logFileName != "" { - if err := logs.ConfigurePersistentLogging(logFileName); err != nil { + if err := logs.ConfigurePersistentLogging(logFileName, format); err != nil { log.WithError(err).Error("Failed to configuring logging to disk.") } } diff --git a/cmd/validator/main.go b/cmd/validator/main.go index 284df48efd..35f61d7cc3 100644 --- a/cmd/validator/main.go +++ b/cmd/validator/main.go @@ -5,6 +5,7 @@ package main import ( "fmt" + "io" "os" "path/filepath" runtimeDebug "runtime/debug" @@ -151,13 +152,20 @@ func main() { format := ctx.String(cmd.LogFormat.Name) switch format { case "text": + // disabling logrus default output so we can control it via different hooks + logrus.SetOutput(io.Discard) + + // create a custom formatter and hook for terminal output formatter := new(prefixed.TextFormatter) formatter.TimestampFormat = "2006-01-02 15:04:05.00" formatter.FullTimestamp = true - // If persistent log files are written - we disable the log messages coloring because - // the colors are ANSI codes and seen as gibberish in the log files. - formatter.DisableColors = logFileName != "" - logrus.SetFormatter(formatter) + formatter.ForceFormatting = true + formatter.ForceColors = true + + logrus.AddHook(&logs.WriterHook{ + Formatter: formatter, + Writer: os.Stderr, + }) case "fluentd": f := joonix.NewFormatter() if err := joonix.DisableTimestampFormat(f); err != nil { @@ -177,7 +185,7 @@ func main() { } if logFileName != "" { - if err := logs.ConfigurePersistentLogging(logFileName); err != nil { + if err := logs.ConfigurePersistentLogging(logFileName, format); err != nil { log.WithError(err).Error("Failed to configuring logging to disk.") } } diff --git a/io/logs/BUILD.bazel b/io/logs/BUILD.bazel index ac6b538156..f42ba78adf 100644 --- a/io/logs/BUILD.bazel +++ b/io/logs/BUILD.bazel @@ -3,6 +3,7 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ + "hook.go", "logutil.go", "stream.go", ], @@ -14,6 +15,7 @@ go_library( "//config/params:go_default_library", "//crypto/rand:go_default_library", "//io/file:go_default_library", + "//runtime/logging/logrus-prefixed-formatter:go_default_library", "@com_github_hashicorp_golang_lru//:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", ], diff --git a/io/logs/hook.go b/io/logs/hook.go new file mode 100644 index 0000000000..e8276a8e2d --- /dev/null +++ b/io/logs/hook.go @@ -0,0 +1,29 @@ +package logs + +import ( + "io" + + "github.com/sirupsen/logrus" +) + +type WriterHook struct { + AllowedLevels []logrus.Level + Writer io.Writer + Formatter logrus.Formatter +} + +func (hook *WriterHook) Levels() []logrus.Level { + if hook.AllowedLevels == nil || len(hook.AllowedLevels) == 0 { + return logrus.AllLevels + } + return hook.AllowedLevels +} + +func (hook *WriterHook) Fire(entry *logrus.Entry) error { + line, err := hook.Formatter.Format(entry) + if err != nil { + return err + } + _, err = hook.Writer.Write(line) + return err +} diff --git a/io/logs/logutil.go b/io/logs/logutil.go index 7208909115..50939d735a 100644 --- a/io/logs/logutil.go +++ b/io/logs/logutil.go @@ -11,6 +11,7 @@ import ( "github.com/OffchainLabs/prysm/v7/config/params" "github.com/OffchainLabs/prysm/v7/io/file" + prefixed "github.com/OffchainLabs/prysm/v7/runtime/logging/logrus-prefixed-formatter" "github.com/sirupsen/logrus" ) @@ -20,7 +21,7 @@ func addLogWriter(w io.Writer) { } // ConfigurePersistentLogging adds a log-to-file writer. File content is identical to stdout. -func ConfigurePersistentLogging(logFileName string) error { +func ConfigurePersistentLogging(logFileName string, format string) error { logrus.WithField("logFileName", logFileName).Info("Logs will be made persistent") if err := file.MkdirAll(filepath.Dir(logFileName)); err != nil { return err @@ -30,7 +31,25 @@ func ConfigurePersistentLogging(logFileName string) error { return err } - addLogWriter(f) + if format != "text" { + addLogWriter(f) + + logrus.Info("File logging initialized") + return nil + } + + // Create formatter and writer hook + formatter := new(prefixed.TextFormatter) + formatter.TimestampFormat = "2006-01-02 15:04:05.00" + formatter.FullTimestamp = true + // If persistent log files are written - we disable the log messages coloring because + // the colors are ANSI codes and seen as gibberish in the log files. + formatter.DisableColors = true + + logrus.AddHook(&WriterHook{ + Formatter: formatter, + Writer: f, + }) logrus.Info("File logging initialized") return nil diff --git a/io/logs/logutil_test.go b/io/logs/logutil_test.go index 7d848c8215..db8efede33 100644 --- a/io/logs/logutil_test.go +++ b/io/logs/logutil_test.go @@ -34,13 +34,13 @@ func TestConfigurePersistantLogging(t *testing.T) { logFileName := "test.log" existingDirectory := "test-1-existing-testing-dir" - err := ConfigurePersistentLogging(fmt.Sprintf("%s/%s/%s", testParentDir, existingDirectory, logFileName)) + err := ConfigurePersistentLogging(fmt.Sprintf("%s/%s/%s", testParentDir, existingDirectory, logFileName), "text") require.NoError(t, err) // 2. Test creation of file along with parent directory nonExistingDirectory := "test-2-non-existing-testing-dir" - err = ConfigurePersistentLogging(fmt.Sprintf("%s/%s/%s", testParentDir, nonExistingDirectory, logFileName)) + err = ConfigurePersistentLogging(fmt.Sprintf("%s/%s/%s", testParentDir, nonExistingDirectory, logFileName), "text") require.NoError(t, err) // 3. Test creation of file in an existing parent directory with a non-existing sub-directory @@ -51,7 +51,7 @@ func TestConfigurePersistantLogging(t *testing.T) { return } - err = ConfigurePersistentLogging(fmt.Sprintf("%s/%s/%s/%s", testParentDir, existingDirectory, nonExistingSubDirectory, logFileName)) + err = ConfigurePersistentLogging(fmt.Sprintf("%s/%s/%s/%s", testParentDir, existingDirectory, nonExistingSubDirectory, logFileName), "text") require.NoError(t, err) //4. Create log file in a directory without 700 permissions diff --git a/tools/bootnode/bootnode.go b/tools/bootnode/bootnode.go index c64646aced..a602a9b312 100644 --- a/tools/bootnode/bootnode.go +++ b/tools/bootnode/bootnode.go @@ -71,7 +71,7 @@ func main() { flag.Parse() if *logFileName != "" { - if err := logs.ConfigurePersistentLogging(*logFileName); err != nil { + if err := logs.ConfigurePersistentLogging(*logFileName, "text"); err != nil { log.WithError(err).Error("Failed to configuring logging to disk.") } }