zoobzio December 12, 2025 Edit this page

Quickstart

Installation

go get github.com/zoobz-io/aperture

Requires Go 1.24+ and capitan.

Minimal Setup

package main

import (
    "context"
    "time"

    "github.com/zoobz-io/aperture"
    "github.com/zoobz-io/capitan"
    "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
    "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
    "go.opentelemetry.io/otel/sdk/log"
    "go.opentelemetry.io/otel/sdk/metric"
    "go.opentelemetry.io/otel/sdk/trace"
)

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

    // 1. Create OTEL providers
    logExporter, _ := otlploghttp.New(ctx, otlploghttp.WithEndpoint("localhost:4318"), otlploghttp.WithInsecure())
    logProvider := log.NewLoggerProvider(log.WithProcessor(log.NewBatchProcessor(logExporter)))
    defer logProvider.Shutdown(ctx)

    metricExporter, _ := otlpmetrichttp.New(ctx, otlpmetrichttp.WithEndpoint("localhost:4318"), otlpmetrichttp.WithInsecure())
    meterProvider := metric.NewMeterProvider(metric.WithReader(
        metric.NewPeriodicReader(metricExporter, metric.WithInterval(60*time.Second)),
    ))
    defer meterProvider.Shutdown(ctx)

    traceExporter, _ := otlptracehttp.New(ctx, otlptracehttp.WithEndpoint("localhost:4318"), otlptracehttp.WithInsecure())
    traceProvider := trace.NewTracerProvider(trace.WithSpanProcessor(trace.NewBatchSpanProcessor(traceExporter)))
    defer traceProvider.Shutdown(ctx)

    // 2. Create aperture (no config = log all events)
    cap := capitan.Default()
    ap, err := aperture.New(cap, logProvider, meterProvider, traceProvider)
    if err != nil {
        panic(err)
    }
    defer ap.Close()

    // 3. Emit events - they automatically become OTEL logs
    sig := capitan.NewSignal("app.started", "Application started")
    cap.Emit(ctx, sig)

    // 4. Graceful shutdown
    cap.Shutdown()
}

What's Happening

  1. OTEL providers - You configure how telemetry reaches your backend (exporters, batching, endpoints)
  2. Aperture bridge - Registers as a capitan observer, receives all events
  3. Automatic transformation - Events become OTEL logs with signal name and fields as attributes
  4. Provider shutdown - Your responsibility to flush and close providers

The key insight: aperture handles event-to-signal transformation, you handle OTEL configuration.

With Configuration

Add metrics and trace correlation:

// Define signals
orderCreated := capitan.NewSignal("order.created", "Order created")
orderCompleted := capitan.NewSignal("order.completed", "Order completed")
orderID := capitan.NewStringKey("order_id")
total := capitan.NewFloat64Key("total")

// Create aperture
ap, _ := aperture.New(cap, logProvider, meterProvider, traceProvider)
defer ap.Close()

// Configure transformations with schema
schema := aperture.Schema{
    // Count order creations
    Metrics: []aperture.MetricSchema{
        {
            Signal: "order.created",
            Name:   "orders_created_total",
            Type:   "counter",
        },
    },

    // Correlate order start/end into spans
    Traces: []aperture.TraceSchema{
        {
            Start:          "order.created",
            End:            "order.completed",
            CorrelationKey: "order_id",
            SpanName:       "order_processing",
        },
    },

    // Only log order events
    Logs: &aperture.LogSchema{
        Whitelist: []string{"order.created", "order.completed"},
    },
}
ap.Apply(schema)

// Emit order flow
cap.Emit(ctx, orderCreated, orderID.Field("ORD-123"), total.Field(99.99))
// ^ Counter incremented, log recorded, span started

time.Sleep(100 * time.Millisecond)

cap.Emit(ctx, orderCompleted, orderID.Field("ORD-123"))
// ^ Log recorded, span completed

Using OTEL Directly

Aperture exposes standard OTEL interfaces:

// Get OTEL primitives
logger := ap.Logger("orders")
meter := ap.Meter("orders")
tracer := ap.Tracer("orders")

// Create custom metrics
counter, _ := meter.Int64Counter("custom_counter")
counter.Add(ctx, 1)

// Create custom spans
ctx, span := tracer.Start(ctx, "custom-operation")
defer span.End()

Next Steps