Back to blog

Slog: Simplifying Structured Logging in Go Applications

4 min
Slog: Simplifying Structured Logging in Go Applications

Introduction

Hello everyone! Today we are going to explore a new way of logging: Slog. Logging provides information about software behavior and helps in identifying and solving problems. Go's standard logging package offers basic logging features, such as logging messages with timestamps. However, as applications grow in complexity, the limitations of the default logging package become evident, leading developers to seek more sophisticated logging.

One of the main limitations of the default logging package is its lack of organization. Slog addresses this by offering first-class support for structured logging, making it easier to produce logs that are readable for humans and machines. Slog offers logging levels out of the box, greater flexibility and extensibility, allowing developers to customize logging behavior to suit their application's needs. This includes support for custom logging levels, multiple output destinations, and context-specific logging.

Slog is designed with performance in mind, providing a faster and more efficient logging solution than the default logging package. This is particularly beneficial in high-performance applications where logging can have a significant impact on performance.

How Slog Works

Let's start with simple logging using the "info" logging level. Here the timestamp, logging level, and message are printed.

package main

import (
    "github.com/slog"
)

func main() {
    slog.Info("Hello, World!")
}

In the terminal, it prints:

2024-03-30T10:00:00Z INFO Hello, World!

Now let's see how different logging levels work. We will try the error and warning levels.

package main

import "github.com/slog"

func main() {
    slog.Error("An error occurred")
    slog.Warning("This is a warning")
}

In the terminal, it prints:

2024-03-30T10:00:00Z ERROR An error occurred
2024-03-30T10:00:00Z WARNING This is a warning

Log Customization

Slog allows printing variables from the code. The first argument is the message and after that we pass key-value pairs. For example, if we want to print the Go version, the key would be "version" and we would get the value from the runtime using the runtime.Version method.

Still, the logs do not look organized and readable for machines. Slog provides two handlers: "text" and "JSON", which format logs well.

Slog Handlers

To use the "text" handler, we create a new logger with the New method of Slog. The first argument is a Slog handler, and we will create a new text handler. It will require an input/output writer, we will use os.Stderr for now. Now we have a new logger. Let's replace slog with logger.

package main

import (
    "os"
    "github.com/slog"
)

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

    logger.Info("Hello, World!")
}
1621872998015 INFO Hello, World!

The time is printed in milliseconds and different logging levels are shown. The message has a "msg" key and other key values are printed here.

Custom Logging Levels

By default, the debug level is off. We need to change the logging level.

package main

import (
    "os"
    "github.com/slog"
)

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

    logger.SetLevel(slog.DebugLevel)

    logger.Debug("This is a debug message")
}

Now the debug print appears on the console.

1621872998015 DEBUG This is a debug message

JSON Handler

Replace the New function of the text handler with New of the JSON handler.

package main

import (
    "os"
    "github.com/slog"
)

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

    logger.Info("Hello, World!")
}

The output is formatted as JSON.

{"time":"2024-03-30T10:00:00Z","level":"INFO","message":"Hello, World!"}

Additional Log Organization

We can create a log group, similar to nested JSON.

package main

import (
    "os"
    "runtime"
    "github.com/slog"
)

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

    logger.Group("OS Info", func(group slog.Group) {
        group.String("OS", runtime.GOOS)
        group.Int("CPUs", runtime.NumCPU())
        group.String("Arch", runtime.GOARCH)
    })

    logger.Info("Hello, World!")
}

The OS information appears as nested JSON.

2024-03-30T10:00:00Z INFO Hello, World!
{
  "OS Info": {
    "OS": "linux",
    "CPUs": 4,
    "Arch": "amd64"
  }
}

Log Attributes

We can add attributes to the logger with the WithAttributes method on the handler. This requires a list of Slog attributes. For example, we will add a Slog string with the key "app version" and put a random version as the value. Now the app version is added to the print.

package main

import (
    "os"
    "github.com/slog"
)

func main() {
    logger := slog.New(slog.NewTextHandler(os.Stderr).WithAttributes(
        slog.String("App Version", "1.0.0"),
    ))

    logger.Info("Hello, World!")
}

In the terminal, it prints:

2024-03-30T10:00:00Z INFO Hello, World! {"App Version":"1.0.0"}

Conclusion

In conclusion, we explored Slog, a library that offers a new way of structured logging in Go applications. Unlike the standard logging package, Slog provides improved organization, custom logging levels, multiple output handlers, and a more flexible syntax for logging messages. Additionally, Slog is designed to be performance-efficient, making it ideal for high-performance applications where logging can have a significant impact on overall system performance.

By adopting Slog in our applications, we can improve log clarity, facilitate troubleshooting, and gain a deeper understanding of software behavior. Structured logging with Slog is not only a functional task but an art that allows us to communicate more effectively with our applications and gain valuable insights to improve their quality and performance.

In summary, Slog is not just a logging tool, but a powerful tool that helps us better understand our digital creations and make informed decisions to continuously improve them.

Share

Related posts