golang.org/x/exp/event is an experimental event system that provides low-cost tracing, metrics, and structured logging. It offers a unified observability interface that doesn't tie libraries to specific APIs or applications to specific export formats. The package is designed for minimal overhead when no exporter is used, making it safe to leave instrumentation calls in libraries.
Note: This package is highly experimental and in a state of flux. It is public only to allow for collaboration on the design and implementation, and may be changed or removed at any time.
go get golang.org/x/exp/eventimport (
"golang.org/x/exp/event"
"golang.org/x/exp/event/adapter"
"golang.org/x/exp/event/keys"
"golang.org/x/exp/event/severity"
)package main
import (
"context"
"golang.org/x/exp/event"
)
func main() {
ctx := context.Background()
// Log an informational message
event.Log(ctx, "application started")
// Create a traced span
ctx, cancel := context.WithCancel(ctx)
defer cancel()
ctx = event.Start(ctx, "process_request")
defer event.End(ctx)
// Log with labels
event.Log(ctx, "processing data",
event.String("user_id", "12345"),
event.Int64("duration_ms", 150),
)
}Events are the fundamental unit of observability in this package. They combine metadata (ID, parent ID, source, timestamp, kind) with user-supplied labels. The event system supports different kinds of events: log events, metric events, span start/end events, and annotations.
Labels are named values that carry information about events. They support multiple data types (bool, bytes, duration, float64, int64, uint64, string, interface{}) and are used to add context to events.
Exporters handle the delivery of events to observability backends. They receive events synchronously from the event call site, so they should return quickly. Exporters can be set on the context or registered as a default exporter.
The package provides several metric types for recording measurements:
func Annotate(ctx context.Context, labels ...Label)Annotate adds labels to the current context without creating a new event.
func End(ctx context.Context, labels ...Label)End records the end of a span started with Start().
func Error(ctx context.Context, msg string, err error, labels ...Label)Error logs an error message with the provided error and labels.
func Log(ctx context.Context, msg string, labels ...Label)Log records a log event with the message and labels.
func Logf(ctx context.Context, msg string, args ...interface{})Logf records a log event with a formatted message (printf-style).
func Start(ctx context.Context, name string, labels ...Label) context.ContextStart creates a new traced span and returns a context with the span attached. The caller must call End() on the returned context or parent context.
func WithExporter(ctx context.Context, e *Exporter) context.ContextWithExporter returns a context with the exporter attached. The exporter is called synchronously from the event call site, so it should return quickly.
func SetDefaultExporter(e *Exporter)SetDefaultExporter sets an exporter that is used if no exporter can be found on the context.
func RegisterHelper(v interface{})RegisterHelper records a function as being an event helper that should not be used when capturing the source information on events. v should be either a string or a function pointer. If v is a string it is of the form Space.Owner.Name where Owner and Name cannot contain '/' and Name also cannot contain '.'.
type Event struct {
ID uint64 // Unique identifier for this event
Parent uint64 // ID of the parent event for this event
Source Source // Source of event; if empty, set by exporter to import path
At time.Time // Time at which the event is delivered to the exporter
Kind Kind // Type of event (LogKind, MetricKind, StartKind, EndKind)
Labels []Label // User-supplied labels associated with the event
}Event holds the information about an event that occurred. It combines the event metadata with the user supplied labels.
func New(ctx context.Context, kind Kind) *EventNew prepares a new event. This is intended to avoid allocations in the steady state case, to do this it uses a pool of events. Events are returned to the pool when Deliver is called. Failure to call Deliver will exhaust the pool and cause allocations. It returns nil if there is no active exporter for this kind of event.
func (ev *Event) Clone() *EventClone makes a deep copy of the Event. Deliver can be called on both Events independently.
func (ev *Event) Deliver() context.ContextDeliver the event to the exporter that was found in New. This also returns the event to the pool, it is an error to do anything with the event after it is delivered.
func (ev *Event) Find(name string) LabelFind searches for a label by name within the event.
func (ev *Event) Trace()Trace records this event as a trace.
type Exporter struct {
// Has unexported fields.
}Exporter synchronizes the delivery of events to handlers.
func NewExporter(handler Handler, opts *ExporterOptions) *ExporterNewExporter creates an Exporter using the supplied handler and options. Event delivery is serialized to enable safe atomic handling.
type ExporterOptions struct {
// If non-nil, sets zero Event.At on delivery.
Now func() time.Time
// Disable some event types, for better performance.
DisableLogging bool
DisableTracing bool
DisableAnnotations bool
DisableMetrics bool
// Enable automatically setting the event Namespace to the calling package's
// import path.
EnableNamespaces bool
}ExporterOptions provides configuration for Exporter behavior.
type Handler interface {
// Event is called with each event.
Event(context.Context, *Event) context.Context
}Handler is the type for something that handles events as they occur.
type Source struct {
Space string // Namespace for the source
Owner string // Owner/package name
Name string // Function or event name
}Source represents the origin of an event.
type Kind intKind represents the type of event.
func NewKind(name string) KindNewKind creates a new event kind with the given name.
func (k Kind) String() stringString returns the string representation of the Kind.
const (
LogKind
MetricKind
StartKind
EndKind
)Standard event kinds.
type Label struct {
Name string
// Has unexported fields.
}Label is a named value.
func Bool(name string, b bool) LabelBool returns a new Label for a boolean value.
func Bytes(name string, data []byte) LabelBytes returns a new Label for a byte slice.
func Duration(name string, d time.Duration) LabelDuration returns a new Label for a time.Duration value.
func Float64(name string, f float64) LabelFloat64 returns a new Label for a floating point number.
func Int64(name string, u int64) LabelInt64 returns a new Label for a signed integer.
func String(name string, s string) LabelString returns a new Label for a string value.
func Uint64(name string, u uint64) LabelUint64 returns a new Label for an unsigned integer.
func Value(name string, value interface{}) LabelValue returns a Label for the supplied interface{} value.
func (v Label) Bool() boolBool returns the bool from a value that was set with Bool. It will panic for any value for which IsBool is not true.
func (v Label) Bytes() []byteBytes returns the value as a bytes array.
func (v Label) Duration() time.DurationDuration returns the time.Duration value.
func (l Label) Equal(l2 Label) boolEqual reports whether two labels are equal.
func (v Label) Float64() float64Float64 returns the float64 from a value that was set with Float64. It will panic for any value for which IsFloat64 is not true.
func (l Label) HasValue() boolHasValue returns true if the value is set to any type.
func (v Label) Int64() int64Int64 returns the int64 from a value that was set with Int64. It will panic for any value for which IsInt64 is not true.
func (v Label) Interface() interface{}Interface returns the value. This will never panic, things that were not set using SetInterface will be unpacked and returned anyway.
func (v Label) IsBool() boolIsBool returns true if the value was built with Bool.
func (v Label) IsBytes() boolIsBytes returns true if the value was built with Bytes.
func (v Label) IsDuration() boolIsDuration returns true if the value was built with Duration.
func (v Label) IsFloat64() boolIsFloat64 returns true if the value was built with Float64.
func (v Label) IsInt64() boolIsInt64 returns true if the value was built with Int64.
func (v Label) IsString() boolIsString returns true if the value was built with String.
func (v Label) IsUint64() boolIsUint64 returns true if the value was built with Uint64.
func (v Label) String() stringString returns the value as a string. This does not panic if v's Kind is not String, instead, it returns a string representation of the value in all cases.
func (v Label) Uint64() uint64Uint64 returns the uint64 from a value that was set with Uint64. It will panic for any value for which IsUint64 is not true.
type Metric interface {
Name() string
Options() MetricOptions
}Metric represents a kind of recorded measurement.
type MetricOptions struct {
// A string that should be common for all metrics of an application or
// service. Defaults to the import path of the package calling
// the metric construction function (NewCounter, etc.).
Namespace string
// Optional description of the metric.
Description string
// Optional unit for the metric. Defaults to UnitDimensionless.
Unit Unit
}MetricOptions provides configuration for metrics.
type Unit stringUnit is a unit of measurement for a metric.
const (
UnitDimensionless Unit = "1"
UnitBytes Unit = "By"
UnitMilliseconds Unit = "ms"
)Standard metric units.
type Counter struct {
// Has unexported fields.
}A Counter is a metric that counts something cumulatively.
func NewCounter(name string, opts *MetricOptions) *CounterNewCounter creates a counter with the given name.
func (c *Counter) Name() stringName returns the name of the counter.
func (c *Counter) Options() MetricOptionsOptions returns the MetricOptions for the counter.
func (c *Counter) Record(ctx context.Context, v int64, labels ...Label)Record delivers a metric event with the given metric, value and labels to the exporter in the context.
type IntDistribution struct {
// Has unexported fields.
}An IntDistribution records a distribution of int64s.
func NewIntDistribution(name string, opts *MetricOptions) *IntDistributionNewIntDistribution creates a new IntDistribution with the given name.
func (d *IntDistribution) Name() stringName returns the name of the distribution.
func (d *IntDistribution) Options() MetricOptionsOptions returns the MetricOptions for the distribution.
func (d *IntDistribution) Record(ctx context.Context, v int64, labels ...Label)Record converts its argument into a Value and records a metric event.
type FloatGauge struct {
// Has unexported fields.
}A FloatGauge records a single floating-point value that may go up or down.
func NewFloatGauge(name string, opts *MetricOptions) *FloatGaugeNewFloatGauge creates a new FloatGauge with the given name.
func (g *FloatGauge) Name() stringName returns the name of the gauge.
func (g *FloatGauge) Options() MetricOptionsOptions returns the MetricOptions for the gauge.
func (g *FloatGauge) Record(ctx context.Context, v float64, labels ...Label)Record converts its argument into a Value and records a metric event.
type DurationDistribution struct {
// Has unexported fields.
}A DurationDistribution records a distribution of durations.
func NewDuration(name string, opts *MetricOptions) *DurationDistributionNewDuration creates a new Duration with the given name.
func (d *DurationDistribution) Name() stringName returns the name of the distribution.
func (d *DurationDistribution) Options() MetricOptionsOptions returns the MetricOptions for the distribution.
func (d *DurationDistribution) Record(ctx context.Context, v time.Duration, labels ...Label)Record converts its argument into a Value and records a metric event.
const (
MetricKey = interfaceKey("metric")
MetricVal = "metricValue"
DurationMetric = interfaceKey("durationMetric")
)Special metric-related constants.
The keys subpackage provides strongly-typed key accessors for Labels. Each key type has a From() method to extract values and an Of() method to create labels.
type Bool stringBool represents a key for boolean values.
func (k Bool) From(l event.Label) boolFrom can be used to get a value from a Label.
func (k Bool) Of(v bool) event.LabelOf creates a new Label with this key and the supplied value.
type Error stringError represents a key for error values.
func (k Error) From(l event.Label) errorFrom can be used to get a value from a Label.
func (k Error) Of(v error) event.LabelOf creates a new Label with this key and the supplied value.
type Float32 stringFloat32 represents a key for 32-bit floating point values.
func (k Float32) From(l event.Label) float32From can be used to get a value from a Label.
func (k Float32) Of(v float32) event.LabelOf creates a new Label with this key and the supplied value.
type Float64 stringFloat64 represents a key for 64-bit floating point values.
func (k Float64) From(l event.Label) float64From can be used to get a value from a Label.
func (k Float64) Of(v float64) event.LabelOf creates a new Label with this key and the supplied value.
type Int stringInt represents a key for int values.
func (k Int) From(l event.Label) intFrom can be used to get a value from a Label.
func (k Int) Of(v int) event.LabelOf creates a new Label with this key and the supplied value.
type Int8 stringInt8 represents a key for 8-bit signed integer values.
func (k Int8) From(l event.Label) int8From can be used to get a value from a Label.
func (k Int8) Of(v int8) event.LabelOf creates a new Label with this key and the supplied value.
type Int16 stringInt16 represents a key for 16-bit signed integer values.
func (k Int16) From(l event.Label) int16From can be used to get a value from a Label.
func (k Int16) Of(v int16) event.LabelOf creates a new Label with this key and the supplied value.
type Int32 stringInt32 represents a key for 32-bit signed integer values.
func (k Int32) From(l event.Label) int32From can be used to get a value from a Label.
func (k Int32) Of(v int32) event.LabelOf creates a new Label with this key and the supplied value.
type Int64 stringInt64 represents a key for 64-bit signed integer values.
func (k Int64) From(l event.Label) int64From can be used to get a value from a Label.
func (k Int64) Of(v int64) event.LabelOf creates a new Label with this key and the supplied value.
type String stringString represents a key for string values.
func (k String) From(l event.Label) stringFrom can be used to get a value from a Label.
func (k String) Of(v string) event.LabelOf creates a new Label with this key and the supplied value.
type Tag stringTag represents a key for tagging labels that have no value. These are used when the existence of the label is the entire information it carries, such as marking events to be of a specific kind, or from a specific package.
func (k Tag) New() event.LabelNew creates a new Label with this key.
type UInt stringUInt represents a key for unsigned integer values.
func (k UInt) From(l event.Label) uintFrom can be used to get a value from a Label.
func (k UInt) Of(v uint) event.LabelOf creates a new Label with this key and the supplied value.
type UInt8 stringUInt8 represents a key for 8-bit unsigned integer values.
func (k UInt8) From(l event.Label) uint8From can be used to get a value from a Label.
func (k UInt8) Of(v uint8) event.LabelOf creates a new Label with this key and the supplied value.
type UInt16 stringUInt16 represents a key for 16-bit unsigned integer values.
func (k UInt16) From(l event.Label) uint16From can be used to get a value from a Label.
func (k UInt16) Of(v uint16) event.LabelOf creates a new Label with this key and the supplied value.
type UInt32 stringUInt32 represents a key for 32-bit unsigned integer values.
func (k UInt32) From(l event.Label) uint32From can be used to get a value from a Label.
func (k UInt32) Of(v uint32) event.LabelOf creates a new Label with this key and the supplied value.
type UInt64 stringUInt64 represents a key for 64-bit unsigned integer values.
func (k UInt64) From(l event.Label) uint64From can be used to get a value from a Label.
func (k UInt64) Of(v uint64) event.LabelOf creates a new Label with this key and the supplied value.
type Value stringValue represents a key for untyped values.
func (k Value) From(l event.Label) interface{}From can be used to get a value from a Label.
func (k Value) Of(v interface{}) event.LabelOf creates a new Label with this key and the supplied value.
The severity subpackage provides severity level support for logging events, aligned with OpenTelemetry's severity levels.
type Level uint64Level represents a severity level of an event. The basic severity levels are designed to match the levels used in open telemetry. Smaller numerical values correspond to less severe events (such as debug events), larger numerical values correspond to more severe events (such as errors and critical events).
The following severity level ranges are defined:
const (
Trace = Level(1)
Debug = Level(5)
Info = Level(9)
Warning = Level(13)
Error = Level(17)
Fatal = Level(21)
Max = Level(24)
)Standard severity levels.
const Key = "level"Key is the label key for severity levels.
func From(t event.Label) LevelFrom can be used to get a severity level value from a Label.
func (l Level) Class() LevelClass returns the class/range of this severity level.
func (l Level) Label() event.LabelLabel creates a label for the severity level.
func (l Level) Log(ctx context.Context, msg string, labels ...event.Label)Log records a log event with this severity level.
func (l Level) Logf(ctx context.Context, msg string, args ...interface{})Logf records a log event with this severity level and a formatted message.
func (l Level) String() stringString returns the string representation of the severity level.
The event package provides adapters for integration with popular logging frameworks:
package gokit // import "golang.org/x/exp/event/adapter/gokit"
func NewLogger() log.LoggerNewLogger returns a go-kit logger backed by the event system.
package logfmt // import "golang.org/x/exp/event/adapter/logfmt"
const TimeFormat = "2006/01/02 15:04:05"
type Handler struct {
Printer
// Has unexported fields.
}
func NewHandler(to io.Writer) *HandlerNewHandler returns a handler that prints events to the supplied writer in logfmt format on a single line.
type Printer struct {
QuoteValues bool
SuppressNamespace bool
// Has unexported fields.
}
func (p *Printer) Event(w io.Writer, ev *event.Event)
func (p *Printer) Ident(w io.Writer, s string)
func (p *Printer) Label(w io.Writer, l event.Label)package logr // import "golang.org/x/exp/event/adapter/logr"
func NewLogger(ctx context.Context, nameSep string) logr.LoggerNewLogger returns a logr.Logger implementation backed by the event system.
package logrus // import "golang.org/x/exp/event/adapter/logrus"
func NewFormatter() logrus.FormatterNewFormatter returns a logrus Formatter for events. Can be used to format the global logger:
logrus.SetFormatter(elogrus.NewFormatter())
logrus.SetOutput(io.Discard)Or a Logger instance:
logger.SetFormatter(elogrus.NewFormatter())
logger.SetOutput(io.Discard)package zap // import "golang.org/x/exp/event/adapter/zap"
func NewCore(ctx context.Context) zapcore.CoreNewCore returns a zapcore.Core implementation for events. To use globally:
zap.ReplaceGlobals(zap.New(NewCore(ctx)))The stdlib adapter provides integration with the Go standard library logging (no additional functions exported).
The zerolog adapter provides integration with zerolog (no additional functions exported).
The otel subpackage provides handlers for OpenTelemetry integration:
package otel // import "golang.org/x/exp/event/otel"
type MetricHandler struct {
// Has unexported fields.
}
func NewMetricHandler(m metric.Meter) *MetricHandlerMetricHandler is an event.Handler for OpenTelemetry metrics. Its Event method handles Metric events and ignores all others.
func (m *MetricHandler) Event(ctx context.Context, e *event.Event) context.Contexttype TraceHandler struct {
// Has unexported fields.
}
func NewTraceHandler(t trace.Tracer) *TraceHandlerfunc (t *TraceHandler) Event(ctx context.Context, ev *event.Event) context.ContextThe eventtest subpackage supports testing event-based code:
package eventtest // import "golang.org/x/exp/event/eventtest"
func NewContext(ctx context.Context, tb testing.TB) context.ContextNewContext returns a context you should use for the active test. You must use this context or a derived one anywhere you want telemetry to be correctly routed back to the test it was constructed with.
type CaptureHandler struct {
Got []event.Event
}
func NewCapture() (context.Context, *CaptureHandler)
func (h *CaptureHandler) Event(ctx context.Context, ev *event.Event) context.Context
func (h *CaptureHandler) Reset()CaptureHandler captures events for testing.
func CmpOptions() []cmp.Option
func ExporterOptions() *event.ExporterOptions
func RunBenchmark(b *testing.B, ctx context.Context, hooks Hooks)
func TestAllocs(t *testing.T, f func(io.Writer) context.Context, hooks Hooks, expect int)
func TestBenchmark(t *testing.T, f func(io.Writer) context.Context, hooks Hooks, expect string)Testing support functions and types.
package main
import (
"context"
"golang.org/x/exp/event"
)
func main() {
ctx := context.Background()
// Simple log
event.Log(ctx, "server started")
// Log with labels
event.Log(ctx, "request received",
event.String("method", "GET"),
event.String("path", "/api/users"),
)
// Log with formatted message
event.Logf(ctx, "processed %d items", 42)
}func processRequest(ctx context.Context, id string) error {
// Create a traced span
ctx = event.Start(ctx, "processRequest")
defer event.End(ctx)
// Log within the span
event.Log(ctx, "processing started",
event.String("request_id", id),
)
// ... do work ...
event.Log(ctx, "processing completed")
return nil
}import (
"context"
"golang.org/x/exp/event"
)
var (
requestCount = event.NewCounter("http_requests_total", &event.MetricOptions{
Description: "Total number of HTTP requests",
})
processingTime = event.NewDuration("http_request_duration_ms", &event.MetricOptions{
Description: "HTTP request processing time",
Unit: event.UnitMilliseconds,
})
)
func handleRequest(ctx context.Context) {
start := time.Now()
// Record counter
requestCount.Record(ctx, 1, event.String("method", "GET"))
// ... handle request ...
// Record duration
processingTime.Record(ctx, time.Since(start))
}import (
"context"
"golang.org/x/exp/event"
"golang.org/x/exp/event/keys"
)
var (
userIDKey keys.Int64 = "user_id"
emailKey keys.String = "email"
)
func processUser(ctx context.Context, userID int64, email string) {
event.Log(ctx, "processing user",
userIDKey.Of(userID),
emailKey.Of(email),
)
}import (
"context"
"golang.org/x/exp/event"
"golang.org/x/exp/event/severity"
)
func logWithSeverity(ctx context.Context) {
severity.Info.Log(ctx, "normal operation")
severity.Warning.Log(ctx, "potential issue detected")
severity.Error.Logf(ctx, "failed to process item: %v", someError)
}import (
"context"
"log"
"golang.org/x/exp/event"
"golang.org/x/exp/event/adapter/logfmt"
)
func main() {
// Create a simple handler that prints to stdout
handler := logfmt.NewHandler(os.Stdout)
// Create an exporter with the handler
exporter := event.NewExporter(handler, &event.ExporterOptions{
EnableNamespaces: true,
})
// Set as default exporter
event.SetDefaultExporter(exporter)
// Or use with context
ctx := context.Background()
ctx = event.WithExporter(ctx, exporter)
event.Log(ctx, "hello world")
}