shotel

package module
v0.0.4 Latest Latest
Warning

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

Go to latest
Published: Oct 22, 2025 License: MIT Imports: 21 Imported by: 0

README

shotel

CI Status codecov Go Report Card CodeQL Go Reference License Go Version Release

OpenTelemetry bridge for Go observability libraries - connects metricz, tracez, hookz, and slog to OTLP collectors.

Why shotel?

Your application is instrumented with lightweight observability libraries (metricz, tracez, hookz), but you need to send that data to an OTLP collector (Jaeger, Prometheus, etc.). Shotel bridges the gap with zero reflection and minimal overhead.

What it does:

  • Translates metricz metrics → OTLP metrics
  • Converts tracez spans → OTLP traces
  • Bridges hookz events → OTLP logs
  • Integrates slog → OTLP logs

What it doesn't do:

  • Replace your instrumentation libraries
  • Force you to use OpenTelemetry APIs everywhere
  • Add complexity to your application code

Installation

go get github.com/zoobzio/shotel

Requirements: Go 1.24+

Quick Start

package main

import (
    "context"
    "log/slog"

    "github.com/zoobzio/rocco"
    "github.com/zoobzio/shotel"
)

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

    // Create shotel instance
    sh, err := shotel.New(ctx, &shotel.Config{
        ServiceName: "my-service",
        Endpoint:    "localhost:4317",
        Insecure:    true,
    })
    if err != nil {
        panic(err)
    }
    defer sh.Shutdown(ctx)

    // Create your instrumented service (rocco example)
    engine := rocco.NewEngine(rocco.DefaultConfig())

    // Wire up observability
    sh.ObserveMetrics(engine,
        rocco.MetricRequestsReceived,
        rocco.MetricRequestDuration,
    )
    sh.ObserveTraces(engine)

    // Hook up logs
    logHandler := sh.CreateLogHandler()
    engine.OnRequestReceived(logHandler)
    engine.OnRequestCompleted(logHandler)

    // Or use slog globally
    sh.SetGlobalSlogHandler()
    slog.Info("service started", "port", 8080)

    engine.Start()
}

Configuration

cfg := &shotel.Config{
    ServiceName:     "my-service",      // Service identifier in telemetry
    Endpoint:        "localhost:4317",  // OTLP collector endpoint
    MetricsInterval: 10 * time.Second,  // How often to poll metrics
    Insecure:        true,              // Disable TLS (dev only)
}

Default configuration:

cfg := shotel.DefaultConfig("my-service")

Metrics Bridge

Shotel polls metrics from any component exposing a Metrics() *metricz.Registry method.

// Observable interface
type Observable interface {
    Metrics() *metricz.Registry
}

// Example with pipz
pipeline := pipz.NewSequence("order-processing", ...)
sh.ObserveMetrics(pipeline,
    pipz.ProcessorCallsTotal,
    pipz.ProcessorErrorsTotal,
)

// Example with rocco
engine := rocco.NewEngine(...)
sh.ObserveMetrics(engine,
    rocco.MetricRequestsReceived,
    rocco.MetricRequestDuration,
)

Supported metric types:

  • Counters → OTLP Int64Counter (delta calculation)
  • Gauges → OTLP Float64ObservableGauge (pull-based)
  • Histograms → OTLP Float64Histogram
  • Timers → OTLP Float64Histogram (milliseconds)

Key reuse: The same key can be used for multiple metric types (counter + gauge) without conflict.

Traces Bridge

Shotel registers handlers on any component exposing a Tracer() *tracez.Tracer method.

// Traceable interface
type Traceable interface {
    Tracer() *tracez.Tracer
}

// Example with rocco
engine := rocco.NewEngine(...)
sh.ObserveTraces(engine)

// All spans automatically flow to OTLP
// - Span names, timestamps, tags preserved
// - Parent-child relationships maintained
// - TraceIDs and SpanIDs tracked

Logs Bridge

Shotel provides factory methods for log integration - you control the registration.

hookz Integration
// Create handler
logHandler := sh.CreateLogHandler()

// Register with your hookz events
engine.OnRequestReceived(logHandler)
engine.OnRequestCompleted(logHandler)
engine.OnRequestRejected(logHandler)

// Events flow: hookz → shotel → OTLP logs
slog Integration
// Option 1: Explicit logger
logger := slog.New(sh.CreateSlogHandler())
logger.Info("message", "key", "value")

// Option 2: Global default
sh.SetGlobalSlogHandler()
slog.Info("message", "key", "value")  // All slog calls → OTLP

// Option 3: With attributes
handler := sh.CreateSlogHandler()
logger := slog.New(handler.WithAttrs([]slog.Attr{
    slog.String("service", "api"),
    slog.Int("version", 1),
}))

// Option 4: With groups
handler := sh.CreateSlogHandler()
logger := slog.New(handler.WithGroup("request"))
logger.Info("received", "method", "GET", "path", "/api/users")
// Produces: request.method=GET, request.path=/api/users

Supported slog types:

  • String, Int64, Uint64, Float64, Bool
  • Duration (as nanoseconds)
  • Time (RFC3339Nano format)

Log levels: Debug, Info, Warn, Error → OTLP severity mapping

Real-World Example

Complete integration with rocco web framework:

package main

import (
    "context"
    "log/slog"
    "time"

    "github.com/zoobzio/rocco"
    "github.com/zoobzio/shotel"
)

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

    // Configure shotel
    sh, err := shotel.New(ctx, &shotel.Config{
        ServiceName:     "rosetta-api",
        Endpoint:        "localhost:4317",
        MetricsInterval: 10 * time.Second,
        Insecure:        true,
    })
    if err != nil {
        panic(err)
    }
    defer sh.Shutdown(context.Background())

    // Create rocco engine (has Metrics(), Tracer(), Hooks())
    engine := rocco.NewEngine(rocco.DefaultConfig())

    // Bridge metrics
    sh.ObserveMetrics(engine,
        rocco.MetricRequestsReceived,
        rocco.MetricRequestsCompleted,
        rocco.MetricRequestsRejected,
        rocco.MetricRequestDuration,
    )

    // Bridge traces
    sh.ObserveTraces(engine)

    // Bridge hookz events to logs
    logHandler := sh.CreateLogHandler()
    engine.OnRequestReceived(logHandler)
    engine.OnRequestCompleted(logHandler)
    engine.OnRequestRejected(logHandler)

    // Use slog for structured logging → OTLP
    sh.SetGlobalSlogHandler()

    // Register handlers
    engine.Register(handlers.NewUsersHandler())

    // Start server
    slog.Info("server starting", "port", 8080)
    engine.Start()
}

Result: All metrics, traces, and logs flow to your OTLP collector (Jaeger, Prometheus, etc.)

Architecture

Application
    ├─ metricz.Registry ──┐
    ├─ tracez.Tracer ─────┤
    ├─ hookz.Hooks ───────┼──> Shotel ──> OTLP Collector ──> Jaeger/Prometheus
    └─ slog.Logger ───────┘                                   Loki/etc.

No reflection, no type assertions - everything is compile-time safe.

Interfaces

Shotel works with any component implementing these interfaces:

// For metrics observation
type Observable interface {
    Metrics() *metricz.Registry
}

// For trace observation
type Traceable interface {
    Tracer() *tracez.Tracer
}

// For logs - user-controlled registration
logHandler := shotel.CreateLogHandler()
slogHandler := shotel.CreateSlogHandler()

Performance

  • Metrics: Polled at configured interval (default 10s)
  • Traces: Zero overhead when no handlers registered
  • Logs: Direct translation, no buffering
  • Zero reflection: All operations are type-safe
  • Minimal allocations: Optimized for production use

Shutdown

Always shutdown gracefully to flush pending data:

defer func() {
    shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    sh.Shutdown(shutdownCtx)
}()

Dependencies

  • github.com/zoobzio/metricz - Metrics primitives
  • github.com/zoobzio/tracez - Tracing primitives
  • go.opentelemetry.io/otel/* - OTLP exporters

Compatible Libraries

Shotel works with any library exposing the Observable/Traceable interfaces:

  • pipz - Data pipelines with built-in metrics/tracing
  • rocco - HTTP framework with observability
  • hookz - Event hooks
  • Custom components implementing the interfaces

License

MIT License - see LICENSE file for details.

Contributing

Contributions welcome! Please see CONTRIBUTING.md for guidelines.

Security

See SECURITY.md for security policy and vulnerability reporting.

Documentation

Overview

Package shotel provides an OpenTelemetry bridge for Go observability libraries.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	// ServiceName identifies this service in telemetry data
	ServiceName string

	// Endpoint is the OTLP collector endpoint (default: localhost:4317)
	Endpoint string

	// MetricsInterval is how often to poll and export metrics (default: 10s)
	MetricsInterval time.Duration

	// Insecure disables TLS for the OTLP connection (default: false)
	Insecure bool
}

Config holds configuration for the OpenTelemetry bridge.

func DefaultConfig

func DefaultConfig(serviceName string) *Config

DefaultConfig returns a Config with sensible defaults.

type Exporter

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

Exporter manages OTLP exporters for metrics, traces, and logs.

func NewExporter

func NewExporter(ctx context.Context, cfg *Config) (*Exporter, error)

NewExporter creates OTLP exporters for metrics, traces, and logs. If cfg.Endpoint is empty, creates in-memory exporters suitable for testing.

func (*Exporter) LogProvider

func (e *Exporter) LogProvider() *sdklog.LoggerProvider

LogProvider returns the log provider for log record creation.

func (*Exporter) MetricReader

func (e *Exporter) MetricReader() metric.Reader

MetricReader returns the metric reader for integration with MeterProvider.

func (*Exporter) Shutdown

func (e *Exporter) Shutdown(ctx context.Context) error

Shutdown gracefully shuts down all exporters.

func (*Exporter) TraceProvider

func (e *Exporter) TraceProvider() *sdktrace.TracerProvider

TraceProvider returns the trace provider for span creation.

type Observable

type Observable interface {
	Metrics() *metricz.Registry
}

Observable defines components that expose metrics.

type Shotel

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

Shotel manages the bridge between application observables and OTLP.

func New

func New(ctx context.Context, cfg *Config) (*Shotel, error)

New creates a new Shotel instance.

func (*Shotel) CreateLogHandler

func (s *Shotel) CreateLogHandler() func(context.Context, any) error

CreateLogHandler returns a hook handler function that sends events to OTLP logs. This handler can be registered with hookz events or any context-aware event system.

Usage with hookz:

handler := shotel.CreateLogHandler()
engine.OnRequestReceived(handler)
engine.OnRequestCompleted(handler)

func (*Shotel) CreateSlogHandler

func (s *Shotel) CreateSlogHandler() slog.Handler

CreateSlogHandler returns an slog.Handler that sends logs to OTLP. This allows standard structured logging to be exported via OTLP.

Usage:

logger := slog.New(shotel.CreateSlogHandler())
logger.Info("request received", "method", "GET", "path", "/api/users")

func (*Shotel) ObserveMetrics

func (s *Shotel) ObserveMetrics(observable Observable, keys ...metricz.Key)

ObserveMetrics registers an observable and starts polling specified metric keys.

func (*Shotel) ObserveTraces

func (s *Shotel) ObserveTraces(traceable Traceable)

ObserveTraces registers a traceable and bridges its spans to OTLP.

func (*Shotel) SetGlobalOtelLogger

func (s *Shotel) SetGlobalOtelLogger()

SetGlobalOtelLogger sets the global OTLP logger provider. This allows other OTLP-aware libraries to use the same log provider.

func (*Shotel) SetGlobalSlogHandler

func (s *Shotel) SetGlobalSlogHandler()

SetGlobalSlogHandler sets the global slog default handler to use OTLP. This redirects all slog.Info/Warn/Error calls to OTLP automatically.

func (*Shotel) Shutdown

func (s *Shotel) Shutdown(ctx context.Context) error

Shutdown gracefully stops all observations and shuts down the exporter.

type Traceable

type Traceable interface {
	Tracer() *tracez.Tracer
}

Traceable defines components that expose a tracer.

Jump to

Keyboard shortcuts

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