Documentation
¶
Overview ¶
Package ion provides production-grade logging, tracing, and metrics for Go services.
Ion unifies structured logging (Zap), distributed tracing, and metrics (OpenTelemetry) behind a minimal, context-first API. It is designed for long-running services and distributed systems where trace correlation, failure isolation, and zero-downtime observability are critical.
Guarantees ¶
These invariants hold in all configurations, including under backend failure:
- Process Safety: Ion never terminates the process (no os.Exit, no panic). Even [Logger.Critical] (mapped to FATAL severity) returns control to the caller.
- Concurrency: All Logger, Tracer, and Meter APIs are safe for concurrent use.
- Failure Isolation: Telemetry backend failures (collector down, disk full) never crash application logic. Data may be lost, but the service continues.
- Lifecycle: Ion.Shutdown flushes all buffers on a best-effort basis within the provided context deadline.
Initialization ¶
Create an Ion instance with New. Use Default for production defaults or Development for local development (pretty output, debug level):
app, warnings, err := ion.New(ion.Default().WithService("my-service"))
if err != nil {
log.Fatal(err)
}
for _, w := range warnings {
log.Printf("ion warning: %v", w)
}
defer app.Shutdown(context.Background())
New always returns a working instance when err is nil. Non-fatal issues (e.g., OTEL connection failure) are reported as Warning values — the service continues with degraded telemetry rather than failing to start.
Context-First Logging ¶
All log methods require context.Context as the first parameter. Ion automatically extracts trace_id and span_id from the active OTEL span in the context and injects them as structured fields. This ensures every log entry is correlated to its trace without any manual plumbing:
app.Info(ctx, "order processed", ion.String("order_id", "abc"), ion.Duration("latency", elapsed))
Use typed field constructors (String, Int, Int64, Uint64, Float64, Bool, Duration, Err) for zero-allocation structured logging. Use F for arbitrary types.
The Logger Interface ¶
The Logger interface defines the logging contract: [Logger.Debug], [Logger.Info], [Logger.Warn], [Logger.Error], and [Logger.Critical]. Accept Logger at package boundaries for dependency injection. Use *Ion internally when a component needs tracing or metrics.
Scoped Children ¶
Use Ion.Child to create scoped observability instances for application components. Each child preserves full access to logging, tracing, and metrics:
http := app.Child("http")
http.Info(ctx, "request received")
tracer := http.Tracer("http.handler")
meter := http.Meter("http.metrics")
Ion.Named and Ion.With also preserve observability (the concrete type behind the Logger interface is *Ion), but Ion.Child returns *Ion directly — no type assertion required. Use Ion.Child when your component needs tracing or metrics.
Tracing and Metrics ¶
Access distributed tracing via Ion.Tracer and metrics via Ion.Meter:
tracer := app.Tracer("order.processor")
ctx, span := tracer.Start(ctx, "ProcessOrder")
defer span.End()
meter := app.Meter("order.metrics")
counter, _ := meter.Int64Counter("orders_processed_total")
counter.Add(ctx, 1)
If tracing or metrics are not enabled in the configuration, these methods return no-op implementations that silently discard data — no nil checks needed.
Ion provides status constants (StatusOK, StatusError, StatusUnset) so that callers do not need to import OpenTelemetry codes directly:
span.SetStatus(ion.StatusError, "validation failed")
Configuration ¶
Ion uses a comprehensive Config struct. Start with Default or Development, then customize with builder methods:
cfg := ion.Default().
WithService("payment-api").
WithOTEL("otel-collector:4317").
WithTracing(""). // inherits OTEL endpoint
WithMetrics("") // inherits OTEL endpoint
Tracing, Metrics, and OTEL Logs inherit endpoint, protocol, and auth from the OTEL config when their own values are empty — configure once, reuse everywhere.
Context Helpers ¶
Inject custom identifiers into context for automatic inclusion in logs:
ctx = ion.WithRequestID(ctx, "req-123") ctx = ion.WithUserID(ctx, "user-42") app.Info(ctx, "processing") // logs include request_id="req-123", user_id="user-42"
Extract values with RequestIDFromContext, UserIDFromContext, TraceIDFromContext.
Lifecycle ¶
Ion.Shutdown must be called before process exit to flush buffered traces, metrics, and OTEL logs. Always defer it in main with a timeout:
defer func() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
app.Shutdown(ctx)
}()
Child instances share the parent's tracer and meter providers. Only shut down the root Ion instance — shutting down a child tears down shared providers.
Sub-Packages ¶
The fields sub-package (github.com/JupiterMetaLabs/ion/fields) provides domain-specific field constructors for blockchain applications: TxHash, BlockHeight, ShardID, Slot, Epoch, Validator, and more.
The middleware sub-packages provide automatic context propagation for HTTP (github.com/JupiterMetaLabs/ion/middleware/ionhttp) and gRPC (github.com/JupiterMetaLabs/ion/middleware/iongrpc).
Index ¶
- func New(cfg Config) (*Ion, []Warning, error)
- func RequestIDFromContext(ctx context.Context) string
- func TraceIDFromContext(ctx context.Context) string
- func UserIDFromContext(ctx context.Context) string
- func WithRequestID(ctx context.Context, requestID string) context.Context
- func WithTraceID(ctx context.Context, traceID string) context.Context
- func WithUserID(ctx context.Context, userID string) context.Context
- type Attr
- type AttrKey
- type Config
- type ConsoleConfig
- type Field
- func Bool(key string, value bool) Field
- func Duration(key string, value time.Duration) Field
- func Err(err error) Field
- func F(key string, value any) Field
- func Float64(key string, value float64) Field
- func Int(key string, value int) Field
- func Int64(key string, value int64) Field
- func String(key, value string) Field
- func Uint64(key string, value uint64) Field
- type FieldType
- type FileConfig
- type Ion
- func (i *Ion) Child(name string, fields ...Field) *Ion
- func (l Ion) Critical(ctx context.Context, msg string, err error, fields ...Field)
- func (l Ion) Debug(ctx context.Context, msg string, fields ...Field)
- func (l Ion) Error(ctx context.Context, msg string, err error, fields ...Field)
- func (l Ion) GetLevel() string
- func (l Ion) Info(ctx context.Context, msg string, fields ...Field)
- func (i *Ion) Meter(name string, opts ...metric.MeterOption) metric.Meter
- func (i *Ion) Named(name string) Logger
- func (l Ion) SetLevel(level string)
- func (i *Ion) Shutdown(ctx context.Context) error
- func (l Ion) Sync() error
- func (i *Ion) Tracer(name string) Tracer
- func (l Ion) Warn(ctx context.Context, msg string, fields ...Field)
- func (i *Ion) With(fields ...Field) Logger
- type Link
- type Logger
- type MetricsConfig
- type OTELConfig
- type Span
- type SpanOption
- type StatusCode
- type Tracer
- type TracingConfig
- type Warning
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func New ¶
New creates a new Ion instance with the given configuration. This is the single entry point for creating ion observability.
Returns:
- *Ion: Always returns a working Ion instance (may use fallbacks)
- []Warning: Non-fatal issues (e.g., OTEL connection failed, tracing disabled)
- error: Fatal configuration errors
func RequestIDFromContext ¶
RequestIDFromContext extracts the request ID from context.
func TraceIDFromContext ¶ added in v0.4.0
TraceIDFromContext extracts the trace ID from context. It first checks for an active OTEL span; if none, falls back to a manually set trace ID. Returns an empty string if no trace ID is available.
func UserIDFromContext ¶
UserIDFromContext extracts the user ID from context.
func WithRequestID ¶
WithRequestID adds a request ID to the context. This ID will be automatically included in logs.
func WithTraceID ¶
WithTraceID adds a trace ID to the context for non-OTEL scenarios. When OTEL tracing is active, trace IDs are extracted automatically from the span context; use this only when you need manual trace correlation without an OTEL span. This ID will be automatically included in logs as the "trace_id" field.
Types ¶
type Attr ¶ added in v0.3.1
Attr is a key-value pair used for trace span attributes and metric dimensions. This is an alias for the OpenTelemetry attribute.KeyValue type.
Create attributes using the standard OTel constructors:
import "go.opentelemetry.io/otel/attribute"
span.SetAttributes(
attribute.String("order.id", orderID),
attribute.Int64("retry.count", 3),
)
Or for metrics:
counter.Add(ctx, 1, metric.WithAttributes(
attribute.String("shard_id", "3"),
))
Note: Ion intentionally does NOT wrap attribute constructors. This ensures users learn the standard OpenTelemetry API, which is an industry skill.
type AttrKey ¶ added in v0.3.1
AttrKey is a type alias for attribute keys. Use attribute.Key("mykey").String("value") for advanced patterns.
type Config ¶
Config holds the complete logger configuration. It is an alias to internal/config.Config to allow sharing with internal packages.
func Development ¶
func Development() Config
Development returns a Config optimized for development.
type ConsoleConfig ¶
type ConsoleConfig = config.ConsoleConfig
ConsoleConfig configures console output.
type Field ¶
type Field struct {
Key string
Type FieldType
Integer int64
StringVal string
Float float64
Interface any
}
Field represents a structured logging field (key-value pair). Field construction is zero-allocation for primitive types (String, Int, etc).
func Duration ¶ added in v0.4.0
Duration creates a duration field. The value is encoded as a string using Go's standard duration formatting (e.g., "1.5s", "250ms").
type FieldType ¶
type FieldType uint8
FieldType identifies the encoding strategy for a Field value. Each type maps to a zero-allocation zap encoder where possible.
const ( // UnknownType is the zero value; fields with this type are encoded via reflection. UnknownType FieldType = iota // StringType encodes a string value. StringType // Int64Type encodes a signed 64-bit integer. Int64Type // Uint64Type encodes an unsigned 64-bit integer (stored in Interface field). Uint64Type // Float64Type encodes a 64-bit floating-point number. Float64Type // BoolType encodes a boolean (stored as 0 or 1 in the Integer field). BoolType // ErrorType encodes an error value. ErrorType // AnyType encodes an arbitrary value via reflection. Use sparingly in hot paths. AnyType )
type Ion ¶ added in v0.2.0
type Ion struct {
// contains filtered or unexported fields
}
Ion is the unified observability instance providing structured logging, distributed tracing, and metrics collection.
Ion implements the Logger interface, so it can be used anywhere a Logger is expected. It also provides access to Tracer and [Meter] for complete observability.
Child instances created via Ion.Named, Ion.With, or Ion.Child preserve full observability capabilities, including access to Tracer and Meter. The Ion.Child method is recommended for components that need tracing or metrics, as it returns *Ion directly without requiring a type assertion.
Example:
app, warnings, err := ion.New(cfg)
if err != nil {
log.Fatal(err)
}
defer app.Shutdown(context.Background())
// Logging
app.Info(ctx, "message", ion.F("key", "value"))
// Scoped child with full observability
http := app.Child("http")
http.Info(ctx, "request received")
tracer := http.Tracer("http.handler")
ctx, span := tracer.Start(ctx, "HandleRequest")
defer span.End()
// Metrics
meter := http.Meter("http.metrics")
counter, _ := meter.Int64Counter("http.requests.total")
counter.Add(ctx, 1)
func (*Ion) Child ¶ added in v0.4.0
Child returns a named child Ion instance, optionally with additional fields. This is the recommended way to create scoped observability for application components because the return type is *Ion, giving direct access to Tracer, Meter, and Shutdown without a type assertion.
Example:
http := app.Child("http", ion.String("version", "v2"))
http.Info(ctx, "request received")
tracer := http.Tracer("http.handler")
meter := http.Meter("http.metrics")
Child instances share the parent's tracer and meter providers. Calling Shutdown on a child will shut down shared providers, affecting the parent and all siblings. In most applications, only the root Ion instance should be shut down.
func (Ion) Critical ¶ added in v0.2.0
Critical logs a message at fatal level but does NOT exit the process.
func (Ion) GetLevel ¶ added in v0.2.0
func (l Ion) GetLevel() string
GetLevel returns the current log level as a lowercase string (e.g., "info", "debug").
func (*Ion) Meter ¶ added in v0.3.0
Meter returns a named meter for creating metric instruments (counters, histograms, etc.). If metrics are not enabled, returns a no-op meter that silently discards all recordings.
func (*Ion) Named ¶ added in v0.2.0
Named returns a child Ion instance with a named sub-logger. The name appears in logs as the "logger" field (e.g., "http", "grpc").
Unlike calling Named on a bare Logger, the returned value preserves access to Tracer, Meter, and full Shutdown orchestration because the concrete type behind the Logger interface is *Ion.
To get the *Ion directly without a type assertion, use Ion.Child instead.
func (Ion) SetLevel ¶ added in v0.2.0
func (l Ion) SetLevel(level string)
SetLevel changes the log level at runtime. Valid levels: debug, info, warn, error, fatal. Invalid level strings are silently ignored; the current level remains unchanged. This change propagates to all child loggers that share the same atomic level.
func (*Ion) Shutdown ¶ added in v0.2.0
Shutdown gracefully shuts down all observability subsystems in order: tracing provider, metrics provider, then the logging backend (including OTEL log export).
The provided context controls the shutdown deadline. Returns the first error encountered, but always attempts to shut down all subsystems.
Important: Child instances created via Ion.Child, Ion.Named, or Ion.With share the parent's tracer and meter providers. Calling Shutdown on a child tears down shared providers, affecting the parent and all siblings. In most applications, only the root Ion instance should be shut down.
func (Ion) Sync ¶ added in v0.2.0
func (l Ion) Sync() error
Sync flushes any buffered log entries to the underlying writers.
func (*Ion) Tracer ¶ added in v0.2.0
Tracer returns a named tracer for creating spans. If tracing is not enabled, returns a no-op tracer (logs warning once).
func (*Ion) With ¶ added in v0.2.0
With returns a child Ion instance with additional fields attached to every log entry.
Unlike calling With on a bare Logger, the returned value preserves access to Tracer, Meter, and full Shutdown orchestration because the concrete type behind the Logger interface is *Ion.
To get the *Ion directly without a type assertion, use Ion.Child instead.
type Link ¶ added in v0.2.3
Link is an alias for trace.Link to avoid importing otel/trace.
func LinkFromContext ¶ added in v0.2.3
LinkFromContext extracts a link from the current context to connect spans.
type Logger ¶
type Logger interface {
// Debug logs a message at debug level.
Debug(ctx context.Context, msg string, fields ...Field)
// Info logs a message at info level.
Info(ctx context.Context, msg string, fields ...Field)
// Warn logs a message at warn level.
Warn(ctx context.Context, msg string, fields ...Field)
// Error logs a message at error level with an error.
Error(ctx context.Context, msg string, err error, fields ...Field)
// Critical logs a message at the highest severity level but does NOT exit.
//
// Critical logs are emitted at FATAL level to ensure visibility in backends,
// but the process is GUARANTEED not to exit.
//
// Usage pattern:
// logger.Critical(ctx, "unrecoverable error", err)
// return err // Let caller decide how to handle
Critical(ctx context.Context, msg string, err error, fields ...Field)
// With returns a child logger with additional fields attached.
// Fields are included in all subsequent log entries.
//
// When called on an [Ion] instance, the returned Logger preserves access to
// tracing and metrics (the concrete type is *Ion). For direct *Ion access
// without a type assertion, use [Ion.Child] instead.
With(fields ...Field) Logger
// Named returns a named sub-logger.
// The name appears in logs as the "logger" field.
//
// When called on an [Ion] instance, the returned Logger preserves access to
// tracing and metrics (the concrete type is *Ion). For direct *Ion access
// without a type assertion, use [Ion.Child] instead.
Named(name string) Logger
// Sync flushes any buffered log entries.
// Applications should call Sync before exiting.
Sync() error
// Shutdown gracefully shuts down the logger, flushing any buffered logs
// and closing background resources (like OTEL exporters).
Shutdown(ctx context.Context) error
// SetLevel changes the log level at runtime.
// Valid levels: debug, info, warn, error, fatal.
SetLevel(level string)
// GetLevel returns the current log level as a string.
GetLevel() string
}
Logger is the primary logging interface. All methods are safe for concurrent use. All log methods require a context.Context as the first parameter for trace correlation.
Example ¶
ctx := context.Background()
// 1. Initialize the logger
logger := newZapLogger(Development())
defer func() { _ = logger.Sync() }()
// 2. Log a simple message (context-first)
logger.Info(ctx, "Hello, World!")
// 3. Log with structured fields
logger.Info(ctx, "User logged in",
F("user_id", 42),
F("ip", "192.168.1.1"),
)
Example (ContextIntegration) ¶
// Initialize logger
logger := newZapLogger(Default())
defer func() { _ = logger.Sync() }()
// Create a context (in a real app, this comes from the request)
ctx := context.Background()
ctx = WithRequestID(ctx, "req-123")
// Context is ALWAYS the first parameter
// Trace IDs are extracted automatically
logger.Info(ctx, "Processing request")
type MetricsConfig ¶ added in v0.3.0
type MetricsConfig = config.MetricsConfig
MetricsConfig configures OpenTelemetry metrics export.
type Span ¶ added in v0.2.0
type Span interface {
// End marks the span as complete.
End()
// SetStatus sets the span status.
// Use [StatusOK], [StatusError], or [StatusUnset].
SetStatus(code StatusCode, description string)
// RecordError records an error as an event.
RecordError(err error)
// SetAttributes sets attributes on the span.
// Use attribute.String(), attribute.Int64(), etc. to create Attr values.
SetAttributes(attrs ...Attr)
// AddEvent adds an event to the span.
// Use attribute.String(), attribute.Int64(), etc. to create Attr values.
AddEvent(name string, attrs ...Attr)
}
Span represents a unit of work in a trace.
type SpanOption ¶ added in v0.2.0
type SpanOption interface {
// contains filtered or unexported methods
}
SpanOption configures span creation.
func WithAttributes ¶ added in v0.2.0
func WithAttributes(attrs ...Attr) SpanOption
WithAttributes adds attributes to the span. Use attribute.String(), attribute.Int64(), etc. to create Attr values.
func WithLinks ¶ added in v0.2.2
func WithLinks(links ...trace.Link) SpanOption
WithLinks adds links to the span.
func WithOTELOptions ¶ added in v0.2.2
func WithOTELOptions(opts ...trace.SpanStartOption) SpanOption
WithOTELOptions allows passing raw OpenTelemetry options directly. This is an escape hatch for advanced features not yet wrapped by Ion.
func WithSpanKind ¶ added in v0.2.0
func WithSpanKind(kind trace.SpanKind) SpanOption
WithSpanKind sets the span kind (client, server, etc).
type StatusCode ¶ added in v0.4.0
StatusCode is an alias for codes.Code, used with [Span.SetStatus]. Ion exports status constants so callers do not need to import "go.opentelemetry.io/otel/codes" directly.
const ( // StatusUnset is the default span status. Most spans end with this. StatusUnset StatusCode = codes.Unset // StatusError indicates the operation contained an error. // Use with [Span.RecordError] for full error reporting. StatusError StatusCode = codes.Error // StatusOK explicitly marks the operation as successful. // Only set this when you need to override a default or parent status. StatusOK StatusCode = codes.Ok )
type Tracer ¶ added in v0.2.0
type Tracer interface {
// Start creates a new span.
Start(ctx context.Context, spanName string, opts ...SpanOption) (context.Context, Span)
}
Tracer creates spans for distributed tracing.
type TracingConfig ¶ added in v0.2.0
type TracingConfig = config.TracingConfig
TracingConfig configures distributed tracing.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
basic
command
|
|
|
benchmark
command
Package main provides a comprehensive benchmark suite for ion logging library.
|
Package main provides a comprehensive benchmark suite for ion logging library. |
|
otel-test
command
Package main tests ion trace correlation with OTEL and Jaeger.
|
Package main tests ion trace correlation with OTEL and Jaeger. |
|
Package fields provides blockchain-specific logging field helpers.
|
Package fields provides blockchain-specific logging field helpers. |
|
internal
|
|
|
core
Package core provides the internal implementation of Ion's logging and tracing.
|
Package core provides the internal implementation of Ion's logging and tracing. |
|
middleware
|
|
|
iongrpc
Package grpc provides gRPC server and client instrumentation using OpenTelemetry.
|
Package grpc provides gRPC server and client instrumentation using OpenTelemetry. |
|
ionhttp
Package http provides HTTP server and client instrumentation using OpenTelemetry.
|
Package http provides HTTP server and client instrumentation using OpenTelemetry. |