Zap

Kesa...大约 5 分钟golangzap

Blazing fast, structured, leveled logging in Go.

示例代码参见zap

1. Quick Start

1.1 Installation

go get -u go.uber.org/zap

1.2 Sugared Logger

In contexts where performance is nice, but not critical, use the SugaredLogger.

It’s 4-10x faster than other structured logging packages and includes both structured and printf-style APIs.

func main() {
	logger, err := zap.NewDevelopment()
	if err != nil {
		log.Fatalf("can't initialize zap logger: %v", err)
	}
	defer logger.Sync()
	sugar := logger.Sugar()
	url := "Example URL"
	sugar.Infow("failed to fetch URL",
		"url", url,
		"attempt", 3,
		"backoff", time.Second,
	)
	sugar.Infof("Failed to fetch URL %s", "url")
}
  • logger.Sync(): flushes buffer, if any
  • sugar.Infow(): Structured context as loosely typed key-value pairs

1.3 Logger

When performance and type safety are critical, use the Logger . It’s even faster than the SugaredLogger and allocates far less, but it only supported structured logging

func main() {
	logger, err := zap.NewDevelopment()
	if err != nil {
		log.Fatalf("can't initialize zap logger: %v", err)
	}
	defer logger.Sync()
	url := "example url"
	logger.Info("failed to fetch URL",
		zap.String("url", url),
		zap.Int("attempt", 3),
		zap.Duration("backoff", time.Second))
}

2. Overview

Package zap provides fast, structured, leveled logging.

For application that log in the hot path, reflection-based serialization and string formatting are prohibitively expensive - they are CPU-intensive and make many small allocations. Put differently, using json.Marhshal and fmt.Fprintf to log tons of interface{} makes your application slow.

Zap takes a different approach. It includes a reflection-free, zero-allocation JSON encoder, and the base Logger strives to avoid serialization overhead and allocations wherever possible. By building the high-level SugaredLogger on that foundation, zap let users choose when they need to count every allocation and when they’d prefer a more familiar, loosely typed API.

2.1 Choosing a Logger

In contexts where performance is nice, but not critical, use the SugaredLogger. It’s 4-10x faster than other structured logging packages and supports both structured and printf-style loggin. Like log15 and go-kit, the SugaredLogger’s structured logging APIs are loosely typed and accept a variadic number of key-value paires.

sugar := zap.NewExample().Sugar()
defer sugar.Sync()
sugar.Infow("failed to fetch URL",
  "url", "http://example.com",
  "attempt", 3,
  "backoff", time.Second,
)
sugar.Infof("failed to fetch URL: %s", "http://example.com")

By default, loggers are unbuffered. However, since zap’s low-level APIs allow buffering, calling Sync before letting your process exit is a good habit.

In the rare contexts where every microsecond and every allocation matter, use the Logger. It’s even faster than the SugaredLogger and allocates far less, but it only supports strongly-typed, structured logging.

logger := zap.NewExample()
defer logger.Sync()
logger.Info("failed to fetch URL",
  zap.String("url", "http://example.com"),
  zap.Int("attempt", 3),
  zap.Duration("backoff", time.Second),
)

Choosing between the Logger and SugaredLogger doesn’t need to an application-wide decision: converting between the two is simple and inexpensive.

logger := zap.NewExample()
defer logger.Sync()
suger := logger.Sugar()
plain := sugar.Desugar()

2.2 Configuring Zap

The simplest way to build a Logger is to use zap’s opinionated presets: NewExample, NewProduction, and NewDevelopment. These presets build a logger with a single function call:

logger, err := zap.NewProduction()
if err != nil {
  log.Fatalf("can't initialize zap logger: %v", err)
}
defer logger.Sync()

Presets are fine for small projects, but larger projects and organizations naturally require a bit more customization. For most users, zap’s Config struct strikes the right balance between flexibility and convenience.

More unusual configurations ( splitting output between files, sending logs to a message queue, etc.) are possible, but require direct use of go.uber.org/zap/zapcoreopen in new window.

2.3 Extending Zap

The zap package itself is a relatively thin wrapper around the interface in go.uber.org/zap/zapcoreopen in new window. Extending zap to support a new encoding ( e.g. BSON), a new log sink ( e.g. Kafka), or something more exotic (perhaps an exception aggregation service, like Sentry or Rollbar ) typically requires implementing the zapcore.Encoder, zapcore.WriteSyncer, or zapcore.Core interfaces.

Similarly, package authors can use the high-performance Encoder and Core implementations in the zapcore package to build their own loggers.

3. Examples

3.1 Presets

package main

import (
	"time"

	"go.uber.org/zap"
)

func main() {
	// Using zap's preset constructors is the simplest way to get a feel for the
	// package, but they don't allow much customization.
	logger := zap.NewExample() // or NewProduction, or NewDevelopment
	defer logger.Sync()

	const url = "http://example.com"

	// In most circumstances, use the SugaredLogger. It's 4-10x faster than most
	// other structured logging packages and has a familiar, loosely-typed API.
	sugar := logger.Sugar()
	sugar.Infow("Failed to fetch URL.",
		// Structured context as loosely typed key-value pairs.
		"url", url,
		"attempt", 3,
		"backoff", time.Second,
	)
	sugar.Infof("Failed to fetch URL: %s", url)

	// In the unusual situations where every microsecond matters, use the
	// Logger. It's even faster than the SugaredLogger, but only supports
	// structured logging.
	logger.Info("Failed to fetch URL.",
		// Structured context as strongly typed fields.
		zap.String("url", url),
		zap.Int("attempt", 3),
		zap.Duration("backoff", time.Second),
	)
}
{"level":"info","msg":"Failed to fetch URL.","url":"http://example.com","attempt":3,"backoff":"1s"}
{"level":"info","msg":"Failed to fetch URL: http://example.com"}
{"level":"info","msg":"Failed to fetch URL.","url":"http://example.com","attempt":3,"backoff":"1s"}

三种预设的区别:

  • NewDevelopment: builds a development Logger that writes DebugLevel and above logs to standard error in a human-friendly format.

    It's a shortcut for NewDevelopmentConfig().Build(...Option).

  • NewExample: builds a Logger that's designed for use in zap's testable examples. It writes DebugLevel and above logs to standard out as JSON, but omits the timestamp and calling function to keep example output short and deterministic

  • NewProduction: builds a sensible production Logger that writes InfoLevel and above logs to standard error as JSON.

    It's a shortcut for NewProductionConfig().Build(...Option).

func main() {
	url := "example"
	devLogger, _ := zap.NewDevelopment()
	defer devLogger.Sync()
	devLogger.Info("failed to fetch url",
		zap.String("url", url),
		zap.Int("attempt", 3),
		zap.Duration("backoff", time.Second))

	exampleLogger := zap.NewExample()
	defer exampleLogger.Sync()
	exampleLogger.Info("failed to fetch url",
		zap.String("url", url),
		zap.Int("attempt", 3),
		zap.Duration("backoff", time.Second))

	prodLogger, _ := zap.NewProduction()
	defer prodLogger.Sync()
	prodLogger.Info("failed to fetch url",
		zap.String("url", url),
		zap.Int("attempt", 3),
		zap.Duration("backoff", time.Second))

}
2021-12-20T11:15:52.398+0800    INFO    presets/main.go:13      failed to fetch url     {"url": "example", "attempt": 3, "backoff": "1s"}
{"level":"info","msg":"failed to fetch url","url":"example","attempt":3,"backoff":"1s"}
{"level":"info","ts":1639970152.3984973,"caller":"presets/main.go:27","msg":"failed to fetch url","url":"example","attempt":3,"backoff":1}

3.2 Basic Configuration

package main

import (
	"encoding/json"

	"go.uber.org/zap"
)

func main() {
	// For some users, the presets offered by the NewProduction, NewDevelopment,
	// and NewExample constructors won't be appropriate. For most of those
	// users, the bundled Config struct offers the right balance of flexibility
	// and convenience. (For more complex needs, see the AdvancedConfiguration
	// example.)
	//
	// See the documentation for Config and zapcore.EncoderConfig for all the
	// available options.
	rawJSON := []byte(`{
	  "level": "debug",
	  "encoding": "json",
	  "outputPaths": ["stdout", "/tmp/logs"],
	  "errorOutputPaths": ["stderr"],
	  "initialFields": {"foo": "bar"},
	  "encoderConfig": {
	    "messageKey": "message",
	    "levelKey": "level",
	    "levelEncoder": "lowercase"
	  }
	}`)

	var cfg zap.Config
	if err := json.Unmarshal(rawJSON, &cfg); err != nil {
		panic(err)
	}
	logger, err := cfg.Build()
	if err != nil {
		panic(err)
	}
	defer logger.Sync()

	logger.Info("logger construction succeeded")
}

3.3 Advanced Configuration

package main

import (
	"io/ioutil"
	"os"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

func main() {
	// The bundled Config struct only supports the most common configuration
	// options. More complex needs, like splitting logs between multiple files
	// or writing to non-file outputs, require use of the zapcore package.
	//
	// In this example, imagine we're both sending our logs to Kafka and writing
	// them to the console. We'd like to encode the console output and the Kafka
	// topics differently, and we'd also like special treatment for
	// high-priority logs.

	// First, define our level-handling logic.
	highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl >= zapcore.ErrorLevel
	})
	lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl < zapcore.ErrorLevel
	})

	// Assume that we have clients for two Kafka topics. The clients implement
	// zapcore.WriteSyncer and are safe for concurrent use. (If they only
	// implement io.Writer, we can use zapcore.AddSync to add a no-op Sync
	// method. If they're not safe for concurrent use, we can add a protecting
	// mutex with zapcore.Lock.)
	topicDebugging := zapcore.AddSync(ioutil.Discard)
	topicErrors := zapcore.AddSync(ioutil.Discard)

	// High-priority output should also go to standard error, and low-priority
	// output should also go to standard out.
	consoleDebugging := zapcore.Lock(os.Stdout)
	consoleErrors := zapcore.Lock(os.Stderr)

	// Optimize the Kafka output for machine consumption and the console output
	// for human operators.
	kafkaEncoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
	consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())

	// Join the outputs, encoders, and level-handling functions into
	// zapcore.Cores, then tee the four cores together.
	core := zapcore.NewTee(
		zapcore.NewCore(kafkaEncoder, topicErrors, highPriority),
		zapcore.NewCore(consoleEncoder, consoleErrors, highPriority),
		zapcore.NewCore(kafkaEncoder, topicDebugging, lowPriority),
		zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority),
	)

	// From a zapcore.Core, it's easy to construct a Logger.
	logger := zap.New(core)
	defer logger.Sync()
	logger.Info("constructed a logger")
}

4. Log rotation

Zap doesn’t natively support rotating log files. It’s easy to integrate a log rotation package like gopkg.in/natefinch/lumberjack.v2open in new window as a zapcore.WriteSyncer

go get -u gopkg.in/natefinch/lumberjack.v2
const num = 10000

func main() {
	// lumberjack.Logger is already safe for concurrent use, so we don't need to
	// lock it.
	w := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "./logs/foo.log",
		MaxSize:    1, // megabytes
		MaxBackups: 3,
		MaxAge:     28, // days
	})
	consoleSyncer := zapcore.Lock(os.Stdout)

	fileEncoder := zapcore.NewJSONEncoder(zap.NewDevelopmentEncoderConfig())
	consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())

	//syncer := zapcore.NewMultiWriteSyncer(w)
	//core := zapcore.NewCore(
	//	zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
	//	syncer,
	//	zap.InfoLevel,
	//)
	core := zapcore.NewTee(
		zapcore.NewCore(fileEncoder, w, zap.InfoLevel),
		zapcore.NewCore(consoleEncoder, consoleSyncer, zap.InfoLevel),
	)
	logger := zap.New(core)
	for i := 0; i < num; i++ {
		logger.Info("log rotation: ", zap.Int("No", i))
	}
}

If MaxBackups and MaxAge are both 0, no old log files will be deleted.

Reference

  1. zapopen in new window docs
  2. FAQopen in new window zap FAQ
  3. lumberjackopen in new window github repo
上次编辑于:
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.2