zapr

package module
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2023 License: Apache-2.0 Imports: 8 Imported by: 972

README

Zapr ⚡

A logr implementation using Zap. Can also be used as slog handler.

Usage

Via logr:

package main

import (
    "fmt"

    "go.uber.org/zap"
    "github.com/go-logr/logr"
    "github.com/go-logr/zapr"
)

func main() {
    var log logr.Logger

    zapLog, err := zap.NewDevelopment()
    if err != nil {
        panic(fmt.Sprintf("who watches the watchmen (%v)?", err))
    }
    log = zapr.NewLogger(zapLog)

    log.Info("Logr in action!", "the answer", 42)
}

Via slog:

package main

import (
	"fmt"
	"log/slog"

	"github.com/go-logr/logr/slogr"
	"github.com/go-logr/zapr"
	"go.uber.org/zap"
)

func main() {
	var log *slog.Logger

	zapLog, err := zap.NewDevelopment()
	if err != nil {
		panic(fmt.Sprintf("who watches the watchmen (%v)?", err))
	}
	log = slog.New(slogr.NewSlogHandler(zapr.NewLogger(zapLog)))

	log.Info("Logr in action!", "the answer", 42)
}

Increasing Verbosity

Zap uses semantically named levels for logging (DebugLevel, InfoLevel, WarningLevel, ...). Logr uses arbitrary numeric levels. By default logr's V(0) is zap's InfoLevel and V(1) is zap's DebugLevel (which is numerically -1). Zap does not have named levels that are more verbose than DebugLevel, but it's possible to fake it.

As of zap v1.19.0 you can do something like the following in your setup code:

    zc := zap.NewProductionConfig()
    zc.Level = zap.NewAtomicLevelAt(zapcore.Level(-2))
    z, err := zc.Build()
    if err != nil {
        // ...
    }
    log := zapr.NewLogger(z)

Zap's levels get more verbose as the number gets smaller and more important and the number gets larger (DebugLevel is -1, InfoLevel is 0, WarnLevel is 1, and so on).

The -2 in the above snippet means that log.V(2).Info() calls will be active. -3 would enable log.V(3).Info(), etc. Note that zap's levels are int8 which means the most verbose level you can give it is -128. The zapr implementation will cap V() levels greater than 127 to 127, so setting the zap level to -128 really means "activate all logs".

Implementation Details

For the most part, concepts in Zap correspond directly with those in logr.

Unlike Zap, all fields must be in the form of sugared fields -- it's illegal to pass a strongly-typed Zap field in a key position to any of the logging methods (Log, Error).

The zapr logr.LogSink implementation also implements logr.SlogHandler. That enables slogr.NewSlogHandler to provide a slog.Handler which just passes parameters through to zapr. zapr handles special slog values (Group, LogValuer), regardless of which front-end API is used.

Documentation

Overview

Package zapr defines an implementation of the github.com/go-logr/logr interfaces built on top of Zap (go.uber.org/zap).

Usage

A new logr.Logger can be constructed from an existing zap.Logger using the NewLogger function:

log := zapr.NewLogger(someZapLogger)

Implementation Details

For the most part, concepts in Zap correspond directly with those in logr.

Unlike Zap, all fields *must* be in the form of sugared fields -- it's illegal to pass a strongly-typed Zap field in a key position to any of the log methods.

Levels in logr correspond to custom debug levels in Zap. Any given level in logr is represents by its inverse in zap (`zapLevel = -1*logrLevel`). For example V(2) is equivalent to log level -2 in Zap, while V(1) is equivalent to Zap's DebugLevel.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewLogger

func NewLogger(l *zap.Logger) logr.Logger

NewLogger creates a new logr.Logger using the given Zap Logger to log.

Example
package main

import (
	"errors"
	"time"

	"github.com/go-logr/zapr"
	"github.com/go-logr/zapr/internal/types"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var errSome = errors.New("some error")

func encodeTime(_ time.Time, enc zapcore.PrimitiveArrayEncoder) {

	enc.AppendString("TIMESTAMP")
}

func buildZapLogger() *zap.Logger {

	zc := zap.NewProductionConfig()
	zc.OutputPaths = []string{"stdout"}
	zc.ErrorOutputPaths = zc.OutputPaths
	zc.DisableStacktrace = true
	zc.DisableCaller = true
	zc.EncoderConfig.EncodeTime = encodeTime
	z, _ := zc.Build()
	return z
}

func main() {
	log := zapr.NewLogger(buildZapLogger())
	log.Info("info message with default options")
	log.Error(errSome, "error message with default options")
	log.Info("support for zap fields as key/value replacement is disabled", zap.Int("answer", 42))
	log.Info("invalid key", 42, "answer")
	log.Info("missing value", "answer")
	obj := types.ObjectRef{Name: "john", Namespace: "doe"}
	log.Info("marshaler", "stringer", obj.String(), "raw", obj)
}
Output:

{"level":"info","ts":"TIMESTAMP","msg":"info message with default options"}
{"level":"error","ts":"TIMESTAMP","msg":"error message with default options","error":"some error"}
{"level":"dpanic","ts":"TIMESTAMP","msg":"strongly-typed Zap Field passed to logr","zap field":{"Key":"answer","Type":11,"Integer":42,"String":"","Interface":null}}
{"level":"info","ts":"TIMESTAMP","msg":"support for zap fields as key/value replacement is disabled"}
{"level":"dpanic","ts":"TIMESTAMP","msg":"non-string key argument passed to logging, ignoring all later arguments","invalid key":42}
{"level":"info","ts":"TIMESTAMP","msg":"invalid key"}
{"level":"dpanic","ts":"TIMESTAMP","msg":"odd number of arguments passed as key-value pairs for logging","ignored key":"answer"}
{"level":"info","ts":"TIMESTAMP","msg":"missing value"}
{"level":"info","ts":"TIMESTAMP","msg":"marshaler","stringer":"doe/john","raw":{"name":"john","namespace":"doe"}}

func NewLoggerWithOptions added in v1.1.0

func NewLoggerWithOptions(l *zap.Logger, opts ...Option) logr.Logger

NewLoggerWithOptions creates a new logr.Logger using the given Zap Logger to log and applies additional options.

Types

type Option added in v1.1.0

type Option func(*zapLogger)

Option is one additional parameter for NewLoggerWithOptions.

func AllowZapFields added in v1.1.0

func AllowZapFields(allowed bool) Option

AllowZapFields controls whether strongly-typed Zap fields may be passed instead of a key/value pair. This is disabled by default because it breaks implementation agnosticism.

Example
package main

import (
	"time"

	"github.com/go-logr/zapr"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

func encodeTime(_ time.Time, enc zapcore.PrimitiveArrayEncoder) {

	enc.AppendString("TIMESTAMP")
}

func buildZapLogger() *zap.Logger {

	zc := zap.NewProductionConfig()
	zc.OutputPaths = []string{"stdout"}
	zc.ErrorOutputPaths = zc.OutputPaths
	zc.DisableStacktrace = true
	zc.DisableCaller = true
	zc.EncoderConfig.EncodeTime = encodeTime
	z, _ := zc.Build()
	return z
}

func main() {
	log := zapr.NewLoggerWithOptions(buildZapLogger(), zapr.AllowZapFields(true))
	log.Info("log zap field", zap.Int("answer", 42))
}
Output:

{"level":"info","ts":"TIMESTAMP","msg":"log zap field","answer":42}

func DPanicOnBugs added in v1.1.0

func DPanicOnBugs(enabled bool) Option

DPanicOnBugs controls whether extra log messages are emitted for invalid log calls with zap's DPanic method. Depending on the configuration of the zap logger, the program then panics after emitting the log message which is useful in development because such invalid log calls are bugs in the program. The log messages explain why a call was invalid (for example, non-string key). Emitting them is enabled by default.

Example
package main

import (
	"time"

	"github.com/go-logr/zapr"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

func encodeTime(_ time.Time, enc zapcore.PrimitiveArrayEncoder) {

	enc.AppendString("TIMESTAMP")
}

func buildZapLogger() *zap.Logger {

	zc := zap.NewProductionConfig()
	zc.OutputPaths = []string{"stdout"}
	zc.ErrorOutputPaths = zc.OutputPaths
	zc.DisableStacktrace = true
	zc.DisableCaller = true
	zc.EncoderConfig.EncodeTime = encodeTime
	z, _ := zc.Build()
	return z
}

func main() {
	log := zapr.NewLoggerWithOptions(buildZapLogger(), zapr.DPanicOnBugs(false))
	log.Info("warnings suppressed", zap.Int("answer", 42))
}
Output:

{"level":"info","ts":"TIMESTAMP","msg":"warnings suppressed"}

func ErrorKey added in v1.1.0

func ErrorKey(key string) Option

ErrorKey replaces the default "error" field name used for the error in Logger.Error calls.

Example
package main

import (
	"errors"
	"time"

	"github.com/go-logr/zapr"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var errSome = errors.New("some error")

func encodeTime(_ time.Time, enc zapcore.PrimitiveArrayEncoder) {

	enc.AppendString("TIMESTAMP")
}

func buildZapLogger() *zap.Logger {

	zc := zap.NewProductionConfig()
	zc.OutputPaths = []string{"stdout"}
	zc.ErrorOutputPaths = zc.OutputPaths
	zc.DisableStacktrace = true
	zc.DisableCaller = true
	zc.EncoderConfig.EncodeTime = encodeTime
	z, _ := zc.Build()
	return z
}

func main() {
	log := zapr.NewLoggerWithOptions(buildZapLogger(), zapr.ErrorKey("err"))
	log.Error(errSome, "error message with non-default error key")
}
Output:

{"level":"error","ts":"TIMESTAMP","msg":"error message with non-default error key","err":"some error"}

func LogInfoLevel added in v1.1.0

func LogInfoLevel(key string) Option

LogInfoLevel controls whether a numeric log level is added to Info log message. The empty string disables this, a non-empty string is the key for the additional field. Errors and internal panic messages do not have a log level and thus are always logged without this extra field.

Example
package main

import (
	"errors"
	"time"

	"github.com/go-logr/zapr"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var errSome = errors.New("some error")

func encodeTime(_ time.Time, enc zapcore.PrimitiveArrayEncoder) {

	enc.AppendString("TIMESTAMP")
}

func buildZapLogger() *zap.Logger {

	zc := zap.NewProductionConfig()
	zc.OutputPaths = []string{"stdout"}
	zc.ErrorOutputPaths = zc.OutputPaths
	zc.DisableStacktrace = true
	zc.DisableCaller = true
	zc.EncoderConfig.EncodeTime = encodeTime
	z, _ := zc.Build()
	return z
}

func main() {
	log := zapr.NewLoggerWithOptions(buildZapLogger(), zapr.LogInfoLevel("v"))
	log.Info("info message with numeric verbosity level")
	log.Error(errSome, "error messages have no numeric verbosity level")
}
Output:

{"level":"info","ts":"TIMESTAMP","msg":"info message with numeric verbosity level","v":0}
{"level":"error","ts":"TIMESTAMP","msg":"error messages have no numeric verbosity level","error":"some error"}

type Underlier added in v0.3.0

type Underlier interface {
	GetUnderlying() *zap.Logger
}

Underlier exposes access to the underlying logging implementation. Since callers only have a logr.Logger, they have to know which implementation is in use, so this interface is less of an abstraction and more of way to test type conversion.

Directories

Path Synopsis
This example shows how to instantiate a zap logger and what output looks like.
This example shows how to instantiate a zap logger and what output looks like.
internal
types
Package types holds a copy of the ObjectRef type from klog for use in the example.
Package types holds a copy of the ObjectRef type from klog for use in the example.

Jump to

Keyboard shortcuts

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