ctxd

package module
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Nov 28, 2020 License: MIT Imports: 8 Imported by: 56

README

Contextualized Structured Logging and Error Handling for Go

This library provides context-driven structured error and logger contracts.

Build Status Coverage Status GoDevDoc time tracker Code lines Comments

Usage

  • Create an adapter for your logger that implements ctxd.Logger or use zapctxd that is built around awesome go.uber.org/zap.
  • Add fields to context and pass it around.
  • Use context for last-mile logging or error emitting.

Example

Structured Logging
logger := ctxd.LoggerMock{}

// Once instrumented context can aid logger with structured information.
ctx := ctxd.AddFields(context.Background(), "foo", "bar")

logger.Info(ctx, "something happened")

// Also context contributes additional information to structured errors.
err := ctxd.NewError(ctx, "something failed", "baz", "quux")

ctxd.LogError(ctx, err, logger.Error)

fmt.Print(logger.String())
// Output:
// info: something happened {"foo":"bar"}
// error: something failed {"baz":"quux","foo":"bar"}

Logger can be instrumented with persistent fields that are affecting every context.

lm := ctxd.LoggerMock{}

var globalLogger ctxd.Logger = &lm

localLogger := ctxd.LoggerWithFields(globalLogger, "local", 123)

ctx1 := ctxd.AddFields(context.Background(),
    "ctx", 1,
    "foo", "bar",
)
ctx2 := ctxd.AddFields(context.Background(), "ctx", 2)

localLogger.Info(ctx1, "hello", "he", "lo")
localLogger.Warn(ctx2, "bye", "by", "ee")

fmt.Print(lm.String())

// Output:
// info: hello {"ctx":1,"foo":"bar","he":"lo","local":123}
// warn: bye {"by":"ee","ctx":2,"local":123}
Handling Errors
ctx := context.Background()

// Elaborate context with fields.
ctx = ctxd.AddFields(ctx,
    "field1", 1,
    "field2", "abc",
)

// Add more fields when creating error.
err := ctxd.NewError(ctx, "something failed",
    "field3", 3.0,
)

err2 := ctxd.WrapError(
    // You can use same or any other context when wrapping error.
    ctxd.AddFields(context.Background(), "field5", "V"),
    err, "wrapped",
    "field4", true)

// Setup your logger.
var (
    lm                 = ctxd.LoggerMock{}
    logger ctxd.Logger = &lm
)

// Inspect error fields.
var se ctxd.StructuredError
if errors.As(err, &se) {
    fmt.Printf("error fields: %v\n", se.Fields())
}

// Log errors.
ctxd.LogError(ctx, err, logger.Error)
ctxd.LogError(ctx, err2, logger.Warn)
fmt.Print(lm.String())

// Output:
// error fields: map[field1:1 field2:abc field3:3]
// error: something failed {"field1":1,"field2":"abc","field3":3}
// warn: wrapped: something failed {"field1":1,"field2":"abc","field3":3,"field4":true,"field5":"V"}

Documentation

Overview

Package ctxd defines contextualized structured logging and error handling.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddFields

func AddFields(ctx context.Context, keysAndValues ...interface{}) context.Context

AddFields returns context with added loosely-typed key-value pairs as fields.

If key-value pairs exist in parent context already, new pairs are appended.

Example
package main

import (
	"context"
	"fmt"

	"github.com/bool64/ctxd"
)

func main() {
	logger := ctxd.LoggerMock{}

	// Once instrumented context can aid logger with structured information.
	ctx := ctxd.AddFields(context.Background(), "foo", "bar")

	logger.Info(ctx, "something happened")

	// Also context contributes additional information to structured errors.
	err := ctxd.NewError(ctx, "something failed", "baz", "quux")

	ctxd.LogError(ctx, err, logger.Error)

	fmt.Print(logger.String())
}
Output:

info: something happened {"foo":"bar"}
error: something failed {"baz":"quux","foo":"bar"}

func ClearFields

func ClearFields(ctx context.Context) context.Context

ClearFields returns context without any fields.

func Fields

func Fields(ctx context.Context) []interface{}

Fields returns loosely-typed key-value pairs found in context or nil.

func IsDebug

func IsDebug(ctx context.Context) bool

IsDebug returns true if debug flag is enabled in context.

func LogError

func LogError(ctx context.Context, err error, l LogFunc)

LogError pushes error value to a contextualized logger method.

If err is nil, LogError produces no operation. LogError function matches Logger methods, e.g. Error.

func LogWriter

func LogWriter(ctx context.Context) io.Writer

LogWriter returns custom log writer found in context or nil.

func NewError

func NewError(ctx context.Context, message string, keysAndValues ...interface{}) error

NewError creates error with optional structured data.

LogError fields from context are also added to error structured data.

func WithDebug

func WithDebug(ctx context.Context) context.Context

WithDebug returns context with debug flag enabled.

func WithLogWriter

func WithLogWriter(ctx context.Context, w io.Writer) context.Context

WithLogWriter returns context with custom log writer. Can be useful to write logs into response stream.

func WrapError

func WrapError(ctx context.Context, err error, message string, keysAndValues ...interface{}) error

WrapError returns an error annotated with message and structured data.

If err is nil, WrapError returns nil. LogError fields from context are also added to error structured data.

Example
package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/bool64/ctxd"
)

func main() {
	ctx := context.Background()

	// Elaborate context with fields.
	ctx = ctxd.AddFields(ctx,
		"field1", 1,
		"field2", "abc",
	)

	// Add more fields when creating error.
	err := ctxd.NewError(ctx, "something failed",
		"field3", 3.0,
	)

	err2 := ctxd.WrapError(
		// You can use same or any other context when wrapping error.
		ctxd.AddFields(context.Background(), "field5", "V"),
		err, "wrapped",
		"field4", true)

	// Setup your logger.
	var (
		lm                 = ctxd.LoggerMock{}
		logger ctxd.Logger = &lm
	)

	// Inspect error fields.
	var se ctxd.StructuredError
	if errors.As(err, &se) {
		fmt.Printf("error fields: %v\n", se.Fields())
	}

	// Log errors.
	ctxd.LogError(ctx, err, logger.Error)
	ctxd.LogError(ctx, err2, logger.Warn)
	fmt.Print(lm.String())

}
Output:

error fields: map[field1:1 field2:abc field3:3]
error: something failed {"field1":1,"field2":"abc","field3":3}
warn: wrapped: something failed {"field1":1,"field2":"abc","field3":3,"field4":true,"field5":"V"}

Types

type DeferredJSON

type DeferredJSON func() interface{}

DeferredJSON postpones log field processing, suitable for debug logging.

func (DeferredJSON) MarshalJSON

func (d DeferredJSON) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler.

type DeferredString

type DeferredString func() interface{}

DeferredString postpones log field processing, suitable for debug logging.

func (DeferredString) String

func (d DeferredString) String() string

String implements fmt.Stringer.

type LogFunc

type LogFunc func(ctx context.Context, msg string, keysAndValues ...interface{})

LogFunc defines contextualized logger function.

type Logger

type Logger interface {
	// Debug logs a message.
	Debug(ctx context.Context, msg string, keysAndValues ...interface{})

	// Info logs a message.
	Info(ctx context.Context, msg string, keysAndValues ...interface{})

	// Important forcibly logs an important message with level INFO disregarding logger level constraints.
	// Can be used for logging historically important information.
	Important(ctx context.Context, msg string, keysAndValues ...interface{})

	// Warn logs a message.
	Warn(ctx context.Context, msg string, keysAndValues ...interface{})

	// Error logs a message.
	Error(ctx context.Context, msg string, keysAndValues ...interface{})
}

Logger is a contextualized structured logger.

Logging methods accept keys and values as variadic argument that contains loosely-typed key-value pairs. When processing pairs, the first element of the pair is used as the field key and the second as the field value.

func LoggerWithFields

func LoggerWithFields(logger Logger, keysAndValues ...interface{}) Logger

LoggerWithFields instruments contextualized logger with global fields.

Example
package main

import (
	"context"
	"fmt"

	"github.com/bool64/ctxd"
)

func main() {
	lm := ctxd.LoggerMock{}

	var globalLogger ctxd.Logger = &lm

	localLogger := ctxd.LoggerWithFields(globalLogger, "local", 123)

	ctx1 := ctxd.AddFields(context.Background(),
		"ctx", 1,
		"foo", "bar",
	)
	ctx2 := ctxd.AddFields(context.Background(), "ctx", 2)

	localLogger.Info(ctx1, "hello", "he", "lo")
	localLogger.Warn(ctx2, "bye", "by", "ee")

	fmt.Print(lm.String())

}
Output:

info: hello {"ctx":1,"foo":"bar","he":"lo","local":123}
warn: bye {"by":"ee","ctx":2,"local":123}

type LoggerMock added in v0.1.2

type LoggerMock struct {
	bytes.Buffer
	OnError func(err error)
}

LoggerMock logs messages to internal buffer.

func (*LoggerMock) Debug added in v0.1.2

func (m *LoggerMock) Debug(ctx context.Context, msg string, keysAndValues ...interface{})

Debug logs a message.

func (*LoggerMock) Error added in v0.1.2

func (m *LoggerMock) Error(ctx context.Context, msg string, keysAndValues ...interface{})

Error logs a message.

func (*LoggerMock) Important added in v0.1.2

func (m *LoggerMock) Important(ctx context.Context, msg string, keysAndValues ...interface{})

Important logs a message.

func (*LoggerMock) Info added in v0.1.2

func (m *LoggerMock) Info(ctx context.Context, msg string, keysAndValues ...interface{})

Info logs a message.

func (*LoggerMock) Warn added in v0.1.2

func (m *LoggerMock) Warn(ctx context.Context, msg string, keysAndValues ...interface{})

Warn logs a message.

type LoggerProvider

type LoggerProvider interface {
	CtxdLogger() Logger
}

LoggerProvider is an embeddable provider interface.

type NoOpLogger

type NoOpLogger struct{}

NoOpLogger is a contextualized logger stub.

func (NoOpLogger) CtxdLogger

func (NoOpLogger) CtxdLogger() Logger

CtxdLogger is a provider.

func (NoOpLogger) Debug

func (NoOpLogger) Debug(_ context.Context, _ string, _ ...interface{})

Debug discards debug message.

func (NoOpLogger) Error

func (NoOpLogger) Error(_ context.Context, _ string, _ ...interface{})

Error discards error message.

func (NoOpLogger) Important

func (NoOpLogger) Important(_ context.Context, _ string, _ ...interface{})

Important discards important message.

func (NoOpLogger) Info

func (NoOpLogger) Info(_ context.Context, _ string, _ ...interface{})

Info discards informational message.

func (NoOpLogger) Warn

func (NoOpLogger) Warn(_ context.Context, _ string, _ ...interface{})

Warn discards warning message.

type StructuredError

type StructuredError interface {
	// Error returns message of error.
	Error() string

	// Tuples returns structured data of error in form of loosely-typed key-value pairs.
	Tuples() []interface{}

	// Fields returns structured data of error as a map.
	Fields() map[string]interface{}
}

StructuredError defines error with message and data.

type Tuples

type Tuples []interface{}

Tuples is a slice of keys and values, e.g. {"key1", 1, "key2", "val2"}.

func (Tuples) Fields

func (t Tuples) Fields() map[string]interface{}

Fields creates a map from key-value pairs.

Jump to

Keyboard shortcuts

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