tap

package
v0.7.1 Latest Latest
Warning

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

Go to latest
Published: Dec 20, 2016 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package tap provides a simple item emitter source and a tracing source.

TODO: break up this package: split emitter from tracer

Emitter

The Emitter source is useful for adding watchable-sources for existing data in your application, and where it wasn't worth it to define a specialized source.

All emitter sources will be named like "/tap/...", to emphasize their generic nature. The normal use case here is for adding adhoc taps into existing program data.

Tracer

The Tracer source is useful for tracing program execution. It can be used to trace things like function calls, goroutine work units, and anything else where you can define a scope of work.

All tracer sources will be named like "/tap/trace/...". A default tracer is provided at "/tap/trace", however the normal usage pattern is to declare one-or-more tracers within a package, and to name the appropriately to the area of the code that is traced.

Index

Examples

Constants

This section is empty.

Variables

View Source
var DefaultTracer = Tracer{
	// contains filtered or unexported fields
}

DefaultTracer is available for easy scope logging without needing to create a separate tracer.

Functions

func Active

func Active() bool

Active returns whether the default tracer is active.

func ResetTraceID added in v0.6.4

func ResetTraceID()

ResetTraceID resets the last trace id; this is intended to be used only for test stability.

Types

type Emitter

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

Emitter provides a simple watchable data source with easy emission.

func AddEmitter

func AddEmitter(name string, tmpl *template.Template) *Emitter

AddEmitter creates an emitter source and adds it to the default gwr sources.

func NewEmitter

func NewEmitter(name string, tmpl *template.Template) *Emitter

NewEmitter creates an Emitter with a given name and text template; if the template is nil, than a default template which just uses the default textual representation is used.

The given name will be prefixed with "/tap/" automatically.

Any templated passed must define an "item" block.

func (*Emitter) Active

func (em *Emitter) Active() bool

Active retruns true if there are any active watchers.

func (*Emitter) Emit

func (em *Emitter) Emit(items ...interface{}) bool

Emit emits item(s) to any active watchers. Returns true if the watcher is (still) active.

func (*Emitter) EmitBatch

func (em *Emitter) EmitBatch(items []interface{}) bool

EmitBatch emits batch of items. Returns true if the watcher is (still) active.

func (*Emitter) Formats added in v0.6.2

func (em *Emitter) Formats() map[string]source.GenericDataFormat

Formats returns emitter-specific formats.

func (*Emitter) Name

func (em *Emitter) Name() string

Name returns the full name of the emitter source; this will be "/tap/name_given_to_New_Emitter".

func (*Emitter) SetWatcher

func (em *Emitter) SetWatcher(watcher source.GenericDataWatcher)

SetWatcher sets the watcher at source addition time.

func (*Emitter) TextTemplate

func (em *Emitter) TextTemplate() *template.Template

TextTemplate returns the template used to marshal items human friendily.

type TraceScope

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

TraceScope represents a traced scope, such as a function call, or an iteration of a worker goroutine loop.

func MaybeScope

func MaybeScope(name string) *TraceScope

MaybeScope creates a new scope on the default tracer, if it is active; otherwise nil is returned.

func Scope

func Scope(name string) *TraceScope

Scope creates a new scope on the default tracer.

func (*TraceScope) Active added in v0.7.0

func (sc *TraceScope) Active() bool

Active returns true if the tracer is active, false otherwise.

func (*TraceScope) BeginTime added in v0.7.0

func (sc *TraceScope) BeginTime() time.Time

BeginTime returns the time of the first scope.Open or scope.OpenCall (only one of these should be called, but the first one wins for begin time anyhow). Sub-scope times do not affect their parent scope's begin/end.

func (*TraceScope) Close

func (sc *TraceScope) Close(args ...interface{}) *TraceScope

Close emits a end record with the given arguments.

func (*TraceScope) CloseCall

func (sc *TraceScope) CloseCall(rets ...interface{}) *TraceScope

CloseCall emits an end record for a function call with the return values.

func (*TraceScope) EndTime added in v0.7.0

func (sc *TraceScope) EndTime() time.Time

EndTime returns the time of the last scope.Close, scope.CloseCall, scope.Error, or scope.ErrorName. Sub-scope times do not affect their parent scope's begin/end.

func (*TraceScope) Error

func (sc *TraceScope) Error(err error, args ...interface{}) *TraceScope

Error emits an error record with the given error and arguments.

func (*TraceScope) ErrorName

func (sc *TraceScope) ErrorName(name string, err error, args ...interface{}) *TraceScope

ErrorName emits an error record with the given error and arguments.

func (*TraceScope) Info

func (sc *TraceScope) Info(args ...interface{}) *TraceScope

Info emits an info record with the passed arguments

func (*TraceScope) Open

func (sc *TraceScope) Open(args ...interface{}) *TraceScope

Open emits a begin record with the given arguments.

func (*TraceScope) OpenCall

func (sc *TraceScope) OpenCall(args ...interface{}) *TraceScope

OpenCall emits a begin record for a function call with the given arguments.

func (*TraceScope) Parent added in v0.7.0

func (sc *TraceScope) Parent() *TraceScope

Parent returns the parent scope; this is nil for root scopes..

func (*TraceScope) Root added in v0.7.0

func (sc *TraceScope) Root() *TraceScope

Root returns the root scope.

func (*TraceScope) Sub

func (sc *TraceScope) Sub(name string) *TraceScope

Sub opens and returns a new sub-scope.

type Tracer

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

Tracer implements a gwr data source that allows easy tracing of scope data, such as function calls, or rounds of a worker goroutine's loop.

Tracers should be created for each area of the application that can be traced. This could be as simple as creating a package-level tracer:

package foo

import "github.com/uber-go/gwr/source"

tracer := source.AddNewTracer("foo")

Tracers can also be attached to parts of the application:

type Thing struct {
    t *Tracer
}

func NewThing() *Thing {
    // ...
    t.tracer = source.AddNewTracer(fmt.Sprintf("foo/%v", someThingIdentifier))
    // ...
}

If Things are not the same life-cycle as the application, then they should have teardown code to remove their tracer data sources:

gwr.DefaultDataSources.Remove(t.tracer.Name())

You can then proceed to trace your functions and methods. First decide where/what you want to start tracing. this will probably be one or more exported functions or methods called by your user.

Within these entry points use Tracer.Scope to create a root scope. Pass this scope along to any called functions that you want to trace. Functions that get passed a scope should start off by calling scope.Sub to create their own scope.

Within a traced function, start off by calling scope.OpenCall to note the start of function. While doing normal work within a traced function, you may call scope.Info to note log any additional data. Finally once done the traced function should call one of scope.CloseCall, scope.Error or scope.ErrorName.

If a traced function has more than one possible way to error, it should use scope.ErrorName to describe what failed. Furthermore, a traced function may call scope.ErrorName more than once if it has recoverable errors, or otherwise makes progress around errors.

You can similarly trace a worker goroutine:

ch := make(chan int)
go func() {
    for n := range ch {
        scope := tracer.Scope("n <- workerChan").Open(n)
        // do something...
        scope.Close()
    }
}()
Example
package main

import (
	"fmt"
	"strings"

	"github.com/uber-go/gwr"
	"github.com/uber-go/gwr/report"
	"github.com/uber-go/gwr/source"
	"github.com/uber-go/gwr/source/tap"
)

var fibTracer = tap.AddNewTracer("fib")

// untracedFib is a classic recursive fibonacci computation
func untracedFib(n int) int {
	if n < 0 {
		return 0
	}
	if n < 2 {
		return 1
	}
	return untracedFib(n-1) + untracedFib(n-2)
}

// tracedFib is a modified copy of untracedFib that uses and passes along a
// tracing scope.
//
// For more complex functions, you can call other methods on scope like:
//   - Info(...) to emit intermediate data
//   - Error(err, ...) to emit any error about to be returned
//   - ErrorName("name", err, ...) to further specify a name describing the path
//     or cause of the error if it would otherwise be unclear
func tracedFib(n int, scope *tap.TraceScope) (r int) {
	scope = scope.Sub("fib").OpenCall(n)
	defer func() { scope.CloseCall(r) }()

	if n < 0 {
		return 0
	}
	if n < 2 {
		return 1
	}
	return tracedFib(n-1, scope) + tracedFib(n-2, scope)
}

// fib is a wrapper that checks if the fibTracer is active (has any watchers)
// and calls either tracedFib or untracedFib accordingly.
//
// This dual-implementation approach is only one option, you could also:
//   - choose to pass along a nil scope, and nil-check it throughout a single
//     implementation path
//   - use Tracer.Scope to always create a scope object, all of its record emits
//     will simply go nowhere
func fib(n int) int {
	if scope := fibTracer.MaybeScope("wrapper"); scope != nil {
		scope.Open(n)
		r := tracedFib(n, scope)
		scope.CloseCall(r)
		return r
	}
	return untracedFib(n)
}

func main() {
	// this just makes trace ids stable for the test
	tap.ResetTraceID()

	// this one won't be traced since there is no watcher yet
	fmt.Printf("the 4th fib is %v\n", fib(4))

	// this causes fibTracer's output to get printed to stdout; the use here is
	// more complicated than you'd have in a real program to get stable test
	// output.
	rep := report.NewPrintfReporter(
		gwr.DefaultDataSources.Get("/tap/trace/fib"),
		(&timeElider{}).printf)
	if err := rep.Start(); err != nil {
		panic(err)
	}
	defer rep.Stop()

	// this one will be traced since there's now a watcher
	fmt.Printf("the 5th fib is %v\n", fib(5))

	// this flushes and stops all watchers on the reported source; otherwise
	// this function returns too quickly for even one of the emitted trace
	// items to have been printed.
	rep.Source().(source.DrainableSource).Drain()

	// this one won't be traced since Drain deactivated the tracer
	fmt.Printf("the 6th fib is %v\n", fib(6))

}

// timeElider is here just to normalize output for the test
type timeElider struct {
	n int
}

func (te *timeElider) printf(format string, args ...interface{}) (int, error) {
	s := fmt.Sprintf(format, args...)
	if fields := strings.Split(s, " "); len(fields) > 6 {
		fields[2] = fmt.Sprintf("DATE TIME_%d", te.n)
		te.n++
		copy(fields[3:], fields[6:])
		fields = fields[:len(fields)-3]
		s = strings.Join(fields, " ")
	}
	return fmt.Printf(s)
}
Output:

the 4th fib is 5
the 5th fib is 8
/tap/trace/fib: --> DATE TIME_0 [1::1] wrapper: 5
/tap/trace/fib: --> DATE TIME_1 [1:1:2] fib(5)
/tap/trace/fib: --> DATE TIME_2 [1:2:3] fib(4)
/tap/trace/fib: --> DATE TIME_3 [1:3:4] fib(3)
/tap/trace/fib: --> DATE TIME_4 [1:4:5] fib(2)
/tap/trace/fib: --> DATE TIME_5 [1:5:6] fib(1)
/tap/trace/fib: <-- DATE TIME_6 [1:5:6] return 1
/tap/trace/fib: --> DATE TIME_7 [1:5:7] fib(0)
/tap/trace/fib: <-- DATE TIME_8 [1:5:7] return 1
/tap/trace/fib: <-- DATE TIME_9 [1:4:5] return 2
/tap/trace/fib: --> DATE TIME_10 [1:4:8] fib(1)
/tap/trace/fib: <-- DATE TIME_11 [1:4:8] return 1
/tap/trace/fib: <-- DATE TIME_12 [1:3:4] return 3
/tap/trace/fib: --> DATE TIME_13 [1:3:9] fib(2)
/tap/trace/fib: --> DATE TIME_14 [1:9:10] fib(1)
/tap/trace/fib: <-- DATE TIME_15 [1:9:10] return 1
/tap/trace/fib: --> DATE TIME_16 [1:9:11] fib(0)
/tap/trace/fib: <-- DATE TIME_17 [1:9:11] return 1
/tap/trace/fib: <-- DATE TIME_18 [1:3:9] return 2
/tap/trace/fib: <-- DATE TIME_19 [1:2:3] return 5
/tap/trace/fib: --> DATE TIME_20 [1:2:12] fib(3)
/tap/trace/fib: --> DATE TIME_21 [1:12:13] fib(2)
/tap/trace/fib: --> DATE TIME_22 [1:13:14] fib(1)
/tap/trace/fib: <-- DATE TIME_23 [1:13:14] return 1
/tap/trace/fib: --> DATE TIME_24 [1:13:15] fib(0)
/tap/trace/fib: <-- DATE TIME_25 [1:13:15] return 1
/tap/trace/fib: <-- DATE TIME_26 [1:12:13] return 2
/tap/trace/fib: --> DATE TIME_27 [1:12:16] fib(1)
/tap/trace/fib: <-- DATE TIME_28 [1:12:16] return 1
/tap/trace/fib: <-- DATE TIME_29 [1:2:12] return 3
/tap/trace/fib: <-- DATE TIME_30 [1:1:2] return 8
/tap/trace/fib: <-- DATE TIME_31 [1::1] return 8
the 6th fib is 13

func AddNewTracer

func AddNewTracer(name string) *Tracer

AddNewTracer creates a new tracer and adds it to the default gwr sources. It panics if the given name is already defined.

func NewTracer

func NewTracer(name string) *Tracer

NewTracer creates a Tracer with a given name.

func (*Tracer) Active

func (src *Tracer) Active() bool

Active returns true if there any watchers; when not active, all emitted data is dropped. This should be used by call sites to control scope creation.

func (*Tracer) Formats added in v0.6.2

func (src *Tracer) Formats() map[string]source.GenericDataFormat

Formats returns tracer-specific formats.

func (*Tracer) MaybeScope

func (src *Tracer) MaybeScope(name string) *TraceScope

MaybeScope creates a new named scope if the tracer is active; otherwise nil is returned.

func (*Tracer) Name

func (src *Tracer) Name() string

Name returns the gwr source name of the tracer.

func (*Tracer) Scope

func (src *Tracer) Scope(name string) *TraceScope

Scope creates a new named trace scope

func (*Tracer) SetWatcher

func (src *Tracer) SetWatcher(watcher source.GenericDataWatcher)

SetWatcher sets the current watcher.

Jump to

Keyboard shortcuts

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