log

package
v0.9.16 Latest Latest
Warning

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

Go to latest
Published: Jan 8, 2024 License: BSD-3-Clause Imports: 19 Imported by: 5

Documentation

Overview

Package log provides a wrapped zap logger interface for microservices to use, and also a simple Wrapper interface to be used by other Baseplate.go packages.

For zap logger related features, we provide both a global logger which can be used by top level functions, and a way to attach logger with additional info (e.g. trace id) to context object and reuse. When you need to use the zap logger and you have a context object, you should use the logger attached to the context, like:

log.C(ctx).Errorw("Something went wrong!", "err", err)

But if you don't have a context object, instead of creating one to use logger, you should use the global one:

log.Errorw("Something went wrong!", "err", err)

Index

Examples

Constants

View Source
const DefaultSentryFlushTimeout = time.Second * 2

DefaultSentryFlushTimeout is the timeout used to call sentry.Flush().

View Source
const RFC3339Nano = "ts=2006-01-02T15:04:05.000000Z"

RFC3339Nano is a time format for TimeEncoder

Variables

View Source
var (
	VersionLogKey = "v"

	Version string
)

Version is the version tag value to be added to the global logger.

If it's changed to non-empty value before the calling of Init* functions (InitFromConfig/InitLogger/InitLoggerJSON/InitLoggerWithConfig/InitSentry), the global logger initialized will come with a tag of VersionKey("v"), added to every line of logs. For InitSentry the global sentry will also be initialized with a tag of "version".

In order to use it, either set it in your main function early, before calling InitLogger* functions, with the value coming from flag/config file/etc.. For example:

func main() {
  log.Version = *flagVersion
  log.InitLoggerJSON(log.Level(*logLevel))
  // ...
}

Or just "stamp" it during build time, by passing additional ldflags to go build command. For example:

go build -ldflags "-X github.com/reddit/baseplate.go/log.Version=$(git rev-parse HEAD)"

Change its value after calling Init* functions will have no effects, unless you call Init* functions again.

Starting from go 1.18, if Version is not stamped at compile time, we'll try to fill it from runtime/debug.ReadBuildInfo, if available.

View Source
var ErrSentryFlushFailed = errors.New("log: sentry flushing failed")

ErrSentryFlushFailed is an error could be returned by the Closer returned by InitSentry, to indicate that the sentry flushing failed.

Functions

func Attach added in v0.2.0

func Attach(ctx context.Context, args AttachArgs) context.Context

Attach attaches a logger and sentry hub with data extracted from args into the context object.

func C added in v0.2.0

C is short for Context.

It extract the logger attached to the current context object, and fallback to the global logger if none is found.

When you have a context object and want to do logging, you should always use this one instead of the global one. For example:

log.C(ctx).Errorw("Something went wrong!", "err", err)

The return value is guaranteed to be non-nil.

func CapitalLevelEncoder

func CapitalLevelEncoder(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder)

CapitalLevelEncoder adds logger level in uppercase

func DPanic

func DPanic(args ...interface{})

DPanic uses fmt.Sprint to construct and log a message.

In development, the logger then panics. (See DPanicLevel for details.)

func DPanicf

func DPanicf(template string, args ...interface{})

DPanicf uses fmt.Sprintf to log a templated message.

In development, the logger then panics. (See DPanicLevel for details.)

func DPanicw

func DPanicw(msg string, keysAndValues ...interface{})

DPanicw logs a message with some additional context.

In development, the logger then panics. (See DPanicLevel for details.)

The variadic key-value pairs are treated as they are in With.

func Debug

func Debug(args ...interface{})

Debug uses fmt.Sprint to construct and log a message.

func Debugf

func Debugf(template string, args ...interface{})

Debugf uses fmt.Sprintf to log a templated message.

func Debugw

func Debugw(msg string, keysAndValues ...interface{})

Debugw logs a message with some additional context.

The variadic key-value pairs are treated as they are in With.

When debug-level logging is disabled, this is much faster than

With(keysAndValues).Debug(msg)

func Error

func Error(args ...interface{})

Error uses fmt.Sprint to construct and log a message.

func ErrorWithSentry

func ErrorWithSentry(ctx context.Context, msg string, err error, keysAndValues ...interface{})

ErrorWithSentry logs a message with some additional context, then sends the error to Sentry.

The variadic key-value pairs are treated as they are in With. and will also be sent to sentry. Note that zap.Field is not supported here and will be ignored while sending to sentry (but they will be logged to error log).

If a sentry hub is attached to the context object passed in (it will be if the context object is from baseplate hooked request context), that hub will be used to do the reporting. Otherwise the global sentry hub will be used instead.

func Errorf

func Errorf(template string, args ...interface{})

Errorf uses fmt.Sprintf to log a templated message.

func Errorw

func Errorw(msg string, keysAndValues ...interface{})

Errorw logs a message with some additional context.

The variadic key-value pairs are treated as they are in With.

func Fatal

func Fatal(args ...interface{})

Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit.

func Fatalf

func Fatalf(template string, args ...interface{})

Fatalf uses fmt.Sprintf to log a templated message, then calls os.Exit.

func Fatalw

func Fatalw(msg string, keysAndValues ...interface{})

Fatalw logs a message with some additional context, then calls os.Exit.

The variadic key-value pairs are treated as they are in With.

func FullCallerEncoder

func FullCallerEncoder(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder)

FullCallerEncoder serializes a caller in /full/path/to/package/file:line format.

func Info

func Info(args ...interface{})

Info uses fmt.Sprint to construct and log a message.

func Infof

func Infof(template string, args ...interface{})

Infof uses fmt.Sprintf to log a templated message.

func Infow

func Infow(msg string, keysAndValues ...interface{})

Infow logs a message with some additional context.

The variadic key-value pairs are treated as they are in With.

func InitFromConfig

func InitFromConfig(cfg Config)

InitFromConfig initializes the log package using the given Config and JSON logger.

func InitLogger

func InitLogger(logLevel Level)

InitLogger provides a quick way to start or replace the global logger.

func InitLoggerJSON

func InitLoggerJSON(logLevel Level)

InitLoggerJSON initializes global logger with full json format.

The JSON format is also compatible with logdna's ingestion format: https://docs.logdna.com/docs/ingestion

func InitLoggerWithConfig

func InitLoggerWithConfig(logLevel Level, cfg zap.Config) error

InitLoggerWithConfig provides a quick way to start or replace the global logger.

Pass in a cfg to provide a logger with custom setting.

This function also wraps the default zap core to convert all int64 and uint64 fields to strings, to prevent the loss of precision by json log ingester. As a result, some of the cfg might get lost during this wrapping, namely OutputPaths and ErrorOutputPaths.

func InitSentry

func InitSentry(cfg SentryConfig) (io.Closer, error)

InitSentry initializes sentry reporting.

The io.Closer returned calls sentry.Flush with SentryFlushTimeout. If it returns an error, that error is guaranteed to wrap ErrSentryFlushFailed.

You can also just call sentry.Init, which provides even more customizations. This function is provided to do the customizations we care about the most, and to provide a Closer to be more consistent with other baseplate packages.

When cfg.ServerName is non-empty, this function also sets hostname tag to the value reported by the kernel (when cfg.ServerName is empty server_name tag will be the hostname).

func JSONTimeEncoder

func JSONTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder)

JSONTimeEncoder encodes time in RFC3339Nano without extra information.

It's suitable to be used in the full JSON format.

func NopWrapper

func NopWrapper(ctx context.Context, msg string)

NopWrapper is a Wrapper implementation that does nothing.

In most cases you don't need to use it directly. The zero value of log.Wrapper is essentially a NopWrapper.

func Panic

func Panic(args ...interface{})

Panic uses fmt.Sprint to construct and log a message, then panics.

func Panicf

func Panicf(template string, args ...interface{})

Panicf uses fmt.Sprintf to log a templated message, then panics.

func Panicw

func Panicw(msg string, keysAndValues ...interface{})

Panicw logs a message with some additional context, then panics.

The variadic key-value pairs are treated as they are in With.

func SentryBeforeSendSwapExceptionTypeAndValue added in v0.9.0

func SentryBeforeSendSwapExceptionTypeAndValue(event *sentry.Event, hint *sentry.EventHint) *sentry.Event

SentryBeforeSendSwapExceptionTypeAndValue is a sentry.BeforeSend implementation that swaps the error message and error type reported to sentry.

See 1 for context on why doing this might be a good idea for your service.

This is not enabled by default, as it makes sentry no longer cluster errors with same type but slightly different error message as the same error. If you want to use this feature, set SentryConfig.BeforeSend to this function. If you have other things you want to do in your BeforeSend, you can call this function in your BeforeSend implementation instead. e.g.:

sentryConfig.BeforeSend = func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
  // Do other things you want to do here.

  // Also do the swapping.
  return log.SentryBeforeSendSwapExceptionTypeAndValue(event, hint)
}

func ShortCallerEncoder

func ShortCallerEncoder(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder)

ShortCallerEncoder serializes a caller in package/file:line format, trimming all but the final directory from the full path.

func Sync

func Sync() error

Sync flushes any buffered log entries.

func TimeEncoder

func TimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder)

TimeEncoder is customized to add ts in the front

func Warn

func Warn(args ...interface{})

Warn uses fmt.Sprint to construct and log a message.

func Warnf

func Warnf(template string, args ...interface{})

Warnf uses fmt.Sprintf to log a templated message.

func Warnw

func Warnw(msg string, keysAndValues ...interface{})

Warnw logs a message with some additional context.

The variadic key-value pairs are treated as they are in With.

func With

func With(args ...interface{}) *zap.SugaredLogger

With wraps around the underline logger's With.

func WrapToThriftLogger added in v0.6.0

func WrapToThriftLogger(w Wrapper) thrift.Logger

WrapToThriftLogger wraps a Wrapper into thrift.Logger.

Types

type AttachArgs added in v0.2.0

type AttachArgs struct {
	TraceID string

	AdditionalPairs map[string]interface{}
}

AttachArgs are used to create loggers and sentry hubs to be attached to context object with pre-filled key-value pairs.

All zero value fields will be ignored and only non-zero values will be attached.

AdditionalPairs are provided to add any free form, additional key-value pairs you want to attach to all logs and sentry reports from the same context object.

type Config

type Config struct {
	// Level is the log level you want to set your service to.
	Level Level `yaml:"level"`
}

Config is the confuration struct for the log package.

Can be deserialized from YAML.

type Counter added in v0.9.6

type Counter interface {
	Add(float64)
}

Counter is a minimal interface for a counter.

This is implemented by both prometheus counter and statsd counter from metricsbp.

type KitWrapper

type KitWrapper zapcore.Level

KitWrapper is a type that implements go-kit/log.Logger interface with zap logger.

func KitLogger

func KitLogger(level Level) KitWrapper

KitLogger returns a go-kit compatible logger.

func (KitWrapper) Log

func (w KitWrapper) Log(keyvals ...interface{}) error

Log implements go-kit/log.Logger interface.

type Level

type Level string

Level is the verbose representation of log level

const (
	NopLevel   Level = "nop"
	DebugLevel Level = "debug"
	InfoLevel  Level = "info"
	WarnLevel  Level = "warn"
	ErrorLevel Level = "error"
	PanicLevel Level = "panic"
	FatalLevel Level = "fatal"

	// This will have the same effect as nop but slower
	ZapNopLevel zapcore.Level = zapcore.FatalLevel + 1
)

Enums for Level

func (Level) ToZapLevel

func (l Level) ToZapLevel() zapcore.Level

ToZapLevel converts Level to a zap acceptable atomic level struct

type SentryConfig

type SentryConfig struct {
	// The Sentry DSN to use.
	// If empty, SENTRY_DSN environment variable will be used instead.
	// If that's also empty, then all sentry operations will be nop.
	DSN string `yaml:"dsn"`

	// SampleRate between 0 and 1, default is 1.
	SampleRate *float64 `yaml:"sampleRate"`

	// The name of your service.
	//
	// By default sentry extracts hostname reported by the kernel for this field.
	// Set it to non-empty to override that
	// (and hostname tag will be used to report hostname automatically).
	ServerName string `yaml:"serverName"`

	// An environment string like "prod", "staging".
	Environment string `yaml:"environment"`

	// List of regexp strings that will be used to match against event's message
	// and if applicable, caught errors type and value.
	// If the match is found, then a whole event will be dropped.
	IgnoreErrors []string `yaml:"ignoreErrors"`

	// FlushTimeout is the timeout to be used to call sentry.Flush when closing
	// the Closer returned by InitSentry.
	// If <=0, DefaultSentryFlushTimeout will be used.
	FlushTimeout time.Duration `yaml:"flushTimeout"`

	// BeforeSend is a callback modifier before emitting an event to Sentry.
	BeforeSend func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event `yaml:"-"`
}

SentryConfig is the config to be passed into InitSentry.

All fields are optional.

type Wrapper

type Wrapper func(ctx context.Context, msg string)

Wrapper defines a simple interface to wrap logging functions.

As principles, library code should:

1. Not do any logging. The library code should communicate errors back to the caller, and let the caller decide how to deal with them (log them, ignore them, panic, etc.)

2. In some rare cases, 1 is not possible, for example the error might happen in a background goroutine. In those cases some logging is necessary, but those should be kept at minimal, and the library code should provide control to the caller on how to do those logging.

This interface is meant to solve Principle 2 above. In reality we might actually use different logging libraries in different services, and they are not always compatible with each other. Wrapper is a simple common ground that it's easy to wrap whatever logging library we use into.

With that in mind, this interface should only be used by library code, when the case satisfies all of the following 3 criteria:

1. A bad thing happened.

2. This is unexpected. For expected errors, the library should either handle it by itself (e.g. retry), or communicate it back to the caller and let them handle it.

3. This is also recoverable. Unrecoverable errors should also be communicated back to the caller to handle.

Baseplate services should use direct logging functions for their logging needs, instead of using Wrapper interface.

For production code using baseplate libraries, Baseplate services should use ErrorWithSentryWrapper in most cases, as whenever the Wrapper is called that's something bad and unexpected happened and the service owner should be aware of. Non-Baseplate services should use error level in whatever logging library they use.

metricsbp.LogWrapper also provides an implementation that emits metrics when it's called. It can be wrapped on top of other log.Wrapper implementations.

For unit tests of library code using Wrapper, TestWrapper is provided that would fail the test when Wrapper is called.

Not all Wrapper implementations take advantage of context object passed in, but the caller should always pass it into Wrapper if they already have one, or just use context.Background() if they don't have one.

var DefaultWrapper Wrapper = ErrorWithSentryWrapper()

DefaultWrapper defines the default one to be used in log.Wrapper.

It affects two places:

1. When using nil-safe calls on log.Wrapper on a nil log.Wrapper.

2. When unmarshaling from text (yaml) and the text is empty.

func CounterWrapper added in v0.9.6

func CounterWrapper(delegate Wrapper, counter Counter) Wrapper

CounterWrapper returns a Wrapper implementation that increases counter by 1 then calls delegate to log the message.

Please note that it's not possible to deserialize this Wrapper directly from yaml, so you usually need to override it in your main function, after baseplate.ParseConfigYAML call, for example:

// a global variable
var tracingFailures = promauto.NewCounter(prometheus.CounterOpts{
  Namespace: "myservice",
  Subsystem: "tracing",
  Name:      "failures_total",
  Help:      "Total number of failures when sending tracing spans to the sidecar",
})

// in main
if err := baseplate.ParseConfigYAML(&cfg); err != nil {
  log.Fatal(err)
}
cfg.Config.Tracing.Logger = log.CounterWrapper(
  cfg.Config.Tracing.Logger, // delegate
  tracingFailures,           // counter
}

func ErrorWithSentryWrapper

func ErrorWithSentryWrapper() Wrapper

ErrorWithSentryWrapper is a Wrapper implementation that both use Zap logger to log at error level, and also send the message to Sentry.

In most cases this should be the one used to pass into Baseplate.go libraries expecting a log.Wrapper. If the service didn't configure sentry, then this wrapper is essentially the same as log.ZapWrapper(log.ErrorLevel).

Note that this should not be used as the logger set into thrift.TSimpleServer, as that would use the logger to log network I/O errors, which would be too spammy to be sent to Sentry. For this reason, it's returning a Wrapper instead of being a Wrapper itself, thus forcing an extra typecasting to be used as a thrift.Logger.

func StdWrapper

func StdWrapper(logger *stdlog.Logger) Wrapper

StdWrapper wraps stdlib log package into a Wrapper.

func TestWrapper

func TestWrapper(tb testing.TB) Wrapper

TestWrapper is a wrapper can be used in test codes.

It fails the test when called.

func ZapWrapper

func ZapWrapper(args ZapWrapperArgs) Wrapper

ZapWrapper wraps zap log package into a Wrapper.

func (Wrapper) Log added in v0.3.0

func (w Wrapper) Log(ctx context.Context, msg string)

Log is the nil-safe way of calling a log.Wrapper.

If w is nil, DefaultWrapper will be used instead.

func (Wrapper) ToThriftLogger added in v0.6.0

func (w Wrapper) ToThriftLogger() thrift.Logger

ToThriftLogger wraps Wrapper into thrift.Logger.

func (*Wrapper) UnmarshalText added in v0.7.1

func (w *Wrapper) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler.

It makes Wrapper possible to be used directly in yaml and other config files.

Please note that this currently only support limited implementations:

- empty: DefaultWrapper.

- "nop": NopWrapper.

- "std": StdWrapper with default stdlib logger (log.New(os.Stderr, "", log.LstdFlags)).

- "zap": ZapWrapper on default level (Info) with no kv pairs.

- "zap:level:k1=v1,k2=v2...": ZapWrapper with given level and kv pairs, with the ":k=v..." part being optional. For example "zap:error" means ZapWrapper on Error level with no kv pairs, "zap:info:key1=value1" means ZapWrapper on Info level with "key1":"value1" pair.

- "sentry": ErrorWithSentryWrapper.

See the example on how to extend it to support other implementations.

Example

This example demonstrates how to write your own type to "extend" log.Wrapper.UnmarshalText to add other implementations.

package main

import (
	"bytes"
	"context"
	"encoding"
	"fmt"
	"strings"

	"gopkg.in/yaml.v2"

	"github.com/reddit/baseplate.go/log"
	"github.com/reddit/baseplate.go/metricsbp"
)

// ExtendedLogWrapper extends log.Wrapper to support metricsbp.LogWrapper.
type ExtendedLogWrapper struct {
	log.Wrapper
}

var st = metricsbp.NewStatsd(
	context.Background(),
	metricsbp.Config{
		// This is to make sure that when we read the metricsbp statsd buffer out
		// manually later in the example they are not already sent to a blackhole
		// sink.
		// This is NOT needed in production code as in production code the sink
		// shall be configured as the actual statsd collector and not read out
		// manually like in this example.
		BufferInMemoryForTesting: true,
	},
)

// UnmarshalText implements encoding.TextUnmarshaler.
//
// In addition to the implementations log.Wrapper.UnmarshalText supports, it
// added supports for:
//
// - "counter:metrics:logger": metricsbp.LogWrapper, with "metrics" being the
// name of the counter metrics and "logger" being the underlying logger.
func (e *ExtendedLogWrapper) UnmarshalText(text []byte) error {
	const counterPrefix = "counter:"
	if s := string(text); strings.HasPrefix(s, counterPrefix) {
		parts := strings.SplitN(s, ":", 3)
		if len(parts) != 3 {
			return fmt.Errorf("unsupported log.Wrapper config: %q", text)
		}
		var w log.Wrapper
		if err := w.UnmarshalText([]byte(parts[2])); err != nil {
			return err
		}
		e.Wrapper = metricsbp.LogWrapper(metricsbp.LogWrapperArgs{
			Counter: parts[1],
			Wrapper: w,
			Statsd:  st,
		})
		return nil
	}
	return e.Wrapper.UnmarshalText(text)
}

func (e ExtendedLogWrapper) ToLogWrapper() log.Wrapper {
	return e.Wrapper
}

var _ encoding.TextUnmarshaler = (*ExtendedLogWrapper)(nil)

// This example demonstrates how to write your own type to "extend"
// log.Wrapper.UnmarshalText to add other implementations.
func main() {
	const (
		invalid     = `logger: fancy`
		counterOnly = `logger: "counter:foo.bar.count:nop"`
	)
	var data struct {
		Logger ExtendedLogWrapper `yaml:"logger"`
	}

	fmt.Printf(
		"This is an invalid config: %s, err: %v\n",
		invalid,
		yaml.Unmarshal([]byte(invalid), &data),
	)

	fmt.Printf(
		"This is an counter-only config: %s, err: %v\n",
		counterOnly,
		yaml.Unmarshal([]byte(counterOnly), &data),
	)
	data.Logger.Log(context.Background(), "Hello, world!")
	var buf bytes.Buffer
	st.WriteTo(&buf)
	fmt.Printf("Counter: %s", buf.String())

}
Output:

This is an invalid config: logger: fancy, err: unsupported log.Wrapper config: "fancy"
This is an counter-only config: logger: "counter:foo.bar.count:nop", err: <nil>
Counter: foo.bar.count:1.000000|c

type ZapWrapperArgs added in v0.9.0

type ZapWrapperArgs struct {
	Level   Level
	KVPairs map[string]interface{}
}

ZapWrapperArgs defines the args used in ZapWrapper.

Jump to

Keyboard shortcuts

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