tracing

package module
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Aug 25, 2021 License: Apache-2.0 Imports: 21 Imported by: 0

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

Examples

Constants

View Source
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

func DefaultErrRegisterFunc(err error, span Span, log Logger)

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).

func ZapLogger

func ZapLogger() *zaplog.Builder

ZapLogger is a shorthand for zaplog.NewZap().

Refer to the zaplog package for usage details and examples.

Types

type AcquireLoggerFunc

type AcquireLoggerFunc func(context.Context) Logger

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 Context

func Context() *ContextBuilder

Context returns a new *ContextBuilder.

func (*ContextBuilder) Build

func (b *ContextBuilder) Build() context.Context

Build builds the context.

func (*ContextBuilder) From

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

type ErrRegisterFunc func(err error, span Span, log Logger)

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

type Logger = logr.Logger

Logger is a symbolic link to logr.Logger.

func DefaultAcquireLoggerFunc

func DefaultAcquireLoggerFunc(ctx context.Context) Logger

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

func LoggerFromContext(ctx context.Context) Logger

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

type Span = trace.Span

Span is a symbolic link to trace.Span.

func SpanFromContext

func SpanFromContext(ctx context.Context) Span

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 Tracer

func Tracer() *TracerBuilder

Tracer returns a new *TracerBuilder.

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

Build builds the SDKTracerProvider.

func (*TracerProviderBuilder) Composite

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

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

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

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

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.

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.

Jump to

Keyboard shortcuts

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