ion

package module
v0.4.1 Latest Latest
Warning

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

Go to latest
Published: Mar 20, 2026 License: MIT Imports: 15 Imported by: 0

README

Ion

Ion is an enterprise-grade observability client for Go services. It unifies structured logging (Zap), distributed tracing, and metrics (OpenTelemetry) into a single, cohesive API designed for high-throughput, long-running infrastructure.

Status: v0.3 — Pre-release (API stable, targeting v1.0) Target: Microservices, Blockchain Nodes, Distributed Systems


Guarantees & Design Invariants

Ion is built on strict operational guarantees. Operators can rely on these invariants in production:

  1. No Process Termination: Ion will never call os.Exit, panic, or log.Fatal. Even Critical level logs are strictly informational (mapped to FATAL severity) and guarantee control flow returns to the caller.
  2. Thread Safety: All public APIs on Logger, Tracer, and Meter are safe for concurrent use by multiple goroutines.
  3. Non-Blocking Telemetry: Trace and metrics export is asynchronous and decoupled from application logic. A slow OTEL collector will never block your business logic. Logs are synchronous to properly handle crash reporting, but rely on high-performance buffered writes.
  4. Failure Isolation: Telemetry backend failures (e.g., Collector down) are isolated. They may result in data loss (dropped spans) but will never crash the service.

Non-Goals

To maintain focus and stability, Ion explicitly avoids:

  • Alerting: Ion emits signals; it does not manage thresholds or paging.
  • Framework Magic: Ion does not auto-inject into HTTP handlers without explicit middleware usage.

Installation

go get github.com/JupiterMetaLabs/ion

Requires Go 1.24+.


Quick Start

A minimal, correct example for a production service.

package main

import (
    "context"
    "log"
    "time"

    "github.com/JupiterMetaLabs/ion"
)

func main() {
    ctx := context.Background()

    // 1. Initialize with Service Identity
    app, warnings, err := ion.New(ion.Default().WithService("payment-node"))
    if err != nil {
        log.Fatalf("Fatal: failed to init observability: %v", err)
    }
    for _, w := range warnings {
        log.Printf("Ion Startup Warning: %v", w)
    }

    // 2. Establish the Lifecycle Contract — flush before exit
    defer func() {
        shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
        defer cancel()
        if err := app.Shutdown(shutdownCtx); err != nil {
            log.Printf("Shutdown data loss: %v", err)
        }
    }()

    // 3. Application Logic
    app.Info(ctx, "node started", ion.String("version", "1.0.0"))
    doWork(ctx, app)
}

func doWork(ctx context.Context, logger ion.Logger) {
    logger.Info(ctx, "processing block", ion.Uint64("height", 100))
}

Scoped Observability

Create scoped children that retain full access to logging, tracing, and metrics. This is the recommended pattern for structuring observability in multi-component applications.

app, _, _ := ion.New(cfg)

// Child() returns *Ion with full capabilities
http := app.Child("http")
http.Info(ctx, "request received")

// Full tracing access — no type assertion needed
tracer := http.Tracer("http.handler")
ctx, span := tracer.Start(ctx, "HandleRequest")
defer span.End()

// Full metrics access
meter := http.Meter("http.metrics")
counter, _ := meter.Int64Counter("http.requests.total")
counter.Add(ctx, 1)

Named() and With() also preserve observability (the concrete type behind the Logger interface is *Ion), but Child() returns *Ion directly — no type assertion required. Use Child() when your component needs tracing or metrics. Use Named()/With() when passing through the Logger interface (e.g., across package boundaries).


Developer Reference

Core Types
Type Description
*Ion Root observability instance. Provides Logger + Tracer() + Meter() + Shutdown().
Logger Interface for structured logging. All methods require context.Context.
Tracer Interface for creating spans. Obtained via Ion.Tracer(name).
Span Represents a unit of work. Call End() when done.
Field Structured key-value pair for log entries. Zero-allocation for primitives.
Config Complete configuration struct. Use Default() or Development() as starting points.
Creating Children
Method Returns Use When
Child(name, fields...) *Ion Component needs logging + tracing + metrics. Recommended.
Named(name) Logger Passing through Logger interface; only logging needed at the call site.
With(fields...) Logger Attaching permanent fields; only logging needed at the call site.

All three preserve full observability. The difference is the return type — Child() gives you *Ion directly, while Named()/With() return Logger (backed by *Ion internally).

Field Constructors

Ion provides typed field constructors for zero-allocation structured logging:

Constructor Type Example
ion.String(key, val) string ion.String("user", "alice")
ion.Int(key, val) int ion.Int("port", 8080)
ion.Int64(key, val) int64 ion.Int64("offset", 1024)
ion.Uint64(key, val) uint64 ion.Uint64("block_height", 19500000)
ion.Float64(key, val) float64 ion.Float64("latency_ms", 12.5)
ion.Bool(key, val) bool ion.Bool("success", true)
ion.Duration(key, val) time.Duration ion.Duration("elapsed", 50*time.Millisecond)
ion.Err(err) error ion.Err(err) (key is always "error")
ion.F(key, val) any ion.F("data", myStruct) — auto-detects type

For blockchain-specific fields, see fields package.

Context Helpers

Ion extracts trace correlation from context.Context automatically. You can also inject custom identifiers:

Function Description
ion.WithRequestID(ctx, id) Adds request_id to all logs from this context.
ion.WithUserID(ctx, id) Adds user_id to all logs from this context.
ion.WithTraceID(ctx, id) Manual trace ID for non-OTEL scenarios.
ion.TraceIDFromContext(ctx) Extracts trace ID (OTEL span or manual).
ion.RequestIDFromContext(ctx) Extracts request ID.
ion.UserIDFromContext(ctx) Extracts user ID.
Log Levels
Level Method Behavior
debug Debug(ctx, msg, fields...) Verbose development info. Disabled in production by default.
info Info(ctx, msg, fields...) Operational state changes. Default minimum level.
warn Warn(ctx, msg, fields...) Recoverable issues. Routed to stderr when ErrorsToStderr is true.
error Error(ctx, msg, err, fields...) Actionable failures. Accepts an error parameter.
fatal Critical(ctx, msg, err, fields...) Highest severity. Does NOT exit. Safe for libraries.

Levels can be changed at runtime via SetLevel("debug"). Changes propagate to all children sharing the same atomic level.


API Overview

1. The Logger

Use the Logger for human-readable events, state changes, and errors. Always pass context.Context — even context.Background() — to maintain the API contract and enable future trace correlation.

// INFO: Operational state changes
app.Info(ctx, "transaction processed",
    ion.String("tx_id", "0x123"),
    ion.Duration("latency", 50*time.Millisecond),
)

// ERROR: Actionable failures. Does not interrupt flow.
if err != nil {
    app.Error(ctx, "database connection failed", err, ion.String("db_host", "primary"))
}

// CRITICAL: Invariant violations (e.g. data corruption).
// GUARANTEE: Does NOT call os.Exit(). Safe to use in libraries.
app.Critical(ctx, "memory corruption detected", nil)
2. Child Loggers (Scopes)

Use Child, Named, and With to create context-aware sub-loggers. Prefer Child() when the component needs tracing or metrics.

// Child: Full observability — logging, tracing, metrics
http := app.Child("http", ion.String("version", "v2"))
tracer := http.Tracer("http.handler")
meter := http.Meter("http.metrics")

// Named: Logger interface — good for cross-package boundaries
httpLog := app.Named("http")   // {"logger": "app.http", ...}
grpcLog := app.Named("grpc")   // {"logger": "app.grpc", ...}

// With: Permanent fields on all subsequent logs
userLogger := app.With(
    ion.Int("user_id", 42),
    ion.String("tenant", "acme-corp"),
)
userLogger.Info(ctx, "action taken")  // {"user_id": 42, "tenant": "acme-corp", ...}
3. The Tracer

Use the Tracer for latency measurement and causal chains. Every Start must have a corresponding End().

func ProcessOrder(ctx context.Context, orderID string) error {
    tracer := app.Tracer("order.processor")
    ctx, span := tracer.Start(ctx, "ProcessOrder")
    defer span.End()

    span.SetAttributes(attribute.String("order.id", orderID))

    if err := validate(ctx); err != nil {
        span.RecordError(err)
        span.SetStatus(ion.StatusError, "validation failed")
        return err
    }
    return nil
}

Ion provides StatusOK, StatusError, and StatusUnset constants so you don't need to import go.opentelemetry.io/otel/codes. For span attributes, ion.Attr is an alias for attribute.KeyValue — import go.opentelemetry.io/otel/attribute to create attribute values.

4. The Meter

Use Meter for operational metrics (counters, histograms, gauges).

meter := app.Meter("http.metrics")
requestCounter, _ := meter.Int64Counter("http_requests_total")
latencyHist, _ := meter.Float64Histogram("http_request_duration_seconds")

requestCounter.Add(ctx, 1)
latencyHist.Record(ctx, 0.025) // 25ms
5. Blockchain Fields

The fields sub-package provides domain-specific constructors with consistent key naming:

import "github.com/JupiterMetaLabs/ion/fields"

app.Info(ctx, "transaction routed",
    fields.TxHash("0xabc123..."),
    fields.ShardID(3),
    fields.Slot(150_000_000),
    fields.Epoch(350),
    fields.BlockHeight(19_500_000),
    fields.LatencyMs(12.5),
)

Categories: Transaction (TxHash, TxType, Nonce, GasUsed, ...), Block & Consensus (BlockHeight, Slot, Epoch, Validator, ...), Network (ChainID, PeerID, NodeID, ...), and Metrics (Count, Size, LatencyMs, ...).


Configuration Reference

Ion uses a comprehensive configuration struct for behavior control. This maps 1:1 with ion.Config.

Root Configuration (ion.Config)
Field Type Default Description
Level string "info" Minimum log level (debug, info, warn, error, fatal).
Development bool false Enables development mode (pretty output, caller location, stack traces).
ServiceName string "unknown" Identity of the service (vital for trace attribution).
Version string "" Service version (e.g., commit hash or semver).
Console ConsoleConfig Enabled: true Configuration for stdout/stderr.
File FileConfig Enabled: false Configuration for file logging (with rotation).
OTEL OTELConfig Enabled: false Configuration for remote OpenTelemetry logging.
Tracing TracingConfig Enabled: false Configuration for distributed tracing.
Metrics MetricsConfig Enabled: false Configuration for OpenTelemetry metrics.
Console Configuration (ion.ConsoleConfig)
Field Type Default Description
Enabled bool true If false, stdout/stderr is silenced.
Format string "json" "json", "pretty", or "systemd" (optimized for Journald).
Color bool true Enables ANSI colors (only applies to pretty format).
ErrorsToStderr bool true Writes warn/error/fatal to stderr, others to stdout.
Level string "" Optional override for console log level. Inherits global level if empty.
File Configuration (ion.FileConfig)
Field Type Default Description
Enabled bool false Enables file writing.
Path string "" Absolute path to the log file (e.g., /var/log/app.log).
MaxSizeMB int 100 Max size per file before rotation.
MaxBackups int 5 Number of old files to keep.
MaxAgeDays int 7 Max age of files to keep.
Compress bool true Gzip old log files.
Level string "" Optional override for file log level. Inherits global level if empty.
OTEL Configuration (ion.OTELConfig)

Controls the OpenTelemetry Logs Exporter. Tracing and Metrics inherit Endpoint, Protocol, and auth fields from OTEL when their own values are empty.

Field Type Default Description
Enabled bool false Enables log export to Collector.
Endpoint string "" host:port or URL. URL schemes override Insecure setting.
Protocol string "grpc" "grpc" (recommended) or "http".
Insecure bool false Disables TLS (dev only). Ignored if Endpoint starts with https://.
Username string "" Basic Auth username.
Password string "" Basic Auth password.
BatchSize int 512 Max logs per export batch.
ExportInterval Duration 5s Flush interval.
Level string "" Optional override for OTEL log level.
Tracing Configuration (ion.TracingConfig)

Controls the OpenTelemetry Trace Provider. Empty fields inherit from OTELConfig.

Field Type Default Description
Enabled bool false Enables trace generation and export.
Endpoint string "" host:port. Inherits OTEL.Endpoint if empty.
Sampler string "ratio:0.1" "always", "never", or "ratio:0.X". Development mode uses "always".
Protocol string "grpc" Inherits OTEL.Protocol if empty.
Username string "" Inherits OTEL.Username if empty.
Password string "" Inherits OTEL.Password if empty.
Metrics Configuration (ion.MetricsConfig)

Controls the OpenTelemetry Metrics Provider (OTLP Push). Empty fields inherit from OTELConfig.

Field Type Default Description
Enabled bool false Enables metrics export.
Endpoint string "" host:port. Inherits OTEL.Endpoint if empty.
Interval Duration 15s Push interval. Development mode uses 5s.
Temporality string "cumulative" "cumulative" (Prometheus-compatible) or "delta".
Protocol string "grpc" Inherits OTEL.Protocol if empty.
Username string "" Inherits OTEL.Username if empty.
Password string "" Inherits OTEL.Password if empty.
Config Builders

For quick setup, use the fluent builder methods:

// Production: Console + OTEL + Tracing on a shared collector
cfg := ion.Default().
    WithService("order-service").
    WithOTEL("otel-collector:4317").
    WithTracing("").  // inherits OTEL endpoint
    WithMetrics("")   // inherits OTEL endpoint

// Development: Pretty console, debug level, sample all traces
cfg := ion.Development().WithService("order-service")

For full control, initialize the struct directly. See examples/basic/main.go Example 6 for a complete production configuration.


Initialization Recipes

Console Only (CLI / Scripts)
cfg := ion.Default()
cfg.Level = "info"
// cfg.Development = true  // Optional: enables caller info + stack traces
Console + File (VM / Bare Metal)
cfg := ion.Default()
cfg.File.Enabled = true
cfg.File.Path = "/var/log/app/app.log"
cfg.File.MaxSizeMB = 500
cfg.File.MaxBackups = 5
OTEL Only (High-Traffic Sidecars)
cfg := ion.Default()
cfg.Console.Enabled = false
cfg.OTEL.Enabled = true
cfg.OTEL.Endpoint = "localhost:4317"
cfg.OTEL.Protocol = "grpc"
Systemd Native (Journald)
cfg := ion.Default()
cfg.Console.Format = "systemd"
cfg.Console.ErrorsToStderr = true
Full Stack (Kubernetes)
cfg := ion.Default()
cfg.Console.Enabled = true
cfg.OTEL.Enabled = true
cfg.OTEL.Endpoint = "otel-collector:4317"
cfg.Tracing.Enabled = true
cfg.Tracing.Sampler = "ratio:0.1"
cfg.Metrics.Enabled = true

Tracing Guide

Distributed tracing requires discipline. For a complete step-by-step guide to span creation, error handling, background goroutines, and best practices, see the Tracing Quickstart.


HTTP & gRPC Integration

Ion provides middleware and interceptors for automatic context propagation.

HTTP Middleware
import "github.com/JupiterMetaLabs/ion/middleware/ionhttp"

mux := http.NewServeMux()
handler := ionhttp.Handler(mux, "payment-api")
http.ListenAndServe(":8080", handler)
gRPC Interceptors
import "github.com/JupiterMetaLabs/ion/middleware/iongrpc"

// Server
s := grpc.NewServer(grpc.StatsHandler(iongrpc.ServerHandler()))

// Client
conn, _ := grpc.Dial(addr, grpc.WithStatsHandler(iongrpc.ClientHandler()))

Examples

The examples/ directory contains runnable demonstrations:

Example File What It Shows
Simple Usage examples/basic/main.go (Example 1) Minimal setup with Development() config.
Dependency Injection examples/basic/main.go (Example 2) Child() pattern for component observability.
Child Loggers examples/basic/main.go (Example 3) Named() and With() for scoped logging.
Metrics examples/basic/main.go (Example 4) Counter and histogram instrumentation.
Blockchain Fields examples/basic/main.go (Example 5) Domain-specific field helpers.
Production Setup examples/basic/main.go (Example 6) Full config with tracing, file rotation, graceful shutdown.
OTEL + Jaeger examples/otel-test/main.go End-to-end trace correlation with Docker Compose.
Benchmarks examples/benchmark/main.go Performance measurement suite.

Best Practices

Context propagation. Always pass context.Context to log methods. Using context.Background() breaks the trace chain — reserve it for main() and background worker roots.

Shutdown is mandatory. Failing to call Shutdown() guarantees data loss for buffered traces, metrics, and OTEL logs. Always defer shutdown in main() with a timeout context.

Use typed fields. Prefer ion.String("key", val) over ion.F("key", val). Typed constructors are zero-allocation and catch type errors at compile time.

Use static keys. ion.String(userInput, "value") is a security risk and breaks log indexing. Keys must be compile-time constants.

Use consistent key naming. Stick to snake_case (e.g., user_id, not userID or uid). The fields package enforces this for blockchain-specific keys.

Prefer Child() for components. When a struct needs logging, tracing, and metrics, accept *ion.Ion and create children with Child(). Use the Logger interface at package boundaries where only logging is needed.

Only shut down the root. Child instances share providers with the parent. Calling Shutdown() on a child tears down shared resources. Shut down only the root *Ion returned by New().

Handle warnings. New() returns warnings for non-fatal issues (e.g., OTEL connection failure). Log these at startup so operators know when telemetry is degraded.


Operational Model

How Ion Works
  • Logs: Emitted synchronously to configured cores (Console/File/OTEL). If your application crashes immediately after a log statement, the log is persisted (up to OS buffering).
  • Traces: Buffered and exported asynchronously. Spans are batched in memory and sent to the OTEL endpoint on a timer or size threshold.
  • Metrics: Pushed asynchronously via OTLP at the configured interval (default 15s).
  • Correlation: trace_id and span_id are extracted from context.Context at the moment of logging.
Production Failure Modes
  • OTEL Collector Down: Exporter retries with exponential backoff. If buffers fill, new spans/logs are dropped. Application performance is preserved.
  • Disk Full (File Logging): Lumberjack rotation attempts to write. If the syscall fails, the application continues but file logs are lost.
  • High Load: Tracing and metrics use bounded buffers. Under extreme load, excess data is dropped to prevent memory leaks.

Further Reading


Versioning

  • Public API: ion.go, logger.go, config.go, fields.go, tracer.go, attrs.go, context.go. Stable since v0.3.
  • Internal: internal/*. No stability guarantees.
  • Behavior: Log format changes or configuration defaults are considered breaking changes.

License

MIT © 2025 JupiterMeta Labs

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

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(cfg Config) (*Ion, []Warning, error)

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

func RequestIDFromContext(ctx context.Context) string

RequestIDFromContext extracts the request ID from context.

func TraceIDFromContext added in v0.4.0

func TraceIDFromContext(ctx context.Context) string

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

func UserIDFromContext(ctx context.Context) string

UserIDFromContext extracts the user ID from context.

func WithRequestID

func WithRequestID(ctx context.Context, requestID string) context.Context

WithRequestID adds a request ID to the context. This ID will be automatically included in logs.

func WithTraceID

func WithTraceID(ctx context.Context, traceID string) context.Context

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.

func WithUserID

func WithUserID(ctx context.Context, userID string) context.Context

WithUserID adds a user ID to the context. This ID will be automatically included in logs as the "user_id" field.

Types

type Attr added in v0.3.1

type Attr = attribute.KeyValue

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

type AttrKey = attribute.Key

AttrKey is a type alias for attribute keys. Use attribute.Key("mykey").String("value") for advanced patterns.

type Config

type Config = config.Config

Config holds the complete logger configuration. It is an alias to internal/config.Config to allow sharing with internal packages.

func Default

func Default() Config

Default returns a Config with sensible production defaults.

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 Bool

func Bool(key string, value bool) Field

Bool creates a boolean field.

func Duration added in v0.4.0

func Duration(key string, value time.Duration) Field

Duration creates a duration field. The value is encoded as a string using Go's standard duration formatting (e.g., "1.5s", "250ms").

func Err

func Err(err error) Field

Err creates an error field with the standard key "error".

func F

func F(key string, value any) Field

F is a convenience constructor for Field. It detects the type and creates the appropriate Field.

func Float64

func Float64(key string, value float64) Field

Float64 creates a float64 field.

func Int

func Int(key string, value int) Field

Int creates an integer field.

func Int64

func Int64(key string, value int64) Field

Int64 creates an int64 field.

func String

func String(key, value string) Field

String creates a string field.

func Uint64 added in v0.2.0

func Uint64(key string, value uint64) Field

Uint64 creates a uint64 field without truncation. Use this for large unsigned values (e.g., block heights, slots).

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 FileConfig

type FileConfig = config.FileConfig

FileConfig configures file output.

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

func (i *Ion) Child(name string, fields ...Field) *Ion

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

func (l Ion) Critical(ctx context.Context, msg string, err error, fields ...Field)

Critical logs a message at fatal level but does NOT exit the process.

func (Ion) Debug added in v0.2.0

func (l Ion) Debug(ctx context.Context, msg string, fields ...Field)

Debug logs a message at debug level.

func (Ion) Error added in v0.2.0

func (l Ion) Error(ctx context.Context, msg string, err error, fields ...Field)

Error logs a message at error level with an optional error.

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) Info added in v0.2.0

func (l Ion) Info(ctx context.Context, msg string, fields ...Field)

Info logs a message at info level.

func (*Ion) Meter added in v0.3.0

func (i *Ion) Meter(name string, opts ...metric.MeterOption) metric.Meter

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

func (i *Ion) Named(name string) Logger

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

func (i *Ion) Shutdown(ctx context.Context) error

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

func (i *Ion) Tracer(name string) Tracer

Tracer returns a named tracer for creating spans. If tracing is not enabled, returns a no-op tracer (logs warning once).

func (Ion) Warn added in v0.2.0

func (l Ion) Warn(ctx context.Context, msg string, fields ...Field)

Warn logs a message at warn level.

func (*Ion) With added in v0.2.0

func (i *Ion) With(fields ...Field) Logger

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 = trace.Link

Link is an alias for trace.Link to avoid importing otel/trace.

func LinkFromContext added in v0.2.3

func LinkFromContext(ctx context.Context) Link

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 OTELConfig

type OTELConfig = config.OTELConfig

OTELConfig configures OTEL log 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(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

type StatusCode = codes.Code

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.

type Warning added in v0.2.0

type Warning struct {
	Component string // "otel", "tracing"
	Err       error
}

Warning represents a non-fatal initialization issue. Ion returns warnings instead of failing when optional components (like OTEL or tracing) cannot be initialized.

func (Warning) Error added in v0.2.0

func (w Warning) Error() string

Error implements the error interface, formatting the warning as "component: message".

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.

Jump to

Keyboard shortcuts

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