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 ¶
- Constants
- func PutAnnotation[V AnnotationValue](ctx context.Context, key string, value V)
- func PutMetadata(ctx context.Context, key string, value any, namespace string)
- func SanitizeAnnotationKey(key string) string
- func SanitizeSpanName(name string) string
- type AnnotationValue
- type CaptureOption
- type HTTPOption
- type Option
- type Tracer
- func (t *Tracer) AWSTracerProvider() smithytracing.TracerProvider
- func (t *Tracer) Capture[Out any](ctx context.Context, name string, fn func(ctx context.Context) (Out, error), ...) (Out, error)
- func (t *Tracer) CaptureLambdaHandler[E, R any](h middleware.Handler[E, R], opts ...CaptureOption) middleware.Handler[E, R]
- func (t *Tracer) CaptureMethod[In, Out any](fn func(context.Context, In) (Out, error), opts ...CaptureOption) func(context.Context, In) (Out, error)
- func (t *Tracer) ForceFlush(ctx context.Context) error
- func (t *Tracer) HTTPClient(opts ...HTTPOption) *http.Client
- func (t *Tracer) Provider() trace.TracerProvider
- func (t *Tracer) Shutdown(ctx context.Context) error
- func (t *Tracer) Transport(base http.RoundTripper, opts ...HTTPOption) http.RoundTripper
Examples ¶
Constants ¶
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.
const ( ColdStartAnnotation = "ColdStart" ServiceAnnotation = "Service" )
The annotations CaptureLambdaHandler adds to every invocation, as Powertools Python names them.
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 ¶
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 ¶
SanitizeAnnotationKey returns key as X-Ray stores it: letters and digits are kept, every other character becomes an underscore.
func SanitizeSpanName ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.