Try   HackMD

Migrating Prysm to Slog

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
slog.SetDefault(logger)
slog.Info("message")
slog.Error("message")
slog.Debug("message")
slog.Warn("message")

Contextual logging:

Instead of logrus.WithFields, contextual logs can be added in a key=>value style. A problem is it not type-safe and can mess up with unbalanced keys.

logger.Info(
  "incoming request",
  "method", "GET",
  "time_taken_ms", 158,
  "path", "/hello/world?q=search",
  "status", 200,
  "user_agent", "Googlebot/2.1 (+http://www.google.com/bot.html)",
)

Compared to logrus, however, we get strongly-typed fields with this usage:

logger.Info(
  "incoming request",
  slog.String("method", "GET"),
  slog.Int("time_taken_ms", 158),
  slog.String("path", "/hello/world?q=search"),
  slog.Int("status", 200),
  slog.String(
    "user_agent",
    "Googlebot/2.1 (+http://www.google.com/bot.html)",
  ),
)

We can also create grouped attributes:

 logger.LogAttrs(
  context.Background(),
  slog.LevelInfo,
  "image uploaded",
  slog.Int("id", 23123),
  slog.Group("properties",
    slog.Int("width", 4000),
    slog.Int("height", 3000),
    slog.String("format", "jpeg"),
  ),
)

Child loggers can be helpful as part of a particular function, as groups can be attached to them. For example, we could have a group logger in trace mode or custom mode for different properties such as peers, attestation contents, etc.

child := logger.With( slog.Group("program_info", slog.Int("pid", os.Getpid()), slog.String("go_version", buildInfo.GoVersion), ), )

We can customize log levels, which would be excellent for p2p and for user experience:

var LevelNames = map[slog.Leveler]string{ LevelTrace: "TRACE", LevelNotice: "NOTICE", LevelFatal: "FATAL", }

The source of a log can also be included in the log itself:

opts := slog.HandlerOptions{ AddSource: true, Level: slog.LevelDebug, } "source":{"function":"main.main","file":"/home/ayo/dev/demo/slog/main.go","line":30}

We can also implement a LogValue interface on any of our types, including attestations, blocks, or any struct to customize how they will be logged, which is very helpful to minimize use of contextual logging.

func (u *User) LogValue() slog.Value {
    return slog.GroupValue(
        slog.String("id", u.ID),
        slog.String("name", u.FirstName+" "+u.LastName),
    )
}

Handlers can be customized for nice colors and formatting in any way we prefer. We could offer users a nicer logging experience this way:

func main() {
	opts := PrettyHandlerOptions{
		SlogOpts: slog.HandlerOptions{
			Level: slog.LevelDebug,
		},
	}
	handler := NewPrettyHandler(os.Stdout, opts)
	logger := slog.New(handler)
	slog.SetDefault(logger)
	slog.Debug(
		"executing database query",
		slog.String("query", "SELECT * FROM users"),
	)
	slog.Info("image upload successful", slog.String("image_id", "39ud88"))
	slog.Warn(
		"storage is 90% full",
		slog.String("available_space", "900.1 MB"),
	)
	slog.Error(
		"An error occurred while processing the request",
		slog.String("url", "https://example.com"),
	)
}

With output:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Integration

Here are some things to note about integrating these changes

  • Fluentd integration and correct JSON formatting (need to make sure that the slog prysm format with JSON matches the fluentd format)
  • File output works correctly: need to make sure that outputting our Prysm slogs to a file work the same as with logrus
  • Determine which types could implement the LogValue interface in Prysm to make them easy to log and avoid a major refactoring
  • Ensure prefixes work
  • Keep debug / info / warn / error the same in the first refactor, then introduce a custom p2p log level and change some of our logs to that
  • Start with the validator client: change all logrus logs in there
  • The webUI supports log streaming: ensure we can still do this