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 ¶
- Variables
- func Active() bool
- func ResetTraceID()
- type Emitter
- func (em *Emitter) Active() bool
- func (em *Emitter) Emit(items ...interface{}) bool
- func (em *Emitter) EmitBatch(items []interface{}) bool
- func (em *Emitter) Formats() map[string]source.GenericDataFormat
- func (em *Emitter) Name() string
- func (em *Emitter) SetWatcher(watcher source.GenericDataWatcher)
- func (em *Emitter) TextTemplate() *template.Template
- type TraceScope
- func (sc *TraceScope) Active() bool
- func (sc *TraceScope) BeginTime() time.Time
- func (sc *TraceScope) Close(args ...interface{}) *TraceScope
- func (sc *TraceScope) CloseCall(rets ...interface{}) *TraceScope
- func (sc *TraceScope) EndTime() time.Time
- func (sc *TraceScope) Error(err error, args ...interface{}) *TraceScope
- func (sc *TraceScope) ErrorName(name string, err error, args ...interface{}) *TraceScope
- func (sc *TraceScope) Info(args ...interface{}) *TraceScope
- func (sc *TraceScope) Open(args ...interface{}) *TraceScope
- func (sc *TraceScope) OpenCall(args ...interface{}) *TraceScope
- func (sc *TraceScope) Parent() *TraceScope
- func (sc *TraceScope) Root() *TraceScope
- func (sc *TraceScope) Sub(name string) *TraceScope
- type Tracer
- func (src *Tracer) Active() bool
- func (src *Tracer) Formats() map[string]source.GenericDataFormat
- func (src *Tracer) MaybeScope(name string) *TraceScope
- func (src *Tracer) Name() string
- func (src *Tracer) Scope(name string) *TraceScope
- func (src *Tracer) SetWatcher(watcher source.GenericDataWatcher)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var DefaultTracer = Tracer{ // contains filtered or unexported fields }
DefaultTracer is available for easy scope logging without needing to create a separate tracer.
Functions ¶
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 ¶
AddEmitter creates an emitter source and adds it to the default gwr sources.
func NewEmitter ¶
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) Emit ¶
Emit emits item(s) to any active watchers. Returns true if the watcher is (still) active.
func (*Emitter) EmitBatch ¶
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 ¶
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 ¶
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 (*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 ¶
AddNewTracer creates a new tracer and adds it to the default gwr sources. It panics if the given name is already defined.
func (*Tracer) Active ¶
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) 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.