Home

Awesome

Log

<p> <picture> <source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/25087/219742757-c8afe0d9-608a-4845-a555-ef59c0af9ebc.png" width="359"> <source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/25087/219743408-3d7bef51-1409-40c0-8159-acc6e52f078e.png" width="359"> <img src="https://user-images.githubusercontent.com/25087/219742757-c8afe0d9-608a-4845-a555-ef59c0af9ebc.png" width="359" /> </picture> <br> <a href="https://github.com/charmbracelet/log/releases"><img src="https://img.shields.io/github/release/charmbracelet/log.svg" alt="Latest Release"></a> <a href="https://pkg.go.dev/github.com/charmbracelet/log?tab=doc"><img src="https://godoc.org/github.com/golang/gddo?status.svg" alt="Go Docs"></a> <a href="https://github.com/charmbracelet/log/actions"><img src="https://github.com/charmbracelet/log/workflows/build/badge.svg" alt="Build Status"></a> <a href="https://codecov.io/gh/charmbracelet/log"><img alt="Codecov branch" src="https://img.shields.io/codecov/c/github/charmbracelet/log/main.svg"></a> <a href="https://goreportcard.com/report/github.com/charmbracelet/log"><img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/charmbracelet/log"></a> </p>

A minimal and colorful Go logging library. 🪵

<picture> <source media="(prefers-color-scheme: dark)" srcset="https://vhs.charm.sh/vhs-1wBImk2iSIuiiD7Ib9rufi.gif"> <source media="(prefers-color-scheme: light)" srcset="https://vhs.charm.sh/vhs-1wBImk2iSIuiiD7Ib9rufi.gif"> <!-- <source media="(prefers-color-scheme: light)" srcset="https://vhs.charm.sh/vhs-2NvOYS29AauVRgRRPmquXx.gif"> --> <img src="https://vhs.charm.sh/vhs-1wBImk2iSIuiiD7Ib9rufi.gif" alt="Made with VHS" /> </picture>

It provides a leveled structured human readable logger with a small API. Unlike standard log, the Charm logger provides customizable colorful human readable logging with batteries included.

Usage

Use go get to download the dependency.

go get github.com/charmbracelet/log@latest

Then, import it in your Go files:

import "github.com/charmbracelet/log"

The Charm logger comes with a global package-wise logger with timestamps turned on, and the logging level set to info.

log.Debug("Cookie 🍪") // won't print anything
log.Info("Hello World!")
<picture> <source media="(prefers-color-scheme: dark)" width="400" srcset="https://vhs.charm.sh/vhs-cKiS8OuRrF1VVVpscM9e3.gif"> <source media="(prefers-color-scheme: light)" width="400" srcset="https://vhs.charm.sh/vhs-cKiS8OuRrF1VVVpscM9e3.gif"> <!-- <source media="(prefers-color-scheme: light)" width="400" srcset="https://vhs.charm.sh/vhs-4AeLaEuO3tDbECR1qe9Jvp.gif"> --> <img width="400" src="https://vhs.charm.sh/vhs-4AeLaEuO3tDbECR1qe9Jvp.gif" alt="Made with VHS" /> </picture>

All logging levels accept optional key/value pairs to be printed along with a message.

err := fmt.Errorf("too much sugar")
log.Error("failed to bake cookies", "err", err)
<picture> <source media="(prefers-color-scheme: dark)" width="600" srcset="https://vhs.charm.sh/vhs-65KIpGw4FTESK0IzkDB9VQ.gif" > <source media="(prefers-color-scheme: light)" width="600" srcset="https://vhs.charm.sh/vhs-65KIpGw4FTESK0IzkDB9VQ.gif" > <!-- <source media="(prefers-color-scheme: light)" width="600" srcset="https://vhs.charm.sh/vhs-7rk8wALXRDoFw8SLFwn9rW.gif"> --> <img width="600" src="https://vhs.charm.sh/vhs-65KIpGw4FTESK0IzkDB9VQ.gif" alt="Made with VHS"> </picture>

You can use log.Print() to print messages without a level prefix.

log.Print("Baking 101")
// 2023/01/04 10:04:06 Baking 101

New loggers

Use New() to create new loggers.

logger := log.New(os.Stderr)
if butter {
    logger.Warn("chewy!", "butter", true)
}
<picture> <source media="(prefers-color-scheme: dark)" width="300" srcset="https://vhs.charm.sh/vhs-3QQdzOW4Zc0bN2tOhAest9.gif"> <source media="(prefers-color-scheme: light)" width="300" srcset="https://vhs.charm.sh/vhs-3QQdzOW4Zc0bN2tOhAest9.gif"> <!-- <source media="(prefers-color-scheme: light)" width="300" srcset="https://vhs.charm.sh/vhs-1nrhNSuFnQkxWD4RoMlE4O.gif"> --> <img width="300" src="https://vhs.charm.sh/vhs-3QQdzOW4Zc0bN2tOhAest9.gif"> </picture>

Levels

Log offers multiple levels to filter your logs on. Available levels are:

log.DebugLevel
log.InfoLevel
log.WarnLevel
log.ErrorLevel
log.FatalLevel

Use log.SetLevel() to set the log level. You can also create a new logger with a specific log level using log.Options{Level: }.

Use the corresponding function to log a message:

err := errors.New("Baking error 101")
log.Debug(err)
log.Info(err)
log.Warn(err)
log.Error(err)
log.Fatal(err) // this calls os.Exit(1)
log.Print(err) // prints regardless of log level

Or use the formatter variant:

format := "%s %d"
log.Debugf(format, "chocolate", 10)
log.Warnf(format, "adding more", 5)
log.Errorf(format, "increasing temp", 420)
log.Fatalf(format, "too hot!", 500) // this calls os.Exit(1)
log.Printf(format, "baking cookies") // prints regardless of log level

// Use these in conjunction with `With(...)` to add more context
log.With("err", err).Errorf("unable to start %s", "oven")

Structured

All the functions above take a message and key-value pairs of anything. The message can also be of type any.

ingredients := []string{"flour", "butter", "sugar", "chocolate"}
log.Debug("Available ingredients", "ingredients", ingredients)
// DEBUG Available ingredients ingredients="[flour butter sugar chocolate]"

Options

You can customize the logger with options. Use log.NewWithOptions() and log.Options{} to customize your new logger.

logger := log.NewWithOptions(os.Stderr, log.Options{
    ReportCaller: true,
    ReportTimestamp: true,
    TimeFormat: time.Kitchen,
    Prefix: "Baking 🍪 ",
})
logger.Info("Starting oven!", "degree", 375)
time.Sleep(10 * time.Minute)
logger.Info("Finished baking")
<picture> <source media="(prefers-color-scheme: dark)" width="700" srcset="https://vhs.charm.sh/vhs-6oSCJcQ5EmFKKELcskJhLo.gif"> <source media="(prefers-color-scheme: light)" width="700" srcset="https://vhs.charm.sh/vhs-6oSCJcQ5EmFKKELcskJhLo.gif"> <!-- <source media="(prefers-color-scheme: light)" width="700" srcset="https://vhs.charm.sh/vhs-2X8Esd8ZsHo4DVPVgR36yx.gif"> --> <img width="700" src="https://vhs.charm.sh/vhs-6oSCJcQ5EmFKKELcskJhLo.gif"> </picture>

You can also use logger setters to customize the logger.

logger := log.New(os.Stderr)
logger.SetReportTimestamp(false)
logger.SetReportCaller(false)
logger.SetLevel(log.DebugLevel)

Use log.SetFormatter() or log.Options{Formatter: } to change the output format. Available options are:

Note styling only affects the TextFormatter. Styling is disabled if the output is not a TTY.

For a list of available options, refer to options.go.

Styles

You can customize the logger styles using Lipgloss. The styles are defined at a global level in styles.go.

// Override the default error level style.
styles := log.DefaultStyles()
styles.Levels[log.ErrorLevel] = lipgloss.NewStyle().
	SetString("ERROR!!").
	Padding(0, 1, 0, 1).
	Background(lipgloss.Color("204")).
	Foreground(lipgloss.Color("0"))
// Add a custom style for key `err`
styles.Keys["err"] = lipgloss.NewStyle().Foreground(lipgloss.Color("204"))
styles.Values["err"] = lipgloss.NewStyle().Bold(true)
logger := log.New(os.Stderr)
logger.SetStyles(styles)
logger.Error("Whoops!", "err", "kitchen on fire")
<picture> <source media="(prefers-color-scheme: dark)" width="400" srcset="https://vhs.charm.sh/vhs-4LXsGvzyH4RdjJaTF4a9MG.gif"> <source media="(prefers-color-scheme: light)" width="400" srcset="https://vhs.charm.sh/vhs-4LXsGvzyH4RdjJaTF4a9MG.gif"> <!-- <source media="(prefers-color-scheme: light)" width="400" srcset="https://vhs.charm.sh/vhs-4f6qLnIfudMMLDD9sxXUrv.gif"> --> <img width="400" src="https://vhs.charm.sh/vhs-4LXsGvzyH4RdjJaTF4a9MG.gif"> </picture>

Sub-logger

Create sub-loggers with their specific fields.

logger := log.NewWithOptions(os.Stderr, log.Options{
    Prefix: "Baking 🍪 "
})
batch2 := logger.With("batch", 2, "chocolateChips", true)
batch2.Debug("Preparing batch 2...")
batch2.Debug("Adding chocolate chips")
<picture> <source media="(prefers-color-scheme: dark)" width="700" srcset="https://vhs.charm.sh/vhs-1JgP5ZRL0oXVspeg50CczR.gif"> <source media="(prefers-color-scheme: light)" width="700" srcset="https://vhs.charm.sh/vhs-1JgP5ZRL0oXVspeg50CczR.gif"> <img width="700" src="https://vhs.charm.sh/vhs-1JgP5ZRL0oXVspeg50CczR.gif"> </picture>

Format Messages

You can use fmt.Sprintf() to format messages.

for item := 1; i <= 100; i++ {
    log.Info(fmt.Sprintf("Baking %d/100...", item))
}
<picture> <source media="(prefers-color-scheme: dark)" width="500" srcset="https://vhs.charm.sh/vhs-4nX5I7qHT09aJ2gU7OaGV5.gif"> <source media="(prefers-color-scheme: light)" width="500" srcset="https://vhs.charm.sh/vhs-4nX5I7qHT09aJ2gU7OaGV5.gif"> <!-- <source media="(prefers-color-scheme: light)" width="500" srcset="https://vhs.charm.sh/vhs-4RHXd4JSucomcPqJGZTpKh.gif"> --> <img width="500" src="https://vhs.charm.sh/vhs-4nX5I7qHT09aJ2gU7OaGV5.gif"> </picture>

Or arguments:

for temp := 375; temp <= 400; temp++ {
    log.Info("Increasing temperature", "degree", fmt.Sprintf("%d°F", temp))
}
<picture> <source media="(prefers-color-scheme: dark)" width="700" srcset="https://vhs.charm.sh/vhs-79YvXcDOsqgHte3bv42UTr.gif"> <source media="(prefers-color-scheme: light)" width="700" srcset="https://vhs.charm.sh/vhs-79YvXcDOsqgHte3bv42UTr.gif"> <!-- <source media="(prefers-color-scheme: light)" width="700" srcset="https://vhs.charm.sh/vhs-4AvAnoA2S53QTOteX8krp4.gif"> --> <img width="700" src="https://vhs.charm.sh/vhs-79YvXcDOsqgHte3bv42UTr.gif"> </picture>

Helper Functions

Skip caller frames in helper functions. Similar to what you can do with testing.TB().Helper().

func startOven(degree int) {
    log.Helper()
    log.Info("Starting oven", "degree", degree)
}

log.SetReportCaller(true)
startOven(400) // INFO <cookies/oven.go:123> Starting oven degree=400
<picture> <source media="(prefers-color-scheme: dark)" width="700" srcset="https://vhs.charm.sh/vhs-6CeQGIV8Ovgr8GD0N6NgTq.gif"> <source media="(prefers-color-scheme: light)" width="700" srcset="https://vhs.charm.sh/vhs-6CeQGIV8Ovgr8GD0N6NgTq.gif"> <!-- <source media="(prefers-color-scheme: light)" width="700" srcset="https://vhs.charm.sh/vhs-6DPg0bVL4K4TkfoHkAn2ap.gif"> --> <img width="700" src="https://vhs.charm.sh/vhs-6CeQGIV8Ovgr8GD0N6NgTq.gif"> </picture>

This will use the caller function (startOven) line number instead of the logging function (log.Info) to report the source location.

Slog Handler

You can use Log as an log/slog handler. Just pass a logger instance to Slog and you're good to go.

handler := log.New(os.Stderr)
logger := slog.New(handler)
logger.Error("meow?")

Standard Log Adapter

Some Go libraries, especially the ones in the standard library, will only accept the standard logger interface. For instance, the HTTP Server from net/http will only take a *log.Logger for its ErrorLog field.

For this, you can use the standard log adapter, which simply wraps the logger in a *log.Logger interface.

logger := log.NewWithOptions(os.Stderr, log.Options{Prefix: "http"})
stdlog := logger.StandardLog(log.StandardLogOptions{
    ForceLevel: log.ErrorLevel,
})
s := &http.Server{
    Addr:     ":8080",
    Handler:  handler,
    ErrorLog: stdlog,
}
stdlog.Printf("Failed to make bake request, %s", fmt.Errorf("temperature is too low"))
// ERROR http: Failed to make bake request, temperature is too low

Gum

<img src="https://vhs.charm.sh/vhs-6jupuFM0s2fXiUrBE0I1vU.gif" width="600" alt="Running gum log with debug and error levels" />

Log integrates with Gum to log messages to output. Use gum log [flags] [message] to handle logging in your shell scripts. See charmbracelet/gum for more information.

License

MIT


Part of Charm.

<a href="https://charm.sh/"><img alt="the Charm logo" src="https://stuff.charm.sh/charm-badge.jpg" width="400"></a>

Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة