log

package module
v2.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 5, 2025 License: MIT Imports: 18 Imported by: 3,831

README

go-log

GoDoc

The logging library used by IPFS Kubo

go-log wraps zap to provide per-subsystem level control and optional log/slog integration for unified logging across IPFS/libp2p components.

Table of Contents

Install

go get github.com/ipfs/go-log

Usage

Once the package is imported under the name logging, an instance of EventLogger can be created like so:

var log = logging.Logger("subsystem name")

It can then be used to emit log messages in plain printf-style messages at seven standard levels:

Levels may be set for all loggers:

lvl, err := logging.LevelFromString("error")
if err != nil {
	panic(err)
}
logging.SetAllLoggers(lvl)

or individually:

err := logging.SetLogLevel("net:pubsub", "info")
if err != nil {
	panic(err)
}

or by regular expression:

err := logging.SetLogLevelRegex("net:.*", "info")
if err != nil {
	panic(err)
}

Environment Variables

This package can be configured through various environment variables.

GOLOG_LOG_LEVEL

Specifies the log-level, both globally and on a per-subsystem basis.

For example, the following will set the global minimum log level to error, but reduce the minimum log level for subsystem1 to info and reduce the minimum log level for subsystem2 to debug.

export GOLOG_LOG_LEVEL="error,subsystem1=info,subsystem2=debug"

IPFS_LOGGING is a deprecated alias for this environment variable.

GOLOG_FILE

Specifies that logs should be written to the specified file. If this option is not specified, logs are written to standard error.

export GOLOG_FILE="/path/to/my/file.log"
GOLOG_OUTPUT

Specifies where logging output should be written. Can take one or more of the following values, combined with +:

  • stdout -- write logs to standard out.
  • stderr -- write logs to standard error.
  • file -- write logs to the file specified by GOLOG_FILE

For example, if you want to log to both a file and standard error:

export GOLOG_FILE="/path/to/my/file.log"
export GOLOG_OUTPUT="stderr+file"

Setting only GOLOG_FILE will prevent logs from being written to standard error.

GOLOG_LOG_FMT

Specifies the log message format. It supports the following values:

  • color -- human readable, colorized (ANSI) output
  • nocolor -- human readable, plain-text output.
  • json -- structured JSON.

For example, to log structured JSON (for easier parsing):

export GOLOG_LOG_FMT="json"

The logging format defaults to color when the output is a terminal, and nocolor otherwise.

IPFS_LOGGING_FMT is a deprecated alias for this environment variable.

GOLOG_LOG_LABELS

Specifies a set of labels that should be added to all log messages as comma-separated key-value pairs. For example, the following add {"app": "example_app", "dc": "sjc-1"} to every log entry.

export GOLOG_LOG_LABELS="app=example_app,dc=sjc-1"
GOLOG_CAPTURE_DEFAULT_SLOG

By default, go-log does NOT automatically install its slog handler as slog.Default(). Applications should explicitly call slog.SetDefault(slog.New(golog.SlogHandler())) for slog integration (see Slog Integration section below).

Alternatively, you can enable automatic installation by setting:

export GOLOG_CAPTURE_DEFAULT_SLOG="true"

When enabled, go-log automatically installs its handler as slog.Default() during SetupLogging(), which allows libraries using slog to automatically use go-log's formatting and dynamic level control.

Slog Integration

go-log provides integration with Go's log/slog package for unified log management. This provides:

  1. Unified formatting: slog logs use the same format as go-log (color/nocolor/json)
  2. Dynamic level control: slog loggers respect SetLogLevel() and environment variables
  3. Subsystem-aware filtering: slog loggers with subsystem attributes get per-subsystem level control

Note: This slog bridge exists as an intermediate solution while go-log uses zap internally. In the future, go-log may migrate from zap to native slog, which would simplify this integration.

Application Setup (Required)

For slog-based logging to use go-log's formatting and level control, applications must explicitly set go-log's handler as the slog default:

import (
	"log/slog"
	golog "github.com/ipfs/go-log/v2"
	"github.com/libp2p/go-libp2p/gologshim"
)

func init() {
	// Set go-log's handler as the application-wide slog default.
	// This ensures all slog-based logging uses go-log's formatting.
	slog.SetDefault(slog.New(golog.SlogHandler()))

	// Wire libraries that use explicit handler passing (like go-libp2p).
	// This ensures proper subsystem attribution for per-logger level control.
	gologshim.SetDefaultHandler(golog.SlogHandler())
}

This two-layer approach ensures:

  • Application-level: All slog usage (application code + libraries) flows through go-log
  • Library-level: Libraries with explicit wiring (like go-libp2p) include proper subsystem attributes
How it works

When configured as shown above, slog-based libraries gain unified formatting and dynamic level control.

Attributes added by libraries:

  • logger: Subsystem name (e.g., "foo", "bar", "baz")
  • Any additional labels from GOLOG_LOG_LABELS

Example:

var log = logging.Logger("foo")  // gologshim
log.Debug("operation failed", "err", err)

When integrated with go-log, output is formatted by go-log (JSON format shown here, also supports color/nocolor):

{
  "level": "debug",
  "ts": "2025-10-27T12:34:56.789+0100",
  "logger": "foo",
  "caller": "foo/foo.go:72",
  "msg": "operation failed",
  "err": "connection refused"
}
Controlling slog logger levels

These loggers respect go-log's level configuration:

# Via environment variable (before daemon starts)
export GOLOG_LOG_LEVEL="error,foo=debug"

# Via API (while daemon is running)
logging.SetLogLevel("foo", "debug")

This works even if the logger is created lazily or hasn't been created yet. Level settings are preserved and applied when the logger is first used.

Direct slog usage without subsystem

When using slog.Default() directly without adding a "logger" attribute, logs still work but have limitations:

What works:

  • Logs appear in output with go-log's formatting (JSON/color/nocolor)
  • Uses global log level from GOLOG_LOG_LEVEL fallback or SetAllLoggers()

Limitations:

  • No subsystem-specific level control via SetLogLevel("subsystem", "level")
  • Empty logger name in output
  • Less efficient (no early atomic level filtering)

Example:

// Direct slog usage - uses global level only
slog.Info("message")  // LoggerName = "", uses global level

// Library with subsystem - subsystem-aware
log := mylib.Logger("foo")
log.Info("message")  // LoggerName = "foo", uses subsystem level

For libraries, use the "logger" attribute pattern to enable per-subsystem control.

Why "logger" attribute?

go-log uses "logger" as the attribute key for subsystem names to maintain backward compatibility with its existing Zap-based output format:

  • Maintains compatibility with existing go-log output format
  • Existing tooling, dashboards, and log processors already parse the "logger" field
  • Simplifies migration path from Zap to slog bridge

Libraries integrating with go-log should use this same attribute key to ensure proper subsystem-aware level control.

For library authors

Libraries using slog can integrate with go-log without adding go-log as a dependency. There are two approaches:

Approach 1: Duck-typing detection (automatic)

Detect go-log's slog bridge via an interface marker to avoid requiring go-log in library's go.mod:

// In your library's logging package
func Logger(subsystem string) *slog.Logger {
    // Check if slog.Default() is go-log's bridge.
    // This works when applications call slog.SetDefault(slog.New(golog.SlogHandler())).
    handler := slog.Default().Handler()

    type goLogBridge interface {
        GoLogBridge()
    }
    if _, ok := handler.(goLogBridge); ok {
        // go-log's bridge is active - use it with subsystem attribute
        h := handler.WithAttrs([]slog.Attr{
            slog.String("logger", subsystem),
        })
        return slog.New(h)
    }

    // Standalone handler when go-log is not present
    return slog.New(createStandaloneHandler(subsystem))
}

Usage in your library:

var log = mylib.Logger("foo")
log.Debug("operation completed", "key", value)

This pattern allows libraries to automatically integrate when the application has set up go-log's handler, without requiring go-log as a dependency.

Approach 2: Explicit handler passing (manual)

Alternatively, expose a way for applications to provide a handler explicitly:

// In your library's logging package
var defaultHandler atomic.Pointer[slog.Handler]

func SetDefaultHandler(handler slog.Handler) {
    defaultHandler.Store(&handler)
}

func Logger(subsystem string) *slog.Logger {
    if h := defaultHandler.Load(); h != nil {
        // Use provided handler with subsystem attribute
        return slog.New((*h).WithAttrs([]slog.Attr{
            slog.String("logger", subsystem),
        }))
    }
    // Standalone handler when go-log is not present
    return slog.New(createStandaloneHandler(subsystem))
}

Usage in your library:

var log = mylib.Logger("bar")
log.Info("started service", "addr", addr)

Application side must explicitly wire it, for example, go-libp2p requires:

import (
    golog "github.com/ipfs/go-log/v2"
    "github.com/libp2p/go-libp2p/gologshim"
)

func init() {
    // Use go-log's SlogHandler() to get the bridge directly.
    // This works regardless of GOLOG_CAPTURE_DEFAULT_SLOG setting.
    gologshim.SetDefaultHandler(golog.SlogHandler())
}

Tradeoff: Approach 2 requires manual coordination in every application, while Approach 1 works automatically when applications set up slog.Default().

For a complete example, see go-libp2p's gologshim.

Enabling automatic slog capture (opt-in)

Note: This is mostly used during development, when a library author decides between Approach 1 or 2 for proper (subsystem-aware) integration with go-log.

You can enable automatic installation of go-log's handler during SetupLogging():

export GOLOG_CAPTURE_DEFAULT_SLOG="true"

When enabled, go-log automatically installs its handler as slog.Default(), which allows slog-based libraries to automatically use go-log's formatting without explicit application setup.

Contribute

Feel free to join in. All welcome. Open an issue!

This repository falls under the IPFS Code of Conduct.

Want to hack on IPFS?

License

MIT

Documentation

Overview

Package log is the logging library used by IPFS & libp2p (https://github.com/ipfs/go-ipfs).

Index

Constants

View Source
const DefaultName = ""

DefaultName is the subsystem name that identifies the default log level.

Variables

View Source
var (
	LevelDebug  = LogLevel(zapcore.DebugLevel)
	LevelInfo   = LogLevel(zapcore.InfoLevel)
	LevelWarn   = LogLevel(zapcore.WarnLevel)
	LevelError  = LogLevel(zapcore.ErrorLevel)
	LevelDPanic = LogLevel(zapcore.DPanicLevel)
	LevelPanic  = LogLevel(zapcore.PanicLevel)
	LevelFatal  = LogLevel(zapcore.FatalLevel)
)
View Source
var ErrNoSuchLogger = errors.New("error: No such logger")

ErrNoSuchLogger is returned when the util pkg is asked for a non existant logger

Functions

func FormatRFC3339

func FormatRFC3339(t time.Time) string

FormatRFC3339 returns the given time in UTC with RFC3999Nano format.

func GetSubsystems

func GetSubsystems() []string

GetSubsystems returns a slice containing the names of the current loggers

func SetAllLoggers

func SetAllLoggers(lvl LogLevel)

SetAllLoggers changes the logging level of all loggers to lvl

func SetDebugLogging

func SetDebugLogging()

SetDebugLogging calls SetAllLoggers with logging.DEBUG

func SetLogLevel

func SetLogLevel(name, level string) error

SetLogLevel changes the log level of a specific subsystem. name=="*" changes all subsystems.

This function works for both native go-log loggers and slog-based loggers (e.g., from go-libp2p via gologshim). If the subsystem doesn't exist yet, a level entry is created and will be applied when the logger is created.

func SetLogLevelRegex

func SetLogLevelRegex(e, l string) error

SetLogLevelRegex sets all loggers to level `l` that match expression `e`. An error is returned if `e` fails to compile.

func SetPrimaryCore added in v2.2.0

func SetPrimaryCore(core zapcore.Core)

SetPrimaryCore changes the primary logging core. If the SetupLogging was called then the previously configured core will be replaced.

func SetupLogging

func SetupLogging(cfg Config)

SetupLogging will initialize the logger backend and set the flags. TODO calling this in `init` pushes all configuration to env variables - move it out of `init`? then we need to change all the code (js-ipfs, go-ipfs) to call this explicitly - have it look for a config file? need to define what that is

func SlogHandler added in v2.9.0

func SlogHandler() slog.Handler

SlogHandler returns go-log's slog.Handler for explicit wiring. This allows applications to integrate slog-based logging with go-log's formatting and level control.

Example usage in an application's init():

import (
    "log/slog"
    golog "github.com/ipfs/go-log/v2"
    "github.com/libp2p/go-libp2p/gologshim"
)

func init() {
    // Set go-log's slog handler as the application-wide default.
    // This ensures all slog-based logging uses go-log's formatting.
    slog.SetDefault(slog.New(golog.SlogHandler()))

    // Wire go-log's slog bridge to go-libp2p's gologshim.
    // This provides go-libp2p loggers with the "logger" attribute
    // for per-subsystem level control.
    gologshim.SetDefaultHandler(golog.SlogHandler())
}

func SubsystemLevelName added in v2.8.0

func SubsystemLevelName(subsys string) (string, error)

SubsystemLevelName returns the current log level name for a given subsystem. An empty name, "", returns the default LogLevel name.

func SubsystemLevelNames added in v2.8.0

func SubsystemLevelNames() map[string]string

SubsystemLevelNames returns a map of all facility names to their current log levels as strings. The map includes the default log level identified by the defaultName string as the map key.

Types

type Config added in v2.1.0

type Config struct {
	// Format overrides the format of the log output. Defaults to ColorizedOutput
	Format LogFormat

	// Level is the default minimum enabled logging level.
	Level LogLevel

	// SubsystemLevels are the default levels per-subsystem. When unspecified, defaults to Level.
	SubsystemLevels map[string]LogLevel

	// Stderr indicates whether logs should be written to stderr.
	Stderr bool

	// Stdout indicates whether logs should be written to stdout.
	Stdout bool

	// File is a path to a file that logs will be written to.
	File string

	// URL with schema supported by zap. Use zap.RegisterSink
	URL string

	// Labels is a set of key-values to apply to all loggers
	Labels map[string]string
}

func GetConfig added in v2.5.0

func GetConfig() Config

GetConfig returns a copy of the saved config. It can be inspected, modified, and re-applied using a subsequent call to SetupLogging().

type EventLogger

type EventLogger interface {
	StandardLogger
}

EventLogger extends the StandardLogger interface to allow for log items containing structured metadata

type LogFormat added in v2.1.0

type LogFormat int
const (
	ColorizedOutput LogFormat = iota
	PlaintextOutput
	JSONOutput
)

type LogLevel

type LogLevel zapcore.Level

LogLevel represents a log severity level. Use the package variables as an enum.

func DefaultLevel added in v2.8.0

func DefaultLevel() LogLevel

DefaultLevel returns the current default LogLevel.

func LevelFromString

func LevelFromString(level string) (LogLevel, error)

LevelFromString parses a string-based level and returns the corresponding LogLevel.

This function is maintained for v1 compatibility only and will be removed in a future version. New code should use Parse instead.

func Parse added in v2.8.0

func Parse(name string) (LogLevel, error)

Parse parses a string-based level and returns the corresponding LogLevel. An error is returned of the string is not the name of a supported LogLevel.

func (LogLevel) String added in v2.8.0

func (lvl LogLevel) String() string

String returns the name of a LogLevel.

type PipeReader added in v2.1.0

type PipeReader struct {
	// contains filtered or unexported fields
}

A PipeReader is a reader that reads from the logger. It is synchronous so blocking on read will affect logging performance.

func NewPipeReader added in v2.1.0

func NewPipeReader(opts ...PipeReaderOption) *PipeReader

NewPipeReader creates a new in-memory reader that reads from all loggers The caller must call Close on the returned reader when done.

By default, it:

  1. Logs JSON. This can be changed by passing the PipeFormat option.
  2. Logs everything that would otherwise be logged to the "primary" log output. That is, everything enabled by SetLogLevel. The minimum log level can be increased by passing the PipeLevel option.

func (*PipeReader) Close added in v2.1.0

func (p *PipeReader) Close() error

Close unregisters the reader from the logger.

func (*PipeReader) Read added in v2.1.0

func (p *PipeReader) Read(data []byte) (int, error)

Read implements the standard Read interface

type PipeReaderOption added in v2.1.0

type PipeReaderOption interface {
	// contains filtered or unexported methods
}

func PipeFormat added in v2.1.0

func PipeFormat(format LogFormat) PipeReaderOption

PipeFormat sets the output format of the pipe reader

func PipeLevel added in v2.1.0

func PipeLevel(level LogLevel) PipeReaderOption

PipeLevel sets the log level of logs sent to the pipe reader.

type StandardLogger

type StandardLogger interface {
	Debug(args ...interface{})
	Debugf(format string, args ...interface{})
	Error(args ...interface{})
	Errorf(format string, args ...interface{})
	Fatal(args ...interface{})
	Fatalf(format string, args ...interface{})
	Info(args ...interface{})
	Infof(format string, args ...interface{})
	Panic(args ...interface{})
	Panicf(format string, args ...interface{})
	Warn(args ...interface{})
	Warnf(format string, args ...interface{})
}

StandardLogger provides API compatibility with standard printf loggers eg. go-logging

type ZapEventLogger

type ZapEventLogger struct {
	zap.SugaredLogger
	// contains filtered or unexported fields
}

ZapEventLogger implements the EventLogger and wraps a go-logging Logger

func Logger

func Logger(system string) *ZapEventLogger

Logger retrieves an event logger by name

func WithSkip added in v2.5.1

func WithSkip(l *ZapEventLogger, skip int) *ZapEventLogger

WithSkip returns a new logger that skips the specified number of stack frames when reporting the line/file.

func WithStacktrace added in v2.4.0

func WithStacktrace(l *ZapEventLogger, level LogLevel) *ZapEventLogger

func (*ZapEventLogger) LevelEnabled added in v2.8.1

func (logger *ZapEventLogger) LevelEnabled(level LogLevel) bool

LevelEnabled returns true if the LogLevel is enabled for the logger.

func (*ZapEventLogger) Warning added in v2.0.6

func (logger *ZapEventLogger) Warning(args ...interface{})

Warning is for compatibility Deprecated: use Warn(args ...interface{}) instead

func (*ZapEventLogger) Warningf added in v2.0.6

func (logger *ZapEventLogger) Warningf(format string, args ...interface{})

Warningf is for compatibility Deprecated: use Warnf(format string, args ...interface{}) instead

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL