Structured logging in the core of Golang

By yuseferi, 30 June, 2023
Structured logging in the core of Golang

Today, I attended Gopherconf Europe and Jonathan Amsterdam present a very interesting topic and it was “Structured Logging in the Core of Golang” After 10 years finally it happened. In the following, I’m going to explain it. be with me by the end of this article.

TDLR; it’s a kind of Zap logger in the core with a bit higher performance.

For more than 10 years, people complaining about the absence of the structured logger in the core of the Golang, and due to this shortage several nice packages like, Logrus, Zap, and Zerolog have been introduced by the Golang community. but after 10 years, in 2023, finally, the Google Go core team finally release something very interesting, Slog ( structured Logging) in the core in version 1.21. it’s experimental at the time of writing this article but for sure and based on What Jonathan presents, its performance and structure sound very promising.

To begin using Slog, start by creating a new Go project or using an existing one. Install Slog using the following command:

go get golang.org/x/exp/slog

After installing Slog, you can import it into your project and start using the logger immediately. Here’s an example of how to use the Slog logger

package main

import (
 "golang.org/x/exp/slog"
)

func main() {
 slog.Info("this is sample log with Slog")
}

By default, Slog includes the timestamp, log level, and message in the output. The available log levels are Debug, Info, Warn, and Error. Slog provides support for structured logging in two formats: text and JSON.

package main

import (
 "os"

 "golang.org/x/exp/slog"
)

func main() {
 textHandler := slog.NewTextHandler(os.Stdout)
 logger := slog.New(textHandler)

 logger.Info("this is sample log with Slog")
}

The output in this case will be formatted as key-value pairs, commonly known as logfmt format. This format is human-readable and easy to parse, making it compatible with various modern systems such as DataDog, Splunk, and Grafana Loki.

Alternatively, you can output logs in JSON format by using the JSON handler:

package main

import (
 "os"

 "golang.org/x/exp/slog"
)

func main() {
 jsonHandler := slog.NewJSONHandler(os.Stdout)
 logger := slog.New(jsonHandler)

 logger.Info("this is sample log with Slog")
}

Each log entry will be logged as a JSON object with properties inside it.

In Zap, we had fields, but here in Slog, it’s called attributes, Slog supports the specification of attributes for more detailed logging. You can add attributes of various types, including string, integer, floating-point, boolean, time, duration, and more.

package main

import (
 "os"

 "golang.org/x/exp/slog"
)

func main() {
 textHandler := slog.NewTextHandler(os.Stdout)
 logger := slog.New(textHandler)
 logger.Info("logs with attributes", slog.Int("counter", 1))
}

Like Zap, In the above example, an integer attribute is added using the slog.Int function. You can include multiple attributes of different types as needed.

Slog allows you to group attributes under a single key. For instance, you can group multiple memory attributes together:

package main

import (
 "os"

 "golang.org/x/exp/slog"
)

func main() {
 textHandler := slog.NewTextHandler(os.Stdout)
 logger := slog.New(textHandler)

 logger.Info("Usage Statistics",
  slog.Group("memory",
  slog.Int("current", 5),
  slog.Int("min", 2),
  slog.Int("max", 85)),
  slog.String("app-version", "v1.1.1"),
 )
}

The output will include the grouped attributes with a prefix indicating the grouping.

If you have attributes that should be included in all logs, such as the service name or application version, you can attach them to the handler. These attributes will be automatically included in every log statement: ( it is something like what you could do with With() in Zap)

package main

import (
 "os"

 "golang.org/x/exp/slog"
)

func main() {
 textHandler := slog.NewTextHandler(os.Stdout).
  WithAttrs([]slog.Attr{slog.String("app-version", "v1.1.1")})
 logger := slog.New(textHandler)

 logger.Info("Generating statistics")
 logger.Info("Usage Statistics",
  slog.Group("memory",
   slog.Int("current", 5),
   slog.Int("min", 2),
   slog.Int("max", 85)),
 )
}

By default, the Slog logger does not log debug-level logs. However, you can create a new logger with the log level set to Debug to include debug logs:

package main

import (
 "os"

 "golang.org/x/exp/slog"
)

func main() {
 opts := slog.HandlerOptions{
  Level: slog.LevelDebug,
 }

 textHandler := opts.NewTextHandler(os.Stdout)
 logger := slog.New(textHandler)

 logger.Debug("Debug")
 logger.Info("Info")
 logger.Warn("Warn")
}

Conclusion:

The Slog logging package offers powerful structured logging capabilities for Go applications. With support for various log formats, attribute types, and log levels, Slog provides flexibility and extensibility. While it is still being developed separately from the Go core, Slog has the potential to enhance the logging experience in the Go ecosystem, making it more appealing and efficient.

ref: https://pkg.go.dev/golang.org/x/exp/slog