ctxdata

package module
v4.0.0 Latest Latest
Warning

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

Go to latest
Published: Sep 22, 2020 License: Apache-2.0 Imports: 5 Imported by: 2

README

ctxdata go.dev reference Build Status

A helper for collecting and emitting metadata throughout a request lifecycle.

When a new request arrives in your program, HTTP server, etc., create a new Data value and inject it into the request context via ctxdata.New.

import "github.com/peterbourgon/ctxdata/v4"

func handler(w http.ResponseWriter, r *http.Request) {
    ctx, d := ctxdata.New(r.Context())

Use the returned context for all further operations on that request.

    otherHandler(w, r.WithContext(ctx))
    result, err := process(ctx, r.Header.Get("X-My-Key"))
    // etc.

Once the Data has been created and injected to the context, use ctxdata.From from any downstream function with access to the context to fetch it and add more metadata about the request.

func otherHandler(w http.ResponseWriter, r *http.Request) {
    d := ctxdata.From(r.Context())
    d.Set("user", r.URL.Query().Get("user"))
    d.Set("corrleation_id", r.Header.Get("X-Correlation-ID"))
    // ...
}

func process(ctx context.Context, key string) {
    begin := time.Now()
    // ...
    ctxdata.From(ctx).Set("process_duration", time.Since(begin))
}

At the end of the request, all of the metadata collected throughout the request's lifecycle will be available.

    fmt.Fprintln(w, "OK")

    for _, kv := range d.GetSlice() {
        log.Printf("%s: %s", kv.Key, kv.Val)
    }
}

Here is an example middleware that writes a so-called wide event in JSON to the dst at the end of each request.

func logMiddleware(next http.Handler, dst io.Writer) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx, d := ctxdata.New(r.Context())
        defer func() { json.NewEncoder(dst).Encode(d.GetAllMap()) }()
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

Documentation

Overview

Package ctxdata provides a helper type for request-scoped metadata.

Example (Middleware)
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/peterbourgon/ctxdata/v4"
)

type Server struct{}

func NewServer() *Server {
	return &Server{}
}

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	d := ctxdata.From(r.Context())
	d.Set("method", r.Method)
	d.Set("path", r.URL.Path)
	d.Set("content_length", r.ContentLength)
	fmt.Fprintln(w, "OK")
}

type Middleware struct {
	next http.Handler
}

func NewMiddleware(next http.Handler) *Middleware {
	return &Middleware{next: next}
}

func (mw *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx, d := ctxdata.New(r.Context())

	defer func() {
		for _, kv := range d.GetAllSlice() {
			fmt.Printf("%s: %v\n", kv.Key, kv.Val)
		}
	}()

	mw.next.ServeHTTP(w, r.WithContext(ctx))
}

func main() {
	server := NewServer()
	middleware := NewMiddleware(server)
	testserver := httptest.NewServer(middleware)
	defer testserver.Close()
	http.Post(testserver.URL+"/path", "text/plain; charset=utf-8", strings.NewReader("hello world"))

}
Output:

method: POST
path: /path
content_length: 11

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrIncompatibleType = errors.New("incompatible type")

ErrIncompatibleType is returned by GetAs if the value associated with a key isn't assignable to the provided target.

View Source
var ErrNoData = errors.New("no data in context")

ErrNoData is returned by accessor methods when they're called on a nil pointer receiver. This typically means From was called on a context that didn't have a Data injected into it previously via New.

View Source
var ErrNotFound = errors.New("key not found")

ErrNotFound is returned by Get when the key isn't present.

Functions

This section is empty.

Types

type Data

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

Data is an opaque type that can be injected into a context at e.g. the start of a request, updated with metadata over the course of the request, and then queried at the end of the request.

When a new request arrives in your program, HTTP server, etc., use the New constructor with the incoming request's context to construct a new, empty Data. Use the returned context for all further operations on that request. Use the From helper function to retrieve a previously-injected Data from a context, and set or get metadata. At the end of the request, all metadata collected will be available from any point in the callstack.

func From

func From(ctx context.Context) *Data

From extracts a Data from the provided context. If no Data object was injected to the context, the returned pointer will be nil, but all Data methods gracefully handle this condition, so it's safe to consider the returned value always valid.

func New

func New(ctx context.Context) (context.Context, *Data)

New constructs a Data object and injects it into the provided context. Use the returned context for all further operations. The returned Data can be queried at any point for metadata collected over the life of the context.

func (*Data) Get

func (d *Data) Get(key string) (val interface{}, err error)

Get the value associated with key, or return ErrNotFound. If this method is called on a nil Data pointer, it returns ErrNoData.

func (*Data) GetAllMap

func (d *Data) GetAllMap() map[string]interface{}

GetAllMap returns a map of key to value. If this method is called on a nil Data pointer, it returns ErrNoData.

func (*Data) GetAllSlice

func (d *Data) GetAllSlice() []KeyVal

GetAllSlice returns a slice of key/value pairs in the order in which they were set. If this method is called on a nil Data pointer, it returns ErrNoData.

func (*Data) GetAs

func (d *Data) GetAs(key string, target interface{}) error

GetAs will try to assign the value associated with key to target. Target must be a pointer to an assignable type. GetAs will return ErrNotFound if the key is not found, and ErrIncompatibleType if the found value is not assignable to target.

Example

This example demonstrates how to use GetAs to retrieve metadata into an arbitrary type.

type DomainError struct {
	Code   int
	Reason string
}

_, d := ctxdata.New(context.Background())
derr := DomainError{Code: http.StatusTeapot, Reason: "Earl Gray exception"}
d.Set("err", derr)

if target := (DomainError{}); d.GetAs("err", &target) == nil {
	fmt.Printf("DomainError Code=%d Reason=%q\n", target.Code, target.Reason)
}
Output:

DomainError Code=418 Reason="Earl Gray exception"

func (*Data) GetBool

func (d *Data) GetBool(key string) (val bool)

GetBool returns the value of key as type bool. If key is not set, or its value is not of type bool, then GetBool returns a zero value of type bool.

func (*Data) GetBoolDefault

func (d *Data) GetBoolDefault(key string, defaultValue bool) bool

GetBoolDefault returns the value of key as type bool. If key is not set, or its value is not of type bool, then GetBoolDefault returns defaultValue.

func (*Data) GetDuration

func (d *Data) GetDuration(key string) (val time.Duration)

GetDuration returns the value of key as type time.Duration. If key is not set, or its value is not of type time.Duration, then GetDuration returns a zero value of type time.Duration.

func (*Data) GetDurationDefault

func (d *Data) GetDurationDefault(key string, defaultValue time.Duration) time.Duration

GetDurationDefault returns the value of key as type time.Duration. If key is not set, or its value is not of type time.Duration, then GetDurationDefault returns defaultValue.

func (*Data) GetError

func (d *Data) GetError(key string) (val error)

GetError returns the value of key as type error. If key is not set, or its value is not of type error, then GetError returns a zero value of type error.

func (*Data) GetErrorDefault

func (d *Data) GetErrorDefault(key string, defaultValue error) error

GetErrorDefault returns the value of key as type error. If key is not set, or its value is not of type error, then GetErrorDefault returns defaultValue.

func (*Data) GetFloat64

func (d *Data) GetFloat64(key string) (val float64)

GetFloat64 returns the value of key as type float64. If key is not set, or its value is not of type float64, then GetFloat64 returns a zero value of type float64.

func (*Data) GetFloat64Default

func (d *Data) GetFloat64Default(key string, defaultValue float64) float64

GetFloat64Default returns the value of key as type float64. If key is not set, or its value is not of type float64, then GetFloat64Default returns defaultValue.

func (*Data) GetInt

func (d *Data) GetInt(key string) (val int)

GetInt returns the value of key as type int. If key is not set, or its value is not of type int, then GetInt returns a zero value of type int.

func (*Data) GetInt64

func (d *Data) GetInt64(key string) (val int64)

GetInt64 returns the value of key as type int64. If key is not set, or its value is not of type int64, then GetInt64 returns a zero value of type int64.

func (*Data) GetInt64Default

func (d *Data) GetInt64Default(key string, defaultValue int64) int64

GetInt64Default returns the value of key as type int64. If key is not set, or its value is not of type int64, then GetInt64Default returns defaultValue.

func (*Data) GetIntDefault

func (d *Data) GetIntDefault(key string, defaultValue int) int

GetIntDefault returns the value of key as type int. If key is not set, or its value is not of type int, then GetIntDefault returns defaultValue.

func (*Data) GetString

func (d *Data) GetString(key string) (val string)

GetString returns the value of key as type string. If key is not set, or its value is not of type string, then GetString returns a zero value of type string.

func (*Data) GetStringDefault

func (d *Data) GetStringDefault(key string, defaultValue string) string

GetStringDefault returns the value of key as type string. If key is not set, or its value is not of type string, then GetStringDefault returns defaultValue.

func (*Data) GetTime

func (d *Data) GetTime(key string) (val time.Time)

GetTime returns the value of key as type time.Time. If key is not set, or its value is not of type time.Time, then GetTime returns a zero value of type time.Time.

func (*Data) GetTimeDefault

func (d *Data) GetTimeDefault(key string, defaultValue time.Time) time.Time

GetTimeDefault returns the value of key as type time.Time. If key is not set, or its value is not of type time.Time, then GetTimeDefault returns defaultValue.

func (*Data) GetUint64

func (d *Data) GetUint64(key string) (val uint64)

GetUint64 returns the value of key as type uint64. If key is not set, or its value is not of type uint64, then GetUint64 returns a zero value of type uint64.

func (*Data) GetUint64Default

func (d *Data) GetUint64Default(key string, defaultValue uint64) uint64

GetUint64Default returns the value of key as type uint64. If key is not set, or its value is not of type uint64, then GetUint64Default returns defaultValue.

func (*Data) Set

func (d *Data) Set(key string, val interface{}) error

Set key to val. If key already exists, it will be overwritten. If this method is called on a nil Data pointer, it returns ErrNoData.

type KeyVal

type KeyVal struct {
	Key string
	Val interface{}
}

KeyVal combines a string key with its abstract value into a single tuple. It's used internally, and as a return type for GetSlice.

Jump to

Keyboard shortcuts

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