tracing

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 39 Imported by: 0

Documentation

Overview

Package tracing is the Tracer of Powertools for AWS Lambda: an opinionated layer over OpenTelemetry that traces a Lambda function to AWS X-Ray, the way the AWS X-Ray SDK migration guide prescribes now that the X-Ray SDKs are in maintenance mode.

Getting started

Build one Tracer per process and decorate the handler:

var tracer = tracing.New(tracing.WithService("payment"))

func main() {
	lambda.Start(tracer.CaptureLambdaHandler(handle))
}

The function needs active tracing (SAM: Tracing: Active), which also gives its role the xray:PutTraceSegments and xray:PutTelemetryRecords permissions.

New builds the tracer provider of the guide's "Lambda manual instrumentation": the X-Ray UDP exporter writing OTLP spans to the endpoint Lambda runs next to the function (AWS_XRAY_DAEMON_ADDRESS), a simple span processor (Lambda freezes the environment between invocations, so nothing may wait in a batch), X-Ray trace ids, and the Lambda resource with service.name set to the service. It registers that provider and the X-Ray propagator as the OpenTelemetry globals (WithGlobals), so that every instrumentation in the process joins the same traces.

The invocation

CaptureLambdaHandler wraps the handler with otellambda: every invocation is a server span, an X-Ray segment, whose parent is the trace Lambda started. The decorator adds what Powertools Python adds:

  • the ColdStart annotation, true for the first invocation of the execution environment (never under provisioned concurrency);
  • the Service annotation, when the tracer has a service;
  • the handler's result as metadata "<handler> response", in the service's namespace;
  • a returned error or a panic: recorded on the span with the failed status, and as metadata "<handler> error". A panic is resumed after being recorded.

The spans of the invocation are flushed when it ends.

Functions

CaptureMethod decorates any func(context.Context, In) (Out, error) so that each call is a span named "## <package>.<function>", an X-Ray subsegment, with the same response and error capture. Capture does the same for a block of code:

total, err := tracer.Capture(ctx, "charge", func(ctx context.Context) (int, error) {
	return charge(ctx, order)
})

WithCaptureResponse and WithCaptureError turn the capture off for one function; the POWERTOOLS_TRACER_CAPTURE_RESPONSE and POWERTOOLS_TRACER_CAPTURE_ERROR variables do it for all of them. Turn the response capture off for results that are sensitive or large.

Annotations and metadata

PutAnnotation adds a string, number or boolean to the current span as an X-Ray annotation, indexed for filter expressions and trace groups. X-Ray turns an attribute into an annotation when its key is listed in the aws.xray.annotations attribute; PutAnnotation keeps that list. Annotation keys hold letters, digits and underscores: other characters become underscores.

PutMetadata adds any JSON-encodable value to the current span as X-Ray metadata, not indexed, under a namespace (by default the service). X-Ray turns plain attributes into metadata of the "default" namespace; to keep Powertools' namespaces, the entries of a namespace are one attribute, "aws.xray.metadata.<namespace>", holding their JSON object, which is how the OpenTelemetry X-Ray exporter spells a metadata namespace.

Both act on the span in the context: the invocation inside a captured handler, the function's span inside a captured function. Without a recording span (tracing disabled) they do nothing.

AWS SDK and HTTP calls

Powertools Python patches the libraries the X-Ray SDK supports. In Go, a client is instrumented when it is built: AWSTracerProvider is the tracer provider of the AWS SDK for Go v2 clients (the SDK's own smithyoteltracing adapter, which replaced the deprecated otelaws), and Transport and HTTPClient trace net/http requests with otelhttp, carrying the X-Ray trace header downstream. IgnoreEndpoint leaves some requests untraced.

Disabling tracing

Tracing is off, and every decorator returns the function it is given, when POWERTOOLS_TRACE_DISABLED is true, outside Lambda (no LAMBDA_TASK_ROOT), under the AWS SAM CLI (AWS_SAM_LOCAL), or when Lambda gives the function no X-Ray endpoint (no AWS_XRAY_DAEMON_ADDRESS: active tracing is off). WithDisabled decides regardless of the environment. Tests therefore need no setup; to look at the spans, pass a recording provider with WithTracerProvider (go.opentelemetry.io/otel/sdk/trace/tracetest).

Environment variables

POWERTOOLS_SERVICE_NAME             the service (WithService)
POWERTOOLS_TRACE_DISABLED           turns tracing off (WithDisabled)
POWERTOOLS_TRACER_CAPTURE_RESPONSE  response capture, default true
POWERTOOLS_TRACER_CAPTURE_ERROR     error capture, default true
AWS_XRAY_DAEMON_ADDRESS             the X-Ray endpoint, set by Lambda
LAMBDA_TASK_ROOT, AWS_SAM_LOCAL     where the function runs

Concurrency

Lambda Managed Instances run several invocations at once in one process. The tracer keeps nothing per invocation outside the context: the parent of each invocation span comes from the trace header aws-lambda-go puts in the invocation's context (it sets _X_AMZN_TRACE_ID only when the environment runs one invocation at a time), the cold start is claimed by exactly one invocation, and annotations of one span made from several goroutines are serialized.

Differences from Powertools Python

  • The X-Ray SDK is replaced by OpenTelemetry. The span otellambda opens for the invocation carries what Python's "## <handler>" subsegment carries; captured functions keep the "## <name>" spans.
  • Tracer is a value built by New, not a class whose configuration every instance shares; share the value, or use the OpenTelemetry globals New registers. Provider is the escape hatch (Python's tracer.provider).
  • patch, patch_modules and auto_patch become explicit instrumentation: AWSTracerProvider, Transport, HTTPClient. aiohttp_trace_config is Python only.
  • Generators, context managers and async functions have no Go form: capture the code that consumes an iterator with Capture, and goroutines need no special care (OpenTelemetry contexts are goroutine safe).
  • Values are encoded with encoding/json/v2 instead of jsonpickle; an error is stored as its message, and a stream (io.Reader) is never consumed. Metadata that would take a span past 32 KiB is dropped, where Python lets the X-Ray daemon reject the segment ("message too long").
  • The tracer turns itself off when there is no X-Ray endpoint, which Python cannot see. AWS_CHALICE_CLI_MODE is Python only.
  • The cold start is one decision shared by every Powertools utility.

Index

Examples

Constants

View Source
const (
	// AnnotationsAttribute lists the attributes X-Ray turns into annotations,
	// indexed for filter expressions and trace groups. Every other attribute
	// becomes metadata in the default namespace.
	AnnotationsAttribute = "aws.xray.annotations"
	// MetadataAttributePrefix, followed by a namespace, names an attribute
	// whose value, a JSON object, X-Ray turns into the metadata of that
	// namespace.
	MetadataAttributePrefix = "aws.xray.metadata."
	// DefaultNamespace is X-Ray's metadata namespace for attributes that name
	// none.
	DefaultNamespace = "default"
)

The attributes X-Ray reads when it turns a span into a segment.

View Source
const (
	ColdStartAnnotation = "ColdStart"
	ServiceAnnotation   = "Service"
)

The annotations CaptureLambdaHandler adds to every invocation, as Powertools Python names them.

View Source
const (
	// DaemonAddressEnv is where Lambda says the X-Ray endpoint listens, when
	// the function has active tracing on.
	DaemonAddressEnv = "AWS_XRAY_DAEMON_ADDRESS"
)

Environment variables the tracer reads besides the POWERTOOLS_* ones.

Variables

This section is empty.

Functions

func PutAnnotation

func PutAnnotation[V AnnotationValue](ctx context.Context, key string, value V)

PutAnnotation annotates the span in ctx (the invocation, or the function captured last) with key and value. X-Ray indexes annotations, so that filter expressions and trace groups can select traces by them; it keeps up to 50 per trace.

X-Ray accepts letters, digits and underscores in an annotation key: any other character becomes an underscore, so "order.id" is annotated as "order_id". Annotating a key again replaces its value. Without a span to annotate (tracing disabled, or no captured function), PutAnnotation does nothing.

Example
package main

import (
	"context"
	"fmt"

	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	"go.opentelemetry.io/otel/sdk/trace/tracetest"

	"github.com/alessandrolattao/powertools-lambda-go/tracing"
)

// recording returns a tracer whose spans stay in memory, as a test of your
// own would build it.
func recording() (tracer *tracing.Tracer, recorder *tracetest.SpanRecorder) {
	recorder = tracetest.NewSpanRecorder()
	tracer = tracing.New(
		tracing.WithService("payment"),
		tracing.WithTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder))),
		tracing.WithGlobals(false),
	)
	return tracer, recorder
}

func main() {
	tracer, recorder := recording()

	_, err := tracer.Capture(context.Background(), "confirm", func(ctx context.Context) (struct{}, error) {
		tracing.PutAnnotation(ctx, "order.id", "o-1") // stored as order_id
		tracing.PutAnnotation(ctx, "amount", 5)
		tracing.PutAnnotation(ctx, "express", true)
		return struct{}{}, nil
	})
	if err != nil {
		panic(err)
	}
	span := recorder.Ended()[0]
	for _, kv := range span.Attributes() {
		fmt.Println(kv.Key, kv.Value.String())
	}
}
Output:
order_id o-1
aws.xray.annotations ["order_id","amount","express"]
amount 5
express true

func PutMetadata

func PutMetadata(ctx context.Context, key string, value any, namespace string)

PutMetadata adds key and value to the metadata of the span in ctx, under namespace. Metadata is not indexed: it carries detail, such as a payload, that traces are not searched by. value is stored as JSON, encoded with encoding/json/v2; a jsontext.Value or a []byte that holds a JSON document is stored as that document, an error as its message. An io.Reader is skipped, since encoding it would consume it. Metadata that would take a span past 32 KiB of JSON is dropped: a span must fit the X-Ray endpoint's datagram.

An empty namespace is the service of the tracer that captured the current function, else POWERTOOLS_SERVICE_NAME, else X-Ray's "default" namespace. Namespaces starting with "AWS." are reserved by X-Ray and refused.

The namespace is kept the way the X-Ray exporter of OpenTelemetry reads it: an attribute named "aws.xray.metadata.<namespace>" whose value is the JSON object of the namespace's entries. Other OpenTelemetry backends show that attribute as a JSON string.

func SanitizeAnnotationKey

func SanitizeAnnotationKey(key string) string

SanitizeAnnotationKey returns key as X-Ray stores it: letters and digits are kept, every other character becomes an underscore.

func SanitizeSpanName

func SanitizeSpanName(name string) string

SanitizeSpanName removes from name the characters X-Ray does not accept in a segment name (?;*()!$~^<>), as Powertools Python does with the names of captured methods.

Types

type AnnotationValue

type AnnotationValue interface {
	~string | ~bool |
		~int | ~int8 | ~int16 | ~int32 | ~int64 |
		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
		~float32 | ~float64
}

AnnotationValue is the type of an annotation: X-Ray indexes strings, numbers and booleans.

type CaptureOption

type CaptureOption func(*capture)

CaptureOption configures CaptureLambdaHandler, CaptureMethod and Capture.

func WithCaptureError

func WithCaptureError(enabled bool) CaptureOption

WithCaptureError decides whether the error a function returns, or its panic, is added to the span as metadata ("<name> error"). It defaults to POWERTOOLS_TRACER_CAPTURE_ERROR, true when unset. The span records the error and the failed status either way, as X-Ray records the exception of a Powertools Python subsegment either way.

func WithCaptureResponse

func WithCaptureResponse(enabled bool) CaptureOption

WithCaptureResponse decides whether the function's result is added to the span as metadata ("<name> response"). It defaults to POWERTOOLS_TRACER_CAPTURE_RESPONSE, true when unset. Turn it off for results that are sensitive, or too large for a span.

func WithName

func WithName(name string) CaptureOption

WithName names the captured function, in place of the name of the Go function: the span of CaptureMethod is "## <name>", and the metadata keys are "<name> response" and "<name> error".

type HTTPOption

type HTTPOption func(*httpConfig)

HTTPOption configures the HTTP instrumentation of Transport and HTTPClient.

func IgnoreEndpoint

func IgnoreEndpoint(hostname string, urls ...string) HTTPOption

IgnoreEndpoint leaves untraced the requests to hostname whose path is one of urls, as Powertools Python's ignore_endpoint does. hostname is a glob ("*.example.com"); an empty hostname matches every host, and no urls match every path. The option may be repeated; a request matching any of them is not traced.

type Option

type Option func(*config)

Option configures a Tracer.

func WithDisabled

func WithDisabled(disabled bool) Option

WithDisabled turns tracing off, or on, regardless of the environment. Left unset, tracing is off when POWERTOOLS_TRACE_DISABLED is true, outside Lambda (LAMBDA_TASK_ROOT unset), under the AWS SAM CLI (AWS_SAM_LOCAL set) and when Lambda gives the function no X-Ray endpoint (AWS_XRAY_DAEMON_ADDRESS unset, active tracing off). Forced on without an endpoint, spans go to 127.0.0.1:2000, where a local X-Ray daemon or collector listens.

func WithGlobals

func WithGlobals(enabled bool) Option

WithGlobals decides whether New registers its tracer provider and the X-Ray propagator as the OpenTelemetry global ones, so that otelhttp, the AWS SDK and every otel.Tracer in the process join the same traces. It defaults to true; a disabled tracer never touches the globals.

func WithService

func WithService(name string) Option

WithService names the service the traces belong to: the Service annotation of every invocation, the namespace of the captured metadata and the service.name of the spans. It defaults to POWERTOOLS_SERVICE_NAME; with neither, invocations carry no Service annotation, as in Powertools Python.

func WithTracerProvider

func WithTracerProvider(tp trace.TracerProvider) Option

WithTracerProvider makes the tracer record its spans with tp instead of building its own X-Ray provider. Tests pass one that records spans in memory. The environment detection of WithDisabled does not apply to it: only WithDisabled and POWERTOOLS_TRACE_DISABLED turn it off. Shutdown leaves tp alone; it belongs to the caller.

type Tracer

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

Tracer records what a Lambda function does as OpenTelemetry spans, exported to AWS X-Ray. Build one per process with New, usually in main, and share it: it is safe for concurrent invocations.

func New

func New(opts ...Option) *Tracer

New returns a Tracer. Unless tracing is disabled (see WithDisabled) or a provider is passed (WithTracerProvider), it builds the tracer provider the AWS X-Ray migration guide prescribes for Lambda: the X-Ray UDP exporter writing to AWS_XRAY_DAEMON_ADDRESS through a simple span processor (Lambda freezes the environment between invocations, so nothing waits in a batch), X-Ray trace ids, and the Lambda resource with service.name set to the service.

New never fails: tracing that cannot be set up is reported on standard error and left off, so that it never stops a function from starting.

Example
package main

import (
	"context"
	"fmt"

	"github.com/alessandrolattao/powertools-lambda-go/tracing"
)

func main() {
	// Outside Lambda, without active tracing, the tracer is off: the
	// decorators return the functions they get and nothing is exported.
	tracer := tracing.New()

	_, span := tracer.Provider().Tracer("example").Start(context.Background(), "probe")
	defer span.End()
	fmt.Println("recording:", span.IsRecording())
}
Output:
recording: false

func (*Tracer) AWSTracerProvider

func (t *Tracer) AWSTracerProvider() smithytracing.TracerProvider

AWSTracerProvider returns the tracer provider an AWS SDK for Go v2 client traces its calls with, so that every call is a span (an X-Ray subsegment) under the span of the context it is made with:

client := dynamodb.NewFromConfig(cfg, func(o *dynamodb.Options) {
	o.TracerProvider = tracer.AWSTracerProvider()
})

It is the SDK's own OpenTelemetry adapter, smithyoteltracing, which replaces the deprecated otelaws instrumentation. A disabled tracer returns the SDK's no-op provider.

func (*Tracer) Capture

func (t *Tracer) Capture[Out any](ctx context.Context, name string, fn func(ctx context.Context) (Out, error), opts ...CaptureOption) (Out, error)

Capture runs fn in a span named "## <name>", as CaptureMethod does for a function, for code that is not a function of its own: a block, a closure, a durable step. fn receives the context that carries the span, for the calls it makes to nest under it.

Example
package main

import (
	"context"
	"fmt"
	"slices"
	"strings"

	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	"go.opentelemetry.io/otel/sdk/trace/tracetest"

	"github.com/alessandrolattao/powertools-lambda-go/tracing"
)

// recording returns a tracer whose spans stay in memory, as a test of your
// own would build it.
func recording() (tracer *tracing.Tracer, recorder *tracetest.SpanRecorder) {
	recorder = tracetest.NewSpanRecorder()
	tracer = tracing.New(
		tracing.WithService("payment"),
		tracing.WithTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder))),
		tracing.WithGlobals(false),
	)
	return tracer, recorder
}

// xray lists the X-Ray annotations and metadata of span, sorted.
func xray(span sdktrace.ReadOnlySpan) string {
	var lines []string
	for _, kv := range span.Attributes() {
		key := string(kv.Key)
		switch {
		case key == tracing.ColdStartAnnotation:
		case key == tracing.AnnotationsAttribute:
			listed := slices.DeleteFunc(kv.Value.AsStringSlice(), func(k string) bool { return k == tracing.ColdStartAnnotation })
			slices.Sort(listed)
			lines = append(lines, fmt.Sprintf("%s: %v", key, listed))
		case strings.HasPrefix(key, tracing.MetadataAttributePrefix), key == tracing.ServiceAnnotation, key == "order_id":
			lines = append(lines, fmt.Sprintf("%s: %s", key, kv.Value.String()))
		}
	}
	slices.Sort(lines)
	return strings.Join(lines, "\n")
}

func main() {
	tracer, recorder := recording()

	total, err := tracer.Capture(context.Background(), "sum", func(ctx context.Context) (int, error) {
		tracing.PutMetadata(ctx, "lines", []int{1, 2, 3}, "cart")
		return 6, nil
	}, tracing.WithCaptureResponse(false))
	if err != nil {
		panic(err)
	}
	span := recorder.Ended()[0]
	fmt.Println(total, span.Name())
	fmt.Println(xray(span))
}
Output:
6 ## sum
aws.xray.metadata.cart: {"lines":[1,2,3]}

func (*Tracer) CaptureLambdaHandler

func (t *Tracer) CaptureLambdaHandler[E, R any](h middleware.Handler[E, R], opts ...CaptureOption) middleware.Handler[E, R]

CaptureLambdaHandler traces every invocation of h. The invocation is the server span otellambda opens, named after the function, whose parent is the trace Lambda started for the invocation: the X-Ray header aws-lambda-go puts in the context (it sets _X_AMZN_TRACE_ID only while the environment runs one invocation at a time), else _X_AMZN_TRACE_ID. A span already active in the context is kept as the parent.

On that span the decorator adds what Powertools Python adds to its "## handler" subsegment:

  • the ColdStart annotation, true for the invocation that paid for the execution environment's initialization;
  • the Service annotation, when the tracer has a service;
  • the result as metadata, "<handler> response" in the service's namespace (WithCaptureResponse);
  • a returned error, or a panic, recorded on the span with the failed status, and as metadata "<handler> error" (WithCaptureError). A panic is recorded with its stack trace and then resumed.

The spans are flushed when the invocation ends, before Lambda can freeze the environment. A disabled tracer returns h as it is.

Example
package main

import (
	"context"
	"fmt"
	"slices"
	"strings"

	"github.com/aws/aws-lambda-go/lambdacontext"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	"go.opentelemetry.io/otel/sdk/trace/tracetest"

	"github.com/alessandrolattao/powertools-lambda-go/tracing"
)

type Order struct {
	ID     string `json:"id"`
	Amount int    `json:"amount"`
}

type Receipt struct {
	OrderID string `json:"orderId"`
	Paid    bool   `json:"paid"`
}

// recording returns a tracer whose spans stay in memory, as a test of your
// own would build it.
func recording() (tracer *tracing.Tracer, recorder *tracetest.SpanRecorder) {
	recorder = tracetest.NewSpanRecorder()
	tracer = tracing.New(
		tracing.WithService("payment"),
		tracing.WithTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder))),
		tracing.WithGlobals(false),
	)
	return tracer, recorder
}

// xray lists the X-Ray annotations and metadata of span, sorted.
func xray(span sdktrace.ReadOnlySpan) string {
	var lines []string
	for _, kv := range span.Attributes() {
		key := string(kv.Key)
		switch {
		case key == tracing.ColdStartAnnotation:
		case key == tracing.AnnotationsAttribute:
			listed := slices.DeleteFunc(kv.Value.AsStringSlice(), func(k string) bool { return k == tracing.ColdStartAnnotation })
			slices.Sort(listed)
			lines = append(lines, fmt.Sprintf("%s: %v", key, listed))
		case strings.HasPrefix(key, tracing.MetadataAttributePrefix), key == tracing.ServiceAnnotation, key == "order_id":
			lines = append(lines, fmt.Sprintf("%s: %s", key, kv.Value.String()))
		}
	}
	slices.Sort(lines)
	return strings.Join(lines, "\n")
}

func main() {
	tracer, recorder := recording()

	handle := func(ctx context.Context, o Order) (Receipt, error) {
		tracing.PutAnnotation(ctx, "order_id", o.ID)
		return Receipt{OrderID: o.ID, Paid: true}, nil
	}
	// In main: lambda.Start(tracer.CaptureLambdaHandler(handle)).
	handler := tracer.CaptureLambdaHandler(handle, tracing.WithName("handle"))

	ctx := lambdacontext.NewContext(context.Background(), &lambdacontext.LambdaContext{AwsRequestID: "req-1"})
	if _, err := handler(ctx, Order{ID: "o-1", Amount: 5}); err != nil {
		panic(err)
	}
	fmt.Println(xray(recorder.Ended()[0]))
}
Output:
Service: payment
aws.xray.annotations: [Service order_id]
aws.xray.metadata.payment: {"handle response":{"orderId":"o-1","paid":true}}
order_id: o-1

func (*Tracer) CaptureMethod

func (t *Tracer) CaptureMethod[In, Out any](fn func(context.Context, In) (Out, error), opts ...CaptureOption) func(context.Context, In) (Out, error)

CaptureMethod traces every call of fn as a span named "## <name>", a subsegment in X-Ray, where name is fn's package and function name ("orders.charge") unless WithName gives one. The span records fn's result and error as CaptureLambdaHandler does, without the ColdStart and Service annotations. A disabled tracer returns fn as it is.

Example
package main

import (
	"context"
	"fmt"
	"slices"
	"strings"

	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	"go.opentelemetry.io/otel/sdk/trace/tracetest"

	"github.com/alessandrolattao/powertools-lambda-go/tracing"
)

type Order struct {
	ID     string `json:"id"`
	Amount int    `json:"amount"`
}

// recording returns a tracer whose spans stay in memory, as a test of your
// own would build it.
func recording() (tracer *tracing.Tracer, recorder *tracetest.SpanRecorder) {
	recorder = tracetest.NewSpanRecorder()
	tracer = tracing.New(
		tracing.WithService("payment"),
		tracing.WithTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder))),
		tracing.WithGlobals(false),
	)
	return tracer, recorder
}

// xray lists the X-Ray annotations and metadata of span, sorted.
func xray(span sdktrace.ReadOnlySpan) string {
	var lines []string
	for _, kv := range span.Attributes() {
		key := string(kv.Key)
		switch {
		case key == tracing.ColdStartAnnotation:
		case key == tracing.AnnotationsAttribute:
			listed := slices.DeleteFunc(kv.Value.AsStringSlice(), func(k string) bool { return k == tracing.ColdStartAnnotation })
			slices.Sort(listed)
			lines = append(lines, fmt.Sprintf("%s: %v", key, listed))
		case strings.HasPrefix(key, tracing.MetadataAttributePrefix), key == tracing.ServiceAnnotation, key == "order_id":
			lines = append(lines, fmt.Sprintf("%s: %s", key, kv.Value.String()))
		}
	}
	slices.Sort(lines)
	return strings.Join(lines, "\n")
}

func main() {
	tracer, recorder := recording()

	charge := tracer.CaptureMethod(func(_ context.Context, o Order) (int, error) {
		return o.Amount * 100, nil
	}, tracing.WithName("charge"))

	cents, err := charge(context.Background(), Order{ID: "o-1", Amount: 5})
	if err != nil {
		panic(err)
	}
	span := recorder.Ended()[0]
	fmt.Println(cents, span.Name())
	fmt.Println(xray(span))
}
Output:
500 ## charge
aws.xray.metadata.payment: {"charge response":500}

func (*Tracer) ForceFlush

func (t *Tracer) ForceFlush(ctx context.Context) error

ForceFlush exports the spans the tracer provider still holds. The handler decorator does it at the end of every invocation already.

func (*Tracer) HTTPClient

func (t *Tracer) HTTPClient(opts ...HTTPOption) *http.Client

HTTPClient returns an http.Client whose requests are traced, as Transport describes. Requests must carry the invocation's context (http.NewRequestWithContext) to nest under its span.

Example
package main

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

	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	"go.opentelemetry.io/otel/sdk/trace/tracetest"

	"github.com/alessandrolattao/powertools-lambda-go/tracing"
)

// recording returns a tracer whose spans stay in memory, as a test of your
// own would build it.
func recording() (tracer *tracing.Tracer, recorder *tracetest.SpanRecorder) {
	recorder = tracetest.NewSpanRecorder()
	tracer = tracing.New(
		tracing.WithService("payment"),
		tracing.WithTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder))),
		tracing.WithGlobals(false),
	)
	return tracer, recorder
}

func main() {
	tracer, recorder := recording()
	api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("trace header sent:", r.Header.Get("X-Amzn-Trace-Id") != "")
		w.WriteHeader(http.StatusNoContent)
	}))
	defer api.Close()

	client := tracer.HTTPClient(tracing.IgnoreEndpoint("", "/health"))
	for _, path := range []string{"/orders", "/health"} {
		req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, api.URL+path, http.NoBody)
		if err != nil {
			panic(err)
		}
		resp, err := client.Do(req)
		if err != nil {
			panic(err)
		}
		_ = resp.Body.Close() //nolint:errcheck // an empty body
	}
	fmt.Println("spans:", len(recorder.Ended()))
}
Output:
trace header sent: true
trace header sent: false
spans: 1

func (*Tracer) Provider

func (t *Tracer) Provider() trace.TracerProvider

Provider returns the OpenTelemetry tracer provider the tracer records with: the escape hatch to everything OpenTelemetry offers that the tracer does not wrap. A disabled tracer returns a no-op provider.

func (*Tracer) Shutdown

func (t *Tracer) Shutdown(ctx context.Context) error

Shutdown flushes and releases the tracer provider New built. A Lambda function never needs it, since every invocation flushes its spans; a program that outlives its handler (a task, a CLI) calls it before exiting. A provider passed with WithTracerProvider is left alone.

func (*Tracer) Transport

func (t *Tracer) Transport(base http.RoundTripper, opts ...HTTPOption) http.RoundTripper

Transport wraps base (http.DefaultTransport when nil) so that every request is a client span under the span of the request's context, and carries the X-Ray trace header to the server. A disabled tracer returns base as it is.

Jump to

Keyboard shortcuts

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