Documentation ¶
Overview ¶
Package tracing includes high-level tools for instrumenting your application (and library) code using OpenTelemetry and go-logr.
This is done by interconnecting logs and traces; such that critical operations that need to be instrumented start a tracing span using the *TracerBuilder builder. Upon starting a span, the user gives it the context which it is operating in. If the context contains a parent span, the new "child" span and the parent are connected together. To the span various types of metadata can be registered, for example attributes, status information, and potential errors. Spans always need to be ended; most commonly using a defer statement right after creation.
The context given to the *TracerBuilder might carry a TracerProvider to use for exporting span data, e.g. to Jaeger for visualization, or a logr.Logger, to which logs are sent. The context can also carry a LogLevelIncreaser, which correlates log levels to trace depth.
The core idea of interconnecting logs and traces is that when some metadata is registered with a span (for example, it starts, ends, or has attributes or errors registered), information about this is also logged. And upon logging something in a function that is executing within a span, it is also registered with the span.
This means you have dual ways of looking at your application's execution; the "waterfall" visualization of spans in a trace in an OpenTelemetry-compliant UI like Jaeger, or through pluggable logging using logr. Additionally, there is a way to output semi-human-readable YAML data based on the trace information, which is useful when you want to unit-test a function based on its output trace data using a "golden file" in a testdata/ directory.
Let's talk about trace depth and log levels. Consider this example trace (tree of spans):
|A (d=0) | -----> |B (d=1) | |D (d=1) | ----> |C (d=2) |
Span A is at depth 0, as this is a "root span". Inside of span A, span B starts, at depth 1 (span B has exactly 1 parent span). Span B spawns span C at depth 2. Span B ends, but after this span D starts at depth 1, as a child of span A. After D is done executing, span A also ends after a while.
Using the TraceEnabler interface, the user can decide what spans are "enabled" and hence sent to the TracerProvider backend, for example, Jaeger. By default, spans of any depth are sent to the backing TracerProvider, but this is often not desirable in production. The TraceEnabler can decide whether a span should be enabled based on all data in tracing.TracerConfig, which includes e.g. span name, trace depth and so on.
For example, MaxDepthEnabler(maxDepth) allows all traces with depth maxDepth or less, but LoggerEnabler() allows traces as long as the given Logger is enabled. With that, lets take a look at how trace depth correlates with log levels.
The LogLevelIncreaser interface, possibly attached to a context, correlates how much the log level (verboseness) should increase as an effect of the trace depth increasing. The NoLogLevelIncrease() implementation, for example, never increases the log level although the trace depth gets arbitrarily deep. However, that is most often not desired, so there is also a NthLogLevelIncrease(n) implementation that raises the log level every n-th increase of trace depth. For example, given the earlier example, log level (often shortened "v") is increased like follows for NthLogLevelIncrease(2):
|A (d=0, v=0) | -----> |B (d=1, v=0) | |D (d=1, v=0) | ----> |C (d=2, v=1) |
As per how logr.Loggers work, log levels can never be decreased, i.e. become less verbose, they can only be increased. The logr.Logger backend enables log levels up to a given maximum, configured by the user, similar to how MaxDepthEnabler works.
Log output for the example above would looks something like:
{"level":"info(v=0)","logger":"A","msg":"starting span"} {"level":"info(v=0)","logger":"B","msg":"starting span"} {"level":"debug(v=1)","logger":"C","msg":"starting span"} {"level":"debug(v=1)","logger":"C","msg":"ending span"} {"level":"info(v=0)","logger":"B","msg":"ending span"} {"level":"info(v=0)","logger":"D","msg":"starting span"} {"level":"info(v=0)","logger":"D","msg":"ending span"} {"level":"info(v=0)","logger":"A","msg":"ending span"}
This is of course a bit dull example, because only the start/end span events are logged, but it shows the spirit. If span operations like span.Set{Name,Attributes,Status} are executed within the instrumented function, e.g. to record errors, important return values, arbitrary attributes, or a decision, this information will be logged automatically, without a need to call log.Info() separately.
At the same time, all trace data is nicely visualized in Jaeger :). For convenience, a builder-pattern constructor for the zap logger, compliant with the Logger interface is provided through the ZapLogger() function and zaplog sub-directory.
In package traceyaml there are utilities for unit testing the traces. In package filetest there are utilities for using "golden" testdata/ files for comparing actual output of loggers, tracers, and general writers against expected output. Both the TracerProviderBuilder and zaplog.Builder support deterministic output for unit tests and examples.
The philosophy behind this package is that instrumentable code (functions, structs, and so on), should use the TracerBuilder to start spans; and will from there get a Span and Logger implementation to use. It is safe for libraries used by other consumers to use the TracerBuilder as well, if the user didn't want or request tracing nor logging, all calls to the Span and Logger will be discarded!
The application owner wanting to (maybe conditionally) enable tracing and logging, creates "backend" implementations of TracerProvider and Logger, e.g. using the TracerProviderBuilder and/or zaplog.Builder. These backends control where the telemetry data is sent, and how much of it is enabled. These "backend" implementations are either attached specifically to a context, or registered globally. Using this setup, telemetry can be enabled even on the fly, using e.g. a HTTP endpoint for debugging a production system.
Have fun using this library and happy tracing!
Example (LoggingAndYAMLTrace) ¶
package main import ( "bytes" "context" "errors" "fmt" golog "log" "github.com/luxas/deklarative/tracing" "go.opentelemetry.io/otel/attribute" ) func main() { // This example shows how tracing and logging is unified from the perspective // of instrumented functions myInstrumentedFunction and childInstrumentedFunction. // // When a function is traced using the tracer in this package, trace data is // also logged. When data is logged using the logger, the similar data is also // registered with the trace. // Make a TracerProvider writing YAML about what's happening to // the yamlTrace buffer. var yamlTrace bytes.Buffer tp, err := tracing.Provider().TestYAMLTo(&yamlTrace).Build() if err != nil { golog.Fatal(err) } // Make an example logger logging to os.Stdout directly. fmt.Println("Log representation:") log := tracing.ZapLogger().Example().LogUpto(1).Build() // Specifically use the given Logger and TracerProvider when performing this // trace operation by crafting a dedicated context. ctx := tracing.Context().WithLogger(log).WithTracerProvider(tp).Build() // Using the context that points to the wanted Logger and TracerProvider, // start instrumenting a function. myInstrumentedFunc might be a function // we at this point control, but it could also be a function we do not // control, e.g. coming from an external library. err = myInstrumentedFunc(ctx) log.Info("error is sampleErr", "is-sampleErr", errors.Is(err, errSample)) // Shutdown the TracerProvider, and output the YAML it yielded to os.Stdout. if err := tp.Shutdown(ctx); err != nil { golog.Fatal(err) } fmt.Printf("\nYAML trace representation:\n%s", yamlTrace.String()) } var errSample = errors.New("sample error") func myInstrumentedFunc(ctx context.Context) (retErr error) { // If an error is returned, capture retErr such that the error is // automatically logged and traced. ctx, span, log := tracing.Tracer().Capture(&retErr).Trace(ctx, "myInstrumentedFunc") // Always remember to end the span when the function or operation is done! defer span.End() // Try logging with different verbosities log.Info("normal verbosity!") // Key-value pairs given to the logger will also be added to the span, // with the "log-attr-" prefix, i.e. there is "log-attr-hello": "from the other side" // in the span. log.V(1).Info("found a message", "hello", "from the other side") // Run the child function twice. Notice how, when the trace depth increases, // the log level also automatically increases (myInstrumentedFunc logged the // span start event with v=0, but child logs this with v=1). for i := 0; i < 2; i++ { child(ctx, i) } // Return an error for demonstration of the capture feature. No need to use // named returns here; retErr is caught in the defer span.End() anyways! return fmt.Errorf("unexpected: %w", errSample) } func child(ctx context.Context, i int) { // Start a child span. As there is an ongoing trace registered in the context, // a child span will be created automatically. _, span := tracing.Tracer().Start(ctx, fmt.Sprintf("child-%d", i)) defer span.End() // Register an event and an attribute. Notice these also showing up in the log. span.AddEvent("DoSTH") span.SetAttributes(attribute.Int("i", i)) }
Output: Log representation: {"level":"info(v=0)","logger":"myInstrumentedFunc","msg":"starting span"} {"level":"info(v=0)","logger":"myInstrumentedFunc","msg":"normal verbosity!"} {"level":"debug(v=1)","logger":"myInstrumentedFunc","msg":"found a message","hello":"from the other side"} {"level":"debug(v=1)","logger":"child-0","msg":"starting span"} {"level":"debug(v=1)","logger":"child-0","msg":"span event","span-event":"DoSTH"} {"level":"debug(v=1)","logger":"child-0","msg":"span attribute change","span-attr-i":0} {"level":"debug(v=1)","logger":"child-0","msg":"ending span"} {"level":"debug(v=1)","logger":"child-1","msg":"starting span"} {"level":"debug(v=1)","logger":"child-1","msg":"span event","span-event":"DoSTH"} {"level":"debug(v=1)","logger":"child-1","msg":"span attribute change","span-attr-i":1} {"level":"debug(v=1)","logger":"child-1","msg":"ending span"} {"level":"error","logger":"myInstrumentedFunc","msg":"span error","error":"unexpected: sample error"} {"level":"info(v=0)","logger":"myInstrumentedFunc","msg":"ending span"} {"level":"info(v=0)","msg":"error is sampleErr","is-sampleErr":true} YAML trace representation: # myInstrumentedFunc - spanName: myInstrumentedFunc attributes: log-attr-hello: from the other side errors: - error: 'unexpected: sample error' children: - spanName: child-0 attributes: i: 0 events: - name: DoSTH - spanName: child-1 attributes: i: 1 events: - name: DoSTH
Index ¶
- Constants
- func DefaultErrRegisterFunc(err error, span Span, log Logger)
- func SetAcquireLoggerFunc(fn AcquireLoggerFunc)
- func SetGlobalLogger(log Logger)
- func SetGlobalTracerProvider(tp TracerProvider)
- func ZapLogger() *zaplog.Builder
- type AcquireLoggerFunc
- type CompositeTracerProviderFunc
- type ContextBuilder
- func (b *ContextBuilder) Build() context.Context
- func (b *ContextBuilder) From(ctx context.Context) *ContextBuilder
- func (b *ContextBuilder) WithLogLevelIncreaser(lli LogLevelIncreaser) *ContextBuilder
- func (b *ContextBuilder) WithLogger(log Logger) *ContextBuilder
- func (b *ContextBuilder) WithTracerProvider(tp TracerProvider) *ContextBuilder
- type Depth
- type ErrRegisterFunc
- type LogLevelIncreaser
- type Logger
- type Span
- type TraceEnabler
- type TracerBuilder
- func (b *TracerBuilder) Capture(err *error) *TracerBuilder
- func (b *TracerBuilder) ErrRegisterFunc(fn ErrRegisterFunc) *TracerBuilder
- func (b *TracerBuilder) Start(ctx context.Context, fnName string, opts ...trace.SpanStartOption) (context.Context, Span)
- func (b *TracerBuilder) Trace(ctx context.Context, fnName string, opts ...trace.SpanStartOption) (context.Context, Span, Logger)
- func (b *TracerBuilder) WithActor(actor interface{}) *TracerBuilder
- func (b *TracerBuilder) WithAttributes(attrs ...attribute.KeyValue) *TracerBuilder
- type TracerConfig
- type TracerNamed
- type TracerProvider
- type TracerProviderBuilder
- func (b *TracerProviderBuilder) Build() (TracerProvider, error)
- func (b *TracerProviderBuilder) Composite(fn CompositeTracerProviderFunc) *TracerProviderBuilder
- func (b *TracerProviderBuilder) DeterministicIDs(seed int64) *TracerProviderBuilder
- func (b *TracerProviderBuilder) InstallGlobally() error
- func (b *TracerProviderBuilder) Synchronous() *TracerProviderBuilder
- func (b *TracerProviderBuilder) TestJSON(g *filetest.Tester) *TracerProviderBuilder
- func (b *TracerProviderBuilder) TestYAML(g *filetest.Tester) *TracerProviderBuilder
- func (b *TracerProviderBuilder) TestYAMLTo(w io.Writer) *TracerProviderBuilder
- func (b *TracerProviderBuilder) TraceUpto(depth Depth) *TracerProviderBuilder
- func (b *TracerProviderBuilder) TraceUptoLogger() *TracerProviderBuilder
- func (b *TracerProviderBuilder) WithAttributes(attrs ...attribute.KeyValue) *TracerProviderBuilder
- func (b *TracerProviderBuilder) WithInsecureJaegerExporter(addr string, opts ...jaeger.CollectorEndpointOption) *TracerProviderBuilder
- func (b *TracerProviderBuilder) WithInsecureOTelExporter(ctx context.Context, addr string, opts ...otlptracegrpc.Option) *TracerProviderBuilder
- func (b *TracerProviderBuilder) WithOptions(opts ...tracesdk.TracerProviderOption) *TracerProviderBuilder
- func (b *TracerProviderBuilder) WithStdoutExporter(opts ...stdouttrace.Option) *TracerProviderBuilder
- func (b *TracerProviderBuilder) WithTraceEnabler(te TraceEnabler) *TracerProviderBuilder
Examples ¶
Constants ¶
const ( // SpanAttributePrefix is the prefix used when logging an attribute registered // with a Span. SpanAttributePrefix = "span-attr-" // LogAttributePrefix is the prefix used when registering a logged attribute // with a Span. LogAttributePrefix = "log-attr-" )
Variables ¶
This section is empty.
Functions ¶
func DefaultErrRegisterFunc ¶
DefaultErrRegisterFunc registers the error with the span using span.RecordError(err) if the error is non-nil.
func SetAcquireLoggerFunc ¶
func SetAcquireLoggerFunc(fn AcquireLoggerFunc)
SetAcquireLoggerFunc sets the globally-registered AcquireLoggerFunc in this package. For example, fn can be DefaultAcquireLoggerFunc (the default) or "sigs.k8s.io/controller-runtime/pkg/log".FromContext.
Example ¶
package main import ( "context" "fmt" golog "log" "github.com/go-logr/logr" "github.com/go-logr/stdr" "github.com/luxas/deklarative/tracing" "github.com/luxas/deklarative/tracing/filetest" ) func myAcquire(ctx context.Context) tracing.Logger { // If there is a logger in the context, use it if log := logr.FromContext(ctx); log != nil { return log } // If not, default to the stdlib logging library with stdr wrapping // it such that it is logr-compliant. Log up to verbosity 1. stdr.SetVerbosity(1) return stdr.New(golog.New(filetest.ExampleStdout, "FooLogger: ", 0)) } func main() { // This is an example of how to use this framework with no TraceProvider // at all. // Use myAcquire to resolve a logger from the context tracing.SetAcquireLoggerFunc(myAcquire) // Construct a zap logger, and a context using it. realLogger := tracing.ZapLogger().Example().LogUpto(1).Build() ctxWithLog := tracing.Context().WithLogger(realLogger).Build() // Call sampleInstrumentedFunc with the zap logger in the context. fmt.Println("realLogger (zapr) is used with ctxWithLog:") sampleInstrumentedFunc(ctxWithLog, "ctxWithLog") // Call sampleInstrumentedFunc with no logger in the context. fmt.Println("myAcquire defaults to stdr if there's no logger in the context:") sampleInstrumentedFunc(context.Background(), "context.Background") } func sampleInstrumentedFunc(ctx context.Context, contextName string) { _, span, log := tracing.Tracer().Trace(ctx, "sampleInstrumentedFunc") defer span.End() log.V(1).Info("got context name", "context-name", contextName) }
Output: realLogger (zapr) is used with ctxWithLog: {"level":"info(v=0)","logger":"sampleInstrumentedFunc","msg":"starting span"} {"level":"debug(v=1)","logger":"sampleInstrumentedFunc","msg":"got context name","context-name":"ctxWithLog"} {"level":"info(v=0)","logger":"sampleInstrumentedFunc","msg":"ending span"} myAcquire defaults to stdr if there's no logger in the context: FooLogger: sampleInstrumentedFunc "level"=0 "msg"="starting span" FooLogger: sampleInstrumentedFunc "level"=1 "msg"="got context name" "context-name"="context.Background" FooLogger: sampleInstrumentedFunc "level"=0 "msg"="ending span"
func SetGlobalLogger ¶
func SetGlobalLogger(log Logger)
SetGlobalLogger sets the globally-registered Logger in this package.
Example ¶
package main import ( "context" "fmt" golog "log" "github.com/go-logr/logr" "github.com/go-logr/stdr" "github.com/luxas/deklarative/tracing" "github.com/luxas/deklarative/tracing/filetest" ) func main() { // This is an example of how to use this framework with no TraceProvider // at all. // If not, default to the stdlib logging library with stdr wrapping // it such that it is logr-compliant. Log up to verbosity 1. stdr.SetVerbosity(1) log := stdr.New(golog.New(filetest.ExampleStdout, "FooLogger: ", 0)) tracing.SetGlobalLogger(log) // Construct a zap logger, and a context using it. realLogger := tracing.ZapLogger().Example().LogUpto(1).Build() ctxWithLog := tracing.Context().WithLogger(realLogger).Build() // Call sampleInstrumentedFunc with the zap logger in the context. fmt.Println("realLogger (zapr) is used with ctxWithLog:") sampleInstrumentedFunc2(ctxWithLog, "ctxWithLog") // Call sampleInstrumentedFunc with no logger in the context. fmt.Println("Use the global stdr logger if there's no logger in the context:") sampleInstrumentedFunc2(context.Background(), "context.Background") tracing.SetGlobalLogger(logr.Discard()) } func sampleInstrumentedFunc2(ctx context.Context, contextName string) { _, span, log := tracing.Tracer().Trace(ctx, "sampleInstrumentedFunc2") defer span.End() log.V(1).Info("got context name", "context-name", contextName) }
Output: realLogger (zapr) is used with ctxWithLog: {"level":"info(v=0)","logger":"sampleInstrumentedFunc2","msg":"starting span"} {"level":"debug(v=1)","logger":"sampleInstrumentedFunc2","msg":"got context name","context-name":"ctxWithLog"} {"level":"info(v=0)","logger":"sampleInstrumentedFunc2","msg":"ending span"} Use the global stdr logger if there's no logger in the context: FooLogger: sampleInstrumentedFunc2 "level"=0 "msg"="starting span" FooLogger: sampleInstrumentedFunc2 "level"=1 "msg"="got context name" "context-name"="context.Background" FooLogger: sampleInstrumentedFunc2 "level"=0 "msg"="ending span"
func SetGlobalTracerProvider ¶
func SetGlobalTracerProvider(tp TracerProvider)
SetGlobalTracerProvider sets globally-registered TracerProvider to tp. This is a shorthand for otel.SetTracerProvider(tp).
Types ¶
type AcquireLoggerFunc ¶
AcquireLoggerFunc represents a function that can resolve a Logger from the given context. Two common implementations are DefaultAcquireLoggerFunc and "sigs.k8s.io/controller-runtime/pkg/log".FromContext.
type CompositeTracerProviderFunc ¶
type CompositeTracerProviderFunc func(TracerProvider) trace.TracerProvider
CompositeTracerProviderFunc builds a composite TracerProvider from the given SDKTracerProvider. If the returned TracerProvider implements SDKTracerProvider, it'll be used as-is. If the returned TracerProvider doesn't implement Shutdown or ForceFlush, the "parent" SDKTracerProvider will be used.
type ContextBuilder ¶
type ContextBuilder struct {
// contains filtered or unexported fields
}
ContextBuilder is a builder-pattern constructor for a context.Context, that possibly includes a TracerProvider, Logger and/or LogLevelIncreaser.
func (*ContextBuilder) Build ¶
func (b *ContextBuilder) Build() context.Context
Build builds the context.
func (*ContextBuilder) From ¶
func (b *ContextBuilder) From(ctx context.Context) *ContextBuilder
From sets the "base context" to start applying context.WithValue operations to. By default this is context.Background().
func (*ContextBuilder) WithLogLevelIncreaser ¶
func (b *ContextBuilder) WithLogLevelIncreaser(lli LogLevelIncreaser) *ContextBuilder
WithLogLevelIncreaser registers a LogLevelIncreaser with the context.
func (*ContextBuilder) WithLogger ¶
func (b *ContextBuilder) WithLogger(log Logger) *ContextBuilder
WithLogger registers a Logger with the context.
func (*ContextBuilder) WithTracerProvider ¶
func (b *ContextBuilder) WithTracerProvider(tp TracerProvider) *ContextBuilder
WithTracerProvider registers a TracerProvider with the context.
type Depth ¶
type Depth uint64
Depth means "how many parent spans do I have?" for a Span. If this is a root span, depth is zero.
type ErrRegisterFunc ¶
ErrRegisterFunc can register the error captured at the end of a function using TracerBuilder.Capture(*error) with the span.
Depending on the error, one might want to call span.RecordError, span.AddEvent, or just log the error.
type LogLevelIncreaser ¶
type LogLevelIncreaser interface {
GetVIncrease(ctx context.Context, cfg *TracerConfig) int
}
LogLevelIncreaser controls how much the verbosity of a Logger should be bumped for a given trace configuration before starting the trace. This is run for each started trace.
func NoLogLevelIncrease ¶
func NoLogLevelIncrease() LogLevelIncreaser
NoLogLevelIncrease returns a LogLevelIncreaser that never bumps the verbosity, regardless of how deep traces there are.
func NthLogLevelIncrease ¶
func NthLogLevelIncrease(n uint64) LogLevelIncreaser
NthLogLevelIncrease returns a LogLevelIncreaser that increases the verbosity of the logger once every n traces of depth.
The default LogLevelIncreaser is NthLogLevelIncrease(1), which essentially means log = log.V(1) for each child trace.
type Logger ¶
Logger is a symbolic link to logr.Logger.
func DefaultAcquireLoggerFunc ¶
DefaultAcquireLoggerFunc is the default AcquireLoggerFunc implementation. It tries to resolve a logger from the given context using logr.FromContext, but if no Logger is registered, it defaults to GetGlobalLogger().
func GetGlobalLogger ¶
func GetGlobalLogger() Logger
GetGlobalLogger gets the globally-registered Logger in this package. The default Logger implementation is logr.Discard().
func LoggerFromContext ¶
LoggerFromContext executes the globally-registered AcquireLoggerFunc in this package to resolve a Logger from the context. By default, DefaultAcquireLoggerFunc is used which uses the Logger in the context, if any, or falls back to GetGlobalLogger().
If you want to customize this behavior, run SetAcquireLoggerFunc().
type Span ¶
Span is a symbolic link to trace.Span.
func SpanFromContext ¶
SpanFromContext retrieves the currently-executing Span stored in the context, if any, or a no-op Span.
type TraceEnabler ¶
type TraceEnabler interface {
Enabled(ctx context.Context, cfg *TracerConfig) bool
}
TraceEnabler controls if a trace with a given config should be started or not. If Enabled returns false, a no-op span will be returned from TracerBuilder.Start() and TracerBuilder.Trace(). The TraceEnabler is checked after the log level has been increased.
func LoggerEnabler ¶
func LoggerEnabler() TraceEnabler
LoggerEnabler is a TraceEnabler that allows all spans as long as the Logger from the context is enabled. If the Logger is logr.Discard, any trace depth is allowed. This is useful when the Logger is the true source of verboseness allowance.
func MaxDepthEnabler ¶
func MaxDepthEnabler(maxDepth Depth) TraceEnabler
MaxDepthEnabler is a TraceEnabler that allows all spans of trace depth below and equal to maxDepth. This is similar to how logr.Loggers are enabled upto a given log level.
type TracerBuilder ¶
type TracerBuilder struct {
// contains filtered or unexported fields
}
TracerBuilder implements trace.Tracer.
func (*TracerBuilder) Capture ¶
func (b *TracerBuilder) Capture(err *error) *TracerBuilder
Capture is used to capture a named error return value from the function this TracerBuilder is executing in. It is possible to "expose" a return value like "func foo() (retErr error) {}" although named returns are never used.
When the deferred span.End() is called at the end of the function, the ErrRegisterFunc will be run for whatever error value this error pointer points to, including if the error value is nil.
This, in combination with ErrRegisterFunc allows for seamless error handling for traced functions; information about the error will propagate both to the Span and the Logger automatically.
A call to this function overwrites any previous value.
func (*TracerBuilder) ErrRegisterFunc ¶
func (b *TracerBuilder) ErrRegisterFunc(fn ErrRegisterFunc) *TracerBuilder
ErrRegisterFunc allows configuring what ErrRegisterFunc shall be run when the traced function ends, if Capture has been called.
By default this is DefaultErrRegisterFunc.
A call to this function overwrites any previous value.
func (*TracerBuilder) Start ¶
func (b *TracerBuilder) Start(ctx context.Context, fnName string, opts ...trace.SpanStartOption) (context.Context, Span)
Start implements trace.Tracer. See Trace for more information about how this trace.Tracer works. The only difference between this function and Trace is the signature; Trace also returns a Logger.
func (*TracerBuilder) Trace ¶
func (b *TracerBuilder) Trace(ctx context.Context, fnName string, opts ...trace.SpanStartOption) (context.Context, Span, Logger)
Trace creates a new Span, derived from the given context, with a Span and Logger name that is a combination of the string representation of the actor (described in WithActor) and fnName.
If WithLogger isn't specified, the logger is retrieved using LoggerFromContext.
If the Logger is logr.Discard(), no logs are output. However, if a Logger is specified, no tracing or logging will take place if it is disabled (in other words, if this span is "too verbose") for the Logger configuration.
If opts contain any attributes, these will be logged when the span starts.
If WithTracerProvider isn't specified, TracerProviderFromContext is used to get the TracerProvider.
If the Logger is not logr.Discard(), updates registered with the span are automatically logged with the SpanAttributePrefix prefix. And vice versa, keysAndValues given to the returned Logger's Info or Error method are registered with the Span with the LogAttributePrefix prefix.
If Capture and possibly ErrRegisterFunc are set, the error return value will be automatically registered to the Span.
func (*TracerBuilder) WithActor ¶
func (b *TracerBuilder) WithActor(actor interface{}) *TracerBuilder
WithActor registers an "actor" for the given function that is instrumented.
If the function instrumented is called e.g. Read and the struct implementing Read is *FooReader, then *FooReader is the actor.
In order to make the span and logger name "*FooReader.Read", and not just an ambiguous "Read", pass the *FooReader as actor here.
If the actor implements TracerNamed, the return value of that will be returned. If actor is a string, that name is used. If actor is a os.Std{in,out,err} or io.Discard, those human-friendly names are used. Otherwise, the type name is resolved by fmt.Sprintf("%T", actor), which automatically registers the package and type name.
func (*TracerBuilder) WithAttributes ¶
func (b *TracerBuilder) WithAttributes(attrs ...attribute.KeyValue) *TracerBuilder
WithAttributes registers attributes that are added as trace.SpanStartOptions automatically, but also logged in the beginning using the logger, if enabled.
A call to this function appends to the list of previous values.
type TracerConfig ¶
type TracerConfig struct { *trace.TracerConfig *trace.SpanConfig TracerName string FuncName string Provider TracerProvider Depth Depth Logger Logger LogLevelIncreaser LogLevelIncreaser }
TracerConfig is a collection of all the data that is present before starting a span in TracerBuilder.Start() and TracerBuilder.Trace(). This information can be used to make policy decisions in for example TraceEnabler or LogLevelIncreaser.
func (*TracerConfig) SpanName ¶
func (tc *TracerConfig) SpanName() string
SpanName combines the TracerName and FuncName to yield a span name.
type TracerNamed ¶
type TracerNamed interface {
TracerName() string
}
TracerNamed is an interface that allows types to customize their name shown in traces and logs.
type TracerProvider ¶
type TracerProvider interface { trace.TracerProvider Shutdown(ctx context.Context) error ForceFlush(ctx context.Context) error // IsNoop returns whether this is a no-op TracerProvider that does nothing. IsNoop() bool // TraceEnabler lets the provider control what spans shall be started. TraceEnabler }
TracerProvider represents a TracerProvider that is generated from the OpenTelemetry SDK and hence can be force-flushed and shutdown (which in both cases flushes all async, batched traces before stopping). The TracerProvider also controls which traces shall be started and which should not.
func GetGlobalTracerProvider ¶
func GetGlobalTracerProvider() TracerProvider
GetGlobalTracerProvider returns the global TracerProvider registered. The default TracerProvider is trace.NewNoopTracerProvider(). This is a shorthand for otel.GetTracerProvider().
func NoopTracerProvider ¶
func NoopTracerProvider() TracerProvider
NoopTracerProvider returns a TracerProvider that returns IsNoop == true, and creates spans that do nothing.
func TracerProviderFromContext ¶
func TracerProviderFromContext(ctx context.Context) TracerProvider
TracerProviderFromContext retrieves the TracerProvider from the context. If the current Span's TracerProvider() is not the no-op TracerProvider returned by trace.NewNoopTracerProvider(), it is used, or otherwise the global from GetGlobalTracerProvider().
type TracerProviderBuilder ¶
type TracerProviderBuilder struct {
// contains filtered or unexported fields
}
TracerProviderBuilder is an opinionated builder-pattern constructor for a TracerProvider that can export spans to stdout, the Jaeger HTTP API or an OpenTelemetry Collector gRPC proxy.
Example ¶
package main import ( "context" "fmt" "os" "github.com/luxas/deklarative/tracing" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) func requireNonNil(err error) { if err != nil { panic(err) } } func main() { // Create a new TracerProvider that logs trace data to os.Stdout, and // is registered globally. It won't trace deeper than 2 child layers. err := tracing.Provider(). TestYAMLTo(os.Stdout). TraceUpto(2). InstallGlobally() requireNonNil(err) // Start from a root, background context, that will be given to instrumented // functions from this example. ctx := context.Background() // Remember to shut down the global tracing provider in the end. defer requireNonNil(tracing.GetGlobalTracerProvider().Shutdown(ctx)) // Create some operator struct with some important data. f := Foo{importantData: "very important!"} // Execute an instrumented operation on this struct. _, _ = f.Operation(ctx) } type Foo struct { importantData string } func (f *Foo) Operation(ctx context.Context) (retStr string, retErr error) { // Start tracing; calculate the tracer name automatically by providing a // reference to the "actor" *Foo. This will fmt.Sprintf("%T") leading to // the prefix being "*tracing_test.Foo". // // Also register important data stored in the struct in an attribute. // // At the end of the function, when the span ends, automatically register // the return error with the trace, if non-nil. ctx, span := tracing.Tracer(). WithActor(f). WithAttributes(attribute.String("important-data", f.importantData)). Capture(&retErr). Start(ctx, "Operation") // Always remember to end the span defer span.End() // Register the return value, the string, as an attribute in the span as well. defer func() { span.SetAttributes(attribute.String("result", retStr)) }() // Start a "child" trace using function doOperation, and conditionally return // the string. if err := doOperation(ctx, 1); err != nil { return "my error value", fmt.Errorf("operation got unexpected error: %w", err) } return "my normal value", nil } func doOperation(ctx context.Context, i int64) (retErr error) { // Just to show off that you don't have to inherit the parent span; it's // possible to also create a new "root" span at any point. var startOpts []trace.SpanStartOption if i == 4 { startOpts = append(startOpts, trace.WithNewRoot()) } // Start the new span, and automatically register the error, if any. ctx, span := tracing.Tracer(). Capture(&retErr). Start(ctx, fmt.Sprintf("doOperation-%d", i), startOpts...) defer span.End() span.SetAttributes(attribute.Int64("i", i)) // Just to show off trace depth here, recursively call itself and // increase i until it is 5, and then return an error. This means that // the error returned at i==5 will be returned for all i < 5, too. // // This, in combination with the trace depth configured above with // TraceUpto(2), means that doOperation-3, although executed, won't // be shown in the output, because that is at depth 3. // // However, as doOperation-4 is a root span, it is at depth 0 and hence // comfortably within the allowed range. if i == 5 { return fmt.Errorf("oh no") //nolint:goerr113 } return doOperation(ctx, i+1) }
Output: # doOperation-4 - spanName: doOperation-4 attributes: i: 4 errors: - error: oh no startConfig: newRoot: true children: - spanName: doOperation-5 attributes: i: 5 errors: - error: oh no # *tracing_test.Foo.Operation - spanName: '*tracing_test.Foo.Operation' attributes: result: my error value errors: - error: 'operation got unexpected error: oh no' startConfig: attributes: important-data: very important! children: - spanName: doOperation-1 attributes: i: 1 errors: - error: oh no children: - spanName: doOperation-2 attributes: i: 2 errors: - error: oh no
func Provider ¶
func Provider() *TracerProviderBuilder
Provider returns a new *TracerProviderBuilder instance.
func (*TracerProviderBuilder) Build ¶
func (b *TracerProviderBuilder) Build() (TracerProvider, error)
Build builds the SDKTracerProvider.
func (*TracerProviderBuilder) Composite ¶
func (b *TracerProviderBuilder) Composite(fn CompositeTracerProviderFunc) *TracerProviderBuilder
Composite builds a composite TracerProvider from the resulting SDKTracerProvider when Build() is called. If the returned TracerProvider implements SDKTracerProvider, it'll be used as-is. If the returned TracerProvider doesn't implement Shutdown or ForceFlush, the "parent" SDKTracerProvider will be used. It is possible to build a chain of composite TracerProviders by calling this function repeatedly.
func (*TracerProviderBuilder) DeterministicIDs ¶
func (b *TracerProviderBuilder) DeterministicIDs(seed int64) *TracerProviderBuilder
DeterministicIDs enables deterministic trace and span IDs. Useful for unit tests. DO NOT use in production.
func (*TracerProviderBuilder) InstallGlobally ¶
func (b *TracerProviderBuilder) InstallGlobally() error
InstallGlobally builds the TracerProvider and registers it globally using otel.SetTracerProvider(tp).
func (*TracerProviderBuilder) Synchronous ¶
func (b *TracerProviderBuilder) Synchronous() *TracerProviderBuilder
Synchronous allows configuring whether the exporters should export in synchronous mode, which is useful for avoiding flakes in unit tests. The default mode is batching. DO NOT use in production.
func (*TracerProviderBuilder) TestJSON ¶
func (b *TracerProviderBuilder) TestJSON(g *filetest.Tester) *TracerProviderBuilder
TestJSON enables Synchronous mode, exports using WithStdoutExporter without timestamps to a filetest.Tester file under testdata/ with the current test name and a ".json" suffix. Deterministic IDs are used with a static seed.
This is useful for unit tests.
func (*TracerProviderBuilder) TestYAML ¶
func (b *TracerProviderBuilder) TestYAML(g *filetest.Tester) *TracerProviderBuilder
TestYAML is a shorthand for TestYAMLTo, that writes to a testdata/ file with the name of the test + the ".yaml" suffix.
This is useful for unit tests.
func (*TracerProviderBuilder) TestYAMLTo ¶
func (b *TracerProviderBuilder) TestYAMLTo(w io.Writer) *TracerProviderBuilder
TestYAMLTo builds a composite TracerProvider that uses traceyaml.New() to write trace testing YAML to writer w. See traceyaml.New for more information about how it works.
This is useful for unit tests.
func (*TracerProviderBuilder) TraceUpto ¶
func (b *TracerProviderBuilder) TraceUpto(depth Depth) *TracerProviderBuilder
TraceUpto includes traces with depth less than or equal to the given depth argument.
func (*TracerProviderBuilder) TraceUptoLogger ¶
func (b *TracerProviderBuilder) TraceUptoLogger() *TracerProviderBuilder
TraceUptoLogger includes trace data as long as the logger is enabled. If a logger is not provided in the context (that is, it is logr.Discard), then there's no depth limit for the tracing.
func (*TracerProviderBuilder) WithAttributes ¶
func (b *TracerProviderBuilder) WithAttributes(attrs ...attribute.KeyValue) *TracerProviderBuilder
WithAttributes allows registering more default attributes for traces created by this TracerProvider. By default semantic conventions of version v1.4.0 are used, with "service.name" => "libgitops".
func (*TracerProviderBuilder) WithInsecureJaegerExporter ¶
func (b *TracerProviderBuilder) WithInsecureJaegerExporter(addr string, opts ...jaeger.CollectorEndpointOption) *TracerProviderBuilder
WithInsecureJaegerExporter registers an exporter to Jaeger using Jaeger's own HTTP API. The default address is "http://localhost:14268/api/traces" if addr is left empty. Additional options can be supplied that can override the default behavior.
func (*TracerProviderBuilder) WithInsecureOTelExporter ¶
func (b *TracerProviderBuilder) WithInsecureOTelExporter(ctx context.Context, addr string, opts ...otlptracegrpc.Option) *TracerProviderBuilder
WithInsecureOTelExporter registers an exporter to an OpenTelemetry Collector on the given address, which defaults to "localhost:55680" if addr is empty. The OpenTelemetry Collector speaks gRPC, hence, don't add any "http(s)://" prefix to addr. The OpenTelemetry Collector is just a proxy, it in turn can forward for example traces to Jaeger and metrics to Prometheus. Additional options can be supplied that can override the default behavior.
func (*TracerProviderBuilder) WithOptions ¶
func (b *TracerProviderBuilder) WithOptions(opts ...tracesdk.TracerProviderOption) *TracerProviderBuilder
WithOptions allows configuring the TracerProvider in various ways, for example tracesdk.WithSpanProcessor(sp) or tracesdk.WithIDGenerator().
func (*TracerProviderBuilder) WithStdoutExporter ¶
func (b *TracerProviderBuilder) WithStdoutExporter(opts ...stdouttrace.Option) *TracerProviderBuilder
WithStdoutExporter exports pretty-formatted telemetry data to os.Stdout, or another writer if stdouttrace.WithWriter(w) is supplied as an option. Note that stdouttrace.WithoutTimestamps() doesn't work due to an upstream bug in OpenTelemetry. TODO: Fix that issue upstream.
func (*TracerProviderBuilder) WithTraceEnabler ¶
func (b *TracerProviderBuilder) WithTraceEnabler(te TraceEnabler) *TracerProviderBuilder
WithTraceEnabler registers a TraceEnabler that determines if tracing shall be enabled for a given TracerConfig.
Source Files ¶
Directories ¶
Path | Synopsis |
---|---|
Package filetest helps in verifying that content written to arbitrary io.Writers match some expected content in testdata files.
|
Package filetest helps in verifying that content written to arbitrary io.Writers match some expected content in testdata files. |
Package traceyaml provides a means to unit test a trace flow, using a YAML file structure that is representative and as close to human-readable as it gets.
|
Package traceyaml provides a means to unit test a trace flow, using a YAML file structure that is representative and as close to human-readable as it gets. |
Code generated by counterfeiter.
|
Code generated by counterfeiter. |
Package zaplog provides a builder-pattern constructor for creating a logr.Logger implementation using Zap with some commonly-good defaults.
|
Package zaplog provides a builder-pattern constructor for creating a logr.Logger implementation using Zap with some commonly-good defaults. |